ID:99561
 
Too many times I have gone onto a game, made my character, talked with people, explored, trained, etc...then fought. And when I fought, I experience a great flaw.

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!
I just used

Attack(mob/M in get_step(src,src.dir))

Instead of

Attack(mob/M as mob in oview(1))
Alkhalif10 wrote:
I just used
mob
verb
Attack(mob/M in get_step(src,src.dir))

While that does work, my way just keeps you from attacking every mob in that spot. If for some reason there are 2 mobs on that same spot, you don't want them both being attacked, right?
Albro1 wrote:
Alkhalif10 wrote:
I just used
mob
verb
Attack(mob/M in get_step(src,src.dir))

While that does work, my way just keeps you from attacking every mob in that spot. If for some reason there are 2 mobs on that same spot, you don't want them both being attacked, right?

Oh yah Okay
I'm sure someone with a lot more motivation than me will go through the entire thing, but I'll just go ahead and mention the more important problem that I see:

mob
var
NPC=0
Enemy=0
tmp/resting=0
health=100
stamina=100
attack=100
defense=50
mob/target

'mob/target' - that's going to get saved in 95% of save systems out there, and has the potential to cause huge problems. Use var/tmp/mob/target.
Murrawhip wrote:
I'm sure someone with a lot more motivation on their hands will go through the entire thing, but I'll just go ahead and mention a few things:

mob
var
NPC=0
Enemy=0
tmp/resting=0
health=100
stamina=100
attack=100
defense=50
mob/target

According to your comments, if NPC=1, don't attack them. Also, if Enemy=0, don't attack them.
As far as I can see, you've got two variables when you should have one.

Also, 'mob/target' - that's going to get saved in 95% of save systems out there, and has the potential to cause huge problems. Use var/tmp/mob/target.

Where did I say that if they were not an enemy, don't attack them? Enemies are NPCs who attack you, or you can kill. But players don't get the Enemy var set to 1, so this would prevent you from attacking another player.

And about the target, that was a last minute thing that I added in at the recommendation of Stephen. You are right about making it a temporary variable, I just forgot to. Sorry ^_^
My point is - isn't a player you're attacking an enemy anyway?
You can attack /anyone/ who has NPC set to 0, right? So what is the point of Enemy?
You only use it here:
if(src.client)
src<<"You do not have enough stamina to do this."
if(src.Enemy)
src.Rest()
return


replace "if(src.Enemy)" with else, and wham, you've saved yourself a var.
Murrawhip wrote:
My point is - isn't a player you're attacking an enemy anyway?
You can attack /anyone/ who has NPC set to 0, right? So what is the point of Enemy?
You only use it here:
if(src.client)
src<<"You do not have enough stamina to do this."
if(src.Enemy)
src.Rest()
return


replace "if(src.Enemy)" with else, and wham, you've saved yourself a var.

What if you need it later, in other code? I use it in my game to put different colored names under mobs. I use it to determine many things. Now, you are right in saying it isn't needed here, but it may be needed elsewhere in an actual project.
var/mob/M = locate() in get_step(src,src.dir)
if(ismob(M))
Attack(M)
I've showed more motivation than I intended.
It's good that you're doing something for BYONDAnime, but maybe you should get someone to check over it before you publish it.

Attack_Verb() is capable (and likely) of attacking twice instead of once.
Jayash wrote:
var/mob/M = locate() in get_step(src,src.dir)
if(ismob(M))
Attack(M)

This will not attack 1 mob at a time.

@Murrawhip, Stephen001 checked it over. And I know it can attack twice; I experienced it myself. And the fix for that is the challenge I gave.
The more the merrier.
who cant do something this simple by them self?
Jayash wrote:
The more the merrier.

That is a matter of personal preference. If you do an attack system that way, good on you. I just wanted to show it this way so that those who do not know how to make it attack 1 at a time now know.
If it was up to me, I would combine the NPC and Enemy variable together... more specifically, I would have a single variable just for the NPC so it is immortal (or better yet, have /mob/NPC as /NPC or /obj/NPC. /obj and /mob derives from the same parent, /atom/movable).

It would have been better if there was some more modular programming involved.

For example, defining a procedure to take care of whatever happens when damage is dealt, like the following snippet
Attacked.Damage(Attacker, Damage Amount)
// Ex: X.Damage(Y, 10)

mob/proc/Damage(mob/Attacker, dmg)
dmg = min(max(0, round(dmg)), src.health) // Makes it so the damage is between 0 and the src's health
src.health = max(0, src.health-dmg) // Making sure that the damage dealt to the mob doesn't result < 0
if(src.health <= 0)
src.Death(Attacker) // Called the Death() procedure
else if(istype(src, /mob/Enemy)) // Or !src.client but the problem may be when a mob leaves and their mob remains, this can be called in that case.
var/mob/Enemy/N = src
N.Dealt_Damage() // Calls this procedure on the Enemy NPC. This is where the Rest() procedure in your example would be


As you can see, the src.Rest() for enemy NPCs has been moved to a more appropriate procedure. The LAST thing you want to do is keep calling/typing that procedure every time you deal damage manually - just define it in a procedure and let the procedure handle the call instead of you always typing it in.

Everyone has their own preference but instead of the resting variable, I would have a list variable with a name akin to non_attack. In this list variable,I would add/remove situations that would result the person unable to attack someone else if the list size is > 0 or if it exists (for example: non_attack += "resting" when the person starts resting). Or simply have a procedure defined to check if a person can attack or not:
mob/proc/Can_Attack()
if(src.non_attack) // if the non_attack variable I was talking about exsits
return 0
else if(src.resting || src.meditating) // How most people would define non-attacking variables in a very inefficient/clean method
return 0
return 1
// This procedure returns 1 if the person can attack

