Pyrel dev log, part 3

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • ekolis
    Knight
    • Apr 2007
    • 921

    #31
    Originally posted by Derakon
    I went ahead and changed "finesseBonus" and "prowessBonus" into "meleeFinesse" and "meleeProwess", with the idea being that melee and missile scores would be completely different stats. This should sidestep the problem of not knowing if e.g. that Ring of Slaying on your finger makes you shoot better. It also means that off-weapon combat bonuses can just be tossed onto the player instead of having to modify the weapon. We could extend this to brands (and even to heft, balance, and the dice on the weapon!) but it'd be pretty clunky to have "meleeFirebrand", "missileHeft", etc. modifiers. Plus it'd probably interfere with dual-wielding somehow, not that Pyrel plans to have that.
    How about instead of duplicating all the stats, you just use tuples (or whatever Python's equivalent of tuples is)? E.g instead of "meleeFinesse" and "missileFinesse", you have ["melee", "finesse"] and ["missile", "finesse"]? This would also allow for more code reuse
    You read the scroll labeled NOBIMUS UPSCOTI...
    You are surrounded by a stasis field!
    The tengu tries to teleport, but fails!

    Comment

    • Derakon
      Prophet
      • Dec 2009
      • 9022

      #32
      Originally posted by ekolis
      How about instead of duplicating all the stats, you just use tuples (or whatever Python's equivalent of tuples is)? E.g instead of "meleeFinesse" and "missileFinesse", you have ["melee", "finesse"] and ["missile", "finesse"]? This would also allow for more code reuse
      No, this is the only place in the game where such concepts are duplicated, and changing the entire stat system to accommodate it (by using tuples as keys instead of strings) doesn't make much sense to me. I don't see how it enables code re-use anyway -- the important thing is the calculations, and those can be written so that they accept the relevant stats as parameters. The only bit of duplication would be where we go "if you're doing melee, then use this set of stats; otherwise, use this set".

      Comment

      • Magnate
        Angband Devteam member
        • May 2007
        • 5110

        #33
        Originally posted by Derakon
        No, this is the only place in the game where such concepts are duplicated, and changing the entire stat system to accommodate it (by using tuples as keys instead of strings) doesn't make much sense to me. I don't see how it enables code re-use anyway -- the important thing is the calculations, and those can be written so that they accept the relevant stats as parameters. The only bit of duplication would be where we go "if you're doing melee, then use this set of stats; otherwise, use this set".
        But isn't that quite an important issue of extensibility? I can foresee at least four different types of combat (melee, martial arts, missiles and psionics), even without dividing them into subtypes. I'm not close enough to the detail yet but tuples do seem quite an improvement. And are you sure they'd never be useful anywhere else in the code? What about stats for trap disarming, e.g. ("mechanical", "finesse") and ("magical", "wisdom") and so on.
        "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

          #34
          Mm, I didn't explain myself very well.

          Functionally there's no real difference between a tuple ('melee', 'finesse') and a string 'meleeFinesse', except that I'd have to modify the game code to load tuples if we decided to support them. The code is not made any easier to deal with if we use tuples -- we still have to track down the (foo, 'finesse') skill to get the relevant finesse score.

          Basically the difference boils down to doing one of these two things:
          Code:
          getDamage(stats['meleeFinesse'], stats['meleeProwess'])
          
          getDamage('melee')
          In the latter case the getDamage function would be responsible for looking up the finesse and prowess scores on its own...but is that really more extensible and allowing for more code re-use?

          Comment

          • zaimoni
            Knight
            • Apr 2007
            • 590

            #35
            Originally posted by Derakon
            Basically the difference boils down to doing one of these two things:
            Code:
            getDamage(stats['meleeFinesse'], stats['meleeProwess'])
            
            getDamage('melee')
            In the latter case the getDamage function would be responsible for looking up the finesse and prowess scores on its own...but is that really more extensible and allowing for more code re-use?
            That has to be decided on a case-by-case basis. The usual answer is "no" (Law of Demeter).
            Zaiband: end the "I shouldn't have survived that" experience. V3.0.6 fork on Hg.
            Zaiband 3.0.10 ETA Mar. 7 2011 (Yes, schedule slipped. Latest testing indicates not enough assert() calls to allow release.)
            Z.C++: pre-alpha C/C++ compiler system (usable preprocessor). Also on Hg. Z.C++ 0.0.10 ETA December 31 2011

            Comment

            • Derakon
              Prophet
              • Dec 2009
              • 9022

              #36
              As an added note, presumably different combat styles would use different calculations for damage anyway -- missile combat needs to synthesize a "weapon" out of your bow and ammo, karate would need some means of deriving damage dice external to your weapon, etc. So there'd be no code re-use anyway.

              Comment

              • Magnate
                Angband Devteam member
                • May 2007
                • 5110

                #37
                Originally posted by Derakon
                As an added note, presumably different combat styles would use different calculations for damage anyway -- missile combat needs to synthesize a "weapon" out of your bow and ammo, karate would need some means of deriving damage dice external to your weapon, etc. So there'd be no code re-use anyway.
                True. I was thinking more about naming hierarchies than about code reuse but am let down by my lack of Python-fu. I am thinking that

                stats->melee->finesse
                stats->missile->finesse
                and friends

                are nicer to use than

                stats->meleeFinesse
                stats->missileFinesse
                and so on.

                The latter means you have many more names at the same hierarchical level, where the former is much neater and more logical. But it's quite possible that this consideration is entirely irrelevant in Pyrel, in which case I apologise for having wasted everybody's time ;-)
                "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

                  #38
                  Mm, you could certainly organize things that way -- a hierarchical system for categorizing which stats go where. You'd use nested dicts for that, not tuples, but that's pretty irrelevant. The question again is, what does this buy us in terms of code re-use or cleanliness? Is it solely a matter of having a hierarchical structure instead of a flat one?

                  I guess my concern with setting up a hierarchy like this is that there's an implicit assumption that no stat outside of a given tier will care about the stats in that tier (e.g. AC never cues off of melee finesse). Of course you're under no obligation to obey that assumption, but it bugs me when the internal structure of data doesn't reflect how that data is used. Code should be as intuitive as possible all the time; it's hard enough to understand even when everything is internally consistent!

                  That said, I'll ask some of my other coding buddies what they think and if they can come up with some strong arguments one way or another.

                  Comment

                  • Magnate
                    Angband Devteam member
                    • May 2007
                    • 5110

                    #39
                    Originally posted by Derakon
                    Mm, you could certainly organize things that way -- a hierarchical system for categorizing which stats go where. You'd use nested dicts for that, not tuples, but that's pretty irrelevant. The question again is, what does this buy us in terms of code re-use or cleanliness? Is it solely a matter of having a hierarchical structure instead of a flat one?
                    Not quite. I am also thinking that a hierarchichal structure is easier to program, so that if I want to abstract it I can refer to stats->[combatType]->Finesse ... but I guess that's just another facet of the code re-use argument so nothing new.
                    I guess my concern with setting up a hierarchy like this is that there's an implicit assumption that no stat outside of a given tier will care about the stats in that tier (e.g. AC never cues off of melee finesse). Of course you're under no obligation to obey that assumption, but it bugs me when the internal structure of data doesn't reflect how that data is used. Code should be as intuitive as possible all the time; it's hard enough to understand even when everything is internally consistent!
                    Yes, I agree with that, and it leads me to two observations:

                    1. Neatness isn't the same as intuitiveness. All the stats being in the same tier makes for a very crowded and messy namespace, but is completely intuitive if they are all of equivalent level (i.e. don't cue off one another)

                    2. The game engine shouldn't contain any assumptions which can possibly be avoided. The whole point of your object-model approach, IIUC, was to set out only the lowest level assumptions about object interactions, and allow gameplay to be determined by rules that are not hard-coded. Hence all the careful thinking about designing the stat tiers so that they did not exclude any permutations of stat influences one might want to allow.

                    Thinking more about both these, especially the latter. There are of course *some* hard-coded assumptions - there have to be to process turns and so on.
                    "Been away so long I hardly knew the place, gee it's good to be back home" - The Beatles

                    Comment

                    • Nick
                      Vanilla maintainer
                      • Apr 2007
                      • 9637

                      #40
                      Originally posted by Magnate
                      The whole point of your object-model approach, IIUC, was to set out only the lowest level assumptions about object interactions, and allow gameplay to be determined by rules that are not hard-coded. Hence all the careful thinking about designing the stat tiers so that they did not exclude any permutations of stat influences one might want to allow.

                      Thinking more about both these, especially the latter. There are of course *some* hard-coded assumptions - there have to be to process turns and so on.
                      A peripheral (you may say irrelevant) point that this made me think of - there's different strata of hard-coded. Within the current Angband code, there are things I don't touch except in extremis (z-*.c), things I try to avoid changing but will if necessary (util.c), things I know I need to change but try to keep it minimal (game-event.c), things I'm happy enough to change without thinking too hard (spells*.c) and things where I change stuff just for the hell of it (effects.c). And that's before I even get to the edit files.

                      Dunno how this applies to Pyrel
                      One for the Dark Lord on his dark throne
                      In the Land of Mordor where the Shadows lie.

                      Comment

                      • AnonymousHero
                        Veteran
                        • Jun 2007
                        • 1393

                        #41
                        Originally posted by Magnate
                        True. I was thinking more about naming hierarchies than about code reuse but am let down by my lack of Python-fu. I am thinking that

                        stats->melee->finesse
                        stats->missile->finesse
                        and friends

                        are nicer to use than

                        stats->meleeFinesse
                        stats->missileFinesse
                        and so on.

                        The latter means you have many more names at the same hierarchical level, where the former is much neater and more logical. But it's quite possible that this consideration is entirely irrelevant in Pyrel, in which case I apologise for having wasted everybody's time ;-)
                        One point in favor of this type of organization might be that you may be able to avoid the "property bucket of doom" as you see with e.g. the "struct player" in current Angband or (even worse) ToME 2.x.

                        However, it should be though about pretty carefully. Unless you gain some amount of encapsulation or reuse from doing this, you're probably just shooting yourself in the foot.

                        There may also be an argument that using strutuctures with named fields (such as "named tuples") as a single function parameter might be less error prone than using two unnamed parameters, since calling a function with multiple arguments can lead to confusion as to which argument is which, especially if the arguments have the same type or similar names or whatever. (This can be alleviated with keyword args, but unfortunately there no way to force callers to use kwargs in Python AFAIK.)

                        IME this type of thing also tends to work out best when using aggregation rather than inheritance as an organizing principle -- simply because aggregation/abstraction boundaries tend to be clearer.

                        Comment

                        • Magnate
                          Angband Devteam member
                          • May 2007
                          • 5110

                          #42
                          Originally posted by Nick
                          A peripheral (you may say irrelevant) point that this made me think of - there's different strata of hard-coded. Within the current Angband code, there are things I don't touch except in extremis (z-*.c), things I try to avoid changing but will if necessary (util.c), things I know I need to change but try to keep it minimal (game-event.c), things I'm happy enough to change without thinking too hard (spells*.c) and things where I change stuff just for the hell of it (effects.c). And that's before I even get to the edit files.

                          Dunno how this applies to Pyrel
                          Well, I was only talking about hard-coded behaviours in the game engine, I wasn't meaning to include any UI or display code, nor low-level utility code (like what RNG the game uses). But you've made me think about what I consider to be hard-coded in Angband - the things you can't change without having to edit source code and recompile. I divide these into two categories:

                          1. Things which are currently in .h files (or would be if I had ever finished what I intended to move into .h files) - these could easily be moved out of the code into modifiable text files (for some values of easily):

                          object flags (including slays and brands)
                          monster spells and their effects
                          elements
                          effects (of potions/scrolls/devices)
                          monster attack types
                          player spells

                          2. Things which are currently in .c files, which would need much more work to extract:

                          player stats and their bonuses
                          player class/race special abilities (although you can add flags for these to .txt files, they don't do anything until you add code for them)
                          combat calculations
                          monster AI
                          traps (though Gabe has extracted these in v4)
                          terrain effects
                          the scoring algorithm

                          That's not a bad list, when you consider all the things that it doesn't contain, which can be happily modified in text files: dungeon rooms, pits and vaults, stores, races and classes (other than special abilities), monsters, ego items, random names, flavours, hints, pain messages etc. etc.

                          My hope is that the object model of Pyrel will enable all these things to be not-hard-coded, though I accept that it will probably be simpler to hard-code some of them to start with.
                          "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

                            #43
                            Anything that involves actual code will be "hard-coded" in Pyrel, because the data file format is strictly a data format -- Pyrel will not be interpreting code by loading it from data files. That's generally considered a bad practice because data cannot be trusted. In practice for Pyrel or Angband in general that's not a huge issue, but it's still a bad idea to assume that any particular data you load is going to do what you expect it to do.

                            Anyway, while the data files will still be just data, that doesn't mean that they can't refer to specific code and cause functions to be invoked. Basically all of the code in proc.py (which should probably be refactored at some point because it's gonna be huge otherwise) is functions that the data files can refer to. For example, if you wanted to, say, link a specific race's STR and CHA scores (they're as strong as they appear to be!) then you could modify their stats loadout to invoke the "percentage bonus based on other stat" proc, telling it to add a bonus to STR based on CHA, and a bonus to CHA based on STR. This wouldn't actually require you to write new code, since that proc already exists.

                            An awful lot of the game can be boiled down to procs. To take a more complicated example at random, let's say we want different monsters to have different mechanisms for monsters responding to the player making noise. What we do is the following:

                            1) Create a new proc trigger condition: "on noise"
                            2) Have a default proc to call when noise is caused, which has a chance of increasing the monster's awareness (until they wake up). This mimicks the current behavior. We stick this default proc into a standard monster template that all monsters use.
                            3) Override this proc for the monsters you want to behave specially, so that they have some different reaction (e.g. noise damages them, or aggravates them, etc.). Create a new proc for the new behavior.

                            Ideally, most of the engine should just be about providing hooks that the data files can latch onto, and implementing procs to be invoked when those hooks are triggered.

                            Comment

                            • Magnate
                              Angband Devteam member
                              • May 2007
                              • 5110

                              #44
                              Originally posted by Derakon
                              Anything that involves actual code will be "hard-coded" in Pyrel, because the data file format is strictly a data format -- Pyrel will not be interpreting code by loading it from data files. That's generally considered a bad practice because data cannot be trusted. In practice for Pyrel or Angband in general that's not a huge issue, but it's still a bad idea to assume that any particular data you load is going to do what you expect it to do.

                              Anyway, while the data files will still be just data, that doesn't mean that they can't refer to specific code and cause functions to be invoked. Basically all of the code in proc.py (which should probably be refactored at some point because it's gonna be huge otherwise) is functions that the data files can refer to. For example, if you wanted to, say, link a specific race's STR and CHA scores (they're as strong as they appear to be!) then you could modify their stats loadout to invoke the "percentage bonus based on other stat" proc, telling it to add a bonus to STR based on CHA, and a bonus to CHA based on STR. This wouldn't actually require you to write new code, since that proc already exists.

                              An awful lot of the game can be boiled down to procs. To take a more complicated example at random, let's say we want different monsters to have different mechanisms for monsters responding to the player making noise. What we do is the following:

                              1) Create a new proc trigger condition: "on noise"
                              2) Have a default proc to call when noise is caused, which has a chance of increasing the monster's awareness (until they wake up). This mimicks the current behavior. We stick this default proc into a standard monster template that all monsters use.
                              3) Override this proc for the monsters you want to behave specially, so that they have some different reaction (e.g. noise damages them, or aggravates them, etc.). Create a new proc for the new behavior.

                              Ideally, most of the engine should just be about providing hooks that the data files can latch onto, and implementing procs to be invoked when those hooks are triggered.
                              Right. I didn't actually understand how that works ... yet. But I will once I have got my head around procs and the Pyrel approach.

                              In the meantime, is there anything in my previous post that you think might not be possible with procs?
                              "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

                                #45
                                Originally posted by Magnate
                                2. Things which are currently in .c files, which would need much more work to extract:

                                player stats and their bonuses
                                I'm not entirely clear on what you mean by this, since stats are defined mostly in p_race.txt and p_class.txt; perhaps you refer to the bonuses those stats provide? E.g. the extra HP you get from CON. Those are effectively already "in the data" (as data + procs) in the form of derived stat bonuses -- your max HP includes a modifier that is a CON-based value multiplied by your character level. Or it will once someone gets around to putting the relevant data in anyway. That is, when writing the player's "stats" record in the data file, you'd include an entry for "max HP" that refers to a proc which takes CON into account.
                                player class/race special abilities (although you can add flags for these to .txt files, they don't do anything until you add code for them)
                                Add triggers in the right places and you can do this with procs. For example, the hobbit's mushroom-identifying ability would be a proc that triggers on item pickup and causes the flavor of the item to be recognize if it is a mushroom. The dwarf's treasure-spotting ability would have to trigger on entity motion, I think.
                                combat calculations
                                At first glance I think this would be better off being mostly core engine logic, though of course it would invoke procs as needed when e.g. weapons hit opponents. I could be wrong.
                                monster AI
                                Proc that triggers on creature update. Of course a lot of utility functions (pathfinding, etc.) would be in the main engine.
                                traps (though Gabe has extracted these in v4)
                                terrain effects
                                Special terrain objects that have procs that trigger when entities enter their tiles.
                                the scoring algorithm
                                I never really found this to be meaningful, to be quite honest. But the way I'd handle this would be to have "score" be a stat like any other, but its modifiers would all be procs. So the base-level ones would take into account player turns, experience total, and the like, then on top of that you'd layer multipliers for class, race, and conduct options (c.f. ironman)...or whatever floats your boat, really. Anyway, all of these would be readily invokable via the data files...probably you wouldn't even need much more in the way of new procs to write since the existing PercentageStatModCalculator would handle most of it.

                                Comment

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