mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 17:52:36 +00:00
## What's going on here Kept you waitin huh! This pr resprites most all walls, windows and other "wall adjacent" things to a 3/4th perspective, technical term is "tall" walls (we are very smart). If you're trying to understand the technical details here, much of the "rendering tech" is built off the idea of split-vis. Basically, split a sprite up and render it on adjacent turfs, to prevent seeing "through" walls/doors, and to support seeing "edges" without actually seeing the atom itself. Most of the rest of it is pipelining done to accommodate how icons are cut. ## Path To Merge Almost* all sprites and code is done at this point. There are some things missing both on and off the bounty list, but that will be the case forever unless we force upstream (you guys) to stop adding new shit that doesn't fit the style. I plan on accepting and integrating prs to the current working repo <https://github.com/wall-nerds/wallening> up until a merge, to make contribution simpler and allow things like bounties to close out more easily This pr is quite bulky, even stripping away map changes it's maybe 7000 LOC (We have a few maps that were modified with UpdatePaths, I am also tentatively pring our test map, for future use.) This may inhibit proper review, although that is part of why I am willing to make it despite my perfectionism. Apologies in advance. Due to the perspective shift, a lot of mapping work is going to need to be done at some point. This comes in varying levels of priority. Many wallmounts are offset by hand, some are stuck in the wall/basically cannot be placed on the east/west/north edges of walls (posters), some just don't look great good in their current position. Tests are currently a minor bit yorked, I thought it was more important to get this up then to clean them fully. ## What does it look like?       ## Credits <details> <summary>Historical Mumbojumbo</summary> I am gonna do my best to document how this project came to be. I am operating off third party info and half remembered details, so if I'm wrong please yell at me. This project started sometime in late 2020, as a product of Rohesie trying to integrate and make easier work from Mojave Sun (A recently defunct fallout server) with /tg/. Mojave Sun (Apparently this was LITERALLY JUST infrared baron, that man is insane) was working with tall walls, IE walls that are 48px tall instead of the normal 32. This was I THINK done based off a technical prototype from aao7 proving A it was possible and B it didn't look like dogwater. This alongside oranges begging the art team for 3/4th walls (he meant TGMC style) lead to Rohesie bringing on contributors from general /tg/, including actionninja who would eventually take over as technical lead and Kryson, who would define /tg/'s version of the artstyle. Much of the formative aspects of this project are their work. The project was coming along pretty well for a few months, but ran into serious technical issues with `SIDE_MAP`, a byond map_format that allows for simpler 3/4th rendering. Due to BULLSHIT I will not detail here, the map format caused issues both at random with flickering and heavily with multiz. Concurrent with this, action stepped down after hacking out the rendering tech and starting work on an icon cutter that would allow for simpler icon generation, leaving ninjanomnom to manage the project. Some time passed, and the project stalled out due to the technical issues. Eventually I built a test case for the issues we had with `SIDE_MAP` and convinced lummox jr (byond's developer) to explain how the fuckin thing actually worked. This understanding made the project theoretically possible, but did not resolve the problems with multi-z. Resolving those required a full rework of how rendering like, worked. I (alongside tattle) took over project development from ninjanomnom at this time, and started work on Plane Cube (#69115), which when finished would finally make the project technically feasible. The time between then and now has been slow, progressive work. Many many artists and technical folks have dumped their time into this (as you can see from the credits). I will get into this more below but I would like to explicitly thank (in no particular order) tattle, draco, arcanemusic, actionninja, imaginos, viro and kylerace for keeping the project alive in this time period. I would have curled up into a ball and died if I had to do this all myself, your help has been indispensable. </details> <details> <summary>Detailed Credits</summary> Deep apologies if I have forgotten someone (I am sure I have, if someone is you please contact me). I've done my best to collate from the git log/my memory. Thanks to (In no particular order): Raccoff: Being funny to bully, creating threshold decals for airlocks aa07: (I think) inspiring the project ActionNinja: Laying the technical rock we build off, supporting me despite byond trying to kill him, building the icon cutter that makes this possible ArcaneMusic: Artistic and technical work spanning from the project's start to literally today, being a constant of motivation and positivity. I can't list all the stuff he's done Armhulen: Key rendering work (he's the reason thindows render right), an upbeat personality and a kick in the ass. Love you arm Azlan: Damn cool sprites, consistently Ben10Omintrix: You know ben showed up just to make basic mobs work, he's just fuckin like that man BigBimmer: A large amount of bounty work, alongside just like, throwing shit around. An absolute joy to work with Capsandi: Plaques, blastdoors, artistic work early on CapybaraExtravagante: Rendering work on wall frames Draco: SO MUCH STUFF. Much of the spritework done over the past two years is his, constantly engaged and will take on anything. I would have given up if not for you Floyd: Early rendering work, so early I don't even know the details. Enjoy freedom brother Imaginos16: A guiding hand through the middle years, handled much of the sprite review and contribution for a good bit there Iamgoofball: A dedication to detail and aesthetic goals, spends a lot of effort dissecting feedback with a focus on making things as good as they can be at the jump Infrared: Part of the impetus for the project, made all the xenomorph stuff in the MS style Jacquerel: A bunch of little upkeep/technical things, has done so much sprite gruntwork (WHY ARE THERE SO MANY PAINTING TYPES) Justice12354: Solved a bunch of error sprites (and worked out how to actually make prs to the project) Thanks bro! Kryson: Built the artstyle of the project, carrying on for years even when it was technically dying, only stopping to casually beat cancer. So much of our style and art is Kryson KylerAce: Handled annoying technical stuff for me, built window frame logic and fully got rid of grilles. LemonInTheDark: Rendering dirtywork, project management and just so much fucking time in dreammaker editing sprites Meyhazah: Table buttons, brass windows and alll the old style doors Mothblocks: Has provided constant support, gave me a deadline and motivation, erased worries about "it not being done", gave just SO much money to fill in the critical holes in sprites. Thanks moth MTandi: Contributed art despite his own blackjack and hookers club opening right down the road, I'm sorry I rolled over some of your sprites man I wish we had finished earlier Ninjanomnomnom: Consulted on gags issues, kept things alive through some truly shit times oranges: This is his fault Rohesie: Organized the effort, did much of the initial like, proof of concept stuff. I hope you're doin well whatever you're up to. san7890: Consulting on mapper UX/design problems, being my pet mapper Senefi: Offsetting items with a focus on detail/the more unused canidates SimplyLogan: Detailed map work and mapper feedback, personally very kind even if we end up talking past each other sometimes. Thank you! SpaceSmithers: Just like, random mapping support out of nowhere, and bein a straight up cool dude Tattle: A bunch of misc project management stuff, organizing the discord, managing the test server, dealing with all the mapping bullshit for me, being my backup in case of bus. I know you think you didn't do much but your presence and work have been a great help Thunder12345: Came out of nowhere and just so much of the random bounties, I'm kind of upset about how much we paid him Time-Green: I hooked him in by fucking with stuff he made and now he's just doin shit, thanks for helping out man! Twaticus: Provided artistic feedback and authority for my poor feeble coder brain, believed in the project for YEARS, was a constant source of ❤️ and affirmation unit0016: I have no god damn idea who she is, popped out of nowhere on the github one day and dealt with a bunch of annoying rendering/refactoring. Godspeed random furry thank you for all your effort and issue reports Viro: A bunch of detailed spriting moving towards 3/4ths, both on and off the wallening fork. If anyone believed this project would be done, it was viro Wallem: Artistic review and consultation, was my go-to guy for a long time when the other two spritetainers were inactive Waltermeldon: Cracked out a bunch of rendering work, he's the reason windows look like not dogwater. Alongside floyd and action spent a TON of time speaking to lummox/unearthing how byond rendering worked trying to make this thing happen ZephyrTFA: Added directional airlock helpers, dealt with a big fuckin bugaboo that was living in my brain like it was nothing. Love you brother And finally: The Mojave Sun development team. They provided a testbed for the idea, committed hundreds and hundreds of hours to the artstyle, and were a large reason we caught issues early enough to meaningfully deal with them. Your work is a testament to what longterm effort and deep detailed care produce. I hope you're doing well whatever you're up to. Go out with a bang! </details> ## Changelog 🆑 Raccoff, aa07, ActionNinja, ArcaneMusic, Armhulen, Azlan, Ben10Omintrix, BigBimmer, Capsandi, CapybaraExtravagante, Draco, Floyd, Iamgoofball, Imaginos16, Infrared, Jacquerel, Justice12354, Kryson, KylerAce, LemonInTheDark, Meyhazah, Mothblocks, MTandi, Ninjanomnom, oranges, Rohesie, Runi-c, san7890, Senefi, SimplyLogan, SomeAngryMiner, SpaceSmithers, Tattle, Thunder12345, Time-Green, Twaticus, unit0016, Viro, Waltermeldon, ZephyrTFA with thanks to the Mojave Sun team! add: Resprites or offsets almost all "tall" objects in the game to match a 3/4ths perspective add: Bunch of rendering mumbo jumbo to make said 3/4ths perspective work /🆑 --------- Co-authored-by: Jacquerel <hnevard@gmail.com> Co-authored-by: san7890 <the@san7890.com> Co-authored-by: = <stewartareid@outlook.com> Co-authored-by: Capsandi <dansullycc@gmail.com> Co-authored-by: ArcaneMusic <hero12290@aol.com> Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com> Co-authored-by: SomeAngryMiner <53237389+SomeAngryMiner@users.noreply.github.com> Co-authored-by: KylerAce <kylerlumpkin1@gmail.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com> Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: Runi-c <5150427+Runi-c@users.noreply.github.com> Co-authored-by: Roryl-c <5150427+Roryl-c@users.noreply.github.com> Co-authored-by: tattle <article.disaster@gmail.com> Co-authored-by: Senefi <20830349+Peliex@users.noreply.github.com> Co-authored-by: Justice <42555530+Justice12354@users.noreply.github.com> Co-authored-by: BluBerry016 <50649185+unit0016@users.noreply.github.com> Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com> Co-authored-by: Emmett Gaines <ninjanomnom@gmail.com> Co-authored-by: Rob Bailey <github@criticalaction.net> Co-authored-by: MMMiracles <lolaccount1@hotmail.com>
1746 lines
70 KiB
Plaintext
1746 lines
70 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
|
|
/// 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
|
|
///Delay in deciseconds between inertia based movement
|
|
var/inertia_move_delay = 5
|
|
///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 reccomend 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
|
|
|
|
///Used for the calculate_adjacencies proc for icon smoothing.
|
|
var/can_be_unanchored = FALSE
|
|
|
|
///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
|
|
|
|
/// The voice that this movable makes when speaking
|
|
var/voice
|
|
|
|
/// The pitch adjustment that this movable uses when speaking.
|
|
var/pitch = 0
|
|
|
|
/// 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 overriden by the parent call
|
|
color = EM_BLOCK_COLOR
|
|
appearance_flags = EMISSIVE_APPEARANCE_FLAGS
|
|
|
|
/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 faciliate 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
|
|
blocker.plane = GET_NEW_PLANE(EMISSIVE_PLANE, PLANE_TO_OFFSET(plane))
|
|
// 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)
|
|
|
|
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)
|
|
|
|
// 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)
|
|
|
|
. = ..()
|
|
|
|
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)
|
|
em_block.render_source = render_target
|
|
else if(!QDELETED(src))
|
|
if(!render_target)
|
|
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(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 % world.icon_size) != 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
|
|
|
|
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, state, 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)
|
|
|
|
/**
|
|
* 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 camera 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
|
|
|
|
// A mid-movement... movement... occured, resolve that first.
|
|
RESOLVE_ACTIVE_MOVEMENT
|
|
|
|
if(!direction)
|
|
direction = get_dir(src, newloc)
|
|
|
|
if(set_dir_on_move && dir != direction && update_dir)
|
|
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))
|
|
new_locs = block(
|
|
newloc,
|
|
locate(
|
|
min(world.maxx, newloc.x + CEILING(bound_width / 32, 1)),
|
|
min(world.maxy, newloc.y + CEILING(bound_height / 32, 1)),
|
|
newloc.z
|
|
)
|
|
) // 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)
|
|
setDir(first_step_dir)
|
|
else if(!inertia_moving)
|
|
newtonian_move(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)
|
|
//dont 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)
|
|
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 (!inertia_moving && momentum_change)
|
|
newtonian_move(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)
|
|
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
|
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
|
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 sucessfully 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 > world.icon_size || bound_height > world.icon_size
|
|
|
|
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(is_multi_tile && isturf(destination))
|
|
var/list/new_locs = block(
|
|
destination,
|
|
locate(
|
|
min(world.maxx, destination.x + ROUND_UP(bound_width / 32)),
|
|
min(world.maxy, destination.y + ROUND_UP(bound_height / 32)),
|
|
destination.z
|
|
)
|
|
)
|
|
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(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
|
|
/// 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
|
|
/atom/movable/proc/newtonian_move(direction, instant = FALSE, start_delay = 0)
|
|
if(!isturf(loc) || Process_Spacemove(direction, continuous_move = TRUE))
|
|
return FALSE
|
|
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_NEWTONIAN_MOVE, direction, start_delay) & COMPONENT_MOVABLE_NEWTONIAN_BLOCK)
|
|
return TRUE
|
|
|
|
AddComponent(/datum/component/drift, direction, instant, start_delay)
|
|
|
|
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_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum)
|
|
if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH)
|
|
hitpush = FALSE // hacky, tie this to something else or a proper workaround later
|
|
|
|
if(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))
|
|
return // in case a signal interceptor broke or deleted the thing before we could process our hit
|
|
if(SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum) & COMSIG_HIT_PREVENTED)
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
|
|
return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
|
|
|
|
/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 ..()
|
|
|
|
/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)
|
|
. = 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(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()
|
|
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
|
|
return turf
|
|
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
|
|
return checked_atom
|
|
|
|
///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)
|
|
if(!no_effect && (visual_effect_icon || used_item))
|
|
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item)
|
|
|
|
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)
|
|
|
|
/// 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)
|
|
|
|
/// 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 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.
|
|
/atom/movable/proc/get_cell()
|
|
return
|
|
|
|
/atom/movable/proc/can_be_pulled(user, grab_state, 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)
|
|
|
|
/**
|
|
* 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)
|
|
|
|
/// Offsets this movable based on dir (in context of wallmounts)
|
|
/// Yes yes mothblocks I know this is not modular but I don't want to make the element bespoke
|
|
/// Insert ranting about element key generation here
|
|
/// This proc is filled in by the directional macros, should not need to manually touch it unless something is wrong
|
|
/atom/movable/proc/wall_mount_offset(direction)
|
|
return
|
|
|
|
/// Returns true if we should layer in common with the general game
|
|
/// If false, render over the frill plane insted
|
|
/atom/movable/proc/wall_mount_common_plane(direction)
|
|
if(direction == SOUTH)
|
|
return TRUE
|
|
return FALSE
|