Just sayin' ;-)
Pyrel dev log, part 4
Collapse
X
-
Yeah, yeah. Wimp.
Anyway, fizzix expressed some confusion about how the code is laid out, which is entirely reasonable for someone starting on a new project, so I went ahead and wrote up a high-level overview of the code. It's not very well-edited (but it's a wiki! Hint Hint!), but it should help people get oriented.
Also, an idea I had that I need to record somewhere: use the same system for displaying the character's status as for displaying monster memory. Obviously the character has some stats that monsters don't (currently) care about, like perception, stealth, and device use; monsters also have different spells. But the overall framework is similar, and I think it's a good idea to reinforce how Angband's systems work especially e.g. with regard to resistances.
The by-equipment breakdown page would be a follow-on page, of course.
Incidentally, it would totally be possible to write up monster memory now; monster stats are fully fleshed-out. -
Not to worry, things aren't dead! In fact, Pyrel has seen a lot of activity lately. Magnate's gotten a lot done on item affixes; Fizzix is laying the groundwork for level generation; Noz has been adding various wizard-mode commands. And for my part, I've mostly been approving pull requests.
The next thing I personally want to tackle is a refactoring of the proc/effect system. The original system has been extended and over-extended and is now more painful to work with than it really needs to be. Part of the problem is that every Proc is bundled into an Effect, which creates a lot of excess layers in the data files. For example, if you want to make a potion that cures 10% HP when drunk, you'd:
* Create a record in object.txt for the potion
* Create an Effect for the potion that triggers on item use and invokes the "healPlayer" Proc
* In the potion's record in object.txt, set the healing amount for the proc to 10%
This is a lot of bouncing back and forth between object records and effect records, and the code is kind of messy too. So here's my proposal for a new system:
* The datafiles get a new file: data/proc_template.txt. There is no data/proc.txt (but entries in proc_template can be "complete" procs if desired).
* Each individual proc has a name (referring to the code it will invoke), a trigger condition (determining when its code will be invoked), and any parameters it requires. Of course, some of these may be filled in by templates. However, the trigger condition is not specified either by the proc record or by its templates.
* When procs are referred to in other records, it is as a mapping of trigger conditions to lists of procs.
I think this covers the use cases we want. The important thing is that we want to be able to use the same in-game "effect" from multiple different triggers, without having to create separate proc records for each trigger. Under this regime, we'd have something like the follow:Code:proc_template.txt: { "name": "modify hitpoints" } object.txt: { "name": "Cure Light Wounds", "templates": ["potion"], "procs": { "on item use": [ {"name": "modify hitpoints", "target": "user", "power": "+10%" }, "decrement quantity" ] } }
Thoughts?Comment
-
Therem Harth expressed some interest in working on spellcasting for Pyrel, so I guess I'd better write down my thoughts on that!
We can think of spells as having three main components: the effect of the spell when cast, the cost of casting the spell, and the chance of failing to successfully cast it. In Pyrel, each of these should be a Proc. How are these Procs handled? We can attach them to items -- call them spellscrolls.
And the items, of course, go into spellbooks, which in Pyrel will be containers. Of course in Vanilla proper you would be unable to put things into or take things out of spellbooks, since they'd be generated with all of their spells already set, but that's a detail that can be saved for later. In fact, spellbooks as a whole can be saved for later -- just implementing working spellscrolls will be enough to get us started.
So the player's holding some spellscrolls and they choose the "cast a spell" command. This command needs to do the following:
Gather this information:
* Which spell the player wants to cast (ItemListPrompt to select a spellscroll)
* How much it costs (examine the selected spellscroll)
* Whether the player really wants to cast it (YesNoPrompt), if the cost exceeds their current allowance
* Any relevant targeting information (TargetPrompt or ItemListPrompt as appropriate)
And then execute:
* Deduct the cost from the player's stats
* Determine if the player succeeds in casting the spell
* Cause the spell's effect to take place
Now we need to decide what the item record looks like. I suggest something like the following:Code:{ "subtype": "&Resist Heat & Cold~", "templates": ["spellscroll"], "allocations": [{"commonness": 20, "minDepth": 10, "maxDepth": 60}] "procs": [{ "triggerCondition": "cast spell", "name": "temporary stat mod", "modAmount": 1, "statNames": ["RES_FIRE", "RES_COLD"], "duration": "10+d10M20", "targeting": "none" }, { "triggerCondition": "spell failure rate", "name": "failure rate calculator", "difficulty": 5, "spellType": "holy" }, { "triggerCondition": "spell cost", "name": "stat calculator", "statName": "holy skill", "multiplier": -.5, "addend": 10 }]
Then the spell failure rate proc is a specific proc only for dealing with spells. It too operates on the player's stats, and all it cares about is how hard the spell is to cast. Presumably it uses a formula such that the higher your skill and the higher your spellcasting stat, the easier the spell is to cast.
Finally the "cast spell" proc actually causes the spell to take effect, by giving the player a temporary bonus to their fire and cold resistance stats. These stats are actually binary on/off but codewise we treat them as if they stack. Actually we should use a separate stat name for temporary and permanent element resistances so that we can use Vanilla's..."unique"...approach to resistance stacking, but that's an unimportant detail.
Note that the "cast spell" proc has an extra "targeting" field. The proc code itself will not refer to this, but the command-execution logic needs to know how the spell is targeted so it can determine what information is needed to execute the spell. Valid targeting values would be "none", "item", and "aimed" -- that last one is for normal projectile targeting. Of course we need to modify the Proc code to provide access to this information; just add this function to the base Proc class:Code:def getParam(self, paramName): if paramName not in self.params: raise RuntimeError("Invalid parameter name [%s]" % paramName) return self.params[paramName]
Comment
-
Let me explain some of this now. First off, the "spell cost" proc here is invoking an as-yet nonexistent proc that calculates a number based on the player's stats. Here we're saying that the spell's cost is 10 - half the player's holy skill stat. So we can go into the Priest class template and give them a holy skill of 10, say (spell cost is 5), and then the Paladin gets a holy skill of 5, so they pay 2.5 extra mana to cast the spell (spell cost is 7).
In other words, make sure we can support the V design where each class has a list of what spells it can cast and what they cost.Comment
-
What if I wanted, say, an amulet that could be used to cast a spell? Would the code that prompts me to choose a spell to cast also iterate over non spellscrolls to see if they have the trigger 'cast spell'?
If I wanted a spell that could only cast in certain situations, would I give it a novel proc under spell failure rate that makes the failure rate 100% when you're not allowed to cast it?
Would it be possible to combine procs that resolve to a numerical or boolean type via min/max/and/or/xor?
It would also be useful to have a generic expression syntax to sneak into places where a calculation is expected (for example, if modify hit points took current hp as x and max hp as y and could be set to 020, 2d12, y*0.1, -x*0.9 for poison, max(2d12+y*0.1), etc). Being able to do such a thing could save on lots of dummy procs that do the same as other procs but with a brief calculation first. I've written an expression evaluating calculator in C# once, took 2 daysMy Chiptune music, made in Famitracker: http://soundcloud.com/patashuComment
-
I don't like this. You want some classes to be better at certain spells than other classes independent of a holy or magic skill stat. For example, a rogue should be better at detecting magical objects and a mage, but a mage is better at creating magical light.
In other words, make sure we can support the V design where each class has a list of what spells it can cast and what they cost.
Thus what I'd suggest is to split the skill stats:
* healing: priests > paladins > rangers >> mages/rogues
* detection: rogues > mages > priests > paladins/rangers
* attack: mages > priests >> rangers > paladins >> rogues
* buffing: priests > mages > paladins? > rogues? > rangers
etc...
To make spells exclusive to certain classes, we can have a failure-rate proc that flat-out prevents casting the spell if your skill with that spell type is below some threshold level. Spells with 100% failure rates are unlearnable.
This leads into Patashu's question...
Originally posted by PatashuWhat if I wanted, say, an amulet that could be used to cast a spell? Would the code that prompts me to choose a spell to cast also iterate over non spellscrolls to see if they have the trigger 'cast spell'?
Put another way, any item that has a "cast spell" proc can be used to cast a spell.
If you have no "failure rate" procs, then the spell always succeeds; if you have no "spell cost" procs, then the spell is free to cast.
If I wanted a spell that could only cast in certain situations, would I give it a novel proc under spell failure rate that makes the failure rate 100% when you're not allowed to cast it?
It would also be useful to have a generic expression syntax to sneak into places where a calculation is expected (for example, if modify hit points took current hp as x and max hp as y and could be set to 020, 2d12, y*0.1, -x*0.9 for poison, max(2d12+y*0.1), etc). Being able to do such a thing could save on lots of dummy procs that do the same as other procs but with a brief calculation first. I've written an expression evaluating calculator in C# once, took 2 days
The only problem with third-party libraries is that they create extra dependencies that people have to install before they can start developing the game. Players wouldn't care as the game would be distributed as a standalone executable with all dependencies bundled in, but ideally devs should be able to get up and running with a minimum of fuss.
So hey, if you feel like rewriting your expression evaluator in Python, then I for one wouldn't say no.Comment
-
You have a fair point that some classes ought to be better at certain types of spells than other classes, but IMO we shouldn't do this by just enumerating the spells and classes and declaring a skill level for each one. Much better to come up with a set of rules that describe the system that we want, so that later when we add new spells we just insert them into the system and they automatically behave themselves nicely.
Thus what I'd suggest is to split the skill stats:
* healing: priests > paladins > rangers >> mages/rogues
* detection: rogues > mages > priests > paladins/rangers
* attack: mages > priests >> rangers > paladins >> rogues
* buffing: priests > mages > paladins? > rogues? > rangers
etc...
To make spells exclusive to certain classes, we can have a failure-rate proc that flat-out prevents casting the spell if your skill with that spell type is below some threshold level. Spells with 100% failure rates are unlearnable.
What we should be aiming for is that the creation of new spells (or items, monsters, level types etc) is simple to understand, not that it shouldn't require some thought or effort in the values. Requiring a person creating a new spell to specify what classes can cast it, what it costs for them, and what the failure rate is for each of them seems like a perfectly fair requirement and not overly burdensome in any way. Actually, to me it seems far easier than figuring out some complicated weighting scheme for different classes.
The only question to me is where the information goes. It could go in the spell or the class. It seems in the pyrel way, it should all go in the spell information, so each spell would have a list of class (or race?) flags that can cast it, what it costs, failure rate, and any other spell characteristics (like xp gain for first casting).Comment
-
I daresay most spells can simply use the skill-based system instead and only a few exceptions, if any, will need the more specific approach.Comment
-
(REDACTED)
Probably still buggy, haven't had much chance to test yet. Will make sure everything works next time I sit down at a computer
Using python's eval() may be a possible alternative, btw.Last edited by Patashu; November 29, 2012, 12:13.My Chiptune music, made in Famitracker: http://soundcloud.com/patashuComment
-
Comment
-
Your code looks hideously complicated since you're (it seems to me) combining parsing and evaluation. Those concerns should be separate -- if you separate them you'll get completely trivial code which is "obviosuly" correct.
EDIT: I also imagine that you'll want to be able to calculate different min, max and average(?) based on the expression for display purposes and that'll be easier to do cleanly if you've separated parsing from evaluation.
You should add a "nose" test suite containing various simple and comlex evaluations along with their expected results. That way you can document that the code works and help *keep* it working.Last edited by AnonymousHero; November 29, 2012, 07:47.Comment
-
Sit down at home, review Calculator.cs for C#, hammer out bug fixes for 2 hours \o/ (Most mistakes were from not being used to python, a few were forgetting important things like the lookaheads/lookbehinds on unary operators so 2V2 doesn't turn into 21.414etc, and getting order of operations right for operators of the same precedence)
EDIT: http://pastebin.com/H07vMSEf fixed 4-5*-5 and similar expressions
Everything not-completely-crazy should work now
Given your evaluator, what is the result of "(8+5)*3"? Or "5+abs(5*abs(-5))"?
39.0
>>> calculatethis("5+abs(5*abs(-5))")
30.0
>>>Last edited by Patashu; November 29, 2012, 22:40.My Chiptune music, made in Famitracker: http://soundcloud.com/patashuComment
-
Sit down at home, review Calculator.cs for C#, hammer out bug fixes for 2 hours \o/ (Most mistakes were from not being used to python, a few were forgetting important things like the lookaheads/lookbehinds on unary operators so 2V2 doesn't turn into 21.414etc, and getting order of operations right for operators of the same precedence)
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
Everything not-completely-crazy should work now
>>> calculatethis("(8+5)*3")
39.0
>>> calculatethis("5+abs(5*abs(-5))")
30.0
>>>"Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
I don't personally think this statement can be supported, but frankly it doesn't matter because we aren't locked into a single system. If you want to have spells that use hardcoded variations depending on the player's class, then you can do that. Just make a different set of procs for determining failure rate / spell cost, and, yes, include the class-specific values in the procs' definitions.
I daresay most spells can simply use the skill-based system instead and only a few exceptions, if any, will need the more specific approach.
I just realised how easy it will be to reimplement Sangband in Pyrel ...."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
Comment