Try   HackMD

The Mobbening: A how-to guide

Introduction

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.

Key Concepts

The AI system

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:

  1. Is there a task running?
    2. Yep, k, lets keep ticking that task
  2. Nope? Okay, evaluate the preconditions of each task (usually stuff like "am I off cooldown?" or "do I have enough energy?")
  3. For the tasks that pass the precondition check, evaluate and score possible targets (like, get humans in range, and score them based on how close they are)
  4. multiply that score by the task weight (some tasks are more important than others)
  5. choose the best scoring task, and start it

Example - sneks

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Next, find all references to /obj/critter/snake (in vscode, that's just right click -> find all references)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Making it a mob

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Congrats, now you have snake mob! You can instantly posess it and walk around as a snake! Isn't class inheritance great?

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

UPDATE:

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Time for AI

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

We'll add a couple of basic tasks: wander and attack.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

And set the ai_type in our mob critter and set is_npc = TRUE

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Hooray!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Special Behaviour

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:

  • replace 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 stuff
  • Aggressive vs Peaceful AI: there are a few ways to do this. The way I've been doing it is to create a peaceful AI holder that doesn't have attack tasks, but you could also just have seek_targets() return an empty list. It's less efficient, but it works in a pinch.

Retaliation

You might want your mob to respond when attacked. You can that easily by just setting a few variables.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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 incapacitated
  • RETALIATE_ONCE will hit back only once

Note that if a mob retaliates, it will interupt whatever other task it is doing.

Weird bugs

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.

Conclusion

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!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →