ID:166704
 
I'm trying to have my attack code add experience to a certain skill everytime it's used. The problem is, that I don't know how I can tell BYOND this:

When Weapontype X is equipped and attacked with, add X experiance to the corrosponding skill
EX: A sword is equipped. An attack is made, and byond adds X experiance to the sword skill.

I'm sure there's a way to set this up with vars, but I'm not sure the best way to go about this. Any help?

~PAK
Pakbaum wrote:
I'm trying to have my attack code add experience to a certain skill everytime it's used. The problem is, that I don't know how I can tell BYOND this:

When Weapontype X is equipped and attacked with, add X experiance to the corrosponding skill
EX: A sword is equipped. An attack is made, and byond adds X experiance to the sword skill.

I'm sure there's a way to set this up with vars, but I'm not sure the best way to go about this. Any help?

~PAK

give the item a variable which determines its skill type.

ie
obj/sword
  dmg=8
  range=1
  type="sword"
In response to Shoe
But how would I get it to check the weapon type after an attack without a whole bunch of "If" statements? Or is that unnavoidable?
In response to Pakbaum
mob/verb/Attack()
//sometin
for(obj/weapons/sword/O in usr.contents)
if(O.suffix=="Equipped")
O.exp+=dmg
wlvlup(O)

obj/proc
wlvlup(O as obj/weapons)
if(O.exp>=O.maxexp)
O.level++
O.dmg++
O.exp-=O.maxexp
O.maxexp*=2


or sometin like that ...
In response to Ripiz
Thanks but I'm saying the player's corrosponding skill increases, not the weapon. So when you use a sword to attack, YOUR sword skill increases, if you use a polearm, your polearm, etc.
In response to Pakbaum
You could just run equipped.type through a switch() statement or something....
In response to Jmurph
Can you give me an example of what you mean?
In response to Pakbaum
Pakbaum wrote:
Can you give me an example of what you mean?

switch(equipped_item.type)
if("sword")swordexp++
if("staff")staffexp++
if("hax'd")world<<"[src] is a 1337 h4x0r!1!!"
//etc.
In response to Shoe
Ok, this is what I have now.
mob/verb
Attack()
var/a = usr.attackrating
var/b = rand(1,20)
var/c = rand(1,100) + usr.critbonus
usr.attackrating += b
if(usr.target == null)
usr << "You don't have a target."
else
if(c <= 10)
usr << "You critically hit!"
target.hp -= usr.strength * 3
Death(target)
if(a >= target.dodgec)
target.hp -= usr.strength
usr << "You hit [target] for [usr.strength] [usr.attackrating]."
Death(target)
else
usr << "You missed![usr.attackrating]"
usr.attackrating -= b
switch(usr.weptype)
if("sword")sword++
if("dagger")dagger++
if("polearm")polearm++
if("archery")archery++
if("blunt")blunt++
if("axe")axe++
if("unnarmed")unnarmed++


But it's not increasing after the attack, what am I doing wrong?
In response to Pakbaum
The first thing you need to do is to discard that code you just posted; it is based on several critial design errors. The next thing you should do is read the Byond-Wiki (Bwicki) page BasicCombatSystem. This page discusses many of the problems in your attack system, like how you're subtracting the target's hp in the attack verb. This may not seem like a large problem, but it is the exact sort of thing which will prevent you from being able to add features like 'equipment proficiency' to your program.

Once you've reworked the attack system, we can get to work on adding weapon proficiency. By reworked, I mean that you at least have separated hp deduction and death from the attack verb, and have put them into their proper procs (hit() and adj_hp(), for example).

Now your code should look something like this:
mob
var
hp
proc
attack(var/mob/target)
target.hit(src, damage)
hit(var/mob/attacker, var/damage)
adj_hp(damage, attacker)
adj_hp(var/amount, var/mob/attacker)
hp -= amount
hp = max(hp,0)
//This makes sure that hp isn't negative.
if(hp <= 0)
die(attacker)
die(var/mob/attacker as mob|null)
Del()

The first thing you should note is that this code does not work, and is not complete. This is simply a design of a system and how it should work, not the actual code for that system. The second thing that you should notice is that there's a lot of seemingly usless procs; for instance, all hit() seems to do is call adj_hp(), and adj_hp() doesn't seem to do anything that couldn't have been done directly in attack(). But these proc are very important, they can be redefined by different mob types to do special actions when: they attack, they are hit, they loose hp, they die. You now have tons of options, which translates to features in your program.

