Thank you. All sounds grand. Hope you're still enjoying it.
Pyrel dev log
Collapse
X
-
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
-
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
-
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 for the Dark Lord on his dark throne
In the Land of Mordor where the Shadows lie.Comment
-
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"}) } ) }
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
-
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
-
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"?
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
-
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"?One for the Dark Lord on his dark throne
In the Land of Mordor where the Shadows lie.Comment
-
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
-
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 BeatlesComment
-
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
-
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.
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 BeatlesComment
-
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?Comment
-
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
-
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).
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
Comment