Think about how rules or rulesets can be limited to specific campaigns or other critera.
orendain opened this issue ยท 3 comments
Following @TheGrayAlien's idea from https://discord.com/channels/841011788195823626/841011980667060244/968149051323338802:
The question is, how can HR restrict a card (via CardAdditionOverridden
rule) to a particular module/campaign (e.g., RatKing/Forest/Queen)?
- Change
CardAdditionOverridden
so that the applicable campaign can be set. - Add a new rule, similar to the above, with an extra field for specifying the campaign.
A similar request could be made for many of the other rules (e.g., monster spawning, points of interest per floor, etc.). So the question could then be: should HR have a standard way of building in campaign-filters into each rule?
However, there can be multiple filter criteria (e.g., number of active players, campaign type). Which transforms the question into: how can (and what kind of) filter criteria be set for rules?
- (Complex) Maybe in addition to each rule's standard config, specific conditions can be attached to each (e.g., only load a particular rule if playing the RatKing).
- (Easier) Have conditions that can be attached, but at a ruleset level (e.g., only allow a ruleset to load when RatKing is selected).
- (Easiest) Do nothing. Encourage rulesets to add a "best played on XYZ map" or "best for 2 players".
While point 1.
above may seem like the most robust solution, it may be overly complicated to implement and potentially challenging to elegantly expose those options via JSON for users to customize.
Both points 2.
and 3.
effectively do the same thing: requires rulesets to have campaign-specific variants. But they also don't elegantly solve the problem at hand.
Not sure what the best course of action may be at this point. May need more data.
I don't think that 1. is necessarily too complex. Thinking about how to represent this from a JSON viewpoint, Rules
is just a list[] and it is already possible to have the same type of rule specified multiple times (e.g. AbilityAoeAdjusted has cumulative effects if you accidentally have it specified twice) - We could extend the existing format for a rule with an optional Limit
field on a per-rule basis...
e.g.
{
"Rule": "AbilityActionCostAdjusted",
"Limit": [ "Elven", "Forest" ],
"Config": {
"Zap": false,
"CourageShanty": false,
"Sneak": false
}
},
{
"Rule": "AbilityActionCostAdjusted",
"Limit": [ "Sewers" ],
"Config": {
"Torch": true,
"CourageShanty": true,
"ReplenishArmor": false
}
},
This would offer good flexibility with allowing rulesets to be tailored to different game types, without us having to make the rules themselves too clever. If a ruleset wants to specify torches as a CardAddition it becomes possible (if somewhat verbose) by specifying multiple CardAdditionOverridden
rules but with different Limit
restrictions. We're already setting MotherBrainGlobalVars.CurrentConfig
early so that we have access to it in PreGameCreated situations. Could we modify LifecycleDirector
so that it would skip rules if they had a Limit
set that didn't match the CurrentConfig
?
I know we can have optional/default parameters in c#... not sure about an optional parameter with newtonsoft json though.
Great JSON example - very clean. Adding some details:
- Rules that implement
ISingular
(orIPatchable
) are currently limited to one rule type per ruleset, validated at registration time. - We'd have to rewrite that validation to allow multiple
IPatchable
rules be specified in a ruleset. Perhaps modifying validation to allow multiple rules of the same type as long as they have disjointed limits. - If a limit is attached to a multiplayer-unsafe rule, then the overarching ruleset may in fact be multiplayer-safe if the rule doesn't get loaded.
- Sometime similar applies to the aggregated "modifiable syncs" of the ruleset (e.g., no board sync required if a rule doesn't get loaded).
- Both of the above could be solved if we hold off on evaluating those values until post-game start.
I was in the mood for playing around with code, so I attempted to sketch out the above approach in code. I've raised a draft PR #348 to make it easy to see the changes involved.
I do not think that the approach I've taken in the PR is the correct one to follow for the following reasons:
- I'd been hoping not to need to specify a limit at all for rules which didn't need it, but because a struct initialises to nil it's always necessary to pass some
GameConfigType
with every rule. As I understand it, the only way around this it to change from using a struct to a class instead where defaults can be set. - I had intended to use
GameConfigType.None
as a wildcard/default if there was nothing specific defined for that particular adventure. My thinking was thatsewers
has the requirement forTorch
in the cards and would want one set of rules, but a more generic set would be fine for applying to elven/forest/serpent levels. This does not work in practice however because the rules themselves do not have visibility of the entire ruleset and do not know if another rule has already been applied, and if you have two different rules limits of bothSewers
andNone
then they will both get applied one after another.GameConfigType.None
is only useful for applying to ALL adventures - as soon as you want to make something specific for a particular adventure, you have to list all of the ones where it's not applicable anymore. - Although I'm sure this is fixable, the struct I used makes the JSON for a ruleset now has extra keys nested inside
Config
which looks messy and confusing.
"Rule": "LevelPropertiesModified",
"Config": {
"Limit": [
"Sewers"
],
"Adjustments": {
"FloorOneHealingFountains": 5,
"FloorOneLootChests": 25,
...
- Although this approach enables multi-adventure rulesets, it is still quite clunky
- Taking this approach will be a breaking change for every JSON ruleset that anyone has made to-date
I'm now thinking that it may be better to have HouseRules Core examine the rulesets and decide whether to apply a rule or not, rather than have each rule try to decide for itself whether to apply the config it was supplied with or not.