ID:1621033
 
(See the best response by GhostAnime.)
Basically I want to know how I can go about making a for proc with equiped items. Each item has its own version of the attack verb, and the player gets it when they equip the item. Thats all working fine, however there is a problem when more than 1 of the equiped item is around.
For example;
for(var/obj/Inventory/Equipment/Weapon/Sword/W)
enemy.hp -= W.sworddmg


If I have two weapons classified under Sword it'll run that twice because of the nature of the for proc. How do I make it so it only runs it for the equipped item?

mob
proc
Equip(obj/Inventory/Equipment/E)
if(!istype(E) || !E.AllowEquip() || !(E.identifier in src.vars) || src.vars["[E.identifier]"])
return 0

E.OnEquip()
src << "You equip a\ [E]."
src.vars["[E.identifier]"] = E
E.suffix = "Equipped"

obj/Inventory/Equipment
Weapon
identifier = "weapon"

The equip proc and the identifier I used to help generate the picture a little better.
Nevermind, fixed it.

var/list/equipped = list()
mob
proc
Equip(obj/Inventory/Equipment/E)
if(!istype(E) || !E.AllowEquip() || !(E.identifier in src.vars) || src.vars["[E.identifier]"])
return 0

E.OnEquip()
src << "You equip a\ [E]."
src.vars["[E.identifier]"] = E
E.suffix = "Equipped"
equipped += E

obj/Inventory/Equipment
Weapon
identifier = "weapon"

for(var/obj/Inventory/Equipment/Weapon/Sword/W in equipped)
enemy.hp -= W.sworddmg


Though if anyone has any better ways of doing it I'd be glad to take the advice.
Uh? What's with the random for loop at the bottom? And why is equipped a global variable?
It was just a snippet, not the full thing.

mob
var/list/equipped = list()

obj/Inventory/Equipment
Weapon
identifier = "weapon"
Sword
proc
Attack()
usr.ustats()
if(usr.attdelay == 1)
return
if (usr.stam < usr.attstam || usr.KOED == 1)
return
usr.AttackProcs()
usr.SMR(src)
for(var/mob/M in get_step(src,src.dir))
M.ustats()
if(M.KOED == 1)
usr.Death(M)
else
usr.critdodge(M)
if(prob(M.dodgechance))
usr << "Dodged!"
flick("Dodge", M)
goto dodge
if(prob(usr.critchance))
for(var/obj/Inventory/Equipment/Weapon/Sword/W in usr.equipped)
usr.mdamage = 1.5 * (W.basedamage + (usr.bdamp * (usr.ustr/2 + usr.udex/4 * (500 / (500 + M.uend)))))
usr << "Critical hit!"
else
for(var/obj/Inventory/Equipment/Weapon/Sword/W in usr.equipped)
usr << "basedmg : [W.basedamage]"
usr << "src = [src], usr = [usr], M = [M]"
usr.mdamage = W.basedamage + (usr.bdamp * ((usr.ustr/4 + usr.udex/4) * (500 / (500 + M.uend))))
usr.damage = round(max(0, usr.mdamage), 1)
M.hp-=usr.damage
M.end += (usr.str / M.end) / 100
usr << "[usr.damage]"
dodge
usr.visiblestatpercentage(M)
usr.KOcheck(M)
usr.HPR(M)
usr.visiblestatpercentage(src)
spawn(usr.attdelayt) usr.attdelay=0

If the player equips the sword they get the sword's Attack proc which replaces the current one they have.
In response to Gluscap
Best response
(1)
You should not be using usr in those procedures. There are very few instances where usr is properly defined (typically where a client explicitly does something like clicking an object).

To avoid any issues in the future (preventative programming per sé), have another reference variable pointing to the owner/mob.

Ex:
Attack()
var/mob/M = src.Owner
// or
var/mob/M = src.loc // Assuming the item is in a user's content
if(!ismob(M)) return // Stop the proc if M is not a /mob


-----------

(2)
It would probably be best to have a procedure made to check for any instances of can-attack. Ex:
Equipment/proc/CanAttack()
var/mob/M = src.loc
if(!ismob(M)) return 0
if(usr.attdelay || (usr.stam < usr.attstam) || usr.KOED)
return 0
return 1


Attack()
if(CanAttack())
Do stuff


Note the lack of == 1? That's because I am evaluating if the value is TRUE or FALSE. False == "", 0 or null. TRUE is any other value (ex: reference, number <> 0, non-empty string).

-----------------

