I could attack the person to my left while I was facing south.
Now, while this may still be plausible in real life, in a game without pixel movement(I.E., has 8, but usually 4 directions), this looks very strange and unappealing.
And there wasn't just that; if there was more than one person in my oview(1), I had a box pop up, asking me who I wanted to attack. This, too, is strange and unappealing.
What is the cause of this flaw? Why, the ol':
mob/verb/Attack(mob/M in oview(1))
Now, the fix for this is not very large. With that in mind, I decided to go ahead and show you a full tutorial of a good attack system. Like my last Helpful Tips for Programmers(Let's just abbreviate it to HTP), this will not be a plug-and-play tutorial.
Just so you can see the differences between a good and a bad attack system, let me start off by showing you a bad one:
mob
verb
Attack(mob/M in oview(1))
var/damage=usr.attack-M.defense
M.health-damage
view()<<"[usr] hit [M] for [damage] damage!"
M.DeathCheck()
Alright, now I will show you how to make a good attack system.
Let's start off simple, by making our verb.
mob
verb
Attack(mob/M in oview(1))
Aah,aah,ah. oview() will not be used in this at all.
Now, lets try again.
mob
verb
Attack_Verb()
Much better. Now before we go any farther with this, lets plan ahead. In an Anime game, you are most likely going to have some sort of AI, right? Well, why make them have their own built in Attack function, when you can make one procedure that handles client and NPC attacking?
mob
proc
Attack(mob/victim)
Ok. So, when defining this procedure, src is the attacker, and victim is the...well, the victim. Now, lets continue by making some variables:
mob
var
NPC=0 //This being 1 means that they are an NPC, and they should not be attacked
Enemy=0 //This being 1, on the other hand, means they are an enemy, so they are an NPC who should be attacked
tmp/resting=0 //We will just use this as a little extra for the Attack.
health=100
stamina=100
attack=100
defense=50
tmp/mob/target
Alright. Now, lets put these to use in our Attack procedure:
mob
proc
Attack(mob/victim)
if(src.resting) return //If the attacker is resting, we don't want him attacking.
if(victim.NPC) return //We don't want them killing important NPCs
if(src.stamina <= 0)
if(src.client)
src<<"You do not have enough stamina to do this." //If their stamina is too low, they can't attack. Also, you don't want people randomly yelling at you asking "why can't I attack?", so this tells them.
if(src.Enemy)
src.Rest() //If they are an Enemy, lets give them enough intelligence to rest. Because NPCs should be just like players, not have infinite stamina.
return
Now we have the beginning set up; everything that the player should not be doing when he/she attacks. As an added bonus, I even added a tip for you to make your NPCs more intelligent.
Now, lets get going with the bulk of it:
var/damage=round(rand(src.attack/1.5,src.attack*1.5)) //This is the damage that you will do. It round()s, so we don't have a decimal, and uses rand() so we don't have 1 set value. We could do an attack with 50% of your attack up to 150%.
var/normal_damage=round(src.attack-M.defense) //This is what the damage should be. This will be used to calculate critical hits.
These 2 variables will be used frequently, so make sure you name them something easy to remember, but short enough that you wont get tired of repeating it over and over again.
//You can do any other checks specific to the game here that will affect the damage or normal_damage variables
damage -= M.defense //Now lets take the victims defense away from the damage, to balance it
if(damage<1) damage=0 //We don't want to do negative damage and heal them!
victim.health-=damage //Take away some health...
src.stamina-=damage/100 //And now lets make it cost something to the attacker as well
This is the main function of the Attack that everyone thinks of. It balances the damage variable, makes sure it isn't negative, and subtracts from the person's health. Although, people just don't seem to understand that causing force upon something else also causes force upon oneself. In simpler words, if you punch something, you will slowly get tired. Or quickly, depending on your physical fitness.(Ideas, ideas, game developers!)
That is where the src.stamina-=damage/100 comes in. This simply takes a fraction of the damage and takes it away from your stamina. And that is why we did that check to see if their stamina was at 0 at the beginning.
if(src.client) //Displaying output to non-clients is pointless. It is a good habit to prevent output to them.
if(damage > normal_damage)
src<<"Critical! You hit [victim] for [damage] damage!"
else src<<"You hit [victim] for [damage] damage!"
if(victim.client)
if(damage > normal_damage)
victim<<"Critical! [src] hit you for [damage] damage!"
else victim<<"[src] hit you for [damage] damage!"
src.DeathCheck(victim) //Assuming your DeathCheck proc is set up as: mob/proc/DeathCheck(mob/victim)
This is the simplest part that people always see in an Attack verb; it tells you and the person the damage done. Or, in some cases, it tells everyone near you.
And that is the Attack procedure. Now back to the main point of this lesson; know who you are attacking. We have a nice Attack procedure set up, but no way to attack someone. So, lets go back to the Attack_Verb() we made.
mob
verb
Attack_Verb()
var/list/mobs=list()
var/mob/m
for(var/mob/M in get_step(usr,usr.dir))
if(M==usr.target) usr.Attack(M)
mobs+=M
m=pick(mobs)
usr.target=m
usr.Attack(m)
Alright. Here, we get a little complicated. In this example, we don't want to be attacking every mob in front of you; so we add every mob in front of you to a list. Then, if that list is longer than 1, it randomly picks someone. It then sets that someone to your target, so you aren't attacking a random person every time. Then, if the list is only 1 entry long, it just attacks that person. In this case, I am not going to make this change your target. In some cases, you may not want it to. It is easily override-able though, so don't worry about that. The line with get_step() is all you need to check who is in front of you. Why? Because get_step() does the work for you. You use the first line to search for any mob in get_step(usr,usr.dir).
The arguments for get_step() are:
get_step(Ref,Dir)
Ref being the reference, the /mob, /obj, or /turf you are referring to. Then Dir is the direction you want to know about. What this does is just tell you the position of a step in the Dir from the Ref.
So, if there is a mob there, you try to attack it. The Attack proc handles everything else.
Well, that is all for now, guys. I hope you all learned something!
I leave those of you who learned from this with one challenge: Make a cool-down for Attack. It doesn't matter how long, just make one to the best of your abilities. And, if you are confident in your work, feel free to post your creation in the comments!
Attack(mob/M in get_step(src,src.dir))
Instead of
Attack(mob/M as mob in oview(1))