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.
Pyrel dev log, part 5
Collapse
X
-
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.Comment
-
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).Comment
-
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):
Code:def trigger(self, source, target, gameMap):
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):
Code:proc.trigger(item = speedPotion, target = player, gameMap = gameMap)
Code:proc.trigger(caster = player, target = player, gameMap = gameMap)
I think this will work, but I'm putting it out here in case there's something I missed. Feedback is welcome.Comment
-
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."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
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.Comment
-
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 } ] }
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" }
Code:"name": "Filthy street urchin", "nativeDepth": 0, "experienceValue": 0, "rarity": 2, "magic": { "frequency": 1, "spells": ["fire ball", "haste self"] },
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.Comment
-
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:
New spells can be created fairly easily; here's some example spell definitions:
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.
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."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
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.).
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 ;-)
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.Comment
-
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.
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.
My point is that damage caps are a kludge. If something is too dangerous it needs rebalancing for the case where it's too dangerous, not capping."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
Damage caps are for breath attacks only. If different monsters have different max damage (beyond the max damage imposed by HP), then players will have to have specific knowledge of those monsters that do extra damage.Comment
-
Regarding damage caps: your average fire hound does ~11 damage/breath because they have 35HP. Meanwhile Glaurung has 6250HP; if he used the same formula with no cap then he'd be doing over 2k damage with a breath when he's at full health. Even resisted, that's instadeath for, say, most mages.
So I'd say we can either keep the damage cap system, or we can change the formula for calculating damage so that it has a nonlinear dependency on creature health (like a logarithmic formula). Functionally that works out to being like a damage cap -- damage asymptotically approaches some fixed value but does not cross it -- but it's not a hard "damage cannot ever exceed this". On the flipside, that makes it very hard to determine how much damage you can expect a given attack to deal -- assuming we keep the HP dependency of breath damage. Which I would like to do; I think it's a neat mechanic.
Also, my plan for damage caps attaches them to the elements, not to the attack type. While most attacks in Vanilla that hit the cap are breath attacks, darkness storms and nether balls can potentially hit the damage cap. I understand Vanilla doesn't work this way, but I think it's more intuitive and the effect on balance will be minimal.Comment
-
Regarding damage caps: your average fire hound does ~11 damage/breath because they have 35HP. Meanwhile Glaurung has 6250HP; if he used the same formula with no cap then he'd be doing over 2k damage with a breath when he's at full health. Even resisted, that's instadeath for, say, most mages.
So I'd say we can either keep the damage cap system, or we can change the formula for calculating damage so that it has a nonlinear dependency on creature health (like a logarithmic formula). Functionally that works out to being like a damage cap -- damage asymptotically approaches some fixed value but does not cross it -- but it's not a hard "damage cannot ever exceed this". On the flipside, that makes it very hard to determine how much damage you can expect a given attack to deal -- assuming we keep the HP dependency of breath damage. Which I would like to do; I think it's a neat mechanic.
Also, my plan for damage caps attaches them to the elements, not to the attack type. While most attacks in Vanilla that hit the cap are breath attacks, darkness storms and nether balls can potentially hit the damage cap. I understand Vanilla doesn't work this way, but I think it's more intuitive and the effect on balance will be minimal."Been away so long I hardly knew the place, gee it's good to be back home" - The BeatlesComment
-
I threw together a first pass at defining all of the resistable elements. You'll note that instead of experience drain here, I have temporary level drain. It's an idea I've always wanted to try out, since I think it'd be a lot more interesting gameplay-wise. No permanent damage to your character, however the immediate ramifications are much more severe than losing experience.
I added the "shortName" field with an eye towards the player status display and monster memory. Here's what the player status display looks like currently:
Obviously missing several fields, but they won't be hard to add. I gave the player a semi-random assortment of resistances for this screenshot. I figure that FA, SI, etc. can be fit in as well without taking up much space. More importantly, a similar display can be used in monster memory, which I want to have use a more tabular arrangement like this old example. Ideally you'd also be able to mouse over an entry in the sidebar and get a popup that gives you more information on that particular stat.
Also notice that I'm using blue for "normal value" instead of green. . This is mostly as a nod towards red-green colorblind players. I guess that this could be an option, but I don't think it's an especially huge deal for non-colorblind players to get used to blue instead of green. As your HP (or other stats) get depleted, the color will smoothly cycle through purple to red, using HSV (hue-saturation-value) to do color rotations.
Oh yeah, and the STR/INT/etc. stats will have linearized display. No more 18/x stats.Comment
Comment