Files
Bubberstation/code/game/atom/_atom.dm
T
LemonInTheDarkandGitHub 9d14e327b5 Begins Improving Sparks/Flares Somewhat, Adds Animatable Light Overlays (#95362)
## About The Pull Request

[Adds a visual tick helper, integrates it into SSmove and
such](https://github.com/tgstation/tgstation/commit/e97035f9f74fad5c67c5bf19d8d5d3bb4bd476b4)

Basically, if we do "stuff" during verb time then the next chance
clients have to actually see it is on the next visual tick (rather then
the normal "this tick"). This is cause clients get their next frame
during maptick, and maptick runs before verbs.

We want to be able to handle this properly because if you say, create an
object and then move it on the same VISUAL tick (NOT game tick), it will
just teleport instead of playing out the move. I don't want this for
stuff like sparks, so we need a way to work around it.

[Moves most users of the _FAST flag to
_INSTANT](https://github.com/tgstation/tgstation/commit/6f96daac00519c69adc7554f52114798a65f3ad5)

These are the kids that don't immediately spawn something and the move
it, and we want to allow them to move actually as soon as possible
(important for stuff like space)

[Improves basic effect systems, makes their products delete when they
stop
moving](https://github.com/tgstation/tgstation/commit/172cb25d80ed34e1ec523172a1677fb524239fba)

Moves some stuff out to getters or vars so children can better decide
how long effects should last/how fast they should move. Uses this to
clean up weird dupe code used by explosions.

Makes all these effects delete on contact with something that stops
them. I'm doing this because an effect just hanging in the air looks
really really odd. Does have consequences for sparks that are already
moving at a wall though, might need a better way to handle that.

Makes all these effects use _FAST loops so they don't just hang in the
air for a second on spawn

Adds a setter proc on sparks for their duration, gonna use this to
improve their effects some

[Refactors overlay lights, adds support for animating their
images](https://github.com/tgstation/tgstation/commit/3ad0083cf2b536df51a6d93dca40eac20c1d62d1)

Implements light_render_source and relevant setters, this allows us to
replace the components of an overlay light with basically whatever we
want

Refactors overlay lighting to handle its images more consistently,
allowing us to hook into an image being modified

Combining the two of these will allow us to consistently copy a light's
image, modify it in some way, and then relay that modification back
down. Allowing us to animate it or do more advanced effects painlessly

Also, fixes ranges of 1 or less not rendering at all on initial set
(thank you kapu)

[In which I get fed up and add a macro helper for UID
generation](https://github.com/tgstation/tgstation/commit/aab48b03d407104d4f9cf9acb034494237def911)

[adds vv hooking for all existing lighting
vars](https://github.com/tgstation/tgstation/commit/b81c6200a0d74c36b440aa3f4c1f22c422090a2d)

[Upgrade effect system's dir picking to avoid duplicates when
possible](https://github.com/tgstation/tgstation/commit/18b622586b509c6be4c4bca4e3e7c175ad75fe91)

[Uses the technique described above to animate spark's lights out as
they
move](https://github.com/tgstation/tgstation/commit/67ba177982213799984a70e89536c5efb3d17e14)

This is a decently nice effect imo, it allows us to bump their power
(read, alpha) since it'll get animated away. I try to sync the animation
to the actual icon state's flow (it's 0.7s long). I also sped them up
somewhat to hopefully have a nicer looking effect? we'll see.

[Abstracts away intercepting overlay lights into a holder
datum](https://github.com/tgstation/tgstation/pull/95362/commits/b3f1fe74f2c3bab1d8912ab8a666bd05677ad032)

This should make it far easier to reuse this pattern!

[Fixes overlay lights flashing to double intensity when picked up off
the
ground](https://github.com/tgstation/tgstation/pull/95362/commits/1d83f2031fa2b33312b2aea4359c0c37c9d04ac7)

We needed to clear out their underlays BEFORE the animation

[Adds a flickering effect to flares and their
children](https://github.com/tgstation/tgstation/pull/95362/commits/b7a858e04a607c58b6c7fbe1476ffe2239e63bde)

I'm still not 100% happy with this, I was trying to avoid it feeling
like a heartbeat with random noise and I.. THINK it worked? it's
honestly quite hard to tell

[Adds the same flickering to lighters, welding tools and life
candles](https://github.com/tgstation/tgstation/pull/95362/commits/3ec44027e17835ae96702cec5f0b12d1f4deb32b)

Also, updated light candles to mirror the appearance of normal candles
and use overlay lighting

EDIT:
I realized while working on flares that I accidentally double applied
color, so if you saw the sparks animations before now it was different
(less vibrant). IDK if I like this better or worse but it is RIGHT and
that's what matters.

## Why It's Good For The Game

I got mad about how bad these looked, and this is a start at improving
them.
Also, adds a framework for more dynamic effects applied to overlay
lights (you could use this to apply a sort of "emergency rotating"
effect, or flicker/buzz for example).

<details>
 <summary>Before</summary>


https://github.com/user-attachments/assets/66437f27-ee3c-4f14-a7ee-4a1c3e68533a


https://github.com/user-attachments/assets/ed14fff8-a7eb-47fe-bab5-9a490ac96629

</details>

<details>
 <summary>After</summary>


https://github.com/user-attachments/assets/fb24ff2e-c745-42a5-8e11-c8a1eeef35a5


https://github.com/user-attachments/assets/fd8c2116-cb92-4fe6-ad3e-786a6538e52a

</details>

## Changelog
🆑
add: Reworks how sparks render. They're now a bit brighter, will fade
out as they move/if they hit something, will stack with each other less
and also won't start hang in the air on spawn.
add: Added a flickering effect to lighters, welding tools, flares,
torches and candles (since they're flames).
fix: Overlay based lights (think flashlights) will no longer flash to
double intensity while being picked up.
refactor: Reworked how some effects (explosion particles, sparks, some
reagent stuff) function, report any bugs!
/🆑
2026-03-20 14:48:57 +13:00

996 lines
37 KiB
Plaintext

/**
* The base type for nearly all physical objects in SS13
* Lots and lots of functionality lives here, although in general we are striving to move
* as much as possible to the components/elements system
*/
/atom
abstract_type = /atom
layer = ABOVE_NORMAL_TURF_LAYER
plane = GAME_PLANE
appearance_flags = TILE_BOUND|LONG_GLIDE
/// pass_flags that we are. If any of this matches a pass_flag on a moving thing, by default, we let them through.
var/pass_flags_self = NONE
///First atom flags var
var/flags_1 = NONE
///Intearaction flags
var/interaction_flags_atom = NONE
var/flags_ricochet = NONE
///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this
var/receive_ricochet_chance_mod = 1
///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
var/receive_ricochet_damage_coeff = 0.33
///Reagents holder
var/datum/reagents/reagents = null
///all of this atom's HUD (med/sec, etc) images. Associative list of the form: list(hud category = hud image or images for that category).
///most of the time hud category is associated with a single image, sometimes its associated with a list of images.
///not every hud in this list is actually used. for ones available for others to see, look at active_hud_list.
var/list/image/hud_list = null
///all of this atom's HUD images which can actually be seen by players with that hud
var/list/image/active_hud_list = null
///HUD images that this atom can provide.
var/list/hud_possible
///How much this atom resists explosions by, in the end
var/explosive_resistance = 0
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays.
var/list/managed_vis_overlays
/// Lazylist of all images (or atoms, I'm sorry) (hopefully attached to us) to update when we change z levels
/// You will need to manage adding/removing from this yourself, but I'll do the updating for you
var/list/image/update_on_z
/// Lazylist of all overlays attached to us to update when we change z levels
/// You will need to manage adding/removing from this yourself, but I'll do the updating for you
/// Oh and note, if order of addition is important this WILL break that. so mind yourself
var/list/image/update_overlays_on_z
///Cooldown tick timer for buckle messages
var/buckle_message_cooldown = 0
///Last fingerprints to touch this atom
var/fingerprintslast
/// Radiation insulation types
var/rad_insulation = RAD_NO_INSULATION
/// The icon state intended to be used for the acid component. Used to override the default acid overlay icon state.
var/custom_acid_overlay = null
var/datum/wires/wires = null
///Light systems, both shouldn't be active at the same time.
var/light_system = COMPLEX_LIGHT
///Range of the light in tiles. Zero means no light.
var/light_range = 0
///Intensity of the light. The stronger, the less shadows you will see on the lit area.
var/light_power = 1
///Hexadecimal RGB string representing the colour of the light. White by default.
var/light_color = COLOR_WHITE
///Boolean variable for toggleable lights. Has no effect without the proper light_system, light_range and light_power values.
var/light_on = TRUE
///Bitflags to determine lighting-related atom properties.
var/light_flags = NONE
// OVERLAY_LIGHT only values
/// An optional render_source to apply to this atom's light overlay
var/light_render_source = ""
// COMPLEX_LIGHT only values
/// Angle of light to show in light_dir
/// 360 is a circle, 90 is a cone, etc.
var/light_angle = 360
/// What angle to project light in
var/light_dir = NORTH
/// How many tiles "up" this light is. 1 is typical, should only really change this if it's a floor light
var/light_height = LIGHTING_HEIGHT
///Our light source. Don't fuck with this directly unless you have a good reason!
var/tmp/datum/light_source/light
///Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
var/tmp/list/light_sources
/// Last name used to calculate a color for the chatmessage overlays
var/chat_color_name
/// Last color calculated for the the chatmessage overlays
var/chat_color
/// A luminescence-shifted value of the last color calculated for chatmessage overlays
var/chat_color_darkened
// Use SET_BASE_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_x and y for you
///Default pixel x shifting for the atom's icon.
var/base_pixel_x = 0
///Default pixel y shifting for the atom's icon.
var/base_pixel_y = 0
// Use SET_BASE_VISUAL_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_w and z for you
///Default pixel w shifting for the atom's icon.
var/base_pixel_w = 0
///Default pixel z shifting for the atom's icon.
var/base_pixel_z = 0
///Used for changing icon states for different base sprites.
var/base_icon_state
///Icon-smoothing behavior.
var/smoothing_flags = NONE
///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags.
var/smoothing_junction = null
///What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it. Must be sorted.
var/list/smoothing_groups = null
///List of smoothing groups this atom can smooth with. If this is null and atom is smooth, it smooths only with itself. Must be sorted.
var/list/canSmoothWith = null
///AI controller that controls this atom. type on init, then turned into an instance during runtime
var/datum/ai_controller/ai_controller
/// forensics datum, contains fingerprints, fibres, blood_dna and hiddenprints on this atom
var/datum/forensics/forensics = null
/// Cached color for all blood on us to avoid doing constant math
var/cached_blood_color = null
/// Cached emissive alpha for all blood on us to avoid doing constant math
var/cached_blood_emissive = null
/// How this atom should react to having its astar blocking checked
var/can_astar_pass = CANASTARPASS_DENSITY
///whether ghosts can see screentips on it
var/ghost_screentips = FALSE
/// Flags to check for in can_perform_action. Used in alt-click & ctrl-click checks
var/interaction_flags_click = NONE
/// Flags to check for in can_perform_action for mouse drag & drop checks. To bypass checks see interaction_flags_atom mouse drop flags
var/interaction_flags_mouse_drop = NONE
/// Generally for niche objects, atoms blacklisted can spawn if enabled by spawner.
var/spawn_blacklisted = FALSE
/**
* Top level of the destroy chain for most atoms
*
* Cleans up the following:
* * Removes alternate apperances from huds that see them
* * qdels the reagent holder from atoms if it exists
* * clears the orbiters list
* * clears overlays and priority overlays
* * clears the light object
*/
/atom/Destroy(force)
if(alternate_appearances)
for(var/current_alternate_appearance in alternate_appearances)
var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance]
selected_alternate_appearance.remove_atom_from_hud(src)
if(reagents)
QDEL_NULL(reagents)
if(forensics)
QDEL_NULL(forensics)
if(atom_storage)
QDEL_NULL(atom_storage)
if(wires)
QDEL_NULL(wires)
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
// Checking length(overlays) before cutting has significant speed benefits
if (length(overlays))
overlays.Cut()
LAZYNULL(managed_overlays)
if(ai_controller)
QDEL_NULL(ai_controller)
if(light)
QDEL_NULL(light)
if (length(light_sources))
light_sources.Cut()
if(smoothing_flags & SMOOTH_QUEUED)
SSicon_smooth.remove_from_queues(src)
#ifndef DISABLE_DREAMLUAU
// These lists cease existing when src does, so we need to clear any lua refs to them that exist.
if(!(datum_flags & DF_STATIC_OBJECT))
DREAMLUAU_CLEAR_REF_USERDATA(contents)
DREAMLUAU_CLEAR_REF_USERDATA(filters)
DREAMLUAU_CLEAR_REF_USERDATA(overlays)
DREAMLUAU_CLEAR_REF_USERDATA(underlays)
#endif
return ..()
/atom/proc/handle_ricochet(obj/projectile/ricocheting_projectile)
var/turf/p_turf = get_turf(ricocheting_projectile)
var/face_direction = get_dir(src, p_turf) || get_dir(src, ricocheting_projectile)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.angle + 180))
var/a_incidence_s = abs(incidence_s)
if(a_incidence_s > 90 && a_incidence_s < 270)
return FALSE
if((ricocheting_projectile.armor_flag in list(BULLET, BOMB)) && ricocheting_projectile.ricochet_incidence_leeway)
if((a_incidence_s < 90 && a_incidence_s < 90 - ricocheting_projectile.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > ricocheting_projectile.ricochet_incidence_leeway))
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
ricocheting_projectile.set_angle(new_angle_s)
return TRUE
/// Whether the mover object can avoid being blocked by this atom, while arriving from (or leaving through) the border_dir.
/atom/proc/CanPass(atom/movable/mover, border_dir)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
if(SEND_SIGNAL(src, COMSIG_ATOM_TRIED_PASS, mover, border_dir) & COMSIG_COMPONENT_PERMIT_PASSAGE)
return TRUE
if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, border_dir)
// This is cheaper than calling the proc every time since most things dont override CanPassThrough
if(!mover.generic_canpass)
return mover.CanPassThrough(src, REVERSE_DIR(border_dir), .)
/// Returns true or false to allow the mover to move through src
/atom/proc/CanAllowThrough(atom/movable/mover, border_dir)
SHOULD_CALL_PARENT(TRUE)
//SHOULD_BE_PURE(TRUE)
if(mover.pass_flags & pass_flags_self)
return TRUE
if(mover.throwing && (pass_flags_self & LETPASSTHROW))
return TRUE
return !density
/**
* Is this atom currently located on centcom (or riding off into the sunset on a shuttle)
*
* Specifically, is it on the z level and within the centcom areas.
* You can also be in a shuttle during endgame transit.
*
* Used in gamemode to identify mobs who have escaped and for some other areas of the code
* who don't want atoms where they shouldn't be
*
* Returns TRUE if this atom is on centcom or an escape shuttle, or FALSE if not
*/
/atom/proc/onCentCom()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
// This doesn't necessarily check that we're at central command,
// but it checks for any shuttles which have finished are still in hyperspace
// (IE, stuff like the whiteship which fly off into the sunset and "escape")
if(is_reserved_level(current_turf.z))
return on_escaped_shuttle(ENDGAME_TRANSIT)
// From here on we only concern ourselves with people actually on the centcom Z
if(!is_centcom_level(current_turf.z))
return FALSE
if(istype(current_turf.loc, /area/centcom))
return TRUE
// Finally, check if we're on an escaped shuttle
return on_escaped_shuttle()
/**
* Is the atom in any of the syndicate areas
*
* Either in the syndie base, or any of their shuttles
*
* Also used in gamemode code for win conditions
*
* Returns TRUE if this atom is on the syndicate recon base, any of its shuttles, or an escape shuttle, or FALSE if not
*/
/atom/proc/onSyndieBase()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
// Syndicate base is loaded in a reserved level. If not reserved, we don't care.
if(!is_reserved_level(current_turf.z))
return FALSE
var/static/list/syndie_typecache = typecacheof(list(
/area/centcom/syndicate_mothership, // syndicate base itself
/area/shuttle/assault_pod, // steel rain
/area/shuttle/syndicate, // infiltrator
))
if(is_type_in_typecache(current_turf.loc, syndie_typecache))
return TRUE
// Finally, check if we're on an escaped shuttle
return on_escaped_shuttle()
/**
* Checks that we're on a shuttle that's escaped
*
* * check_for_launch_status - What launch status do we check for? Generally the two you want to check for are ENDGAME_LAUNCHED or ENDGAME_TRANSIT
*
* Returns TRUE if this atom is on a shuttle which is escaping or has escaped, or FALSE otherwise
*/
/atom/proc/on_escaped_shuttle(check_for_launch_status = ENDGAME_LAUNCHED)
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
for(var/obj/docking_port/mobile/mobile_docking_port as anything in SSshuttle.mobile_docking_ports)
if(mobile_docking_port.launch_status != check_for_launch_status)
continue
for(var/area/shuttle/shuttle_area as anything in mobile_docking_port.shuttle_areas)
if(shuttle_area == current_turf.loc)
return TRUE
return FALSE
/**
* Is the atom in an away mission
*
* Must be in the away mission z-level to return TRUE
*
* Also used in gamemode code for win conditions
*/
/atom/proc/onAwayMission()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
if(is_away_level(current_turf.z))
return TRUE
return FALSE
/**
* Called whenever an item is crafted, either via stack recipes or crafting recipes from the crafting menu
*
* By default, it just cycles through the list of movables used in the recipe and calls used_in_craft() for each of them,
* then it either moves them inside the object if they're in the list of parts for the recipe
* or deletes them if they're not.
* The proc can be overriden by subtypes, as long as it always call parent.
*/
/atom/proc/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_ON_CRAFT, components, current_recipe)
var/list/remaining_parts = LAZYLISTDUPLICATE(current_recipe?.parts)
var/list/parts_by_type = LAZYLISTDUPLICATE(remaining_parts)
for(var/parttype in parts_by_type) //necessary for our is_type_in_list() call with the zebra arg set to true
parts_by_type[parttype] = parttype
for(var/atom/movable/movable as anything in components) // machinery or structure objects in the list are guaranteed to be used up. We only check items.
movable.used_in_craft(src, current_recipe)
var/matched_type = is_type_in_list(movable, parts_by_type, zebra = TRUE)
if(!matched_type)
continue
if(isliving(movable.loc) && isitem(movable))
var/mob/living/living = movable.loc
living.transferItemToLoc(movable, src)
else
movable.forceMove(src)
if(matched_type)
remaining_parts[matched_type] -= 1
if(remaining_parts[matched_type] <= 0)
remaining_parts -= matched_type
///Take air from the passed in gas mixture datum
/atom/proc/assume_air(datum/gas_mixture/giver)
return null
///Remove air from this atom
/atom/proc/remove_air(amount)
return null
///Return the current air environment in this atom
/atom/proc/return_air()
if(loc)
return loc.return_air()
else
return null
///Return the air if we can analyze it
/atom/proc/return_analyzable_air()
return null
/atom/proc/Bumped(atom/movable/bumped_atom)
set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, bumped_atom)
/// Convenience proc to see if a container is open for chemistry handling
/atom/proc/is_open_container()
return is_refillable() && is_drainable()
/// Is this atom injectable into other atoms
/atom/proc/is_injectable()
return reagents && (reagents.flags & (INJECTABLE | REFILLABLE))
/// Can we draw from this atom with an injectable atom
/atom/proc/is_drawable()
return reagents && (reagents.flags & (DRAWABLE | DRAINABLE))
/// Can this atoms reagents be refilled
/atom/proc/is_refillable()
return reagents && (reagents.flags & REFILLABLE)
/// Is this atom drainable of reagents
/atom/proc/is_drainable()
return reagents && (reagents.flags & DRAINABLE)
/// Can we dunk stuff into this container?
/atom/proc/is_dunkable()
return reagents && (reagents.flags & DUNKABLE)
/** Handles exposing this atom to a list of reagents.
*
* Sends COMSIG_ATOM_EXPOSE_REAGENTS
* Calls expose_atom() for every reagent in the reagent list.
*
* Arguments:
* - [reagents][/list]: The list of reagents the atom is being exposed to.
* - [source][/datum/reagents]: The reagent holder the reagents are being sourced from.
* - methods: How the atom is being exposed to the reagents. Bitflags.
* - show_message: Whether to display anything to mobs when they are exposed.
*/
/atom/proc/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, show_message=TRUE)
. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, methods, show_message)
if(. & COMPONENT_NO_EXPOSE_REAGENTS)
return
for(var/datum/reagent/current_reagent as anything in reagents)
. |= current_reagent.expose_atom(src, reagents[current_reagent], methods)
/// Are you allowed to drop stuff inside this atom
/atom/proc/AllowDrop()
return FALSE
///Is this atom within 1 tile of another atom
/atom/proc/HasProximity(atom/movable/proximity_check_mob as mob|obj)
return
/// Sets the wire datum of an atom
/atom/proc/set_wires(datum/wires/new_wires)
wires = new_wires
///Return true if we're inside the passed in atom
/atom/proc/in_contents_of(container)//can take class or object instance as argument
if(ispath(container))
if(istype(src.loc, container))
return TRUE
else if(src in container)
return TRUE
return FALSE
/**
* Checks the atom's loc and calls update_held_items on it if it is a mob.
*
* This should only be used in situations when you are unable to use /datum/element/update_icon_updates_onmob for whatever reason.
* Check code/datums/elements/update_icon_updates_onmob.dm before using this. Adding that to the atom and calling update_appearance will work for most cases.
*
* Arguments:
* * mob/target - The mob to update the icons of. Optional argument, use if the atom's loc is not the mob you want to update.
*/
/atom/proc/update_inhand_icon(mob/target = loc)
SHOULD_CALL_PARENT(TRUE)
if(!istype(target))
return
target.update_held_items()
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_INHAND_ICON, target)
/**
* An atom we are buckled or is contained within us has tried to move
*
* Default behaviour is to send a warning that the user can't move while buckled as long
* as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks)
*/
/atom/proc/relaymove(mob/living/user, direction)
if(SEND_SIGNAL(src, COMSIG_ATOM_RELAYMOVE, user, direction) & COMSIG_BLOCK_RELAYMOVE)
return
if(buckle_message_cooldown <= world.time)
buckle_message_cooldown = world.time + 25
balloon_alert(user, "can't move while buckled!")
return
/**
* A special case of relaymove() in which the person relaying the move may be "driving" this atom
*
* This is a special case for vehicles and ridden animals where the relayed movement may be handled
* by the riding component attached to this atom. Returns TRUE as long as there's nothing blocking
* the movement, or FALSE if the signal gets a reply that specifically blocks the movement
*/
/atom/proc/relaydrive(mob/living/user, direction)
return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE)
///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list
/mob/living/proc/get_blood_dna_list()
var/datum/blood_type/blood_type = get_bloodtype()
if (!blood_type)
return
return list(blood_type.dna_string = blood_type)
///Get the mobs dna list
/mob/living/carbon/get_blood_dna_list()
var/datum/blood_type/blood_type = get_bloodtype()
if (!blood_type)
return
if (dna?.unique_enzymes)
return list(dna.unique_enzymes = blood_type)
return list(blood_type.dna_string = blood_type)
/mob/living/silicon/get_blood_dna_list()
return
///Is this atom in space
/atom/proc/isinspace()
if(isspaceturf(get_turf(src)))
return TRUE
else
return FALSE
/**
* If someone's trying to dump items onto our atom, where should they be dumped to?
*
* Return a loc to place objects, or null to stop dumping.
*/
/atom/proc/get_dumping_location()
return null
/**
* the vision impairment to give to the mob whose perspective is set to that atom
*
* (e.g. an unfocused camera giving you an impaired vision when looking through it)
*/
/atom/proc/get_remote_view_fullscreens(mob/user)
return
/**
* the sight changes to give to the mob whose perspective is set to that atom
*
* (e.g. A mob with nightvision loses its nightvision while looking through a normal camera)
*/
/atom/proc/update_remote_sight(mob/living/user)
return
/**
* Hook for running code when a dir change occurs
*
* Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc)
*/
/atom/proc/setDir(newdir)
SHOULD_CALL_PARENT(TRUE)
if (SEND_SIGNAL(src, COMSIG_ATOM_PRE_DIR_CHANGE, dir, newdir) & COMPONENT_ATOM_BLOCK_DIR_CHANGE)
newdir = dir
return
SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
var/oldDir = dir
dir = newdir
SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, oldDir, newdir)
if(smoothing_flags & SMOOTH_BORDER_OBJECT)
QUEUE_SMOOTH_NEIGHBORS(src)
/**
* Wash this atom
*
* This will clean it off any temporary stuff like blood. Override this in your item to add custom cleaning behavior.
* Returns true if any washing was necessary and thus performed
* Arguments:
* * clean_types: any of the CLEAN_ constants
* Returns: A bitflag if it successfully cleaned something: e.g. COMPONENT_CLEANED, or NONE if not. COMPONENT_CLEANED_GAIN_XP being flipped on signals whether the cleaning should yield cleaning xp.
*/
/atom/proc/wash(clean_types)
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, clean_types)
if(.)
return
// Basically "if has washable coloration"
if(length(atom_colours) >= WASHABLE_COLOUR_PRIORITY && atom_colours[WASHABLE_COLOUR_PRIORITY])
remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
return COMPONENT_CLEANED|COMPONENT_CLEANED_GAIN_XP
return NONE
///Where atoms should drop if taken from this atom
/atom/proc/drop_location()
var/atom/location = loc
if(!location)
return null
return location.AllowDrop() ? location : location.drop_location()
/**
* An atom has entered this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_ENTERED]
*/
/atom/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, arrived, old_loc, old_locs)
SEND_SIGNAL(arrived, COMSIG_ATOM_ENTERING, src, old_loc, old_locs)
/**
* An atom is attempting to exit this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_EXIT]
*/
/atom/Exit(atom/movable/leaving, direction)
// Don't call `..()` here, otherwise `Uncross()` gets called.
// See the doc comment on `Uncross()` to learn why this is bad.
if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT)
return FALSE
return TRUE
/**
* An atom has exited this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_EXITED]
*/
/atom/Exited(atom/movable/gone, direction)
SEND_SIGNAL(src, COMSIG_ATOM_EXITED, gone, direction)
SEND_SIGNAL(gone, COMSIG_ATOM_EXITING, src, direction)
///Return atom temperature
/atom/proc/return_temperature()
return
/atom/proc/process_recipes(mob/living/user, obj/item/processed_object, list/processing_recipes)
//Only one recipe? use the first
if(processing_recipes.len == 1)
StartProcessingAtom(user, processed_object, processing_recipes[1])
return
//Otherwise, select one with a radial
ShowProcessingGui(user, processed_object, processing_recipes)
///Creates the radial and processes the selected option
/atom/proc/ShowProcessingGui(mob/living/user, obj/item/processed_object, list/possible_options)
var/list/choices_to_options = list() //Dict of object name | dict of object processing settings
var/list/choices = list()
for(var/list/current_option as anything in possible_options)
var/atom/current_option_type = current_option[TOOL_PROCESSING_RESULT]
choices_to_options[initial(current_option_type.name)] = current_option
var/image/option_image = image(icon = initial(current_option_type.icon), icon_state = initial(current_option_type.icon_state))
choices += list("[initial(current_option_type.name)]" = option_image)
var/pick = show_radial_menu(user, src, choices, radius = 36, require_near = TRUE)
if(!pick)
return
StartProcessingAtom(user, processed_object, choices_to_options[pick])
/atom/proc/StartProcessingAtom(mob/living/user, obj/item/process_item, list/chosen_option)
var/processing_time = chosen_option[TOOL_PROCESSING_TIME]
var/sound_to_play = chosen_option[TOOL_PROCESSING_SOUND]
to_chat(user, span_notice("You start working on [src]."))
if(sound_to_play)
playsound(src, sound_to_play, 50, TRUE)
if(!process_item.use_tool(src, user, processing_time, volume=50))
return
var/atom/atom_to_create = chosen_option[TOOL_PROCESSING_RESULT]
var/list/atom/created_atoms = list()
var/amount_to_create = chosen_option[TOOL_PROCESSING_AMOUNT]
for(var/i = 1 to amount_to_create)
var/atom/created_atom = new atom_to_create(drop_location())
created_atom.OnCreatedFromProcessing(user, process_item, chosen_option, src)
if(custom_materials)
created_atom.set_custom_materials(custom_materials, 1 / amount_to_create)
created_atom.pixel_x = pixel_x
created_atom.pixel_y = pixel_y
if(i > 1)
created_atom.pixel_x += rand(-8,8)
created_atom.pixel_y += rand(-8,8)
created_atoms.Add(created_atom)
to_chat(user, span_notice("You manage to create [amount_to_create] [initial(atom_to_create.gender) == PLURAL ? "[initial(atom_to_create.name)]" : "[initial(atom_to_create.name)][plural_s(initial(atom_to_create.name))]"] from [src]."))
SEND_SIGNAL(src, COMSIG_ATOM_PROCESSED, user, process_item, created_atoms)
UsedforProcessing(user, process_item, chosen_option, created_atoms)
/atom/proc/UsedforProcessing(mob/living/user, obj/item/used_item, list/chosen_option, list/created_atoms)
qdel(src)
return
/atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/work_tool, list/chosen_option, atom/original_atom)
SHOULD_CALL_PARENT(TRUE)
if(HAS_TRAIT(original_atom, TRAIT_FOOD_SILVER))
ADD_TRAIT(src, TRAIT_FOOD_SILVER, INNATE_TRAIT) // stinky food always stinky
SEND_SIGNAL(src, COMSIG_ATOM_CREATEDBY_PROCESSING, original_atom, chosen_option)
if(user.mind)
ADD_TRAIT(src, TRAIT_FOOD_CHEF_MADE, REF(user.mind))
///Connect this atom to a shuttle
/atom/proc/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
return
/atom/proc/intercept_zImpact(list/falling_movables, levels = 1)
SHOULD_CALL_PARENT(TRUE)
. |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, falling_movables, levels)
///Setter for the `density` variable to append behavior related to its changing.
/atom/proc/set_density(new_value)
SHOULD_CALL_PARENT(TRUE)
if(density == new_value)
return
. = density
density = new_value
SEND_SIGNAL(src, COMSIG_ATOM_DENSITY_CHANGED)
///Setter for the `base_pixel_x` variable to append behavior related to its changing.
/atom/proc/set_base_pixel_x(new_value)
if(base_pixel_x == new_value)
return
. = base_pixel_x
base_pixel_x = new_value
pixel_x = pixel_x + base_pixel_x - .
///Setter for the `base_pixel_y` variable to append behavior related to its changing.
/atom/proc/set_base_pixel_y(new_value)
if(base_pixel_y == new_value)
return
. = base_pixel_y
base_pixel_y = new_value
pixel_y = pixel_y + base_pixel_y - .
// Not a valid operation, turfs and movables handle block differently
/atom/proc/set_explosion_block(explosion_block)
return
/**
* Returns true if this atom has gravity for the passed in turf
*
* Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with
* the forced gravity var.
*
* micro-optimized to hell because this proc is very hot, being called several times per movement every movement.
*
* HEY JACKASS, LISTEN
* IF YOU ADD SOMETHING TO THIS PROC, MAKE SURE /mob/living ACCOUNTS FOR IT
* Living mobs treat gravity in an event based manner. We've decomposed this proc into different checks
* for them to use. If you add more to it, make sure you do that, or things will behave strangely
*
* Gravity situations:
* * No gravity if you're not in a turf
* * No gravity if this atom is in is a space turf
* * No gravity if the area has NO_GRAVITY flag (space, ordnance bomb site, nearstation, solars)
* * Gravity if the area it's in always has gravity
* * Gravity if there's a gravity generator on the z level
* * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY
* * otherwise no gravity
*/
/atom/proc/has_gravity(turf/gravity_turf)
if(!isturf(gravity_turf))
gravity_turf = get_turf(src)
if(!gravity_turf)//no gravity in nullspace
return FALSE
var/list/forced_gravity = list()
SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity)
SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
if(length(forced_gravity))
var/positive_grav = max(forced_gravity)
var/negative_grav = min(min(forced_gravity), 0) //negative grav needs to be below or equal to 0
//our gravity is sum of the most massive positive and negative numbers returned by the signal
//so that adding two forced_gravity elements with an effect size of 1 each doesnt add to 2 gravity
//but negative force gravity effects can cancel out positive ones
return (positive_grav + negative_grav)
var/area/turf_area = gravity_turf.loc
return (!gravity_turf.force_no_gravity && !(turf_area.area_flags & NO_GRAVITY)) && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.default_gravity)
/**
* Used to set something as 'open' if it's being used as a supplypod
*
* Override this if you want an atom to be usable as a supplypod.
*/
/atom/proc/set_opened()
return
/**
* Used to set something as 'closed' if it's being used as a supplypod
*
* Override this if you want an atom to be usable as a supplypod.
*/
/atom/proc/set_closed()
return
///Called after the atom is 'tamed' for type-specific operations, Usually called by the tameable component but also other things.
/atom/proc/tamed(mob/living/tamer, obj/item/food)
SHOULD_CALL_PARENT(TRUE)
ADD_TRAIT(src, TRAIT_TAMED, INNATE_TRAIT)
return
/**
* Used to attempt to charge an object with a payment component.
*
* Use this if an atom needs to attempt to charge another atom.
*/
/atom/proc/attempt_charge(atom/sender, atom/target, extra_fees = 0)
return SEND_SIGNAL(sender, COMSIG_OBJ_ATTEMPT_CHARGE, target, extra_fees)
///Passes Stat Browser Panel clicks to the game and calls client click on an atom
/atom/Topic(href, list/href_list)
. = ..()
if(!usr?.client)
return
var/client/usr_client = usr.client
var/list/paramslist = list()
if(href_list["statpanel_item_click"])
switch(href_list["statpanel_item_click"])
if("left")
paramslist[LEFT_CLICK] = "1"
if("right")
paramslist[RIGHT_CLICK] = "1"
if("middle")
paramslist[MIDDLE_CLICK] = "1"
else
return
if(href_list["statpanel_item_shiftclick"])
paramslist[SHIFT_CLICK] = "1"
if(href_list["statpanel_item_ctrlclick"])
paramslist[CTRL_CLICK] = "1"
if(href_list["statpanel_item_altclick"])
paramslist[ALT_CLICK] = "1"
var/mouseparams = list2params(paramslist)
usr_client.Click(src, loc, null, mouseparams)
return TRUE
/atom/MouseEntered(location, control, params)
SSmouse_entered.hovers[usr.client] = src
/// Fired whenever this atom is the most recent to be hovered over in the tick.
/// Preferred over MouseEntered if you do not need information such as the position of the mouse.
/// Especially because this is deferred over a tick, do not trust that `client` is not null.
/atom/proc/on_mouse_enter(client/client)
SHOULD_NOT_SLEEP(TRUE)
var/mob/user = client?.mob
if (isnull(user))
return
SEND_SIGNAL(user, COMSIG_ATOM_MOUSE_ENTERED, src)
// Screentips
var/datum/hud/active_hud = user.hud_used
if(!active_hud)
return
var/screentips_enabled = active_hud.screentips_enabled
if(screentips_enabled == SCREENTIP_PREFERENCE_DISABLED || flags_1 & NO_SCREENTIPS_1)
active_hud.screentip_text.maptext = ""
return
var/lmb_rmb_line = ""
var/ctrl_lmb_ctrl_rmb_line = ""
var/alt_lmb_alt_rmb_line = ""
var/shift_lmb_ctrl_shift_lmb_line = ""
var/extra_lines = 0
var/extra_context = ""
var/used_name = name
if(isliving(user) || isovermind(user) || iscameramob(user) || (ghost_screentips && isobserver(user)))
var/obj/item/held_item = user.get_active_held_item()
if (user.mob_flags & MOB_HAS_SCREENTIPS_NAME_OVERRIDE)
var/list/returned_name = list(used_name)
var/name_override_returns = SEND_SIGNAL(user, COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER, returned_name, held_item, src)
if (name_override_returns & SCREENTIP_NAME_SET)
used_name = returned_name[1]
if (flags_1 & HAS_CONTEXTUAL_SCREENTIPS_1 || held_item?.item_flags & ITEM_HAS_CONTEXTUAL_SCREENTIPS)
var/list/context = list()
var/contextual_screentip_returns = \
SEND_SIGNAL(src, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, context, held_item, user) \
| (held_item && SEND_SIGNAL(held_item, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, context, src, user))
if (contextual_screentip_returns & CONTEXTUAL_SCREENTIP_SET)
var/screentip_images = active_hud.screentip_images
// LMB and RMB on one line...
var/lmb_text = build_context(context, SCREENTIP_CONTEXT_LMB, screentip_images)
var/rmb_text = build_context(context, SCREENTIP_CONTEXT_RMB, screentip_images)
if (lmb_text != "")
lmb_rmb_line = lmb_text
if (rmb_text != "")
lmb_rmb_line += " | [rmb_text]"
else if (rmb_text != "")
lmb_rmb_line = rmb_text
// Ctrl-LMB, Ctrl-RMB on one line...
if (lmb_rmb_line != "")
lmb_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_CTRL_LMB in context)
ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_LMB, screentip_images)
if (SCREENTIP_CONTEXT_CTRL_RMB in context)
if (ctrl_lmb_ctrl_rmb_line != "")
ctrl_lmb_ctrl_rmb_line += " | "
ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_RMB, screentip_images)
// Alt-LMB, Alt-RMB on one line...
if (ctrl_lmb_ctrl_rmb_line != "")
ctrl_lmb_ctrl_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_ALT_LMB in context)
alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_LMB, screentip_images)
if (SCREENTIP_CONTEXT_ALT_RMB in context)
if (alt_lmb_alt_rmb_line != "")
alt_lmb_alt_rmb_line += " | "
alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_RMB, screentip_images)
// Shift-LMB, Ctrl-Shift-LMB on one line...
if (alt_lmb_alt_rmb_line != "")
alt_lmb_alt_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_SHIFT_LMB in context)
shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_SHIFT_LMB, screentip_images)
if (SCREENTIP_CONTEXT_CTRL_SHIFT_LMB in context)
if (shift_lmb_ctrl_shift_lmb_line != "")
shift_lmb_ctrl_shift_lmb_line += " | "
shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_SHIFT_LMB, screentip_images)
if (shift_lmb_ctrl_shift_lmb_line != "")
extra_lines++
if(extra_lines)
extra_context = "<br><span class='subcontext'>[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line]</span>"
var/new_maptext
if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
new_maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
new_maptext = "<span class='context' style='text-align: center; color: [active_hud.screentip_color]'>[used_name][extra_context]</span>"
if (length(used_name) * 10 > active_hud.screentip_text.maptext_width)
INVOKE_ASYNC(src, PROC_REF(set_hover_maptext), client, active_hud, new_maptext)
return
active_hud.screentip_text.maptext = new_maptext
active_hud.screentip_text.maptext_y = 10 - (extra_lines > 0 ? 11 + 9 * (extra_lines - 1): 0)
/atom/proc/set_hover_maptext(client/client, datum/hud/active_hud, new_maptext)
var/map_height
WXH_TO_HEIGHT(client.MeasureText(new_maptext, null, active_hud.screentip_text.maptext_width), map_height)
active_hud.screentip_text.maptext = new_maptext
active_hud.screentip_text.maptext_y = 26 - map_height
/**
* This proc is used for telling whether something can pass by this atom in a given direction, for use by the pathfinding system.
*
* Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
* multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
*
* For turfs this will only be used if pathing_pass_method is TURF_PATHING_PASS_PROC
*
* Arguments:
* * to_dir - What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
* * pass_info - Datum that stores info about the thing that's trying to pass us
*
* IMPORTANT NOTE: /turf/proc/LinkBlockedWithAccess assumes that overrides of CanAStarPass will always return true if density is FALSE
* If this is NOT you, ensure you edit your can_astar_pass variable. Check __DEFINES/path.dm
**/
/atom/proc/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
if(pass_info.pass_flags & pass_flags_self)
return TRUE
. = !density
/// Logic for adding reskin components goes here. Override for atom-specific reskin setups.
/atom/proc/setup_reskins()
return