ID:1869567
 
(See the best response by Ter13.)
Problem description: So I updated to the BYOND beta today but this piece of code worked fine before. Am I missing something or is it a bug?

runtime error: Undefined operation: 40 / null
proc name: Attack (/mob/verb/Attack)
source file: Battle.dm,26
usr: Rickoshay (/mob/player)
src: Rickoshay (/mob/player)
call stack:
Rickoshay (/mob/player): Attack()


Code:
mob
verb
Attack()
if(usr.attacking || usr.dead || usr.frozen)
return

var/mob/m = locate(/mob) in obounds(usr, 5, dir) //get_step(usr, dir)

if(m)
if(m == usr || m == friendly || m.dead)
return

usr.attacking = 1

if(usr.dizzy)
if(usr.miss())
usr << "You miss due to being dizzy"
sleep(5)
usr.attacking = 0
return

//animate(usr, icon_state = "punch 1", loop=1)
var/rand_dmg = rand(1,10)
var/damage = round(rand_dmg * usr.str / m.def)

m.take_damage(damage, usr)

sleep(5)
usr.attacking = 0

var/damage = round(rand_dmg * usr.str / m.def)

Is m.def null?
No, not for the NPC or player.
Did you do a debug output to make sure?
Heh, strange. Turns out you were right it was null. First time it had done this though.

This is how I had it set out.

mob
var
def
player
str = 5
def = 5
enemy
def = 5
Well, the real source of your issue is polymorphism abuse. You are using /mob as a category rather than a polymorphic type in its own right...

But at this point you are probably too far along to change that quickly.
Not really it's only a small project. A dungeon crawler. I'm not quite sure I understand what you mean though.

Could you explain for future projects or so I can fix the issue now?
Basically Ter means that there's no real point in having mob/player and mob/enemy be separate types unless you're overriding their behavior or other info. If default str and def are going to be 5, then that should be set where str and def are defined: in /mob.

That also happens to be the source of your problem, I think. You're not defining def to be any value other than null except in these subtypes, but you really should have sensible defaults at the place where the var was defined. Which means most likely, in this case locate() found a mob that was neither /mob/player nor /mob/enemy; therefore its defense stat is null.
Incidentally I'd change this to use bitflags:

if(usr.attacking || usr.dead || usr.frozen)

All of these binary true/false states are much better handled as bitflags.

if(usr.state & (ATTACKING | DEAD | FROZEN))

Also, instead of using locate() to get a mob, I'd use a loop instead. That way, you can skip over friendly mobs with the continue statement. Right now, if you have one friendly mob in range and it happens to be the one that was targeted, you can't attack anybody.
Ahhh I see, I had the player and NPC's in my original code with all different stats but I've set a default for /mob.

Also thanks for all the info Lummox been a big help. I don't really know much about bitflags, but I'll give Unknown Person's tutorial a read.
Just also realised I had my NPC's set out like this. /mob/npc/enemy/rat and instead of putting that down on the map i put /mob/npc/. Which had no defined def.

Well you live and learn I suppose.
Best response
Gladly.

So here's the basics of the issue.

mob/player is the player
mob/enemy is a non-player enemy that you can fight.

Fighting is behavior that's tied to players and npc enemies only.

So why are combat stats higher in the tree than either of those two children?

To put it more simply:

Let's say you have reptiles and birds. Birds evolved from reptiles, so the family tree would look like this:

reptile
avian


An avian is a child of a reptile. The avian can do everything that the reptile can do, but the reptile may not be able to do everything that the avian can do. This is called polymorphism.

Now, mammals are also a child of reptiles.

reptile
avian
mammal


Both avians and mammals are reptiles, but reptiles are neither avian or mammal. Likewise, avians and mammals are siblings, and thus an avian isn't a mammal and a mammal isn't an avian.

Now, you might ask, why is this important? Biology cares about something called "morphology", or how an animal is shaped. We classify animals into groups and trees based on subtle changes in shape/function (morphology) over time.

In software design, though, we care about behavior. We classify objects into trees based on how they are structured and function.

Now, if behavior makes objects different from one another, that means we need a more in-depth example to really drive your problem home.

Let's define some behaviors: (some of these aren't always true, but for the purposes of this explanation, let's go with it.)

- Reptiles and Avians lay eggs
- Avians and Mammals are warm blooded
- Mammals give live birth
- Reptiles and Avians have scales
- Mammals have fur
- Reptiles are born without parental dependence.
- Avians and mammals care for their young after birth.

So let's look at how this works with some really bad examples:

reptile
var
lays_eggs = 1
has_scales = 1
cold_blooded = 1
avian
cold_blooded = 0
var
care_for_young = 1
warm_blooded = 1
mammal
lays_eggs = 0
has_scales = 0
cold_blooded = 0
var
live_birth = 1
care_for_young = 1
has_fur = 1
warm_blooded = 1


