Wednesday, 13 July 2011

All fired up: part two

jdunson brings up a really interesting point in the original All Fired Up post:

Something like an "Aura of Flame" might well be a multiplier; "Increases all flame damage by +200%". Some cases might provide base damage as well, some not.
I had been thinking along similar lines about a number of related effects. Now I have a flexible way of representing multiple scalars (pvals) on an object in Unangband, I'm inspired to immediately extend the types of bonuses that can be represented.

I've already included context specific combat bonuses: to-hit, damage, number of blows/shots, damage multipliers, range; for melee, ranged weapons, throwing, traps, unarmed combat. This addresses the design problem I outlined in Throwabow: if you throw a bow with a high damage bonus, do you inflict the extra damage?

(Aside: I can immediately see from the above, that I need to include context specific damage dice and sides).

What other interesting bonuses are there?

jdunson's 'boost all fire damage by 200%' suggestion is actually more complicated than it initially appears, because of the number of different ways fire damage can occur - as I outline in Different Ways of Killing. There's weapons, spells, magic items (rods, staffs, wands), thrown oil and fiery potions. Do I also boost damage from a flask of oil (or another fire damage source) set in a trap? Does the bonus boost allies fire damage (which in Unangband is treated as equivalent to damage from the player)?

And for effects, such as spells, which persist over time, does the damage boost still apply if the item is removed?

For those bothered by my repeated mention of Team Fortress 2, the way damage effects apply there is another excellent example of unintended consequences of any of the above decisions. At the moment, TF2 has an example of a damage nerf (the Detonator) which is a secondary weapon which only does +35% damage against burning opponents, unlike the weapon it is based on (the Flaregun) which does +200% damage in the same situation. However, if you switch away from the Detonator before the projectile hits, the game no longer applies the nerf, and so you get the full +200% bonus. TF2's history of bug fixes is littered with this kind of interaction (damage bonuses against aerial opponents if they are merely standing on stairs and so on).

So I believe I'm better off by focusing on the context of the damage bonus, and then designing weapons which list the possible related contexts. To take an example: I could add boosts to casting spells by increasing the effective level of the player casting the spells, the effective level of the player when learning spells (which makes more spells available), the failure rate of spells and the casting cost of spells. In addition to a general boost, I could also add spell book specific boosts. A sword of fire would then list each of the above boosts which regards to the fire spell books, along with the relative minimum and maximum values; and when randomly generating a sword of fire item, specific values are chosen.

This does become a problem for the player to compare weapons - which is already a major issue, but hopefully the bonuses are going to be concrete enough to make a comparison (Do I want to be able to cast fireballs, or get an extra +x1 damage multiplier?).


Dan Kline said...

Hey Andrew, I'd recommend going with something similar to the abilities and attribute system we described in the GDC 2011 talk about Darkspore. It was very similar to what we used in Diablo 3.

I don't know if the slides are detailed enough, and it's a bit to tricky to post here, but feel free to shoot me any questions.

The gist is that you (1) separate out the ability instance from the character itself, so that if the character changes, say by unequipping a weapon, or deleting themselves, you don't even know - you copied all the relevant state when you fired the ability. And (2) you have generic attributes that represent your state (such as aforementioned fire boost) that code specifies how they combine independent of each other. There can be multiple fire boosts for different types of circumstances or types of combinations - you just copy the attribute state around with the ability.

Declare Fire Boost is an additive +% attribute.
Centralized Damage code: when applying damage tagged as fire (or whatever condition), check this attribute and apply it
Fire sword: Sets Fire Boost to 200%.
Fire Damage Source: activates, which creates an ability, which executes the damage (potentially after some time, like poison), which runs the gameplay code, which checks the stored attributes and sees fire boost, and applies it generically as that attribute is coded to apply. It then combines it with other attributes explicitly as desired.

Best part is your damage code is all centralized, so debugging becomes much easier, no matter how many attributes are necessary.

The transparency of any approach is going to rely more on the nature of the attributes chosen then the architecture, so that's a tough design call.

PS Also, why do I have to create a Blogger account just to comment? sigh.

Dan Kline said...

PPS Also, iirc there was a very good reason why D3 and WoW and such games used boosts as X% rather then multipliers. You're sharply hitting the core of it. Multipliers sound cooler but have a host of issues.

Andrew Doull said...

Thanks Dan. I'd been meaning to check this out but I've got a big todo list at the moment (Playing Dark Spore is on that list as well :)

Of course, as you're undoubtedly aware, not every ability should have state copied across: stealing health is an excellent example which makes no sense if the recipient is dead.

That's also another example of unintended consequences from TF2: The Powerjack used to give you 75 hp if it was out when you killed someone. This initially makes less sense than the fact you now have to now have the Powerjack actually make the kill, until you realise that the Pyro is a DoT class, so you could unfortunately have your afterburn effect kill the person you're meleeing due to unlucky timing. So the old, buggy, Powerjack made more sense from a design point of view.

Dan Kline said...

y, "life steal" is it's own attribute which gets copied with the state data, along with the "casterID" of the original source. The game code, when the ability does damage, checks for that attribute, takes that percent of the damage actually done (which, like you point out, could be 0 if they are already dead, or almost dead), and then heals the caster. If necessary, you can even have this "heal" be a new abilities. Abilities making abilities gets you out of a lot of awkward jams. Things like disease that spreads.

It really handles all the cases well. We wrote all our ability-specific code in Lua too, so our iteration times on those specific abilities was really high. We had about 1000 when we shipped; wouldn't have been able to pull it off in another system.

We'll be giving the talk again at AIIDE this year, if you can come out.

Andrew Doull said...

Yet another conference I'd love to attend...