(3)
It would be much better to have a Damage() procedure so if any adjustments is needed, it would be easy to fix rather than updating all individual items. Ex:
Weapon/proc
Hurt(Damage, mob/Enemy) // Damage = amount
if(!isnum(Damage) || Damage < 0) return 0

Enemy.hp = max(0, Enemy.hp - Damage) // 0 is the lowest value

if(Enemy.hp <= 0)
Enemy.Death(Owner)
return 1

Attack()
...damage calc...
src.Damage(amount, Opponent)

mob/proc/Death(mob/Killer)
if(src.hp > 0) return
if(Killer == src)
world << "[src.name] committed suicide."
else if(Killer)
world << "[Killer.name] killed [src.name]!"
else // A killer is not defined
world << "[src.name] died from an Act of God!"


-----------

(4)

Instead of looping for all swords in a person's inventory for an item variable access, use src. src = source = source where the procedure was called from/on.

With how it is designed right now, if you had 2+ swords in your inventory, it would count ALL those weapons.

So if I bought 100 swords... well, imagine the damage inflicted 100 times... yeah...


var/multiplier = 1
if(Crit) multiplier += 0.5
else if(SuperCrit) multiplier += 1.0

var/damage = max(1, multiplier * (src.basedamage + (usr.bdamp * (usr.ustr/2 + usr.udex/4 * (500 / (500 + M.uend))))))


Taking your proc, you can reduce what you have from 9 lines to ~3!

---------

(5)
Small tip. You can do a mini-if()/compact if() statement by doing X?Y:Z which translates to if(X)
Y
else
Z


Why is this a tip? Well, look at the following:

//  if critical, * 1.5 otherwise * 1
var/damage = max(1, (Crit?1.5:1) * (src.basedamage + (usr.bdamp * (usr.ustr/2 + usr.udex/4 * (500 / (500 + M.uend))))))


----

It is better to have a modular system set up. Have a general procedure for Damage(), Death(), etc. and have each one of those redefined if needed.

This reduces the amount of stuff you have to code in the end. Ex:
Weapon
proc
Attack(Damage, mob/Enemy)
var/mob/M = src.loc
if(!M) return 0
if(!ismob(Enemy)) // Do not attack if no enemies defined
M << "Enemy not found"
return 0
else if(!CanAttack())
M << "You cannot attack right now!"
return 0

/*

Nevermind this, it was another idea I was thinking about but not likely going to work


"." is the default return value, ..(Enemy) is... well, read the first part.

if(!Damage) . = ..(0, Enemy) // Get value from the parent procedure or if redefined.

else . = Damage
*/


. = Damage
if(!isnum(.)) CRASH("Something went wrong!")
if(. == -999) return // Safety check. We'll pass -999 as the value if we do not want the following to be called.

Damage = max(1, Damage) // Lowest damage possible is 1
Damage = min(Damage, Enemy.hp) // Does not go above opponent's HP value.

return Hurt(Damage, Enemy) // Honestly, we could define all this stuff in here and get rid of one extra proc.

CanAttack()
...

Hurt(Damage, mob/Enemy)
... see one of the above points
... check if opponent is dead
return Damage


Sword
Attack()
.../M = loc...
var/Damage = rand(1,10) // 1-10 damage

for(var/mob/E in get_step(M, M.dir)) // Damage all M in range for same value
var/dmg = Damage - (E.def / 2)
// You may want to do min/max() here for safety but that is already taken care of in the parent procedure
dmg = ..(dmg, E) // I have not tried this out; it should theoretically work...
M << "You stabbed [E.name] for [dmg]!"

return -999

Excalibur
Attack()
var/mob/E = locate(/mob) in oview(M, 100) // First person spotted will be killed
return ..(E.hp, E) // Damage for all of E's HP.

Useless
Attack()
// Without ..(...), the parent procedure is not called. Meaning you overwrote it with this which is simply nothing
M << "You flail around like a splashing Magikarp!"
// return -999 is not needed here since we did not call the parent/initial definition of the proc


Mind you, the above is an example. Note how I don't have to do all the pre-checks in each item Attack() as it is done when I call ..()

Once you defined the basis, minimal redefinitions needs to be done instead of copying/pasting the old procs in (which may lead to easy errors or time-consuming fixes)
ufffjkrfek

i have the attack proc three times in my code already, regular, sword and axe. this will take a while to understand to the point i can apply this knowledge on a need to basis. i guess i'll start fixing up the attack code asap, thanks for the information