Angband.oook.cz
Angband.oook.cz
AboutVariantsLadderForumCompetitionComicScreenshotsFunniesLinks

Go Back   Angband Forums > Angband > Variants

Reply
 
Thread Tools Display Modes
Old June 1, 2012, 21:24   #41
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,047
Derakon is on a distinguished road
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]}}
    }
]
Derakon is offline   Reply With Quote
Old June 2, 2012, 11:24   #42
Magnate
Angband Devteam member
 
Join Date: May 2007
Location: London, UK
Posts: 5,054
Magnate is on a distinguished road
Send a message via MSN to Magnate Send a message via Yahoo to Magnate Send a message via Skype™ to Magnate
Quote:
Originally Posted by Derakon View Post
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?
__________________
"3.4 is much better than 3.1, 3.2 or 3.3. It still is easier than 3.0.9, but it is more convenient to play without being ridiculously easy, so it is my new favorite of the versions." - Timo Pietila
Magnate is offline   Reply With Quote
Old June 2, 2012, 18:00   #43
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,047
Derakon is on a distinguished road
Quote:
Originally Posted by Magnate View Post
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.

Quote:
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.
Derakon is offline   Reply With Quote
Old June 2, 2012, 21:46   #44
Narvius
Knight
 
Narvius's Avatar
 
Join Date: Dec 2007
Location: Poland, Katowice
Age: 25
Posts: 588
Narvius is on a distinguished road
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.
Narvius is offline   Reply With Quote
Old June 2, 2012, 22:21   #45
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,047
Derakon is on a distinguished road
Quote:
Originally Posted by Narvius View Post
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.
Derakon is offline   Reply With Quote
Old June 4, 2012, 08:52   #46
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,320
AnonymousHero is on a distinguished road
A recursive descent expression parser/evaluator should be pretty easy to do. If you have a grammar without left recursion you can translate it pretty directly into simple python code. The same wiki page has an example grammar of which a subset is expressions and the necessary C code to implement expression parsing. (Though I wouldn't recommend a direct translation of that code -- it looks pretty old-school what with global state, etc.)

Tokenization is easily handled with regexes. (A simple dictionary of regex->token type should work ok for a simple expression language.)
AnonymousHero is offline   Reply With Quote
Old June 4, 2012, 19:08   #47
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,047
Derakon is on a distinguished road
I don't really like the idea of implementing my own parser into Pyrel, mostly because parsers are solved problems -- writing my own doesn't really add anything interesting beyond new bug opportunities. This ought to be a situation in which there's a readily-available library that does what I need (once I substitute in the appropriate values for the strings we care about, of course).

Anyway, it's not critical that I tackle that problem right now, so I'm moving on to map generation. The current map generator is completely hardcoded, so obviously it has to go. Here's how I plan the new sequence to work:

We have the GameMap class instance. This holds all of the entities in the game. We create a single instance of this class at the start, and it remains for the duration of the game. It persists across levels largely because there are other entities that also persist across levels, and the easiest way to handle this is to have a "PERSISTENT" container in the GameMap instance that I can add those entities to. Examples of persistent entities would be the player and everything they're carrying; shops; hypothetical monsters that are allowed to chase the player across levels; etc. Note that the GameMap can contain entities that are not "physically present" on the current level.

When we want to generate a new level, we call GameMap.makeLevel(level). "level" here is a numeric argument indicating the depth. Obviously in variants this could be extended to specify which dungeon you're in, or to differentiate between different "level 0" maps instead of implicitly assuming the town. makeLevel() then invokes a function to decide on what map generator to use.

As of v4 we have four options: town, standard dungeon, labyrinth, and cavern. However, given that we've talked about mix&matching these generators (having levels that are largely "vanilla" but mix in a bit of natural caverns, for example), it seems likely that instead each "generator" will consist of calling a selection of functions that form the actual map. For example, everyone will share the same magma/quartz seam generator code; likewise, everyone needs the same rules for deciding if down staircases are generable (e.g. because the player's at level 99 and Sauron still lives). Still, a high-level function will be needed to decide how those different sub-functions are organized and invoked. And of course we need different rules for placing monsters and items as well.

We'll need an Allocator class that can handle picking monsters and items -- not just for level generation, but also for item drops and summons. Monsters and items use the same basic "rarity" mechanic, so the Allocator can iterate through the entirety of the monster/item records, find everything that's in-depth, sum up their rarities, and use that to decide how common various things are with respect to each other. I believe Magnate said this was called something like a "table selector" in Vanilla's code. Actually what will probably be done is that every monster/item in the game is added to the table, but they get multipliers to their rarity based on how in-depth they are. For example, every additional level of out-of-depthness makes a monster 25% more rare and an item 50% more rare. Or whatever.

