diff --git a/.github/guides/STANDARDS.md b/.github/guides/STANDARDS.md
index a9243e50a10..2ce126ba752 100644
--- a/.github/guides/STANDARDS.md
+++ b/.github/guides/STANDARDS.md
@@ -321,6 +321,21 @@ However, DM also has a dot variable, accessed just as `.` on its own, defaulting
With `.` being everpresent in every proc, can we use it as a temporary variable? Of course we can! However, the `.` operator cannot replace a typecasted variable - it can hold data any other var in DM can, it just can't be accessed as one, although the `.` operator is compatible with a few operators that look weird but work perfectly fine, such as: `.++` for incrementing `.'s` value, or `.[1]` for accessing the first element of `.`, provided that it's a list.
+### The BYOND walk procs
+
+BYOND has a few procs that move one atom towards/away from another, `walk()`, `walk_to()`, `walk_towards`, `walk_away()` and `walk_rand()`.
+
+The way they pull this off, while fine for the language itself, makes a mess of our master-controller, and can cause the whole game to slow down. Do not use them.
+
+The following is a list of procs, and their safe replacements.
+
+* Removing something from the loop `walk(0)` -> `SSmove_manager.stop_looping()`
+* Move in a direction `walk()` -> `SSmove_manager.move()`
+* Move towards a thing, taking turf density into account`walk_to()` -> `SSmove_manager.move_to()`
+* Move in a thing's direction, ignoring turf density `walk_towards()` -> `SSmove_manager.home_onto()` and `SSmove_manager.move_towards_legacy()`, check the documentation to see which you like better
+* Move away from something, taking turf density into account `walk_away()` -> `SSmove_manager.move_away()`
+* Move to a random place nearby. NOT random walk `walk_rand()` -> `SSmove_manager.move_rand()` is random walk, `SSmove_manager.move_to_rand()` is walk to a random place
+
### BYOND hellspawn
What follows is documentation of inconsistent or strange behavior found in our engine, BYOND.
diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm
index 2dd06aba271..128d32fa9c0 100644
--- a/code/__DEFINES/MC.dm
+++ b/code/__DEFINES/MC.dm
@@ -38,6 +38,7 @@
/** Treat wait as a tick count, not DS, run every wait ticks. */
/// (also forces it to run first in the tick (unless SS_BACKGROUND))
+/// (We don't want to be choked out by other subsystems queuing into us)
/// (implies all runlevels because of how it works)
/// This is designed for basically anything that works as a mini-mc (like SStimer)
#define SS_TICKER 8
@@ -73,6 +74,13 @@
}\
/datum/controller/subsystem/timer/##X
+#define MOVEMENT_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/movement/##X);\
+/datum/controller/subsystem/movement/##X/New(){\
+ NEW_SS_GLOBAL(SS##X);\
+ PreInit();\
+}\
+/datum/controller/subsystem/movement/##X
+
#define PROCESSING_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/processing/##X);\
/datum/controller/subsystem/processing/##X/New(){\
NEW_SS_GLOBAL(SS##X);\
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index a76ae514236..80dee577737 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -5,7 +5,7 @@
///from base of atom/movable/Moved(): (/atom)
#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move"
#define COMPONENT_MOVABLE_BLOCK_PRE_MOVE (1<<0)
-///from base of atom/movable/Moved(): (/atom, dir)
+///from base of atom/movable/Moved(): (atom/old_loc, dir, forced, list/old_locs)
#define COMSIG_MOVABLE_MOVED "movable_moved"
///from base of atom/movable/Cross(): (/atom/movable)
#define COMSIG_MOVABLE_CROSS "movable_cross"
@@ -13,6 +13,9 @@
#define COMSIG_MOVABLE_CROSS_OVER "movable_cross_am"
///from base of atom/movable/Bump(): (/atom)
#define COMSIG_MOVABLE_BUMP "movable_bump"
+///from base of atom/movable/newtonian_move(): (inertia_direction)
+#define COMSIG_MOVABLE_NEWTONIAN_MOVE "movable_newtonian_move"
+ #define COMPONENT_MOVABLE_NEWTONIAN_BLOCK (1<<0)
///from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum)
#define COMSIG_MOVABLE_IMPACT "movable_impact"
#define COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH (1<<0) //if true, flip if the impact will push what it hits
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
index d96f6f2633a..31dc06ac577 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
@@ -36,3 +36,7 @@
#define COMSIG_ATOM_SINGULARITY_TRY_MOVE "atom_singularity_try_move"
/// When returned from `COMSIG_ATOM_SINGULARITY_TRY_MOVE`, the singularity will move to that turf
#define SINGULARITY_TRY_MOVE_BLOCK (1 << 0)
+///from base of atom/experience_pressure_difference(): (pressure_difference, direction, pressure_resistance_prob_delta)
+#define COMSIG_ATOM_PRE_PRESSURE_PUSH "atom_pre_pressure_push"
+ ///prevents pressure movement
+ #define COMSIG_ATOM_BLOCKS_PRESSURE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm
index 6af44ed10f6..e7e1a0bf7a7 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm
@@ -10,9 +10,6 @@
#define COMSIG_BLOOD_WARP "mob_ability_blood_warp"
/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): ()
#define COMSIG_STARTED_CHARGE "mob_ability_charge_started"
-/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/on_bump(): (atom/target)
-#define COMSIG_BUMPED_CHARGE "mob_ability_charge_bumped"
- #define COMPONENT_OVERRIDE_CHARGE_BUMP (1<<0)
/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): ()
#define COMSIG_FINISHED_CHARGE "mob_ability_charge_finished"
/// from base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): ()
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index b794f47294d..ee321b100fa 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -29,6 +29,10 @@
#define COMSIG_MOB_CLIENT_PRE_MOVE "mob_client_pre_move"
/// Should always match COMPONENT_MOVABLE_BLOCK_PRE_MOVE as these are interchangeable and used to block movement.
#define COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+ /// From base of /client/Move()
+#define COMSIG_MOB_CLIENT_PRE_LIVING_MOVE "mob_client_pre_living_move"
+ /// Should we stop the current living movement attempt
+ #define COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE
/// From base of /client/Move()
#define COMSIG_MOB_CLIENT_MOVED "mob_client_moved"
/// From base of /client/proc/change_view() (mob/source, new_size)
diff --git a/code/__DEFINES/dcs/signals/signals_moveloop.dm b/code/__DEFINES/dcs/signals/signals_moveloop.dm
new file mode 100644
index 00000000000..44cf57c7205
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_moveloop.dm
@@ -0,0 +1,11 @@
+///from [/datum/move_loop/start_loop] ():
+#define COMSIG_MOVELOOP_START "moveloop_start"
+///from [/datum/move_loop/stop_loop] ():
+#define COMSIG_MOVELOOP_STOP "moveloop_stop"
+///from [/datum/move_loop/process] ():
+#define COMSIG_MOVELOOP_PREPROCESS_CHECK "moveloop_preprocess_check"
+ #define MOVELOOP_SKIP_STEP (1<<0)
+///from [/datum/move_loop/process] (succeeded, visual_delay):
+#define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess"
+//from [/datum/move_loop/has_target/jps/recalculate_path] ():
+#define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath"
diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index f67b1b772a9..c4cf7e10194 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -39,7 +39,7 @@
#define WRAP(val, min, max) clamp(( min == max ? min : (val) - (round(((val) - (min))/((max) - (min))) * ((max) - (min))) ),min,max)
// Real modulus that handles decimals
-#define MODULUS(x, y) ( (x) - (y) * round((x) / (y)) )
+#define MODULUS(x, y) ( (x) - FLOOR(x, y))
// Cotangent
#define COT(x) (1 / tan(x))
diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm
index eb52cafdc58..b6e612e6c7b 100644
--- a/code/__DEFINES/movement.dm
+++ b/code/__DEFINES/movement.dm
@@ -13,7 +13,25 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
/// Then that's multiplied by the global glide size multiplier. 1.25 by default feels pretty close to spot on. This is just to try to get byond to behave.
/// The whole result is then clamped to within the range above.
/// Not very readable but it works
-#define DELAY_TO_GLIDE_SIZE(delay) (clamp(((32 / max((delay) / world.tick_lag, 1)) * GLOB.glide_size_multiplier), MIN_GLIDE_SIZE, MAX_GLIDE_SIZE))
+#define DELAY_TO_GLIDE_SIZE(delay) (clamp(((world.icon_size / max((delay) / world.tick_lag, 1)) * GLOB.glide_size_multiplier), MIN_GLIDE_SIZE, MAX_GLIDE_SIZE))
+
+///Similar to DELAY_TO_GLIDE_SIZE, except without the clamping, and it supports piping in an unrelated scalar
+#define MOVEMENT_ADJUSTED_GLIDE_SIZE(delay, movement_disparity) (world.icon_size / ((delay) / world.tick_lag) * movement_disparity * GLOB.glide_size_multiplier)
+
+//Movement loop priority. Only one loop can run at a time, this dictates that
+// Higher numbers beat lower numbers
+///Standard, go lower then this if you want to override, higher otherwise
+#define MOVEMENT_DEFAULT_PRIORITY 10
+///Very few things should override this
+#define MOVEMENT_SPACE_PRIORITY 100
+///Higher then the heavens
+#define MOVEMENT_ABOVE_SPACE_PRIORITY (MOVEMENT_SPACE_PRIORITY + 1)
+
+//Movement loop flags
+///Should the loop act immediately following its addition?
+#define MOVEMENT_LOOP_START_FAST (1<<0)
+///Do we not use the priority system?
+#define MOVEMENT_LOOP_IGNORE_PRIORITY (1<<1)
/**
* currently_z_moving defines. Higher numbers mean higher priority.
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 74c8c4c0907..381a85a6bff 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -199,7 +199,7 @@ GLOBAL_LIST_EMPTY(species_list)
var/user_loc = user.loc
var/drifting = FALSE
- if(!user.Process_Spacemove(0) && user.inertia_dir)
+ if(SSmove_manager.processing_on(user, SSspacedrift))
drifting = TRUE
var/target_loc = target.loc
@@ -232,7 +232,7 @@ GLOBAL_LIST_EMPTY(species_list)
if(!QDELETED(progbar))
progbar.update(world.time - starttime)
- if(drifting && !user.inertia_dir)
+ if(drifting && !SSmove_manager.processing_on(user, SSspacedrift))
drifting = FALSE
user_loc = user.loc
@@ -294,7 +294,7 @@ GLOBAL_LIST_EMPTY(species_list)
var/atom/user_loc = user.loc
var/drifting = FALSE
- if(!user.Process_Spacemove(0) && user.inertia_dir)
+ if(SSmove_manager.processing_on(user, SSspacedrift))
drifting = TRUE
var/holding = user.get_active_held_item()
@@ -315,7 +315,7 @@ GLOBAL_LIST_EMPTY(species_list)
if(!QDELETED(progbar))
progbar.update(world.time - starttime)
- if(drifting && !user.inertia_dir)
+ if(drifting && !SSmove_manager.processing_on(user, SSspacedrift))
drifting = FALSE
user_loc = user.loc
@@ -360,7 +360,7 @@ GLOBAL_LIST_EMPTY(species_list)
time *= user.cached_multiplicative_actions_slowdown
var/drifting = FALSE
- if(!user.Process_Spacemove(0) && user.inertia_dir)
+ if(SSmove_manager.processing_on(user, SSspacedrift))
drifting = TRUE
var/list/originalloc = list()
@@ -393,7 +393,7 @@ GLOBAL_LIST_EMPTY(species_list)
. = FALSE
break
- if(drifting && !user.inertia_dir)
+ if(drifting && !SSmove_manager.processing_on(user, SSspacedrift))
drifting = FALSE
user_loc = user.loc
diff --git a/code/__HELPERS/type_processing.dm b/code/__HELPERS/type_processing.dm
index 127626a1344..4df4cece096 100644
--- a/code/__HELPERS/type_processing.dm
+++ b/code/__HELPERS/type_processing.dm
@@ -63,3 +63,6 @@
if(findtext("[key]", filter, -end_len) || findtext("[value]", filter, -end_len))
matches[key] = value
return matches
+
+/proc/return_typenames(type)
+ return splittext("[type]", "/")
diff --git a/code/controllers/subsystem/processing/ai_movement.dm b/code/controllers/subsystem/movement/ai_movement.dm
similarity index 69%
rename from code/controllers/subsystem/processing/ai_movement.dm
rename to code/controllers/subsystem/movement/ai_movement.dm
index 6a6d64548ca..2be37cd3838 100644
--- a/code/controllers/subsystem/processing/ai_movement.dm
+++ b/code/controllers/subsystem/movement/ai_movement.dm
@@ -1,20 +1,19 @@
/// The subsystem used to tick [/datum/ai_movement] instances. Handling the movement of individual AI instances
-PROCESSING_SUBSYSTEM_DEF(ai_movement)
+MOVEMENT_SUBSYSTEM_DEF(ai_movement)
name = "AI movement"
- flags = SS_KEEP_TIMING|SS_BACKGROUND
+ flags = SS_BACKGROUND|SS_TICKER
priority = FIRE_PRIORITY_NPC_MOVEMENT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_AI_MOVEMENT
- wait = 1
///an assoc list of all ai_movement types. Assoc type to instance
var/list/movement_types
-/datum/controller/subsystem/processing/ai_movement/Initialize(timeofday)
+/datum/controller/subsystem/movement/ai_movement/Initialize(timeofday)
SetupAIMovementInstances()
return ..()
-/datum/controller/subsystem/processing/ai_movement/proc/SetupAIMovementInstances()
+/datum/controller/subsystem/movement/ai_movement/proc/SetupAIMovementInstances()
movement_types = list()
for(var/key as anything in subtypesof(/datum/ai_movement))
var/datum/ai_movement/ai_movement = new key
diff --git a/code/controllers/subsystem/movement/move_handler.dm b/code/controllers/subsystem/movement/move_handler.dm
new file mode 100644
index 00000000000..751a133b5c9
--- /dev/null
+++ b/code/controllers/subsystem/movement/move_handler.dm
@@ -0,0 +1,160 @@
+/**
+ * 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/controller/subsystem/move_manager/proc/add_to_loop] helper procs that use them
+ *
+**/
+SUBSYSTEM_DEF(move_manager)
+ name = "Movement Handler"
+ flags = SS_NO_INIT | SS_NO_FIRE
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+///Adds a movable thing to a movement subsystem. Returns TRUE if it all worked, FALSE if it failed somehow
+/datum/controller/subsystem/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/controller/subsystem/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
+ var/datum/move_loop/running_loop
+ ///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
+
+ 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
+
+ current_subsystem.remove_loop(running_loop)
+ contesting_subsystem.add_loop(contestant)
+ running_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)
+ remove_from.remove_loop(loop_to_remove)
+ running_loop = null
+ 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
diff --git a/code/controllers/subsystem/movement/movement.dm b/code/controllers/subsystem/movement/movement.dm
new file mode 100644
index 00000000000..21342ac8f78
--- /dev/null
+++ b/code/controllers/subsystem/movement/movement.dm
@@ -0,0 +1,51 @@
+SUBSYSTEM_DEF(movement)
+ name = "Movement Loops"
+ flags = SS_NO_INIT|SS_BACKGROUND|SS_TICKER
+ wait = 1 //Fire each tick
+ ///The list of datums we're processing
+ var/list/processing = list()
+ ///Used to make pausing possible
+ var/list/currentrun = list()
+ ///The time we started our last fire at
+ var/canonical_time = 0
+ ///The visual delay of the subsystem
+ var/visual_delay = 1
+
+/datum/controller/subsystem/movement/stat_entry(msg)
+ msg = "P:[length(processing)]"
+ return ..()
+
+/datum/controller/subsystem/movement/Recover()
+ //Get ready this is gonna be horrible
+ //We need to do this to support subtypes by the by
+ var/list/typenames = return_typenames(src.type)
+ var/our_name = typenames[length(typenames)] //Get the last name in the list, IE the subsystem identifier
+
+ var/datum/controller/subsystem/movement/old_version = global.vars["SS[our_name]"]
+ processing = old_version.processing
+ currentrun = old_version.currentrun
+
+/datum/controller/subsystem/movement/fire(resumed)
+ if(!resumed)
+ canonical_time = world.time
+ currentrun = processing.Copy()
+
+ var/list/running = currentrun //Cache for... you've heard this before
+ while(running.len)
+ var/datum/move_loop/loop = running[running.len]
+ running.len--
+ if(loop.timer <= canonical_time)
+ loop.process() //This shouldn't get nulls, if it does, runtime
+ if (MC_TICK_CHECK)
+ return
+ visual_delay = MC_AVERAGE_FAST(visual_delay, max((world.time - canonical_time) / wait, 1))
+
+/datum/controller/subsystem/movement/proc/add_loop(datum/move_loop/add)
+ processing += add
+ add.start_loop()
+
+/datum/controller/subsystem/movement/proc/remove_loop(datum/move_loop/remove)
+ processing -= remove
+ currentrun -= remove
+ remove.stop_loop()
+
diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm
new file mode 100644
index 00000000000..a4511146c1f
--- /dev/null
+++ b/code/controllers/subsystem/movement/movement_types.dm
@@ -0,0 +1,753 @@
+///Template class of the movement datums, handles the timing portion of the loops
+/datum/move_loop
+ ///The movement packet that owns us
+ var/datum/movement_packet/owner
+ ///The subsystem we're processing on
+ var/datum/controller/subsystem/movement/controller
+ ///An extra reference we pass around
+ ///It is on occasion useful to have a reference to some datum without storing it on the moving object
+ ///Mostly comes up in high performance senarios where we care about things being singletons
+ ///This feels horrible, but constantly making components seems worse
+ var/datum/extra_info
+ ///The thing we're moving about
+ var/atom/movable/moving
+ ///Defines how different move loops override each other. Lower numbers beat higher numbers
+ var/priority = MOVEMENT_DEFAULT_PRIORITY
+ ///Bitfield of different things that affect how a loop operates
+ var/flags
+ ///Time till we stop processing in deci-seconds, defaults to forever
+ var/lifetime = INFINITY
+ ///Delay between each move in deci-seconds
+ var/delay = 1
+ ///The next time we should process
+ var/timer = 0
+
+/datum/move_loop/New(datum/movement_packet/owner, datum/controller/subsystem/movement/controller, atom/moving, priority, flags, datum/extra_info)
+ src.owner = owner
+ src.controller = controller
+ src.extra_info = extra_info
+ if(extra_info)
+ RegisterSignal(extra_info, COMSIG_PARENT_QDELETING, .proc/info_deleted)
+ src.moving = moving
+ src.priority = priority
+ src.flags = flags
+
+/datum/move_loop/proc/setup(delay = 1, timeout = INFINITY)
+ if(!ismovable(moving) || !owner)
+ return FALSE
+
+ src.delay = max(delay, world.tick_lag) //Please...
+ src.lifetime = timeout
+ return TRUE
+
+/datum/move_loop/proc/start_loop()
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_START)
+ //If this is our first time starting to move with this loop
+ //And we're meant to start instantly
+ if(!timer && flags & MOVEMENT_LOOP_START_FAST)
+ timer = world.time
+ return
+ timer = world.time + delay
+
+/datum/move_loop/proc/stop_loop()
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_STOP)
+
+/datum/move_loop/proc/info_deleted(datum/source)
+ SIGNAL_HANDLER
+ extra_info = null
+
+/datum/move_loop/Destroy()
+ if(owner)
+ owner.remove_loop(controller, src)
+ owner = null
+ moving = null
+ controller = null
+ extra_info = null
+ return ..()
+
+///Exists as a helper so outside code can modify delay while also modifying timer
+/datum/move_loop/proc/set_delay(new_delay)
+ delay = max(new_delay, world.tick_lag)
+ timer = world.time + delay
+
+/datum/move_loop/process()
+ var/old_delay = delay //The signal can sometimes change delay
+
+ if(SEND_SIGNAL(src, COMSIG_MOVELOOP_PREPROCESS_CHECK) & MOVELOOP_SKIP_STEP) //Chance for the object to react
+ return
+
+ lifetime -= old_delay //This needs to be based on work over time, not just time passed
+
+ if(lifetime < 0) //Otherwise lag would make things look really weird
+ qdel(src)
+ return
+
+ var/visual_delay = controller.visual_delay
+ var/success = move()
+
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_POSTPROCESS, success, delay * visual_delay)
+
+ timer = world.time + delay
+ if(QDELETED(src) || !success) //Can happen
+ return
+
+ moving.set_glide_size(MOVEMENT_ADJUSTED_GLIDE_SIZE(delay, visual_delay))
+
+///Handles the actual move, overriden by children
+///Returns FALSE if nothing happen, TRUE otherwise
+/datum/move_loop/proc/move()
+ return FALSE
+
+///Removes the atom from some movement subsystem. Defaults to SSmovement
+/datum/controller/subsystem/move_manager/proc/stop_looping(atom/movable/moving, datum/controller/subsystem/movement/subsystem = SSmovement)
+ var/datum/movement_packet/our_info = moving.move_packet
+ if(!our_info)
+ return FALSE
+ return our_info.remove_subsystem(subsystem)
+
+/**
+ * Replacement for walk()
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * direction - The direction we want to move in
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/move, priority, flags, extra_info, delay, timeout, direction)
+
+///Replacement for walk()
+/datum/move_loop/move
+ var/direction
+
+/datum/move_loop/move/setup(delay, timeout, dir)
+ . = ..()
+ if(!.)
+ return
+ direction = dir
+
+/datum/move_loop/move/move()
+ var/atom/old_loc = moving.loc
+ moving.Move(get_step(moving, direction), direction)
+ // We cannot rely on the return value of Move(), we care about teleports and it doesn't
+ return old_loc != moving.loc
+
+/**
+ * Like move(), but it uses byond's pathfinding on a step by step basis
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * direction - The direction we want to move in
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_to_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/move/move_to, priority, flags, extra_info, delay, timeout, direction)
+
+/datum/move_loop/move/move_to
+
+/datum/move_loop/move/move_to/move()
+ var/atom/old_loc = moving.loc
+ step_to(moving, get_step(moving, direction))
+ return old_loc != moving.loc
+
+
+/**
+ * Like move(), but we don't care about collision at all
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * direction - The direction we want to move in
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/force_move_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/move/force, priority, flags, extra_info, delay, timeout, direction)
+
+/datum/move_loop/move/force
+
+/datum/move_loop/move/force/move()
+ var/atom/old_loc = moving.loc
+ moving.forceMove(get_step(moving, direction))
+ return old_loc != moving.loc
+
+
+/datum/move_loop/has_target
+ ///The thing we're moving in relation to, either at or away from
+ var/atom/target
+
+/datum/move_loop/has_target/setup(delay, timeout, atom/chasing)
+ . = ..()
+ if(!.)
+ return
+ if(!isatom(chasing))
+ qdel(src)
+ return FALSE
+
+ target = chasing
+
+ if(!isturf(target))
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/handle_no_target) //Don't do this for turfs, because we don't care
+
+/datum/move_loop/has_target/Destroy()
+ target = null
+ return ..()
+
+/datum/move_loop/has_target/proc/handle_no_target()
+ SIGNAL_HANDLER
+ qdel(src)
+
+
+/**
+ * Used for force-move loops, similar to move_towards_legacy() but not quite the same
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/force_move(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/has_target/force_move, priority, flags, extra_info, delay, timeout, chasing)
+
+///Used for force-move loops
+/datum/move_loop/has_target/force_move
+
+/datum/move_loop/has_target/force_move/move()
+ var/atom/old_loc = moving.loc
+ moving.forceMove(get_step(moving, get_dir(moving, target)))
+ return old_loc != moving.loc
+
+
+/**
+ * Used for following jps defined paths. The proc signature here's a bit long, I'm sorry
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * repath_delay - How often we're allowed to recalculate our path
+ * max_path_length - The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
+ * miminum_distance - Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example
+ * id - An ID card representing what access we have and what doors we can open
+ * simulated_only - Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
+ * avoid - If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
+ * skip_first - Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break things
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/jps_move(moving,
+ chasing,
+ delay,
+ timeout,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ obj/item/card/id/id,
+ simulated_only,
+ turf/avoid,
+ skip_first,
+ subsystem,
+ priority,
+ flags,
+ datum/extra_info)
+ return add_to_loop(moving,
+ subsystem,
+ /datum/move_loop/has_target/jps,
+ priority,
+ flags,
+ extra_info,
+ delay,
+ timeout,
+ chasing,
+ repath_delay,
+ max_path_length,
+ minimum_distance,
+ id,
+ simulated_only,
+ avoid,
+ skip_first)
+
+/datum/move_loop/has_target/jps
+ ///How often we're allowed to recalculate our path
+ var/repath_delay
+ ///Max amount of steps to search
+ var/max_path_length
+ ///Minimum distance to the target before path returns
+ var/minimum_distance
+ ///An ID card representing what access we have and what doors we can open. Kill me
+ var/obj/item/card/id/id
+ ///Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
+ var/simulated_only
+ ///A perticular turf to avoid
+ var/turf/avoid
+ ///Should we skip the first step? This is the tile we're currently on, which breaks some things
+ var/skip_first
+ ///A list for the path we're currently following
+ var/list/movement_path
+ ///Cooldown for repathing, prevents spam
+ COOLDOWN_DECLARE(repath_cooldown)
+
+/datum/move_loop/has_target/jps/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, obj/item/card/id/id, simulated_only, turf/avoid, skip_first)
+ . = ..()
+ if(!.)
+ return
+ src.repath_delay = repath_delay
+ src.max_path_length = max_path_length
+ src.minimum_distance = minimum_distance
+ src.id = id
+ src.simulated_only = simulated_only
+ src.avoid = avoid
+ src.skip_first = skip_first
+ if(istype(id, /obj/item/card/id))
+ RegisterSignal(id, COMSIG_PARENT_QDELETING, .proc/handle_no_id) //I prefer erroring to harddels. If this breaks anything consider making id info into a datum or something
+
+/datum/move_loop/has_target/jps/start_loop()
+ . = ..()
+ INVOKE_ASYNC(src, .proc/recalculate_path)
+
+/datum/move_loop/has_target/jps/Destroy()
+ id = null //Kill me
+ avoid = null
+ return ..()
+
+/datum/move_loop/has_target/jps/proc/handle_no_id()
+ SIGNAL_HANDLER
+ id = null
+
+//Returns FALSE if the recalculation failed, TRUE otherwise
+/datum/move_loop/has_target/jps/proc/recalculate_path()
+ if(!COOLDOWN_FINISHED(src, repath_cooldown))
+ return
+ COOLDOWN_START(src, repath_cooldown, repath_delay)
+ SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_REPATH)
+ movement_path = get_path_to(moving, target, max_path_length, minimum_distance, id, simulated_only, avoid, skip_first)
+
+/datum/move_loop/has_target/jps/move()
+ if(!length(movement_path))
+ INVOKE_ASYNC(src, .proc/recalculate_path)
+ if(!length(movement_path))
+ return FALSE
+
+ var/turf/next_step = movement_path[1]
+ var/atom/old_loc = moving.loc
+ moving.Move(next_step, get_dir(moving, next_step))
+ . = (old_loc != moving.loc)
+
+ // this check if we're on exactly the next tile may be overly brittle for dense objects who may get bumped slightly
+ // to the side while moving but could maybe still follow their path without needing a whole new path
+ if(get_turf(moving) == next_step)
+ movement_path.Cut(1,2)
+ else
+ INVOKE_ASYNC(src, .proc/recalculate_path)
+ return FALSE
+
+
+///Base class of move_to and move_away, deals with the distance and target aspect of things
+/datum/move_loop/has_target/dist_bound
+ var/distance = 0
+
+/datum/move_loop/has_target/dist_bound/setup(delay, timeout, atom/chasing, dist = 0)
+ . = ..()
+ if(!.)
+ return
+ distance = dist
+
+///Returns FALSE if the movement should pause, TRUE otherwise
+/datum/move_loop/has_target/dist_bound/proc/check_dist()
+ return FALSE
+
+/datum/move_loop/has_target/dist_bound/move()
+ if(!check_dist()) //If we're too close don't do the move
+ timer = world.time //Make sure to move as soon as possible
+ return FALSE
+ return TRUE
+
+
+/**
+ * Wrapper around walk_to()
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * min_dist - the closest we're allower to get to the target
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_to(moving, chasing, min_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/has_target/dist_bound/move_to, priority, flags, extra_info, delay, timeout, chasing, min_dist)
+
+///Wrapper around walk_to()
+/datum/move_loop/has_target/dist_bound/move_to
+
+/datum/move_loop/has_target/dist_bound/move_to/check_dist()
+ return (get_dist(moving, target) >= distance) //If you get too close, stop moving closer
+
+/datum/move_loop/has_target/dist_bound/move_to/move()
+ . = ..()
+ if(!.)
+ return
+ var/atom/old_loc = moving.loc
+ step_to(moving, target)
+ return old_loc != moving.loc
+
+/**
+ * Wrapper around walk_away()
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * max_dist - the furthest away from the target we're allowed to get
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_away(moving, chasing, max_dist, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/has_target/dist_bound/move_away, priority, flags, extra_info, delay, timeout, chasing, max_dist)
+
+///Wrapper around walk_away()
+/datum/move_loop/has_target/dist_bound/move_away
+
+/datum/move_loop/has_target/dist_bound/move_away/check_dist()
+ return (get_dist(moving, target) <= distance) //If you get too far out, stop moving away
+
+/datum/move_loop/has_target/dist_bound/move_away/move()
+ . = ..()
+ if(!.)
+ return
+ var/atom/old_loc = moving.loc
+ step_away(moving, target)
+ return old_loc != moving.loc
+
+
+/**
+ * Helper proc for the move_towards datum
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * home - Should we move towards the object at all times? Or launch towards them, but allow walls and such to take us off track. Defaults to FALSE
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to INFINITY
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_towards(moving, chasing, delay, home, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/has_target/move_towards, priority, flags, extra_info, delay, timeout, chasing, home)
+
+/**
+ * Helper proc for homing onto something with move_towards
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * home - Should we move towards the object at all times? Or launch towards them, but allow walls and such to take us off track. Defaults to FALSE
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to INFINITY
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/home_onto(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return move_towards(moving, chasing, delay, TRUE, timeout, subsystem, priority, flags, extra_info)
+
+///Used as a alternative to walk_towards
+/datum/move_loop/has_target/move_towards
+ ///The turf we want to move into, used for course correction
+ var/turf/moving_towards
+ ///Should we try and stay on the path, or is deviation alright
+ var/home = FALSE
+ ///When this gets larger then 1 we move a turf
+ var/x_ticker = 0
+ var/y_ticker = 0
+ ///The rate at which we move, between 0 and 1
+ var/x_rate = 1
+ var/y_rate = 1
+ //We store the signs of x and y seperately, because byond will round negative numbers down
+ //So doing all our operations with absolute values then multiplying them is easier
+ var/x_sign = 0
+ var/y_sign = 0
+
+/datum/move_loop/has_target/move_towards/setup(delay, timeout, atom/chasing, home = FALSE)
+ . = ..()
+ if(!.)
+ return FALSE
+ src.home = home
+
+ if(home)
+ if(ismovable(target))
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/update_slope) //If it can move, update your slope when it does
+ RegisterSignal(moving, COMSIG_MOVABLE_MOVED, .proc/handle_move)
+ update_slope()
+
+/datum/move_loop/has_target/move_towards/Destroy()
+ if(home)
+ if(ismovable(target))
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ if(moving)
+ UnregisterSignal(moving, COMSIG_MOVABLE_MOVED)
+ return ..()
+
+/datum/move_loop/has_target/move_towards/move()
+ //Move our tickers forward a step, we're guaranteed at least one step forward because of how the code is written
+ if(x_rate) //Did you know that rounding by 0 throws a divide by 0 error?
+ x_ticker = FLOOR(x_ticker + x_rate, x_rate)
+ if(y_rate)
+ y_ticker = FLOOR(y_ticker + y_rate, y_rate)
+
+ var/x = moving.x
+ var/y = moving.y
+ var/z = moving.z
+
+ moving_towards = locate(x + round(x_ticker) * x_sign, y + round(y_ticker) * y_sign, z)
+ //The tickers serve as good methods of tracking remainder
+ if(x_ticker >= 1)
+ x_ticker = MODULUS(x_ticker, 1) //I swear to god if you somehow go up by one then one in a tick I'm gonna go mad
+ if(y_ticker >= 1)
+ y_ticker = MODULUS(x_ticker, 1)
+ var/atom/old_loc = moving.loc
+ moving.Move(moving_towards, get_dir(moving, moving_towards))
+
+ //YOU FOUND THEM! GOOD JOB
+ if(home && get_turf(moving) == get_turf(target))
+ x_rate = 0
+ y_rate = 0
+ return
+ return old_loc != moving.loc
+
+/datum/move_loop/has_target/move_towards/proc/handle_move(source, atom/OldLoc, Dir, Forced = FALSE)
+ SIGNAL_HANDLER
+ if(moving.loc != moving_towards && home) //If we didn't go where we should have, update slope to account for the deviation
+ update_slope()
+
+/datum/move_loop/has_target/move_towards/handle_no_target()
+ if(home)
+ return ..()
+ target = null
+
+/**
+ * Recalculates the slope between our object and the target, sets our rates to it
+ *
+ * The math below is reminiscent of something like y = mx + b
+ * Except we don't need to care about axis, since we do all our movement in steps of 1
+ * Because of that all that matters is we only move one tile at a time
+ * So we take the smaller delta, divide it by the larger one, and get smaller step per large step
+ * Then we set the large step to 1, and we're done. This way we're guaranteed to never move more then a tile at once
+ * And we can have nice lines
+**/
+/datum/move_loop/has_target/move_towards/proc/update_slope()
+ SIGNAL_HANDLER
+
+ //You'll notice this is rise over run, except we flip the formula upside down depending on the larger number
+ //This is so we never move more then one tile at once
+ var/delta_y = target.y - moving.y
+ var/delta_x = target.x - moving.x
+ //It's more convienent to store delta x and y as absolute values
+ //and modify them right at the end then it is to deal with rounding errors
+ x_sign = (delta_x > 0) ? 1 : -1
+ y_sign = (delta_y > 0) ? 1 : -1
+ delta_x = abs(delta_x)
+ delta_y = abs(delta_y)
+
+ if(delta_x >= delta_y)
+ if(delta_x == 0) //Just go up/down
+ x_rate = 0
+ y_rate = 1
+ return
+ x_rate = 1
+ y_rate = delta_y / delta_x //rise over run, you know the deal
+ else
+ if(delta_y == 0) //Just go right/left
+ x_rate = 1
+ y_rate = 0
+ return
+ x_rate = delta_x / delta_y //Keep the larger step size at 1
+ y_rate = 1
+
+/**
+ * Wrapper for walk_towards, not reccomended, as it's movement ends up being a bit stilted
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * chasing - The atom we want to move towards
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_towards_legacy(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/has_target/move_towards_budget, priority, flags, extra_info, delay, timeout, chasing)
+
+///The actual implementation of walk_towards()
+/datum/move_loop/has_target/move_towards_budget
+
+/datum/move_loop/has_target/move_towards_budget/move()
+ var/turf/target_turf = get_step_towards(moving, target)
+ var/atom/old_loc = moving.loc
+ moving.Move(target_turf, get_dir(moving, target_turf))
+ return old_loc != moving.loc
+
+
+/**
+ * Helper proc for the move_rand datum
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * directions - A list of acceptable directions to try and move in. Defaults to GLOB.alldirs
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_rand(moving, directions, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ if(!directions)
+ directions = GLOB.alldirs
+ return add_to_loop(moving, subsystem, /datum/move_loop/move_rand, priority, flags, extra_info, delay, timeout, directions)
+
+/**
+ * This isn't actually the same as walk_rand
+ * Because walk_rand is really more like walk_to_rand
+ * It appears to pick a spot outside of range, and move towards it, then pick a new spot, etc.
+ * I can't actually replicate this on our side, because of how bad our pathfinding is, and cause I'm not totally sure I know what it's doing.
+ * I can just implement a random-walk though
+**/
+/datum/move_loop/move_rand
+ var/list/potential_directions
+
+/datum/move_loop/move_rand/setup(delay, timeout, list/directions)
+ . = ..()
+ if(!.)
+ return
+ potential_directions = directions
+
+/datum/move_loop/move_rand/move()
+ var/list/potential_dirs = potential_directions.Copy()
+ while(potential_dirs.len)
+ var/testdir = pick(potential_dirs)
+ var/turf/moving_towards = get_step(moving, testdir)
+ var/atom/old_loc = moving.loc
+ moving.Move(moving_towards, testdir)
+ if(old_loc != moving.loc) //If it worked, we're done
+ return TRUE
+ potential_dirs -= testdir
+ return FALSE
+
+/**
+ * Wrapper around walk_rand(), doesn't actually result in a random walk, it's more like moving to random places in viewish
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_to_rand(moving, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/move_to_rand, priority, flags, extra_info, delay, timeout)
+
+///Wrapper around step_rand
+/datum/move_loop/move_to_rand
+
+/datum/move_loop/move_to_rand/move()
+ var/atom/old_loc = moving.loc
+ step_rand(moving)
+ return old_loc != moving.loc
+
+/**
+ * Snowflake disposal movement. Moves a disposal holder along a chain of disposal pipes
+ *
+ * Returns TRUE if the loop sucessfully started, or FALSE if it failed
+ *
+ * Arguments:
+ * moving - The atom we want to move
+ * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
+ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
+ * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
+ * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
+ * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
+ *
+**/
+/datum/controller/subsystem/move_manager/proc/move_disposals(moving, delay, timeout, subsystem, priority, flags, datum/extra_info)
+ return add_to_loop(moving, subsystem, /datum/move_loop/disposal_holder, priority, flags, extra_info, delay, timeout)
+
+/// Disposal holders need to move through a chain of pipes
+/// Rather then through the world. This supports this
+/// If this ever changes, get rid of this, add drift component like logic to the holder
+/// And move them to move()
+/datum/move_loop/disposal_holder
+
+/datum/move_loop/disposal_holder/setup(delay = 1, timeout = INFINITY)
+ // This is a horrible pattern.
+ // Move loops should almost never need to be one offs. Please don't do this if you can help it
+ if(!istype(moving, /obj/structure/disposalholder))
+ stack_trace("You tried to make a [moving.type] object move like a disposals holder, stop that!")
+ return FALSE
+ return ..()
+
+/datum/move_loop/disposal_holder/move()
+ var/obj/structure/disposalholder/holder = moving
+ var/atom/old_loc = moving.loc
+ holder.current_pipe.transfer(holder)
+ return old_loc != moving.loc
diff --git a/code/controllers/subsystem/movement/spacedrift.dm b/code/controllers/subsystem/movement/spacedrift.dm
new file mode 100644
index 00000000000..4002b5eb555
--- /dev/null
+++ b/code/controllers/subsystem/movement/spacedrift.dm
@@ -0,0 +1,5 @@
+MOVEMENT_SUBSYSTEM_DEF(spacedrift)
+ name = "Space Drift"
+ priority = FIRE_PRIORITY_SPACEDRIFT
+ flags = SS_NO_INIT|SS_TICKER
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
diff --git a/code/controllers/subsystem/processing/conveyors.dm b/code/controllers/subsystem/processing/conveyors.dm
index 51df126071a..471750b10a4 100644
--- a/code/controllers/subsystem/processing/conveyors.dm
+++ b/code/controllers/subsystem/processing/conveyors.dm
@@ -1,3 +1,2 @@
-PROCESSING_SUBSYSTEM_DEF(conveyors)
+MOVEMENT_SUBSYSTEM_DEF(conveyors)
name = "Conveyor Belts"
- wait = 0.2 SECONDS
diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm
deleted file mode 100644
index c1c7e2a872f..00000000000
--- a/code/controllers/subsystem/spacedrift.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-SUBSYSTEM_DEF(spacedrift)
- name = "Space Drift"
- priority = FIRE_PRIORITY_SPACEDRIFT
- wait = 5
- flags = SS_NO_INIT|SS_KEEP_TIMING
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
-
- var/list/currentrun = list()
- var/list/processing = list()
-
-/datum/controller/subsystem/spacedrift/stat_entry(msg)
- msg = "P:[length(processing)]"
- return ..()
-
-
-/datum/controller/subsystem/spacedrift/fire(resumed = FALSE)
- if (!resumed)
- src.currentrun = processing.Copy()
-
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
-
- while (currentrun.len)
- var/atom/movable/AM = currentrun[currentrun.len]
- currentrun.len--
- if (!AM)
- processing -= AM
- if (MC_TICK_CHECK)
- return
- continue
-
- if (AM.inertia_next_move > world.time)
- if (MC_TICK_CHECK)
- return
- continue
-
- if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0))
- AM.inertia_dir = 0
-
- if (!AM.inertia_dir)
- AM.inertia_last_loc = null
- processing -= AM
- if (MC_TICK_CHECK)
- return
- continue
-
- var/old_dir = AM.dir
- var/old_loc = AM.loc
- AM.inertia_moving = TRUE
- AM.set_glide_size(DELAY_TO_GLIDE_SIZE(AM.inertia_move_delay))
- step(AM, AM.inertia_dir)
- AM.inertia_moving = FALSE
- AM.inertia_next_move = world.time + AM.inertia_move_delay
- if (AM.loc == old_loc)
- AM.inertia_dir = 0
-
- AM.setDir(old_dir)
- AM.inertia_last_loc = AM.loc
- if (MC_TICK_CHECK)
- return
diff --git a/code/datums/actions/mobs/charge.dm b/code/datums/actions/mobs/charge.dm
index 58bb762c575..c808563485c 100644
--- a/code/datums/actions/mobs/charge.dm
+++ b/code/datums/actions/mobs/charge.dm
@@ -16,12 +16,10 @@
var/charge_damage = 30
/// If we destroy objects while charging
var/destroy_objects = TRUE
- /// Associative boolean list of chargers that are currently charging
+ /// If the current move is being triggered by us or not
+ var/actively_moving = FALSE
+ /// List of charging mobs
var/list/charging = list()
- /// Associative list of chargers and their hit targets
- var/list/already_hit = list()
- /// Associative direction list of chargers that lets our move signal know how we are supposed to move
- var/list/next_move_allowed = list()
/datum/action/cooldown/mob_cooldown/charge/New(Target, delay, past, distance, speed, damage, destroy)
. = ..()
@@ -53,35 +51,63 @@
var/chargeturf = get_turf(target_atom)
if(!chargeturf)
return
- charger.setDir(get_dir(charger, target_atom))
- var/turf/T = get_ranged_target_turf(chargeturf, charger.dir, past)
- if(!T)
+ var/dir = get_dir(charger, target_atom)
+ var/turf/target = get_ranged_target_turf(chargeturf, dir, past)
+ if(!target)
return
+
+ if(charger in charging)
+ // Stop any existing charging, this'll clean things up properly
+ SSmove_manager.stop_looping(charger)
+
+ charging += charger
SEND_SIGNAL(owner, COMSIG_STARTED_CHARGE)
RegisterSignal(charger, COMSIG_MOVABLE_BUMP, .proc/on_bump)
RegisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE, .proc/on_move)
RegisterSignal(charger, COMSIG_MOVABLE_MOVED, .proc/on_moved)
- charging[charger] = TRUE
- already_hit[charger] = list()
DestroySurroundings(charger)
- charger.setDir(get_dir(charger, target_atom))
- do_charge_indicator(charger, T)
+ charger.setDir(dir)
+ do_charge_indicator(charger, target)
+
SLEEP_CHECK_DEATH(delay, charger)
- var/distance = min(get_dist(charger, T), charge_distance)
- for(var/i in 1 to distance)
- // Prevents movement from the user during the charge
- SLEEP_CHECK_DEATH(charge_speed, charger)
- next_move_allowed[charger] = get_dir(charger, T)
- step_towards(charger, T)
- next_move_allowed.Remove(charger)
- UnregisterSignal(charger, COMSIG_MOVABLE_BUMP)
- UnregisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE)
- UnregisterSignal(charger, COMSIG_MOVABLE_MOVED)
- charging.Remove(charger)
- already_hit.Remove(charger)
- SEND_SIGNAL(owner, COMSIG_FINISHED_CHARGE)
+
+ var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
+
+ var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ if(!new_loop)
+ return
+ RegisterSignal(new_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(new_loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
+ RegisterSignal(new_loop, COMSIG_PARENT_QDELETING, .proc/charge_end)
+ if(ismob(charger))
+ RegisterSignal(charger, COMSIG_MOB_STATCHANGE, .proc/stat_changed)
+
+ // Yes this is disgusting. But we need to queue this stuff, and this code just isn't setup to support that right now. So gotta do it with sleeps
+ sleep(time_to_hit + charge_speed)
+
return TRUE
+/datum/action/cooldown/mob_cooldown/charge/proc/pre_move(datum)
+ SIGNAL_HANDLER
+ // If you sleep in Move() you deserve what's coming to you
+ actively_moving = TRUE
+
+/datum/action/cooldown/mob_cooldown/charge/proc/post_move(datum)
+ SIGNAL_HANDLER
+ actively_moving = FALSE
+
+/datum/action/cooldown/mob_cooldown/charge/proc/charge_end(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/atom/movable/charger = source.moving
+ UnregisterSignal(charger, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_STATCHANGE))
+ SEND_SIGNAL(owner, COMSIG_FINISHED_CHARGE)
+ charging -= charger
+
+/datum/action/cooldown/mob_cooldown/charge/proc/stat_changed(mob/source, new_stat, old_stat)
+ SIGNAL_HANDLER
+ if(new_stat == DEAD)
+ SSmove_manager.stop_looping(source) //This will cause the loop to qdel, triggering an end to our charging
+
/datum/action/cooldown/mob_cooldown/charge/proc/do_charge_indicator(atom/charger, atom/charge_target)
var/turf/target_turf = get_turf(charge_target)
if(!target_turf)
@@ -92,23 +118,15 @@
/datum/action/cooldown/mob_cooldown/charge/proc/on_move(atom/source, atom/new_loc)
SIGNAL_HANDLER
- var/expected_dir = next_move_allowed[source]
- if(!expected_dir)
+ if(!actively_moving)
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
- var/real_dir = get_dir(source, new_loc)
- if(!(expected_dir & real_dir))
- return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
- // Disable the flag for the direction we moved (this is so diagonal movements can be fully completed)
- next_move_allowed[source] = expected_dir & ~real_dir
- if(charging[source])
- new /obj/effect/temp_visual/decoy/fading(source.loc, source)
- INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
+ new /obj/effect/temp_visual/decoy/fading(source.loc, source)
+ INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
/datum/action/cooldown/mob_cooldown/charge/proc/on_moved(atom/source)
SIGNAL_HANDLER
- if(charging[source])
- playsound(source, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
- INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
+ playsound(source, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
+ INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
/datum/action/cooldown/mob_cooldown/charge/proc/DestroySurroundings(atom/movable/charger)
if(!destroy_objects)
@@ -116,46 +134,43 @@
if(!isanimal(charger))
return
for(var/dir in GLOB.cardinals)
- var/turf/T = get_step(charger, dir)
- if(QDELETED(T))
+ var/turf/next_turf = get_step(charger, dir)
+ if(!next_turf)
continue
- if(T.Adjacent(charger))
- if(iswallturf(T) || ismineralturf(T))
- if(!isanimal(charger))
- SSexplosions.medturf += T
- continue
- T.attack_animal(charger)
+ if(next_turf.Adjacent(charger) && (iswallturf(next_turf) || ismineralturf(next_turf)))
+ if(!isanimal(charger))
+ SSexplosions.medturf += next_turf
continue
- for(var/obj/O in T.contents)
- if(!O.Adjacent(charger))
+ next_turf.attack_animal(charger)
+ continue
+ for(var/obj/object in next_turf.contents)
+ if(!object.Adjacent(charger))
continue
- if((ismachinery(O) || isstructure(O)) && O.density && !O.IsObscured())
- if(!isanimal(charger))
- SSexplosions.med_mov_atom += target
- break
- O.attack_animal(charger)
+ if(!ismachinery(object) && !isstructure(object))
+ continue
+ if(!object.density || object.IsObscured())
+ continue
+ if(!isanimal(charger))
+ SSexplosions.med_mov_atom += target
break
+ object.attack_animal(charger)
+ break
/datum/action/cooldown/mob_cooldown/charge/proc/on_bump(atom/movable/source, atom/target)
SIGNAL_HANDLER
- if(SEND_SIGNAL(owner, COMSIG_BUMPED_CHARGE, target) & COMPONENT_OVERRIDE_CHARGE_BUMP)
+ if(owner == target)
return
- if(charging[source])
- if(owner == target)
- return
- if(isturf(target) || isobj(target) && target.density)
- if(isobj(target))
- SSexplosions.med_mov_atom += target
- else
- SSexplosions.medturf += target
- INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
- hit_target(source, target, charge_damage)
+ if(isturf(target))
+ SSexplosions.medturf += target
+ if(isobj(target) && target.density)
+ SSexplosions.med_mov_atom += target
+
+ INVOKE_ASYNC(src, .proc/DestroySurroundings, source)
+ hit_target(source, target, charge_damage)
/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt)
- var/list/hit_things = already_hit[source]
- if(!isliving(target) || hit_things.Find(target))
+ if(!isliving(target))
return
- hit_things.Add(target)
var/mob/living/living_target = target
living_target.visible_message("[source] slams into [living_target]!", "[source] tramples you into the ground!")
source.forceMove(get_turf(living_target))
@@ -173,26 +188,28 @@
charger.Shake(15, 15, 1 SECONDS)
/datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt)
+ var/mob/living/living_source
+ if(isliving(source))
+ living_source = source
+
if(!isliving(target))
- if(target.density && !target.CanPass(source, get_dir(target, source)))
- source.visible_message(span_danger("[source] smashes into [target]!"))
- if(isliving(source))
- var/mob/living/living_source = source
- living_source.Stun(6, ignore_canstun = TRUE)
+ if(!target.density || target.CanPass(source, get_dir(target, source)))
+ return
+ source.visible_message(span_danger("[source] smashes into [target]!"))
+ if(!living_source)
+ return
+ living_source.Stun(6, ignore_canstun = TRUE)
return
+
var/mob/living/living_target = target
- var/blocked = FALSE
if(ishuman(living_target))
var/mob/living/carbon/human/human_target = living_target
- if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK))
- blocked = TRUE
- if(!blocked)
- living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!"))
- living_target.Knockdown(6)
- else
- if(isliving(source))
- var/mob/living/living_source = source
+ if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK) && living_source)
living_source.Stun(6, ignore_canstun = TRUE)
+ return
+
+ living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!"))
+ living_target.Knockdown(6)
/datum/action/cooldown/mob_cooldown/charge/triple_charge
name = "Triple Charge"
@@ -253,7 +270,7 @@
our_clone.alpha = 127.5
our_clone.move_through_mob = owner
our_clone.spawn_blood = spawn_blood
- INVOKE_ASYNC(src, .proc/do_charge, our_clone, target_atom, delay, past)
+ do_charge(our_clone, target_atom, delay, past)
if(use_self)
do_charge(owner, target_atom, delay, past)
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
index 2662a71ff01..f69b7723a90 100644
--- a/code/datums/ai/_ai_controller.dm
+++ b/code/datums/ai/_ai_controller.dm
@@ -44,10 +44,6 @@ multiple modular subtrees with behaviors
var/movement_delay = 0.1 SECONDS
// The variables below are fucking stupid and should be put into the blackboard at some point.
- ///A list for the path we're currently following, if we're using JPS pathing
- var/list/movement_path
- ///Cooldown for JPS movement, how often we're allowed to try making a new path
- COOLDOWN_DECLARE(repath_cooldown)
///AI paused time
var/paused_until = 0
@@ -117,6 +113,8 @@ multiple modular subtrees with behaviors
///Proc for deinitializing the pawn to the old controller
/datum/ai_controller/proc/UnpossessPawn(destroy)
UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
+ if(ai_movement.moving_controllers[src])
+ ai_movement.stop_moving_towards(src)
pawn.ai_controller = null
pawn = null
if(destroy)
@@ -133,7 +131,7 @@ multiple modular subtrees with behaviors
///Runs any actions that are currently running
/datum/ai_controller/process(delta_time)
if(!able_to_run())
- walk(pawn, 0) //stop moving
+ SSmove_manager.stop_looping(pawn) //stop moving
return //this should remove them from processing in the future through event-based stuff.
if(!LAZYLEN(current_behaviors) && idle_behavior)
@@ -261,3 +259,14 @@ multiple modular subtrees with behaviors
/// Use this proc to define how your controller defines what access the pawn has for the sake of pathfinding, likely pointing to whatever ID slot is relevant
/datum/ai_controller/proc/get_access()
return
+
+///Returns the minimum required distance to preform one of our current behaviors. Honestly this should just be cached or something but fuck you
+/datum/ai_controller/proc/get_minimum_distance()
+ var/minimum_distance = max_target_distance
+ // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future
+ // we should let whatever sets the current_movement_target also set the min distance and max path length
+ // (or at least cache it on the controller)
+ for(var/datum/ai_behavior/iter_behavior as anything in current_behaviors)
+ if(iter_behavior.required_distance < minimum_distance)
+ minimum_distance = iter_behavior.required_distance
+ return minimum_distance
diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm
index 00b7a52a93e..be386a3b692 100644
--- a/code/datums/ai/monkey/monkey_behaviors.dm
+++ b/code/datums/ai/monkey/monkey_behaviors.dm
@@ -132,7 +132,7 @@
break
if(target)
- walk_away(living_pawn, target, MONKEY_ENEMY_VISION, 5)
+ SSmove_manager.move_away(living_pawn, target, max_dist=MONKEY_ENEMY_VISION, delay=5)
else
finish_action(controller, TRUE)
@@ -172,7 +172,7 @@
/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
var/mob/living/living_pawn = controller.pawn
- walk(living_pawn, 0)
+ SSmove_manager.stop_looping(living_pawn)
controller.blackboard[target_key] = null
/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm
index 540dccae849..aacbb7f39a2 100644
--- a/code/datums/ai/movement/_ai_movement.dm
+++ b/code/datums/ai/movement/_ai_movement.dm
@@ -2,22 +2,22 @@
/datum/ai_movement
///Assoc list ist of controllers that are currently moving as key, and what they are moving to as value
var/list/moving_controllers = list()
- ///Does this type require processing?
- var/requires_processing = TRUE
///How many times a given controller can fail on their route before they just give up
var/max_pathing_attempts
+//Override this to setup the moveloop you want to use
/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ SHOULD_CALL_PARENT(TRUE)
controller.pathing_attempts = 0
controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE] = min_distance
- if(!moving_controllers.len && requires_processing)
- START_PROCESSING(SSai_movement, src)
moving_controllers[controller] = current_movement_target
/datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller)
controller.pathing_attempts = 0
moving_controllers -= controller
+ SSmove_manager.stop_looping(controller.pawn, SSai_movement)
- if(!moving_controllers.len && requires_processing)
- STOP_PROCESSING(SSai_movement, src)
-
+/datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller)
+ controller.pathing_attempts++
+ if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.CancelActions()
diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm
index 12845b167ac..78dae03b270 100644
--- a/code/datums/ai/movement/ai_movement_basic_avoidance.dm
+++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm
@@ -1,39 +1,50 @@
///Uses Byond's basic obstacle avoidance mvovement
/datum/ai_movement/basic_avoidance
- requires_processing = TRUE
max_pathing_attempts = 10
-///Put your movement behavior in here!
-/datum/ai_movement/basic_avoidance/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/min_dist = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE]
+ var/delay = controller.movement_delay
+ var/datum/move_loop/loop = SSmove_manager.move_to(moving, current_movement_target, min_dist, delay, subsystem = SSai_movement, extra_info = controller)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
- var/atom/movable/movable_pawn = controller.pawn
+/datum/ai_movement/basic_avoidance/proc/pre_move(datum/move_loop/has_target/dist_bound/source)
+ SIGNAL_HANDLER
+ var/atom/movable/pawn = source.moving
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
+ source.distance = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE]
- // Check if this controller can actually run, so we don't chase people with corpses
- if(!controller.able_to_run())
- walk(controller.pawn, 0) //stop moving
- controller.CancelActions()
- continue
+ var/can_move = TRUE
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && pawn.pulledby)
+ can_move = FALSE
- var/can_move = TRUE
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run())
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
- if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
- can_move = FALSE
+ if(!isturf(pawn.loc)) //No moving if not on a turf
+ can_move = FALSE
- if(!isturf(movable_pawn.loc)) //No moving if not on a turf
- can_move = FALSE
+ var/turf/target_turf = get_step_to(pawn, source.target)
- var/current_loc = get_turf(movable_pawn)
+ if(is_type_in_typecache(target_turf, GLOB.dangerous_turfs))
+ can_move = FALSE
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+ if(can_move)
+ return
+ increment_pathing_failures(controller)
+ return MOVELOOP_SKIP_STEP
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- step_to(movable_pawn, controller.current_movement_target, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+/datum/ai_movement/basic_avoidance/proc/post_move(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+ if(succeeded)
+ return
+ var/datum/ai_controller/controller = source.extra_info
+ increment_pathing_failures(controller)
- if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm
index 56f9e63bfa1..53cbf9dc456 100644
--- a/code/datums/ai/movement/ai_movement_dumb.dm
+++ b/code/datums/ai/movement/ai_movement_dumb.dm
@@ -3,36 +3,46 @@
max_pathing_attempts = 16
///Put your movement behavior in here!
-/datum/ai_movement/dumb/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/delay = controller.movement_delay
+ var/datum/move_loop/loop = SSmove_manager.move_towards_legacy(moving, current_movement_target, delay, subsystem = SSai_movement, extra_info = controller)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
- var/atom/movable/movable_pawn = controller.pawn
+/datum/ai_movement/dumb/proc/pre_move(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+ var/atom/movable/pawn = source.moving
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
- // Check if this controller can actually run, so we don't chase people with corpses
- if(!controller.able_to_run())
- walk(controller.pawn, 0) //stop moving
- controller.CancelActions()
- continue
+ var/can_move = TRUE
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && pawn.pulledby) //Need to store more state. Annoying.
+ can_move = FALSE
- var/can_move = TRUE
+ if(!isturf(pawn.loc)) //No moving if not on a turf
+ can_move = FALSE
- if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
- can_move = FALSE
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run())
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
- if(!isturf(movable_pawn.loc)) //No moving if not on a turf
- can_move = FALSE
+ var/turf/target_turf = get_step_towards(pawn, source.target)
- var/current_loc = get_turf(movable_pawn)
+ if(is_type_in_typecache(target_turf, GLOB.dangerous_turfs))
+ can_move = FALSE
- var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+ if(can_move)
+ return
+ increment_pathing_failures(controller)
+ return MOVELOOP_SKIP_STEP
- if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
- movable_pawn.Move(target_turf, get_dir(current_loc, target_turf))
-
- if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
+/datum/ai_movement/dumb/proc/post_move(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+ if(succeeded)
+ return
+ var/datum/ai_controller/controller = source.extra_info
+ increment_pathing_failures(controller)
diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm
index 23b8fccb291..e0a1402f98b 100644
--- a/code/datums/ai/movement/ai_movement_jps.dm
+++ b/code/datums/ai/movement/ai_movement_jps.dm
@@ -4,68 +4,59 @@
/datum/ai_movement/jps
max_pathing_attempts = 4
-///Put your movement behavior in here!
-/datum/ai_movement/jps/process(delta_time)
- for(var/datum/ai_controller/controller as anything in moving_controllers)
- if(!COOLDOWN_FINISHED(controller, movement_cooldown))
- continue
- COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ . = ..()
+ var/atom/movable/moving = controller.pawn
+ var/delay = controller.movement_delay
- var/atom/movable/movable_pawn = controller.pawn
+ var/datum/move_loop/loop = SSmove_manager.jps_move(moving,
+ current_movement_target,
+ delay,
+ repath_delay = 2 SECONDS,
+ max_path_length = AI_MAX_PATH_LENGTH,
+ minimum_distance = controller.get_minimum_distance(),
+ id = controller.get_access(),
+ subsystem = SSai_movement,
+ extra_info = controller)
- // Check if this controller can actually run, so we don't chase people with corpses
- if(!controller.able_to_run())
- walk(controller.pawn, 0) //stop moving
- controller.CancelActions()
- continue
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_JPS_REPATH, .proc/repath_incoming)
- if(!isturf(movable_pawn.loc)) //No moving if not on a turf
- continue
+/datum/ai_movement/jps/proc/pre_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/atom/movable/pawn = source.moving
+ var/datum/ai_controller/controller = source.extra_info
+ source.delay = controller.movement_delay
- if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
- continue
+ var/can_move = TRUE
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && pawn.pulledby) //Need to store more state. Annoying.
+ can_move = FALSE
- var/minimum_distance = controller.max_target_distance
- // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future
- // we should let whatever sets the current_movement_target also set the min distance and max path length
- // (or at least cache it on the controller)
- if(LAZYLEN(controller.current_behaviors))
- for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
- if(iter_behavior.required_distance < minimum_distance)
- minimum_distance = iter_behavior.required_distance
+ if(!isturf(pawn.loc)) //No moving if not on a turf
+ can_move = FALSE
- if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
- continue
+ // Check if this controller can actually run, so we don't chase people with corpses
+ if(!controller.able_to_run())
+ controller.CancelActions()
+ qdel(source) //stop moving
+ return MOVELOOP_SKIP_STEP
- var/generate_path = FALSE // set to TRUE when we either have no path, or we failed a step
- if(length(controller.movement_path))
- var/turf/next_step = controller.movement_path[1]
- movable_pawn.Move(next_step)
+ if(can_move)
+ return
+ increment_pathing_failures(controller)
+ return MOVELOOP_SKIP_STEP
- // this check if we're on exactly the next tile may be overly brittle for dense pawns who may get bumped slightly
- // to the side while moving but could maybe still follow their path without needing a whole new path
- if(get_turf(movable_pawn) == next_step)
- controller.movement_path.Cut(1,2)
- else
- generate_path = TRUE
- else
- generate_path = TRUE
+/datum/ai_movement/jps/proc/post_move(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+ if(succeeded)
+ return
+ var/datum/ai_controller/controller = source.extra_info
+ increment_pathing_failures(controller)
- if(generate_path)
- if(!COOLDOWN_FINISHED(controller, repath_cooldown))
- continue
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
- controller.CancelActions()
- continue
+/datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source)
+ SIGNAL_HANDLER
+ var/datum/ai_controller/controller = source.extra_info
- COOLDOWN_START(controller, repath_cooldown, 2 SECONDS)
- controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, AI_MAX_PATH_LENGTH, minimum_distance, id=controller.get_access())
-
-/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target)
- controller.movement_path = null
- return ..()
-
-/datum/ai_movement/jps/stop_moving_towards(datum/ai_controller/controller)
- controller.movement_path = null
- return ..()
+ source.id = controller.get_access()
+ source.minimum_distance = controller.get_minimum_distance()
diff --git a/code/datums/components/conveyor_movement.dm b/code/datums/components/conveyor_movement.dm
new file mode 100644
index 00000000000..689863c537e
--- /dev/null
+++ b/code/datums/components/conveyor_movement.dm
@@ -0,0 +1,37 @@
+//Make a component to do things like gravity/flying checks
+///Manages the loop caused by being on a conveyor belt
+///Prevents movement while you're floating, etc
+///Takes the direction to move, delay between steps, and time before starting to move as arguments
+/datum/component/convey
+ var/living_parent = FALSE
+ var/speed
+
+/datum/component/convey/Initialize(direction, speed, start_delay)
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ living_parent = isliving(parent)
+ src.speed = speed
+ if(!start_delay)
+ start_delay = speed
+ var/atom/movable/moving_parent = parent
+ var/datum/move_loop/loop = SSmove_manager.move(moving_parent, direction, delay = start_delay, subsystem = SSconveyors, flags=MOVEMENT_LOOP_IGNORE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/should_move)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/loop_ended)
+
+/datum/component/convey/proc/should_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ source.delay = speed //We use the default delay
+ if(living_parent)
+ var/mob/living/moving_mob = parent
+ if((moving_mob.movement_type & FLYING) && !moving_mob.stat)
+ return MOVELOOP_SKIP_STEP
+ var/atom/movable/moving_parent = parent
+ if(moving_parent.anchored || !moving_parent.has_gravity())
+ return MOVELOOP_SKIP_STEP
+
+/datum/component/convey/proc/loop_ended(datum/source)
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
+ qdel(src)
diff --git a/code/datums/components/drift.dm b/code/datums/components/drift.dm
new file mode 100644
index 00000000000..5037444e669
--- /dev/null
+++ b/code/datums/components/drift.dm
@@ -0,0 +1,115 @@
+///Component that handles drifting
+///Manages a movement loop that actually does the legwork of moving someone
+///Alongside dealing with the post movement input blocking required to make things look nice
+/datum/component/drift
+ var/atom/inertia_last_loc
+ var/old_dir
+ var/datum/move_loop/move/drifting_loop
+ var/block_inputs_until
+
+/datum/component/drift/Initialize(direction)
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+ . = ..()
+
+ var/atom/movable/movable_parent = parent
+ drifting_loop = SSmove_manager.move(moving = parent, direction = direction, delay = movable_parent.inertia_move_delay, subsystem = SSspacedrift, priority = MOVEMENT_SPACE_PRIORITY)
+
+ if(!drifting_loop) //Really want to qdel here but can't
+ return COMPONENT_INCOMPATIBLE
+
+ RegisterSignal(movable_parent, COMSIG_MOVABLE_NEWTONIAN_MOVE, .proc/newtonian_impulse)
+
+ RegisterSignal(drifting_loop, COMSIG_MOVELOOP_START, .proc/drifting_start)
+ RegisterSignal(drifting_loop, COMSIG_MOVELOOP_STOP, .proc/drifting_stop)
+ RegisterSignal(drifting_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/before_move)
+ RegisterSignal(drifting_loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/after_move)
+ RegisterSignal(drifting_loop, COMSIG_PARENT_QDELETING, .proc/loop_death)
+
+/datum/component/drift/Destroy()
+ inertia_last_loc = null
+ if(!QDELETED(drifting_loop))
+ qdel(drifting_loop)
+ drifting_loop = null
+ var/atom/movable/movable_parent = parent
+ movable_parent.inertia_moving = FALSE
+ return ..()
+
+/datum/component/drift/proc/newtonian_impulse(datum/source, inertia_direction)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ inertia_last_loc = movable_parent.loc
+ drifting_loop.direction = inertia_direction
+ if(!inertia_direction)
+ qdel(src)
+ return COMPONENT_MOVABLE_NEWTONIAN_BLOCK
+
+/datum/component/drift/proc/drifting_start()
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ inertia_last_loc = movable_parent.loc
+ RegisterSignal(movable_parent, COMSIG_MOVABLE_MOVED, .proc/handle_move)
+
+/datum/component/drift/proc/drifting_stop()
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ movable_parent.inertia_moving = FALSE
+ UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_NEWTONIAN_MOVE))
+
+/datum/component/drift/proc/before_move(datum/source)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ movable_parent.inertia_moving = TRUE
+ old_dir = movable_parent.dir
+
+/datum/component/drift/proc/after_move(datum/source, succeeded, visual_delay)
+ SIGNAL_HANDLER
+ if(!succeeded)
+ qdel(src)
+ return
+
+ var/atom/movable/movable_parent = parent
+ movable_parent.inertia_moving = FALSE
+ movable_parent.setDir(old_dir)
+ if(movable_parent.Process_Spacemove(0))
+ glide_to_halt(visual_delay)
+ return
+
+ inertia_last_loc = movable_parent.loc
+
+/datum/component/drift/proc/loop_death(datum/source)
+ SIGNAL_HANDLER
+ drifting_loop = null
+ UnregisterSignal(parent, COMSIG_MOVABLE_NEWTONIAN_MOVE)
+
+/datum/component/drift/proc/handle_move(datum/source, old_loc)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ if(!isturf(movable_parent.loc))
+ qdel(src)
+ return
+ if(movable_parent.inertia_moving) //This'll be handled elsewhere
+ return
+ if(!movable_parent.Process_Spacemove(0))
+ return
+ qdel(src)
+
+/datum/component/drift/proc/glide_to_halt(glide_for)
+ if(!ismob(parent))
+ qdel(src)
+ return
+
+ var/mob/mob_parent = parent
+ var/client/our_client = mob_parent.client
+ if(!our_client)
+ qdel(src)
+ return
+
+ block_inputs_until = world.time + glide_for
+ QDEL_IN(src, glide_for + 1)
+ qdel(drifting_loop)
+ RegisterSignal(parent, COMSIG_MOB_CLIENT_PRE_MOVE, .proc/allow_final_movement)
+
+/datum/component/drift/proc/allow_final_movement(datum/source)
+ if(world.time < block_inputs_until)
+ return COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE
diff --git a/code/datums/components/force_move.dm b/code/datums/components/force_move.dm
new file mode 100644
index 00000000000..23e4926f101
--- /dev/null
+++ b/code/datums/components/force_move.dm
@@ -0,0 +1,38 @@
+///Forced directional movement, but with a twist
+///Let's block pressure and client movements while doing it so we can't be interrupted
+///Supports spinning on each move, for lube related reasons
+/datum/component/force_move
+
+/datum/component/force_move/Initialize(atom/target, spin)
+ if(!target || !ismob(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ var/mob/mob_parent = parent
+ var/dist = get_dist(mob_parent, target)
+ var/datum/move_loop/loop = SSmove_manager.move_towards(mob_parent, target, delay = 1, timeout = dist)
+ RegisterSignal(mob_parent, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, .proc/stop_move)
+ RegisterSignal(mob_parent, COMSIG_ATOM_PRE_PRESSURE_PUSH, .proc/stop_pressure)
+ if(spin)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/slip_spin)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/loop_ended)
+
+/datum/component/force_move/proc/stop_move(datum/source)
+ SIGNAL_HANDLER
+ return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE
+
+/datum/component/force_move/proc/stop_pressure(datum/source)
+ SIGNAL_HANDLER
+ return COMSIG_ATOM_BLOCKS_PRESSURE
+
+/datum/component/force_move/proc/slip_spin(datum/source)
+ SIGNAL_HANDLER
+ var/mob/mob_parent = parent
+ mob_parent.spin(1, 1)
+
+/datum/component/force_move/proc/loop_ended(datum/source)
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
+ qdel(src)
+
+
diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm
index 0ad859e953f..93371f0430b 100644
--- a/code/datums/forced_movement.dm
+++ b/code/datums/forced_movement.dm
@@ -1,4 +1,5 @@
//Just new and forget
+//Depricated, use movement loops instead. Exists to support things that want to move more then 10 times a second
/datum/forced_movement
var/atom/movable/victim
var/atom/target
@@ -34,6 +35,7 @@
target = null
return ..()
+//Todo: convert
/datum/forced_movement/process()
if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc)
qdel(src)
@@ -61,15 +63,15 @@
. = step_towards(vic, tar)
//shit way for getting around corners
- if(!.)
- if(tar.x > vic.x)
+ if(!.) //If stepping towards the target failed
+ if(tar.x > vic.x) //If we're going x, step x
if(step(vic, EAST))
. = TRUE
else if(tar.x < vic.x)
if(step(vic, WEST))
. = TRUE
- if(!.)
+ if(!.) //If the x step failed, go y
if(tar.y > vic.y)
if(step(vic, NORTH))
. = TRUE
@@ -77,7 +79,7 @@
if(step(vic, SOUTH))
. = TRUE
- if(!.)
+ if(!.) //If both failed, try again for some reason
if(recursive)
return FALSE
else
diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm
index 3770191cc0f..70ea170ab38 100644
--- a/code/datums/proximity_monitor/fields/timestop.dm
+++ b/code/datums/proximity_monitor/fields/timestop.dm
@@ -205,7 +205,7 @@
frozen_mobs += L
L.Stun(20, ignore_canstun = TRUE)
ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT)
- walk(L, 0) //stops them mid pathing even if they're stunimmune
+ SSmove_manager.stop_looping(src) //stops them mid pathing even if they're stunimmune //This is really dumb
if(isanimal(L))
var/mob/living/simple_animal/S = L
S.toggle_ai(AI_OFF)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 29a8e52cfe0..821ed7be250 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -25,10 +25,9 @@
var/verb_sing = "sings"
var/verb_yell = "yells"
var/speech_span
- var/inertia_dir = 0
- var/atom/inertia_last_loc
- var/inertia_moving = 0
- var/inertia_next_move = 0
+ ///Are we moving with inertia? Mostly used as an optimization
+ var/inertia_moving = FALSE
+ ///Delay in deciseconds between inertia based movement
var/inertia_move_delay = 5
/// Things we can pass through while moving. If any of this matches the thing we're trying to pass's [pass_flags_self], then we can pass through.
var/pass_flags = NONE
@@ -36,6 +35,8 @@
var/generic_canpass = TRUE
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
+ ///Holds information about any movement loops currently running/waiting to run on the movable. Lazy, will be null if nothing's going on
+ var/datum/movement_packet/move_packet
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
/**
* an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
@@ -136,6 +137,11 @@
orbiting.end_orbit(src)
orbiting = null
+ if(move_packet)
+ if(!QDELETED(move_packet))
+ qdel(move_packet)
+ move_packet = null
+
if(important_recursive_contents && (important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] || important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
SSspatial_grid.force_remove_from_cell(src)
@@ -585,7 +591,6 @@
if(!. && set_dir_on_move && !face_mouse)
setDir(first_step_dir)
else if (!inertia_moving)
- inertia_next_move = world.time + inertia_move_delay
newtonian_move(direct)
moving_diagonally = 0
return
@@ -652,7 +657,6 @@
SHOULD_CALL_PARENT(TRUE)
if (!inertia_moving)
- inertia_next_move = world.time + inertia_move_delay
newtonian_move(movement_dir)
if (client_mobs_in_contents)
update_parallax_contents()
@@ -970,14 +974,14 @@
/// Only moves the object if it's under no gravity
/atom/movable/proc/newtonian_move(direction)
if(!isturf(loc) || Process_Spacemove(0))
- inertia_dir = 0
return FALSE
- inertia_dir = direction
- if(!direction)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_NEWTONIAN_MOVE, direction) & COMPONENT_MOVABLE_NEWTONIAN_BLOCK)
return TRUE
- inertia_last_loc = loc
- SSspacedrift.processing[src] = src
+
+ set_glide_size(MOVEMENT_ADJUSTED_GLIDE_SIZE(inertia_move_delay, SSspacedrift.visual_delay))
+ AddComponent(/datum/component/drift, direction)
+
return TRUE
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
@@ -1089,11 +1093,9 @@
/atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override)
for(var/mob/living/buckled_mob as anything in buckled_mobs)
- if(buckled_mob.loc != newloc && !buckled_mob.Move(newloc, direct, glide_size_override))
- Move(buckled_mob.loc, direct)
+ if(!buckled_mob.Move(newloc, direct, glide_size_override)) //If a mob buckled to us can't make the same move as us
+ Move(buckled_mob.loc, direct) //Move back to its location
last_move = buckled_mob.last_move
- inertia_dir = last_move
- buckled_mob.inertia_dir = last_move
return FALSE
return TRUE
diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm
index bb7d9a7d1a4..aaf7b382e3d 100644
--- a/code/game/objects/effects/decals/cleanable/aliens.dm
+++ b/code/game/objects/effects/decals/cleanable/aliens.dm
@@ -32,15 +32,25 @@
RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, .proc/on_pipe_eject)
/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions, mapload=FALSE)
- set waitfor = FALSE
+ SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions)
var/direction = pick(directions)
- for(var/i in 1 to pick(1, 200; 2, 150; 3, 50; 4, 17; 50)) //the 3% chance of 50 steps is intentional and played for laughs.
- if (!mapload)
- sleep(2)
- if(i > 0)
+ var/delay = 2
+ var/range = pick(0, 200; 1, 150; 2, 50; 3, 17; 50) //the 3% chance of 50 steps is intentional and played for laughs.
+ if(!step_to(src, get_step(src, direction), 0))
+ return
+ if(mapload)
+ for (var/i in 1 to range)
new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc)
- if(!step_to(src, get_step(src, direction), 0))
- break
+ if (!step_to(src, get_step(src, direction), 0))
+ break
+ return
+
+ var/datum/move_loop/loop = SSmove_manager.move_to_dir(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/spread_movement_effects)
+
+/obj/effect/decal/cleanable/xenoblood/xgibs/proc/spread_movement_effects(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+ new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc)
/obj/effect/decal/cleanable/xenoblood/xgibs/proc/on_pipe_eject(atom/source, direction)
SIGNAL_HANDLER
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 5f9bd002f96..df898b0bb27 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -106,6 +106,8 @@
dryname = "rotting gibs"
drydesc = "They look bloody and gruesome while some terrible smell fills the air."
+ ///Information about the diseases our streaking spawns
+ var/list/streak_diseases
/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases)
. = ..()
@@ -141,17 +143,26 @@
streak(dirs)
/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions, mapload=FALSE)
- set waitfor = FALSE
- var/list/diseases = list()
- SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases)
+ SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, streak_diseases)
var/direction = pick(directions)
- for(var/i in 0 to pick(0, 200; 1, 150; 2, 50; 3, 17; 50)) //the 3% chance of 50 steps is intentional and played for laughs.
- if (!mapload)
- sleep(2)
- if(i > 0)
- new /obj/effect/decal/cleanable/blood/splatter(loc, diseases)
- if(!step_to(src, get_step(src, direction), 0))
- break
+ streak_diseases = list()
+ var/delay = 2
+ var/range = pick(0, 200; 1, 150; 2, 50; 3, 17; 50) //the 3% chance of 50 steps is intentional and played for laughs.
+ if(!step_to(src, get_step(src, direction), 0))
+ return
+ if(mapload)
+ for (var/i = 1, i < range, i++)
+ new /obj/effect/decal/cleanable/blood/splatter(loc, streak_diseases)
+ if (!step_to(src, get_step(src, direction), 0))
+ break
+ return
+
+ var/datum/move_loop/loop = SSmove_manager.move_to(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/spread_movement_effects)
+
+/obj/effect/decal/cleanable/blood/gibs/proc/spread_movement_effects(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+ new /obj/effect/decal/cleanable/blood/splatter(loc, streak_diseases)
/obj/effect/decal/cleanable/blood/gibs/up
icon_state = "gibup1"
@@ -332,34 +343,45 @@
loc.add_blood_DNA(blood_dna_info)
return ..()
-/// Set the splatter up to fly through the air until it rounds out of steam or hits something. Contains sleep() pending imminent moveloop rework, don't call without async'ing it
+/// Set the splatter up to fly through the air until it rounds out of steam or hits something
/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range)
- for(var/i in 1 to range)
- step_towards(src,target_turf)
- sleep(2) // Will be resolved pending Potato's moveloop rework
- prev_loc = loc
- for(var/atom/iter_atom in get_turf(src))
- if(hit_endpoint)
- return
- if(splatter_strength <= 0)
- break
+ var/delay = 2
+ var/datum/move_loop/loop = SSmove_manager.move_towards(src, target_turf, delay, timeout = delay * range, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/loop_done)
- if(isitem(iter_atom))
- iter_atom.add_blood_DNA(blood_dna_info)
- splatter_strength--
- else if(ishuman(iter_atom))
- var/mob/living/carbon/human/splashed_human = iter_atom
- if(splashed_human.wear_suit)
- splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
- splashed_human.update_inv_wear_suit() //updates mob overlays to show the new blood (no refresh)
- if(splashed_human.w_uniform)
- splashed_human.w_uniform.add_blood_DNA(blood_dna_info)
- splashed_human.update_inv_w_uniform() //updates mob overlays to show the new blood (no refresh)
- splatter_strength--
- if(splatter_strength <= 0) // we used all the puff so we delete it.
- qdel(src)
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/pre_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ prev_loc = loc
+
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/post_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ for(var/atom/iter_atom in get_turf(src))
+ if(hit_endpoint)
return
- qdel(src)
+ if(splatter_strength <= 0)
+ break
+
+ if(isitem(iter_atom))
+ iter_atom.add_blood_DNA(blood_dna_info)
+ splatter_strength--
+ else if(ishuman(iter_atom))
+ var/mob/living/carbon/human/splashed_human = iter_atom
+ if(splashed_human.wear_suit)
+ splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
+ splashed_human.update_inv_wear_suit() //updates mob overlays to show the new blood (no refresh)
+ if(splashed_human.w_uniform)
+ splashed_human.w_uniform.add_blood_DNA(blood_dna_info)
+ splashed_human.update_inv_w_uniform() //updates mob overlays to show the new blood (no refresh)
+ splatter_strength--
+ if(splatter_strength <= 0) // we used all the puff so we delete it.
+ qdel(src)
+
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/loop_done(datum/source)
+ SIGNAL_HANDLER
+ if(!QDELETED(src))
+ qdel(src)
/obj/effect/decal/cleanable/blood/hitsplatter/Bump(atom/bumped_atom)
if(!iswallturf(bumped_atom) && !istype(bumped_atom, /obj/structure/window))
diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm
index 70e8012093b..42d39ded0b9 100644
--- a/code/game/objects/effects/decals/cleanable/robots.dm
+++ b/code/game/objects/effects/decals/cleanable/robots.dm
@@ -18,20 +18,30 @@
RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, .proc/on_pipe_eject)
/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions, mapload=FALSE)
- set waitfor = FALSE
var/direction = pick(directions)
- for (var/i in 1 to pick(1, 200; 2, 150; 3, 50; 4, 17; 50)) //the 3% chance of 50 steps is intentional and played for laughs.
- if (!mapload)
- sleep(2)
- if (i > 0)
+ var/delay = 2
+ var/range = pick(1, 200; 2, 150; 3, 50; 4, 17; 50) //the 3% chance of 50 steps is intentional and played for laughs.
+ if(!step_to(src, get_step(src, direction), 0))
+ return
+ if(mapload)
+ for (var/i in 1 to range)
if (prob(40))
new /obj/effect/decal/cleanable/oil/streak(src.loc)
- else if (prob(10) && !mapload)
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(3, 1, src)
- s.start()
- if (!step_to(src, get_step(src, direction), 0))
- break
+ if (!step_to(src, get_step(src, direction), 0))
+ break
+ return
+
+ var/datum/move_loop/loop = SSmove_manager.move_to_dir(src, get_step(src, direction), delay = delay, timeout = range * delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/spread_movement_effects)
+
+/obj/effect/decal/cleanable/robot_debris/proc/spread_movement_effects(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+ if (prob(40))
+ new /obj/effect/decal/cleanable/oil/streak(src.loc)
+ else if (prob(10))
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(3, 1, src)
+ s.start()
/obj/effect/decal/cleanable/robot_debris/proc/on_pipe_eject(atom/source, direction)
SIGNAL_HANDLER
diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm
index 2867ab05b2c..8db5f7cc6c5 100644
--- a/code/game/objects/effects/decals/misc.dm
+++ b/code/game/objects/effects/decals/misc.dm
@@ -19,10 +19,80 @@
icon = 'icons/obj/chempuff.dmi'
pass_flags = PASSTABLE | PASSGRILLE
layer = FLY_LAYER
+ ///The mob who sourced this puff, if one exists
+ var/mob/user
+ ///The sprayer who fired this puff
+ var/obj/item/reagent_containers/spray/sprayer
+ ///How many interactions we have left before we disappear early
+ var/lifetime = INFINITY
+ ///Are we a part of a stream?
+ var/stream
+
+/obj/effect/decal/chempuff/Destroy(force)
+ user = null
+ sprayer = null
+ return ..()
/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B)
return
+/obj/effect/decal/chempuff/proc/loop_ended(datum/source)
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
+ qdel(src)
+
+/obj/effect/decal/chempuff/proc/check_move(datum/move_loop/source, succeeded)
+ if(QDELETED(src)) //Reasons PLEASE WORK I SWEAR TO GOD
+ return
+ if(!succeeded) //If we hit something
+ qdel(src)
+ return
+
+ var/puff_reagents_string = reagents.log_list()
+ var/travelled_max_distance = (source.lifetime - source.delay <= 0)
+ var/turf/our_turf = get_turf(src)
+
+ for(var/atom/movable/turf_atom in our_turf)
+ if(turf_atom == src || turf_atom.invisibility) //we ignore the puff itself and stuff below the floor
+ continue
+
+ if(lifetime < 0)
+ break
+
+ if(!stream)
+ reagents.expose(turf_atom, VAPOR)
+ log_combat(user, turf_atom, "sprayed", sprayer, addition="which had [puff_reagents_string]")
+ if(ismob(turf_atom))
+ lifetime -= 1
+ continue
+
+ if(isliving(turf_atom))
+ var/mob/living/turf_mob = turf_atom
+
+ if(!turf_mob.can_inject())
+ continue
+ if(turf_mob.body_position != STANDING_UP && !travelled_max_distance)
+ continue
+
+ reagents.expose(turf_mob, VAPOR)
+ log_combat(user, turf_mob, "sprayed", sprayer, addition="which had [puff_reagents_string]")
+ lifetime -= 1
+
+ else if(travelled_max_distance)
+ reagents.expose(turf_atom, VAPOR)
+ log_combat(user, turf_atom, "sprayed", sprayer, addition="which had [puff_reagents_string]")
+ lifetime -= 1
+
+ if(lifetime >= 0 && (!stream || travelled_max_distance))
+ reagents.expose(our_turf, VAPOR)
+ log_combat(user, our_turf, "sprayed", sprayer, addition="which had [puff_reagents_string]")
+ lifetime -= 1
+
+ // Did we use up all the puff early?
+ if(lifetime < 0)
+ qdel(src)
+
/obj/effect/decal/fakelattice
name = "lattice"
desc = "A lightweight support lattice."
diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm
index 3cc738e2295..550e241a296 100644
--- a/code/game/objects/effects/effect_system/effect_system.dm
+++ b/code/game/objects/effects/effect_system/effect_system.dm
@@ -25,7 +25,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
/datum/effect_system
var/number = 3
- var/cardinals = FALSE
+ var/cardinals_only = FALSE
var/turf/location
var/atom/holder
var/effect_type
@@ -37,15 +37,10 @@ would spawn and follow the beaker, even if it is carried or thrown.
location = null
return ..()
-/datum/effect_system/proc/set_up(n = 3, c = FALSE, loca)
- if(n > 10)
- n = 10
- number = n
- cardinals = c
- if(isturf(loca))
- location = loca
- else
- location = get_turf(loca)
+/datum/effect_system/proc/set_up(number = 3, cardinals_only = FALSE, location)
+ src.number = min(number, 10)
+ src.cardinals_only = cardinals_only
+ src.location = get_turf(location)
/datum/effect_system/proc/attach(atom/atom)
holder = atom
@@ -56,26 +51,27 @@ would spawn and follow the beaker, even if it is carried or thrown.
for(var/i in 1 to number)
if(total_effects > 20)
return
- INVOKE_ASYNC(src, .proc/generate_effect)
+ generate_effect()
/datum/effect_system/proc/generate_effect()
if(holder)
location = get_turf(holder)
- var/obj/effect/E = new effect_type(location)
+ var/obj/effect/effect = new effect_type(location)
total_effects++
var/direction
- if(cardinals)
+ if(cardinals_only)
direction = pick(GLOB.cardinals)
else
direction = pick(GLOB.alldirs)
- var/steps_amt = pick(1,2,3)
- for(var/j in 1 to steps_amt)
- sleep(5)
- step(E,direction)
- if(!QDELETED(src))
- addtimer(CALLBACK(src, .proc/decrement_total_effect), 20)
+ var/step_amt = pick(1,2,3)
+ var/step_delay = 5
-/datum/effect_system/proc/decrement_total_effect()
+ var/datum/move_loop/loop = SSmove_manager.move(effect, direction, step_delay, timeout = step_delay * step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/decrement_total_effect)
+
+/datum/effect_system/proc/decrement_total_effect(datum/source)
+ SIGNAL_HANDLER
total_effects--
- if(autocleanup && total_effects <= 0)
- qdel(src)
+ if(!autocleanup || total_effects > 0)
+ return
+ QDEL_IN(src, 2 SECONDS)
diff --git a/code/game/objects/effects/effect_system/effects_explosion.dm b/code/game/objects/effects/effect_system/effects_explosion.dm
index c76ea8bdc99..09184e6ef8b 100644
--- a/code/game/objects/effects/effect_system/effects_explosion.dm
+++ b/code/game/objects/effects/effect_system/effects_explosion.dm
@@ -9,11 +9,15 @@
return INITIALIZE_HINT_LATELOAD
/obj/effect/particle_effect/expl_particles/LateInitialize()
- var/direct = pick(GLOB.alldirs)
- var/steps_amt = pick(25;1,50;2,100;3,200;4)
- for(var/j in 1 to steps_amt)
- step(src, direct)
- sleep(1)
+ var/step_amt = pick(25;1,50;2,100;3,200;4)
+
+ var/datum/move_loop/loop = SSmove_manager.move(src, pick(GLOB.alldirs), 1, timeout = step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/end_particle)
+
+/obj/effect/particle_effect/expl_particles/proc/end_particle(datum/source)
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
qdel(src)
/datum/effect_system/expl_particles
@@ -40,11 +44,8 @@
/datum/effect_system/explosion
-/datum/effect_system/explosion/set_up(loca)
- if(isturf(loca))
- location = loca
- else
- location = get_turf(loca)
+/datum/effect_system/explosion/set_up(location)
+ src.location = get_turf(location)
/datum/effect_system/explosion/start()
new/obj/effect/explosion( location )
@@ -58,6 +59,7 @@
var/datum/effect_system/smoke_spread/S = new
S.set_up(2, location)
S.start()
+
/datum/effect_system/explosion/smoke/start()
..()
addtimer(CALLBACK(src, .proc/create_smoke), 5)
diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm
index 1d4d481e28d..a852a8bb113 100644
--- a/code/game/objects/effects/effect_system/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/effects_foam.dm
@@ -175,6 +175,7 @@
/obj/effect/particle_effect/foam/proc/spread_foam()
var/turf/t_loc = get_turf(src)
+ //This should just be atmos adjacent turfs, come on guys
for(var/turf/T in t_loc.reachableAdjacentTurfs())
var/obj/effect/particle_effect/foam/foundfoam = locate() in T //Don't spread foam where there's already foam!
if(foundfoam)
diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm
index b64537d8b0a..413edd9ec17 100644
--- a/code/game/objects/effects/effect_system/effects_sparks.dm
+++ b/code/game/objects/effects/effect_system/effects_sparks.dm
@@ -5,13 +5,9 @@
// will always spawn at the items location.
/////////////////////////////////////////////
-/proc/do_sparks(n, c, source)
- // n - number of sparks
- // c - cardinals, bool, do the sparks only move in cardinal directions?
- // source - source of the sparks.
-
+/proc/do_sparks(number, cardinal_only, datum/source)
var/datum/effect_system/spark_spread/sparks = new
- sparks.set_up(n, c, source)
+ sparks.set_up(number, cardinal_only, source)
sparks.autocleanup = TRUE
sparks.start()
diff --git a/code/game/objects/effects/effect_system/effects_water.dm b/code/game/objects/effects/effect_system/effects_water.dm
index 780e8ae7ebd..5b265c11b99 100644
--- a/code/game/objects/effects/effect_system/effects_water.dm
+++ b/code/game/objects/effects/effect_system/effects_water.dm
@@ -25,6 +25,17 @@
A.reagents.expose_temperature(-25)
return ..()
+///Extinguisher snowflake
+/obj/effect/particle_effect/water/extinguisher
+
+/obj/effect/particle_effect/water/extinguisher/Move()
+ . = ..()
+ if(!reagents)
+ return
+ reagents.expose(get_turf(src))
+ for(var/atom/thing as anything in get_turf(src))
+ reagents.expose(thing)
+
/////////////////////////////////////////////
// GENERIC STEAM SPREAD SYSTEM
diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm
index b3c2839fe90..8ec5ec113c5 100644
--- a/code/game/objects/effects/spiderwebs.dm
+++ b/code/game/objects/effects/spiderwebs.dm
@@ -203,7 +203,7 @@
var/list/nearby = oview(10, src)
if(nearby.len)
var/target_atom = pick(nearby)
- walk_to(src, target_atom)
+ SSmove_manager.move_to(src, target_atom)
if(prob(40))
src.visible_message(span_notice("\The [src] skitters[pick(" away"," around","")]."))
else if(prob(10))
@@ -211,7 +211,7 @@
for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src))
if(!v.welded)
entry_vent = v
- walk_to(src, entry_vent, 1)
+ SSmove_manager.move_to(src, entry_vent, 1)
break
if(isturf(loc))
amount_grown += rand(0,2)
diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index a0b89054233..e1f83b002b9 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -48,7 +48,6 @@
qdel(src)
/* Tosses things in a certain direction */
-
/obj/effect/step_trigger/thrower
var/direction = SOUTH // the direction of throw
var/tiles = 3 // if 0: forever until atom hits a stopper
@@ -56,14 +55,13 @@
var/speed = 1 // delay of movement
var/facedir = 0 // if 1: atom faces the direction of movement
var/nostop = 0 // if 1: will only be stopped by teleporters
+ ///List of moving atoms mapped to their inital direction
var/list/affecting = list()
/obj/effect/step_trigger/thrower/Trigger(atom/A)
if(!A || !ismovable(A))
return
var/atom/movable/AM = A
- var/curtiles = 0
- var/stopthrow = FALSE
for(var/obj/effect/step_trigger/thrower/T in orange(2, src))
if(AM in T.affecting)
return
@@ -71,39 +69,41 @@
if(immobilize)
ADD_TRAIT(AM, TRAIT_IMMOBILIZED, src)
- affecting.Add(AM)
- while(AM && !stopthrow)
- if(tiles)
- if(curtiles >= tiles)
- break
- if(AM.z != src.z)
- break
+ affecting[AM] = AM.dir
+ var/datum/move_loop/loop = SSmove_manager.move(AM, direction, speed, tiles ? tiles * speed : INFINITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_move)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/set_to_normal)
- curtiles++
+/obj/effect/step_trigger/thrower/proc/pre_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/atom/movable/being_moved = source.moving
+ affecting[being_moved] = being_moved.dir
- sleep(speed)
+/obj/effect/step_trigger/thrower/proc/post_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/atom/movable/being_moved = source.moving
+ if(!facedir)
+ being_moved.setDir(affecting[being_moved])
+ if(being_moved.z != z)
+ qdel(source)
+ return
+ if(!nostop)
+ for(var/obj/effect/step_trigger/T in get_turf(being_moved))
+ if(T.stopper && T != src)
+ qdel(source)
+ return
+ else
+ for(var/obj/effect/step_trigger/teleporter/T in get_turf(being_moved))
+ if(T.stopper)
+ qdel(source)
+ return
- // Calculate if we should stop the process
- if(!nostop)
- for(var/obj/effect/step_trigger/T in get_step(AM, direction))
- if(T.stopper && T != src)
- stopthrow = TRUE
- else
- for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction))
- if(T.stopper)
- stopthrow = TRUE
-
- if(AM)
- var/predir = AM.dir
- step(AM, direction)
- if(!facedir)
- AM.setDir(predir)
-
-
-
- affecting.Remove(AM)
-
- REMOVE_TRAIT(AM, TRAIT_IMMOBILIZED, src)
+/obj/effect/step_trigger/thrower/proc/set_to_normal(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/atom/movable/being_moved = source.moving
+ affecting -= being_moved
+ REMOVE_TRAIT(being_moved, TRAIT_IMMOBILIZED, src)
/* Stops things thrown by a thrower, doesn't do anything */
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index fc0e57b8da5..972a0e635f1 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -149,6 +149,7 @@
R.cooling_temperature = cooling_power
else
to_chat(user, span_warning("\The [W] is empty!"))
+
return TRUE
else
return FALSE
@@ -183,8 +184,8 @@
var/obj/B = user.buckled
var/movementdirection = turn(direction,180)
addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection), 1)
-
- else user.newtonian_move(turn(direction, 180))
+ else
+ user.newtonian_move(turn(direction, 180))
//Get all the turfs that can be shot at
var/turf/T = get_turf(target)
@@ -196,61 +197,43 @@
var/turf/T4 = get_step(T2,turn(direction, -90))
the_targets.Add(T3,T4)
- var/list/water_particles=list()
+ var/list/water_particles = list()
for(var/a in 1 to 5)
- var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(src))
+ var/obj/effect/particle_effect/water/extinguisher/water = new /obj/effect/particle_effect/water/extinguisher(get_turf(src))
var/my_target = pick(the_targets)
- water_particles[W] = my_target
+ water_particles[water] = my_target
// If precise, remove turf from targets so it won't be picked more than once
if(precision)
the_targets -= my_target
- var/datum/reagents/R = new/datum/reagents(5)
- W.reagents = R
- R.my_atom = W
- reagents.trans_to(W,1, transfered_by = user)
+ var/datum/reagents/water_reagents = new /datum/reagents(5)
+ water.reagents = water_reagents
+ water_reagents.my_atom = water
+ reagents.trans_to(water, 1, transfered_by = user)
//Make em move dat ass, hun
- addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2)
+ move_particles(water_particles)
//Particle movement loop
-/obj/item/extinguisher/proc/move_particles(list/particles, repetition=0)
- //Check if there's anything in here first
- if(!particles || particles.len == 0)
- return
+/obj/item/extinguisher/proc/move_particles(list/particles)
+ var/delay = 2
// Second loop: Get all the water particles and make them move to their target
- for(var/obj/effect/particle_effect/water/W in particles)
- var/turf/my_target = particles[W]
- if(!W)
- continue
- step_towards(W,my_target)
- if(!W.reagents)
- continue
- W.reagents.expose(get_turf(W))
- for(var/A in get_turf(W))
- W.reagents.expose(A)
- if(W.loc == my_target)
- particles -= W
- if(repetition < power)
- repetition++
- addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2)
+ for(var/obj/effect/particle_effect/water/extinguisher/water as anything in particles)
+ SSmove_manager.move_towards_legacy(water, particles[water], delay, timeout = delay * power, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
//Chair movement loop
-/obj/item/extinguisher/proc/move_chair(obj/B, movementdirection, repetition=0)
- step(B, movementdirection)
+/obj/item/extinguisher/proc/move_chair(obj/buckled_object, movementdirection)
+ var/datum/move_loop/loop = SSmove_manager.move(buckled_object, movementdirection, 1, timeout = 9, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ //This means the chair slowing down is dependant on the extinguisher existing, which is weird
+ //Couldn't figure out a better way though
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/manage_chair_speed)
- var/timer_seconds
- switch(repetition)
- if(0 to 2)
- timer_seconds = 1
- if(3 to 4)
- timer_seconds = 2
- if(5 to 8)
- timer_seconds = 3
- else
- return
-
- repetition++
- addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds)
+/obj/item/extinguisher/proc/manage_chair_speed(datum/move_loop/move/source)
+ SIGNAL_HANDLER
+ switch(source.lifetime)
+ if(5 to 4)
+ source.delay = 2
+ if(3 to 1)
+ source.delay = 3
/obj/item/extinguisher/AltClick(mob/user)
if(!user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE, TRUE))
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index d4e0f938134..dabe49e6c51 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -345,8 +345,8 @@
var/list/obj/item/oldContents = contents.Copy()
SEND_SIGNAL(src, COMSIG_TRY_STORAGE_QUICK_EMPTY)
// Make each item scatter a bit
- for(var/obj/item/I in oldContents)
- INVOKE_ASYNC(src, .proc/do_scatter, I)
+ for(var/obj/item/tray_item in oldContents)
+ do_scatter(tray_item)
if(prob(50))
playsound(M, 'sound/items/trayhit1.ogg', 50, TRUE)
@@ -358,11 +358,18 @@
M.Paralyze(40)
update_appearance()
-/obj/item/storage/bag/tray/proc/do_scatter(obj/item/I)
- for(var/i in 1 to rand(1,2))
- if(I)
- step(I, pick(NORTH,SOUTH,EAST,WEST))
- sleep(rand(2,4))
+/obj/item/storage/bag/tray/proc/do_scatter(obj/item/tray_item)
+ var/delay = rand(2,4)
+ var/datum/move_loop/loop = SSmove_manager.move_rand(tray_item, list(NORTH,SOUTH,EAST,WEST), delay, timeout = rand(1, 2) * delay, flags = MOVEMENT_LOOP_START_FAST)
+ //This does mean scattering is tied to the tray. Not sure how better to handle it
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/change_speed)
+
+/obj/item/storage/bag/tray/proc/change_speed(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/new_delay = rand(2, 4)
+ var/count = source.lifetime / source.delay
+ source.lifetime = count * new_delay
+ source.delay = new_delay
/obj/item/storage/bag/tray/update_overlays()
. = ..()
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index 0b3c5376586..57a11021c72 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -251,7 +251,7 @@
var/obj/item/watertank/tank
var/nozzle_mode = 0
var/metal_synthesis_cooldown = 0
- var/resin_cooldown = 0
+ COOLDOWN_DECLARE(resin_cooldown)
/obj/item/extinguisher/mini/nozzle/Initialize(mapload)
. = ..()
@@ -308,20 +308,20 @@
if(R.total_volume < 100)
to_chat(user, span_warning("You need at least 100 units of water to use the resin launcher!"))
return
- if(resin_cooldown)
+ if(!COOLDOWN_FINISHED(src, resin_cooldown))
to_chat(user, span_warning("Resin launcher is still recharging..."))
return
- resin_cooldown = TRUE
+ COOLDOWN_START(src, resin_cooldown, 10 SECONDS)
R.remove_any(100)
- var/obj/effect/resin_container/A = new (get_turf(src))
+ var/obj/effect/resin_container/resin = new (get_turf(src))
log_game("[key_name(user)] used Resin Launcher at [AREACOORD(user)].")
playsound(src,'sound/items/syringeproj.ogg',40,TRUE)
- for(var/i in 1 to 5)
- step_towards(A, target)
- sleep(2)
- A.Smoke()
- addtimer(VARSET_CALLBACK(src, resin_cooldown, FALSE), 10 SECONDS)
+ var/delay = 2
+ var/datum/move_loop/loop = SSmove_manager.move_towards(resin, target, delay, timeout = delay * 5, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/resin_stop_check)
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/resin_landed)
return
+
if(nozzle_mode == RESIN_FOAM)
if(!Adj|| !isturf(target))
return
@@ -338,6 +338,20 @@
to_chat(user, span_warning("Resin foam mix is still being synthesized..."))
return
+/obj/item/extinguisher/mini/nozzle/proc/resin_stop_check(datum/move_loop/source, succeeded)
+ SIGNAL_HANDLER
+ if(succeeded)
+ return
+ resin_landed(source)
+ qdel(source)
+
+/obj/item/extinguisher/mini/nozzle/proc/resin_landed(datum/move_loop/source)
+ SIGNAL_HANDLER
+ if(!istype(source.moving, /obj/effect/resin_container) || QDELETED(source.moving))
+ return
+ var/obj/effect/resin_container/resin = source.moving
+ resin.Smoke()
+
/obj/item/extinguisher/mini/nozzle/proc/reduce_metal_synth_cooldown()
metal_synthesis_cooldown--
@@ -356,6 +370,9 @@
playsound(src,'sound/effects/bamf.ogg',100,TRUE)
qdel(src)
+/obj/effect/resin_container/newtonian_move() // Please don't spacedrift thanks
+ return TRUE
+
#undef EXTINGUISHER
#undef RESIN_LAUNCHER
#undef RESIN_FOAM
diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm
index 73ba9019946..951e1acbee2 100644
--- a/code/game/objects/structures/transit_tubes/station.dm
+++ b/code/game/objects/structures/transit_tubes/station.dm
@@ -142,7 +142,7 @@
close_animation()
sleep(CLOSE_DURATION + 2)
if(open_status == STATION_TUBE_CLOSED && pod && pod.loc == loc)
- pod.follow_tube()
+ pod.follow_tube(src)
pod_moving = FALSE
return TRUE
return FALSE
@@ -241,7 +241,7 @@
for(var/obj/structure/transit_tube_pod/pod in loc)
if(!pod.moving)
pod_moving = TRUE
- pod.follow_tube()
+ pod.follow_tube(src)
pod_moving = FALSE
return TRUE
return FALSE
diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
index 1c37b568180..1d1d5028d00 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
@@ -11,12 +11,6 @@
var/datum/gas_mixture/air_contents = new()
var/occupied_icon_state = "pod_occupied"
var/obj/structure/transit_tube/current_tube = null
- var/next_dir
- var/next_loc
- var/enter_delay = 0
- var/exit_delay
- var/moving_time = 0
-
/obj/structure/transit_tube_pod/Initialize(mapload)
. = ..()
@@ -25,7 +19,6 @@
air_contents.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD
air_contents.temperature = T20C
-
/obj/structure/transit_tube_pod/Destroy()
empty_pod()
return ..()
@@ -97,70 +90,68 @@
M.forceMove(location)
update_appearance()
-/obj/structure/transit_tube_pod/Process_Spacemove()
- if(moving) //No drifting while moving in the tubes
- return TRUE
- else
- return ..()
-
-/obj/structure/transit_tube_pod/proc/follow_tube()
- set waitfor = FALSE
- if(moving)
+/obj/structure/transit_tube_pod/proc/follow_tube(obj/structure/transit_tube/tube)
+ if(moving || !tube.has_exit(dir))
return
moving = TRUE
+ current_tube = tube
- for(var/obj/structure/transit_tube/tube in loc)
- if(tube.has_exit(dir))
+ var/datum/move_loop/engine = SSmove_manager.force_move_dir(src, dir, 0, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(engine, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/before_pipe_transfer)
+ RegisterSignal(engine, COMSIG_MOVELOOP_POSTPROCESS, .proc/after_pipe_transfer)
+ RegisterSignal(engine, COMSIG_PARENT_QDELETING, .proc/engine_finish)
+ calibrate_engine(engine)
+
+/obj/structure/transit_tube_pod/proc/before_pipe_transfer(datum/move_loop/move/source)
+ SIGNAL_HANDLER
+ setDir(source.direction)
+
+/obj/structure/transit_tube_pod/proc/after_pipe_transfer(datum/move_loop/move/source)
+ SIGNAL_HANDLER
+
+ set_density(current_tube.density)
+ if(current_tube.should_stop_pod(src, source.direction))
+ current_tube.pod_stopped(src, dir)
+ qdel(source)
+ return
+
+ calibrate_engine(source)
+
+/obj/structure/transit_tube_pod/proc/calibrate_engine(datum/move_loop/move/engine)
+ var/next_dir = current_tube.get_exit(dir)
+
+ if(!next_dir)
+ qdel(engine)
+ return
+
+ var/exit_delay = current_tube.exit_delay(src, dir)
+ var/atom/next_loc = get_step(loc, next_dir)
+
+ current_tube = null
+ for(var/obj/structure/transit_tube/tube in next_loc)
+ if(tube.has_entrance(next_dir))
current_tube = tube
break
- move_animation(MOVE_ANIMATION_STAGE_ONE)
-
-///timer loop that handles the pod moving from tube to tube
-/obj/structure/transit_tube_pod/proc/move_animation(stage = MOVE_ANIMATION_STAGE_ONE)
- if(stage == MOVE_ANIMATION_STAGE_ONE)
- next_dir = current_tube.get_exit(dir)
-
- if(!next_dir)
- return
-
- exit_delay = current_tube.exit_delay(src, dir)
- next_loc = get_step(loc, next_dir)
-
- current_tube = null
- for(var/obj/structure/transit_tube/tube in next_loc)
- if(tube.has_entrance(next_dir))
- current_tube = tube
- break
-
- if(current_tube == null)
- setDir(next_dir)
- Move(get_step(loc, dir), dir, DELAY_TO_GLIDE_SIZE(exit_delay)) // Allow collisions when leaving the tubes.
- return
-
- enter_delay = current_tube.enter_delay(src, next_dir)
- if(enter_delay > 0)
- addtimer(CALLBACK(src, .proc/move_animation, MOVE_ANIMATION_STAGE_TWO), enter_delay)
- return
- else
- stage = MOVE_ANIMATION_STAGE_TWO
- if(stage == MOVE_ANIMATION_STAGE_TWO)
+ if(!current_tube)
setDir(next_dir)
- set_glide_size(DELAY_TO_GLIDE_SIZE(enter_delay + exit_delay))
- forceMove(next_loc) // When moving from one tube to another, skip collision and such.
- set_density(current_tube.density)
+ // Allow collisions when leaving the tubes.
+ Move(get_step(loc, dir), dir, DELAY_TO_GLIDE_SIZE(exit_delay))
+ qdel(src)
+ return
- if(current_tube?.should_stop_pod(src, next_dir))
- current_tube.pod_stopped(src, dir)
- else
- addtimer(CALLBACK(src, .proc/move_animation, MOVE_ANIMATION_STAGE_ONE), exit_delay)
- return
+ var/enter_delay = current_tube.enter_delay(src, next_dir)
+ engine.direction = next_dir
+ engine.set_delay(enter_delay + exit_delay)
+
+/obj/structure/transit_tube_pod/proc/engine_finish()
set_density(TRUE)
moving = FALSE
var/obj/structure/transit_tube/TT = locate(/obj/structure/transit_tube) in loc
- if(!TT || (!(dir in TT.tube_dirs) && !(turn(dir,180) in TT.tube_dirs))) //landed on a turf without transit tube or not in our direction
+ //landed on a turf without transit tube or not in our direction
+ if(!TT || (!(dir in TT.tube_dirs) && !(turn(dir,180) in TT.tube_dirs)))
outside_tube()
/obj/structure/transit_tube_pod/proc/outside_tube()
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index 2ac36c0a96f..83c607ec109 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -258,48 +258,47 @@
movable_content.wash(CLEAN_WASH)
return TRUE
-/turf/open/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, paralyze_amount, force_drop)
- if(C.movement_type & FLYING)
+/turf/open/handle_slip(mob/living/carbon/slipper, knockdown_amount, obj/O, lube, paralyze_amount, force_drop)
+ if(slipper.movement_type & FLYING)
return FALSE
if(has_gravity(src))
var/obj/buckled_obj
- if(C.buckled)
- buckled_obj = C.buckled
+ if(slipper.buckled)
+ buckled_obj = slipper.buckled
if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube.
return FALSE
else
- if(!(lube & SLIP_WHEN_CRAWLING) && (C.body_position == LYING_DOWN || !(C.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
+ if(!(lube & SLIP_WHEN_CRAWLING) && (slipper.body_position == LYING_DOWN || !(slipper.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
return FALSE
- if(C.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
+ if(slipper.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
return FALSE
if(!(lube&SLIDE_ICE))
- to_chat(C, span_notice("You slipped[ O ? " on the [O.name]" : ""]!"))
- playsound(C.loc, 'sound/misc/slip.ogg', 50, TRUE, -3)
+ to_chat(slipper, span_notice("You slipped[ O ? " on the [O.name]" : ""]!"))
+ playsound(slipper.loc, 'sound/misc/slip.ogg', 50, TRUE, -3)
- SEND_SIGNAL(C, COMSIG_ON_CARBON_SLIP)
+ SEND_SIGNAL(slipper, COMSIG_ON_CARBON_SLIP)
if(force_drop)
- for(var/obj/item/I in C.held_items)
- C.accident(I)
+ for(var/obj/item/I in slipper.held_items)
+ slipper.accident(I)
- var/olddir = C.dir
- C.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
+ var/olddir = slipper.dir
+ slipper.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
if(!(lube & SLIDE_ICE))
- C.Knockdown(knockdown_amount)
- C.Paralyze(paralyze_amount)
- C.stop_pulling()
+ slipper.Knockdown(knockdown_amount)
+ slipper.Paralyze(paralyze_amount)
+ slipper.stop_pulling()
else
- C.Knockdown(20)
+ slipper.Knockdown(20)
if(buckled_obj)
- buckled_obj.unbuckle_mob(C)
+ buckled_obj.unbuckle_mob(slipper)
lube |= SLIDE_ICE
- if(lube&SLIDE)
- new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 4), 1, FALSE, CALLBACK(C, /mob/living/carbon/.proc/spin, 1, 1))
+ var/turf/target = get_ranged_target_turf(slipper, olddir, 4)
+ if(lube & SLIDE)
+ slipper.AddComponent(/datum/component/force_move, target, TRUE)
else if(lube&SLIDE_ICE)
- if(C.force_moving) //If we're already slipping extend it
- qdel(C.force_moving)
- new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 1), 1, FALSE) //spinning would be bad for ice, fucks up the next dir
+ slipper.AddComponent(/datum/component/force_move, target, FALSE)//spinning would be bad for ice, fucks up the next dir
return TRUE
/turf/open/proc/MakeSlippery(wet_setting = TURF_WET_WATER, min_wet_time = 0, wet_time_to_add = 0, max_wet_time = MAXIMUM_WET_TIME, permanent)
diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
index ad673e6ca0b..24a6972a117 100644
--- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
+++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
@@ -376,10 +376,12 @@
var/last_high_pressure_movement_air_cycle = 0
/atom/movable/proc/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
+ set waitfor = FALSE
+ if(SEND_SIGNAL(src, COMSIG_ATOM_PRE_PRESSURE_PUSH) & COMSIG_ATOM_BLOCKS_PRESSURE)
+ return
var/const/PROBABILITY_OFFSET = 25
var/const/PROBABILITY_BASE_PRECENT = 75
var/max_force = sqrt(pressure_difference) * (MOVE_FORCE_DEFAULT / 5)
- set waitfor = FALSE
var/move_prob = 100
if (pressure_resistance > 0)
move_prob = (pressure_difference / pressure_resistance * PROBABILITY_BASE_PRECENT) - PROBABILITY_OFFSET
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 4953f50a6c4..1dec7cf7498 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -42,8 +42,10 @@
var/datum/preferences/prefs = null
///last turn of the controlled mob, I think this is only used by mechs?
var/last_turn = 0
- ///Move delay of controlled mob, related to input handling
+ ///Move delay of controlled mob, any keypresses inside this period will persist until the next proper move
var/move_delay = 0
+ ///The visual delay to use for the current client.Move(), mostly used for making a client based move look like it came from some other slower source
+ var/visual_delay = 0
///Current area of the controlled mob
var/area = null
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 9b8a0f0c769..73360783c7a 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -86,9 +86,9 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
RegisterSignal(src, COMSIG_ATOM_ENTERING, .proc/on_entering_atom)
if(special_target)
- walk_towards(src, special_target, 1)
+ SSmove_manager.home_onto(src, special_target)
else
- walk_towards(src, destination, 1)
+ SSmove_manager.move_towards(src, destination)
/obj/effect/immovablerod/Destroy(force)
UnregisterSignal(src, COMSIG_ATOM_ENTERING)
@@ -153,7 +153,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
return
visible_message(span_danger("[src] phases into reality."))
- walk_towards(src, special_target, 1)
+ SSmove_manager.home_onto(src, special_target)
if(loc == target_turf)
complete_trajectory()
@@ -169,7 +169,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
if(target_turf.z != z)
if(loopy_rod)
complete_trajectory()
- return
+ return ..()
qdel(src)
return
@@ -194,6 +194,9 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
/obj/effect/immovablerod/singularity_pull()
return
+/obj/effect/immovablerod/Process_Spacemove()
+ return TRUE
+
/obj/effect/immovablerod/Bump(atom/clong)
if(prob(10))
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
@@ -293,12 +296,12 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
* Stops your rod's automated movement. Sit... Stay... Good rod!
*/
/obj/effect/immovablerod/proc/sit_stay_good_rod()
- walk(src, 0)
+ SSmove_manager.stop_looping(src)
/**
* Allows your rod to release restraint level zero and go for a walk.
*
- * If walkies_location is set, rod will walk_towards the location, chasing it across z-levels if necessary.
+ * If walkies_location is set, rod will move towards the location, chasing it across z-levels if necessary.
* If walkies_location is not set, rod will call complete_trajectory() and follow the logic from that proc.
*
* Arguments:
@@ -307,7 +310,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
/obj/effect/immovablerod/proc/go_for_a_walk(walkies_location = null)
if(walkies_location)
special_target = walkies_location
- walk_towards(src, special_target, 1)
+ SSmove_manager.home_onto(src, special_target)
return
complete_trajectory()
@@ -323,4 +326,4 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
*/
/obj/effect/immovablerod/proc/walk_in_direction(direction)
destination = get_edge_target_turf(src, direction)
- walk_towards(src, destination, 1)
+ SSmove_manager.move_towards(src, destination)
diff --git a/code/modules/meteors/meteors.dm b/code/modules/meteors/meteors.dm
index f772830e5b8..63962fe2706 100644
--- a/code/modules/meteors/meteors.dm
+++ b/code/modules/meteors/meteors.dm
@@ -3,6 +3,7 @@
GLOBAL_VAR_INIT(meteor_wave_delay, 625) //minimum wait between waves in tenths of seconds
//set to at least 100 unless you want evarr ruining every round
+// This spelling mistake? Name? is older then git, I'm scared to touch it
//Meteors probability of spawning during a given wave
GLOBAL_LIST_INIT(meteors_normal, list(/obj/effect/meteor/dust=3, /obj/effect/meteor/medium=8, /obj/effect/meteor/big=3, \
@@ -40,8 +41,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust=1)) //for space dust eve
if(max_i<=0)
return
var/Me = pick_weight(meteortypes)
- var/obj/effect/meteor/M = new Me(pickedstart, pickedgoal)
- M.dest = pickedgoal
+ new Me(pickedstart, pickedgoal)
/proc/spaceDebrisStartLoc(startSide, Z)
var/starty
@@ -90,82 +90,97 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust=1)) //for space dust eve
icon_state = "small"
density = TRUE
anchored = TRUE
- var/hits = 4
- var/hitpwr = EXPLODE_HEAVY //Level of ex_act to be called on hit.
- var/dest
pass_flags = PASSTABLE
+
+ ///The resilience of our meteor
+ var/hits = 4
+ ///Level of ex_act to be called on hit.
+ var/hitpwr = EXPLODE_HEAVY
+ //Should we shake people's screens on impact
var/heavy = FALSE
+ ///Sound to play when you hit something
var/meteorsound = 'sound/effects/meteorimpact.ogg'
+ ///Our starting z level, prevents infinite meteors
var/z_original
- var/threat = 0 // used for determining which meteors are most interesting
- var/lifetime = DEFAULT_METEOR_LIFETIME
- var/timerid = null
+ ///Used for determining which meteors are most interesting
+ var/threat = 0
+
+ //Potential items to spawn when you die
var/list/meteordrop = list(/obj/item/stack/ore/iron)
+ ///How much stuff to spawn when you die
var/dropamt = 2
-/obj/effect/meteor/Move()
- if(z != z_original || loc == dest)
- qdel(src)
- return FALSE
+ ///The thing we're moving towards, usually a turf
+ var/atom/dest
+ ///Lifetime in seconds
+ var/lifetime = DEFAULT_METEOR_LIFETIME
- . = ..() //process movement...
+/obj/effect/meteor/Initialize(mapload, turf/target)
+ . = ..()
+ z_original = z
+ GLOB.meteor_list += src
+ SSaugury.register_doom(src, threat)
+ SpinAnimation()
+ chase_target(target)
- if(.)//.. if did move, ram the turf we get in
+/obj/effect/meteor/Destroy()
+ GLOB.meteor_list -= src
+ return ..()
+
+/obj/effect/meteor/Moved(atom/OldLoc, Dir, Forced = FALSE)
+ . = ..()
+ if(QDELETED(src))
+ return
+
+ if(OldLoc != loc)//If did move, ram the turf we get in
var/turf/T = get_turf(loc)
ram_turf(T)
if(prob(10) && !isspaceturf(T))//randomly takes a 'hit' from ramming
get_hit()
-/obj/effect/meteor/Destroy()
- if (timerid)
- deltimer(timerid)
- GLOB.meteor_list -= src
- walk(src,0) //this cancels the walk_towards() proc
- return ..()
+ if(z != z_original || loc == get_turf(dest))
+ qdel(src)
+ return
-/obj/effect/meteor/Initialize(mapload, target)
- . = ..()
- z_original = z
- GLOB.meteor_list += src
- SSaugury.register_doom(src, threat)
- SpinAnimation()
- timerid = QDEL_IN(src, lifetime)
- chase_target(target)
+/obj/effect/meteor/Process_Spacemove()
+ return TRUE //Keeps us from drifting for no reason
/obj/effect/meteor/Bump(atom/A)
+ . = ..() //What could go wrong
if(A)
ram_turf(get_turf(A))
playsound(src.loc, meteorsound, 40, TRUE)
get_hit()
+/obj/effect/meteor/proc/chase_target(atom/chasing, delay, home)
+ if(!isatom(chasing))
+ return
+ var/datum/move_loop/new_loop = SSmove_manager.move_towards(src, chasing, delay, home, lifetime)
+ if(!new_loop)
+ return
+
+ RegisterSignal(new_loop, COMSIG_PARENT_QDELETING, .proc/handle_stopping)
+
+///Deals with what happens when we stop moving, IE we die
+/obj/effect/meteor/proc/handle_stopping()
+ SIGNAL_HANDLER
+ if(!QDELETED(src))
+ qdel(src)
+
/obj/effect/meteor/proc/ram_turf(turf/T)
- //first bust whatever is in the turf
- for(var/thing in T)
- if(thing == src)
- continue
- if(isliving(thing))
- var/mob/living/living_thing = thing
- living_thing.visible_message(span_warning("[src] slams into [living_thing]."), span_userdanger("[src] slams into you!."))
- switch(hitpwr)
- if(EXPLODE_DEVASTATE)
- SSexplosions.high_mov_atom += thing
- if(EXPLODE_HEAVY)
- SSexplosions.med_mov_atom += thing
- if(EXPLODE_LIGHT)
- SSexplosions.low_mov_atom += thing
-
- //then, ram the turf if it still exists
- if(T)
- switch(hitpwr)
- if(EXPLODE_DEVASTATE)
- SSexplosions.highturf += T
- if(EXPLODE_HEAVY)
- SSexplosions.medturf += T
- if(EXPLODE_LIGHT)
- SSexplosions.lowturf += T
-
+ //first yell at mobs about them dying horribly
+ for(var/mob/living/thing in T)
+ thing.visible_message(span_warning("[src] slams into [thing]."), span_userdanger("[src] slams into you!."))
+ //then, ram the turf
+ switch(hitpwr)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.highturf += T
+ if(EXPLODE_HEAVY)
+ SSexplosions.medturf += T
+ if(EXPLODE_LIGHT)
+ SSexplosions.lowturf += T
//process getting 'hit' by colliding with a dense object
//or randomly when ramming turfs
@@ -193,11 +208,6 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust=1)) //for space dust eve
var/thing_to_spawn = pick(meteordrop)
new thing_to_spawn(get_turf(src))
-/obj/effect/meteor/proc/chase_target(atom/chasing, delay = 1)
- set waitfor = FALSE
- if(chasing)
- walk_towards(src, chasing, delay)
-
/obj/effect/meteor/proc/meteor_effect()
if(heavy)
var/sound/meteor_sound = sound(meteorsound)
diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
index 8118f0a320f..a286e05c311 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
@@ -24,8 +24,7 @@
. = ..()
if(!.)
return
- adjustBruteLoss(5 + rand(1,9))
- new /datum/forced_movement(src, get_step_away(user,src, 30), 1)
+ user.AddComponent(/datum/component/force_move, get_step_away(user,src, 30))
/mob/living/carbon/alien/larva/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && !visual_effect_icon)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 566da32b00a..af737ad2dae 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -1408,4 +1408,4 @@
our_splatter.add_blood_DNA(return_blood_DNA())
our_splatter.blood_dna_info = get_blood_dna_list()
var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
- INVOKE_ASYNC(our_splatter, /obj/effect/decal/cleanable/blood/hitsplatter/.proc/fly_towards, targ, splatter_strength)
+ our_splatter.fly_towards(targ, splatter_strength)
diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm
index 78be2badfb5..9610394edad 100644
--- a/code/modules/mob/living/init_signals.dm
+++ b/code/modules/mob/living/init_signals.dm
@@ -73,7 +73,7 @@
SIGNAL_HANDLER
mobility_flags &= ~MOBILITY_MOVE
if(living_flags & MOVES_ON_ITS_OWN)
- walk(src, 0) //stop mid walk
+ SSmove_manager.stop_looping(src) //stop mid walk //This is also really dumb
/// Called when [TRAIT_IMMOBILIZED] is removed from the mob.
/mob/living/proc/on_immobilized_trait_loss(datum/source)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 24959a64f03..aa9e0c5e0d9 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -935,12 +935,13 @@
playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE)
if(buckled || mob_negates_gravity())
return
+
if(client && client.move_delay >= world.time + world.tick_lag*2)
pressure_resistance_prob_delta -= 30
var/list/turfs_to_check = list()
- if(has_limbs)
+ if(!has_limbs)
var/turf/T = get_step(src, angle2dir(dir2angle(direction)+90))
if (T)
turfs_to_check += T
@@ -958,8 +959,7 @@
if (AM.density && AM.anchored)
pressure_resistance_prob_delta -= 20
break
- if(!force_moving)
- ..(pressure_difference, direction, pressure_resistance_prob_delta)
+ ..(pressure_difference, direction, pressure_resistance_prob_delta)
/mob/living/can_resist()
return !((next_move > world.time) || incapacitated(ignore_restraints = TRUE, ignore_stasis = TRUE))
@@ -1031,7 +1031,7 @@
"You struggle as you fail to break free of [pulledby]'s grip!", null, null, pulledby)
to_chat(pulledby, "[src] struggles as they fail to break free of your grip!")
if(moving_resist && client) //we resisted by trying to move
- client.move_delay = world.time + 40
+ client.move_delay = world.time + 4 SECONDS
else
pulledby.stop_pulling()
return FALSE
diff --git a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
index bdd7f6adf5e..eba10e502d2 100644
--- a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
+++ b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
@@ -63,7 +63,7 @@
switch(mode)
if(BOT_IDLE) // idle
update_appearance()
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
look_for_perp() // see if any criminals are in range
if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol
mode = BOT_START_PATROL // switch to patrol mode
@@ -72,7 +72,7 @@
playsound(src,'sound/effects/beepskyspinsabre.ogg',100,TRUE,-1)
// general beepsky doesn't give up so easily, jedi scum
if(frustration >= 20)
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
back_to_idle()
return
if(target) // make sure target exists
@@ -83,7 +83,7 @@
return
else // not next to perp
var/turf/olddist = get_dist(src, target)
- walk_to(src, target,1,4)
+ SSmove_manager.move_to(src, target, 1, 4)
if((get_dist(src, target)) >= (olddist))
frustration++
else
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index b477d27a633..276261ec003 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -72,7 +72,7 @@
target = null
oldtarget_name = null
set_anchored(FALSE)
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
last_found = world.time
limiting_spam = FALSE
@@ -213,7 +213,7 @@
if(BOT_IDLE) // idle
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
look_for_perp()
if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL)
mode = BOT_START_PATROL
@@ -222,7 +222,7 @@
// if can't reach perp for long enough, go idle
if(frustration >= 5) //gives up easier than beepsky
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
back_to_idle()
return
@@ -241,7 +241,7 @@
else // not next to perp
var/turf/olddist = get_dist(src, target)
- walk_to(src, target,1,4)
+ SSmove_manager.move_to(src, target, 1, 4)
if((get_dist(src, target)) >= (olddist))
frustration++
else
@@ -308,8 +308,6 @@
continue
/mob/living/simple_animal/bot/honkbot/explode()
-
- walk_to(src,0)
visible_message(span_boldannounce("[src] blows apart!"))
var/atom/Tsec = drop_location()
//doesn't drop cardboard nor its assembly, since its a very frail material.
diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
index 160bf9de4c1..a73b0e7ffc4 100644
--- a/code/modules/mob/living/simple_animal/bot/hygienebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
@@ -51,7 +51,6 @@
AddElement(/datum/element/connect_loc, loc_connections)
/mob/living/simple_animal/bot/hygienebot/explode()
- walk_to(src,0)
visible_message(span_boldannounce("[src] blows apart in a foamy explosion!"))
do_sparks(3, TRUE, src)
bot_mode_flags &= ~BOT_MODE_ON
@@ -86,7 +85,7 @@
..()
target = null
oldtarget_name = null
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
last_found = world.time
/mob/living/simple_animal/bot/hygienebot/handle_automated_action()
@@ -103,7 +102,7 @@
switch(mode)
if(BOT_IDLE) // idle
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
look_for_lowhygiene() // see if any disgusting fucks are in range
if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol
mode = BOT_START_PATROL // switch to patrol mode
@@ -135,7 +134,7 @@
if(olddist > 20 || frustration > 100) // Focus on something else
back_to_idle()
return
- walk_to(src, target,0, currentspeed)
+ SSmove_manager.move_to(src, target, 0, currentspeed)
if(mad && prob(min(frustration * 2, 60)))
playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1)
speak(pick("Get back here you foul smelling fucker.", "STOP RUNNING OR I WILL CUT YOUR ARTERIES!", "Just fucking let me clean you you arsehole!", "STOP. RUNNING.", "Either you stop running or I will fucking drag you out of an airlock.", "I just want to fucking clean you you troglodyte.", "If you don't come back here I'll put a green cloud around you cunt."))
@@ -167,7 +166,7 @@
/mob/living/simple_animal/bot/hygienebot/proc/back_to_idle()
mode = BOT_IDLE
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
target = null
frustration = 0
last_found = world.time
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index f5649587a6f..8006211a341 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -134,7 +134,7 @@
target = null
oldtarget_name = null
set_anchored(FALSE)
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
last_found = world.time
/mob/living/simple_animal/bot/secbot/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)//shocks only make him angry
@@ -318,7 +318,7 @@
switch(mode)
if(BOT_IDLE) // idle
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
look_for_perp() // see if any criminals are in range
if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol
mode = BOT_START_PATROL // switch to patrol mode
@@ -326,7 +326,7 @@
if(BOT_HUNT) // hunting for perp
// if can't reach perp for long enough, go idle
if(frustration >= 8)
- walk_to(src,0)
+ SSmove_manager.stop_looping(src)
back_to_idle()
return
@@ -349,7 +349,7 @@
// not next to perp
var/turf/olddist = get_dist(src, target)
- walk_to(src, target,1,4)
+ SSmove_manager.move_to(src, target, 1, 4)
if((get_dist(src, target)) >= (olddist))
frustration++
else
@@ -450,8 +450,6 @@
return FALSE
/mob/living/simple_animal/bot/secbot/explode()
-
- walk_to(src, 0)
visible_message(span_boldannounce("[src] blows apart!"))
var/atom/Tsec = drop_location()
if(bot_type == ADVANCED_SEC_BOT)
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 8ff0e972f11..571a286f4de 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -246,7 +246,7 @@
if(!stat && !resting && !buckled)
turns_since_scan++
if(turns_since_scan > 5)
- walk_to(src, 0)
+ SSmove_manager.stop_looping(src)
turns_since_scan = 0
if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) ))
movement_target = null
@@ -260,7 +260,7 @@
break
if(movement_target)
stop_automated_movement = 1
- walk_to(src,movement_target,0,3)
+ SSmove_manager.move_to(src, movement_target, 0, 3)
/mob/living/simple_animal/pet/cat/jerry //Holy shit we left jerry on donut ~ Arcane ~Fikou
name = "Jerry"
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index c58be9125e0..4cecbda4f55 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -66,7 +66,7 @@
/mob/living/simple_animal/hostile/Life(delta_time = SSMOBS_DT, times_fired)
. = ..()
if(!.) //dead
- walk(src, 0) //stops walking
+ SSmove_manager.stop_looping(src)
/mob/living/simple_animal/hostile/handle_automated_action()
if(AIStatus == AI_OFF)
@@ -283,11 +283,11 @@
if(!target.Adjacent(target_from) && ranged_cooldown <= world.time) //But make sure they're not in range for a melee attack and our range attack is off cooldown
OpenFire(target)
if(!Process_Spacemove()) //Drifting
- walk(src,0)
+ SSmove_manager.stop_looping(src)
return 1
if(retreat_distance != null) //If we have a retreat distance, check if we need to run from our target
if(target_distance <= retreat_distance) //If target's closer than our retreat distance, run
- walk_away(src,target,retreat_distance,move_to_delay)
+ SSmove_manager.move_away(src, target, retreat_distance, move_to_delay)
else
Goto(target,move_to_delay,minimum_distance) //Otherwise, get to our minimum distance so we chase them
else
@@ -320,7 +320,7 @@
approaching_target = TRUE
else
approaching_target = FALSE
- walk_to(src, target, minimum_distance, delay)
+ SSmove_manager.move_to(src, target, minimum_distance, delay)
/mob/living/simple_animal/hostile/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
. = ..()
@@ -359,7 +359,7 @@
GiveTarget(null)
approaching_target = FALSE
in_melee = FALSE
- walk(src, 0)
+ SSmove_manager.stop_looping(src)
LoseAggro()
//////////////END HOSTILE MOB TARGETTING AND AGGRESSION////////////
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
index ed2994cec0c..ddd212883af 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
@@ -71,7 +71,7 @@
/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE)
if(attack_state == MOOK_ATTACK_NEUTRAL && target)
attack_state = MOOK_ATTACK_WARMUP
- walk(src,0)
+ SSmove_manager.stop_looping(src)
update_icons()
if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo)
addtimer(CALLBACK(src, .proc/SlashCombo), ATTACK_INTERMISSION_TIME)
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
index 0c8e2b15ef4..1ced1676766 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
@@ -123,7 +123,7 @@
/mob/living/simple_animal/hostile/jungle/seedling/proc/WarmupAttack()
if(combatant_state == SEEDLING_STATE_NEUTRAL)
combatant_state = SEEDLING_STATE_WARMUP
- walk(src,0)
+ SSmove_manager.stop_looping(src)
update_icons()
var/target_dist = get_dist(src,target)
var/living_target_check = isliving(target)
diff --git a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
index 2b653064b79..f9390fe6900 100644
--- a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
@@ -111,7 +111,7 @@
ranged = 0
minimum_distance = 1
- walk(M,0)//end any lingering movement loops, to prevent the haunted mecha bug
+ SSmove_manager.stop_looping(src)//end any lingering movement loops, to prevent the haunted mecha bug
//Checks if a mecha is valid for theft
/mob/living/simple_animal/hostile/syndicate/mecha_pilot/proc/is_valid_mecha(obj/vehicle/sealed/mecha/M)
@@ -290,6 +290,6 @@
/mob/living/simple_animal/hostile/syndicate/mecha_pilot/Goto(target, delay, minimum_distance)
if(mecha)
- walk_to(mecha, target, minimum_distance, mecha.movedelay)
+ SSmove_manager.move_to(mecha, target, minimum_distance, mecha.movedelay)
else
..()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 9a7d253e03c..e77586bd663 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -123,11 +123,10 @@ Difficulty: Hard
if(!BUBBLEGUM_SMASH)
triple_charge.Trigger(target = target)
+ else if(prob(50 + anger_modifier))
+ hallucination_charge.Trigger(target = target)
else
- if(prob(50 + anger_modifier))
- hallucination_charge.Trigger(target = target)
- else
- hallucination_charge_surround.Trigger(target = target)
+ hallucination_charge_surround.Trigger(target = target)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood(mob/target)
var/list/targets = list(target)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
index a63bd6485f5..0073cb1a64a 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
@@ -23,42 +23,65 @@
environment_smash = ENVIRONMENT_SMASH_NONE
sentience_type = SENTIENCE_BOSS
layer = LARGE_MOB_LAYER
- var/doing_move_loop = FALSE
var/mob/living/set_target
- var/timerid
+ var/datum/move_loop/has_target/force_move/our_loop
/mob/living/simple_animal/hostile/asteroid/curseblob/Initialize(mapload)
. = ..()
- timerid = QDEL_IN(src, 600)
+ QDEL_IN(src, 60 SECONDS)
AddElement(/datum/element/simple_flying)
playsound(src, 'sound/effects/curse1.ogg', 100, TRUE, -1)
/mob/living/simple_animal/hostile/asteroid/curseblob/Destroy()
new /obj/effect/temp_visual/dir_setting/curse/blob(loc, dir)
- doing_move_loop = FALSE
+ set_target = null
return ..()
-/mob/living/simple_animal/hostile/asteroid/curseblob/Goto(move_target, delay, minimum_distance)
+/mob/living/simple_animal/hostile/asteroid/curseblob/Goto(move_target, delay, minimum_distance) //Observe
+ if(check_for_target())
+ return
move_loop(target, delay)
/mob/living/simple_animal/hostile/asteroid/curseblob/proc/move_loop(move_target, delay)
- set waitfor = FALSE
- if(doing_move_loop)
+ if(our_loop)
return
- doing_move_loop = TRUE
- if(check_for_target())
+ our_loop = SSmove_manager.force_move(src, move_target, delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ if(!our_loop)
return
- while(!QDELETED(src) && doing_move_loop && isturf(loc) && !check_for_target())
- var/step_turf = get_step(src, get_dir(src, set_target))
- if(step_turf != get_turf(set_target))
- forceMove(step_turf)
- sleep(delay)
- doing_move_loop = FALSE
+ RegisterSignal(move_target, COMSIG_MOB_STATCHANGE, .proc/stat_change)
+ RegisterSignal(move_target, COMSIG_MOVABLE_Z_CHANGED, .proc/target_z_change)
+ RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, .proc/our_z_change)
+ RegisterSignal(our_loop, COMSIG_PARENT_QDELETING, .proc/handle_loop_end)
+
+/mob/living/simple_animal/hostile/asteroid/curseblob/proc/stat_change(datum/source, new_stat)
+ SIGNAL_HANDLER
+ if(new_stat != CONSCIOUS)
+ qdel(src)
+
+/mob/living/simple_animal/hostile/asteroid/curseblob/proc/target_z_change(datum/source, old_z, new_z)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/mob/living/simple_animal/hostile/asteroid/curseblob/proc/our_z_change(datum/source, old_z, new_z)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/mob/living/simple_animal/hostile/asteroid/curseblob/proc/handle_loop_end()
+ SIGNAL_HANDLER
+ if(QDELETED(src))
+ return
+ qdel(src)
+
+/mob/living/simple_animal/hostile/asteroid/curseblob/handle_target_del(datum/source)
+ . = ..()
+ qdel(src)
/mob/living/simple_animal/hostile/asteroid/curseblob/proc/check_for_target()
- if(QDELETED(set_target) || set_target.stat != CONSCIOUS || z != set_target.z)
- if(!QDELETED(src))
- qdel(src)
+ if(QDELETED(src) || !set_target)
+ return TRUE
+ if(set_target.stat != CONSCIOUS)
+ return TRUE
+ if(set_target.z != z)
return TRUE
/mob/living/simple_animal/hostile/asteroid/curseblob/GiveTarget(new_target)
diff --git a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
index ba3983cbdbc..3769792b28e 100644
--- a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
@@ -111,7 +111,7 @@
/mob/living/simple_animal/hostile/asteroid/fugu/proc/Deflate()
if(wumbo)
- walk(src, 0)
+ SSmove_manager.stop_looping(src)
wumbo = 0
icon_state = "Fugu0"
obj_damage = 0
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 481adc8a331..07d01f48eb0 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -142,7 +142,7 @@
if(held_item)
held_item.forceMove(drop_location())
held_item = null
- walk(src,0)
+ SSmove_manager.stop_looping(src)
if(buckled)
buckled.unbuckle_mob(src,force=1)
@@ -368,6 +368,10 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
icon_state = icon_living
drop_held_item(0)
+/mob/living/simple_animal/parrot/Process_Spacemove()
+ if(!stat) //Birds can fly, fun fact. No I don't care that space doesn't have air. Space parrots bitch
+ return TRUE
+ return ..()
/*
* AI - Not really intelligent, but I'm calling it AI anyway.
*/
@@ -462,7 +466,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
//-----WANDERING - This is basically a 'I dont know what to do yet' state
else if(parrot_state == PARROT_WANDER)
//Stop movement, we'll set it later
- walk(src, 0)
+ SSmove_manager.stop_looping(src)
parrot_interest = null
//Wander around aimlessly. This will help keep the loops from searches down
@@ -500,7 +504,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
return
//-----STEALING
else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL))
- walk(src,0)
+ SSmove_manager.stop_looping(src)
if(!parrot_interest || held_item)
parrot_state = PARROT_SWOOP | PARROT_RETURN
return
@@ -524,7 +528,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
parrot_state = PARROT_SWOOP | PARROT_RETURN
return
- walk_to(src, parrot_interest, 1, parrot_speed)
+ SSmove_manager.move_to(src, parrot_interest, 1, parrot_speed)
if(isStuck())
return
@@ -532,7 +536,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
//-----RETURNING TO PERCH
else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN))
- walk(src, 0)
+ SSmove_manager.stop_looping(src)
if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isn't inside of something else.
parrot_perch = null
parrot_state = PARROT_WANDER
@@ -545,7 +549,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
icon_state = icon_sit
return
- walk_to(src, parrot_perch, 1, parrot_speed)
+ SSmove_manager.move_to(src, parrot_perch, 1, parrot_speed)
if(isStuck())
return
@@ -553,11 +557,11 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
//-----FLEEING
else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE))
- walk(src,0)
+ SSmove_manager.stop_looping(src)
if(!parrot_interest || !isliving(parrot_interest)) //Sanity
parrot_state = PARROT_WANDER
- walk_away(src, parrot_interest, 1, parrot_speed)
+ SSmove_manager.move_away(src, parrot_interest, 1, parrot_speed)
if(isStuck())
return
@@ -598,14 +602,14 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
L.attack_animal(src)//Time for the hurt to begin!
//Otherwise, fly towards the mob!
else
- walk_to(src, parrot_interest, 1, parrot_speed)
+ SSmove_manager.move_to(src, parrot_interest, 1, parrot_speed)
if(isStuck())
return
return
//-----STATE MISHAP
else //This should not happen. If it does lets reset everything and try again
- walk(src,0)
+ SSmove_manager.stop_looping(src)
parrot_interest = null
parrot_perch = null
drop_held_item()
@@ -995,7 +999,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
if(!ishuman(parrot_interest))
parrot_interest = null
else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest))
- walk_to(src, parrot_interest, 0, parrot_speed)
+ SSmove_manager.move_to(src, parrot_interest, 0, parrot_speed)
Possess(parrot_interest)
..()
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 6b5d80ea5c1..29f05af40a0 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -223,8 +223,6 @@
if (T && AIStatus == AI_Z_OFF)
SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src
- //Walking counts as a reference, putting this here because most things don't walk, clean this up once walk() procs are dead
- walk(src, 0)
return ..()
/mob/living/simple_animal/examine(mob/user)
@@ -462,7 +460,7 @@
if(del_on_death)
..()
//Prevent infinite loops if the mob Destroy() is overridden in such
- //a manner as to cause a call to death() again
+ //a manner as to cause a call to death() again //Pain
del_on_death = FALSE
qdel(src)
else
@@ -697,7 +695,7 @@
return
if(!Adjacent(hunted))
stop_automated_movement = TRUE
- walk_to(src,hunted,0,3)
+ SSmove_manager.move_to(src, hunted, 0, 3)
if(Adjacent(hunted))
hunt(hunted) // In case it gets next to the target immediately, skip the scan timer and kill it.
return
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index a3cbb1b147f..9ed1954a314 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -68,7 +68,6 @@
return FALSE
next_move_dir_add = 0
next_move_dir_sub = 0
-
var/old_move_delay = move_delay
move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick
if(!mob || !mob.loc)
@@ -84,7 +83,7 @@
if(mob.stat == DEAD)
mob.ghostize()
return FALSE
- if(mob.force_moving)
+ if(SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE) & COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE)
return FALSE
//SKYRAT EDIT ADDITION BEGIN - PIXEL_SHIFT
if(mob.shifting)
@@ -136,6 +135,10 @@
else
move_delay = world.time
+ //Basically an optional override for our glide size
+ //Sometimes you want to look like you're moving with a delay you don't actually have yet
+ visual_delay = 0
+
var/confusion = L.get_confusion()
if(confusion)
var/newdir = 0
@@ -154,7 +157,10 @@
if((direct & (direct - 1)) && mob.loc == new_loc) //moved diagonally successfully
add_delay *= SQRT_2
- mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay))
+ if(visual_delay)
+ mob.set_glide_size(visual_delay)
+ else
+ mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay))
move_delay += add_delay
if(.) // If mob is null here, we deserve the runtime
if(mob.throwing)
@@ -337,6 +343,14 @@
var/turf/turf = get_turf(src)
return !isgroundlessturf(turf) && HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)
+/mob/newtonian_move(direction)
+ . = ..()
+ if(!.) //Only do this if we're actually going somewhere
+ return
+ if(!client)
+ return
+ client.visual_delay = MOVEMENT_ADJUSTED_GLIDE_SIZE(inertia_move_delay, SSspacedrift.visual_delay) //Make sure moving into a space move looks like a space move
+
/// Called when this mob slips over, override as needed
/mob/proc/slip(knockdown_amount, obj/O, lube, paralyze, force_drop)
mind?.add_memory(MEMORY_SLIPPED, list(DETAIL_WHAT_BY = O, DETAIL_PROTAGONIST = src), story_value = STORY_VALUE_OKAY)
diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm
index 21041093a7a..df5ebf0b1e2 100644
--- a/code/modules/projectiles/guns/special/meat_hook.dm
+++ b/code/modules/projectiles/guns/special/meat_hook.dm
@@ -52,6 +52,8 @@
if(A.anchored)
return
A.visible_message(span_danger("[A] is snagged by [firer]'s hook!"))
+ //Should really be a movement loop, but I don't want to support moving 5 tiles a tick
+ //It just looks bad
new /datum/forced_movement(A, get_turf(firer), 5, TRUE)
if (isliving(target))
var/mob/living/fresh_meat = target
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 94955e89a31..24ba93ee017 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -81,56 +81,13 @@
/// Handles exposing atoms to the reagents contained in a spray's chempuff. Deletes the chempuff when it's completed.
/obj/item/reagent_containers/spray/proc/do_spray(atom/target, wait_step, obj/effect/decal/chempuff/reagent_puff, range, puff_reagent_left, mob/user)
- set waitfor = FALSE
-
- var/puff_reagents_string = reagent_puff.reagents.log_list()
-
- for(var/travelled_distance in 1 to range)
- var/has_travelled_max_distance = (travelled_distance == range)
-
- step_towards(reagent_puff, target)
- sleep(wait_step)
-
- for(var/atom/turf_atom in get_turf(reagent_puff))
- if(turf_atom == reagent_puff || turf_atom.invisibility) //we ignore the puff itself and stuff below the floor
- continue
-
- if(puff_reagent_left <= 0)
- break
-
- if(stream_mode)
- if(isliving(turf_atom))
- var/mob/living/turf_mob = turf_atom
-
- if(!turf_mob.can_inject())
- continue
-
- if((turf_mob.body_position == STANDING_UP) || has_travelled_max_distance)
- reagent_puff.reagents.expose(turf_mob, VAPOR)
- log_combat(user, turf_mob, "sprayed", src, addition="which had [puff_reagents_string]")
- puff_reagent_left -= 1
- else if(has_travelled_max_distance)
- reagent_puff.reagents.expose(turf_atom, VAPOR)
- log_combat(user, turf_atom, "sprayed", src, addition="which had [puff_reagents_string]")
- puff_reagent_left -= 1
- else
- reagent_puff.reagents.expose(turf_atom, VAPOR)
- log_combat(user, turf_atom, "sprayed", src, addition="which had [puff_reagents_string]")
- if(ismob(turf_atom))
- puff_reagent_left -= 1
-
- if(puff_reagent_left > 0 && (!stream_mode || has_travelled_max_distance))
- var/turf/puff_turf = get_turf(reagent_puff)
- reagent_puff.reagents.expose(puff_turf, VAPOR)
- log_combat(user, puff_turf, "sprayed", src, addition="which had [puff_reagents_string]")
- puff_reagent_left -= 1
-
- // Did we use up all the puff early? Time to delete the puff and return.
- if(puff_reagent_left <= 0)
- qdel(reagent_puff)
- return
-
- qdel(reagent_puff)
+ var/datum/move_loop/our_loop = SSmove_manager.move_towards_legacy(reagent_puff, target, wait_step, timeout = range * wait_step, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ reagent_puff.user = user
+ reagent_puff.sprayer = src
+ reagent_puff.lifetime = puff_reagent_left
+ reagent_puff.stream = stream_mode
+ reagent_puff.RegisterSignal(our_loop, COMSIG_PARENT_QDELETING, /obj/effect/decal/chempuff/proc/loop_ended)
+ reagent_puff.RegisterSignal(our_loop, COMSIG_MOVELOOP_POSTPROCESS, /obj/effect/decal/chempuff/proc/check_move)
/obj/item/reagent_containers/spray/attack_self(mob/user)
. = ..()
@@ -221,7 +178,7 @@
if(do_mob(user,user,30))
if(reagents.total_volume >= amount_per_transfer_from_this)//if not empty
user.visible_message(span_suicide("[user] pulls the trigger!"))
- src.spray(user)
+ spray(user)
return BRUTELOSS
else
user.visible_message(span_suicide("[user] pulls the trigger...but \the [src] is empty!"))
diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm
index 9e168f62342..ca2f05ce664 100644
--- a/code/modules/recycling/conveyor.dm
+++ b/code/modules/recycling/conveyor.dm
@@ -15,8 +15,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
name = "conveyor belt"
desc = "A conveyor belt."
layer = BELOW_OPEN_DOOR_LAYER
- processing_flags = START_PROCESSING_MANUALLY
- subsystem_type = /datum/controller/subsystem/processing/conveyors
+ processing_flags = NONE
/// The current state of the switch.
var/operating = CONVEYOR_OFF
/// This is the default (forward) direction, set by the map dir.
@@ -33,6 +32,8 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
var/flipped = FALSE
/// Are we currently conveying items?
var/conveying = FALSE
+ //Direction -> if we have a conveyor belt in that direction
+ var/list/neighbors
/obj/machinery/conveyor/examine(mob/user)
. = ..()
@@ -60,25 +61,40 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
processing_flags = START_PROCESSING_ON_INIT
/obj/machinery/conveyor/auto/Initialize(mapload, newdir)
- operating = TRUE
- return ..()
+ . = ..()
+ set_operating(TRUE)
/obj/machinery/conveyor/auto/update()
. = ..()
if(.)
- operating = TRUE
- update_appearance()
+ set_operating(TRUE)
+// create a conveyor
/obj/machinery/conveyor/Initialize(mapload, new_dir, new_id)
- . = ..()
+ ..()
if(new_dir)
setDir(new_dir)
if(new_id)
id = new_id
+ neighbors = list()
+ ///Leaving onto conveyor detection won't work at this point, but that's alright since it's an optimization anyway
+ ///Should be fine without it
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXITED = .proc/conveyable_exit,
+ COMSIG_ATOM_ENTERED = .proc/conveyable_enter,
+ COMSIG_ATOM_CREATED = .proc/conveyable_enter
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
update_move_direction()
LAZYADD(GLOB.conveyors_by_id[id], src)
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/conveyor/LateInitialize()
+ . = ..()
+ build_neighbors()
/obj/machinery/conveyor/Destroy()
+ set_operating(FALSE)
LAZYREMOVE(GLOB.conveyors_by_id[id], src)
return ..()
@@ -95,6 +111,34 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
. = ..()
update_move_direction()
+/obj/machinery/conveyor/Moved(atom/OldLoc, Dir)
+ . = ..()
+ if(!.)
+ return
+ //Now that we've moved, rebuild our neighbors list
+ neighbors = list()
+ build_neighbors()
+
+/obj/machinery/conveyor/proc/build_neighbors()
+ //This is acceptable because conveyor belts only move sometimes. Otherwise would be n^2 insanity
+ var/turf/our_turf = get_turf(src)
+ for(var/direction in GLOB.cardinals)
+ var/turf/new_turf = get_step(our_turf, direction)
+ var/obj/machinery/conveyor/valid = locate(/obj/machinery/conveyor) in new_turf
+ if(QDELETED(valid))
+ continue
+ neighbors["[direction]"] = TRUE
+ valid.neighbors["[DIRFLIP(direction)]"] = TRUE
+ RegisterSignal(valid, COMSIG_MOVABLE_MOVED, .proc/nearby_belt_changed, override=TRUE)
+ RegisterSignal(valid, COMSIG_PARENT_QDELETING, .proc/nearby_belt_changed, override=TRUE)
+ valid.RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/nearby_belt_changed, override=TRUE)
+ valid.RegisterSignal(src, COMSIG_PARENT_QDELETING, .proc/nearby_belt_changed, override=TRUE)
+
+/obj/machinery/conveyor/proc/nearby_belt_changed(datum/source)
+ SIGNAL_HANDLER
+ neighbors = list()
+ build_neighbors()
+
/**
* Proc to handle updating the directions in which the conveyor belt is moving items.
*/
@@ -148,73 +192,55 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
icon_state = "[base_icon_state][inverted ? -operating : operating ][flipped ? "-flipped" : ""]"
return ..()
-/**
- * Proc to update the conveyor depending on if it's got power or not.
- *
- * Returns TRUE if it is still able to be operating after the update, FALSE if not.
- */
+/obj/machinery/conveyor/proc/set_operating(new_value)
+ if(operating == new_value)
+ return
+ operating = new_value
+ update_appearance()
+ update_move_direction()
+ if(!operating) //If we ever turn off, disable moveloops
+ for(var/atom/movable/movable in get_turf(src))
+ stop_conveying(movable)
+
/obj/machinery/conveyor/proc/update()
. = TRUE
if(machine_stat & NOPOWER)
- operating = FALSE
- . = FALSE
- update_appearance()
-
-// machine process
-// move items to the target location
-/obj/machinery/conveyor/process()
- if(machine_stat & NOPOWER)
+ set_operating(FALSE)
+ return FALSE
+ if(!operating) //If we're on, start conveying so moveloops on our tile can be refreshed if they stopped for some reason
return
+ for(var/atom/movable/movable in get_turf(src))
+ start_conveying(movable)
- // If the conveyor is off or already moving items.
- if(!operating || conveying)
+/obj/machinery/conveyor/proc/conveyable_enter(datum/source, atom/convayable)
+ SIGNAL_HANDLER
+ if(operating == CONVEYOR_OFF)
+ SSmove_manager.stop_looping(convayable, SSconveyors)
return
-
- use_power(6)
-
- // Get the first 30 items in contents.
- var/turf/loc_turf = loc
- var/list/items = loc_turf.contents - src
- if(!LAZYLEN(items)) // Don't do anything at all if theres nothing there but the conveyor.
+ var/datum/move_loop/move/moving_loop = SSmove_manager.processing_on(convayable, SSconveyors)
+ if(moving_loop)
+ moving_loop.direction = movedir
return
+ start_conveying(convayable)
- var/list/affecting
- if(length(items) > MAX_CONVEYOR_ITEMS_MOVE)
- affecting = items.Copy(1, MAX_CONVEYOR_ITEMS_MOVE + 1) // Lists start at 1 lol.
- else
- affecting = items
+/obj/machinery/conveyor/proc/conveyable_exit(datum/source, atom/convayable, direction)
+ SIGNAL_HANDLER
+ var/has_conveyor = neighbors["[direction]"]
+ if(!has_conveyor || !isturf(convayable.loc)) //If you've entered something on us, stop moving
+ SSmove_manager.stop_looping(convayable, SSconveyors)
- conveying = TRUE
+/obj/machinery/conveyor/proc/start_conveying(atom/movable/moving)
+ var/static/list/unconveyables = typecacheof(list(/obj/effect, /mob/dead))
+ if(!istype(moving) || is_type_in_typecache(moving, unconveyables) || moving == src)
+ return
+ moving.AddComponent(/datum/component/convey, movedir, 0.2 SECONDS)
- addtimer(CALLBACK(src, .proc/convey, affecting), 1) // Movement effect.
-
-/**
- * Proc to handle moving items along the conveyor belt.
- */
-/obj/machinery/conveyor/proc/convey(list/affecting)
- for(var/thing in affecting)
- if(!ismovable(thing)) // This is like a third faster than for(var/atom/movable in affecting)
- continue
- var/atom/movable/movable_thing = thing
- // Give this a chance to yield if the server is busy
- stoplag()
-
- if(QDELETED(movable_thing) || (movable_thing.loc != loc))
- continue
-
- if(iseffect(movable_thing) || isdead(movable_thing))
- continue
-
- if(isliving(movable_thing))
- var/mob/living/zoom_mob = movable_thing
- if((zoom_mob.movement_type & FLYING) && !zoom_mob.stat)
- continue
-
- if(!movable_thing.anchored && movable_thing.has_gravity())
- step(movable_thing, movedir)
-
- conveying = FALSE
+/obj/machinery/conveyor/proc/stop_conveying(atom/movable/thing)
+ if(!ismovable(thing))
+ return
+ SSmove_manager.stop_looping(thing, SSconveyors)
+// attack with item, place item on conveyor
/obj/machinery/conveyor/attackby(obj/item/attacking_item, mob/living/user, params)
if(attacking_item.tool_behaviour == TOOL_CROWBAR)
user.visible_message(span_notice("[user] struggles to pry up [src] with [attacking_item]."), \
@@ -222,9 +248,10 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
if(!attacking_item.use_tool(src, user, 4 SECONDS, volume = 40))
return
-
+ set_operating(FALSE)
var/obj/item/stack/conveyor/belt_item = new /obj/item/stack/conveyor(loc, 1, TRUE, null, null, id)
- transfer_fingerprints_to(belt_item)
+ if(!QDELETED(belt_item)) //God I hate stacks
+ transfer_fingerprints_to(belt_item)
to_chat(user, span_notice("You remove [src]."))
qdel(src)
@@ -268,7 +295,6 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
. = ..()
update()
-
// Conveyor switch
/obj/machinery/conveyor_switch
name = "conveyor switch"
@@ -328,13 +354,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
/// Updates all conveyor belts that are linked to this switch, and tells them to start processing.
/obj/machinery/conveyor_switch/proc/update_linked_conveyors()
for(var/obj/machinery/conveyor/belt in GLOB.conveyors_by_id[id])
- belt.operating = position
- belt.update_move_direction()
- belt.update_appearance()
- if(belt.operating)
- belt.begin_processing()
- else
- belt.end_processing()
+ belt.set_operating(position)
CHECK_TICK
/// Finds any switches with same `id` as this one, and set their position and icon to match us.
diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm
index 12b844a37ff..351bef2141e 100644
--- a/code/modules/recycling/disposal/holder.dm
+++ b/code/modules/recycling/disposal/holder.dm
@@ -7,6 +7,8 @@
invisibility = INVISIBILITY_MAXIMUM
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
dir = NONE
+ var/obj/structure/disposalpipe/last_pipe
+ var/obj/structure/disposalpipe/current_pipe
var/datum/gas_mixture/gas // gas used to flush, will appear at exit point
var/active = FALSE // true if the holder is moving, otherwise inactive
var/count = 1000 // can travel 1000 steps before going inactive (in case of loops)
@@ -16,6 +18,8 @@
/obj/structure/disposalholder/Destroy()
active = FALSE
+ last_pipe = null
+ current_pipe = null
return ..()
// initialize a holder from the contents of a disposal unit
@@ -59,40 +63,48 @@
forceMove(D.trunk)
active = TRUE
setDir(DOWN)
- move()
+ start_moving()
-// movement process, persists while holder is moving through pipes
-/obj/structure/disposalholder/proc/move()
- set waitfor = FALSE
- var/ticks = 1
- var/obj/structure/disposalpipe/last
- while(active)
- var/obj/structure/disposalpipe/curr = loc
- last = curr
- set_glide_size(DELAY_TO_GLIDE_SIZE(ticks * world.tick_lag))
- curr = curr.transfer(src)
- if(!curr && active)
- last.expel(src, get_turf(src), dir)
+/// Starts the movement process, persists while the holder is moving through pipes
+/obj/structure/disposalholder/proc/start_moving()
+ var/delay = world.tick_lag
+ var/datum/move_loop/our_loop = SSmove_manager.move_disposals(src, delay = delay, timeout = delay * count)
+ if(our_loop)
+ RegisterSignal(our_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, .proc/pre_move)
+ RegisterSignal(our_loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/try_expel)
+ RegisterSignal(our_loop, COMSIG_PARENT_QDELETING, .proc/movement_stop)
+ current_pipe = loc
- ticks = stoplag()
- if(!(count--))
- active = FALSE
+/obj/structure/disposalholder/proc/pre_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ last_pipe = loc
+
+/obj/structure/disposalholder/proc/try_expel(datum/move_loop/source, succeed, visual_delay)
+ SIGNAL_HANDLER
+ current_pipe = loc
+ if(current_pipe || !active)
+ return
+ last_pipe.expel(src, get_turf(src), dir)
+
+/obj/structure/disposalholder/proc/movement_stop(datum/source)
+ SIGNAL_HANDLER
+ current_pipe = null
+ last_pipe = null
//failsafe in the case the holder is somehow forcemoved somewhere that's not a disposal pipe. Otherwise the above loop breaks.
/obj/structure/disposalholder/Moved(atom/oldLoc, dir)
. = ..()
var/static/list/pipes_typecache = typecacheof(/obj/structure/disposalpipe)
//Moved to nullspace gang
- if(!loc)
+ if(!loc || pipes_typecache[loc.type])
return
- if(!pipes_typecache[loc.type])
- var/turf/T = get_turf(loc)
- if(T)
- vent_gas(T)
- for(var/A in contents)
- var/atom/movable/AM = A
- AM.forceMove(drop_location())
- qdel(src)
+ var/turf/T = get_turf(loc)
+ if(T)
+ vent_gas(T)
+ for(var/A in contents)
+ var/atom/movable/AM = A
+ AM.forceMove(drop_location())
+ qdel(src)
// find the turf which should contain the next pipe
/obj/structure/disposalholder/proc/nextloc()
diff --git a/code/modules/recycling/disposal/multiz.dm b/code/modules/recycling/disposal/multiz.dm
index 69f4601314e..86e34073f3d 100644
--- a/code/modules/recycling/disposal/multiz.dm
+++ b/code/modules/recycling/disposal/multiz.dm
@@ -15,35 +15,28 @@
/obj/structure/disposalpipe/trunk/multiz/transfer(obj/structure/disposalholder/H)
if(H.dir == DOWN) //Since we're a trunk, you can still place a chute / bin over us. If theyve entered from there, treat this as a normal trunk
return ..()
- // otherwise, go to the linked object
- if(multiz_dir) //Are we a trunk that goes up? Or down?
- var/turf/T = null
- if(multiz_dir == MULTIZ_PIPE_UP)
- T = SSmapping.get_turf_above(get_turf(src)) //Get the turf above us
- if(multiz_dir == MULTIZ_PIPE_DOWN)
- T = SSmapping.get_turf_below(get_turf(src))
- if(!T)
- expel(H, get_turf(src), pick(GLOB.cardinals))
- return //Nothing located.
- var/obj/structure/disposalpipe/trunk/multiz/pipe = locate(/obj/structure/disposalpipe/trunk/multiz) in T
- if(pipe)
- var/obj/structure/disposalholder/destination = new(pipe) //For future reference, the disposal holder is the thing that carries mobs
- destination.init(pipe) //This instantiates it
- destination.merge(H) //This takes the contents of H (Our disposal holder that's travelling into us) and puts them into the destination holder
- destination.active = TRUE //Active allows it to process and move
- destination.setDir(DOWN) //This tells the trunk above us NOT to loop it back down to us, or else you get an infinite loop
- destination.move()
- return null //Which removes the disposalholder
- else
- var/obj/structure/disposaloutlet/O = linked
- if(istype(O))
- O.expel(H) // expel at outlet
- else
- var/obj/machinery/disposal/D = linked
- D.expel(H) // expel at disposal
+ //If we for some reason do not have a multiz dir, just like, use the default logic
+ if(!multiz_dir)
+ return ..()
- // Returning null without expelling holder makes the holder expell itself
- return null
+ //Are we a trunk that goes up? Or down?
+ var/turf/target = null
+ if(multiz_dir == MULTIZ_PIPE_UP)
+ target = SSmapping.get_turf_above(get_turf(src))
+ if(multiz_dir == MULTIZ_PIPE_DOWN)
+ target = SSmapping.get_turf_below(get_turf(src))
+ if(!target) //Nothing located.
+ return
+
+ var/obj/structure/disposalpipe/trunk/multiz/pipe = locate(/obj/structure/disposalpipe/trunk/multiz) in target
+ if(!pipe)
+ return
+ var/obj/structure/disposalholder/destination = new(pipe) //For future reference, the disposal holder is the thing that carries mobs
+ destination.init(pipe) //This instantiates it
+ destination.merge(H) //This takes the contents of H (Our disposal holder that's travelling into us) and puts them into the destination holder
+ destination.active = TRUE //Active allows it to process and move
+ destination.setDir(DOWN) //This tells the trunk above us NOT to loop it back down to us, or else you get an infinite loop
+ destination.start_moving()
#undef MULTIZ_PIPE_UP
#undef MULTIZ_PIPE_DOWN
diff --git a/code/modules/ruins/lavalandruin_code/biodome_winter.dm b/code/modules/ruins/lavalandruin_code/biodome_winter.dm
index a529f92c36e..98aa475b8c2 100644
--- a/code/modules/ruins/lavalandruin_code/biodome_winter.dm
+++ b/code/modules/ruins/lavalandruin_code/biodome_winter.dm
@@ -45,5 +45,5 @@
hit_object.make_frozen_visual()
else if(isliving(hit_atom))
var/mob/living/hit_mob = hit_atom
- walk(hit_mob, 0) //stops them mid pathing even if they're stunimmune
+ SSmove_manager.stop_looping(hit_mob) //stops them mid pathing even if they're stunimmune
hit_mob.apply_status_effect(/datum/status_effect/ice_block_talisman, 3 SECONDS)
diff --git a/code/modules/spells/spell_types/rod_form.dm b/code/modules/spells/spell_types/rod_form.dm
index 9d739507785..81cc6e0ea4f 100644
--- a/code/modules/spells/spell_types/rod_form.dm
+++ b/code/modules/spells/spell_types/rod_form.dm
@@ -37,10 +37,10 @@
notify = FALSE
dnd_style_level_up = FALSE
-/obj/effect/immovablerod/wizard/Move()
+/obj/effect/immovablerod/wizard/Moved()
+ . = ..()
if(get_dist(start_turf, get_turf(src)) >= max_distance)
qdel(src)
- ..()
/obj/effect/immovablerod/wizard/Destroy()
if(wizard)
diff --git a/code/modules/surgery/organs/external/wings.dm b/code/modules/surgery/organs/external/wings.dm
index 1a841285617..77d43b16b74 100644
--- a/code/modules/surgery/organs/external/wings.dm
+++ b/code/modules/surgery/organs/external/wings.dm
@@ -107,7 +107,7 @@
buckled_obj.unbuckle_mob(human)
step(buckled_obj, olddir)
else
- new /datum/forced_movement(human, get_ranged_target_turf(human, olddir, 4), 1, FALSE, CALLBACK(human, /mob/living/carbon/.proc/spin, 1, 1))
+ human.AddComponent(/datum/component/force_move, get_ranged_target_turf(human, olddir, 4), TRUE)
return TRUE
///UNSAFE PROC, should only be called through the Activate or other sources that check for CanFly
diff --git a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
index 1fb3ef26e0e..1753bba75de 100644
--- a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
@@ -117,23 +117,22 @@
atomstothrow = range(3, target)
else
atomstothrow = orange(3, target)
- for(var/atom/movable/A in atomstothrow)
- if(A.anchored || A.move_resist >= MOVE_FORCE_EXTREMELY_STRONG)
+ for(var/atom/movable/scatter in atomstothrow)
+ if(scatter.anchored || scatter.move_resist >= MOVE_FORCE_EXTREMELY_STRONG)
continue
- if(ismob(A))
- var/mob/M = A
- if(M.mob_negates_gravity())
+ if(ismob(scatter))
+ var/mob/scatter_mob = scatter
+ if(scatter_mob.mob_negates_gravity())
continue
- INVOKE_ASYNC(src, .proc/do_scatter, A, target)
+ do_scatter(scatter, target)
var/turf/targetturf = get_turf(target)
log_game("[key_name(source)] used a Gravitational Catapult repulse wave on [AREACOORD(targetturf)]")
return ..()
-/obj/item/mecha_parts/mecha_equipment/gravcatapult/proc/do_scatter(atom/movable/A, atom/movable/target)
- var/iter = 5-get_dist(A,target)
- for(var/i in 0 to iter)
- step_away(A,target)
- sleep(2)
+/obj/item/mecha_parts/mecha_equipment/gravcatapult/proc/do_scatter(atom/movable/scatter, atom/movable/target)
+ var/dist = 5 - get_dist(scatter, target)
+ var/delay = 2
+ SSmove_manager.move_away(scatter, target, delay = delay, timeout = delay * dist, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
/obj/item/mecha_parts/mecha_equipment/gravcatapult/get_equip_info()
return "[..()] [mode==1?"([movable_target||"Nothing"])":null] \[S|P\]"
diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
index 08713bf36ad..396d34e1bca 100644
--- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
@@ -163,6 +163,7 @@
energy_drain = 0
range = MECHA_MELEE|MECHA_RANGED
mech_flags = EXOSUIT_MODULE_WORKING
+ var/sprays_left = 0
/obj/item/mecha_parts/mecha_equipment/extinguisher/Initialize(mapload)
. = ..()
@@ -183,37 +184,36 @@
if(reagents.total_volume <= 0)
return
playsound(chassis, 'sound/effects/extinguish.ogg', 75, TRUE, -3)
- var/direction = get_dir(chassis,target)
- var/turf/T = get_turf(target)
- var/turf/T1 = get_step(T,turn(direction, 90))
- var/turf/T2 = get_step(T,turn(direction, -90))
- var/list/the_targets = list(T,T1,T2)
- INVOKE_ASYNC(src, .proc/do_extinguish, the_targets, source)
+ sprays_left += 5
+ add_hiddenprint(source) //log prints so admins can figure out who touched it last.
+ log_combat(source, target, "fired an extinguisher at")
+ spray_extinguisher(target)
return ..()
-///Creates new water effects and moves them, takes a list of turfs as an argument
-/obj/item/mecha_parts/mecha_equipment/extinguisher/proc/do_extinguish(list/targets, mob/user)//this could be made slighty better but extinguisher code sucks even more so...
- for(var/a=0 to 5)//generate new water...
- var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(chassis))
- var/turf/my_target = pick(targets)
- var/datum/reagents/R = new/datum/reagents(5)
- W.reagents = R
- R.my_atom = W
- reagents.trans_to(W,1, transfered_by = user)
- for(var/b=0 to 4)//...and move it 4 tiles
- if(!W)
- return
- step_towards(W,my_target)
- if(!W)
- return
- var/turf/W_turf = get_turf(W)
- W.reagents.expose(W_turf)
- for(var/atom/atm in W_turf)
- W.reagents.expose(atm)
- if(W.loc == my_target)
- break
- sleep(2)
+/obj/item/mecha_parts/mecha_equipment/extinguisher/proc/spray_extinguisher(atom/target)
+ var/direction = get_dir(chassis, target)
+ var/turf/T1 = get_turf(target)
+ var/turf/T2 = get_step(T1,turn(direction, 90))
+ var/turf/T3 = get_step(T1,turn(direction, -90))
+ var/list/targets = list(T1,T2,T3)
+
+ var/obj/effect/particle_effect/water/extinguisher/water = new /obj/effect/particle_effect/water/extinguisher(get_turf(chassis))
+ var/datum/reagents/water_reagents = new /datum/reagents(5)
+ water.reagents = water_reagents
+ water_reagents.my_atom = water
+ reagents.trans_to(water, 1)
+
+ var/delay = 2
+ var/datum/move_loop/our_loop = SSmove_manager.move_towards_legacy(water, pick(targets), delay, timeout = delay * 4, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
+ RegisterSignal(our_loop, COMSIG_PARENT_QDELETING, .proc/water_finished_moving)
+
+/obj/item/mecha_parts/mecha_equipment/extinguisher/proc/water_finished_moving(datum/move_loop/has_target/source)
+ SIGNAL_HANDLER
+ sprays_left--
+ if(!sprays_left)
+ return
+ extinguish(source.target)
/obj/item/mecha_parts/mecha_equipment/extinguisher/get_equip_info()
return "[..()] \[[src.reagents.total_volume]\]"
diff --git a/tgstation.dme b/tgstation.dme
index 1f54feeca94..51cedc64b08 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -201,6 +201,7 @@
#include "code\__DEFINES\dcs\signals\signals_mind.dm"
#include "code\__DEFINES\dcs\signals\signals_mod.dm"
#include "code\__DEFINES\dcs\signals\signals_mood.dm"
+#include "code\__DEFINES\dcs\signals\signals_moveloop.dm"
#include "code\__DEFINES\dcs\signals\signals_movetype.dm"
#include "code\__DEFINES\dcs\signals\signals_music.dm"
#include "code\__DEFINES\dcs\signals\signals_NTNet.dm"
@@ -511,7 +512,6 @@
#include "code\controllers\subsystem\skills.dm"
#include "code\controllers\subsystem\sound_loops.dm"
#include "code\controllers\subsystem\sounds.dm"
-#include "code\controllers\subsystem\spacedrift.dm"
#include "code\controllers\subsystem\spatial_gridmap.dm"
#include "code\controllers\subsystem\speech_controller.dm"
#include "code\controllers\subsystem\statpanel.dm"
@@ -530,10 +530,14 @@
#include "code\controllers\subsystem\wardrobe.dm"
#include "code\controllers\subsystem\weather.dm"
#include "code\controllers\subsystem\wiremod_composite.dm"
+#include "code\controllers\subsystem\movement\ai_movement.dm"
+#include "code\controllers\subsystem\movement\move_handler.dm"
+#include "code\controllers\subsystem\movement\movement.dm"
+#include "code\controllers\subsystem\movement\movement_types.dm"
+#include "code\controllers\subsystem\movement\spacedrift.dm"
#include "code\controllers\subsystem\processing\acid.dm"
#include "code\controllers\subsystem\processing\ai_basic_avoidance.dm"
#include "code\controllers\subsystem\processing\ai_behaviors.dm"
-#include "code\controllers\subsystem\processing\ai_movement.dm"
#include "code\controllers\subsystem\processing\antag_hud.dm"
#include "code\controllers\subsystem\processing\aura_healing.dm"
#include "code\controllers\subsystem\processing\clock_component.dm"
@@ -704,6 +708,7 @@
#include "code\datums\components\connect_loc_behalf.dm"
#include "code\datums\components\connect_range.dm"
#include "code\datums\components\construction.dm"
+#include "code\datums\components\conveyor_movement.dm"
#include "code\datums\components\cracked.dm"
#include "code\datums\components\creamed.dm"
#include "code\datums\components\cult_ritual_item.dm"
@@ -712,6 +717,7 @@
#include "code\datums\components\deadchat_control.dm"
#include "code\datums\components\dejavu.dm"
#include "code\datums\components\deployable.dm"
+#include "code\datums\components\drift.dm"
#include "code\datums\components\earprotection.dm"
#include "code\datums\components\edit_complainer.dm"
#include "code\datums\components\effect_remover.dm"
@@ -721,6 +727,7 @@
#include "code\datums\components\engraved.dm"
#include "code\datums\components\explodable.dm"
#include "code\datums\components\faction_granter.dm"
+#include "code\datums\components\force_move.dm"
#include "code\datums\components\forensics.dm"
#include "code\datums\components\fov_handler.dm"
#include "code\datums\components\fullauto.dm"