mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 07:46:20 +00:00
My original plan was to just implement materials into crafting so that items would inherit the materials of their components, allowing for some interesting stuff if the material flags of the item allow it. However to my dismay crafting is a pile of old tech debt, starting from the old `del_reqs` and `CheckParts` which still contain lines about old janky bandaids that are no longer in use nor reachable, up to the `customizable_reagent_holder` component which has some harddel issues when your custom food is sliced, and items used in food recipes not being deleted and instead stored inside the result with no purpose as well as other inconsistencies like stack recipes that transfer materials having counterparts in the UI that don't do that. EDIT: Several things have come up while working on this, so I apologise that it ended up changing over 100+ files. I managed to atomize some of the changes, but it's a bit tedious. EDIT: TLDR because I was told this section is too vague and there's too much going on. This PR: - Improves the dated crafting code (not the UI). - replaced `atom/CheckParts` and `crafting_recipe/on_craft_completion` with `atom/on_craft_completion`. - Reqs used in food recipes are now deleted by default and not stored inside the result (they did nothing). - Renames the customizable_reagent_holder comp and improves it (No harddels/ref issues). - Adds a unit test that tries to craft all recipes to see what's wrong (it skips some of the much more specific reqs for now). - In the unit test is also the code to make sure materials of the crafted item and a non-crafted item of the same type are roughly the same, so far only applied to food. - Some mild material/food refactoring around the fact that food item code has been changed to support materials. Improving the backbone of the crafting system. Also materials and food code. 🆑 refactor: Refactored crafting backend. Report possible pesky bugs. balance: the MEAT backpack (from the MEAT cargo pack) may be a smidge different because of code standardization. /🆑
1802 lines
74 KiB
Plaintext
1802 lines
74 KiB
Plaintext
/atom/movable
|
|
layer = OBJ_LAYER
|
|
glide_size = 8
|
|
appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
|
|
|
|
var/last_move = null
|
|
/// A list containing arguments for Moved().
|
|
VAR_PRIVATE/tmp/list/active_movement
|
|
var/anchored = FALSE
|
|
var/move_resist = MOVE_RESIST_DEFAULT
|
|
var/move_force = MOVE_FORCE_DEFAULT
|
|
var/pull_force = PULL_FORCE_DEFAULT
|
|
var/datum/thrownthing/throwing = null
|
|
var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported
|
|
var/throw_range = 7
|
|
///Max range this atom can be thrown via telekinesis
|
|
var/tk_throw_range = 10
|
|
var/mob/pulledby = null
|
|
/// What language holder type to init as
|
|
var/initial_language_holder = /datum/language_holder/atom_basic
|
|
/// Holds all languages this mob can speak and understand
|
|
VAR_PRIVATE/datum/language_holder/language_holder
|
|
/// The list of factions this atom belongs to
|
|
var/list/faction
|
|
|
|
/// Use get_default_say_verb() in say.dm instead of reading verb_say.
|
|
var/verb_say = "says"
|
|
var/verb_ask = "asks"
|
|
var/verb_exclaim = "exclaims"
|
|
var/verb_whisper = "whispers"
|
|
var/verb_sing = "sings"
|
|
var/verb_yell = "yells"
|
|
var/speech_span
|
|
///Are we moving with inertia? Mostly used as an optimization
|
|
var/inertia_moving = FALSE
|
|
///Multiplier for inertia based movement in space
|
|
var/inertia_move_multiplier = 1
|
|
///Object "weight", higher weight reduces acceleration applied to the object
|
|
var/inertia_force_weight = 1
|
|
///The last time we pushed off something
|
|
///This is a hack to get around dumb him him me scenarios
|
|
var/last_pushoff
|
|
/// 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
|
|
/// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour
|
|
var/generic_canpass = TRUE
|
|
///0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
|
|
var/moving_diagonally = 0
|
|
///attempt to resume grab after moving instead of before.
|
|
var/atom/movable/moving_from_pull
|
|
///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
|
|
/**
|
|
* an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
|
|
* each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration.
|
|
* do NOT add channels to this for little reason as it can add considerable memory usage.
|
|
*/
|
|
var/list/important_recursive_contents
|
|
///contains every client mob corresponding to every client eye in this container. lazily updated by SSparallax and is sparse:
|
|
///only the last container of a client eye has this list assuming no movement since SSparallax's last fire
|
|
var/list/client_mobs_in_contents
|
|
|
|
/// String representing the spatial grid groups we want to be held in.
|
|
/// acts as a key to the list of spatial grid contents types we exist in via SSspatial_grid.spatial_grid_categories.
|
|
/// We do it like this to prevent people trying to mutate them and to save memory on holding the lists ourselves
|
|
var/spatial_grid_key
|
|
|
|
/**
|
|
* In case you have multiple types, you automatically use the most useful one.
|
|
* IE: Skating on ice, flippers on water, flying over chasm/space, etc.
|
|
* I recommend you use the movetype_handler system and not modify this directly, especially for living mobs.
|
|
*/
|
|
var/movement_type = GROUND
|
|
|
|
var/atom/movable/pulling
|
|
var/grab_state = GRAB_PASSIVE
|
|
/// The strongest grab we can acomplish
|
|
var/max_grab = GRAB_PASSIVE
|
|
var/throwforce = 0
|
|
var/datum/component/orbiter/orbiting
|
|
|
|
///is the mob currently ascending or descending through z levels?
|
|
var/currently_z_moving
|
|
|
|
/// Either [EMISSIVE_BLOCK_NONE], [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
|
|
var/blocks_emissive = EMISSIVE_BLOCK_NONE
|
|
///Internal holder for emissive blocker object, do not use directly use blocks_emissive
|
|
var/atom/movable/render_step/emissive_blocker/em_block
|
|
|
|
///Lazylist to keep track on the sources of illumination.
|
|
var/list/affected_dynamic_lights
|
|
///Highest-intensity light affecting us, which determines our visibility.
|
|
var/affecting_dynamic_lumi = 0
|
|
|
|
/// Whether this atom should have its dir automatically changed when it moves. Setting this to FALSE allows for things such as directional windows to retain dir on moving without snowflake code all of the place.
|
|
var/set_dir_on_move = TRUE
|
|
|
|
/// The degree of thermal insulation that mobs in list/contents have from the external environment, between 0 and 1
|
|
var/contents_thermal_insulation = 0
|
|
/// The degree of pressure protection that mobs in list/contents have from the external environment, between 0 and 1
|
|
var/contents_pressure_protection = 0
|
|
|
|
/// Whether a user will face atoms on entering them with a mouse. Despite being a mob variable, it is here for performances //SKYRAT EDIT ADDITION
|
|
var/face_mouse = FALSE //SKYRAT EDIT ADDITION
|
|
|
|
/// The voice that this movable makes when speaking
|
|
var/voice
|
|
|
|
/// The pitch adjustment that this movable uses when speaking.
|
|
var/pitch = 0
|
|
|
|
/// Datum that keeps all data related to zero-g drifting and handles related code/comsigs
|
|
var/datum/drift_handler/drift_handler
|
|
|
|
/// The filter to apply to the voice when processing the TTS audio message.
|
|
var/voice_filter = ""
|
|
|
|
/// Set to anything other than "" to activate the silicon voice effect for TTS messages.
|
|
var/tts_silicon_voice_effect = ""
|
|
|
|
/// Value used to increment ex_act() if reactionary_explosions is on
|
|
/// How much we as a source block explosions by
|
|
/// Will not automatically apply to the turf below you, you need to apply /datum/element/block_explosives in conjunction with this
|
|
var/explosion_block = 0
|
|
|
|
// Access levels, used in modules\jobs\access.dm
|
|
/// List of accesses needed to use this object: The user must possess all accesses in this list in order to use the object.
|
|
/// Example: If req_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have both ACCESS_ENGINE and ACCESS_CE in order to use the object.
|
|
var/list/req_access
|
|
/// List of accesses needed to use this object: The user must possess at least one access in this list in order to use the object.
|
|
/// Example: If req_one_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have either ACCESS_ENGINE or ACCESS_CE in order to use the object.
|
|
var/list/req_one_access
|
|
|
|
/mutable_appearance/emissive_blocker
|
|
|
|
/mutable_appearance/emissive_blocker/New()
|
|
. = ..()
|
|
// Need to do this here because it's overridden by the parent call
|
|
// This is a microop which is the sole reason why this child exists, because its static this is a really cheap way to set color without setting or checking it every time we create an atom
|
|
color = EM_BLOCK_COLOR
|
|
|
|
/atom/movable/Initialize(mapload, ...)
|
|
. = ..()
|
|
#ifdef UNIT_TESTS
|
|
if(explosion_block && !HAS_TRAIT(src, TRAIT_BLOCKING_EXPLOSIVES))
|
|
stack_trace("[type] blocks explosives, but does not have the managing element applied")
|
|
#endif
|
|
|
|
#if EMISSIVE_BLOCK_GENERIC != 0
|
|
#error EMISSIVE_BLOCK_GENERIC is expected to be 0 to facilitate a weird optimization hack where we rely on it being the most common.
|
|
#error Read the comment in code/game/atoms_movable.dm for details.
|
|
#endif
|
|
|
|
// This one is incredible.
|
|
// `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
|
|
// From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
|
|
// So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
|
|
// If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
|
|
// This saves several hundred milliseconds of init time.
|
|
if (blocks_emissive)
|
|
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
|
|
render_target = ref(src)
|
|
em_block = new(null, src)
|
|
overlays += em_block
|
|
if(managed_overlays)
|
|
if(islist(managed_overlays))
|
|
managed_overlays += em_block
|
|
else
|
|
managed_overlays = list(managed_overlays, em_block)
|
|
else
|
|
managed_overlays = em_block
|
|
else
|
|
var/static/mutable_appearance/emissive_blocker/blocker = new()
|
|
blocker.icon = icon
|
|
blocker.icon_state = icon_state
|
|
blocker.dir = dir
|
|
blocker.appearance_flags = appearance_flags | EMISSIVE_APPEARANCE_FLAGS
|
|
blocker.plane = GET_NEW_PLANE(EMISSIVE_PLANE, PLANE_TO_OFFSET(plane)) // Takes a light path through the normal macro for a microop
|
|
// Ok so this is really cursed, but I want to set with this blocker cheaply while
|
|
// Still allowing it to be removed from the overlays list later
|
|
// So I'm gonna flatten it, then insert the flattened overlay into overlays AND the managed overlays list, directly
|
|
// I'm sorry
|
|
var/mutable_appearance/flat = blocker.appearance
|
|
overlays += flat
|
|
if(managed_overlays)
|
|
if(islist(managed_overlays))
|
|
managed_overlays += flat
|
|
else
|
|
managed_overlays = list(managed_overlays, flat)
|
|
else
|
|
managed_overlays = flat
|
|
|
|
if(opacity)
|
|
AddElement(/datum/element/light_blocking)
|
|
switch(light_system)
|
|
if(OVERLAY_LIGHT)
|
|
AddComponent(/datum/component/overlay_lighting)
|
|
if(OVERLAY_LIGHT_DIRECTIONAL)
|
|
AddComponent(/datum/component/overlay_lighting, is_directional = TRUE)
|
|
if(OVERLAY_LIGHT_BEAM)
|
|
AddComponent(/datum/component/overlay_lighting, is_directional = TRUE, is_beam = TRUE)
|
|
|
|
/atom/movable/Destroy(force)
|
|
QDEL_NULL(language_holder)
|
|
QDEL_NULL(em_block)
|
|
QDEL_NULL(drift_handler)
|
|
|
|
unbuckle_all_mobs(force = TRUE)
|
|
|
|
if(loc)
|
|
//Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
|
|
if(((can_atmos_pass == ATMOS_PASS_DENSITY && density) || can_atmos_pass == ATMOS_PASS_NO) && isturf(loc))
|
|
can_atmos_pass = ATMOS_PASS_YES
|
|
air_update_turf(TRUE, FALSE)
|
|
|
|
if(opacity)
|
|
RemoveElement(/datum/element/light_blocking)
|
|
|
|
invisibility = INVISIBILITY_ABSTRACT
|
|
|
|
if(pulledby)
|
|
pulledby.stop_pulling()
|
|
if(pulling)
|
|
stop_pulling()
|
|
|
|
if(orbiting)
|
|
orbiting.end_orbit(src)
|
|
orbiting = null
|
|
|
|
if(move_packet)
|
|
if(!QDELETED(move_packet))
|
|
qdel(move_packet)
|
|
move_packet = null
|
|
|
|
if(spatial_grid_key)
|
|
SSspatial_grid.force_remove_from_grid(src)
|
|
|
|
LAZYNULL(client_mobs_in_contents)
|
|
|
|
#ifndef DISABLE_DREAMLUAU
|
|
// These lists cease existing when src does, so we need to clear any lua refs to them that exist.
|
|
DREAMLUAU_CLEAR_REF_USERDATA(vis_contents)
|
|
DREAMLUAU_CLEAR_REF_USERDATA(vis_locs)
|
|
#endif
|
|
|
|
. = ..()
|
|
|
|
for(var/movable_content in contents)
|
|
qdel(movable_content)
|
|
|
|
moveToNullspace()
|
|
|
|
//This absolutely must be after moveToNullspace()
|
|
//We rely on Entered and Exited to manage this list, and the copy of this list that is on any /atom/movable "Containers"
|
|
//If we clear this before the nullspace move, a ref to this object will be hung in any of its movable containers
|
|
LAZYNULL(important_recursive_contents)
|
|
|
|
|
|
vis_locs = null //clears this atom out of all viscontents
|
|
|
|
// Checking length(vis_contents) before cutting has significant speed benefits
|
|
if (length(vis_contents))
|
|
vis_contents.Cut()
|
|
|
|
/atom/movable/proc/update_emissive_block()
|
|
// This one is incredible.
|
|
// `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
|
|
// From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
|
|
// So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
|
|
// If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
|
|
// This saves several hundred milliseconds of init time.
|
|
if (blocks_emissive)
|
|
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
|
|
if(em_block)
|
|
SET_PLANE(em_block, EMISSIVE_PLANE, src)
|
|
else if(!QDELETED(src))
|
|
render_target = ref(src)
|
|
em_block = new(null, src)
|
|
return em_block
|
|
// Implied else if (blocks_emissive == EMISSIVE_BLOCK_NONE) -> return
|
|
// EMISSIVE_BLOCK_GENERIC == 0
|
|
else
|
|
return fast_emissive_blocker(src)
|
|
|
|
/// Generates a space underlay for a turf
|
|
/// This provides proper lighting support alongside just looking nice
|
|
/// Accepts the appearance to make "spaceish", and the turf we're doing this for
|
|
/proc/generate_space_underlay(mutable_appearance/underlay_appearance, turf/generate_for)
|
|
underlay_appearance.icon = 'icons/turf/space.dmi'
|
|
underlay_appearance.icon_state = "space"
|
|
SET_PLANE(underlay_appearance, PLANE_SPACE, generate_for)
|
|
if(!generate_for.render_target)
|
|
generate_for.render_target = ref(generate_for)
|
|
var/atom/movable/render_step/emissive_blocker/em_block = new(null, generate_for)
|
|
underlay_appearance.overlays += em_block
|
|
// We used it because it's convienient and easy, but it's gotta go now or it'll hang refs
|
|
QDEL_NULL(em_block)
|
|
// We're gonna build a light, and mask it with the base turf's appearance
|
|
// grab a 32x32 square of it
|
|
// I would like to use GLOB.starbright_overlays here
|
|
// But that breaks down for... some? reason. I think receiving a render relay breaks keep_together or something
|
|
// So we're just gonna accept that this'll break with starlight color changing. hardly matters since this is really only for offset stuff, but I'd love to fix it someday
|
|
var/mutable_appearance/light = new(GLOB.starlight_objects[GET_TURF_PLANE_OFFSET(generate_for) + 1])
|
|
light.render_target = ""
|
|
light.appearance_flags |= KEEP_TOGETHER
|
|
// Now apply a copy of the turf, set to multiply
|
|
// This will multiply against our light, so we only light up the bits that aren't "on" the wall
|
|
var/mutable_appearance/mask = new(generate_for.appearance)
|
|
mask.blend_mode = BLEND_MULTIPLY
|
|
mask.render_target = ""
|
|
mask.pixel_x = 0
|
|
mask.pixel_y = 0
|
|
mask.pixel_w = 0
|
|
mask.pixel_z = 0
|
|
mask.transform = null
|
|
mask.underlays = list() // Begone foul lighting overlay
|
|
SET_PLANE(mask, FLOAT_PLANE, generate_for)
|
|
mask.layer = FLOAT_LAYER
|
|
|
|
// Bump the opacity to full, will this work?
|
|
mask.color = list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,255, 0,0,0,0)
|
|
light.overlays += mask
|
|
underlay_appearance.overlays += light
|
|
|
|
// Now, we're going to make a copy of the mask. Instead of using it to multiply against our light
|
|
// We're going to use it to multiply against the turf lighting plane. Going to mask away the turf light
|
|
// And rely on LIGHTING_MASK_LAYER to ensure we mask ONLY that bit
|
|
var/mutable_appearance/turf_mask = new(mask.appearance)
|
|
SET_PLANE(turf_mask, LIGHTING_PLANE, generate_for)
|
|
turf_mask.layer = LIGHTING_MASK_LAYER
|
|
/// Any color becomes white. Anything else is black, and it's fully opaque
|
|
/// Ought to work
|
|
turf_mask.color = list(255,255,255,0, 255,255,255,0, 255,255,255,0, 0,0,0,0, 0,0,0,255)
|
|
underlay_appearance.overlays += turf_mask
|
|
|
|
/atom/movable/update_overlays()
|
|
var/list/overlays = ..()
|
|
var/emissive_block = update_emissive_block()
|
|
if(emissive_block)
|
|
// Emissive block should always go at the beginning of the list
|
|
overlays.Insert(1, emissive_block)
|
|
return overlays
|
|
|
|
/atom/movable/proc/onZImpact(turf/impacted_turf, levels, impact_flags = NONE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(!(impact_flags & ZIMPACT_NO_MESSAGE))
|
|
visible_message(
|
|
span_danger("[src] crashes into [impacted_turf]!"),
|
|
span_userdanger("You crash into [impacted_turf]!"),
|
|
)
|
|
if(!(impact_flags & ZIMPACT_NO_SPIN))
|
|
INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2)
|
|
SEND_SIGNAL(src, COMSIG_ATOM_ON_Z_IMPACT, impacted_turf, levels)
|
|
return TRUE
|
|
|
|
/*
|
|
* Attempts to move using zMove if direction is UP or DOWN, step if not
|
|
*
|
|
* Args:
|
|
* direction: The direction to go
|
|
* z_move_flags: bitflags used for checks in zMove and can_z_move
|
|
*/
|
|
/atom/movable/proc/try_step_multiz(direction, z_move_flags = ZMOVE_FLIGHT_FLAGS)
|
|
if(direction == UP || direction == DOWN)
|
|
return zMove(direction, null, z_move_flags)
|
|
return step(src, direction)
|
|
|
|
/*
|
|
* The core multi-z movement proc. Used to move a movable through z levels.
|
|
* If target is null, it'll be determined by the can_z_move proc, which can potentially return null if
|
|
* conditions aren't met (see z_move_flags defines in __DEFINES/movement.dm for info) or if dir isn't set.
|
|
* Bear in mind you don't need to set both target and dir when calling this proc, but at least one or two.
|
|
* This will set the currently_z_moving to CURRENTLY_Z_MOVING_GENERIC if unset, and then clear it after
|
|
* Forcemove().
|
|
*
|
|
*
|
|
* Args:
|
|
* * dir: the direction to go, UP or DOWN, only relevant if target is null.
|
|
* * target: The target turf to move the src to. Set by can_z_move() if null.
|
|
* * z_move_flags: bitflags used for various checks in both this proc and can_z_move(). See __DEFINES/movement.dm.
|
|
*/
|
|
/atom/movable/proc/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS)
|
|
if(!target)
|
|
target = can_z_move(dir, get_turf(src), null, z_move_flags)
|
|
if(!target)
|
|
set_currently_z_moving(FALSE, TRUE)
|
|
return FALSE
|
|
|
|
var/list/moving_movs = get_z_move_affected(z_move_flags)
|
|
|
|
for(var/atom/movable/movable as anything in moving_movs)
|
|
movable.currently_z_moving = currently_z_moving || CURRENTLY_Z_MOVING_GENERIC
|
|
movable.forceMove(target)
|
|
movable.set_currently_z_moving(FALSE, TRUE)
|
|
// This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range.
|
|
if(z_move_flags & ZMOVE_CHECK_PULLS)
|
|
for(var/atom/movable/moved_mov as anything in moving_movs)
|
|
if(z_move_flags & ZMOVE_CHECK_PULLEDBY && moved_mov.pulledby && (moved_mov.z != moved_mov.pulledby.z || get_dist(moved_mov, moved_mov.pulledby) > 1))
|
|
moved_mov.pulledby.stop_pulling()
|
|
if(z_move_flags & ZMOVE_CHECK_PULLING)
|
|
moved_mov.check_pulling(TRUE)
|
|
return TRUE
|
|
|
|
/// Returns a list of movables that should also be affected when src moves through zlevels, and src.
|
|
/atom/movable/proc/get_z_move_affected(z_move_flags)
|
|
. = list(src)
|
|
if(buckled_mobs)
|
|
. |= buckled_mobs
|
|
if(!(z_move_flags & ZMOVE_INCLUDE_PULLED))
|
|
return
|
|
for(var/mob/living/buckled as anything in buckled_mobs)
|
|
if(buckled.pulling)
|
|
. |= buckled.pulling
|
|
if(pulling)
|
|
. |= pulling
|
|
if (pulling.buckled_mobs)
|
|
. |= pulling.buckled_mobs
|
|
|
|
//makes conga lines work with ladders and flying up and down; checks if the guy you are pulling is pulling someone,
|
|
//then uses recursion to run the same function again
|
|
if (pulling.pulling)
|
|
. |= pulling.pulling.get_z_move_affected(z_move_flags)
|
|
|
|
/**
|
|
* Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so.
|
|
* Args:
|
|
* * direction: the direction to go, UP or DOWN, only relevant if target is null.
|
|
* * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source.
|
|
* * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm.
|
|
* * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels.
|
|
*/
|
|
/atom/movable/proc/can_z_move(direction, turf/start, turf/destination, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider)
|
|
if(!start)
|
|
start = get_turf(src)
|
|
if(!start)
|
|
return FALSE
|
|
if(!direction)
|
|
if(!destination)
|
|
return FALSE
|
|
direction = get_dir_multiz(start, destination)
|
|
if(direction != UP && direction != DOWN)
|
|
return FALSE
|
|
if(!destination)
|
|
destination = get_step_multiz(start, direction)
|
|
if(!destination)
|
|
if(z_move_flags & ZMOVE_FEEDBACK)
|
|
to_chat(rider || src, span_warning("There's nowhere to go in that direction!"))
|
|
return FALSE
|
|
if(SEND_SIGNAL(src, COMSIG_CAN_Z_MOVE, start, destination) & COMPONENT_CANT_Z_MOVE)
|
|
return FALSE
|
|
if(z_move_flags & ZMOVE_FALL_CHECKS && (throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start)))
|
|
return FALSE
|
|
if(z_move_flags & ZMOVE_CAN_FLY_CHECKS && !(movement_type & (FLYING|FLOATING)) && has_gravity(start))
|
|
if(z_move_flags & ZMOVE_FEEDBACK)
|
|
if(rider)
|
|
to_chat(rider, span_warning("[src] [p_are()] incapable of flight."))
|
|
else
|
|
to_chat(src, span_warning("You are not Superman."))
|
|
return FALSE
|
|
if((!(z_move_flags & ZMOVE_IGNORE_OBSTACLES) && !(start.zPassOut(direction) && destination.zPassIn(direction))) || (!(z_move_flags & ZMOVE_ALLOW_ANCHORED) && anchored))
|
|
if(z_move_flags & ZMOVE_FEEDBACK)
|
|
to_chat(rider || src, span_warning("You couldn't move there!"))
|
|
return FALSE
|
|
return destination //used by some child types checks and zMove()
|
|
|
|
/atom/movable/vv_edit_var(var_name, var_value)
|
|
var/static/list/banned_edits = list(NAMEOF_STATIC(src, step_x) = TRUE, NAMEOF_STATIC(src, step_y) = TRUE, NAMEOF_STATIC(src, step_size) = TRUE, NAMEOF_STATIC(src, bounds) = TRUE)
|
|
var/static/list/careful_edits = list(NAMEOF_STATIC(src, bound_x) = TRUE, NAMEOF_STATIC(src, bound_y) = TRUE, NAMEOF_STATIC(src, bound_width) = TRUE, NAMEOF_STATIC(src, bound_height) = TRUE)
|
|
var/static/list/not_falsey_edits = list(NAMEOF_STATIC(src, bound_width) = TRUE, NAMEOF_STATIC(src, bound_height) = TRUE)
|
|
if(banned_edits[var_name])
|
|
return FALSE //PLEASE no.
|
|
if(careful_edits[var_name] && (var_value % ICON_SIZE_ALL) != 0)
|
|
return FALSE
|
|
if(not_falsey_edits[var_name] && !var_value)
|
|
return FALSE
|
|
|
|
switch(var_name)
|
|
if(NAMEOF(src, x))
|
|
var/turf/current_turf = locate(var_value, y, z)
|
|
if(current_turf)
|
|
admin_teleport(current_turf)
|
|
return TRUE
|
|
return FALSE
|
|
if(NAMEOF(src, y))
|
|
var/turf/T = locate(x, var_value, z)
|
|
if(T)
|
|
admin_teleport(T)
|
|
return TRUE
|
|
return FALSE
|
|
if(NAMEOF(src, z))
|
|
var/turf/T = locate(x, y, var_value)
|
|
if(T)
|
|
admin_teleport(T)
|
|
return TRUE
|
|
return FALSE
|
|
if(NAMEOF(src, loc))
|
|
if(isatom(var_value) || isnull(var_value))
|
|
admin_teleport(var_value)
|
|
return TRUE
|
|
return FALSE
|
|
if(NAMEOF(src, anchored))
|
|
set_anchored(var_value)
|
|
. = TRUE
|
|
if(NAMEOF(src, pulledby))
|
|
set_pulledby(var_value)
|
|
. = TRUE
|
|
if(NAMEOF(src, glide_size))
|
|
set_glide_size(var_value)
|
|
. = TRUE
|
|
// THE BUBBER EDIT ADDITION BEGIN - BLOOPER
|
|
if(NAMEOF(src, blooper)) // Sorry, Vishenka.
|
|
if(isfile(var_value))
|
|
blooper = sound(var_value) //bark() expects vocal_bark to already be a sound datum, for performance reasons. adminbus QoL!
|
|
. = TRUE
|
|
// THE BUBBER EDIT ADDITION END
|
|
if(!isnull(.))
|
|
datum_flags |= DF_VAR_EDITED
|
|
return
|
|
|
|
return ..()
|
|
|
|
|
|
/atom/movable/proc/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE)
|
|
if(QDELETED(pulled_atom))
|
|
return FALSE
|
|
if(!(pulled_atom.can_be_pulled(src, force)))
|
|
return FALSE
|
|
|
|
// If we're pulling something then drop what we're currently pulling and pull this instead.
|
|
if(pulling)
|
|
if(state == 0)
|
|
stop_pulling()
|
|
return FALSE
|
|
// Are we trying to pull something we are already pulling? Then enter grab cycle and end.
|
|
if(pulled_atom == pulling)
|
|
setGrabState(state)
|
|
if(istype(pulled_atom,/mob/living))
|
|
var/mob/living/pulled_mob = pulled_atom
|
|
pulled_mob.grabbedby(src)
|
|
return TRUE
|
|
stop_pulling()
|
|
|
|
if(pulled_atom.pulledby)
|
|
log_combat(pulled_atom, pulled_atom.pulledby, "pulled from", src)
|
|
pulled_atom.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
|
|
pulling = pulled_atom
|
|
pulled_atom.set_pulledby(src)
|
|
SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, pulled_atom, state, force)
|
|
setGrabState(state)
|
|
if(ismob(pulled_atom))
|
|
var/mob/pulled_mob = pulled_atom
|
|
log_combat(src, pulled_mob, "grabbed", addition="passive grab")
|
|
if(!supress_message)
|
|
pulled_mob.visible_message(span_warning("[src] grabs [pulled_mob] passively."), \
|
|
span_danger("[src] grabs you passively."))
|
|
return TRUE
|
|
|
|
/atom/movable/proc/stop_pulling()
|
|
if(!pulling)
|
|
return
|
|
pulling.set_pulledby(null)
|
|
setGrabState(GRAB_PASSIVE)
|
|
var/atom/movable/old_pulling = pulling
|
|
pulling = null
|
|
SEND_SIGNAL(old_pulling, COMSIG_ATOM_NO_LONGER_PULLED, src)
|
|
SEND_SIGNAL(src, COMSIG_ATOM_NO_LONGER_PULLING, old_pulling)
|
|
|
|
///Reports the event of the change in value of the pulledby variable.
|
|
/atom/movable/proc/set_pulledby(new_pulledby)
|
|
if(new_pulledby == pulledby)
|
|
return FALSE //null signals there was a change, be sure to return FALSE if none happened here.
|
|
. = pulledby
|
|
pulledby = new_pulledby
|
|
|
|
|
|
/atom/movable/proc/Move_Pulled(atom/moving_atom)
|
|
if(!pulling)
|
|
return FALSE
|
|
if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling))
|
|
stop_pulling()
|
|
return FALSE
|
|
if(isliving(pulling))
|
|
var/mob/living/pulling_mob = pulling
|
|
if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
|
|
stop_pulling()
|
|
return FALSE
|
|
if(moving_atom == loc && pulling.density)
|
|
return FALSE
|
|
var/move_dir = get_dir(pulling.loc, moving_atom)
|
|
if(!Process_Spacemove(move_dir))
|
|
return FALSE
|
|
pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size)
|
|
return TRUE
|
|
|
|
/mob/living/Move_Pulled(atom/moving_atom)
|
|
. = ..()
|
|
if(!. || !isliving(moving_atom))
|
|
return
|
|
var/mob/living/pulled_mob = moving_atom
|
|
set_pull_offsets(pulled_mob, grab_state, animate = FALSE)
|
|
|
|
/**
|
|
* Checks if the pulling and pulledby should be stopped because they're out of reach.
|
|
* If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs.
|
|
*/
|
|
/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE)
|
|
if(pulling)
|
|
if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed))
|
|
stop_pulling()
|
|
else if(!isturf(loc))
|
|
stop_pulling()
|
|
else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
|
|
log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.")
|
|
stop_pulling()
|
|
else if(pulling.anchored || pulling.move_resist > move_force)
|
|
stop_pulling()
|
|
if(!only_pulling && pulledby && moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, pulledby) > 1 || (z != pulledby.z && !z_allowed))) //separated from our puller and not in the middle of a diagonal move.
|
|
pulledby.stop_pulling()
|
|
|
|
/atom/movable/proc/set_glide_size(target = 8)
|
|
if (HAS_TRAIT(src, TRAIT_NO_GLIDE))
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
|
|
glide_size = target
|
|
|
|
for(var/mob/buckled_mob as anything in buckled_mobs)
|
|
buckled_mob.set_glide_size(target)
|
|
|
|
/**
|
|
* meant for movement with zero side effects. only use for objects that are supposed to move "invisibly" (like eye mobs or ghosts)
|
|
* if you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this
|
|
* most of the time you want forceMove()
|
|
*/
|
|
/atom/movable/proc/abstract_move(atom/new_loc)
|
|
RESOLVE_ACTIVE_MOVEMENT // This should NEVER happen, but, just in case...
|
|
var/atom/old_loc = loc
|
|
var/direction = get_dir(old_loc, new_loc)
|
|
loc = new_loc
|
|
Moved(old_loc, direction, TRUE, momentum_change = FALSE)
|
|
|
|
////////////////////////////////////////
|
|
// Here's where we rewrite how byond handles movement except slightly different
|
|
// To be removed on step_ conversion
|
|
// All this work to prevent a second bump
|
|
/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, update_dir = TRUE)
|
|
. = FALSE
|
|
if(!newloc || newloc == loc)
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_ATTEMPTED_MOVE, newloc, direction)
|
|
// A mid-movement... movement... occurred, resolve that first.
|
|
RESOLVE_ACTIVE_MOVEMENT
|
|
|
|
if(!direction)
|
|
direction = get_dir(src, newloc)
|
|
|
|
if(set_dir_on_move && dir != direction && update_dir && !face_mouse) // SKYRAT EDIT - && !face_mouse
|
|
setDir(direction)
|
|
|
|
var/is_multi_tile_object = is_multi_tile_object(src)
|
|
|
|
var/list/old_locs
|
|
if(is_multi_tile_object && isturf(loc))
|
|
old_locs = locs // locs is a special list, this is effectively the same as .Copy() but with less steps
|
|
for(var/atom/exiting_loc as anything in old_locs)
|
|
if(!exiting_loc.Exit(src, direction))
|
|
return
|
|
else
|
|
if(!loc.Exit(src, direction))
|
|
return
|
|
|
|
var/list/new_locs
|
|
if(is_multi_tile_object && isturf(newloc))
|
|
var/dx = newloc.x
|
|
var/dy = newloc.y
|
|
var/dz = newloc.z
|
|
new_locs = block(
|
|
dx, dy, dz,
|
|
dx + ceil(bound_width / 32), dy + ceil(bound_height / 32), dz
|
|
) // If this is a multi-tile object then we need to predict the new locs and check if they allow our entrance.
|
|
for(var/atom/entering_loc as anything in new_locs)
|
|
if(!entering_loc.Enter(src))
|
|
return
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, entering_loc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
|
|
return
|
|
else // Else just try to enter the single destination.
|
|
if(!newloc.Enter(src))
|
|
return
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
|
|
return
|
|
|
|
// Past this is the point of no return
|
|
var/atom/oldloc = loc
|
|
var/area/oldarea = get_area(oldloc)
|
|
var/area/newarea = get_area(newloc)
|
|
|
|
SET_ACTIVE_MOVEMENT(oldloc, direction, FALSE, old_locs)
|
|
loc = newloc
|
|
|
|
. = TRUE
|
|
|
|
if(old_locs) // This condition will only be true if it is a multi-tile object.
|
|
for(var/atom/exited_loc as anything in (old_locs - new_locs))
|
|
exited_loc.Exited(src, direction)
|
|
else // Else there's just one loc to be exited.
|
|
oldloc.Exited(src, direction)
|
|
if(oldarea != newarea)
|
|
oldarea.Exited(src, direction)
|
|
|
|
if(new_locs) // Same here, only if multi-tile.
|
|
for(var/atom/entered_loc as anything in (new_locs - old_locs))
|
|
entered_loc.Entered(src, oldloc, old_locs)
|
|
else
|
|
newloc.Entered(src, oldloc, old_locs)
|
|
if(oldarea != newarea)
|
|
newarea.Entered(src, oldarea)
|
|
|
|
RESOLVE_ACTIVE_MOVEMENT
|
|
|
|
////////////////////////////////////////
|
|
|
|
/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
|
|
var/atom/movable/pullee = pulling
|
|
var/turf/current_turf = loc
|
|
if(!moving_from_pull)
|
|
check_pulling(z_allowed = TRUE)
|
|
if(!loc || !newloc)
|
|
return FALSE
|
|
var/atom/oldloc = loc
|
|
//Early override for some cases like diagonal movement
|
|
if(glide_size_override && glide_size != glide_size_override)
|
|
set_glide_size(glide_size_override)
|
|
|
|
if(loc != newloc)
|
|
if (!(direct & (direct - 1))) //Cardinal move
|
|
. = ..()
|
|
else //Diagonal move, split it into cardinal moves
|
|
moving_diagonally = FIRST_DIAG_STEP
|
|
var/first_step_dir
|
|
// The `&& moving_diagonally` checks are so that a forceMove taking
|
|
// place due to a Crossed, Bumped, etc. call will interrupt
|
|
// the second half of the diagonal movement, or the second attempt
|
|
// at a first half if step() fails because we hit something.
|
|
if (direct & NORTH)
|
|
if (direct & EAST)
|
|
if (step(src, NORTH) && moving_diagonally)
|
|
first_step_dir = NORTH
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, EAST)
|
|
else if (moving_diagonally && step(src, EAST))
|
|
first_step_dir = EAST
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, NORTH)
|
|
else if (direct & WEST)
|
|
if (step(src, NORTH) && moving_diagonally)
|
|
first_step_dir = NORTH
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, WEST)
|
|
else if (moving_diagonally && step(src, WEST))
|
|
first_step_dir = WEST
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, NORTH)
|
|
else if (direct & SOUTH)
|
|
if (direct & EAST)
|
|
if (step(src, SOUTH) && moving_diagonally)
|
|
first_step_dir = SOUTH
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, EAST)
|
|
else if (moving_diagonally && step(src, EAST))
|
|
first_step_dir = EAST
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, SOUTH)
|
|
else if (direct & WEST)
|
|
if (step(src, SOUTH) && moving_diagonally)
|
|
first_step_dir = SOUTH
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, WEST)
|
|
else if (moving_diagonally && step(src, WEST))
|
|
first_step_dir = WEST
|
|
moving_diagonally = SECOND_DIAG_STEP
|
|
. = step(src, SOUTH)
|
|
if(moving_diagonally == SECOND_DIAG_STEP)
|
|
if(!. && set_dir_on_move && update_dir && !face_mouse) // SKYRAT EDIT CHANGE - && !face_mouse
|
|
setDir(first_step_dir)
|
|
else if(!inertia_moving)
|
|
newtonian_move(dir2angle(direct))
|
|
if(client_mobs_in_contents)
|
|
update_parallax_contents()
|
|
moving_diagonally = 0
|
|
return
|
|
|
|
if(!loc || (loc == oldloc && oldloc != newloc))
|
|
last_move = 0
|
|
set_currently_z_moving(FALSE, TRUE)
|
|
return
|
|
|
|
if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
|
|
if(pulling.anchored)
|
|
stop_pulling()
|
|
else
|
|
//puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus.
|
|
if(!pulling.pulling?.moving_from_pull)
|
|
var/pull_dir = get_dir(pulling, src)
|
|
var/target_turf = current_turf
|
|
|
|
// Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls.
|
|
// You may wonder why we're not just forcemoving the pulling movable and regrabbing it.
|
|
// The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines.
|
|
if(pulling.z != z)
|
|
target_turf = get_step(pulling, get_dir(pulling, current_turf))
|
|
|
|
if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1)
|
|
pulling.move_from_pull(src, target_turf, glide_size)
|
|
if (pulledby)
|
|
if (pulledby.currently_z_moving)
|
|
check_pulling(z_allowed = TRUE)
|
|
//don't call check_pulling() here at all if there is a pulledby that is not currently z moving
|
|
//because it breaks stair conga lines, for some fucking reason.
|
|
//it's fine because the pull will be checked when this whole proc is called by the mob doing the pulling anyways
|
|
else
|
|
check_pulling()
|
|
|
|
//glide_size strangely enough can change mid movement animation and update correctly while the animation is playing
|
|
//This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs.
|
|
if(glide_size_override)
|
|
set_glide_size(glide_size_override)
|
|
|
|
last_move = direct
|
|
|
|
if(set_dir_on_move && dir != direct && update_dir && !face_mouse) // SKYRAT EDIT CHANGE - && !face_mouse)
|
|
setDir(direct)
|
|
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
|
|
. = FALSE
|
|
|
|
if(currently_z_moving)
|
|
if(. && loc == newloc)
|
|
var/turf/pitfall = get_turf(src)
|
|
pitfall.zFall(src, falling_from_move = TRUE)
|
|
else
|
|
set_currently_z_moving(FALSE, TRUE)
|
|
|
|
/// Called when src is being moved to a target turf because another movable (puller) is moving around.
|
|
/atom/movable/proc/move_from_pull(atom/movable/puller, turf/target_turf, glide_size_override)
|
|
moving_from_pull = puller
|
|
Move(target_turf, get_dir(src, target_turf), glide_size_override)
|
|
moving_from_pull = null
|
|
|
|
/**
|
|
* Called after a successful Move(). By this point, we've already moved.
|
|
* Arguments:
|
|
* * old_loc is the location prior to the move. Can be null to indicate nullspace.
|
|
* * movement_dir is the direction the movement took place. Can be NONE if it was some sort of teleport.
|
|
* * The forced flag indicates whether this was a forced move, which skips many checks of regular movement.
|
|
* * The old_locs is an optional argument, in case the moved movable was present in multiple locations before the movement.
|
|
* * momentum_change represents whether this movement is due to a "new" force if TRUE or an already "existing" force if FALSE
|
|
**/
|
|
/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
if (!moving_diagonally && !inertia_moving && momentum_change && movement_dir)
|
|
newtonian_move(dir2angle(movement_dir))
|
|
// If we ain't moving diagonally right now, update our parallax
|
|
// We don't do this all the time because diag movements should trigger one call to this, not two
|
|
// Waste of cpu time, and it fucks the animate
|
|
if (!moving_diagonally && client_mobs_in_contents)
|
|
update_parallax_contents()
|
|
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, movement_dir, forced, old_locs, momentum_change)
|
|
|
|
if(old_loc)
|
|
SEND_SIGNAL(old_loc, COMSIG_ATOM_ABSTRACT_EXITED, src, movement_dir)
|
|
if(loc)
|
|
SEND_SIGNAL(loc, COMSIG_ATOM_ABSTRACT_ENTERED, src, old_loc, old_locs)
|
|
|
|
var/turf/old_turf = get_turf(old_loc)
|
|
var/turf/new_turf = get_turf(src)
|
|
|
|
if (old_turf?.z != new_turf?.z)
|
|
var/same_z_layer = (GET_TURF_PLANE_OFFSET(old_turf) == GET_TURF_PLANE_OFFSET(new_turf))
|
|
on_changed_z_level(old_turf, new_turf, same_z_layer)
|
|
|
|
if(HAS_SPATIAL_GRID_CONTENTS(src))
|
|
if(old_turf && new_turf && (old_turf.z != new_turf.z \
|
|
|| GET_SPATIAL_INDEX(old_turf.x) != GET_SPATIAL_INDEX(new_turf.x) \
|
|
|| GET_SPATIAL_INDEX(old_turf.y) != GET_SPATIAL_INDEX(new_turf.y)))
|
|
|
|
SSspatial_grid.exit_cell(src, old_turf)
|
|
SSspatial_grid.enter_cell(src, new_turf)
|
|
|
|
else if(old_turf && !new_turf)
|
|
SSspatial_grid.exit_cell(src, old_turf)
|
|
|
|
else if(new_turf && !old_turf)
|
|
SSspatial_grid.enter_cell(src, new_turf)
|
|
|
|
return TRUE
|
|
|
|
// Make sure you know what you're doing if you call this
|
|
// You probably want CanPass()
|
|
/atom/movable/Cross(atom/movable/crossed_atom)
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, crossed_atom) & COMPONENT_BLOCK_CROSS)
|
|
return FALSE
|
|
if(SEND_SIGNAL(crossed_atom, COMSIG_MOVABLE_CROSS_OVER, src) & COMPONENT_BLOCK_CROSS)
|
|
return FALSE
|
|
return CanPass(crossed_atom, get_dir(src, crossed_atom))
|
|
|
|
///default byond proc that is deprecated for us in lieu of signals. do not call
|
|
/atom/movable/Crossed(atom/movable/crossed_atom, oldloc)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
CRASH("atom/movable/Crossed() was called!")
|
|
|
|
/**
|
|
* `Uncross()` is a default BYOND proc that is called when something is *going*
|
|
* to exit this atom's turf. It is prefered over `Uncrossed` when you want to
|
|
* deny that movement, such as in the case of border objects, objects that allow
|
|
* you to walk through them in any direction except the one they block
|
|
* (think side windows).
|
|
*
|
|
* While being seemingly harmless, most everything doesn't actually want to
|
|
* use this, meaning that we are wasting proc calls for every single atom
|
|
* on a turf, every single time something exits it, when basically nothing
|
|
* cares.
|
|
*
|
|
* This overhead caused real problems on Sybil round #159709, where lag
|
|
* attributed to Uncross was so bad that the entire master controller
|
|
* collapsed and people made Among Us lobbies in OOC.
|
|
*
|
|
* If you want to replicate the old `Uncross()` behavior, the most apt
|
|
* replacement is [`/datum/element/connect_loc`] while hooking onto
|
|
* [`COMSIG_ATOM_EXIT`].
|
|
*/
|
|
/atom/movable/Uncross()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
CRASH("Uncross() should not be being called, please read the doc-comment for it for why.")
|
|
|
|
/**
|
|
* default byond proc that is normally called on everything inside the previous turf
|
|
* a movable was in after moving to its current turf
|
|
* this is wasteful since the vast majority of objects do not use Uncrossed
|
|
* use connect_loc to register to COMSIG_ATOM_EXITED instead
|
|
*/
|
|
/atom/movable/Uncrossed(atom/movable/uncrossed_atom)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
CRASH("/atom/movable/Uncrossed() was called")
|
|
|
|
/atom/movable/Bump(atom/bumped_atom)
|
|
if(!bumped_atom)
|
|
CRASH("Bump was called with no argument.")
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) & COMPONENT_INTERCEPT_BUMPED)
|
|
return
|
|
. = ..()
|
|
if(!QDELETED(throwing))
|
|
throwing.finalize(hit = TRUE, target = bumped_atom)
|
|
. = TRUE
|
|
if(QDELETED(bumped_atom))
|
|
return
|
|
bumped_atom.Bumped(src)
|
|
|
|
/atom/movable/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
|
|
if(!LAZYLEN(gone.important_recursive_contents))
|
|
return
|
|
var/list/nested_locs = get_nested_locs(src) + src
|
|
for(var/channel in gone.important_recursive_contents)
|
|
for(var/atom/movable/location as anything in nested_locs)
|
|
LAZYINITLIST(location.important_recursive_contents)
|
|
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
|
LAZYINITLIST(recursive_contents[channel])
|
|
recursive_contents[channel] -= gone.important_recursive_contents[channel]
|
|
switch(channel)
|
|
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
|
if(!length(recursive_contents[channel]))
|
|
// This relies on a nice property of the linked recursive and gridmap types
|
|
// They're defined in relation to each other, so they have the same value
|
|
SSspatial_grid.remove_grid_awareness(location, channel)
|
|
ASSOC_UNSETEMPTY(recursive_contents, channel)
|
|
UNSETEMPTY(location.important_recursive_contents)
|
|
|
|
/atom/movable/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
|
. = ..()
|
|
|
|
if(!LAZYLEN(arrived.important_recursive_contents))
|
|
return
|
|
var/list/nested_locs = get_nested_locs(src) + src
|
|
for(var/channel in arrived.important_recursive_contents)
|
|
for(var/atom/movable/location as anything in nested_locs)
|
|
LAZYINITLIST(location.important_recursive_contents)
|
|
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
|
LAZYINITLIST(recursive_contents[channel])
|
|
switch(channel)
|
|
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
|
if(!length(recursive_contents[channel]))
|
|
SSspatial_grid.add_grid_awareness(location, channel)
|
|
recursive_contents[channel] |= arrived.important_recursive_contents[channel]
|
|
|
|
///allows this movable to hear and adds itself to the important_recursive_contents list of itself and every movable loc its in
|
|
/atom/movable/proc/become_hearing_sensitive(trait_source = TRAIT_GENERIC)
|
|
var/already_hearing_sensitive = HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE)
|
|
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
|
if(already_hearing_sensitive) // If we were already hearing sensitive, we don't wanna be in important_recursive_contents twice, else we'll have potential issues like one radio sending the same message multiple times
|
|
return
|
|
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
LAZYINITLIST(location.important_recursive_contents)
|
|
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
|
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
|
|
SSspatial_grid.add_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
|
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] += list(src)
|
|
|
|
var/turf/our_turf = get_turf(src)
|
|
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
|
|
|
/**
|
|
* removes the hearing sensitivity channel from the important_recursive_contents list of this and all nested locs containing us if there are no more sources of the trait left
|
|
* since RECURSIVE_CONTENTS_HEARING_SENSITIVE is also a spatial grid content type, removes us from the spatial grid if the trait is removed
|
|
*
|
|
* * trait_source - trait source define or ALL, if ALL, force removes hearing sensitivity. if a trait source define, removes hearing sensitivity only if the trait is removed
|
|
*/
|
|
/atom/movable/proc/lose_hearing_sensitivity(trait_source = TRAIT_GENERIC)
|
|
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
|
return
|
|
REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
|
if(HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
|
return
|
|
|
|
var/turf/our_turf = get_turf(src)
|
|
/// We get our awareness updated by the important recursive contents stuff, here we remove our membership
|
|
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
|
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
|
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] -= src
|
|
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
|
|
SSspatial_grid.remove_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
|
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
|
UNSETEMPTY(location.important_recursive_contents)
|
|
|
|
///allows this movable to know when it has "entered" another area no matter how many movable atoms its stuffed into, uses important_recursive_contents
|
|
/atom/movable/proc/become_area_sensitive(trait_source = TRAIT_GENERIC)
|
|
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
|
|
ADD_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
|
|
|
|
///removes the area sensitive channel from the important_recursive_contents list of this and all nested locs containing us if there are no more source of the trait left
|
|
/atom/movable/proc/lose_area_sensitivity(trait_source = TRAIT_GENERIC)
|
|
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
|
return
|
|
REMOVE_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
|
|
if(HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
|
return
|
|
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
|
|
|
|
///propogates ourselves through our nested contents, similar to other important_recursive_contents procs
|
|
///main difference is that client contents need to possibly duplicate recursive contents for the clients mob AND its eye
|
|
/mob/proc/enable_client_mobs_in_contents()
|
|
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
|
|
LAZYINITLIST(movable_loc.important_recursive_contents)
|
|
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
|
|
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
|
|
SSspatial_grid.add_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
|
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
|
|
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] |= src
|
|
|
|
var/turf/our_turf = get_turf(src)
|
|
/// We got our awareness updated by the important recursive contents stuff, now we add our membership
|
|
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
|
|
|
///Clears the clients channel of this mob
|
|
/mob/proc/clear_important_client_contents()
|
|
var/turf/our_turf = get_turf(src)
|
|
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
|
|
|
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
|
|
LAZYINITLIST(movable_loc.important_recursive_contents)
|
|
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
|
|
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
|
|
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] -= src
|
|
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
|
|
SSspatial_grid.remove_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
|
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
|
UNSETEMPTY(movable_loc.important_recursive_contents)
|
|
|
|
///called when this movable becomes the parent of a storage component that is currently being viewed by a player. uses important_recursive_contents
|
|
/atom/movable/proc/become_active_storage(datum/storage/source)
|
|
if(!HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_ACTIVE_STORAGE, src)
|
|
ADD_TRAIT(src, TRAIT_ACTIVE_STORAGE, REF(source))
|
|
|
|
///called when this movable's storage component is no longer viewed by any players, unsets important_recursive_contents
|
|
/atom/movable/proc/lose_active_storage(datum/storage/source)
|
|
if(!HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
|
|
return
|
|
REMOVE_TRAIT(src, TRAIT_ACTIVE_STORAGE, REF(source))
|
|
if(HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
|
|
return
|
|
|
|
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
|
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_ACTIVE_STORAGE, src)
|
|
|
|
///Sets the anchored var and returns if it was successfully changed or not.
|
|
/atom/movable/proc/set_anchored(anchorvalue)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(anchored == anchorvalue)
|
|
return
|
|
. = anchored
|
|
anchored = anchorvalue
|
|
if(anchored && pulledby)
|
|
pulledby.stop_pulling()
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue)
|
|
|
|
/// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others.
|
|
/atom/movable/proc/set_currently_z_moving(new_z_moving_value, forced = FALSE)
|
|
if(forced)
|
|
currently_z_moving = new_z_moving_value
|
|
return TRUE
|
|
var/old_z_moving_value = currently_z_moving
|
|
currently_z_moving = max(currently_z_moving, new_z_moving_value)
|
|
return currently_z_moving > old_z_moving_value
|
|
|
|
/atom/movable/proc/forceMove(atom/destination)
|
|
. = FALSE
|
|
if(destination)
|
|
. = doMove(destination)
|
|
else
|
|
CRASH("No valid destination passed into forceMove")
|
|
|
|
/atom/movable/proc/moveToNullspace()
|
|
return doMove(null)
|
|
|
|
/atom/movable/proc/doMove(atom/destination)
|
|
. = FALSE
|
|
RESOLVE_ACTIVE_MOVEMENT
|
|
|
|
var/atom/oldloc = loc
|
|
var/is_multi_tile = bound_width > ICON_SIZE_X || bound_height > ICON_SIZE_Y
|
|
|
|
SET_ACTIVE_MOVEMENT(oldloc, NONE, TRUE, null)
|
|
|
|
if(destination)
|
|
///zMove already handles whether a pull from another movable should be broken.
|
|
if(pulledby && !currently_z_moving)
|
|
pulledby.stop_pulling()
|
|
|
|
var/same_loc = oldloc == destination
|
|
var/area/old_area = get_area(oldloc)
|
|
var/area/destarea = get_area(destination)
|
|
var/movement_dir = get_dir(src, destination)
|
|
|
|
moving_diagonally = 0
|
|
|
|
loc = destination
|
|
|
|
if(!same_loc)
|
|
if(loc == oldloc)
|
|
// when attempting to move an atom A into an atom B which already contains A, BYOND seems
|
|
// to silently refuse to move A to the new loc. This can really break stuff (see #77067)
|
|
stack_trace("Attempt to move [src] to [destination] was rejected by BYOND, possibly due to cyclic contents")
|
|
return FALSE
|
|
|
|
if(is_multi_tile && isturf(destination))
|
|
var/dx = destination.x
|
|
var/dy = destination.y
|
|
var/dz = destination.z
|
|
var/list/new_locs = block(
|
|
dx, dy, dz,
|
|
dx + ROUND_UP(bound_width / ICON_SIZE_X), dy + ROUND_UP(bound_height / ICON_SIZE_Y), dz
|
|
)
|
|
if(old_area && old_area != destarea)
|
|
old_area.Exited(src, movement_dir)
|
|
for(var/atom/left_loc as anything in locs - new_locs)
|
|
left_loc.Exited(src, movement_dir)
|
|
|
|
for(var/atom/entering_loc as anything in new_locs - locs)
|
|
entering_loc.Entered(src, movement_dir)
|
|
|
|
if(old_area && old_area != destarea)
|
|
destarea.Entered(src, movement_dir)
|
|
else
|
|
if(oldloc)
|
|
oldloc.Exited(src, movement_dir)
|
|
if(old_area && old_area != destarea)
|
|
old_area.Exited(src, movement_dir)
|
|
destination.Entered(src, oldloc)
|
|
if(destarea && old_area != destarea)
|
|
destarea.Entered(src, old_area)
|
|
|
|
. = TRUE
|
|
|
|
//If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
|
|
else
|
|
. = TRUE
|
|
|
|
if (oldloc)
|
|
loc = null
|
|
var/area/old_area = get_area(oldloc)
|
|
if(is_multi_tile && isturf(oldloc))
|
|
for(var/atom/old_loc as anything in locs)
|
|
old_loc.Exited(src, NONE)
|
|
else
|
|
oldloc.Exited(src, NONE)
|
|
|
|
if(old_area)
|
|
old_area.Exited(src, NONE)
|
|
|
|
RESOLVE_ACTIVE_MOVEMENT
|
|
|
|
/**
|
|
* Called when a movable changes z-levels.
|
|
*
|
|
* Arguments:
|
|
* * old_turf - The previous turf they were on before.
|
|
* * new_turf - The turf they have now entered.
|
|
* * same_z_layer - If their old and new z levels are on the same level of plane offsets or not
|
|
* * notify_contents - Whether or not to notify the movable's contents that their z-level has changed. NOTE, IF YOU SET THIS, YOU NEED TO MANUALLY SET PLANE OF THE CONTENTS LATER
|
|
*/
|
|
/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf, same_z_layer)
|
|
|
|
// If our turfs are on different z "layers", recalc our planes
|
|
if(!same_z_layer && !QDELETED(src))
|
|
SET_PLANE(src, PLANE_TO_TRUE(src.plane), new_turf)
|
|
// a TON of overlays use planes, and thus require offsets
|
|
// so we do this. sucks to suck
|
|
update_appearance()
|
|
|
|
if(update_on_z)
|
|
// I so much wish this could be somewhere else. alas, no.
|
|
for(var/image/update as anything in update_on_z)
|
|
SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
|
|
if(update_overlays_on_z)
|
|
// This EVEN more so
|
|
cut_overlay(update_overlays_on_z)
|
|
// This even more so
|
|
for(var/mutable_appearance/update in update_overlays_on_z)
|
|
SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
|
|
add_overlay(update_overlays_on_z)
|
|
|
|
if(!notify_contents)
|
|
return
|
|
|
|
for (var/atom/movable/content as anything in src) // Notify contents of Z-transition.
|
|
content.on_changed_z_level(old_turf, new_turf, same_z_layer)
|
|
|
|
/**
|
|
* Called whenever an object moves and by mobs when they attempt to move themselves through space
|
|
* And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
|
|
*
|
|
* Return FALSE to have src start/keep drifting in a no-grav area and TRUE to stop/not start drifting
|
|
*
|
|
* Mobs should return 1 if they should be able to move of their own volition, see [/client/proc/Move]
|
|
*
|
|
* Arguments:
|
|
* * movement_dir - 0 when stopping or any dir when trying to move
|
|
* * continuous_move - If this check is coming from something in the context of already drifting
|
|
*/
|
|
/atom/movable/proc/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
|
|
if(has_gravity())
|
|
return TRUE
|
|
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE)
|
|
return TRUE
|
|
|
|
if(pulledby && (pulledby.pulledby != src || moving_from_pull))
|
|
return TRUE
|
|
|
|
if(throwing)
|
|
return TRUE
|
|
|
|
if(!isturf(loc))
|
|
return TRUE
|
|
|
|
if (handle_spacemove_grabbing())
|
|
return TRUE
|
|
|
|
if(locate(/obj/structure/spacevine) in range(1, get_turf(src))) //SKYRAT EDIT: allow walking when vines are around
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/atom/movable/proc/handle_spacemove_grabbing()
|
|
if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
|
|
return TRUE
|
|
|
|
/// Only moves the object if it's under no gravity
|
|
/// Accepts the direction to move, if the push should be instant, and an optional parameter to fine tune the start delay
|
|
/// Drift force determines how much acceleration should be applied. Controlled cap, if set, will ensure that if the object was moving slower than the cap before, it cannot accelerate past the cap from this move.
|
|
/atom/movable/proc/newtonian_move(inertia_angle, instant = FALSE, start_delay = 0, drift_force = 1 NEWTONS, controlled_cap = null, force_loop = TRUE)
|
|
if(!isturf(loc) || Process_Spacemove(angle2dir(inertia_angle), continuous_move = TRUE))
|
|
return FALSE
|
|
|
|
if (!isnull(drift_handler))
|
|
if (drift_handler.newtonian_impulse(inertia_angle, start_delay, drift_force, controlled_cap, force_loop))
|
|
return TRUE
|
|
|
|
new /datum/drift_handler(src, inertia_angle, instant, start_delay, drift_force)
|
|
// Something went wrong and it failed to create itself, most likely we have a higher priority loop already
|
|
if (QDELETED(drift_handler))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/atom/movable/set_explosion_block(explosion_block)
|
|
var/old_block = src.explosion_block
|
|
explosive_resistance -= old_block
|
|
src.explosion_block = explosion_block
|
|
explosive_resistance += explosion_block
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_EXPLOSION_BLOCK_CHANGED, old_block, explosion_block)
|
|
|
|
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
|
set waitfor = FALSE
|
|
var/hitpush = TRUE
|
|
var/impact_flags = pre_impact(hit_atom, throwingdatum)
|
|
if(impact_flags & COMPONENT_MOVABLE_IMPACT_NEVERMIND)
|
|
return // in case a signal interceptor broke or deleted the thing before we could process our hit
|
|
if(impact_flags & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH)
|
|
hitpush = FALSE
|
|
var/caught = hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum, caught)
|
|
return caught
|
|
|
|
///Called before we attempt to call hitby and send the COMSIG_MOVABLE_IMPACT signal
|
|
/atom/movable/proc/pre_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
|
var/impact_flags = SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum)
|
|
var/target_flags = SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum)
|
|
if(target_flags & COMSIG_HIT_PREVENTED)
|
|
impact_flags |= COMPONENT_MOVABLE_IMPACT_NEVERMIND
|
|
return impact_flags
|
|
|
|
/atom/movable/hitby(atom/movable/hitting_atom, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum)
|
|
if(HAS_TRAIT(src, TRAIT_NO_THROW_HITPUSH))
|
|
hitpush = FALSE
|
|
if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO))))
|
|
step(src, hitting_atom.dir)
|
|
return ..()
|
|
|
|
// Calls throw_at after checking that the move strength is greater than the thrown atom's move resist. Identical args.
|
|
/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
|
|
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
|
|
return
|
|
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
|
|
|
|
///If this returns FALSE then callback will not be called.
|
|
/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE, throw_datum_typepath = /datum/thrownthing)
|
|
. = FALSE
|
|
|
|
if(QDELETED(src))
|
|
CRASH("Qdeleted thing being thrown around.")
|
|
|
|
if (!target || speed <= 0)
|
|
return
|
|
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW)
|
|
return
|
|
|
|
if (pulledby)
|
|
pulledby.stop_pulling()
|
|
|
|
//They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
|
|
if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
|
|
var/user_momentum = thrower.cached_multiplicative_slowdown
|
|
if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
|
|
user_momentum = world.tick_lag
|
|
|
|
user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
|
|
|
|
if (get_dir(thrower, target) & last_move)
|
|
user_momentum = user_momentum //basically a noop, but needed
|
|
else if (get_dir(target, thrower) & last_move)
|
|
user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
|
|
else
|
|
user_momentum = 0
|
|
|
|
|
|
if (user_momentum)
|
|
//first lets add that momentum to range.
|
|
range *= (user_momentum / speed) + 1
|
|
//then lets add it to speed
|
|
speed += user_momentum
|
|
if (speed <= 0)
|
|
return//no throw speed, the user was moving too fast.
|
|
|
|
. = TRUE // No failure conditions past this point.
|
|
|
|
var/target_zone
|
|
if(QDELETED(thrower))
|
|
thrower = null //Let's not pass a qdeleting reference if any.
|
|
else
|
|
target_zone = thrower.zone_selected
|
|
|
|
var/datum/thrownthing/thrown_thing = new throw_datum_typepath(src, target, get_dir(src, target), range, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
|
|
|
|
var/dist_x = abs(target.x - src.x)
|
|
var/dist_y = abs(target.y - src.y)
|
|
var/dx = (target.x > src.x) ? EAST : WEST
|
|
var/dy = (target.y > src.y) ? NORTH : SOUTH
|
|
|
|
if (dist_x == dist_y)
|
|
thrown_thing.pure_diagonal = 1
|
|
|
|
else if(dist_x <= dist_y)
|
|
var/olddist_x = dist_x
|
|
var/olddx = dx
|
|
dist_x = dist_y
|
|
dist_y = olddist_x
|
|
dx = dy
|
|
dy = olddx
|
|
thrown_thing.dist_x = dist_x
|
|
thrown_thing.dist_y = dist_y
|
|
thrown_thing.dx = dx
|
|
thrown_thing.dy = dy
|
|
thrown_thing.diagonal_error = dist_x/2 - dist_y
|
|
thrown_thing.start_time = world.time
|
|
|
|
if(pulledby)
|
|
pulledby.stop_pulling()
|
|
if (quickstart && (throwing || SSthrowing.state == SS_RUNNING)) //Avoid stack overflow edgecases.
|
|
quickstart = FALSE
|
|
throwing = thrown_thing
|
|
if(spin)
|
|
SpinAnimation(5, 1)
|
|
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, thrown_thing, spin)
|
|
SSthrowing.processing[src] = thrown_thing
|
|
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
|
|
SSthrowing.currentrun[src] = thrown_thing
|
|
if (quickstart)
|
|
thrown_thing.tick()
|
|
|
|
/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.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
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
|
|
return FALSE
|
|
|
|
/atom/movable/proc/force_push(atom/movable/pushed_atom, force = move_force, direction, silent = FALSE)
|
|
. = pushed_atom.force_pushed(src, force, direction)
|
|
if(!silent && .)
|
|
visible_message(span_warning("[src] forcefully pushes against [pushed_atom]!"), span_warning("You forcefully push against [pushed_atom]!"))
|
|
|
|
/atom/movable/proc/move_crush(atom/movable/crushed_atom, force = move_force, direction, silent = FALSE)
|
|
. = crushed_atom.move_crushed(src, force, direction)
|
|
if(!silent && .)
|
|
visible_message(span_danger("[src] crushes past [crushed_atom]!"), span_danger("You crush [crushed_atom]!"))
|
|
|
|
/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
|
|
return FALSE
|
|
|
|
/atom/movable/CanAllowThrough(atom/movable/mover, border_dir)
|
|
. = ..()
|
|
if(mover in buckled_mobs)
|
|
return TRUE
|
|
|
|
/// Returns true or false to allow src to move through the blocker, mover has final say
|
|
/atom/movable/proc/CanPassThrough(atom/blocker, movement_dir, blocker_opinion)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SHOULD_BE_PURE(TRUE)
|
|
|
|
var/blocking_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_PASS_THROUGH, blocker, movement_dir)
|
|
if(!blocking_signal)
|
|
return blocker_opinion
|
|
|
|
if(blocking_signal & COMSIG_COMPONENT_PERMIT_PASSAGE)
|
|
return TRUE
|
|
else //we have a COMSIG_COMPONENT_REFUSE_PASSAGE but like its either this or that, unlike someone wanna adds half-passing through but fuck you
|
|
return FALSE
|
|
|
|
/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
|
|
/atom/movable/proc/on_exit_storage(datum/storage/master_storage)
|
|
return
|
|
|
|
/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
|
|
/atom/movable/proc/on_enter_storage(datum/storage/master_storage)
|
|
return
|
|
|
|
/atom/movable/proc/get_spacemove_backup()
|
|
var/atom/secondary_backup
|
|
for(var/checked_range in orange(1, get_turf(src)))
|
|
if(isarea(checked_range))
|
|
continue
|
|
if(isturf(checked_range))
|
|
var/turf/turf = checked_range
|
|
if(!turf.density)
|
|
continue
|
|
if (get_dir(src, turf) in GLOB.cardinals)
|
|
return turf
|
|
secondary_backup = turf
|
|
continue
|
|
var/atom/movable/checked_atom = checked_range
|
|
if(checked_atom.density || !checked_atom.CanPass(src, get_dir(src, checked_atom)))
|
|
if(checked_atom.last_pushoff == world.time)
|
|
continue
|
|
if (get_dir(src, checked_atom) in GLOB.cardinals)
|
|
return checked_atom
|
|
secondary_backup = checked_atom
|
|
return secondary_backup
|
|
|
|
///called when a mob resists while inside a container that is itself inside something.
|
|
/atom/movable/proc/relay_container_resist_act(mob/living/user, obj/container)
|
|
return
|
|
|
|
|
|
/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE, item_animation_override = null)
|
|
if(!no_effect && (visual_effect_icon || used_item))
|
|
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item, animation_type = item_animation_override)
|
|
|
|
if(attacked_atom == src)
|
|
return //don't do an animation if attacking self
|
|
var/pixel_x_diff = 0
|
|
var/pixel_y_diff = 0
|
|
var/turn_dir = 1
|
|
|
|
var/direction = get_dir(src, attacked_atom)
|
|
if(direction & NORTH)
|
|
pixel_y_diff = 8
|
|
turn_dir = prob(50) ? -1 : 1
|
|
else if(direction & SOUTH)
|
|
pixel_y_diff = -8
|
|
turn_dir = prob(50) ? -1 : 1
|
|
|
|
if(direction & EAST)
|
|
pixel_x_diff = 8
|
|
else if(direction & WEST)
|
|
pixel_x_diff = -8
|
|
turn_dir = -1
|
|
|
|
if(fov_effect)
|
|
play_fov_effect(attacked_atom, 5, "attack")
|
|
|
|
var/matrix/initial_transform = matrix(transform)
|
|
var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
|
|
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
|
|
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING, flags = ANIMATION_PARALLEL)
|
|
|
|
/* Language procs
|
|
* Unless you are doing something very specific, these are the ones you want to use.
|
|
*/
|
|
|
|
/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one.
|
|
/atom/movable/proc/get_language_holder()
|
|
RETURN_TYPE(/datum/language_holder)
|
|
if(QDELING(src))
|
|
CRASH("get_language_holder() called on a QDELing atom, \
|
|
this will try to re-instantiate the language holder that's about to be deleted, which is bad.")
|
|
|
|
if(!language_holder)
|
|
language_holder = new initial_language_holder(src)
|
|
return language_holder
|
|
|
|
/// Grants the supplied language and sets omnitongue true.
|
|
/atom/movable/proc/grant_language(language, language_flags = ALL, source = LANGUAGE_ATOM)
|
|
return get_language_holder().grant_language(language, language_flags, source)
|
|
|
|
/// Grants every language.
|
|
/atom/movable/proc/grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
|
|
return get_language_holder().grant_all_languages(language_flags, grant_omnitongue, source)
|
|
|
|
/// Grants partial understanding of a language.
|
|
/atom/movable/proc/grant_partial_language(language, amount = 50, source = LANGUAGE_ATOM)
|
|
return get_language_holder().grant_partial_language(language, amount, source)
|
|
|
|
/// Removes a single language.
|
|
/atom/movable/proc/remove_language(language, language_flags = ALL, source = LANGUAGE_ALL)
|
|
return get_language_holder().remove_language(language, language_flags, source)
|
|
|
|
/// Removes every language and sets omnitongue false.
|
|
/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE)
|
|
return get_language_holder().remove_all_languages(source, remove_omnitongue)
|
|
|
|
/// Removes partial understanding of a language.
|
|
/atom/movable/proc/remove_partial_language(language, source = LANGUAGE_ALL)
|
|
return get_language_holder().remove_partial_language(language, source)
|
|
|
|
/// Removes all partial languages.
|
|
/atom/movable/proc/remove_all_partial_languages(source = LANGUAGE_ALL)
|
|
return get_language_holder().remove_all_partial_languages(source)
|
|
|
|
/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later.
|
|
/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM)
|
|
return get_language_holder().add_blocked_language(language, source)
|
|
|
|
/// Removes a language from the blocked language list.
|
|
/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM)
|
|
return get_language_holder().remove_blocked_language(language, source)
|
|
|
|
/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language.
|
|
/atom/movable/proc/has_language(language, flags_to_check)
|
|
return get_language_holder().has_language(language, flags_to_check)
|
|
|
|
/// Checks if atom can speak the language.
|
|
/atom/movable/proc/can_speak_language(language)
|
|
return get_language_holder().can_speak_language(language)
|
|
|
|
/// Returns the result of tongue specific limitations on spoken languages.
|
|
/atom/movable/proc/could_speak_language(datum/language/language_path)
|
|
return TRUE
|
|
|
|
/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible.
|
|
/atom/movable/proc/get_selected_language()
|
|
return get_language_holder().get_selected_language()
|
|
|
|
/// Gets a random understood language, useful for hallucinations and such.
|
|
/atom/movable/proc/get_random_understood_language()
|
|
return get_language_holder().get_random_understood_language()
|
|
|
|
/// Gets a lazylist of all mutually understood languages.
|
|
/atom/movable/proc/get_partially_understood_languages() as /list
|
|
RETURN_TYPE(/list)
|
|
return get_language_holder().best_mutual_languages
|
|
|
|
/// Gets a random spoken language, useful for forced speech and such.
|
|
/atom/movable/proc/get_random_spoken_language()
|
|
return get_language_holder().get_random_spoken_language()
|
|
|
|
/// Copies all languages into the supplied atom/language holder. Source should be overridden when you
|
|
/// do not want the language overwritten by later atom updates or want to avoid blocked languages.
|
|
/atom/movable/proc/copy_languages(datum/language_holder/from_holder, source_override)
|
|
if(ismovable(from_holder))
|
|
var/atom/movable/thing = from_holder
|
|
from_holder = thing.get_language_holder()
|
|
|
|
return get_language_holder().copy_languages(from_holder, source_override)
|
|
|
|
/// Sets the passed path as the active language
|
|
/// Returns the currently selected language if successful, if the language was not valid, returns null
|
|
/atom/movable/proc/set_active_language(language_path)
|
|
var/datum/language_holder/our_holder = get_language_holder()
|
|
our_holder.selected_language = language_path
|
|
|
|
return our_holder.get_selected_language() // verifies its validity, returns it if successful.
|
|
|
|
/**
|
|
* Randomizes our atom's language to an uncommon language if:
|
|
* - They are on the station Z level
|
|
* OR
|
|
* - They are on the escape shuttle
|
|
*/
|
|
/atom/movable/proc/randomize_language_if_on_station()
|
|
var/turf/atom_turf = get_turf(src)
|
|
var/area/atom_area = get_area(src)
|
|
|
|
if(!atom_turf) // some machines spawn in nullspace
|
|
return FALSE
|
|
|
|
if(!is_station_level(atom_turf.z) && !istype(atom_area, /area/shuttle/escape))
|
|
// Why snowflake check for escape shuttle? Well, a lot of shuttles spawn with machines
|
|
// but docked at centcom, and I wanted those machines to also speak funny languages
|
|
return FALSE
|
|
grant_random_uncommon_language()
|
|
return TRUE
|
|
|
|
/// Teaches a random non-common language and sets it as the active language
|
|
/atom/movable/proc/grant_random_uncommon_language(source)
|
|
if (!length(GLOB.uncommon_roundstart_languages))
|
|
return FALSE
|
|
var/picked = pick(GLOB.uncommon_roundstart_languages)
|
|
grant_language(picked, source = source)
|
|
set_active_language(picked)
|
|
return TRUE
|
|
|
|
/* End language procs */
|
|
|
|
/**
|
|
* Returns an atom's power cell, if it has one. Overload for individual items.
|
|
* Args
|
|
*
|
|
* * /atom/movable/interface - the atom that is trying to interact with this cell
|
|
* * mob/user - the mob that is holding the interface
|
|
*/
|
|
/atom/movable/proc/get_cell(atom/movable/interface, mob/user)
|
|
return
|
|
|
|
/atom/movable/proc/can_be_pulled(user, force)
|
|
if(src == user || !isturf(loc))
|
|
return FALSE
|
|
if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_PULLED, user) & COMSIG_ATOM_CANT_PULL)
|
|
return FALSE
|
|
if(anchored || throwing)
|
|
return FALSE
|
|
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* Updates the grab state of the movable
|
|
*
|
|
* This exists to act as a hook for behaviour
|
|
*/
|
|
/atom/movable/proc/setGrabState(newstate)
|
|
if(newstate == grab_state)
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate)
|
|
. = grab_state
|
|
grab_state = newstate
|
|
switch(grab_state) // Current state.
|
|
if(GRAB_PASSIVE)
|
|
pulling.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), CHOKEHOLD_TRAIT)
|
|
if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher.
|
|
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
|
|
if(GRAB_AGGRESSIVE)
|
|
if(. >= GRAB_NECK) // Grab got downgraded.
|
|
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
|
|
else // Grab got upgraded from a passive one.
|
|
pulling.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), CHOKEHOLD_TRAIT)
|
|
if(GRAB_NECK, GRAB_KILL)
|
|
if(. <= GRAB_AGGRESSIVE)
|
|
ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
|
|
|
|
/**
|
|
* Adds the deadchat_plays component to this atom with simple movement commands.
|
|
*
|
|
* Returns the component added.
|
|
* Arguments:
|
|
* * mode - Either ANARCHY_MODE or DEMOCRACY_MODE passed to the deadchat_control component. See [/datum/component/deadchat_control] for more info.
|
|
* * cooldown - The cooldown between command inputs passed to the deadchat_control component. See [/datum/component/deadchat_control] for more info.
|
|
*/
|
|
/atom/movable/proc/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS)
|
|
return AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list(), cooldown)
|
|
|
|
/atom/movable/vv_get_dropdown()
|
|
. = ..()
|
|
VV_DROPDOWN_OPTION("", "---------")
|
|
VV_DROPDOWN_OPTION(VV_HK_OBSERVE_FOLLOW, "Observe Follow")
|
|
VV_DROPDOWN_OPTION(VV_HK_GET_MOVABLE, "Get Movable")
|
|
VV_DROPDOWN_OPTION(VV_HK_EDIT_PARTICLES, "Edit Particles")
|
|
VV_DROPDOWN_OPTION(VV_HK_DEADCHAT_PLAYS, "Start/Stop Deadchat Plays")
|
|
VV_DROPDOWN_OPTION(VV_HK_ADD_FANTASY_AFFIX, "Add Fantasy Affix")
|
|
|
|
/atom/movable/vv_do_topic(list/href_list)
|
|
. = ..()
|
|
|
|
if(!.)
|
|
return
|
|
|
|
if(href_list[VV_HK_OBSERVE_FOLLOW])
|
|
if(!check_rights(R_ADMIN))
|
|
return
|
|
usr.client?.admin_follow(src)
|
|
|
|
if(href_list[VV_HK_GET_MOVABLE])
|
|
if(!check_rights(R_ADMIN))
|
|
return
|
|
if(QDELETED(src))
|
|
return
|
|
forceMove(get_turf(usr))
|
|
|
|
if(href_list[VV_HK_EDIT_PARTICLES] && check_rights(R_VAREDIT))
|
|
var/client/C = usr.client
|
|
C?.open_particle_editor(src)
|
|
|
|
if(href_list[VV_HK_DEADCHAT_PLAYS] && check_rights(R_FUN))
|
|
if(tgui_alert(usr, "Allow deadchat to control [src] via chat commands?", "Deadchat Plays [src]", list("Allow", "Cancel")) != "Allow")
|
|
return
|
|
// Alert is async, so quick sanity check to make sure we should still be doing this.
|
|
if(QDELETED(src))
|
|
return
|
|
// This should never happen, but if it does it should not be silent.
|
|
if(deadchat_plays() == COMPONENT_INCOMPATIBLE)
|
|
to_chat(usr, span_warning("Deadchat control not compatible with [src]."))
|
|
CRASH("deadchat_control component incompatible with object of type: [type]")
|
|
to_chat(usr, span_notice("Deadchat now control [src]."))
|
|
log_admin("[key_name(usr)] has added deadchat control to [src]")
|
|
message_admins(span_notice("[key_name(usr)] has added deadchat control to [src]"))
|
|
|
|
/**
|
|
* A wrapper for setDir that should only be able to fail by living mobs.
|
|
*
|
|
* Called from [/atom/movable/proc/keyLoop], this exists to be overwritten by living mobs with a check to see if we're actually alive enough to change directions
|
|
*/
|
|
/atom/movable/proc/keybind_face_direction(direction)
|
|
setDir(direction)
|
|
|
|
///This handles special behavior that happens when the movable is used in crafting (slapcrafting and UI, not sheets or lathes or processing with a tool)
|
|
/atom/movable/proc/used_in_craft(atom/result, datum/crafting_recipe/current_recipe)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_ATOM_USED_IN_CRAFT, result)
|
|
|
|
/**
|
|
* Check if the other atom/movable has any factions the same as us. Defined at the atom/movable level so it can be defined for just about anything.
|
|
*
|
|
* If exact match is set, then all our factions must match exactly
|
|
*/
|
|
/atom/movable/proc/faction_check_atom(atom/movable/target, exact_match)
|
|
if(!exact_match)
|
|
return faction_check(faction, target.faction, FALSE)
|
|
|
|
var/list/faction_src = LAZYCOPY(faction)
|
|
var/list/faction_target = LAZYCOPY(target.faction)
|
|
if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list.
|
|
faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match.
|
|
if(!("[REF(target)]" in faction_src))
|
|
faction_target -= "[REF(target)]" //same thing here.
|
|
return faction_check(faction_src, faction_target, TRUE)
|