mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-04-11 06:41:39 +01:00
Adds a movement looping system, replaces inbuild procs and spacedrift with it (#62567)
* Adds a subsystem to handle automated directional movement, replaces all instances of walk_towards with it. Makes meteors and immovable rods not drift in space, and makes immovable rods more destructive. Note, I've opted not to use byond's method of moving towards something, which is effectively Move(src, get_step(src, get_dir(src, target))) as it's cringe and doesn't make a smooth line. I've replaced it with a autoupdating rise over run setup, read the code for more details * woop forgot the subsystem * Documentation, contributing.md entry, and some cleanup * Makes the moveloop datum more oop friendly, sets us up for a lot of conversions * Converts the curseblob and walk_away() to the subsystem * Changes the default for override from FALSE to TRUE * converts walk() over, still need to add a replacement proc for it, but we didn't actually have anything that used the raw proc * converts the rest of walk_to() over, nearing the end now * cleans up some errors * Fully documents everything, fills in some missing movement types, uses the power of oop to make things cleaner, and typepaths longer * Finishes the contributing.md stuff * Done * Fefaults -> Defaults, can you tell I wrote this at 1AM? * resolves bubblegum issues * Roh's suggestions Co-authored-by: Rohesie <rohesie@gmail.com> * Cleanup * Hey lemon, did you know that Destroy() lives on datums? ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh * Converts over the discrepencies created in my absense * HAHA FUCK YOU I PAY MY DUES * Whoops lost some stuff in the merge * Converts the system from seconds to deciseconds to make dealing with the api more sane * Some stuff I missed * Makes movement an inheritable subsystem type, splits the moveloop file into two, one for the subsystem, and one for the datums * Makes a subsystem that handles directing movers out to other subsystems. It's a bit bad right now, but it's a good first step. I think I'll move the move loop datum to a lazy var on mobs instead of an assoc list, don't like lists. Also makes the movement procs global, I'll move em to the /movement subsystem at some point or something like that * Converts the existing uses of the procs over to the new format * Adds support for subsystem precedence, so a type of A can override type B. General cleanup, still kinda in debug mode but it's getting better * I'll admit I'm not too familiar with this, but I think it will work * Adds starting logic so movement types "pausing" makes any sense Redoes how waiting is handled to make it based on world.time directly. I don't remember why. I think it's better this way. Adds a drifting movement type, moves space drift over to it. Needs severe work before it's ready, too much info stored and modified on the moving object, see comment Starts work on making drifting smooth * Moves almost all space drifting vars over to signals on the movement datum Properly implements glide size stuff for both the subsystem and the loops. Space drift will be smoother now. It's not perfect, but it'll work just fine for now Adds a way to override a client'd mob's glide size mid move, uses it to make entering a spacedrift look right Adds a way to delay a client move outside of just move_delay, meant to be used for long periods, and setup such that it doesn't make inputs persist Adds flags to movement loops, alongside MOVELOOP_OVERRIDE_CLIENT_CONTROL, which blocks client movements while the loop is firing, and for it's visual delay after This means you can't exit a space drift until you hit the actual wall. This feels a lot better Some general logic stuff, move() will return true/false if it succeeded or failed Adds a stop_loop() proc that's called when a move loop is no longer active Suck my nuts * Moves precedence to the loop instead of the subsystem * Moves drifting into a component, this lets me explictly block input after the move loop ends, so people can't move the moment they functionally move onto a new tile This is a bit underdeveloped currently, but that's a problem for another day Cleans up some uses of move procs, fixes runtimes in metoer and curseblob code Adds signals for stopping/starting a move loop, sending one for destroy is redundant. Moves existing event signals from the movable being acted on to the loop itself, makes more sense this way Makes the move handler return the created loop up the chain so we can register to it Fixes a logic error in loop contesting code that lead to loops never actually being removed from subsystems because they didn't know they should be. Properly changes lifetime from a time to stop, to functionally an amount of moves to complete before stopping Adds some new signals for pre/post loop process. This is to better tie into components. I decided I didn't like the idea of tying all functionality to the loops themselves The loop decides functionally how to move, components or just tied in signals can decide when/when not to move and can modify properties of the loop Making a new loop for things like atmos drift, something I'm interested in tackling in the future, seemed silly * Moves movement procs directly to the subsystem for better namespacing or whatever * Moves movement packets onto /atom/movable, no longer need the debugging I've decided to not just put their contents fully onto atom movable, since it makes debugging on live much harder, can't sdql for them anymore. Fixes a runtime in meteor code, properly this time Fixes a logic error in stop_looping Makes move manager NO_INIT, because well, it doesn't init * Commits human sin, makes Recover() work properly for movement subsystems * Fixes immovable rod orbits not always working, they were returning too early in moved and fucking up the var we use to track move count, and thus not sending a signal properly * Reworks the curseblob to use signals more, and to not use override * Missed this in the movement ss commit * Removes override, makes having a higher or equal precedence take its place * Updates documentation * Cleans up some unused defines * Nukes the unused flags option * Whoops forgot to qdel check * Removes an unused var I had for client move prevention before I started using a component * Let's do this properly * Modernizes meteor code to better match how explosions actually work currently * Some more cleanup * Cleans up effect code a little bit Nukes the effect system's sleep loop, we use movement loops instead As a part of that, instead of 1 timer per effect spawned, we react to loop failure and make it 1 timer per effect system This should reduce the amoumt of slowdown we see after mass lighting break It's not everything, we're still making a timer per spark effect, but it cuts things down significantly * Updates explosions to not sleep * Adds support for modifying a loops delay post process, makes extinguisher code suck less then it does currently, nukes some more sleeps and timer loops * Converts water tank resin over to move loops rather then sleeps, minor behavior change mind, the cooldown starts on fire rather then on land, but I think that makes more sense anyway * compile and runtime fix * Fixes some runtimes, cleans up some code, ensures feature parity when it comes to logging * Prevents resin foam from space drifting * Adds support for flags back into the system, I need it for reasons * Updates move_towards to fix some bugs and resolve some inconsistent behavior, implements a flag that makes a loop's first move start instantly * Fixes extinguishers not actually transfering any reagents * Converts sprays to the new system. This does actually minorly change behavior, in that I've changed the order of spray actions from step -> sleep -> wash to step -> wash -> sleep, but I'm not terribly torn up about it because frankly I think it feels better * Converts grav catapults over to the new system * Converts trays over to moveloops * Converts robot streaking to move loops, the other two coming soon * Compile you won't. Also fixes a behavior issue with oil streaks * Does directional step_to properly, cleans up the other two streaking types * Converts step_trigger over, not that it's actually used anywhere. Changes how stoping a move works, you need to explicitly qdel, other the step is just considered to be ignored. This will make life easier later * Adds a jps movement loop. It's a bit bloaty, id is stupid, but it'll work just fine * Makes the system support passing in a datum that's just used as extra context for the move. The hope is this makes signalizing things less of an absolute headache * Begins the conversion of ai movement datums to movement loops * These two are reasonably simple, only weird thing I'm doing is A: Not allowing target hotswapping, which I hope none is doing, and B: passing the controller into the move loop as extra context so things work properly * JPS is a bit more complex, partially because the old implementation was a bit weird. 2 major things. 1: I'm dropping what I think was a redundant behavior minimum distance check from the premove bit of logic, since I'm pretty sure it didn't do anything. 2, instead of just stoping the step in an error state like being pulled, we count it against our max move total * Audit * Moves most forced movement to the framework, adds some components to make things nicer * Implements a flag that makes the loop always operate, regardless of precedence and without impacting any other loops * Moves movement subsystems into the right folder * Hey potato what if you had two procs that did the same thing and one called the other? Wow it's useless * Merges slipping and force movement * Converys conveyors over to the system. It's a bit fragile, but I think it's totally worth it to save the sleep loop * Precedence -> Priority, cleans up some logic errors, makes priority highest to lowest instead of lowest to highest, straight cleans some code up * Makes poly and bubbles ignore spacedrift, now that precedence actually functions properly. I'm likely missing cases of this, will deal with it later * Depression, thy name is linter * Fixes linter, and hopefully fixes the runtimes in ci too * Wew * Sets sprays and extinguishers back to legacy, since people do actually seem to have noticed * Spelling errors my beloved Co-authored-by: Kylerace <kylerlumpkin1@gmail.com> * More detail, moves return descriptions * Converts transit tubes to the system? * Adds the glide size modifier. Not honestly sure that this should be default, considering how crummy it makes things look for normal walking, but it's useful as hell here * Adds a force move in dir template, actual support for fast initial steps (wtf old me) and a helper proc for setting delay * Cleans up displosal code a bit, I thought about adding it to the system but it would functionally be just 'disposal loops'. Maybe I'll make a template subtype? not sure how I want to handle stuff like this * Cleans up mob movement a bit * Let's use the controller's visual delay * Makes the resin thrower nicer, cries * Cleans up some comments, replaces an implicit world.icon_size with an explicit one, fixes up a typecheck * typecache instead of double istype. Can't do much about the !atom/movable, list would be too big I feel * hhh * bro wtf * Documents the why of SS_TICKER * Puts SSmovement on SS_TICKER. Lets us support tick steps * Cleans up the charge action. Makes it use moveloops * Fixes CI? kinda worried that this just got dropped * Converts disposal pipes to move loops. They stutter a bit more then usual as of now, hoping that's a me thing, if it's not I'ma look at uping the priority of the base subsystem * Moves the move subsystems off background, puts some on ssticker * Prevents some things that shouldn't move in space from moving in space * Documents the general form and usage of the system * Virgin one vs chad once Co-authored-by: Kylerace <kylerlumpkin1@gmail.com> * Removes unneeded check * Moves appropriate movement subsystems into SS_BACKGROUND. Removes redundant SS_KEEP_TIMINGs I do want the behavior of SS_TICKER, which at this point is tick based waits, and ignoring overtime when calculating next fire. Since honestly, these subsystems should ignore overtime in regards to next fire, the cost of moving A may be nothing compared to the cost of moving B. * Makes the MODULUS macro use floor. I knew our coders would never let me down, glad this exists, thanks ninja Fixes teleporting caused by shitty round() behavior, adds a "you hit your target" case to homing loops * Converts blood splatters to move loops, that'll do it Co-authored-by: Rohesie <rohesie@gmail.com> Co-authored-by: Kylerace <kylerlumpkin1@gmail.com>
This commit is contained in:
15
.github/guides/STANDARDS.md
vendored
15
.github/guides/STANDARDS.md
vendored
@@ -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.
|
||||
|
||||
@@ -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);\
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(): ()
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
code/__DEFINES/dcs/signals/signals_moveloop.dm
Normal file
11
code/__DEFINES/dcs/signals/signals_moveloop.dm
Normal file
@@ -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"
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -203,7 +203,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
|
||||
@@ -236,7 +236,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
|
||||
|
||||
@@ -298,7 +298,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()
|
||||
@@ -319,7 +319,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
|
||||
|
||||
@@ -364,7 +364,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()
|
||||
@@ -397,7 +397,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
|
||||
|
||||
|
||||
@@ -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]", "/")
|
||||
|
||||
@@ -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
|
||||
160
code/controllers/subsystem/movement/move_handler.dm
Normal file
160
code/controllers/subsystem/movement/move_handler.dm
Normal file
@@ -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
|
||||
51
code/controllers/subsystem/movement/movement.dm
Normal file
51
code/controllers/subsystem/movement/movement.dm
Normal file
@@ -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()
|
||||
|
||||
753
code/controllers/subsystem/movement/movement_types.dm
Normal file
753
code/controllers/subsystem/movement/movement_types.dm
Normal file
@@ -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
|
||||
5
code/controllers/subsystem/movement/spacedrift.dm
Normal file
5
code/controllers/subsystem/movement/spacedrift.dm
Normal file
@@ -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
|
||||
@@ -1,3 +1,2 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(conveyors)
|
||||
MOVEMENT_SUBSYSTEM_DEF(conveyors)
|
||||
name = "Conveyor Belts"
|
||||
wait = 0.2 SECONDS
|
||||
|
||||
@@ -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
|
||||
@@ -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("<span class='danger'>[source] slams into [living_target]!</span>", "<span class='userdanger'>[source] tramples you into the ground!</span>")
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
37
code/datums/components/conveyor_movement.dm
Normal file
37
code/datums/components/conveyor_movement.dm
Normal file
@@ -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)
|
||||
115
code/datums/components/drift.dm
Normal file
115
code/datums/components/drift.dm
Normal file
@@ -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
|
||||
38
code/datums/components/force_move.dm
Normal file
38
code/datums/components/force_move.dm
Normal file
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -132,6 +133,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)
|
||||
|
||||
@@ -581,7 +587,6 @@
|
||||
if(!. && set_dir_on_move)
|
||||
setDir(first_step_dir)
|
||||
else if (!inertia_moving)
|
||||
inertia_next_move = world.time + inertia_move_delay
|
||||
newtonian_move(direct)
|
||||
moving_diagonally = 0
|
||||
return
|
||||
@@ -648,7 +653,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()
|
||||
@@ -963,14 +967,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)
|
||||
@@ -1082,11 +1086,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -346,8 +346,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)
|
||||
@@ -359,11 +359,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()
|
||||
. = ..()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -204,48 +204,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1387,4 +1387,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -888,12 +888,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
|
||||
@@ -911,8 +912,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))
|
||||
@@ -971,7 +971,7 @@
|
||||
span_warning("You struggle as you fail to break free of [pulledby]'s grip!"), null, null, pulledby)
|
||||
to_chat(pulledby, span_danger("[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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -346,7 +346,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
|
||||
@@ -447,8 +447,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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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////////////
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -366,6 +366,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.
|
||||
*/
|
||||
@@ -460,7 +464,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
|
||||
@@ -498,7 +502,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
|
||||
@@ -522,7 +526,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
|
||||
|
||||
@@ -530,7 +534,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
|
||||
@@ -543,7 +547,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
|
||||
|
||||
@@ -551,11 +555,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
|
||||
|
||||
@@ -596,14 +600,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()
|
||||
@@ -994,7 +998,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)
|
||||
..()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
var/mob/living/L = mob //Already checked for isliving earlier
|
||||
@@ -129,6 +128,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
|
||||
@@ -147,7 +150,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)
|
||||
@@ -330,6 +336,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -104,7 +104,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
|
||||
|
||||
@@ -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] \[<a href='?src=[REF(src)];mode=1'>S</a>|<a href='?src=[REF(src)];mode=2'>P</a>\]"
|
||||
|
||||
@@ -160,6 +160,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)
|
||||
. = ..()
|
||||
@@ -180,37 +181,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]\]"
|
||||
|
||||
@@ -202,6 +202,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"
|
||||
@@ -465,7 +466,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"
|
||||
@@ -484,10 +484,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"
|
||||
@@ -658,6 +662,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"
|
||||
@@ -666,6 +671,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"
|
||||
@@ -675,6 +681,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"
|
||||
|
||||
Reference in New Issue
Block a user