Changes:
- Tested various actions in the forest after the recent refactoring.
- Fixed
EAT
verb. - Fixed
LOOK IN
verb.
Bugs & Problems:
- Nothing new
Changes:
EAT
verb.LOOK IN
verb.Bugs & Problems:
Lots of good stuff this week.
Changelog:
Known Issues:
Next Up:
Changes:
Bugs & Problems:
I’m putting some thought into refactoring the Rules handling a little bit. I have two goals: 1) making the process of modifying/replacing action output more elegant, and 2) supporting rule chaining (allowing multiple Rules to modify the same output).
Right now a Rule returns text, along with a constant which specifies whether the Rule cancels the action, replaces the action, does nothing, or appends to the action’s output. However, I’ve already done the groundwork for Rules that happen before or after the action is handled. The idea is that I can have rules like:
The first Rule cancels the regular action processing, whereas the second merely adds to the output. This approach renders the “append” rule handling moot. Instead, I’d like to create some kind of Action object, which is created before the first Rules are processed. It would look something like:
1 | var action = { |
This object would be passed to each rule in sequence, where it might be modified (primarily the output field). It can be prepended, appended, replaced entirely, etc. The resulting object is passed to the next rule, and so on until all rules have been processed or a rule has cancelled the action.
The full flow would look like:
Note that the After rules cannot cancel the action. It’s too late. They can still prepend, append, or change the output.
Lastly, this makes me think certain rules will need broken into two rules (Before and After), which I’d like to streamline. Could expand the understand() rule builder to have before() and after() methods (effectively generating two rules in one overall block), but I don’t know if that’s really worthwhile (vs just generating two rules one after the other). Probably not.
Open questions:
Changes:
Bugs & Problems:
Yesterday I added an Internal Rule feature to the engine; it’s an extension of the Rulebook system with some interesting uses.
The Rulebook is now categorized, so every Rule belongs to a Rule Group. The current groups are:
I can add more Groups later that can be executed at different times, or based on specific types of events. This will be important both for functionality improvements and performance, since I can avoid running every rule on every tick.
Internal rules are triggered directly by the ECS, and aren’t dependent (entirely) on action execution. They can happen during non-tick executions, and can have a variety of effects. The basic idea is that I can define an internal action called (for example) getLocationName
, and then apply rules to that action as if it were a regular verb action. I can also supply a default function if no rules override it.
1 | ECS.addInternalAction('getLocationName', function(data){ |
This defines the default action for the getLocationName, which simply returns the name of the location. This is used by the Place component to print the location name during place descriptions.
Ok, so far I’ve just complicated the place descriptions. Next up is the fun part: modifying location names based on rules.
1 | understand('inside object location name rule') |
This verbosely-named Rule checks to see if the actor (usually the player) is in/on an object in the location. They could be hiding inside a box, soaking in the hot springs, etc. If so, it appends a parenthetical description to the location name, e.g. “Hot Springs (inside pools)”.
Similar internal rules can be created for object names (appending object state or changing the name entirely), controlling which objects can be seen through a window, etc. Like any other rule, internal rules can be deregistered once they’ve served their purpose.
Did a lot of refactoring today.
Changes:
Bugs & Problems:
Added some important new functionality and fixed a longstanding bug today.
Changes:
Bugs & Problems:
Lots of progress this week, plus changes to the update schedule. Finally built a more streamlined setup for managing rules.
Changelog:
Known Issues:
Next Up:
Yesterday I mentioned a new system for building ‘rules’, whatever that means. Now I’ll go over whatever that means.
I’ve previously covered a couple pieces of functionality for handling special cases: Command Interrupts and Filters. Both are useful (and will stick around in some capacity), but had a few notable shortcomings.
Filters let me do things like blocking items inside Containers from vision (unless the container is open), as well as adding implicit actions (such as opening a door when trying to move in a direction). Very useful, but the code for it looks like this:
1 | var f = function(args){ |
It’s a decent chunk of code, and while it’s not that hard to follow, I think I can do better.
Command Interrupts are handy for things like menus, snarky one-off responses, easter eggs and the like. Generally they only happen once, and they happen instead of regular verb processing. Nothing particularly wrong with them, but they don’t have the versatility I was looking for.
Examples:
WANDER AIMLESSLY
, which can be used anywhere within the Forest region.I’ll explain the solution to this problem, and then come back to these examples.
The Rulebook is loosely modeled after Inform 7, like many things in the engine. In Inform 7, you can write a statement like:
1 | Instead of punching the goblin: say "That seems unlikely to achieve anything."; stop the action. |
This is part of their Rule system, which handles Instead, Before, After, and many other types of rules, which they group into Rulebooks.
Mine’s a bit simpler for now, but is structured in such a way that I can add new capabilities to it over time (and Modules can add capabilities/rules, of course).
The SRPG Rules can handle:
I’d also like to add some simple attribute checking, to avoid the need for callbacks in most filters. One of the end goals is to minimize the amount of custom code written for rules, in favor of a more expressive style for which the Rulebook generates the appropriate code automatically.
Rules use chaining (each function/method returns the object), similar to how many ORMs implement querying. Most of the chained functions resolve into filters, which are grouped together by type and processed in blocks (for example, Location filters are resolved together).
Let’s get back to the 3 examples listed above.
I’ll show the code for this Rule, then explain in detail what each step does. I’m pretending for the moment that the “simple attribute checking” element is already in place.
1 | understand('dangerously hot rule') |
Now the explanation:
Next up is the first Rule actually in the game. Any time the player is in the Forest, they can choose to WANDER AIMLESSLY
. This is not an actual Verb, but the command is intercepted by the Rule (similar to how a command interrupt would).
1 | understand('rule for wandering aimlessly') |
Explanation:
Lastly, here’s a rule to prevent the player from accidentally leaving the Rainbow Sword behind on a hill.
1 | understand('remember your sword rule') |
Explanation:
WALK
, RUN
, E
, etc)I have a bunch of Rule functions I need to add, including some I’ve cheated by using in these examples. I say ‘cheated’, but they are achievable in the current system by using while() callbacks (it’s just more verbose).
With all that in place, I think I could rewrite the container restriction from the top of this post to be less verbose/unwieldy. Something like:
1 | understand('container interaction rule') |
I also need to put some thought into cases like the dangerous temperature example above. I suspect that it will get triggered even if the action being taken is leaving a cold/hot area, due to the order in which verbs and appending occur.
Lastly, I’d like to build in the capacity for ‘meta filters’. In Inform, you can define (for example) a set of behaviors as ‘embarrassing’, and then reference the ‘embarrassing’ trait in other rules.
I’m pretty happy with how the Rulebook is working so far, but I know there’s a lot more I can do with it.