Brilliant Breakthroughs and Impossible Projects


  • Coder

    I thought this may be a nice go-to topic for any aspiring MUSH developers or those of us who have been old timers and know our way around the block.

    What I would like is for this to be an open ended discussion and point/counter-point topic where everyone can bring up any challenging code snippit, project, or solution that they have worked on, are currently working on, or plan to work on in the future.

    Some of us have been doing this for well over 20 years, some have likely only have 20 hours. Let's combine all our knowledge and experience and help develop a gestalt learning and discussion forum that we can hopefully improve on all of our practices.

    Also, please try to keep heated debates about 'what is best' out of the picture. There'll always be dozens of right and wrong ways to do something. This should be more on how to better to something, how to solve issues we may have thought impossible, and to cross the bridge of knowledge and experience.

    Also, please feel free to provide multiple ways to do things. Since there are various MUSH platforms, each with their own unique feature sets, saying 'this is how I would do it on PennMUSH' then a follow up of 'This is how I would do it on RhostMUSH' or 'TinyMUSH' or 'MUX2' or Evennia or anything else would be greatly appreciated.

    Not only will this give alternatives, but it also will highlight the varying differences between the codebases as well as the differences in how we all approach a problem and work to a solution.

    So, with nothing else to say, let the code question and answers commence!


  • Coder

    So I'm not sure if this is the right topic but it seems to kinda fit so I figured I'd try. Especially interested in @Thenomain and @Lithium's thoughts since we were talking about making the next-gen servers easier to learn.

    Ares plugins have Command Handlers - each responsible for a single command. You can think of them as the equivalent of &CMD-FOO attributes from Penn/Tiny/Rhost land.

    Now the easiest and most straightforward thing would be to have command handlers implement a single method to do everything.

    Side note: Ares uses Ruby, but what I have below is more psuedocode showing the general idea for simplicity. I want to focus on the overall flow, not the actual code.

    class PageCmd
       def on_command(client, cmd)
           # Figure out the parameters, like *=*
           recipients = cmd.args.arg1
           message = cmd.args.arg2
           # Do error checks
           if (a recipient is not valid)
             client.emit "Don't know who you're trying to page."
             return # do not continue
           end
           # Emit the page to everyone
           recipients.each { emit page to recipient }
           client.emit page message
    

    The pseudocode there is pretty short, but in reality it would be a bit longer.

    Now, in the current version of Ares I tried to make the coder's life easier by breaking this process up into three distinct steps with three distinct Ruby methods: crack parses the parameters, checks do the error checking and abort if there's a problem, handle executes the command if there are no problems.

    class PageCmd
      # Define member variables for your command parameters
      # so they're available throughout the class
      attr_accessor :recipients, :message
    
      def crack
         # Figure out the parameters, like *=*
         recipients = cmd.args.arg1
         message = cmd.args.arg2
      end
    
      # This is an error-check method, because it starts with check_
      # If it returns nil, all is well.  If it returns a message, that message gets
      # emitted to the client and the command is aborted.
      # You can have other check methods too checking different things
      def check_recipients
         if (a recipient is not valid)
             return "Don't know who you're trying to page."
         end
         return nil  # All is well
      end 
    
      # All the error checks have passed, do the thing.
      def handle
          # Emit the page to everyone
          recipients.each { emit page to recipient }
          client.emit page message
      end
    

    I personally like the second example better. I think each step is broken out clearly. There's less repetition with the error check because the if (error) emit return stuff is bundled up in the check method handling.

    BUT is it too much for new devs to absorb? Functions that get auto-executed based on their names? Member variables? The emit/return stuff happening behind the scenes? I worry that in making it cleaner, it's actually made it harder to learn.

    Thoughts?



  • I'll bite, I suppose.

    I'm working on a new BBS for MUSH, coded in such a way as to be compatible on Penn, Rhost, and MUX (but primarily written for Penn). This BBS will be use Myrddin's baseline features, but expand out from there with reply threading. This is meant to allow for discussion below a specific topic (you could reply to bboard 3/3). The BBS will also feature familiar locks from Myrddin's, as well as individual per-post locks. This came about as a theory bit because of my extreme hatred of AnomalyJobs, and having a threaded BBS, where all commands (except for wrappers like +apply and such that are shortcuts) seems like it could work as an alternative.

    So far it's working nicely, though I'm not completely finished yet.


  • Coder

    @faraday

    I like the quasi throw/catch, but what order would these fire in? In my code methodology, I often use things like: if (x = target->thing) which I realize is horrible code but you get the idea. This may be an absolute Mush-ism, but sometimes you do state changes while checking validity.

    The answer could be "don't do that", which IMO is fine.


  • Coder

    @Thenomain You can do more than one check per error-checker method if you need to sequence things. It's a tool not a religion :)

          def check_can_set_actor
            return nil if self.name == enactor_name
            return nil if Actors.can_set_actor?(enactor)
            return "You're not allowed to do that."
          end
    

    Technically they are also run in sorted alphabetical order, but I think it's getting a bit too wacky if you rely on that. The only time that gets used is for some of the built-in checks like 'is logged in', which you can include with a single line of code:

    include CommandRequiresLogin

    You want to make sure they're logged in before you start checking for things like character permissions and whatnot, so it's important that it runs first.

    Edit to add -- Oh, I think I missed what you were saying about state changes. If you need to do some funky state-based stuff, you should probably put the error check inside of the handle method. Otherwise the state change wouldn't be persisted. The check methods are just a shortcut for simple atomic things that can short-circuit the processing early on.


  • Coder

    @faraday said in Brilliant Breakthroughs and Impossible Projects:

    It's a tool not a religion

    That's totally opposite the usual mood about code bases; you really are tired of this hobby, aren't you?

    ;)


  • Coder

    @Thenomain said in Brilliant Breakthroughs and Impossible Projects:

    That's totally opposite the usual mood about code bases; you really are tired of this hobby, aren't you?

    Seriously, the number of code holy wars encountered in this hobby are but a pale shadow of the number encountered in my day to day job. But yes, so sick of all of them. :)


  • Coder

    @Bobotron said in Brilliant Breakthroughs and Impossible Projects:

    I'll bite, I suppose.

    I'm working on a new BBS for MUSH, coded in such a way as to be compatible on Penn, Rhost, and MUX (but primarily written for Penn). This BBS will be use Myrddin's baseline features, but expand out from there with reply threading. This is meant to allow for discussion below a specific topic (you could reply to bboard 3/3). The BBS will also feature familiar locks from Myrddin's, as well as individual per-post locks. This came about as a theory bit because of my extreme hatred of AnomalyJobs, and having a threaded BBS, where all commands (except for wrappers like +apply and such that are shortcuts) seems like it could work as an alternative.

    So far it's working nicely, though I'm not completely finished yet.

    When you play in Rhost, check out packmath(). It's something where you can apply math to a compressed RADIX number.

    So like:

    think pack(12345,36)
    9IX
    think packmath(9IX,36,70000,+)
    1RJD
    think unpack(1RJD,36)
    82345
    

    It'd be a nice way to tweak your indexing without having to unpack the numbers first.



  • @Ashen-Shugar

    I'll look at that. Right now I have the method to mimic baseconv() from where you gave it to me on MUS*H, set up to make a baseconv() function for MUX and Rhost.


  • Coder

    @faraday

    So if I understand you correctly, when the correct command key (or alias?) has been identified, it jumps to the given handler class and then fires in turn the cracks, checks and handle methods?

    This sounds pretty reasonable to me, (although 'cracks' is a strange name to me - some sort of mush/ruby-ism?).
    You should not be doing this for ease-of-use only though I think; it will then only increase complexity just as you fear. The main motivation should be to encourage handler inheritance: allowing devs to implement parsing for a whole group of commands at once.

    In Evennia we do a similar thing; when a Command has been identified; we in turn call the Command's at_pre_cmd, parse, func and at_post_cmd -methods in turn. Of these, only parse and func are implemented with any regularity (the pre/post methods are needed by those wanting to plug in custom stuff without changing default command functionality).
    The parse method was implemented once for almost all our "mux-like" commands and a second time for certain more advanced admin commands. Most devs never touch it but gets the parsing for free, implementing only func for every new Command, that is the actual actions performed on the already parsed input.

    So as long as you emphasize that command handlers are classes that can be inherited, you can get rid of a lot of boiler plate for your users with parsing only needing to be coded once for big swathes of commands.
    .
    Griatch


  • Coder

    @Bobotron New BBS sounds cool :D

    @everyoneelse

    I'm terrified of hooking the general "pose" and "say" commands. I ran into this issue while doing my +poseorder code, where I wanted to also include a +repose code, but the way I wanted to do it required hooking those commands.

    So: Has anyone hooked the basic pose/say commands? Is there some big scary reason why we shouldn't be doing it?

    (I am talking TinyMUX, but I'm sure it's similar-ish enough in Rhost and Penn.)


  • Coder

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    So: Has anyone hooked the basic pose/say commands? Is there some big scary reason why we shouldn't be doing it?

    I've used it for my pose autospacer and combat pose tracking on Penn and it worked just fine. I know others have used it for pose order/repose stuff too without incident.

    @Griatch Thanks for the feedback. I think we might be talking about two different levels of argument parsing with the parse/crack methods. Your parse sounds very generic if you can use one version for all MUX-like commands. Crack is highly command-specific, because what it does is take something like: <target>=<message> or <board #>/<post #> and break it into sensibly-named variables like "target", "message", "board_num", and "post_num". There's almost zero opportunity for inheritance there because almost every command has different arguments. There are utilities for common scenarios, but that's more composition than inheritance.

    But yeah, we have a similar command flow with the base command handler's on_command method calling log, crack, error-check (aborting if any check_xxx method returns an error), and handle. Most handlers implement crack, handle, and at least one check, but there are exceptions. "who" just has handle. Some override log for privacy so we don't log things like pages and poses.


  • Coder

    @faraday

    naybe you are right and we work at different abstraction levels, yes. All Evennia hands over to the command is

    cmdnameargs
    

    Where cmdname is the key or alias of the command used and args is everything after it, with no added space.

    At least from what I learned about the base "MUX-like" command set we use in Evennia, some 80% of commands can efficiently be parsed hereon by understanding

    cmdname[/switch[/switch]...] [arg[;alias]...] [= arg[;alias...],...]
    

    Splitting that into optional easy-to use components make's it easy to do any extra fluff on a case by case basis. As said we have one more base class (inheriting from this one) that also handles commands with syntax for assigning variables etc; but that's basically all we need, and all other default commands in Evennia inherits from this.
    .
    Griatch


  • Coder

    @Griatch said in Brilliant Breakthroughs and Impossible Projects:

    Splitting that into optional easy-to use components make's it easy to do any extra fluff on a case by case basis.

    Yeah definitely different layers of abstraction. The Ares engine does similar parsing to what you're talking about. crack is for figuring out args. So you just leave that up to individual commands to figure out, splitting the string as needed? Nothing wrong with that, btw, I just codified that as a separate step.


  • Coder

    @faraday said in Brilliant Breakthroughs and Impossible Projects:

    @Griatch said in Brilliant Breakthroughs and Impossible Projects:

    Splitting that into optional easy-to use components make's it easy to do any extra fluff on a case by case basis.

    Yeah definitely different layers of abstraction. The Ares engine does similar parsing to what you're talking about. crack is for figuring out args. So you just leave that up to individual commands to figure out, splitting the string as needed? Nothing wrong with that, btw, I just codified that as a separate step.

    In principle every individual command class could exactly control it's own parsing, yes. In reality commands end up being parsed so similarly they all inherit one or two parsers with only very few requiring individual extra parsing on top of that.
    .
    Griatch


  • Coder

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    Has anyone hooked the basic pose/say commands?

    Yes. That's how "posebreak" works.


  • Coder

    @Thenomain said in Brilliant Breakthroughs and Impossible Projects:

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    Has anyone hooked the basic pose/say commands?

    Yes. That's how "posebreak" works.

    Sorry, hooked the entire command, ignored it, and added those commands to the soft code.

    Posebreak is just using before/after, not replacing.


  • Coder

    Evennia has something called inline functions which are functions provided by the game dev that an unprivileged user may embed in any return string. These functions will be evaluated anew for every recipient of the string. The system is intended for creating dynamic content and also supports nesting.

    Here is an example; a time zone converter. First a simplified example of the python function (made outside the game by the dev in a module the inlinefunc system looks for such functions):

    def mytime( timestring, timezone, session=None):
        # ... convert difference between session time zone and given time zone and return the converted time ...
        return converted_timestring
    

    Here, we ignore error-checking/validation for clarity. The first two arguments to mytime are input by the user while session is added by Evennia for every session receiving a string in question.

    A user may now embed mytime in any output string; for example ad I'm located in central Europe I could do something like this:

    page Foo = Hi, so I'll be around at $mytime(14:00, GMT+1) tomorrow.
    

    And user Foo, being on the US east coast (GMT-5 I think?) would see:

    Griatch pages: Hi, so I'll be around at 08:00 (GMT-5) tomorrow.
    

    I take it something like this is handled directly in softcode in mush bases.
    .
    Griatch


  • Coder

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    @Thenomain said in Brilliant Breakthroughs and Impossible Projects:

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    Has anyone hooked the basic pose/say commands?

    Yes. That's how "posebreak" works.

    Sorry, hooked the entire command, ignored it, and added those commands to the soft code.

    Posebreak is just using before/after, not replacing.

    I believe on Eldritch I wrote a function called 'say()' which would take the arguments 'say( <sayer>, <pose-structure> )' and do all the work of working out the spacing, which is half of what say/pose does. All tabletalk ('TT') code has to do this anyhow. The trick would then be assuring parsing is maintained which, now that I think about it, shouldn't be horrible. Input as 'noparse', just before outputting apply 'objeval( %#, <blah> )', and cross your fingers.


  • Coder

    @Thenomain said in Brilliant Breakthroughs and Impossible Projects:

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    @Thenomain said in Brilliant Breakthroughs and Impossible Projects:

    @skew said in Brilliant Breakthroughs and Impossible Projects:

    Has anyone hooked the basic pose/say commands?

    Yes. That's how "posebreak" works.

    Sorry, hooked the entire command, ignored it, and added those commands to the soft code.

    Posebreak is just using before/after, not replacing.

    I believe on Eldritch I wrote a function called 'say()' which would take the arguments 'say( <sayer>, <pose-structure> )' and do all the work of working out the spacing, which is half of what say/pose does. All tabletalk ('TT') code has to do this anyhow. The trick would then be assuring parsing is maintained which, now that I think about it, shouldn't be horrible. Input as 'noparse', just before outputting apply 'objeval( %#, <blah> )', and cross your fingers.

    What does "table talk code" mean in this context?
    .
    Griatch


Log in to reply
 

Looks like your connection to MU Soapbox was lost, please wait while we try to reconnect.