Notice how mammal has properties inherited from reptile that don't make sense? Like "has scales", and "cold_blooded". Also look at how warm_blooded and care_for_young are both defined independently each in avian and mammal. This can be bad for general questions about an animal.

Your gut reaction would be to do something like this:

reptile
var
thermal_type = EXOTHERM
birth_type = EGG_LAYER
skin_type = SCALES
nurture_type = ABANDON_YOUNG
avian
skin_type = FEATHERS | SCALES
thermal_type = ENDOTHERM
nurture_type = NURTURE_YOUNG
mammal
skin_type = FUR | SKIN
thermal_type = ENDOTHERM
nurture_type = NURTURE_YOUNG
birth_type = LIVE_BIRTHER


This is better, but it unnecessarily puts all of the behavioral burden on the reptile type. A reptile doesn't need to know how to give live birth in order to have children that do. This type of approach is not actually polymorphism, it's basically an interface-record design. It's not a bad approach all of the time, but in many cases it leads to undesirable behavior.


Now, basically, what's going on in your game is that you have combat-related variables defined under the base type because you want to use usr without compiler errors.

This is one of the areas that I will trash about BYOND. Verbs. Verbs are one of the main things that BYOND programmers learn that they can't seem to do right, and it ruins them on transitioning to a proper programming language. In a proper programming language, there's no such thing as "usr", and that's a good thing.

The reason so many people do verbs wrong is because they don't understand polymorphism at all. Think about it: Who needs verbs? Players. Players need verbs. A player almost always will be connected to a mob, but the player themselves is represented by the client. A client can transition between multiple mobs during its lifetime in a world, so mob-specific behavior should never be exposed to the client object.

