Pyrel dev log, part 5

Collapse
X
 
  • Time
  • Show
Clear All
new posts

  • Derakon
    replied
    Originally posted by Magnate
    These don't seem to allow for spells which deliver multiple effects (e.g. cure poison, heal hp, remove stunning etc.). Are you clear on the differences between spells and procs? I had always imagined that the "atomic" effects would be procs, and that spells would be calls of one or more procs with certain variables set (e.g. amounts of damage/healing, range etc.).
    Mm, this is a fair point. I see a few possible options; the simplest would probably be for the code that parses spells from the creature record to check a new file, spells.txt, before it goes to proc_templates.txt. spells.txt would allow for spells that chain several procs together. If no entry in spells.txt is found for the specified spell name, then proc_templates.txt would serve as a fallback option.

    I can probably help here, since he wrote it for me to do the evaluations of formulae in lootTemplates. Obviously d_m himself could be more help when he's around ;-)
    Right, I spoke with him a bit while I was working on extending the evaluator; if he finds the spare time to work on it then that'd be ideal. But I don't think it's the end of the world to use '#' as the operator, so this is pretty low-priority for me.

    The question arises as to who, if anyone, will bother to add all the edge cases to make a version of Pyrel that plays exactly like a version of V - and, if nobody does, what that will mean for Pyrel. It won't be as different as Sil or FA, but different enough to dissatisfy V purists.
    What I expect will happen is that people will playtest, say "I don't like that this particular edge case isn't handled", and then someone will implement that edge case. And the ones that aren't complained about won't be changed.

    On a point of detail, I see no reason why DamageCap should be a property of an element, other than that this is how V does it. Logically damage caps should be set on a per-instance basis (i.e. part of the spell definition in the monster record). That would be much more flexible.
    This is the default damage cap; if you want to specify a different one for a specific spell then you can of course do so. I haven't added the code for applying damage caps yet, though.

    Leave a comment:


  • Magnate
    replied
    Originally posted by Derakon
    I'm semi-seriously considering adding The Filthy Street Ürchin as a secret boss character now.
    Do it!
    I'm pretty satisfied with the basic spellcasting framework now. I've added an element system, including acid damage to armor, destruction of inventory items, and destruction of floor items. Elemental effects are defined in a new file, element.txt:
    I'm very encouraged to see this. I got as far as speccing out an element.txt for V/v4 before you started Pyrel. My collected thoughts are at http://trac.rephial.org/wiki/NewEffects, in case it is helpful.
    New spells can be created fairly easily; here's some example spell definitions:
    These don't seem to allow for spells which deliver multiple effects (e.g. cure poison, heal hp, remove stunning etc.). Are you clear on the differences between spells and procs? I had always imagined that the "atomic" effects would be procs, and that spells would be calls of one or more procs with certain variables set (e.g. amounts of damage/healing, range etc.).
    The # syntax in the spell definitions is representing dice rolling in Pyrel's expression evaluator -- I couldn't use "d" because the evaluator assumes that alphabetic strings indicate variable names, and I don't understand the code well enough (d_m wrote it) to change that.
    I can probably help here, since he wrote it for me to do the evaluations of formulae in lootTemplates. Obviously d_m himself could be more help when he's around ;-)
    One thing I came to realize while working on all this is that Pyrel will be playable well before it has all of the weird little edge cases of Vanilla. For example:

    * No diminishing returns on speed
    * All elemental attacks have the same damage cap regardless of source (c.f. darkness storms in Vanilla beating out the cap on darkness damage)
    * No stacking temporary/permanent resists
    * Square field of view / square explosions

    The reason for this is that I'm focusing on implementing the core functionality first, and just leaving room for edge cases for later -- but because they're edge cases they have relatively little impact on gameplay and I'm sure someone will be playing the game as soon as it's winnable.

    As a side effect, we should get some interesting insight on how necessary some of these edge cases are. I look forward to it.

    Similarly, I'm not bothering to look too closely at Vanilla's formulae right now. For example, I've set the damage-over-time from cuts to be (timer / 10) HP per turn, and for poison to be (timer / 50) (and meanwhile, the timer for poison is set to be (damage / 5) turns). I expect Vanilla's values to be completely different, but I'm not especially bothered by that on the assumption that most of these values were never really seriously inspected to begin with.

    Of course, changing those is trivial, so if we decide that Vanilla's values are really the ones to use, then putting them in is easy. I just haven't wanted to spend time on tracking down the relevant source code.
    I'm sure this is the right approach. The question arises as to who, if anyone, will bother to add all the edge cases to make a version of Pyrel that plays exactly like a version of V - and, if nobody does, what that will mean for Pyrel. It won't be as different as Sil or FA, but different enough to dissatisfy V purists.

    On a point of detail, I see no reason why DamageCap should be a property of an element, other than that this is how V does it. Logically damage caps should be set on a per-instance basis (i.e. part of the spell definition in the monster record). That would be much more flexible.

    Leave a comment:


  • Derakon
    replied
    Originally posted by scud
    *crosses off Debug Town from list of places to visit*
    I'm semi-seriously considering adding The Filthy Street Ürchin as a secret boss character now.

    I'm pretty satisfied with the basic spellcasting framework now. I've added an element system, including acid damage to armor, destruction of inventory items, and destruction of floor items. Elemental effects are defined in a new file, element.txt:
    Code:
        {
            "name": "acid",
            "damageCap": 1600,
            "display": {"ascii": {"color": "L_GREEN"}},
            "procs": [
                {
                    "name": "damage equipment",
                    "message": "was damaged by absorbing acid!",
                    "failMessage": "was undamaged!",
                    "validSlots": ["back", "body", "feet", "hands",
                        "head", "shield"],
                    "damagedStat": "armorMod",
                    "capStat": "baseArmor",
                    "HPDamageMultiplier": 0.5
                }
            ]
        }
    New spells can be created fairly easily; here's some example spell definitions:
    Code:
        {
            "name": "haste self",
            "code": "temporary stat mod",
            "target": "self",
            "statName": "speed", "modAmount": 1, "duration": "10+d10",
            "message": "%s speeds up"
        },
        {
            "name": "poison bolt",
            "code": "launch projectile",
            "target": "creature",
            "damage": "level#6", "element": "poison",
            "message": "%s casts a poison bolt"
        },
        {
            "name": "fire ball",
            "code": "launch explosive",
            "target": "creature",
            "damage": "level#6", "radius": 3, "element": "fire",
            "message": "%s casts a fire ball"
        }
    And here's (part of) a creature record using those spells:
    Code:
        "name": "Filthy street urchin",
        "nativeDepth": 0,
        "experienceValue": 0,
        "rarity": 2,
        "magic": {
            "frequency": 1,
            "spells": ["fire ball", "haste self"]
        },
    The # syntax in the spell definitions is representing dice rolling in Pyrel's expression evaluator -- I couldn't use "d" because the evaluator assumes that alphabetic strings indicate variable names, and I don't understand the code well enough (d_m wrote it) to change that.

    One thing I came to realize while working on all this is that Pyrel will be playable well before it has all of the weird little edge cases of Vanilla. For example:

    * No diminishing returns on speed
    * All elemental attacks have the same damage cap regardless of source (c.f. darkness storms in Vanilla beating out the cap on darkness damage)
    * No stacking temporary/permanent resists
    * Square field of view / square explosions

    The reason for this is that I'm focusing on implementing the core functionality first, and just leaving room for edge cases for later -- but because they're edge cases they have relatively little impact on gameplay and I'm sure someone will be playing the game as soon as it's winnable.

    As a side effect, we should get some interesting insight on how necessary some of these edge cases are. I look forward to it.

    Similarly, I'm not bothering to look too closely at Vanilla's formulae right now. For example, I've set the damage-over-time from cuts to be (timer / 10) HP per turn, and for poison to be (timer / 50) (and meanwhile, the timer for poison is set to be (damage / 5) turns). I expect Vanilla's values to be completely different, but I'm not especially bothered by that on the assumption that most of these values were never really seriously inspected to begin with.

    Of course, changing those is trivial, so if we decide that Vanilla's values are really the ones to use, then putting them in is easy. I just haven't wanted to spend time on tracking down the relevant source code.

    Leave a comment:


  • scud
    replied
    Originally posted by Derakon
    the filthy street urchin in Debug Town will fling firebolts around with reckless abandon now, and those firebolts will damage items in the player's inventory
    *crosses off Debug Town from list of places to visit*

    Leave a comment:


  • Derakon
    replied
    That approach to calling Procs seems to be working well so far. I've made some progress with monster spellcasting -- the filthy street urchin in Debug Town will fling firebolts around with reckless abandon now, and those firebolts will damage items in the player's inventory. In fact all elemental damage to inventory items should work now; I still need to do damage to equipment (from acid/disenchantment) and damage to floor items -- but floor items will require area-of-effect spells first.

    Leave a comment:


  • Magnate
    replied
    Seems eminently sensible to me. The whole point of procs is to use them for as many things as possible! It just means a bit more code in each proc to disentangle different argument combinations, but IMO that's worthwhile for cleaner calling code.

    Leave a comment:


  • Derakon
    replied
    I've started work on creature spellcasting, on the assumption that a complete creature AI will make the game much more interesting. I've run into a bit of a problem with the current Proc system.

    As it stands, the parameters that are passed to a Proc's trigger() function (where it takes effect) depend on how the Proc is triggered. For example, if the Proc is triggered by using an item, then the parameters are the item user, the item itself, and the gameMap (i.e. current game state):
    Code:
    def trigger(self, user, item, gameMap):
    If the Proc is triggered by casting a spell, then the parameters ought to be the spellcaster, the target, and the gameMap:
    Code:
    def trigger(self, source, target, gameMap):
    Ideally I should be able to use the same Proc for both e.g. potions of Speed and the spell Haste Self (both the player and monster versions). The Proc that handles temporary speed boosts doesn't actually care about the item being used, so that's fine. But as it stands, the caller would have to know what kinds of parameters the Proc expects, which makes the caller's code very messy as it must arrange arguments in the way the Proc expects.

    Now, obviously, if a Proc requires certain bits of information, then that information has to be provided to the Proc. And some Procs will need to know what item was used to invoke them -- but those Procs will presumably only ever be used in conjunction with items. Other Procs that are more general need to be more flexible. So here's what I'm thinking.

    In Python (and many other languages) you can write functions that accept any arguments. In Python's case, that works like this:
    Code:
    def trigger(self, **kwargs):
    The "kwargs" parameter is a dictionary that maps parameter names to their values. So if you called the trigger() function like this:
    Code:
    proc.trigger(item = speedPotion, target = player, gameMap = gameMap)
    then the dictionary would contain the keys "item", "target", and "gameMap". If you called it like this:
    Code:
    proc.trigger(caster = player, target = player, gameMap = gameMap)
    then the dictionary would contain "caster", "target", and "gameMap" instead. As far as the Proc is concerned, as long as the "target" parameter is present, everything's fine -- that's the only parameter it actually needs to function.

    I think this will work, but I'm putting it out here in case there's something I missed. Feedback is welcome.

    Leave a comment:


  • Derakon
    replied
    I wouldn't worry about the display code -- it's pretty zippy right now. If we ever need even more speed out of it then I can rewrite it to use OpenGL rendering, which would be even faster than C code could ever manage.

    In any event, the golden rule of optimization: first make it work, then if it's not fast enough, measure its speed, make changes, and verify that those changes actually improved things (and didn't make it not work).

    Leave a comment:


  • LostTemplar
    replied
    I mean that it is probably good idea to write display code in C also to make sure that it is fast.
    Maybe there are some other bottlenecks worth rewriting in C. Something, that is very unlikely to be changed in any variant.

    Leave a comment:


  • Derakon
    replied
    Originally posted by LostTemplar
    Just do the same with display code (with several different ways to display characters) and it will be fine.
    To be sure I understand, are you suggesting that we have multiple implementations of the heatmap code that are either user-selectable or that the game automatically picks between? Because otherwise I'm not sure how the display code is relevant to the current conversation.

    Leave a comment:


  • LostTemplar
    replied
    Antoine: basically, the C code is compiled into a shared library, and then you use the ctypes library (a builtin Python library) to load it and do the necessary datatype conversions.
    Just do the same with display code (with several different ways to display characters) and it will be fine.

    Leave a comment:


  • Derakon
    replied
    Cython was the first thing I tried when trying to optimize the Python version. It cut runtime to 1/3rd the "pure Python" version, but that was still too slow.

    Antoine: basically, the C code is compiled into a shared library, and then you use the ctypes library (a builtin Python library) to load it and do the necessary datatype conversions. I'll see about pushing a branch to my repo later today so you can see what the code looks like.

    Leave a comment:


  • fph
    replied
    Have you considered Cython?

    Leave a comment:


  • Antoine
    replied
    Derakon, would you be so kind as to tell us how (vaguely) the C-called-from-Python thing works?

    A.

    Leave a comment:


  • Derakon
    replied
    A friend of mine from outside the Angband community was kind enough to whip up some C code for implementing the heat maps needed for pathfinding. They're about a billion times faster than the best-optimized Python code I could manage, which is a bit frustrating but there you go. This does leave us with some questions, though:

    1) Relying on this will require Pyrel to have a build step. It's pretty trivial -- just a single invocation of gcc with no external dependencies -- but it's a build step. Of course you only need to recompile if you change the compiled code, so as long as you a) already have a compiled version for your platform, and b) never need to modify it, you don't actually need a compiler. So we could e.g. check in precompiled binaries into the git repository and automatically select the one appropriate for your platform at runtime, for example.

    Normally Python projects have a setup.py script that, when run, does all of the necessary installation stuff. I've never made one of these, and I'm not clear how they decide what compiler to use. I'm also not clear that that's the right thing to do for a game project which expects to be run as a standalone application (as opposed to a Python library that will be used by other developers, which is the more usual case I've seen). Certainly people who just want to play Pyrel should not be required to have a compiler installed.

    2) Do we ditch the pure-Python implementation or not? It could still be used as a fallback, but it is significantly slower (at least an order of magnitude).

    3) This is a purely stylistic thing, but the C implementation uses a large bitfield (as an array of bytes whose individual bits are twiddled directly) to represent which cells are obstructed. We could keep it that way, or eat a (trivial) 8x increase in memory costs and work with 1-byte booleans. Or an unknown increase in memory cost and work with platform-appropriate ints. Opinions?

    Leave a comment:

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