|
Larkin Wizard
Joined: 25 Mar 2003 Posts: 1113 Location: USA
|
Posted: Tue Dec 13, 2005 2:49 pm
Open and optimized interface across packages |
I develop many scripts for zMUD that I then give to others, and I would like to describe a situation where I ran into some trouble and how I think it can be solved in CMUD.
As many people know, combat in the Iron Realms games is extremely complex. We require very intricate sets of scripts to track afflictions, defenses, balances, etc, and execute the proper set of commands to keep ourselves healthy and deffed up. The system I use for Achaea is provided free to those that want to download it and install it (open source), but it has to be general enough to work for anyone who wants to try it out. This raises a few unique problems that most combat systems don't ever have to consider.
The main issue is that I would like to provide a generic interface, via a set of hooks, into the system that can then be customized outside of the core script. The open source scripts are upgraded as bugs are found and features added, so users need to upgrade to pull in these new features (or at least to do so easily). They don't want to lose any changes they make to the scripts, so this limits what they can do to customize the system for their character's abilities and fighting tactics. Some settings that remain constant, such as toggleable flags or health/mana values, are in another class folder so they aren't deleted in an upgrade, but not every customization is that simple.
The interface issue is also an optimization one, as I could have one trigger execute multiple aliases (which can be toggled on or off, based on a character's abilities or status) rather than have everyone trying to parse that same trigger. Correct me if I'm wrong here, but I'm guessing that alias matching is faster and more efficient than trigger matching.
Part of this comes from the period of time that I used MUSHclient to write plugins. MUSHclient makes use of regular expression aliases, which I found immensely useful, and also allows for explicit ordering of execution via an optional priority number and a flag to continue evaluation. The evaluation flag has a double use, where it allows you to execute more than one trigger or alias on a single appearance (or call) and also allows you to "abort" processing of further aliases or triggers on a match by enabling a trigger or alias with a high priority and the evaluation flag off, effectively stopping other matches that would have come later. (I hope that makes sense.) I found both of these to be very useful in various situations.
Different people want to use different commands to achieve the same results. For example, a monk has the ability to just make himself deaf or blind with one of his skill sets, where as most others will eat an herb to do either. The way zMUD is now, it's difficult to have an alias that can be overridden with that alias being part of the core functionality of the system and also a customizeable setting. (It's a bad example, but all I have off the top of my head without going over my scripts with a fine-toothed comb.)
There are a few weaknesses in the #ONINPUT trigger, which I've documented in a development journal thing I wrote up while writing my scripts. The primary problem is multiple matches on the same pattern and conflicts in gagging text or setting/reading variables. The order of execution isn't clear enough, either, and having them execute based on sort order isn't the best way to go, especially for a system that is given to hundreds of other people. (I'd have to document the quirk and tell person after person that it will only run properly if they do blah blah to sort their triggers for execution ordering.)
Sorry for the rambling, and I'd like to point out that this isn't a rant. I'm just trying to suggest features. I'll have more later, I'm sure. Thanks. |
|
|
|
Larkin Wizard
Joined: 25 Mar 2003 Posts: 1113 Location: USA
|
Posted: Tue Dec 13, 2005 3:25 pm |
I know I didn't give very good, concrete examples, so let me just give one here.
I've got an alias that deals with marking afflictions. Right now, it's basically doing more jobs than I want it to do, and it probably should even be doing more. I'd like to have multiple calls to allow me (or a user) to specify actions for various afflictions. For example, some are more critical and should display a large warning message, and some will prevent certain types of curing. The simple example will show how I print out afflictions received and how I delete them from the map.
Code: |
#ALIAS acp_afflicted {
#if (@Settings.print_affs) {
#forall "%-1" {
#if (!%iskey(@Afflictions, %i)) {
acp_printaff %i
}
}
}
#addkey Afflictions "%-1" 1
#addkey Flags heal 1
}
#ALIAS acp_cured {
#forall "%-1" {
#if (%iskey(@Afflictions, %i)) {
acp_printcure %i
} {
#if (@Afflictions.unknown) {
#delkey Afflictions unknown
acp_printcure unknown
}
}
#delkey Afflictions {%i}
#if (%iskey(@PipeCures, %i)) {acp_smokecure}
}
#if (@Afflictions) {#addkey Flags heal 1}
#if (@Auto.Defense) {#addkey Flags def 1}
#if (@Flags.ate_herb) {#addkey Flags herb_cured 1} {
#if (@Flags.smoking) {#addkey Flags pipe_cured 1} {
#if (@Flags.focused) {#addkey Flags focus_cured 1} {
#if (@Flags.touched_tree) {#addkey Flags tree_cured 1}
}
}
}
}
|
The acp_afflicted alias wants to display afflictions as you receive them (one per line, but the parameter can be a string list, for efficiency) and it's toggleable with a flag right now. What I'd rather see (regex just off the top of my head, so don't take it literally):
Code: |
#REGEXALIAS "acp_printaff" {^\s*acp_afflicted\s+([\a\|]+?)\s*$} {
#forall "%1" {
#if (!%iskey(@Afflictions, %i)) {
#say Afflicted -- %i
}
}
}
#REGEXALIAS "acp_trackaff" {^\s*acp_afflicted\s+([\a\|]+?)\s*$} {
#addkey Afflictions {%1} 1
#addkey Flags heal 1
}
|
I could then disable the acp_printaff alias to turn off the messages and wouldn't have to keep checking the flag. In fact, this alias could be in a totally separate class folder. This is the type of customization that users could do for themselves by "hooking" into the commands executed by the system to add extra processing on any alias-like regular expression.
The acp_cured alias illustrates my point even better, actually. You can see there's lots more than just printing a message and deleting the keys. I needed failsafes for when a cure was attempted and no cure followed, so when a cure is successful I set a flag to indicate this to the failsafes. For example, eat herb, cure paralysis, prompt, and this is when the failsafe doesn't need to do anything. However, eat herb, prompt, and no cure in between, I need to reset all afflictions that would have been cured by eating that herb. The failsafes in acp_cured are a kludge, and I'd like to see a better way of doing it, similar to the regex alias example above, where each failsafe could hook into the cures and set its own flag. The hooks could then be enabled only when the failsafe is looking for a cure.
Hope this helps more! |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Tue Dec 13, 2005 5:28 pm |
Thanks for starting this thread. Concrete examples like this are exactly what I need to improve CMUD.
A couple of things in CMUD will already help you with this:
1) CMUD has a clear way to set a numeric priority order for each type of setting. For triggers, this priority is the execution order. A list of currently enabled triggers is created, and sorted by priority. CMUD then loops through that list to test each trigger pattern in order.
The issue of aborting further trigger processing is tricky. You never know if the player has other plugins or triggers that might need the text. So to allow one plugin to abort the rest of the trigger processing for a particular line isn't usually a good idea. Within a single package, it's better to set flags, like you are already doing, to determine if "downstream" triggers should fire. Or enable/disable other triggers in the list as needed to determine which ones are still active. But I probably won't allow one plugin to abort the trigger processing for other plugins.
If it is useful, I can imagine having a flag that aborts further processing for a given line of text within the package. In other words, you could stop other triggers in your package from firing on a line, but other packages would still be able to trigger on it.
2) Something that is even more useful is the ability for a package to define "settings" for itself. In CMUD you will be able to design User Interface panels using zApp ZML code to allow the player to change various preferences for the package/plugin. For example, you mentioned having a list of afflictions...imagine being able to display a set of checkboxes that the player could check or uncheck to determine how your triggers worked. Using this UI form, you can ask the player questions and have them fill in various values needed for the plugin. You can also use this within your own packages to create a different user interface for your package.
For example, imagine a package that handles following another player around on the MUD. You need to know the name of the person you are following, and you then create a trigger that checks to see when that player leaves the current room so you can follow them. Normally, you have a variable called "@Target" which is the name of the person you are following, and then you enable or disable the trigger as needed. Imagine putting this into a package and then displaying a UI panel for the package where you can enter a value into a "Target" field and then have a checkbox to enable or disable the trigger.
I don't yet know how much of this will make it into the first beta version, but this is part of the important package functionality that CMUD will have. It should really help people make useful and modular packages. |
|
|
|
Larkin Wizard
Joined: 25 Mar 2003 Posts: 1113 Location: USA
|
Posted: Tue Dec 13, 2005 6:30 pm |
Thank you for the reply. I am very glad to hear that hard-coded numeric priority will be available to me in CMUD! And, I wasn't expecting that a package would abort processing for other packages, just override its own further evaluation by enabling an "interruptor" trigger or alias.
It sounds like packages will share code and still be separated as plugins. Is there a sort of sandbox for each package with a global shared area? Or is it all just defined by the class folders, basically? One thing I dislike about the plugins in MUSHclient is how difficult it is to communicate between plugins (data can be passed in with aliases, but cannot be read back out easily). I'd like to know how CMUD shares or communicates between these packages, if it's any different from the way things are done now in zMUD.
My combat systems use wizards to allow configuration of parameters, such as class, potion sipping thresholds, defenses to keep, etc. The zMUD wizard format is painful and difficult to maintain, especially when adding an entry and having to renumber most of the items. Will CMUD have a custom wizard building capability? Can we expect something that's easier to use, as it will likely use ZML (XML schema?) to define the UI?
Regarding what I mentioned about #ONINPUT triggers, here's a link to my journal, if you're interested in reading my articles. The final code I ended up using for my system differs a bit from the last part of this article, as I found a multi-state trigger more effective than a temporary alarm, but this covers the issues.
Thanks again. |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Tue Dec 13, 2005 8:35 pm |
In many ways, a package is just another class folder. So within a package, if you refer to a variable, it looks for that variable within the package first, then starts looking in other packages. I'm still trying to pin down a more specific "search order" for this so that it's clearer than it was in zMUD.
As in zMUD, you can always specific a "full path" to a setting too. The full path allows you to access any setting in any package from anywhere. So communication between plugins should be really easy. But as in zMUD, if you create a brand new variable, it will get created within the current class of the current package, so in some sense, a package is a "sandbox" (just not very secure or protected).
As far as wizards, the trigger wizard in zMUD will probably get dumped in favor of the new packages. Nobody was really using the zMUD Trigger Wizard...at least I never saw very many posts with it. I think there is more power in the new package concept, especially if a package is able to define it's own preferences and set up it's own ZML UI screens. But as I mentioned, not all of this will be part of the first beta. |
|
|
|
JQuilici Adept
Joined: 21 Sep 2005 Posts: 250 Location: Austin, TX
|
Posted: Fri Feb 17, 2006 9:34 pm |
I was re-reading this post, and it occurred to me to wonder about the interaction of trigger priorities and enabling/disabling settings/packages/classes. If I understand the explanation above correctly, CMUD will take all of the triggers, winnow out the ones that match the current line, sort them into priority order, then execute the trigger actions in the sorted order.
Thus, let's assume we have three triggers (t1, t2, t3) that all match the current line, with priorities 10, 20, and 30. I assume the largest numerical prority fires first, so t3's actions go first, then t2's, then t1's. So far so good, but what happens if:
- t1 is enabled to start, but t3 disables it? Does t1 fire or not?
- t2 is disabled to start, but t3 enables it? Does t2 fire (because it was enabled before we 'got down' to priority 20), or not (because wasn't in the list of enabled triggers when we started)?
- t3 is disabled to start, but t2 enables it? Does t3 fire even though we've 'passed' priority 30? If so, when? Right after t2 (that enables it), or after t1 (at the end of the pass)?
Basically this boils down to the question of when the 'enabled' flag is bound to a trigger that could match the current line.
I'm not advocating any specific answers (yet), but it'd be nice to have the behavior defined, one way or the other. I have absolutely no expectation about what happens if one trigger enables/disables a second one with equal priority - if you write code like that you deserve what you get. |
|
_________________ Come visit Mozart Mud...and tell an imm that Aerith sent you! |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Sat Feb 18, 2006 12:17 am |
Actually, CMUD sorts on priority first before even matching the text. Specifically, the lower "priority" actually fires first. I.e., a priority of "1" is first priority, "2" is second priority, etc.
But if I ignore that and just look at your example:
1) if t3 goes first and disabled t1, then t1 doesn't fire.
2) if t2 is disabled but t3 fires first and enables it, then t2 fires.
3) if t3 is disabled but t2 enables it, then t3 doesn't fire because once CMUD is working on t2, it's already past t3 in the priority list.
The specific logic that is currently implemented is:
1) the trigger list is always sorted by priority order.
2) when a line is received, start looping through the trigger list and if a trigger is enabled, then test it against the current line, and if it matches, immediately execute it. If the trigger is disabled, then it is skipped for this line.
I agree that any code written to depend upon this would be pretty silly. And actually this works in exactly the same way as zMUD currently works except that the priority is given a numeric value and is more visible. In zMUD, when you view the trigger list Unsorted then you see the exact order that zMUD is testing the triggers, and this is the same as the "priority sorted" list in CMUD. |
|
|
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|