mob/verb/Attack()
if(!src.Can_Attack()) // If the person cannot attack, stop it.
return
...

For the ! operator, read up the article green programming to see why it was used (think this as a cliffhanger).

An other issue is that you're "if(mobs.len > 1)" code block is indented incorrectly, it is within the for() which means it occurs every time it loops through a /mob, you want it outside that loop. And a pick(/list) with one value always returns that one value so you do not even need the if, just set m as the picked value:
 var/list/mobs=list()
var/mob/M
for(M in get_step(usr,usr.dir))
if(M==usr.target)
usr.Attack(M)
return // No point of the others
mobs+=M
M = pick(mobs)
usr.target = M
usr.Attack(M)
(I would have checked if the target was within the turf first but this works as well...)

And you do not want the variable /mob/target to be saved, you should make that a /tmp variable
/mob/var/tmp/mob/target


Other than that, good work! ^_^
Kafei Keaton wrote:
who cant do something this simple by them self?

Who? Apparently, the programmers of the games I described at the beginning.
Albro1 wrote:
Jayash wrote:
var/mob/M = locate() in get_step(src,src.dir)
if(ismob(M))
Attack(M)

This will not attack 1 mob at a time.

@Murrawhip, Stephen001 checked it over. And I know it can attack twice; I experienced it myself. And the fix for that is the challenge I gave.


Yes, that WILL attack 1 mob at a time..

Why post code that is intendedly broken? From reading your passage, it's very unclear that something needs to be fixed, and most newbies actually using your article are just going to copy-paste the code, and have a broken attack system. You need to make it clearer to the public that your attack system is broken, and won't work.
GhostAnime wrote:
If it was up to me, I would combine the NPC and Enemy variable together... more specifically, I would have a single variable just for the NPC so it is immortal (or better yet, have /mob/NPC as /NPC or /obj/NPC. /obj and /mob derives from the same parent, /atom/movable).

It would have been better if there was some more modular programming involved.

For example, defining a procedure to take care of whatever happens when damage is dealt, like the following snippet
Attacked.Damage(Attacker, Damage Amount)
> // Ex: X.Damage(Y, 10)
>
> mob/proc/Damage(mob/Attacker, dmg)
> dmg = min(max(0, round(dmg)), src.health) // Makes it so the damage is between 0 and the src's health
> src.health = max(0, src.health-dmg) // Making sure that the damage dealt to the mob doesn't result < 0
> if(src.health <= 0)
> src.Death(Attacker) // Called the Death() procedure
> else if(istype(src, /mob/Enemy)) // Or !src.client but the problem may be when a mob leaves and their mob remains, this can be called in that case.
> var/mob/Enemy/N = src
> N.Dealt_Damage() // Calls this procedure on the Enemy NPC. This is where the Rest() procedure in your example would be
>

As you can see, the src.Rest() for enemy NPCs has been moved to a more appropriate procedure. The LAST thing you want to do is keep calling/typing that procedure every time you deal damage manually - just define it in a procedure and let the procedure handle the call instead of you always typing it in.

Everyone has their own preference but instead of the resting variable, I would have a list variable with a name akin to non_attack. In this list variable,I would add/remove situations that would result the person unable to attack someone else if the list size is > 0 or if it exists (for example: non_attack += "resting" when the person starts resting). Or simply have a procedure defined to check if a person can attack or not:
mob/proc/Can_Attack()
> if(src.non_attack) // if the non_attack variable I was talking about exsits
> return 0
> else if(src.resting || src.meditating) // How most people would define non-attacking variables in a very inefficient/clean method
> return 0
> return 1
> // This procedure returns 1 if the person can attack
>
> mob/verb/Attack()
> if(!src.Can_Attack()) // If the person cannot attack, stop it.
> return
> ...

For the ! operator, read up the article green programming to see why it was used (think this as a cliffhanger).

An other issue is that you're "if(mobs.len > 1)" code block is indented incorrectly, it is within the for() which means it occurs every time it loops through a /mob, you want it outside that loop. And a pick(/list) with one value always returns that one value so you do not even need the if, just set m as the picked value:
 var/list/mobs=list()
> var/mob/M
> for(M in get_step(usr,usr.dir))
> if(M==usr.target)
> usr.Attack(M)
> return // No point of the others
> mobs+=M
> M = pick(mobs)
> usr.target = M
> usr.Attack(M)
(I would have checked if the target was within the turf first but this works as well...)
And you do not want the variable /mob/target to be saved, you should make that a /tmp variable
/mob/var/tmp/mob/target

Other than that, good work! ^_^


I was just about to post that -.- with the for proc
GhostAnime wrote:
It would have been better if there was some more modular programming involved.

If I may, Ghost, modular programming is such a weak term that it hardly even applies I find. A more OO applicable notion (although it applies elsewhere) is Separation of Concerns. The only reason I recommend this seemedly minor change of terms is the latter will yield you far more practical material in a google search. Modular programming is more of a marketing buzz-term consultants use to sell boring software to clients as being very good.
Thanks. I know about the ! operator already, I just don't use it as often as I should. Also, I am using a list for those things as well. It goes something like this:
mob
var
tmp
list/tempvars=list
#define RESTING "resting"

Then a simple:
if(src.tempvars[RESTING]) return
Page: 1 2