Pyrel dev log, part 3
Collapse
X
-
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
-
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.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"."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
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: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?Code:getDamage(stats['meleeFinesse'], stats['meleeProwess']) getDamage('melee')Comment
-
That has to be decided on a case-by-case basis. The usual answer is "no" (Law of Demeter).Basically the difference boils down to doing one of these two things: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?Code:getDamage(stats['meleeFinesse'], stats['meleeProwess']) getDamage('melee')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 2011Comment
-
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
-
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 thatAs 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.
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 BeatlesComment
-
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
-
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.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?
Yes, I agree with that, and it leads me to two observations: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!
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 BeatlesComment
-
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.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.
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
-
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.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 ;-)
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
-
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: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
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 BeatlesComment
-
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
-
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.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.
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 BeatlesComment
-
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.
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.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)
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.combat calculations
Proc that triggers on creature update. Of course a lot of utility functions (pathfinding, etc.) would be in the main engine.monster AI
Special terrain objects that have procs that trigger when entities enter their tiles.traps (though Gabe has extracted these in v4)
terrain effects
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.the scoring algorithmComment
Comment