We're making all the nasty /obj/critter
in /mob/living/critter
. Why? because fuck you thats why because /obj/critter
tries to replicate a lot of mob-like behaviour, which results in duplicated unmaintainable code, and is just bad OOP practice.
I made some stuff to make it easier, which you can find in this PR and this tutorial will show you how to use it, by example.
There's a few bits to this. Obviously first thing, we need to create a mob. Then we need to add AI to that mob. Then we need to replicate any special behaviour the /obj/critter
had. Then we need to replace any old references to the /obj/critter
with the new /mob/living/critter
Creating mobs is pretty easy.
Adding the AI is a little more difficult.
Special behaviour can be a pain, but can also be easy.
Replacing is like 99% easy.
The new(ish) mobAI system works very differently from the chain-of-if-statements that /obj/critter
generally uses.
Essentially, at each tick the AI system does the following:
You can view the code for this example in this PR: https://github.com/goonstation/goonstation/pull/11129
Alright, to start, we find the object definition. In this case, it's in /code/obj/critter/misc.dm
Next, find all references to /obj/critter/snake
(in vscode, that's just right click -> find all references)
This tells us what we're gonna need to modify, and any special behaviour it might have. Unsuprisingly, snakes have the special behaviour of being used for the "Sticks to snakes" spell. We'll get to that.
Go to /code/mob/living/critter/
and create a new file. This one is going to be snake.dm
. Make sure it says "ticked" in the bottom right corner of vscode.
Now create the path and add some appropriate parameters. We can copy/paste a good amount of this from the /obj/critter
definition.
We can also use the /small_animal
class to get some easy stuff out of the way, like health holders.
Congrats, now you have snake mob! You can instantly posess it and walk around as a snake! Isn't class inheritance great?
Let's set some simple properties so we don't accidentally affect balance. We can copy most of these from the /small_animal
class and modify them to match the /obj/critter
values
In an effort to standardise mob attacks, we prefer mobs to use limbs. Snakes have a mouth, which is a type of limb, so we have to set hand_count
to 1
and add a simple setup_hands()
proc like this:
Lets make our snake do stuff. First we have to create it a brain.
If you look under /code/mob/living/critter/ai
you will find th generic_critter.dm
file, which contains the basic critter behaviour. We'll be using this to add AI to the snake.
In that same folder, I'll create a snake.dm
file which will contain our AI definition. As it says in the generic_critter.dm
file, we do that by creating an AI holder for our critter, and subclassing /datum/aiTask/prioritizer/critter
like so:
We'll add a couple of basic tasks: wander and attack.
And set the ai_type
in our mob critter and set is_npc = TRUE
Hooray, now our snake does stuff! It will chase you, and try to bite you (as snakes frequently do in the wild). Snakes have simple attacks, and so the defaults are enough. Just give it a mouth limb, and it'll try to use it. Some mobs have special attack behaviour, which you will need to implement by adding your own critter_attack(var/mob/target)
proc, which was specifically added just for this problem! You can see an example of this in /mob/living/critter/spider
Often you can just copy and paste some parts of /obj/critter
's CritterAttack()
function - wow! It's almost like that was done on purpose to make this easier! You can see other procs that were added for this in code/mob/living/critter.dm
We can test our snake in game, and see that it will attack us properly:
The /obj/critter/snake
does have some special targetting restrictions, and we can use those here with one of the helper-procs from code/mob/living/critter.dm
We can use seek_target(var/range)
to return a list of valid targets within range that the critter AI will use. We can't quite copy and paste this from the /obj/critter
, but it's not that different. The main difference is that /obj/critter/SeekTarget()
returns a single target and sets the task, where /mob/living/critter/seek_target()
returns a list of potential targets, and the AI task scores them instead.
Also, snakes hiss when they go after a target, so let's include that here as well - with a prob()
, just to avoid spam.
Hooray!
This particular critter is created by the "Sticks to Snakes" wizard spell, and we need to replicate that behaviour.
Fortunately, most of this is just a copy/paste job, since it basically just sets icon state and name appropriately, and stores the converted object inside the critter to be dropped on death. We'll need to make some modifications to handle the "on death" part. We also need to replace references to /obj/critter/snake
with /mob/living/critter/small_animal/snake
in the special behaviour procs, and throughout the rest of the code.
Some tips:
src.alive
with !isdead(src)
because isalive()
returns false when the mob is unconcious.CritterDeath()
can just be replaced with death()
it does basically the same stuffseek_targets()
return an empty list. It's less efficient, but it works in a pinch.You might want your mob to respond when attacked. You can that easily by just setting a few variables.
Setting ai_retaliates
to TRUE
will make it respond to being harmed.
Setting ai_retaliates_patience
will determine how many hits it takes before this mob will attack back. 0
means it will attack immediately upon being harmed.
ai_retaliates_persistence
determines how aggressively the mob should retaliate. There are three settings:
RETALIATE_UNTIL_DEAD
will keep attacking the attacker until the attacker dies.RETALIATE_UNTIL_INCAP
will keep attacking the attacker until they are knocked down or otherwise incapacitatedRETALIATE_ONCE
will hit back only onceNote that if a mob retaliates, it will interupt whatever other task it is doing.
So when testing this, it didn't work first time - converting things into snakes dropped their brain on the floor, so I replace the contents check with a direct reference instead, which makes more sense anyway.
Also, the sticks to snakes spell allows conversion of snakes into double snakes, but the /mob
check was first, so I had to move the double snake check up.
And that's it - we now have a mob snake, so you can suplex snakes when wizards are about! Plus all the usual mob behaviour, including posession. You can play the game as a baton-snake if you want now!