Imagin that you want one of your mobs, a ninja for example, to counter attack whenever attacked. This is simple with my version:
mob/ninja/hit(var/mob/attacker, var/damage)
.=..() //Do the default behavior; defined above.
attack(attacker)

Imagin what this would look like in the other code:
mob/verb
Attack()
... (lots of code)
if(target.type == /mob/ninja)
var/ninja_a = target.attackrating
//same for b and c, and then the += command
//let's leave out all the critial stuff for simplicity, eh?
src.hp -= target.strength
src << "You've been hit yada yada"
target << "You counter attacked yada yada"
... (lots of code)

Ack! Even with the over simplification, it's still a monster. The code for your attack system will be over a mile long.



Anyway, let's move on to your original question, equipment proficiency. We'll start with attacking (yes, I'm going to give you proficiency with defending, too!). Instead of keeping a horribly long switch statement with each type of weapon, and having to hard code each new type when you add it, we'll use an associative list (Lummox JR has a great article on associative lists). Each item in the list will be a string (text surrounded by quotes, such as "polearm"), and each item will be associated with a numerical value representing how good the player is with that type of weapon. An example entry might look like this: "staff" = 5
That would mean that the mob's proficiency with staff type weapons has a value of 5. Each weapon will have a variable saying what type it is.
obj/weapon
var
class as text
staff
class = "staff"
mob
var
obj/weapon/equiped_weapon
list/weapon_proficiencies[0]
//This makes sure the mob starts with a list,
//and not null.
verb
interface_attack()
set name = "Attack"
var/target = locate(/mob) in get_step(src,dir)
attack(target)
proc
attack(var/mob/target)
var/weapon_type = equiped_weapon.class
var/proficiency = weapon_proficiencies[weapon_type]
if(!proficiency)
//The mob may not have an entry for this type
//of weapon yet.
proficiency = 1
var/damage = /*some combination of weapon strength,
player strength, proficiency, and randomness.*/

proficiency++
weapon_proficiencies[weapon_type] = proficiency
target.hit(src, damage)

I don't know about you, but I think that's super snazzy. The best part is that you can add as many weapon types as you want, and you'll never have to recode anything! You could add the following snippet, and you'd never have to go back and edit a switch statement:
obj/weapon/squid
class = "mollusk"
obj/weapon/rainbow
class = "etherial"
obj/weapon/sword_of_pwnage
class = "1337"

With a little bit of work, the above could be changed into armor proficiency, or how good you are with using your armor. Or, you could add defense proficiency, or how good you are at defending against different types of weapons. Or you could add both!

Let's look at something else that can be done, though. Something much more interesting and useful. What if the squid weapon in the above example was supposed to confuse the target whenever it attacked? Instead of adding a switch statement to the attack verb to check what we're attacking, we'll give the weapon it's own way of doing what it wants.
mob/proc/attack(var/mob/target)
var/weapon_type = equiped_weapon.class
var/proficiency = weapon_proficiencies[weapon_type]
if(!proficiency)
//The mob may not have an entry for this type
//of weapon yet.
proficiency = 1
var/damage = /*some combination of player strength, proficiency, and randomness.*/
proficiency++
weapon_proficiencies[weapon_type] = proficiency
weapon.attack(target, damage, src)
obj/weapon
proc
attack(var/mob/target, var/damage, var/mob/attacker)
var/adjusted_damage = /*some combination of damage, weapon strength, and randomness.*/
target.hit(attacker, adjusted_damage)
obj/weapon/squid/attack(var/mob/target, var/damage, var/mob/attacker)
.=..() //Do the regular stuff before we do the custom stuff.
if(target) //check if the target is still alive after being hit.
target.confuse()

And there you have it, a custom squid weapon. The same priciple applies to all custom types. A vampire can override it's attack() proc in order to steal health from the victem. Chainmail shirts can defend better against being attacked by bladed weapons (/obj/armor objects would need their own 'hit()' or 'defense()' proc, to be called somewhere in target.hit()). The sword_of_pwnage can even decide if you are 1337 enough to wield it:
obj/weapon/sword_of_pwnage
attack(var/attacker)
if(attacker.key != "Lummox JR")
attacker << "A light from heaven proclaims"
while(attacker)
sleep(15)
attacker << "Thou not worthy!"
attacker.adj_hp(-10)
step_rand(attacker)
In response to IainPeregrine
when it comes to battle systems, i'd rather keep my 'attack' verb one. it is awfully confusing when your code is speard around with items =/
In response to Shoe
Shoe wrote:
when it comes to battle systems, i'd rather keep my 'attack' verb one. it is awfully confusing when your code is speard around with items =/

Simply put, then your games will be featureless. Imagin what your code will look like when you have ninjas/indians, ice/fire armor, and swords/axes? What if ninjas are weak against fire and excell at using axes, but indians don't use ice armor well and defend well against swords? Your attack 'verb' will look like this:

if(ninja && fire && sword)
...
if(ninja && fire && axe)
...
if(ninja && ice && sword)
...
etc.

Every time you add a new object you'll have to go back to each condition and make sure that it's hard coded to include the new type. Each time you add a new feature, you'll have to completely redo the entire system.

Battles require a combat system, and not just an 'attack verb'. A system is a group of interrelated objects, functions, and variables that work together to perform a task or a set of related tasks. A verb is one way in which the user interacts with a system.

The bottom line is that objects are supposed to own the methods (procs and verbs) that define their behavior. If a mob is able to attack, it should define the way in which it attacks. If a mob is able to be hit, it should define how it reacts to being hit. If a weapon should do something special when it attacks, the weapon should define that, not some arbitrary third party object. Why should an attacking mob define the way a turf reacts when a potion is droped onto it from a player who just was killed by the attacking mob?
mob/verb/attack()
if(mob.died)
if(potion in inventory)
if(turf can hold a new item)
turf.contents += the potion
else
delete potion
if(sword in inventory)
if(turf can hold a new item)
//yada yada for each new feature / item / player / anything
// that you ever want to use in your game.
In response to IainPeregrine
I completely agree. It would be so much easier to just have a proc that checks something. You would never have to change the proc, but if you had it all in one place like an attack verb, you'd have to keep changing it, over and over again, like iain said.
In response to IainPeregrine
IainPeregrine wrote:
Shoe wrote:
when it comes to battle systems, i'd rather keep my 'attack' verb one. it is awfully confusing when your code is speard around with items =/

Simply put, then your games will be featureless. Imagin what your code will look like when you have ninjas/indians, ice/fire armor, and swords/axes? What if ninjas are weak against fire and excell at using axes, but indians don't use ice armor well and defend well against swords? Your attack 'verb' will look like this:

if(ninja && fire && sword)
...
if(ninja && fire && axe)
...
if(ninja && ice && sword)
...
etc.

Every time you add a new object you'll have to go back to each condition and make sure that it's hard coded to include the new type. Each time you add a new feature, you'll have to completely redo the entire system.

Battles require a combat system, and not just an 'attack verb'. A system is a group of interrelated objects, functions, and variables that work together to perform a task or a set of related tasks. A verb is one way in which the user interacts with a system.

The bottom line is that objects are supposed to own the methods (procs and verbs) that define their behavior. If a mob is able to attack, it should define the way in which it attacks. If a mob is able to be hit, it should define how it reacts to being hit. If a weapon should do something special when it attacks, the weapon should define that, not some arbitrary third party object. Why should an attacking mob define the way a turf reacts when a potion is droped onto it from a player who just was killed by the attacking mob?
> mob/verb/attack()
> if(mob.died)
> if(potion in inventory)
> if(turf can hold a new item)
> turf.contents += the potion
> else
> delete potion
> if(sword in inventory)
> if(turf can hold a new item)
> //yada yada for each new feature / item / player / anything
> // that you ever want to use in your game.
>


when i used the term 'attack' verb, i meant a verb that contained all of my needs when i 'attack'.

but i have to agree on this one, when it comes to complicated battle systems such as this. but i still think simple battle systems should keep it in one verb and maybe a proc or two.
In response to Pakbaum
I see that you're using a text variable to describe the weapons' classes; however, I think it would be much easier to instead create equipment subtypes. Also, it's probably better to have the player mob owning a reference variable to its equipped weapon. I recommend checking out hub://wizkidd0123.equipmentdemo.
You need to be using subtypes.
Instead of:
obj/Sword

You should use a more specific path like this:
obj/Items/Weapons/Swords/Broadsword

(It may seem tedious but you will find things like these can save you a lot of time as opposed to hardcoding everything.)

When you need to check the type of a weapon, you simply need to check the paths:
if(Player.Weapon.type in typesof(/obj/Items/Weapons/Swords))


Note: Don't directly copy anything; This code takes in a few assumptions.

In response to Hobbesx
Well, if your trying to do it based on uses, why not pull it off as if it were a mob? Set it EXP and maxEXP and have a level up proc for it, increase its damage or something. I'm just giving an example for his first post, I didn't read all of the others.
In response to IainPeregrine
Ok ok, I've reworked it, but because I'm not exactly sure how some of the things work, could you explain anything I've done wrong? And I'm also aware that ya'll may think it may have well been copied and pasted, but I typed it all out to understand it better.

//ATTACK
mob
var
obj/weapon/weptype
list/weapon_skills[0]

verb
attack_base()
set name = "Attack"
var/target = locate(/mob) in get_step(src,dir)
attack(target)
proc
attack(var/mob/target)
var/weapon_type = weptype.wtype
var/proficiency = weapon_skills[weapon_type]
if(!proficiency)
usr << "You are not skilled with this weapon type, and cannot use it!"
var/damage = strength + weptype.damageb
proficiency++
target.hit(src,damage)

hit(var/mob/attacker as mob, var/damage as num)
var/blocked = attacker.attackrating < target.dodgec
var/unblocked_damage = max(damage-blocked,0)
. = unblocked_damage
src << "[attacker.name] attacks you. [unblocked_damage] damage dealt!"
health_adjustment(-unblocked_damage, attacker)

health_adjustment(var/amount as num, var/mob/adjuster as mob)
hp -= amount
if(hp <= 0)
Death(adjuster)
hp = min(hp, maxhp)
return hp

xp_adjustment(var/amount as num)
xp += amount
return xp



//WEAPON/OBJ
obj/var
wtype as text
damageb = 10

mob/var
armr = 0
armrn = ""

obj/weapon
verb
Arm()
if(usr.armr!=0)
usr << "[usr.armrn] is already armed."
else
suffix = "Armed"
usr.armr = 1
usr.armrn = src.name
usr.weptype = src.wtype


Unarm()
if(usr.armrn!=src.name)
usr << "You do not have this armed."
else
suffix = null
usr.armr = 0
usr.armrn = ""
usr.weptype = "unnarmed"


Thank you for any help.
And BTW:

runtime error: Cannot read null.wtype
proc name: attack (/mob/proc/attack)
usr: Pakbaum (/mob)
src: Pakbaum (/mob)
call stack:
Pakbaum (/mob): attack(null)
Pakbaum (/mob): Attack()

Is what I get when I try it out.
In response to Pakbaum
Pakbaum wrote:
Ok ok, I've reworked it, but because I'm not exactly sure how some of the things work, could you explain anything I've done wrong? And I'm also aware that ya'll may think it may have well been copied and pasted, but I typed it all out to understand it better.

Good. From the looks of your code, and the changes you made to it, it seems that you made an earnest effort to learn and understand what you're doing. The immediate cause of your runtime error is (probably) that the attacker didn't equip a weapon; hence, "weptype" would be "null" (nothing) and the program can't access "null.wtype". One of the first things that we can do to fix this is to check if the attacker doesn't have a weapon, and do some sort of default action if he doesn't:
attack(var/mob/target)
var/weapon_type
var/weapon_damage
if(weptype) // if the player has a weapon
weapon_type = weptype.wtype
weapon_damage = weptype.damageb
else //if the player doesn't have a weapon
weapon_type = "unarmed"
weapon_damage = 0
var/proficiency = weapon_skills[weapon_type]
if(!proficiency)
usr << "You are not skilled with this weapon type, and cannot use it!"
return
/*You forgot to exit out of the proc last time,
so the player would have gotten the message
but still attacked.*/

/*Exiting is fine, but they'll never be able to
use an item until their proficiency with that
class of weapon has been set. This could be done
in some sort of training, quest, or storyline element.*/

var/damage = strength + weapon_damamge
/*I've replaced weptype.damageb with weapon_damage
so that unarmed players can attack without causing
a runtime error (cannot access null.damageb)
*/

proficiency++
/*If we're not using proficiency in the damage calculation
(in other words, if we not making the damage greater
when the player is more proficient) then why are
we increasing it?*/

/*Also, proficiency is only a local (temporary)
variable. If we really do want proficiency to increase
with each attack, then we need to save the temporary
variable back into the list.*/

weapon_skills[weapon_type] = proficiency
target.hit(src,damage)

Also, the name of your weapon storing variable is misleading. "weptype" would mean that the variable stores a weapon type, something like "polearm" (text describing the type) or /obj/weapon/polearm (an actual DM type path). Instead, the weptype variable should be storing a data object, something like a mob or an obj. A better name would be something like "equiped_weapon" or just "weapon". I would strongly suggest that you change the name, and more importantly, that you come to understand the difference, it is a very important concept.

So, why is weptype null? Because we never set it to any value: obj/weapon/weptype
That line doesn't actually make a new object, it just defines a variable and tells the compiler that it will be of type weapon; right now it's value is null, we need to store an actual object in that variable. This is normally done in an "equip" verb: weptype = weapon_object()
I see that you set weptype equal to "wtype" down in the Arm function (/obj/weapon/verb/Arm). If wtype is text (like "polearm") then you'll get errors in your attack function when you try to access "polearm".wtype
Instead, set weptype to the actual object that is being equiped: usr.weptype = src
Now we can access it's variables and functions.

That should be enough to fix the runtime error you experienced, but there's a couple other things in this code I think are worth looking at.

In /mob/attack_base you never check if a target wasn't found. If a player calls the verb attack_base while no one is in front of them, then locate will return null, and target will be set to null. So we need to check if a target was found, and bail out (exit, return) if there was no one there:
attack_base()
set name = "Attack"
var/target = locate(/mob) in get_step(src,dir)
if(!target)
//no one there!
return
attack(target)


In /mob/hit you set the blocked variable equal to a booleen value (0 or 1). This is great considering that the word 'blocked' would mean the variable was either TRUE (blocked) or FALSE (not blocked), so that later you could check if(blocked). The problem is the next line, where you are subtracting blocked from damage. blocked can only ever be 1 or 0, so unblocked_damage will always be either damage, or damage-1; I don't think this is what you wanted. Because I don't know how attackrating or dodgec work, I can't suggest what to do; I might have been able to suggest something had 'dodgec' a more descriptive name.

What is damageb (in /obj/var)? Battle_damage, or maybe damage_base? I'd suggest a more descriptive name, it could save you a lot of time six months from now. Similarly, armr and armrn don't tell me much about what they do, or how n makes them different.

Thank you for any help.

No problem, it's great to help someone who's making this kind of effort to learn the language.
In response to IainPeregrine
So how would I manually add the proficiency? So that when character creation is going on, and the user chooses the weapon, they can start out with that proficiency?

New code:
mob
var
obj/weapon/wep_equipped
list/weapon_skills[0]

verb
attack_base()
set name = "Attack"
var/target = locate(/mob) in get_step(src,dir)
attack(target)
proc
attack(var/mob/target)
var/weapon_type
var/weapon_damage
if(wep_equipped)
weapon_type = wep_equipped.wtype
weapon_damage = wep_equipped.damageb
else
weapon_type = "unarmed"
weapon_damage = strength
var/proficiency = weapon_skills[weapon_type]
if(!proficiency)
usr << "You are not skilled with this weapon type, and cannot use it!"
return
var/damage = strength + wep_equipped.damageb
proficiency++
target.hit(src,damage)

hit(var/mob/attacker as mob, var/damage as num)
var/blocked = attacker.attackrating < target.dodgec
var/unblocked_damage = max(damage-blocked,0)
. = unblocked_damage
src << "[attacker.name] attacks you. [unblocked_damage] damage dealt!"
health_adjustment(-unblocked_damage, attacker)

health_adjustment(var/amount as num, var/mob/adjuster as mob)
hp -= amount
if(hp <= 0)
Death(adjuster)
hp = min(hp, maxhp)
return hp

xp_adjustment(var/amount as num)
xp += amount
return xp
Page: 1 2