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"