How do I architect effect-modifiable properties well in C#?



  • I'm working on a reimplementation of a two-decade-old game as a hobby project -- it uses DirectDraw, and on modern systems, something that it does (likely with custom cursors) causes a hard deadlock very quickly -- and I find myself unable to come up with a good architecture for a pretty key wargame feature: stats.

    Each army has several attributes -- Strength, Hits, Move, Vision, Leadership, Chaos, Morale, Fear, Fortify, and Siege to name a few. These each come with a base value based on the type of unit they are and certain initial bonuses (side boost in campaign, whether a side is "enhanced" or not, or an outbuilding attached to a city), and can be modified by other effects, some of which are directly on the unit (medals, spells), some of which are on the unit's stack (banding, spells) or from another unit (heroism, spells), and some of which are situational (+Leadership/-Chaos for Strength in combat, terrain bonuses, spells...).

    Full example:
    A dwarven archer is currently in combat. Dwarven archers typically have 2 strength. However, this particular army of dwarven archers was produced by an enhanced side, in a city with a blacksmith, so they instead start with 5 strength. They have the spell Strength cast on them, which grants them +2, for 7 strength. They're in a stack with a hero with 2 morale, but they're fighting a stack of several wights with a total of 4 fear, so they take a -2 penalty. Their final ability score for strength is 5.

    Applying direct bonuses or maluses to a simple int property is most likely the easiest way to implement this, but it's likely to get out of sync if more complicated expressions start appearing, and I'm likely to forget all the points to implement the addition/removal of the changes, driving things further out of sync. However, if I go to the other extreme and create a bunch of functor classes, one for each effect, and attach an instance of each to each army for each stat, recalculated on the fly each time the stat is checked, it's quickly going to become a performance and bug-tracking nightmare, compounded by the fact that spells and magical items are not hard-coded and new ones can be created per scenario.

    How do I efficiently represent and manage this ever-shifting pile of expression trees?


  • Considered Harmful



  • It's a question of whether you want to have this be declarative (Strength has a common data structure associated with it that has +2 in the "strength modifier" field) or imperative (when rolling for a strength check, Strength adds 2 to the value).

    Personally, I'd say declarative would be easier to maintain in the long run.

    See also: Dwarf Fortress syndromes: http://dwarffortresswiki.org/index.php/DF2014:Syndrome#The_anatomy_of_a_syndrome


  • Considered Harmful

    @ben_lubar There's stuff that only fits in imperative, though. And once you've structured your system for declarative, it becomes very difficult to shoehorn imperative in.


  • BINNED

    I agree with Ben in the most part. However... I'm not a C# guy, but couldn't you get away with reflection, or at least a lookup list of attributes? As in, when adding something that can modify attributes, you either give it a reference to the unit or a list of units, and it just calls ModifyAttr("Strength", 2), and have your base class for units just do something like (pseudocode):

    Unit::ModifyAttr(String name, int value) {
        if(this.HasAttr(name)) {
            this.GetAttr(name) += value;
        }
    }
    

    I'm assuming there's at least some extra stuff that's making it more difficult (percentage based stuff that should only get applied on the very end)?



  • @twelvebaud said in How do I architect effect-modifiable properties well in C#?:

    Applying direct bonuses or maluses to a simple int property is most likely the easiest way to implement this, but it's likely to get out of sync if more complicated expressions start appearing, and I'm likely to forget all the points to implement the addition/removal of the changes, driving things further out of sync. However, if I go to the other extreme and create a bunch of functor classes, one for each effect, and attach an instance of each to each army for each stat, recalculated on the fly each time the stat is checked, it's quickly going to become a performance and bug-tracking nightmare, compounded by the fact that spells and magical items are not hard-coded and new ones can be created per scenario.

    Is adding/removing new modifiers going to be common? Or, to rephrase, if you compare the number of times you request a stat's value with the number of times a modifier is added/removed, is the second number going to be much smaller, about the same or much larger?

    Without knowing much more about the situation: in the first case, you could compute the final value eagerly each time a modifier is added/removed and cache it (but still record all modifiers with the value; if nothing else, then for debugging), in the other two cases, compute it on the fly.



  • Semi :pendant:

    Architect is not a verb. More importantly an architecture comes into existence by examination of a large number of designed and determining both the common and differentiating elements at a (typically) strategic level.


  • :belt_onion:

    @thecpuwizard said in How do I architect effect-modifiable properties well in C#?:

    Semi :pendant:

    Architect is not a verb.

    The OED disagrees with you.



  • @heterodox said in How do I architect effect-modifiable properties well in C#?:

    The OED disagrees with you.

    OED has jumped the shark


  • :belt_onion:

    @japonicus said in How do I architect effect-modifiable properties well in C#?:

    OED has jumped the shark

    Sorry, should have said "As does every other dictionary I could find."



  • @heterodox TIL

    There appears to be usage of 'architect' as a verb back to the early 1800's - I still don't like it and don't see that it adds anything over 'design' apart from grandiosity.

    Verbing weirds language. :sadface:



  • @pie_flavor said in How do I architect effect-modifiable properties well in C#?:

    Sponge does this nicely.

    That's pretty much what I'm going for. The part I'm unsure about is how to arrange the eventproperty listeners -- do I have one representing "all spells", or one representing "all castings of this type of spell", or one representing "this one casting of this one spell"? The former has very few instances that are called frequently, while the latter has many instances that are called rarely. Then, how do I register and track them, so I can dispatch requests to them?

    @onyx said in How do I architect effect-modifiable properties well in C#?:

    I'm assuming there's at least some extra stuff that's making it more difficult

    Which effects affect which units can change rapidly, potentially from something as little as double-clicking a stack (grouping all the units together, including per-group bonuses) or a single tile of movement. I don't have confidence that I can enumerate every case where an effect starts or stops applying and add an event handler with a call to ModifyAttr, and if I discover that a unit has an obviously-incorrect attribute, I won't know how it ended up that way.


  • BINNED

    @twelvebaud In that case, I'd propose what I initially wanted to, but deleted it because it sounded like overengineering. Sorry if I mess up the terminology or something, I don't do .NET, but...

    I assume that you have structures for unit stacks and similar things that can apply modifiers transitively, yes? So, I'd say:

    1. Attach event listeners on the unit when it's added to the stack or other "parent" structure
    2. Have anything that can modify stats (spell, medal, another unit...) notify the "parent" about the change and then raise an event that units themselves are listening for to modify the actual stats. Do this both for addition and removal of medals / spells / whatevers

    This way you let the parent structure, whatever it is in this case, act as an arbiter and you don't need to drill down to each unit individually. You'd have to cascade it as well I guess, so adding a stack to a city would make it so you bind a listener on the stack itself, so applying an effect from the city all the way down to the unit would go like:

    1. City has an effect. Raises an event caught by all the stacks present in the city
    2. Now stack has an effect it got from the city, it raises an event caught by all the units.

    Might have messed up how it works here since I don't know the game itself, but I assume it's something like that. As to how to cascade it properly without having to write a bunch of stuff manually... dunno, I don't .NET, is something like traits available? Also, adding an effect to the unit itself is kinda weird with this architecture, but it's the thing that makes the most sense to me 🤷♂


  • Considered Harmful

    @twelvebaud said in How do I architect effect-modifiable properties well in C#?:

    do I have one representing "all spells", or one representing "all castings of this type of spell", or one representing "this one casting of this one spell"?

    Seems like the second option would be best. The spell is the algorithm, where given certain stats it has a certain effect, and the casting of the spell is an instance of that spell object, where the circumstances of the cast get passed as arguments to the constructor. As for how to call them, you would have an interface that defines how to get an entity's damage resistance modifiers, and the damage application modifiers get passed into the damage method.



  • I have a couple of projects where I've been meaning to do this kind of thing, but with lots of computation types. I think I just made a Modifier class, and was going to have things have a list of those.


Log in to reply