ID:121140
 
Keywords: cbp, combat, programming
Like anything else you're designing, we want to make sure that the combat system conforms to a strict set of rules. I'm starting with this combat system because I seem to find more and more people using the same badly implemented combat system. I don't expect this to reduce that count any, but I like writing, and I like programming, and it's something I haven't done in awhile. So here we go.

Starting out first, the most important thing when designing anything is consistency. What seems to be one of the most common implementations of combat, according to the developer forums, is something gross like this:
mob
proc
deathCheck(mob/killer)
if(health < 1)
world << "[killer] killed [src]."
if(client)
loc = locate(1,1,1) // etc...
else
del src // lol

verb
punch(mob/target in oview(1))
target.health -= rand(1,100)
target.deathCheck(src)


There are a couple of problems with this implementation. Mostly, it's redundant. Every time we reduce the enemy's health, we have to manually call deathCheck for them. Not to mention that if in the future you wanted to add something like damage reduction, you end up having to do a lot of extra work on top of what we're already doing. Instead we should sum up all of this stuff in one single function. Since all of them are directly related and there is probably no situation, or very few, where the exact same process isn't going to be repeated for every instance of damage taking, a single function approach will suit us just fine.

mob
proc
getHurt(damage, mob/attacker)
health -= damage
if(health < 1)
// death stuff


Now instead of lowing their health and doing a death check every time, we'll just call mob.getHurt() when we want a mob to take damage, and eventually die. Since everything is confined to the one function, changing how we take damage, and how we die, or what defines death, just takes a couple of lines. For example, if we want to add something like "damage resistance," we could just do something like:

mob
proc
getHurt(damage, mob/attacker)
var/damageResistance = getDamageResistance() // some value representing how much damage needs to be mitigated, as a percentage, like 60% (0.6).
if(damageResistance)
damage *= 1-damageResistance // reduce the damage by the given percentage. 1 - 0.6 = 0.4, 40% of what we originally had.

health -= damage
if(health<1)
// etc...


Now, in the spirit of modularity (removing as much redundant code, and making things that we want to repeat a lot, maybe in other places in code, into individual functions), we can end up with quite a few nifty functions that, maybe right now we don't have much use for, but later on could be pretty nice to have around.

Edited to include the variables we use, added isDying(), which determines if we should die now (health<1, etc...), and isDead() now checks if we're currently dead (added a death-state for mobs). Also added live() for making creatures "live" again, which is used by respawn().
mob
var
health = 100
maxHealth = 100

// boolean indicating we've died
dead = FALSE

proc
// take some hurt.
// optional attacker can be provided, for when someone is killing us.
getHurt(damage, mob/attacker)
if(isDead()) // nothing to be done if we're already dead.
return

health -= damage

if(isDying())
die(attacker)

// should we die? can we even die? let's check.
isDying()
//if(isModerator())
// return FALSE
return (health < 1) // by default, <1 should be impending death for all

// are we currently dead?
// this is for games where death does not instantly result in returning to the game.
// you could have an in-between state where the character is a ghost and has to find
// their body, or they go to hell or heaven, or something fancy like that.
isDead()
return (dead == TRUE)

// do some dying.
die(mob/killer)
dead = TRUE // we're dead now.
//deathAnnounce(src, killer || null)
//deathCry()
if(client)
//dropCorpse()
respawn()

else
//killer.getKillExperience(src) -- instead of putting the formula here, we can do it over there... another useful trick
//dropCorpse()
del src

// if we have anything special we need to do to make this character
// become "alive" again, do it here. like losing our halo, restoring our
// normal icon (maybe we became a ghost?), or anything like that. might also
// be wise to restore our health here instead of respawn().
live()
dead = FALSE
//restoreHealth()

// throw us to our graveyard.
// unlike live(), respawn() is meant to restore us to life
// and then move us to a proper respawn point.
respawn()
live() // we're back alive now. woo.
//Move(getGraveyard())


Now we have a nifty way of determing if people should die, and a way to instantly kill someone. Now the act of dying is not dependant on the player losing their health; since we've separated the function of dying from simply getting hurt, we can, with absolutely certainty, kill someone with one function call. Ignoring any type of damage mitigation, any type of inability to die (immortals, zombies, whatever). Pretty cool, right? Now you can add like, a slay command that doesn't have to do like... damage(mob, 99999999999999999999999999999999). Which is the most important command in a game, right?

We've also partitioned the act of respawning, which doesn't have much use right now, but it does separate the act of respawning from dying, which will make our code a lot easier to read, where everyone can tell what every line of code is doing based on just the name of the function being called.

