ID:2925223
 
(See the best response by Ter13.)
Code:
mob/player
Move(loc, dir = 0)
if(busy)
return // Prevent movement (player is busy).
if(..(loc, dir))
if(pet)
// Add current position to queue.
movement_queue += list(x, y, z)
if(movement_queue.len > 10) // Limit the queue length.
movement_queue.Cut(1, 1)
// Call follow_path if not following.
if(!pet.following)
pet.following = TRUE
spawn() pet.follow_path()


    // Proc to follow the player's path
mob/pet
proc/follow_path()
if(owner.movement_queue.len > 0)
var/list/coords = owner.movement_queue[1] // Get the oldest set of coordinates.
owner << "Processing coords: [coords]" //Debug output
owner.movement_queue.Cut(1, 1) // Remove the first set from the queue.
if(islist(coords) && coords.len == 3) // Ensure the coordinates list has the correct length.
var/x = coords[1]
var/y = coords[2]
var/z = coords[3]
if(src.loc != locate(x, y, z))
owner << "Pet moving to: ([x], [y], [z])" //Debug output
step_to(src, locate(x, y, z))
spawn(2) follow_path() // Call the proc again after a delay.
else
owner << "Invalid coords length or not a list: [coords]" //Debug output
following = FALSE
return
else
owner << "Finished processing movement queue" //Debug output


Problem description:

The debug messages are output:

Finished processing movement queue
Processing coords: 3
Invalid coords length or not a list: 3


The problem is that the pet does not follow the player. I am positive I have mangled the code at this point. The goal was to make the pet follow the owner's exact path of movement.

Example: If the owner if moving EAST 1 tile and decides to move NORTH 1; The pet should still move EAST 1 tile in order to remain behind the owner. It's a way of mimicking the owner's steps.

I don't expect a 100% fix from anyone, but pointing me in the right direction would be greatly appreciated.
Best response
movement_queue += list(x, y, z)


This isn't doing what you think it is. This is actually adding 3 items to the movement_queue. Adding list b to list a adds all the items in list b to the end of list a instead of the list itself.

What you want is:

movement_queue[++movement_queue.len] = list(x,y,z)


So the other issue is essentially that you are trying to move the monster to the position the player just moved to, instead of the position the player just moved from. You want to cache their x,y,z before the movement, and then append it to the queue.

Honestly, if you want a proper teardown of this whole thing, it's pretty overcomplicated. What we're gonna do is add a global ticker to handle the follower logic and build it into the root /mob type using reference ids so that garbage collection can still happen like it should and so we don't pollute any savefiles on accident.

var/list/follower_mobs = list()

world
Tick()
..()
//tick any mobs set to follow another mob.
for(var/ref in follower_mobs)
locate(ref)?.onTick()

mob
var
tmp
list/followers
list/watchpath
following
last_move = -1#INF

step_delay = 10 //configure to change how often the mob steps

Move()
var/oloc = loc
if((. = ..()))
var/list/l

//let our followers know where to walk
for(var/v in followers)
l = locate(v)?.watchpath
if(l) l += oloc

//set the last move time for lockouts.
last_move = world.time

proc
//call to tell this mob to follow another one.
Follow(mob/target)
var/ref = "\ref[src]"

//clean up any exiting followers
if(following)
locate(following)?.followers -= ref
watchpath.Cut(1,0)
else if(target)
//we're transitioning to a different target
watchpath = list()
global.follower_mobs += ref
else
//we're canceling a previous follow
watchpath = null
global.follower_mobs -= ref

//get ready to follow a new object
if(target)
if(!target.followers)
target.followers = list(ref)
else
target.followers += ref

following = "\ref[target]"

Del()
//clean up anything we're following
if(following)
Follow(null)

//clean up anything that's following us
for(var/ref in followers)
locate(ref)?.Follow(null)
..()

proc
//called when the global ticker fires on this object
onTick()
//make sure there's someplace to follow, and we're not locked out of movement by step delay
if(watchpath?.len && last_move + step_delay <= world.time)
var/loc = watchpath[1]
if(src.loc==loc || Move(watchpath))
watchpath.Cut(1,2)
In response to Ter13
I appreciate the in-depth post in detail. Some of what you wrote I understand- Most I don't. I'm a bit intimidated by your method. This just tells me I need to go back to studying what I'm doing. Thank you!
mob/player/Move(location,direction)
var last_position = loc
var last_direction = dir
if(..(location,direction) && pet)
pet.dir = last_direction
pet.loc = last_position
In response to Kozuma3
Kozuma3 wrote:
mob/player/Move(location,direction)
> var last_position = loc
> var last_direction = dir
> if(..(location,direction) && pet)
> pet.dir = last_direction
> pet.loc = last_position


Short and sweet. I don't know why I was overcomplicating the entire process. This really shines on the areas I still need to improve on. Thanks for the assist. The movement is extremely smooth at 60 FPS. =)
In response to Propylparaben
Here's similar code but modified to allow for a line of pets. Good luck on learning, the best part is having fun :)

mob/player/var/list/pets = new

mob/player/Move(location,direction)
var last_location = loc
var last_direction = dir
var list/cache
if(..(location,direction) && pets.len)
for(var/mob/pet/p in pets)
cache = list(p.loc,p.dir)
p.Move(last_location,last_direction)
last_location = cache[1]
last_direction = cache[2]