mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-09 23:27:56 +01:00
61fb177584
## About The Pull Request When the `force_move()` component is applied, it registers two signals on the involved mob, to stop it moving, and a third one on the `move_loop` it's created. The signal on the created loop is there so that when the loop ends, the `force_move` component can delete itself and free the user. However, there are no checks for whether the `move_loop` was successfully created(which isn't the actual problem, although I have made it runtime there so it is easier to catch in the future). The issue is that when creating two identical moveloops, it cancels the creation of the loop, returning nothing even though there is a loop in effect. The fix was to return the existing loop in this scenario, because it's identical to what would have been made had there not been a loop already. ## Why It's Good For The Game fixes #96069 - Certain raptors transfer hits to their owners, and (this seems like an oversight in and of itself) piercing projectiles thus hit twice, which in the case of the honk staff does two slips, two `force_move`s, two loops, and triggers our identical loop problem. fixes #88352 - The issue report is vague and lacking in specific scenarios, but the mention of slipping again fixing it(which it does with this bug, as the new `force_move` component deletes the broken one) makes me pretty sure it's the same bug fixes #95994 - I'm not certain on this one because I don't know how the Parade works internally, but it seems likely that it could cause two slips on the same tile fixes #94515 - I'm just assuming this is the same bug based on it having the same symptoms ## Changelog 🆑 fix: Several ways you could become permanently immobile, primarily based around lube & other methods of slipping, have been fixed /🆑
174 lines
7.1 KiB
Plaintext
174 lines
7.1 KiB
Plaintext
/**
|
|
* Acts as a namespace for movement packet/type related procs
|
|
*
|
|
* Exists to provide an in code implementation of movement looping
|
|
* Replaces things like walk() or walk_to(), among others
|
|
*
|
|
* Because we're doing things in engine, we have a lot more control over how different operations are performed
|
|
* We also get more say in when things happen, so we can subject movements to the whims of the master controller
|
|
* Rather then using a fuck ton of cpu just moving mobs or meteors
|
|
*
|
|
* The goal is to keep the loops themselves reasonably barebone, and implement more advanced behavior and control via the signals
|
|
*
|
|
* This may be bypassed in cases where snowflakes are nessesary, or where performance is important. S not a hard and fast thing
|
|
*
|
|
* Every atom can have a movement packet, which contains information and behavior about currently active loops, and queuing info
|
|
* Loops control how movement actually happens. So there's a "move in this direction" loop, a "move randomly" loop
|
|
*
|
|
* You can find the logic for this control in this file
|
|
*
|
|
* Specifics of how different loops operate can be found in the movement_types.dm file, alongside the [add to loop][/datum/move_manager/proc/add_to_loop] helper procs that use them
|
|
*
|
|
**/
|
|
/datum/move_manager
|
|
|
|
GLOBAL_DATUM_INIT(move_manager, /datum/move_manager, new)
|
|
|
|
///Adds a movable thing to a movement subsystem. Returns TRUE if it all worked, FALSE if it failed somehow
|
|
/datum/move_manager/proc/add_to_loop(atom/movable/thing_to_add, datum/controller/subsystem/movement/subsystem = SSmovement, datum/move_loop/loop_type, priority = MOVEMENT_DEFAULT_PRIORITY, flags, datum/extra_info)
|
|
var/datum/movement_packet/our_data = thing_to_add.move_packet
|
|
if(!our_data)
|
|
our_data = new(thing_to_add)
|
|
|
|
var/list/arguments = args.Copy(2) //Drop the atom, since the movement packet already knows about it
|
|
return our_data.add_loop(arglist(arguments))
|
|
|
|
///Returns the subsystem's loop if we're processing on it, null otherwise
|
|
/datum/move_manager/proc/processing_on(atom/movable/packet_owner, datum/controller/subsystem/movement/subsystem)
|
|
var/datum/movement_packet/packet = packet_owner.move_packet
|
|
if(!packet)
|
|
return
|
|
var/datum/move_loop/linked_loop = packet.existing_loops[subsystem]
|
|
if(!linked_loop)
|
|
return
|
|
if(linked_loop.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
|
|
return linked_loop
|
|
if(linked_loop != packet.running_loop)
|
|
return
|
|
return linked_loop
|
|
|
|
///A packet of information that describes the current state of a moving object
|
|
/datum/movement_packet
|
|
///Our parent atom
|
|
var/atom/movable/parent
|
|
///The move loop that's currently running, excluding those that ignore priority.
|
|
var/datum/move_loop/running_loop
|
|
/**
|
|
* Flags passed from the move loop before it calls move() and unset right after.
|
|
* Allows for properties of a move loop to be easily checked by mechanics outside of it.
|
|
* Having this a bitfield rather than a type var means we don't get screwed over
|
|
* if the move loop gets deleted mid-move, FYI.
|
|
*/
|
|
var/processing_move_loop_flags = NONE
|
|
///Assoc list of subsystems -> loop datum. Only one datum is allowed per subsystem
|
|
var/list/existing_loops = list()
|
|
|
|
/datum/movement_packet/New(atom/movable/parent)
|
|
src.parent = parent
|
|
parent.move_packet = src
|
|
|
|
/datum/movement_packet/Destroy(force)
|
|
parent.move_packet = null
|
|
parent = null
|
|
for(var/datum/controller/subsystem/processor as anything in existing_loops)
|
|
var/datum/move_loop/loop = existing_loops[processor]
|
|
if(QDELETED(loop))
|
|
continue
|
|
qdel(loop)
|
|
existing_loops.Cut()
|
|
existing_loops = null //Catch anyone modifying this post del
|
|
return ..()
|
|
|
|
///Adds a loop to our parent. Returns the created loop if a success, null otherwise
|
|
/datum/movement_packet/proc/add_loop(datum/controller/subsystem/movement/subsystem, datum/move_loop/loop_type, priority, flags, datum/extra_info)
|
|
var/datum/move_loop/existing_loop = existing_loops[subsystem]
|
|
|
|
if(existing_loop && existing_loop.priority > priority)
|
|
if(!(existing_loop.flags & MOVEMENT_LOOP_IGNORE_PRIORITY) && !(flags & MOVEMENT_LOOP_IGNORE_PRIORITY))
|
|
return //Give up
|
|
|
|
if(existing_loop?.compare_loops(arglist(args.Copy(2))))
|
|
return existing_loop //it already exists stop trying to make the same moveloop
|
|
|
|
var/datum/move_loop/new_loop = new loop_type(src, subsystem, parent, priority, flags, extra_info) //Pass the mob to move and ourselves in via new
|
|
var/list/arguments = args.Copy(6) //Just send the args we've not already dealt with
|
|
|
|
var/worked_out = new_loop.setup(arglist(arguments)) //Here goes the rest
|
|
if(!worked_out)
|
|
qdel(new_loop)
|
|
return
|
|
|
|
existing_loops[subsystem] = new_loop
|
|
if(existing_loop)
|
|
qdel(existing_loop) //We need to do this here because otherwise the packet would think it was empty, and self destruct
|
|
contest_running_loop(new_loop)
|
|
return new_loop
|
|
|
|
///Attempts to contest the current running move loop. Returns TRUE if the loop is active, FALSE otherwise
|
|
/datum/movement_packet/proc/contest_running_loop(datum/move_loop/contestant)
|
|
var/datum/controller/subsystem/movement/contesting_subsystem = contestant.controller
|
|
|
|
if(contestant.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
|
|
contesting_subsystem.add_loop(contestant)
|
|
return TRUE
|
|
if(!running_loop)
|
|
running_loop = contestant
|
|
contesting_subsystem.add_loop(running_loop)
|
|
return TRUE
|
|
if(running_loop.priority > contestant.priority)
|
|
return FALSE
|
|
|
|
var/datum/controller/subsystem/movement/current_subsystem = running_loop.controller
|
|
|
|
var/current_running_loop = running_loop
|
|
running_loop = contestant
|
|
current_subsystem.remove_loop(current_running_loop)
|
|
if(running_loop != contestant) // A signal registrant could have messed with things
|
|
return FALSE
|
|
contesting_subsystem.add_loop(contestant)
|
|
return TRUE
|
|
|
|
///Tries to figure out the current favorite loop to run. More complex then just deciding between two different loops, assumes no running loop currently exists
|
|
/datum/movement_packet/proc/decide_on_running_loop()
|
|
if(running_loop)
|
|
return
|
|
if(!length(existing_loops)) //Die
|
|
qdel(src)
|
|
return
|
|
var/datum/move_loop/favorite
|
|
for(var/datum/controller/subsystem/movement/owner as anything in existing_loops)
|
|
var/datum/move_loop/checking = existing_loops[owner]
|
|
if(checking.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
|
|
continue
|
|
if(favorite && favorite.priority > checking.priority)
|
|
continue
|
|
favorite = checking
|
|
|
|
if(!favorite) //This isn't an error state, since some loops ignore the concept of a running loop
|
|
return
|
|
|
|
var/datum/controller/subsystem/movement/favorite_subsystem = favorite.controller
|
|
|
|
running_loop = favorite
|
|
favorite_subsystem.add_loop(running_loop)
|
|
|
|
/datum/movement_packet/proc/remove_loop(datum/controller/subsystem/movement/remove_from, datum/move_loop/loop_to_remove)
|
|
if(loop_to_remove == running_loop)
|
|
running_loop = null
|
|
remove_from.remove_loop(loop_to_remove)
|
|
if(loop_to_remove.flags & MOVEMENT_LOOP_IGNORE_PRIORITY)
|
|
remove_from.remove_loop(loop_to_remove)
|
|
if(QDELETED(src))
|
|
return
|
|
if(existing_loops[remove_from] == loop_to_remove)
|
|
existing_loops -= remove_from
|
|
decide_on_running_loop()
|
|
return
|
|
|
|
/datum/movement_packet/proc/remove_subsystem(datum/controller/subsystem/movement/remove)
|
|
var/datum/move_loop/our_loop = existing_loops[remove]
|
|
if(!our_loop)
|
|
return FALSE
|
|
qdel(our_loop)
|
|
return TRUE
|