Now we have a framework that we can build on. It is consistent with itself, and there is never a time during combat when we are going to be unsure about the status of the combatants. They die when they are supposed to, and we can kill them if we need to. Or move them back to the graveyard with full health. Or whatever.
nay x 1000
This is very bad, I could tell right away be looking at the way you check health in deathCheck().
Good work! This should help a few aspiring programmers.
SuperAntx wrote:
This is very bad, I could tell right away be looking at the way you check health in deathCheck().

You didn't read the article did you?
I'm surprised that ToadFish didn't even explain why he nay'd it 1,000 times. You guys aren't offering him any helpful critiques. I don't see how "health" is incorrectly used. If src health is below 1, etc...
Don't mind Toadfish and SuperAntx. Toadfish is just being silly, I don't know what SuperAntx is doing.

If anyone has any complaints about what's being said here, I'd love to hear it. My main goal is to put out and explain why the system I'm proposing is a good one, and is definitely a step up from what we see every day on the forums.

If there's some flaw here, I'd love to hear it. Because this is the first of a few.
You have the sexiest CSS so far.


And I appreciate this thread. It's very well-written.
Masterdarwin88 wrote:
You have the sexiest CSS so far.


And I appreciate this thread. It's very well-written.

^
Truseeker wrote:
I'm surprised that ToadFish didn't even explain why he nay'd it 1,000 times. You guys aren't offering him any helpful critiques. I don't see how "health" is incorrectly used. If src health is below 1, etc...

I was just kidding, I yea'd it. Now I shall proceed to actually reading it.

Yusuke13 wrote:
You didn't read the article did you?

Yes, and I stand by my statement. Keeth's death check will kill someone with 0.1 health points.
SuperAntx wrote:
Yes, and I stand by my statement. Keeth's death check will kill someone with 0.1 health points.

Anything less than 1 is negligible, as far as I'm concerned (considering this is my code and all).

If you want your characters to be able to have 0.1 health and live, that's your prerogative. That's kind of the entire point of this discussion. Being able to change the rules to work how you want, quickly and easily.
I can't think of a game where I've had decimals in my health points number.
Keeth wrote:
Anything less than 1 is negligible,

It does matter and that's what's wrong with your lesson here. You're killing people who still have a sliver of health left. Instead of checking if it's less than 1 you should be checking if it's under or equal to 0.

EmpirezTeam wrote:
I can't think of a game where I've had decimals in my health points number.

Keeth isn't rounding his damage, this combat system supports decimal values.
We talked about this yesterday. What's up with the extremes? That (health < 1) check is only tangentially related to the article and does not make it 'very bad'.

Also, killing the player at (health < 1) or (health <= 0) is entirely the game maker's choice. The entire point of the article is recreating the system so it's flexible enough that you can change stuff like this at will.
SuperAntx wrote:
It does matter and that's what's wrong with your lesson here.

It doesn't matter at all, actually. 0.1 is not a sliver of health in my game. It's no health, cause they're dead by the time it reaches 0.1. If you want <= 0 to be dead in your game, that's your choice.

EmpirezTeam wrote:
I can't think of a game where I've had decimals in my health points number.

Diablo 2 actually had decimal values for its health and mana in the same way. One of the key ways it worked without any issues was because you never knew how much damage was actually being dealt, I think.

It also allowed for thinks like +1.5 health. Which could stack with something else that gave +1.5 health, and give you 3 health. Instead of having to choose between 1 or 2.

I just like the option.
Toadfish, <1 is not the same as <=0. The code Keeth wrote kills players who have health. This is wrong, it is a bug; especially so in a system which supports decimal health values.
It's not a bug, SuperAntx. It is intended, and it is accounted for. Players will never see anything less than 1 health. Once they take damage to be below 1, they will die. Players don't "have health" in any significant sense, other than it's not 0. You might as well be complaining to the DND writers for making 0 to -5 "stunned" instead of dead.

I'd appreciate it if you'd stop filling the comments with nonsense. Your point has been addressed. If you have anything else to contribute, do so or leave.
Lige wrote:
It's not really nonsense. He was addressing an issue, which others might agree with. You stated at 0.1 health, they'd be dead anyway. However, 0.1 is greater than 0; meaning they have a small chance at survival.

I don't really understand the complaint against my formula, since my formula is not meant to be copy and pasted. The entire point of the post, like Toadfish said, was to demonstrate modular, consistent, and easily understood processes to follow. What defines death is inconsequential, since this post has nothing to do with that.

As for the post itself, it's good that you're wanting to help others, but it's not a method I would follow. yea+ for trying.

You're free not to. If you have anything to contribute about the actual content of the post, I'd like to hear it. Let's keep it aimed at the purpose though; do the sets of processes make sense, or are they flawed?
I would use "src.health","src.maxHealth", etc. just to remind me of who they belong to. It's not required, but for more complicated, it really helps with the organization and readability.
Page: 1 2