ID:1350599
 
If there is anything that I'm doing wrong or inefficiently, please let me know, and I'll update this.


mob
NPC // I personally prefer to use an extra "NPC" parent so that my followers will still have some of the same attributes as other NPC's, this is not required.
Follower //This is required. It's a "Follower" parent, that way you can easily create multiple followers without having to write all this code over and over again
var
Leader // Who is (s)he following?
Following = 0 // Is she still following them?
DidWait = 1 // This variable indicates whether or not your follower disappears when you click them again after dismissing them.
Click()
if(Leader == usr) //If their leader clicked them.
if(DidWait) //Did you click them after their timer ran out?
if(!Following)
usr << "<b>[src]:</b> Alright, you lead the way.</b>"
walk_to(src,usr) // Follower(src) will now constantly walk to the person who clicked her
Following = 1

else
Following = 0
walk(src, 0) //Stop walking
DidWait = 0 //They didn't wait long enough
sleep(10)// Wait a second to make DidWait = 1. Use Spawn() if you want to display the message immediately.
DidWait = 1
usr << "<b>[src]:</b> I'll wait here, master..</b>" //Tell the player it's safe to click them without deleting their loyal follower.
else
del(src) // Delete the follower
else
Leader = usr //This is just for testing purposes, in case you can't be bothered to immediately make a quest, much less play one to test a follower that may or may not work.
return


What this snippet does is create a very basic follower for your player. If you click them while they're waiting, they will follow you. If you click them while they're following you, they will stop following you, but if you click two times without waiting, you dismiss them.

Use spawn(10) rather than sleep(10), or you are going to only see the message once you've waited long enough.
That's kind of the point. I want players to know when it's okay to click them again. I did still edit the comment in case some people would rather the message display immediately, though.
I assume this is for single player games only? Otherwise, it looks like anyone can simply click your follower and "steal" it from you.
No, that's just for testing. If you want to actually implement a real follower in your game, you can remove the testing lines and have a quest involving them or simply buy them from a store, depending on how you want players to acquire them.
I think it would be better to tell them first (Since you make them stop following first anyways).

I wouldn't want to have them stop following, then wait, then hear them say they are waiting. It's dumb.
To give an example of how differently you can handle these systems, I'd like to show how I tend to implement actions and behaviors.

Good AI managers are basically just state machines. A state machine is just a pathway to a number of potential actions (or states). State Machines should monitor the higher level thought process of an AI node, and apply logic to the input to determine the most correct output. At its most basic, we can consider each action a verb, while the state machine manages the subject, object, and adjectives. That said, state machines are easier to implement and manage if they are modular and each piece can interact with the others to create emergent behavior.

Basically, all mobs have the potential to have actions applied to them. Actions are singleton prototypes that use action_memory in lieu of variables. The Behavior manager is also a singleton object, because the state of the machine is maintained in the object being acted upon's action_memory. I'm not sharing my Behavior manager here. It's too lengthy, and not quite ready for a production environment.

Your state machine should act on your mobs like a player would. That means that our actions should be useable on the player too. If our NPCs and our players interact with the world with the same tools, it allows for some much nicer results, and a unified codeflow.

Basically, if you want to have a mob do something in your world, you can just get the action by name from a global list of actions, then tell the action that the mob will be using it for its behavior now. The Action will negotiate priority and memory spaces with competing or queued actions, and then they will operate on the mob.

mob
var
tmp
action/action
list/action_memory
proc
Activate()
src.behavior.Activated(src)
Deactivate()
src.behavior.Deactivated(src)

action
var
//static variables are global variables referenced from this type.
static
action_count = 0
list
//all global actions should be referenced by unique name here.
actions = list()
proc
get_id(var/mob/m)
return m.action_memory["id"]

get_startTime(var/mob/m)
return m.action_memory["start"]

//called when an action starts processing.
//by default, force any action currently processing to yield, then set up the action memory for the assigned mob
begin(var/mob/m)
if(action_count>65535)
action_count = 0
if(m.action!=null)
if(!m.action.yield(m,src))
return null
m.action = src
m.action_memory = list()
m.action_memory["id"] = ++action_count
m.action_memory["start"] = world.time
return m

//call when an action completes successfully.
//if callback "endfunc/obj/args" are defined, trigger the callback.
end(var/mob/m)
if(m.action_memory.Find("endfunc"))
var/datum/d = m.action_memory["endobj"]
call(d,m.action_memory["endfunc"])(arglist(m.action_memory["endargs"]))
m.action_memory = null
m.action = null

