Pyrel dev log, part 4

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Magnate
    Angband Devteam member
    • May 2007
    • 5110

    #31
    Originally posted by Mikko Lehtinen
    It's not necessarily just for fine-tuning rarities. I've crafted a very simple but efficient rarity system for a future version of Mist. I'm going to have only six different rarity lines for melee weapons, and perhaps for devices too:

    Code:
    A:0/55:12/30:24/0
    A:0/40:24/10:36/10:60/0
    A:0/5:12/40:60/0
    A:0/0:12/5:24/40:60/10
    A:12/0:36/40:60/20
    A:36/0:60/70
    As long as there are as many melee weapons of each rarity, melee weapons as a group will always be just as common, but your chances of finding better weapons increases slightly every time you take the stairs down.
    You'll be able to replicate that in Pyrel using the "allocations" member of each object's definition.

    I've cut and pasted my stream of consciousness to http://bitbucket.org/derakon/pyrel/w...m%20generation where it now precedes the more low-level code-related stuff. I was of course intending to write a 'proper' wiki entry on item generation when Pyrel 'went public' on rephial or its own site or wherever. Glad it was helpful.

    @Derakon: I assume you'll be copying the instructional parts of previous dev log entries to said wiki at some point?
    "Been away so long I hardly knew the place, gee it's good to be back home" - The Beatles

    Comment

    • Derakon
      Prophet
      • Dec 2009
      • 9022

      #32
      Originally posted by Magnate
      @Derakon: I assume you'll be copying the instructional parts of previous dev log entries to said wiki at some point?
      Good idea!

      (They'll probably need some tweaking since they're "this is what I plan to do", not "this is what was done", but they'll make a good starting point)

      Comment

      • Patashu
        Knight
        • Jan 2008
        • 528

        #33
        Ok, calc is now an order of magnitude slower than eval for common cases (although I would like to have it much faster!)

        Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.


        using timeit with 10000 repeats in the python interpreter:
        Code:
        calc("1")  0.0418998227178
        eval("1")  0.0812901957397
        1  0.000138238181535
        calc("1+1")  0.576610918564
        eval("1+1")  0.0973678084556
        1+1  0.000208210600249
        calc("1+2-3*4/5*6-7+8")  1.62794541196
        eval("1+2-3*4/5*6-7+8")  0.184303583337
        1+2-3*4/5*6-7+8  0.00143016787479
        Outstanding issues:
        1) How should it take in variables - should it take an object with them, a dictionary with them? Should it discard them after one calculation or only when you explicitly tell it? Should the calculator itself be an object, such that you garbage collect it when you no longer need its variables?
        2) Should it always return a float? (It could try to determine if it is an int and return that instead. Or just return the string and let the caller decide.)
        3) User defined functions?
        4) Faster!
        Last edited by Patashu; December 5, 2012, 00:44.
        My Chiptune music, made in Famitracker: http://soundcloud.com/patashu

        Comment

        • Derakon
          Prophet
          • Dec 2009
          • 9022

          #34
          Quick note, folks -- Aerdan's IRC logs seem to be offline, so I can't guarantee I'll see anything you say to me. Might be best to do our asynchronous communication on the forum for the time being.

          Magnate -- I see your pull requests, I'll try to get to them tonight but if I don't, tomorrow's looking pretty busy unfortunately.

          Comment

          • Derakon
            Prophet
            • Dec 2009
            • 9022

            #35
            Okay, let's start picking up where we left off. Uh, which is where, exactly? I know Magnate is working on item generation and Fizzix is working on dungeon generation. I don't know if anyone else is tackling a major feature at the moment. Checking the To-Do List, it looks like the top things are:

            1) Porting in effects from Vanilla (spell, item, and monster effects).
            2) Limiting player knowledge (limited LOS; unidentified and partially-identified items)
            3) Making creatures smarter (pathfinding)

            I'd like to start on adding effects in. Some of these are straightforward -- anything that applies a (de)buff, for example, can be done right now with no further modification to the code. However, many spells require what Vanilla calls "projections" where a projectile needs to move across terrain. I believe the same system is also used to determine what has LOS on what. Both of these will need some efficient way to determine if a given tile is obstructed or not. And that in turn ties into pathfinding. So basically all three of these major features are related at some level.

            In Vanilla, determining if a tile is obstructed is fairly straightforward: there's one terrain type and one monster per tile, and they either obstruct (sight/projection/movement) or they don't. Pyrel doesn't have that kind of simplicity -- there can be any number of Things in a tile (terrain, creatures, and more), and we might potentially want any number of them to act as an obstruction, so we'll need to be clever if we want calculations to be fast.

            What these kinds of algorithms want to operate on is a 2D array of booleans, that indicates if a cell is "open" (visible / accessible / etc.) or "closed" (obstructed). Call it an "accessibility array". So we have a function that accepts a list of Things in the tile and decides if the tile is open or closed. Where we run into trouble is when we try to do this every tick for every cell for every thing that needs to check accessibility. Imagine calculating LOS for 95 orcs when the player opens a pit. Every orc would need to check if the other 94 orcs block its view (not to mention the walls and the player) -- a naive algorithm would perform horribly. What we need, I believe, is a cache.

            The cache will remember, for a given function, the status of the function's accessibility array. All of the orcs use the same sight function (being able to see through creatures but not walls), so they all use the same logic to determine if a given tile is blocked or not. Thus we need only compute accessibility once for each tile, instead of once for each tile for each orc; the result can be compiled into an array and passed to the LOS computation function (which would need to run once for each orc). Moreover, so long as the contents of the tile do not change, we don't have to recompute its accessibility. We can use the same "dirty tile" logic we use in drawing the game map to determine which tiles need to have their accessibility re-checked (i.e. to flush cache entries). NB currently dirty tiles are tracked by the artists; that should be moved into the GameMap, probably.

            So what this looks like, in practice, is that we have two sets of functions. The first set determines tile accessibility, and the second set makes use of the accessibility arrays. For example:
            Code:
            First set
            1) Can see through tile
            2) Can walk through tile (normal)
            3) Can walk through tile (wallwalker)
            
            Second set
            A) Compute LOS
            B) Pathfind to player
            C) Fire projectile
            Each function in the second set begins by requesting a specific accessibility array from the GameMap. It then does whatever it needs to do with that array (e.g. compute a path, or determine where a projectile stops). The first time the accessibility array is requested, it has to be computed for the entire map; from then on, incremental updates can be used instead. This should work well for most use cases I can think of; it's not ideal for functions that are used rarely (e.g. projectiles that can pass through glass walls), but those are used rarely, so their impact shouldn't be heavily felt.

            When a new map is generated, every tile is dirty, of course. We can track accessibility functions that were used commonly on the previous level, and pre-emptively compute their accessibility arrays as part of new map generation, in a multithreaded manner so that they don't contribute excessively to level generation time.

            Potentially we can let the second-set functions request subsets of the accessibility array. That would then save on the amount of dirty cells that would need to be recomputed -- you wouldn't have to do the entire map. I think this mostly only makes sense for projectile calculations, though, since those have a known maximum range (dependent on the projectile function). I don't know how much time it would save in practice, since at the very most there's only going to be a couple hundred or so dirty cells per player turn, right?

            Does this all seem reasonable? Unreasonable? Am I overlooking anything?

            Comment

            • LostTemplar
              Knight
              • Aug 2009
              • 670

              #36
              Maybe just have one value per tile, that determine it's accessiblity status (not just boolean, but also not too genral, e.g. lava is different from floor, etc.) Which is computed when and only when the given tile is modifyed, not when someone wants to look thru it.

              Comment

              • jdh
                Rookie
                • Jan 2013
                • 10

                #37
                Originally posted by Derakon
                What these kinds of algorithms want to operate on is a 2D array of booleans, that indicates if a cell is "open" (visible / accessible / etc.) or "closed" (obstructed). Call it an "accessibility array". So we have a function that accepts a list of Things in the tile and decides if the tile is open or closed. Where we run into trouble is when we try to do this every tick for every cell for every thing that needs to check accessibility. Imagine calculating LOS for 95 orcs when the player opens a pit. Every orc would need to check if the other 94 orcs block its view (not to mention the walls and the player) -- a naive algorithm would perform horribly. What we need, I believe, is a cache.

                The cache will remember, for a given function, the status of the function's accessibility array. All of the orcs use the same sight function (being able to see through creatures but not walls), so they all use the same logic to determine if a given tile is blocked or not. Thus we need only compute accessibility once for each tile, instead of once for each tile for each orc;
                Two more complicated examples come to mind:

                1. Opacity: you might be able to see through a few orcs if you are only a few orcs back in the mob, but a crowd of 100? And what if you have mist, rain, smoke or fog? It would be cool if it could accumulate and have stages of effect: you see poorly 3 or 4 tiles away, hallucinations or mirages 4-10 steps away and nothing further than that...

                2. Directionality: suppose you would like to have one-way mirrors that let you see into rooms but not out, or perhaps camouflaged entrances which monsters see out of but are difficult to see into?

                Comment

                • Derakon
                  Prophet
                  • Dec 2009
                  • 9022

                  #38
                  Originally posted by jdh
                  1. Opacity: you might be able to see through a few orcs if you are only a few orcs back in the mob, but a crowd of 100? And what if you have mist, rain, smoke or fog? It would be cool if it could accumulate and have stages of effect: you see poorly 3 or 4 tiles away, hallucinations or mirages 4-10 steps away and nothing further than that...
                  There's no reason you couldn't have an array of floating point numbers instead of an array of booleans. Then 0 represents completely unobstructed, 1 completely obstructed, and anything in-between is partially obstructed.

                  2. Directionality: suppose you would like to have one-way mirrors that let you see into rooms but not out, or perhaps camouflaged entrances which monsters see out of but are difficult to see into?
                  This would need to be handled on a per-invocation basis (as opposed to when the accessibility array is updated), since you can't have directionality without having a source and target. The way I'd do it would be to have a value (e.g. None) that indicates that the tile needs to be handled specially. So while trying to get LOS, the function checks that tile, sees that it requires special handling, and manually checks the tile contents.

                  Originally posted by LostTemplar
                  Maybe just have one value per tile, that determine it's accessiblity status (not just boolean, but also not too genral, e.g. lava is different from floor, etc.) Which is computed when and only when the given tile is modifyed, not when someone wants to look thru it.
                  It sounds like you're suggesting I hardcode a set of kinds of accessibility into the game engine? But that means modifying the engine if we come up with new ways of seeing / projecting. For example, if we want to add X-ray vision and lead walls, then we'd have to modify the base engine instead of just adding a new pair of functions for accessibility and augmented LOS. Feel free to correct me if I'm misunderstanding you.

                  Comment

                  • jdh
                    Rookie
                    • Jan 2013
                    • 10

                    #39
                    Originally posted by Derakon
                    This would need to be handled on a per-invocation basis (as opposed to when the accessibility array is updated), since you can't have directionality without having a source and target. The way I'd do it would be to have a value (e.g. None) that indicates that the tile needs to be handled specially. So while trying to get LOS, the function checks that tile, sees that it requires special handling, and manually checks the tile contents.
                    I would think you could implement this as a sort of directed graph, with a cost for traveling from each tile to each adjacent tile: e.g., if you have tiles in each of the 8 keypad directions, there would be a cost for moving to each. (I don't know whether you're contemplating extensibility to other geometries.) Your cache would store costs for entire paths, but each path greater than 1 would have several other paths as subsets: cost(a,d) for a -> b -> c -> d would check cache[a,d], then fall back to cost(a,c)+cost(c,d). This may be excessive cleverness and I don't know how you would apply it to things other than line projections (such as Sil's noise concept), but I wouldn't think it would be too many more than 95 calculations for 95 orcs checking whether they can see the player.

                    Comment

                    • LostTemplar
                      Knight
                      • Aug 2009
                      • 670

                      #40
                      It is a matter of taste, what to hardcode, you cannot make engine universal. Basically there must be a function, which tells you if a tile is transparent, there are two ways to use this function: call it when tile is checked in LOS function, or call it when tile is changed. I think, that second way will be more effective.

                      If you have a lot of functions for different kinds of LOS, it still may be faster to call them all for every tile changed. IMHO you will never really have a lot of different LOS types.

                      Comment

                      • Kinematics
                        Apprentice
                        • Feb 2013
                        • 72

                        #41
                        Some thoughts on the issues of stat calculations from the #3 thread that never seemed to have gotten resolved (at least, not in the thread, and the code looks like it's still using the same ideas).


                        More confusing ways to apply: Suppose you decide that hobbits, rather than a static +3 bonus to dex, get a bonus of level/10, or just +20%. (note: seems less likely for base stats, and more likely to apply to things like speed, such as hengband's level-based speed increase for ninjas; just an example, though) Where does this apply in the calculation hierarchy?

                        It's part of what's considered the 'Inherent' level of calculation -- the race modifier -- yet depends on other calculations. Which leads me to a slightly different approach.


                        I would construct it like this:

                        Fundamental - initial birth value (dice roll, player buy, whatever); cannot change during the game.

                        Fixed mods - Additions that -do not- depend on the stat itself. static +3 from race; +level/10 to speed; etc.

                        Multiplier mods - Additions that -do- depend on the stat itself. Ring of +20% str, etc. These are only calculated after Fixed mods are done.


                        Whether it's a permanent or temporary effect doesn't matter. Ring of +3 str or Ring of +20% str are both permanent, but don't get calculated at the same time. A spell of Giant Strength that adds +5 to str for 50 turns would go under Fixed mods, not Multiplier mods, and there's no reason to treat it differently than a ring of +5 str.


                        Now, the hypothetical example:

                        Okay, so say we have a ring that gives you a bonus to INT based on your magic device skill. Say further that magic device skill is determined in large part by your DEX.
                        Ring would adjust Int as a Fixed mod based on Magic Devices.
                        Dex would adjust Magic Devices skill as a Fixed mod.

                        So when calculating Int, it gets to the Fixed mods level, determines there's a ring that modifies it (or the ring inserts its StatMod call into the queue), asks the ring how much to modify things by. Ring says "Hold on a minute..", asks how much the MD skill is; MD skill queries Dex, which carries out all of its calculations, returns a value, MD gets calculated, returned to the Ring, Ring figures out its value, and gives that to Int. No problems.

                        Now suppose Magic Devices depends on Int, and the Ring mods Int based on Magic Device skill. What I would do:

                        What is current Int? Start adding Fixed mods to the Fundamental value. When a StatMod() is called, mark it as 'In Use', and release it afterwards.
                        Call Ring.StatMod(). It queries Magic Devices for skill.
                        Magic Devices needs to know Int. Requests current Int value.
                        What is current Int? Start adding Fixed mods to the Fundamental value. Ring.StatMod() is 'In Use', so gets skipped. Returns a value for all adjustments other than Ring.StatMod().
                        We now have a Magic Devices skill value relevant to the Ring's query (not full magic skill, but magic skill without the bonus the ring provides). Return that.
                        Ring.StatMod() now has a value for Magic Devices skill, and returns the amount it increase Int by.
                        Int completes the Fixed mods calculations.
                        Int can then proceed to later add a Ring of +20% Int using the value that includes the MD-based Int ring value.

                        Supposing you had 2 such rings -- each gets locked during the Magic Device Skill query, so that the MDS value that they each use is based on neither of the rings being present. I'm pretty sure this would hold even if every single equip slot was making this same modification, though that would add a huge number of near-circular redundant calls. You could maybe cache each StatMod() call within a given top-level request, to reduce the redundancy; just make sure not to re-cache during a circular cycle.



                        Differentiating between Fixed and Multiplier mods would probably be easiest by just having two different functions. You can either insert a StatMods() call into the queue for complicated calculations that will eventually provide a fixed value addition, or just provide a simple percentage to the total multiplier value to be used as the final adjustment after Fixed mods are added in. EG:

                        OnEquip ring of str, AddStatMod(MyFunction)
                        OnEquip ring of +20% str, AddStatMultiplier(20%)

                        Though technically there's nothing to prevent the StatMod() call from calculating its result from the stat itself; it's just bad form, and will give lesser results [Edit: actually, it would probably give identical results; it would just take more work since you have to calculate everything twice], but won't break anything.

                        Comment

                        • Derakon
                          Prophet
                          • Dec 2009
                          • 9022

                          #42
                          I admit that I didn't read your entire post, so I apologize for short-circuiting the conversation. But my eventual conclusion on this problem is that there is no right answer, and it's up to the game designer to pick modifier tiers in a way that makes sense. The game will enforce an ordering of stat modifiers, so there can be no cyclic dependencies, but that's all it will do. "Inherent", "permanent", "temporary", etc. tiers may or may not make sense depending on what the designer wants to do, but they clearly won't fit every possible design.

                          Comment

                          • Kinematics
                            Apprentice
                            • Feb 2013
                            • 72

                            #43
                            "Inherent", "permanent", "temporary", etc. tiers may or may not make sense depending on what the designer wants to do, but they clearly won't fit every possible design.
                            Been going over the code more. I -think- it's generic enough that my thoughts could be represented with the framework.

                            If I'm reading it correctly, each mod can set its own tier? That is, individual stats aren't given tiers, just the mods, so the tier isn't dependent on what stat is being calculated?

                            So, I could have a Ring +3 str on the Add (first) tier, and a Ring +20% str on the Multiply (second) tier, and generally define all stats and mods with respect to just those two tiers?

                            Possible bug: Not sure, but it looks like if the mods are listed out of order with respect to tier (eg: a queue of tier 1, tier 3, tier 2, tier 3, tier 1, etc) that the results would be unpredictable (specifically, if there are any multiplications in the queue).

                            Also, I think you preempt the recursive calls too early, and too completely. Basically, rather than blocking recursion at the stat level, it should be blocked at the mod level. Would require mods to be copied by reference rather than generating multiple instances of them, so that local state has meaning, but then you can just have ~~
                            Code:
                                if (!blocked) then
                                  blocked = true
                                  get proc value
                                  blocked = false
                                  return value
                                else
                                  return 0
                            Or use whatever atomic lock operations you want, though it shouldn't matter if you're not doing multi-threading.

                            Honestly, I still think the tiers are a bad idea. The same operation on a different tier can give a different result, which means a single effect (eg: +20% str) can't be relied on to produce consistent values if applied at different tiers. Plus they don't actually add anything of value; their entire purpose of stopping circular calculations can be handled by simply blocking the recursion.

                            There's really only two necessary operations: unit-based addition (the value is concrete), and unitless multiplication (the value depends on the existing value, and can only be applied after all the concrete values are added in). One might argue the option of multiplying all of the the unitless values together vs adding them together before multiplying by the total (I'd suggest the latter); otherwise I'd expect completely agnostic calculations.

                            Comment

                            • Derakon
                              Prophet
                              • Dec 2009
                              • 9022

                              #44
                              Originally posted by Kinematics
                              There's really only two necessary operations: unit-based addition (the value is concrete), and unitless multiplication (the value depends on the existing value, and can only be applied after all the concrete values are added in). One might argue the option of multiplying all of the the unitless values together vs adding them together before multiplying by the total (I'd suggest the latter); otherwise I'd expect completely agnostic calculations.
                              This is true until you decide to add a modifier that can't be easily expressed by addition or multiplication. A StatMod can invoke a Proc to generate a value based on whatever it likes, including e.g. distance from something on a map (grunts getting a morale bonus when close to their leaders), items in inventory (taking a speed penalty because you're carrying heavy stuff), time of day (vampires get bonuses at night), etc. All of these could scale in very weird ways if that's the way the designer wants to run things.

                              Pyrel's guiding principle for design is "If I can imagine use cases I might reasonably implement that require this feature, then it should be implemented." I'm not going to try to stretch the design to do everything under the sun -- it'd never be done, that way -- but I can easily imagine wanting to have complex stat modifier interactions. Stats form such a key part of the game; that makes them a good place to look to if you want to add interesting decisions.

                              For what it's worth, if you want to have additive and multiplicative modifiers as separate things, then you can add a new stat that all the multipliers add to, and then attach a low-priority modifier to the base stat that incorporates the multipliers. E.g. have a "STR Multiplier" stat which receives additive modifiers, and then have a multiplicative modifier on STR that uses 1 + the "STR Multiplier" value. This is kind of indirect, but it gets the job done just fine.

                              Comment

                              • Kinematics
                                Apprentice
                                • Feb 2013
                                • 72

                                #45
                                First: sorry for the length, but it takes a bit to go through all the points, and I want to make sure that each one is clear. If I'm completely out in left field on this analysis, smack me and send me to my room.


                                This is true until you decide to add a modifier that can't be easily expressed by addition or multiplication. A StatMod can invoke a Proc to generate a value based on whatever it likes, including e.g. distance from something on a map (grunts getting a morale bonus when close to their leaders), items in inventory (taking a speed penalty because you're carrying heavy stuff), time of day (vampires get bonuses at night), etc. All of these could scale in very weird ways if that's the way the designer wants to run things.
                                Hmm. I'm not sure I get what you mean here. Each of those effects -depend- on non-integral information, but will any of them ever -generate- non-integral (or at least floating point) information? And how does tiering make those possible where non-tiering would not? (Note: Please answer this; it's not a rhetorical question.)

                                [Aside: Ah, I see you actually have 3 modifiers per mod: an addition, a multiplication, and a trigger (the results of which are added). I've been thinking of things as solely having a trigger value, though hoisting the addition/multiplication for trivial needs makes things simpler for most cases. However it does leave out the possibility of the trigger producing a multiplier that can apply only to totals that have not had multipliers applied, unless your tiers are explicitly separated between additive and multiplicative mods, which isn't something you can guarantee based on the code alone.]


                                EG: Time of day affects a vampire's strength. Ok, then maybe something like, every hour after dusk, til midnight, the str modifier increases by a non-linear amount (eg: +1 str, +3 str, +6 str, +10 str, +15 str, +25 str). You need to figure out the time of day, and the difference in time, maybe a lookup table, etc, but ultimately it all feeds back into a modification to str, which will only ever be an addition or multiplication (or maybe theoretically log or exponent, though those can largely be reduced to multiplications). That is, the final result of the StatMod function can only perform a simple value manipulation of the stat; you'll never modify the stat by the time itself because the combinations of those two values doesn't make sense (different units, can't combine).

                                Regardless, you'll never be returning a time value, or a distance value, or whatever. A function may need to know the distance from leader as a parameter, but it must eventually return an actual value that needs to be combined with the base value (eg: morale). None of that oddball stuff is a stat or stat mod, unless maybe you want to allow for dimensional-altering spells that change the distances between any two points (look up a pdf on hyperbolic space-time curvature as it relates to the Cthulhu Mythos for some fun reading). Even then, though, the alteration to the distance must still give you a value that you can use as a parameter, which means it itself is still subject to the same basic mathematical manipulations.


                                Pyrel's guiding principle for design is "If I can imagine use cases I might reasonably implement that require this feature, then it should be implemented." I'm not going to try to stretch the design to do everything under the sun -- it'd never be done, that way -- but I can easily imagine wanting to have complex stat modifier interactions. Stats form such a key part of the game; that makes them a good place to look to if you want to add interesting decisions.
                                I can absolutely agree with the general principal espoused here. What I disagree with is whether the tiering implementation actually serves that goal.

                                Basically, I cannot think of any sort of stat manipulation that cannot be expressed as either [stat += StatMod()] or [stat += stat * StatMod()] (though the aformentioned hyperbolic space-time might be a little tricky). Now, StatMod() itself can do all kinds of funky stuff, but that's completely isolated from the ultimate adjustment to the stat (at the level where tiers are in play), and any other stats that StatMod() references must themselves be constrained by those same limits.

                                The point of using tiers was so that you could be certain that if you needed information about a stat to make a calculation within a StatMod(), then that stat was completely calculated before attempting to use it. However recursion restriction gives you exactly that already. You just can't have a StatMod() that modifies itself, which is the same restriction that is required when using tiers.

                                So it doesn't seem that you *gain* anything with tiers except extra complexity.


                                Now, all that said, can I think of any way to make use of tiers that could not be done without them?

                                1) An effect (eg: pylon in the dungeon) that amplifies magical bonuses. In other words, it doesn't increase base stats, but instead gives a +50% potency to any magical enhancements. So if you had a +10 str spell on you, this would make it a +15 str spell, but a +10 str ring would stay +10.

                                Counterargument: The tiers wouldn't explicitly allow for this anyway. A modification at a higher level than temp effects would still also apply to every tier below it, so this bonus would affect gear and base stats as well. Instead you'd need a query that gave you all the current temporary effect bonuses. At that point you can take 50% of that value and return it to be added to the stat, but then tiers become irrelevant, as the query would be of a certain 'type' of StatMod.

                                One could, perhaps, have a modifier that explicitly applies to each effect itself (and thus could apply to only temporary effect), but it would be difficult to have a common reference that would be universally workable, and it would require each effect to be aware of the possibility and code that into its own inner workings. Seems fragile.


                                2) Same thing, but only affecting base stats, not gear or temp effects. In that case tiering works. However this is really just a special case of the general desire to "affect only the stats on a particular tier" (and fails on any except the lowest tier), which can just as easily be described as "affect only the stat mods of this 'type'" (which would work for any defined type, even if those types are effectively just tiers, if such a query was available).


                                3) An effect that requires a minimum value before a bonus is given, and that excludes a certain class of other bonuses (eg: no magic bonuses, or no gear). In that case, tiering would work better because you want to exclude a set of bonuses, which means you need to have knowledge of all possible bonus types except the one you want to exclude if you wanted to use the query method (though it wouldn't be too difficult to instead say "get All mods; remove type 'x' mods from this list").


                                4) Vampire at night gets a 50% bonus to all gear mods, one of which is a ring that increases Int based on Magic Device Skill (which itself depends on Int). So now, what's the value of Int?

                                Tiered: can't get just all gear effects, but assuming we take all effects underneath 'temporary' (thus, base+gear) the 50% multiplier is straightforward, if not exactly what we wanted. However the ring just won't work because of the tiering -- either MDS is above Int, or Int is above MDS, and can't have both at the same time.

                                Non-tiered: Query for all gear effects means that aspect is simple. Ring requires MDS, which requires Int, which requires both the ring and the Vampire bonus. Recursion block means that MDS is calculated without the ring's Int bonus, but with the Vampire bonus for the remaining gear. After that the ring bonus is returned, and the Vampire bonus is calculated again on -all- of the gear. Leads to a lot of duplicate calls, which could be mildly annoying if this were to occur on, say, Searching, where you're likely to repeat the call very frequently, and had lots of effects with complicated computations. However it gives us a consistent, 'mostly' correct answer every time.



                                aside #2) My redirection of everything to a query of type is in large part simply a renaming of the existing 'tier' value -- you still need some means of isolating classes of mods, and just because I say tiers aren't necessary doesn't mean that part of their potential use isn't necessary. However a query on type has freedom to choose any arbitrary mod class at any given time, whereas tiers can necessarily only know about lower tiers (unless you keep adding higher tiers, but then you run into the "turtles all the way down" problem).



                                aside #3) I left it out of my earlier post to try to keep things simple, but wanted to be clear about the issue of inconsistent values.

                                Any time you combine multiplied values in a tiered system, you necessarily introduce the possibility that adding exactly the same two stat mods can give different results, simply due to the fact that they are applied on different tiers. EG:

                                Base str: 100

                                Ring +10 str & Ring +20% str = 130 str (due to them being on the same tier)
                                Ring +20% str & Spell +10 str = 130 str
                                Ring +10 str & Spell +20% str = 132 str !

                                Also:
                                Ring +20% str & Ring +20% str = 140 str
                                Spell +20% str & Spell +20% str = 140 str
                                Ring +20% str & Spell +20% str = 144 str !

                                This inconsistency may eventually lead to issues with difficulty in balancing the game, or potential exploits due to spell/gear combinations that yield values outside expectations.


                                If I'm significantly misreading the behavior of the code, let me know.

                                Comment

                                Working...
                                😀
                                😂
                                🥰
                                😘
                                🤢
                                😎
                                😞
                                😡
                                👍
                                👎