Actual placement of the monsters/items in the level is up to the generator, of course; the allocator merely selects what should be placed.

As far as items are concerned, here's how I think generation to be done:

* Compile the frequency table for all items.
* Append the frequency table for all artifacts to this table.
* Select from the table. If it is a non-artifact equipment item, hand it off to the affix/promotion system, otherwise just use the item straight.

I'm not entirely clear on how exactly artifacts are allocated in current Vanilla, but this system I'm proposing would let us directly compare the frequency of a given artifact to the frequencies of normal items, which I don't believe is currently possible.

(Of course, I'll be postponing artifacts for the forseeable future, as well as the affix system. They're in that big bucket of things that I consider to be "fluff", which also includes stuff like proper combat calculations, actual creature AI, the look command, etc.)

Any thoughts?

Last edited by Derakon; June 4, 2012 at 19:16.
Derakon is offline   Reply With Quote
Old June 4, 2012, 20:31   #48
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,320
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Derakon View Post
I don't really like the idea of implementing my own parser into Pyrel, mostly because parsers are solved problems -- writing my own doesn't really add anything interesting beyond new bug opportunities. This ought to be a situation in which there's a readily-available library that does what I need (once I substitute in the appropriate values for the strings we care about, of course).
Certainly, however your point about dependencies is a real worry, especially if you don't pick something which has wide support on distros (and Windows + Mac OS X).

My point was simply that a recursive descent parser is about the same complexity as actually specifying the grammar. Unless you're going for efficiency (or a different class of languages that recursive descent can't handle) it's not actually a big deal to just implement this yourself. (Contrary to other types of parsers.)

Quote:
Originally Posted by Derakon View Post
When we want to generate a new level, we call GameMap.makeLevel(level). "level" here is a numeric argument indicating the depth. Obviously in variants this could be extended to specify which dungeon you're in, or to differentiate between different "level 0" maps instead of implicitly assuming the town. makeLevel() then invokes a function to decide on what map generator to use.
Implementing a highly linked structure, having every map location contain links to all the different exits from the location could a simpler and make it easier for variants to implement highly branching level structures (including persistent levels).

So you'd have something like this:

Code:
{ name: "town"
, level: 0,
, exits: {
      "down" : { name: "dlvl 1"
                  , level: 1,
                  , exits: {
                         "down" : ....
                         "up" : (reference back to town level },
                         "..." : ...
                 }
      }
}
The exit names are free-form and are referred to by terrain in the map levels, e.g. ">" in "dlvl 1" refers to "down".

You might want to permit exits which are actually functions/objects which can dynamically compute the map which the exit leads to. This lets you avoid computing the full map structure and do things like persistent levels.

This kind of structure should make it really easy to implement various kinds of map interconnectedness structures -- even things like infinite dungeons that are more interesting than simply going from level 1.. infinity.
AnonymousHero is offline   Reply With Quote
Old June 4, 2012, 20:44   #49
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 8,047
Derakon is on a distinguished road
Persistent maps can be readily handled by only comparatively minor tweaks to the game:

1) Create a new class, call it something like StashedMap.
2) Set up a mapping of (staircase XY position + dungeon level) to StashedMaps.
3) When taking a staircase, first generate a new StashedMap and stick everything into it (except for the entities that always persist across levels, of course). Then, if we've been to the new level before, use a level generator that pulls info from its StashedMap; otherwise, create a new level.

This gets a bit more complicated if you want non-player things to be able to move from a StashedMap to the current map but should still be doable.

Fair point on the parser being comparatively simple to implement.

For what it's worth, staircases are Terrain instances whose interactions include "go up" or "go down" -- which invoke GameMap.makeLevel(current level +- 1) as appropriate.
Derakon is offline   Reply With Quote
Old June 5, 2012, 02:56   #50
Sirridan
Knight
 
Sirridan's Avatar
 
Join Date: May 2009
Posts: 560
Sirridan is on a distinguished road
I feel bad that I never finished my python port

If you want any help though, I'll gladly throw in my two or three cents. Especially now that my life is much less chaotic.
Sirridan is offline   Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Angband 3.5-dev Magnate Vanilla 70 July 2, 2012 17:47
JBand progress log. PaulBlay Development 38 June 27, 2009 10:19
Hackers triumph over pricing.log beast-file ... Magnate Vanilla 3 April 23, 2009 00:00
Angband/65 development log PaulBlay Development 0 April 16, 2009 19:55
Export entire message log to text file PaulBlay Vanilla 2 February 28, 2009 20:24


All times are GMT +1. The time now is 05:43.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2017, vBulletin Solutions, Inc.