//call when an action is terminated early.
//if callback "cancelfun/obj/args" are defined, trigger the callback.
canceled(var/mob/m)
if(m.action_memory.Find("cancelfunc"))
var/datum/d = m.action_memory["cancelobj"]
call(d,m.action_memory["cancelfunc"])(arglist(m.action_memory["cancelargs"]))
m.action_memory = null
m.action = null

//call to see if we are going to give up control to another action.
yield(var/mob/m,var/action/a)
//by default, cancel the action and return true, which will allow the other action to take control.
src.canceled(m)
return 1



This is an example of an action. This action moves a mob over time toward a destination:

(Note that this example can move more than once per tick depending on movement speed, so... That's why it uses a loop.)

action
var/static
actions = list("moveto"=new/action/move_to())
move_to
proc
act(var/mob/m)
var/d = m.action_memory["speedremainder"]
var/dv = 100/m.action_memory["speed"]
var/atom/o = m.action_memory["goal"]
var/sid = m.action_memory["id"]
while(d<1&&m.action_memory["id"]==sid)
if(bounds_dist(m,o)<=m.action_memory["distance"])
end(m)
return
d += dv
if(!step(m,get_center_dir(m,o)))
canceled(m)
return
m.action_memory["speedremainder"] = d-round(d)
spawn(round(d))
if(m.action!=null)
if(m.action==src&&m.action_memory["id"]==sid)
act(m)

begin(var/mob/m,var/atom/goal,var/distance,var/speed)
. = ..(m)
if(.)
m.action_memory["goal"] = goal
m.action_memory["distance"] = distance
m.action_memory["speed"] = speed
m.action_memory["speedremainder"] = 0
act(m)


I actually wrote a rudimentary pixel-movement follower script using this:

These are some helper functions needed for pixel movement and the project settings.

#define TILE_WIDTH 32
#define TILE_HEIGHT 32
#define STEP_SIZE 4

mob
step_size = STEP_SIZE
obj
step_size = STEP_SIZE
world
fps = 32
mob = /mob/avatar
icon_size = "32x32"
map_format = SIDE_MAP

proc
round_nearest(var/v)
. = round(v)
var/rv = v - .
if(rv>=0.5)
return . + 1
else if(rv<=-0.5)
return . - 1
else
return .

get_center_dir(var/atom/o1,var/atom/o2)
var/sx = (o1.x-1)*TILE_WIDTH
var/sy = (o1.y-1)*TILE_HEIGHT
if(istype(o1,/atom/movable))
var/atom/movable/o = o1
sx += o.step_x + o.bound_width/2 + o.bound_x
sy += o.step_y + o.bound_height/2 + o.bound_y
else
sx += TILE_WIDTH/2
sy += TILE_HEIGHT/2
var/ex = (o2.x-1)*TILE_WIDTH
var/ey = (o2.y-1)*TILE_HEIGHT
if(istype(o2,/atom/movable))
var/atom/movable/o = o2
ex += o.step_x + o.bound_width/2 + o.bound_x
ey += o.step_y + o.bound_height/2 + o.bound_y
else
ex += TILE_WIDTH/2
ey += TILE_HEIGHT/2
return turn(EAST,round_nearest(arctan(ex-sx,ey-sy)/15)*15)

arctan(x,y)
return (y>=0)?(arccos(x/sqrt(x*x+y*y))):(-arccos(x/sqrt(x*x+y*y)))


We set up a party system real fast:

mob
var
action/action
list/action_memory = list()
speed = 300
avatar
bound_x = 4
bound_y = 0
bound_width = 24
bound_height = 24
var
party/party = new/party()
Moved(var/OldLoc,var/Dir,var/newDir,var/step_x,var/step_y)
if(OldLoc==null||src.loc==null||get_dist(OldLoc,src.loc)>1)
src.party.Coil(src.loc)
src.party.Moved(OldLoc,Dir)
New()
..()
spawn()
src.party.characters += src
src.party.characters += new/mob/partytest(src.loc)
src.party.characters += new/mob/partytest(src.loc)
src.party.characters += new/mob/partytest(src.loc)

partytest
density = 0
bound_x = 4
bound_y = 0
bound_width = 24
bound_height = 24


And then we set up the client to tell the party to move when you click on a turf.

client
var
movehelper/movehelper = new/movehelper()
Click(var/atom/movable/O,var/location,var/control,var/params)
var/list/l = params2list(params)
var/action/a = actions["moveto"]
var/d = 0
if(istype(O,/turf))
src.movehelper.loc = O
src.movehelper.resize(src.mob)
src.movehelper.step_x = (round_nearest((text2num(l["icon-x"]) - movehelper.bound_width/2 - movehelper.bound_x)/STEP_SIZE)*STEP_SIZE)
src.movehelper.step_y = (round_nearest((text2num(l["icon-y"]) - movehelper.bound_height/2 - movehelper.bound_y)/STEP_SIZE)*STEP_SIZE)
d = -min(movehelper.bound_width,movehelper.bound_height)
a.begin(src.mob,src.movehelper,d,src.mob.speed)
else
a.begin(src.mob,O,d,src.mob.speed)


Movehelper assists us in making sure we've reached our destination fully, because DM has silly ideas about the location being at the bottom-left of an object, rather than the center of a bounding box/mob.

movehelper
parent_type = /atom/movable
density = 0
proc
resize(var/atom/movable/O)
src.bound_x = O.bound_x
src.bound_y = O.bound_y
src.bound_width = O.bound_width
src.bound_height = O.bound_height

atom
movable
proc
Moved(var/OldLoc,var/Dir,var/mdir,var/step_x,var/step_y)
Move(var/NewLoc,var/Dir=0,var/step_x=0,var/step_y=0)
var/OldLoc = src.loc
var/osx = src.step_x
var/osy = src.step_y
var/odir = src.dir
. = ..(NewLoc,Dir,step_x,step_y)
if(.)
Moved(OldLoc,odir,Dir,osx,osy)


And lastly, the party datum helps us to control your followers a little better. Movement reminds me a bit of earthbound, but with some issues. Once I get edge-sliding in, these issues won't be a problem anymore.

party
var
list/characters = list()
proc
Moved(var/d)
for(var/count=2;count<=characters.len;count++)
var/mob/m = characters[count]
var/mob/lc = characters[count-1]
if(bounds_dist(m,characters[count-1])>0)
step(m,get_center_dir(m,lc))
Coil(var/loc)
var/mob/m
for(var/count=2;count<characters.len;count++)
m = characters[count]
m.loc = loc



Your way is definitely easier, but it doesn't fit the mold of what belongs in a "snippet". It's not that your example isn't complex enough. It's that it's simple AND poorly explained.

You've basically shown how to use the walk command. A little more effort next time?
I'm not sure I understand. What is a state machine, and how would it apply to a follower mob?
In response to GamerMania
A state machine will allow for a follower mob and more. I thought he explained state machines pretty well. You could always Google it (Or use the forum search, I know there have been several explanations on the forum).
In response to Albro1
I'm afraid you'll have to explain like I'm 5. I've done research and even looked at a few resources, but I'm still having trouble understanding exactly what they are and why you would use them instead of procs.
In response to GamerMania
GamerMania wrote:
I'm afraid you'll have to explain like I'm 5. I've done research and even looked at a few resources, but I'm still having trouble understanding exactly what they are and why you would use them instead of procs.

You could easily write your entire state machine in a set of procs. I use objects so that I can dynamically change properties about a state machine, extend them, and even mix their behaviors together with a reference.

You can't easily do that with a proc.

These are singleton objects, though, so that means they are about as good as procs, exept they sacrifice a bit of overhead when it comes to invoking them.

Its main advantages, however, come from not having to know anything about the state of the machine to manipulate and change it ahead of time.
In response to GamerMania
GamerMania wrote:
I'm afraid you'll have to explain like I'm 5. I've done research and even looked at a few resources, but I'm still having trouble understanding exactly what they are and why you would use them instead of procs.

A state machine is simply a system where the unit (the "machine"; in our case, the AI bot) has a list of specific motives/goals/functions ("states") that they have pre-defined for them. They can only be engaged in one of these states at a time, but they can change their state based on outside input and other "decision making" triggers.

The system requires two basic things: drivers for each state, and a "brain" to choose which state the bot should be using. That's really all this boils down to.

1) A central controller/"brain" for the AI:

This will take in all input (where the bot is, where the player is, and what he's doing, the levels of all pertinent stats in the bot, the bots current "needs", etc., anything in your game that can sway the AI's current goals) and use those to make a "decision" about which "state" the bot should be in ("am I hungry? I should switch to the "find food" state." "is the player attacking me? can I fight back, or should I run? If fight, then switch to the fighting state, if run, then switch to the running state." "should I be following somebody? then switch to the following state." etc.)

2. State controllers:

There will be one of these for every possible state your bots might need ("fight", "defend", "run", "follow", "eat", etc. Within each of these, you define everything that the bot must do to satisfy this state. Whatever it takes the bot to accomplish the current goal. (perhaps it's a system for combat, with instructions on what to do during combat; "if armed, not seriously injured, and within range, attack player. If injured, and have healing potion, take potion. If injured with no way to heal? maybe switch to "run away" state." etc.)

Ter13's method is to contain these pieces within objects (which is one of the most convenient ways to do it; but you could use datums as well, or you could do it entirely through procs; but like he mentions, that makes it harder to revise because it's less modular)