That's where verb troubles start coming in. Because usr is always typed as /mob. Your player is /mob/player in your game, so any variables you define under /mob/player won't be accessible to usr. Most people, instead of learning to avoid this pitfall the elegant way, instead only cause themselves more headaches just like the little one you discovered (it's only the tip of the iceberg). They do this by jamming every variable into the lowest level possible. In proper polymorphic design, variables should always be embedded at the highest level possible, but the lowest level necessary. If you have a type that has no need of a variable, it shouldn't have it. Period.

So what's the elegant way, you ask? Well, that's actually amazingly simple. It's called typecasting.

You can actually treat an object as another type of object by typecasting. It's really easy to do.

var/mob/player/user = usr


That's called a typecast. We are casting from the default type /mob to a more explicit type: /mob/player. Now, our variable user has access to /mob/player specific variables.

But you have another problem. Both /mob/player and /mob/enemy need access to combat variables. Well, that's easy too. Let's create a child class of mob that is the parent class of both player and enemy. This child class will contain all behavior related to combat and will be called a combatant.

mob
combatant
var
health = 100
max_health = 100
str = 5
def = 5
player
enemy


Now, player and enemy inherit combatant behavior from the root type.

So what about non-combatants? When would we want something you couldn't fight? Well, what about a quest-giver or a tutorial NPC that you don't want players to be able to kill? Sure, you could make it so that it regenerates health constantly at a rate that makes it unkillable, but that's just extra processing. Just don't make it fightable in the first place.

Alright, let's take a look at verb structure. I honestly argue that verbs should always be avoided except at the client level and for right-click menus (even then, the right-click menu is ugly and could be made yourself to look a lot better).

We should assume that verbs are always being called by a player. That means you should always cast usr into a /mob/combatant/player typed variable.

mob
combatant
proc
Attack(mob/combatant/target)
target.TakeDamage(rand(1,10)*str/target.def,src)

TakeDamage(amt,mob/combantant/attacker)
health -= amt
if(health<=0)
attacker.Kill(src)
Die(attacker)

Die(mob/combatant/killer)
return

Kill(mob/combatant/victim)
return
player
var
exp = 0
respawn_point = "game start"
verb
AttackVerb()
set name = "Attack"
var/mob/combatant/target = locate(/mob/combatant) in get_step(src,dir) //you'll want to fix this if you use pixel movement
if(target)
Attack(target)
proc
GainExp()

Die()
loc = locate(respawn_point)
enemy
var
exp_value

Die(mob/combatant/killer)
if(istype(killer,/mob/combatant/player))
var/mob/combatant/player/p = killer
p.GainExp(exp_value)
loc = null


The above example is a relatively simplistic approach for handling polymorphic behavior in a game. I really dislike verbs quite a lot, so the AttackVerb verb is ugly to me, but it does to the job as an example.

Notice how we defined functions in Combatant and then changed their behavior further down in enemy and player? That's polymorphic design. We're leaving ourselves lots of room for customization and behavioral changes without the lower-level code having to be aware of what higher level code is going to do.


A not very important section:

Now, because every time I talk about polymorphism on these forums, someone like Forum_account shows up and goes on about multiple inheritance and how polymorphism prevents multiple inheritance. It doesn't. BYOND doesn't allow multiple inheritance, but you can fake it and it's pretty cool.

Let's say you want to implement a shopkeeper that does a bunch of shopkeepy stuff, but isn't fightable. Then you later want to make a shopkeeper that also does shopkeepy stuff, but is fightable. You either have to abandon my combatant type, or you have to make a combatant behavior for something not acting like a combatant which is silly.

There's a third option.

mob
combatant
shopkeeper
var
shop_open = 0
list/shop_items
list/shop_prices
proc
Shop(mob/combatant/player/p)
//do a bunch of shoppy stuff here.
shopkeeper
var
shop_open = 0
list/shop_items
list/shop_prices
proc
Shop(mob/combatant/player/p)
//do a bunch of shoppy stuff here.


Okay, this is the basis for our approach. But it gets better. So you don't want to have to implement the same code twice? I hear you.

Let's take a look at how to make this easier to maintain:

i_shopkeeper
var
shop_open = 0
list/shop_items
list/shop_prices
proc
Shop(mob/combatant/player/p)
//this is just an example
if(1==1)
return 1
return 0


Okay, so we just put a third type in with the same code. This is worse, not better dammit! Watch.

#define SHOPKEEPER_BEHAVIOR var/__is_shopkeeper=1,shop_open=0,list/shop_items,list/shop_prices;proc/Shop(mob/combatant/player/p){if(1==1){return 1;}return 0;};

proc
is_shopkeeper(mob/m)
if(m&&istype(m)&&m.vars["__is_shopkeeper"]==1)
. = 1
else
. = 0

i_shopkeeper
var
__is_shopkeeper = 1
shop_open = 0
list/shop_items
list/shop_prices
proc
Shop(mob/combatant/player/p)
//this is just an example
if(1==1)
return 1
return 0

mob
combatant
shopkeeper
SHOPKEEPER_BEHAVIOR
shopkeeper
SHOPKEEPER_BEHAVIOR


Now, while you are maintaining your code, the code in i_shopkeeper is called the "interface code". You should never create i_shopkeeper objects. Only use it as a type for casting. This is called an interface.

By reducing the required code to create a shopkeeper into a single line, we can include all of the code to create a shopkeeper with a single preprocessor rather than having to maintain the same code in multiple areas over and over again. You just need to remember to minimize any changes you make into a single line in the proper format and update the behavior definition.

Now you can do cool stuff like this:

if(is_shopkeeper(object))
var/i_shopkeeper/shopkeep = object
i_shopkeeper.Shop()


the i_shopkeeper interface is never created. i_shopkeeper actually refers to an object that is an instance of either /mob/shopkeeper or /mob/combatant/shopkeeper. So you are actually directly calling that mob's Shop proc instead of the interface's Shop proc. You can do this over and over again with multiple behaviors and cobble together types that use a bunch of different interfaces to mix and match behavior across different object types without having to type it more than once.

Unfortunately, you have to reformat the code any time you change it, so it does require a bit of setup.
Oh, wow. That was a really good read Ter and incredibly helpful. You should post that as one of your snippet Sundays.

I may have to go over this again due to it being nearly 12:30 here in the UK but I'm definitely gonna implement some of this into my code. Shopkeeper's aren't necessary in my game, but all the same it could come in handy in the future.

Once again, thank you.
I will warn you the multiple-inheritance model is still in its infancy. I'm still heavily refining the technique and it may never be a good thing to actually use in practice because it's currently hacky. I'm just putting it out there as an example that stupid discussions crop up in reference to polymorphism in this community largely due to pervasive ignorance regarding the concept spread by a couple of people in particular who are falsely considered to have the best grasp of DM in this community.
In response to Ter13
Half those bitflags look stolen from Dwarf Fortress raws. :)
In response to Lummox JR
Lummox JR wrote:
Half those bitflags look stolen from Dwarf Fortress raws. :)

I'd like to look under the hood of his game one day, but I think I'd wind up carving forgotten beasts into the floor all day and demanding seashells until I throw myself off the nearest cliff from melancholy.
Just a quick question would this also be a viable option? Just because I like having players and NPC's in different files. Although then again it might be better to keep it all in the same file.

mob
combatant
player


npc
parent_type = /mob/combatant/

enemy
Yeah, parent_type is just shorthand for setting the inheritance path.
You can also do mob/combatant/npc to achieve the same result and make it much easier to search for the path later on.