Pyrel dev log

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

    #31
    Thank you. All sounds grand. Hope you're still enjoying it.
    "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
      Progress has been a bit slow lately because I started playing a videogame (King's Bounty: The Legend), and boy howdy do those things have a way of sucking up your spare time. Anyway, I've completed a first attempt at allowing items to modify other items -- I can equip a Ring of Prowess and it will improve the apparent prowess bonus on my equipped weapon. However, I think I want to change the mechanism, since it doesn't handle unequipping the weapon while the ring is still equipped very well. I see two options here:

      1) When the ring is equipped, it applies a bonus to all equipped items in the appropriate slot(s), and adds a trigger to their onUnequip functions to remove that bonus.
      2) Extend the equipment system to include triggers associated with specific slots and actions; the ring would modify that instead of directly modifying the equipped item(s).

      I'm also going to need something like #2 for items that modify stuff in your inventory, since otherwise every time an item gets picked up I'll have to iterate over all my equipment to see if anything should be done about it. So I'm leaning towards #2 as a general-purpose solution.

      Currently 2314 lines, of which 536 are comments and 284 are whitespace. Not a bad ratio! Even if I'm being a bit liberal with the \todo tag...

      Comment

      • Derakon
        Prophet
        • Dec 2009
        • 9022

        #33
        I consider the first pass on equipment bonuses and procs to be complete now. Actually it was complete about a week ago but I didn't get around to posting here. There are of course tons of holes in the implementation, but they should all be missing-content holes, not missing-framework holes.

        It bugged me how stupidly slow the draw code was, so I've sped it up a touch. It's still pretty dumb, but at least it's not iterating over the contents of every visible cell on every refresh now. Doesn't mean that it won't need to be optimized further, but at least it's not nagging at my brain any more.

        I think the next serious thing to touch on is terrain, as a prelude to writing the level generation framework. I took a few looks at Angband's terrain edit file and decided I don't really want to just port that straight over like I more-or-less did with item and creature records. That means I get to come up with my own designs.

        First off, in Pyrel "terrain" isn't meaningfully distinct from any other interactable object. Walls are simply Things that sit in cells on the map and obstruct movement. Doors would be walls that can be opened. Floors are implicit. So there's no terrain layer, no traps layer, none of that.

        What does that mean as far as terrain is concerned? I was thinking that perhaps terrain is just the non-item, non-creature stuff that is added to the map during map generation. That's a pretty broad categorization, though, which means that the terrain records (the stuff that goes into the "edit files") would need to be very flexible, with each record having only very few required entries -- pretty much just the name and any display information.

        I mean, doors can be opened, locked, unlocked, bashed, or tunneled through, each of which results in some kind of state transition (well, tunneling destroys the door outright). Walls can just be tunneled through. Stairs can only be interacted with via the < and > commands. These are a lot of disparate abilities. Current Angband handles them via flags. For example, up stairs have the flags

        PWALK | PPASS | MWALK | MPASS | LOOK | EXIT_UP

        meaning that players and monsters can walk on them freely, they're notable enough to jump to with the "look" command, and they serve as an upwards-facing exit out of levels. Some of them make sense as flags -- walkability, for example, is already effectively handled as a flag in Pyrel (walkable objects are not in the BLOCKERS container). Some of them I'd want to represent as a state transition, though.

        For example, opening a door results in an open door; clearly the door should be a member of the OPENABLE container and thus implement the onOpen function. That's nice an atomic. But bashing a door down is not atomic -- the player can weaken the door several times before getting through, while any individual bash attempt may be completely ineffectual. Moreover the difficulty of bashing a door increases with depth. Tunneling through a wall is atomic (you either succeed or you fail, with no memory), but the wall may contain an item (in rubble, or treasure veins), which may or may not be visible before tunneling, and whose value depends on depth. So how do I represent all that?

        What we're basically dealing with here are building blocks for levels. However, I suspect they need to be agnostic of the levels they're being placed into, because so much of that depends on the level generation code. For example, while I could directly encode the difficulty of picking a lock into the terrain record as e.g. 2+1d4M8 or something, who's to say that the level generator wouldn't want to make a vault blocked off by nigh-impossibly-difficult locks?

        I'm starting to wonder if I shouldn't forgo a terrain edit file altogether and just directly instantiate a bunch of classes for the different types of terrain I want to have. It seems like it would be more or less impossible to have anything except a cosmetic difference without modifying the code anyway, unless I want to make for really complicated terrain records.

        I don't have any firm answers, yet, but this is the kind of thing I'm thinking about. Feel free to chime in if you have suggestions.

        Comment

        • Nick
          Vanilla maintainer
          • Apr 2007
          • 9637

          #34
          Originally posted by Derakon
          I'm starting to wonder if I shouldn't forgo a terrain edit file altogether and just directly instantiate a bunch of classes for the different types of terrain I want to have. It seems like it would be more or less impossible to have anything except a cosmetic difference without modifying the code anyway, unless I want to make for really complicated terrain records.
          One of the things to think about here is how many different terrain flags you have, and in how many ways they can be put together. If the code is all based on flags and there are enough of them, you can (for example) add a terrain type which holds objects, doesn't hold traps, blocks line of sight but not spells and is illuminated by torchlight quickly and easily to an edit file without having to mess with code at all. In that case I guess you would have classes generated by the edit file ... but I haven't really thought that bit through.
          One for the Dark Lord on his dark throne
          In the Land of Mordor where the Shadows lie.

          Comment

          • Derakon
            Prophet
            • Dec 2009
            • 9022

            #35
            You're right that there are clearly aspects of terrain that should be handled by flags. Visibility, walkability, etc. should be handled by flags. I was thinking more about the terrain transitions and how to properly handle them in code. Here are some examples of possible terrain transitions:

            * closed door -> (open) -> open door
            * closed door -> (bash) -> (open door or broken door)
            * repeat above for locked and jammed doors
            * most walls -> (tunnel) -> no terrain
            * curtain -> (open or move) -> opened curtain

            This kind of thing makes me want to encode the valid state transitions into the terrain record. The main problem here is that many of the transitions have a difficulty rating and/or a progress value, or may generate items when completed (for treasure veins or rubble), or the like. So there's a lot of complexity here.

            It seems clear I should be trying to leverage the proc/effect code here somehow. Procs are stateful, so I could handle progress that way. That still leaves me with the problem of how to represent all this in the records, though. And I really can't see a way I can avoid having a separate entry for each of normal, locked, and jammed doors, for example...

            (Note that I'm omitting a bunch of stuff like visibility/walkability/etc. flags, and display info, for the sake of brevity)
            Code:
            {
                "name": "granite wall",
                "interactions": (
                    {
                        "action": "tunnel",
                        "procs": ({"name": "tunnelWall", "difficulty": 1000})
                    }
                )
            },
            {
                "name": "quartz vein",
                "interactions": (
                    {
                        "action": "tunnel",
                        "procs": ({"name": "tunnelWall", "difficulty": "200})
                        )
                    }
                )
            },
            {
                "name": "quartz vein with treasure",
                "ancestor": "quartz vein",
                "interactions": (
                    {
                        "action": "tunnel",
                        "postProcs": ({"name": "dropTreasure", "amount": "10+5d10M800"})
                    }
                )
            },
            {
                "name": "door",
                "interactions": (
                    {
                        "action": "open",
                        "procs": ({"name": "replaceTerrain", "replacement": "open door"})
                    },
                    {
                        "action": "bash",
                        "procs": ({"name": "bashDoor", "difficulty": "1+M10"}, 
                            {"name": "replaceTerrain", "replacement": "broken door"}
                        )
                    }
                )
            },
            {
                "name": "locked door",
                "ancestor": "door",
                "interactions": (
                    {
                        "action": "open",
                        "preProcs": ({"name": "unlockDoor", "difficulty": "1+M10"})
                    }
                )
            }
            So the way I envision the procs working here is, they execute in a chain, and each one has the ability to halt execution of the chain. For example, when bashing a door, first you have to pass the "bashDoor" proc's difficulty check before you're allowed to execute the "replaceTerrain" proc. Terrain that inherits from other terrain can insert its procs before or after the procs that the ancestor terrain implements -- for example, quartz vein with treasure adds on a proc after the "tunnelWall" proc, which thus will only execute when tunnelWall returns True -- i.e. when tunnelling succeeds.

            This is pretty verbose, and it doesn't let you bash a door and end up with a non-broken door afterwords. And I'm not certain that the execution-chain system is going to handle all the different use cases I can think of.

            Comment

            • Derakon
              Prophet
              • Dec 2009
              • 9022

              #36
              Okay, another post about terrain, with still no actual code written yet. That's okay! Far better to spend X time now thinking of a good system than to spend 5X time down the road rewriting a bad one (and then still having to spend the X to think of a good system anyway!).

              I saw Magnate ask on IRC for some information about how terrain fits into the object model, hence the post.

              Pyrel's tiles are general-purpose containers that can hold just about anything, with no requirements on quantity or type. So you could theoretically stuff any number of monsters into a single tile if you wanted to -- though behaviorally this is blocked because each monster is an obstacle to movement by any other monster or the player. Similarly, you could have multiple traps, multiple terrain objects, or of course multiple items.

              Magnate, you suggested basically having arbitrary characteristics that could be associated with a given terrain object -- for example lock difficulty, or tunnel difficulty, or degree of LOS obstruction, etc. That is absolutely doable; the main question then becomes how we cleanly integrate those kinds of features into the main codebase, which is what I've been discussing earlier.

              We could possibly stick multiple terrain objects into a single tile, representing a locked door as a lock + a door. Then the lock would block all attempts at opening until it is picked (at which point it is destroyed), and the door would block all attempts at moving through the tile. Of course, to do this properly we need to handle multiple actionable items in a single tile -- when you say you want to open what is on that tile, the game needs to first go to the lock, and not the door. This would also make the act of 'l'ooking around more complicated -- how do we know to represent the lock + door pairing as a "locked door"?

              Comment

              • fizzix
                Prophet
                • Aug 2009
                • 3025

                #37
                Originally posted by Derakon
                Okay, another post about terrain, with still no actual code written yet. That's okay! Far better to spend X time now thinking of a good system than to spend 5X time down the road rewriting a bad one (and then still having to spend the X to think of a good system anyway!).

                I saw Magnate ask on IRC for some information about how terrain fits into the object model, hence the post.

                Pyrel's tiles are general-purpose containers that can hold just about anything, with no requirements on quantity or type. So you could theoretically stuff any number of monsters into a single tile if you wanted to -- though behaviorally this is blocked because each monster is an obstacle to movement by any other monster or the player. Similarly, you could have multiple traps, multiple terrain objects, or of course multiple items.

                Magnate, you suggested basically having arbitrary characteristics that could be associated with a given terrain object -- for example lock difficulty, or tunnel difficulty, or degree of LOS obstruction, etc. That is absolutely doable; the main question then becomes how we cleanly integrate those kinds of features into the main codebase, which is what I've been discussing earlier.

                We could possibly stick multiple terrain objects into a single tile, representing a locked door as a lock + a door. Then the lock would block all attempts at opening until it is picked (at which point it is destroyed), and the door would block all attempts at moving through the tile. Of course, to do this properly we need to handle multiple actionable items in a single tile -- when you say you want to open what is on that tile, the game needs to first go to the lock, and not the door. This would also make the act of 'l'ooking around more complicated -- how do we know to represent the lock + door pairing as a "locked door"?
                It seems like you have two options. The first is to do what current angband does and have every possible terrain have a unique entry along with its descriptions. You could imagine this would not be so good because you have silliness like 10 different doors for varying difficulties of lockedness or stuckness.

                The second approach, where you allow multiple terrains to be on the same square seems like the better way to go. Especially as this would allow additional effects like lingering poison or fog clouds.

                When you attempt to interact with a terrain tile you interact with all the terrains on it. So the door + lock works as you said. To determine what to see when looking you probably need a priority value so you know which to display. In this case door would probably have priority so you'd see just a plain door, or closed door on looking. You won't know it's locked until you try to open it. You could go further and say that the lock is hidden until you attempt to open it, and then it's not hidden. Just like a trap is hidden until you discover it.

                If you really want a player to be able to tell that the door is locked, you can allow for a "verbose" command on look that tells you all the known properties of the tile. In the locked door case it might be something like:
                • floor
                • closed door
                • door lock


                So you can imagine some of the characteristics that go into a general terrain feature as something like
                • name
                • look priority (door > door lock > floor)
                • action priority (door lock > door > floor)
                • strength (i.e. difficulty of tunneling, picking, or damage from poison cloud)
                • duration
                • movement cost
                • sound impedance (we *might* want this someday)
                • sight impedance (right now we only have 0, or infinite. But you can imagine something like fog which shortens MAX_SIGHT when looking through the fog)
                • projectile impedance (again, right now we're either 0 or infinite)

                Comment

                • Nick
                  Vanilla maintainer
                  • Apr 2007
                  • 9637

                  #38
                  Originally posted by Derakon
                  We could possibly stick multiple terrain objects into a single tile, representing a locked door as a lock + a door. Then the lock would block all attempts at opening until it is picked (at which point it is destroyed), and the door would block all attempts at moving through the tile. Of course, to do this properly we need to handle multiple actionable items in a single tile -- when you say you want to open what is on that tile, the game needs to first go to the lock, and not the door. This would also make the act of 'l'ooking around more complicated -- how do we know to represent the lock + door pairing as a "locked door"?
                  Are you regarding traps as terrain? Because if not, you could treat the lock as a trap which needs to be disarmed before the door can open.
                  One for the Dark Lord on his dark throne
                  In the Land of Mordor where the Shadows lie.

                  Comment

                  • Derakon
                    Prophet
                    • Dec 2009
                    • 9022

                    #39
                    I ended up going with the system I described with the giant codeblock example a few posts up (the one with the "preProcs", "procs", and "postProcs" lists). It seemed the least objectionable. I've implemented tunneling and opening of doors; the appropriate terrain transformations occur successfully. At this point I think I'm going to want to create a more generic "transform terrain into other terrain" proc, and clean up how we handle determining if a given terrain is eligible for a given interaction -- but the basics are there, so that's progress.

                    The way variable stuff is handled is by using the same "boosted die" mechanic that is used to decide item powers (the "A + XdY MZ" format. Both the terrain flags and the procs can thus have random variance as well as a level-dependent component.

                    Comment

                    • Magnate
                      Angband Devteam member
                      • May 2007
                      • 5110

                      #40
                      Originally posted by Derakon
                      The way variable stuff is handled is by using the same "boosted die" mechanic that is used to decide item powers (the "A + XdY MZ" format. Both the terrain flags and the procs can thus have random variance as well as a level-dependent component.
                      Neat! I hadn't thought of that.

                      It's a good job you don't play Diablo III ...
                      "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

                        #41
                        I played the heck out of Diablo 2, but as soon as I heard about D3 not being moddable (even before we heard that it would be online-only), I lost interest. Unmodded D2 is a pretty straight-up clickfest, but the mods created so much more depth in terms of skill balance, item crafting, mob difficulty, and so on, that pretty much as soon as I tried them I lost all interest in the unmodded game. I fully expected D3 to have a similar problem, and now it's unfixable. Oh well...

                        Anyway, I went ahead and implemented the generic "replace terrain with other terrain" proc, as well as a first attempt at a message proc that does some basic string substitution (the "<2>" in the string means "replace this with the string representation of the second argument to the proc", which is really rather horribly hackish).

                        Eventually I'd like to implement some kind of generic "skill check" that would replace the specific "tunnel" proc currently used for the granite wall definition, as well as future procs for picking locks, aiming wands, etc. that all basically boil down to "the player has a chance of succeeding based on some formula." The implementation difficulty would be in how to handle the skill lookup / task difficulty, since the proc definition in terrain.txt doesn't have direct access to the object that represents the actor.

                        Here's terrain.txt. It's a bit wordy but I think it gets the job done pretty well.
                        Code:
                        [
                            {
                                "name": "granite wall",
                                "display": {"ascii": {"symbol": "#", "color": [180, 180, 180]}},
                                "flags": ["OBSTRUCT"],
                                "interactions": [
                                    {
                                        "action": "tunnel",
                                        "procs": [{"name": "tunnel", "difficulty": 2}]
                                    }
                                ]
                            },
                            {
                                "name": "door",
                                "display": {"ascii": {"symbol": "+", "color": [128, 64, 0]}},
                                "flags": ["OBSTRUCT"],
                                "interactions": [
                                    {
                                        "action": "open",
                                        "procs": [{"name": "print smart message", 
                                                   "messageString": "<2> opened the door."},
                                                  {"name": "replace terrain", "newTerrain": "open door"}]
                                    }
                                ]
                            },
                            {
                                "name": "open door",
                                "display": {"ascii": {"symbol": "'", "color": [128, 64, 0]}},
                                "interactions": [
                                    {
                                        "action": "close",
                                        "procs": [{"name": "print smart message",
                                                   "messageString": "<2> closed the door."},
                                                  {"name": "replace terrain", "newTerrain": "door"}]
                                    }
                                ]
                            },
                            {
                                "name": "decorative floor tile",
                                "display": {"ascii": {"symbol": ".", "color": [128, 128, 255]}}
                            }
                        ]

                        Comment

                        • Magnate
                          Angband Devteam member
                          • May 2007
                          • 5110

                          #42
                          Originally posted by Derakon
                          Eventually I'd like to implement some kind of generic "skill check" that would replace the specific "tunnel" proc currently used for the granite wall definition, as well as future procs for picking locks, aiming wands, etc. that all basically boil down to "the player has a chance of succeeding based on some formula." The implementation difficulty would be in how to handle the skill lookup / task difficulty, since the proc definition in terrain.txt doesn't have direct access to the object that represents the actor.
                          I assume your use of "actor" means that this generic skill check won't be limited to the player, which is good. This lack of access is exactly the problem I had with my effects theorycrafting, so I've been waiting eagerly to see how you solve this.

                          My other problem was the control of the factors affecting difficulty: presumably the correct place to resolve them all is in the proc itself?
                          "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
                            Originally posted by Magnate
                            I assume your use of "actor" means that this generic skill check won't be limited to the player, which is good. This lack of access is exactly the problem I had with my effects theorycrafting, so I've been waiting eagerly to see how you solve this.
                            It's a tricky problem. The best idea I've had so far is to do the following:

                            1) Assume the actor is a Creature. This seems fairly safe.
                            2) Add two skill mappings for the Creature class -- the intrinsic skills and the skills modified by equipment and temporary effects (much like the way item properties are currently handled, and in fact I would probably use the same code).
                            3) Promote the "boosted die" equation system into a full-on equation evaluator with smart string substitution -- the code would search for key strings like "<actor.magicDevice>" or "<target.savingThrow>" and insert the appropriate skill values. The annoying thing about this is that actually solving the equation would require either eval(), which is a security hole (it amounts to "run this string as Python code"), or writing my own math parser, which seems like overkill.

                            But I'll keep thinking about it.

                            My other problem was the control of the factors affecting difficulty: presumably the correct place to resolve them all is in the proc itself?
                            Right. The data files have proc declarations, which may include variable parameters. When the proc is created, it generates a mapping of parameter names to parameter values, which it will need when it is triggered -- but at creation, it may decide to evaluate some or all of its parameters. For example, the difficulty of activating a wand is a parameter that is decided at proc creation, because it doesn't change from one attempt to another. But the amount of damage that wand deals would be a parameter that is decided when the proc is triggered.

                            Comment

                            • Narvius
                              Knight
                              • Dec 2007
                              • 589

                              #44
                              Let's say you have "<actor.attack> + 1d20". Do all the string substitution and randomizing on the string. So, you get for example "3 + 12". Once you're down to only numbers and mathematical operators, run it through eval. This won't require any further parsing, since you only really manipulate single tokens, which doesn't require any context, while the final calculating is done by Python. And I don't think any harm can done by mathematical expressions, so it's safe despite using eval (if done right, of course).
                              If you can convincingly pretend you're crazy, you probably are.

                              Comment

                              • Derakon
                                Prophet
                                • Dec 2009
                                • 9022

                                #45
                                Originally posted by Narvius
                                Let's say you have "<actor.attack> + 1d20". Do all the string substitution and randomizing on the string. So, you get for example "3 + 12". Once you're down to only numbers and mathematical operators, run it through eval. This won't require any further parsing, since you only really manipulate single tokens, which doesn't require any context, while the final calculating is done by Python. And I don't think any harm can done by mathematical expressions, so it's safe despite using eval (if done right, of course).
                                The problem is recognizing e.g. "import os; os.rmdir('~')" in the string you're going to eval.

                                There may well be a mathematical-expression evaluator that would ban anything else, but it's probably not a built-in and thus would introduce another dependency for developers to have to install before they could work on Pyrel.

                                Comment

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