Brilliant Breakthroughs and Impossible Projects
-
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!
-
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.
-
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.
-
@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.
-
@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?
-
@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.
-
@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.
-
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.
-
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
andhandle
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
andat_post_cmd
-methods in turn. Of these, onlyparse
andfunc
are implemented with any regularity (the pre/post methods are needed by those wanting to plug in custom stuff without changing default command functionality).
Theparse
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 onlyfunc
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 -
@Bobotron New BBS sounds cool
@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.)
-
@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 callinglog
,crack
,error-check
(aborting if anycheck_xxx
method returns an error), andhandle
. Most handlers implement crack, handle, and at least one check, but there are exceptions. "who" just hashandle
. Some overridelog
for privacy so we don't log things like pages and poses. -
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 andargs
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 -
@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. -
@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 -
@skew said in Brilliant Breakthroughs and Impossible Projects:
Has anyone hooked the basic pose/say commands?
Yes. That's how "posebreak" works.
-
@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.
-
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 whilesession
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 -
@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.
-
@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