Files
S.P.L.U.R.T-Station-13/code/game/atoms.dm

1593 lines
53 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
layer = TURF_LAYER
plane = GAME_PLANE
appearance_flags = TILE_BOUND
/// 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
var/level = 2
///If non-null, overrides a/an/some in all cases
var/article
///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/ricochet_chance_mod = 1
///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
var/ricochet_damage_mod = 0.33
///Reagents holder
var/datum/reagents/reagents = null
///This atom's HUD (med/sec, etc) images. Associative list.
var/list/image/hud_list = null
///HUD images that this atom can provide.
var/list/hud_possible
///Value used to increment ex_act() if reactionary_explosions is on
var/explosion_block = 0
/// Flags for explosions
var/explosion_flags = NONE
/// Amount to decrease wave explosions by
var/wave_explosion_block = 0
/// Amount to multiply wave explosions by
var/wave_explosion_multiply = 1
//its inherent color, the colored paint applied on it, special color effect etc...
/**
* used to store the different colors on an atom
*
* its inherent color, the colored paint applied on it, special color effect etc...
*/
var/list/atom_colours
/// a very temporary list of overlays to remove
var/list/remove_overlays
/// a very temporary list of overlays to add
var/list/add_overlays
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
var/list/managed_vis_overlays
///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc
var/list/managed_overlays
///Proximity monitor associated with this atom
var/datum/proximity_monitor/proximity_monitor
///Last fingerprints to touch this atom
var/fingerprintslast
var/list/filter_data //For handling persistent filters
///Price of an item in a vending machine, overriding the base vending machine price. Define in terms of paycheck defines as opposed to raw numbers.
var/custom_price
///Price of an item in a vending machine, overriding the premium vending machine price. Define in terms of paycheck defines as opposed to raw numbers.
var/custom_premium_price
//List of datums orbiting this atom
var/datum/component/orbiter/orbiters
var/rad_flags = NONE // Will move to flags_1 when i can be arsed to
/// Radiation insulation types
var/rad_insulation = RAD_NO_INSULATION
///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials].
var/list/custom_materials
///Bitfield for how the atom handles materials.
var/material_flags = NONE
///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines.
var/material_modifier = 1
var/datum/wires/wires = null
var/icon/blood_splatter_icon
var/list/fingerprints
var/list/fingerprintshidden
var/list/blood_DNA
var/list/suit_fibers
/// 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
///Used for changing icon states for different base sprites.
var/base_icon_state
///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
var/list/targeted_by
///Reference to atom being orbited
var/atom/orbit_target
/**
* Called when an atom is created in byond (built in engine proc)
*
* Not a lot happens here in SS13 code, as we offload most of the work to the
* [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader
* if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate
* result is that the Intialize proc is called.
*
* We also generate a tag here if the DF_USE_TAG flag is set on the atom
*/
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
world.preloader_load(src)
if(datum_flags & DF_USE_TAG)
GenerateTag()
var/do_initialize = SSatoms.initialized
if(do_initialize != INITIALIZATION_INSSATOMS)
args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
if(SSatoms.InitAtom(src, args))
//we were deleted
return
/**
* The primary method that objects are setup in SS13 with
*
* we don't use New as we have better control over when this is called and we can choose
* to delay calls or hook other logic in and so forth
*
* During roundstart map parsing, atoms are queued for intialization in the base atom/New(),
* After the map has loaded, then Initalize is called on all atoms one by one. NB: this
* is also true for loading map templates as well, so they don't Initalize until all objects
* in the map file are parsed and present in the world
*
* If you're creating an object at any point after SSInit has run then this proc will be
* immediately be called from New.
*
* mapload: This parameter is true if the atom being loaded is either being intialized during
* the Atom subsystem intialization, or if the atom is being loaded from the map template.
* If the item is being created at runtime any time after the Atom subsystem is intialized then
* it's false.
*
* You must always call the parent of this proc, otherwise failures will occur as the item
* will not be seen as initalized (this can lead to all sorts of strange behaviour, like
* the item being completely unclickable)
*
* You must not sleep in this proc, or any subprocs
*
* Any parameters from new are passed through (excluding loc), naturally if you're loading from a map
* there are no other arguments
*
* Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur.
*
* Note: the following functions don't call the base for optimization and must copypasta handling:
* * [/turf/proc/Initialize]
* * [/turf/open/space/proc/Initialize]
*/
/atom/proc/Initialize(mapload, ...)
SHOULD_NOT_SLEEP(TRUE)
SHOULD_CALL_PARENT(TRUE)
if(flags_1 & INITIALIZED_1)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
if(loc)
SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc`
//atom color stuff
if(color)
add_atom_colour(color, FIXED_COLOUR_PRIORITY)
if (light_power && light_range)
update_light()
if (opacity && isturf(loc))
var/turf/T = loc
T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways.
if (canSmoothWith)
canSmoothWith = typelist("canSmoothWith", canSmoothWith)
// apply materials properly from the default custom_materials value
set_custom_materials(custom_materials)
ComponentInitialize()
return INITIALIZE_HINT_NORMAL
/**
* Late Intialization, for code that should run after all atoms have run Intialization
*
* To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize]
* proc must return the hint
* [INITIALIZE_HINT_LATELOAD] otherwise you will never be called.
*
* useful for doing things like finding other machines on GLOB.machines because you can guarantee
* that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
* code has been run
*/
/atom/proc/LateInitialize()
set waitfor = FALSE
/// Put your [AddComponent] calls here
/atom/proc/ComponentInitialize()
return
/**
* 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()
if(alternate_appearances)
for(var/K in alternate_appearances)
var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K]
AA.remove_from_hud(src)
if(reagents)
QDEL_NULL(reagents)
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
LAZYCLEARLIST(overlays)
for(var/i in targeted_by)
var/mob/M = i
LAZYREMOVE(M.do_afters, src)
targeted_by = null
QDEL_NULL(light)
return ..()
/**
* Checks if a projectile should ricochet off of us. Projectiles get final say.
* [__DEFINES/projectiles.dm] for return values.
*/
/atom/proc/check_projectile_ricochet(obj/item/projectile/P)
return (flags_1 & DEFAULT_RICOCHET_1)? PROJECTILE_RICOCHET_YES : PROJECTILE_RICOCHET_NO
/atom/proc/handle_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
var/a_incidence_s = abs(incidence_s)
if(a_incidence_s > 90 && a_incidence_s < 270)
return FALSE
if((P.flag in list(BULLET, BOMB)) && P.ricochet_incidence_leeway)
if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway))
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
return TRUE
/atom/proc/CanPass(atom/movable/mover, turf/target)
//SHOULD_CALL_PARENT(TRUE)
if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, target)
// This is cheaper than calling the proc every time since most things dont override CanPassThrough
if(!mover.generic_canpass)
return mover.CanPassThrough(src, target, .)
/// Returns true or false to allow the mover to move through src
/atom/proc/CanAllowThrough(atom/movable/mover, turf/target)
//SHOULD_CALL_PARENT(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
*
* Specifically, is it on the z level and within the centcom areas
*
* You can also be in a shuttleshuttle 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
*/
/atom/proc/onCentCom()
var/turf/T = get_turf(src)
if(!T)
return FALSE
if(is_reserved_level(T.z))
for(var/A in SSshuttle.mobile)
var/obj/docking_port/mobile/M = A
if(M.launch_status == ENDGAME_TRANSIT)
for(var/place in M.shuttle_areas)
var/area/shuttle/shuttle_area = place
if(T in shuttle_area)
return TRUE
if(!is_centcom_level(T.z))//if not, don't bother
return FALSE
//Check for centcom itself
if(istype(T.loc, /area/centcom))
return TRUE
//Check for centcom shuttles
for(var/A in SSshuttle.mobile)
var/obj/docking_port/mobile/M = A
if(M.launch_status == ENDGAME_LAUNCHED)
for(var/place in M.shuttle_areas)
var/area/shuttle/shuttle_area = place
if(T in shuttle_area)
return TRUE
/**
* Is the atom in any of the centcom syndicate areas
*
* Either in the syndie base on centcom, or any of their shuttles
*
* Also used in gamemode code for win conditions
*/
/atom/proc/onSyndieBase()
var/turf/T = get_turf(src)
if(!T)
return FALSE
if(!is_centcom_level(T.z))//if not, don't bother
return FALSE
if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod))
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/T = get_turf(src)
if(!T)
return FALSE
if(is_away_level(T.z))
return TRUE
return FALSE
/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
if(does_attack_animation)
user.DelayNextAction(CLICK_CD_MELEE)
log_combat(user, src, "punched", "hulk powers")
user.do_attack_animation(src, ATTACK_EFFECT_SMASH)
/atom/proc/CheckParts(list/parts_list)
for(var/A in parts_list)
if(istype(A, /datum/reagent))
if(!reagents)
reagents = new()
reagents.reagent_list.Add(A)
reagents.conditional_update()
else if(ismovable(A))
var/atom/movable/M = A
if(isliving(M.loc))
var/mob/living/L = M.loc
L.transferItemToLoc(M, src)
else
M.forceMove(src)
//common name
/atom/proc/update_multiz(prune_on_fail = FALSE)
return FALSE
/atom/proc/assume_air(datum/gas_mixture/giver)
return null
/atom/proc/assume_air_moles(datum/gas_mixture/giver, moles)
return null
/atom/proc/assume_air_ratio(datum/gas_mixture/giver, ratio)
return null
/atom/proc/remove_air(amount)
return null
/atom/proc/remove_air_ratio(ratio)
return null
/atom/proc/transfer_air(datum/gas_mixture/taker, amount)
return null
/atom/proc/transfer_air_ratio(datum/gas_mixture/taker, ratio)
return null
/atom/proc/return_air()
if(loc)
return loc.return_air()
else
return null
/atom/proc/check_eye(mob/user)
return
/atom/proc/Bumped(atom/movable/AM)
set waitfor = FALSE
// Convenience procs to see if a container is open for chemistry handling
/atom/proc/is_open_container()
return is_refillable() && is_drainable()
/atom/proc/is_injectable(allowmobs = TRUE)
return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE))
/atom/proc/is_drawable(allowmobs = TRUE)
return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE))
/atom/proc/is_refillable()
return reagents && (reagents.reagents_holder_flags & REFILLABLE)
/atom/proc/is_drainable()
return reagents && (reagents.reagents_holder_flags & DRAINABLE)
/atom/proc/AllowDrop()
return FALSE
/atom/proc/CheckExit()
return TRUE
/atom/proc/HasProximity(atom/movable/AM as mob|obj)
return
/atom/proc/emp_act(severity)
var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
if(!(protection & EMP_PROTECT_WIRES) && istype(wires))
wires.emp_pulse(severity)
return protection // Pass the protection value collected here upwards
/**
* React to a hit by a projectile object
*
* Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/item/projectile/proc/on_hit] on the projectile
*
* @params
* P - projectile
* def_zone - zone hit
* piercing_hit - is this hit piercing or normal?
*/
/atom/proc/bullet_act(obj/item/projectile/P, def_zone, piercing_hit = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
. = P.on_hit(src, 0, def_zone, piercing_hit)
//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it.
// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first.
/atom/proc/shove_act(mob/living/target, mob/living/user)
return FALSE
/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
/atom/proc/get_examine_name(mob/user)
. = "\a [src]"
var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]")
if(article)
. = "[article] [src]"
override[EXAMINE_POSITION_ARTICLE] = article
var/should_override = FALSE
if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED)
should_override = TRUE
if(blood_DNA && !istype(src, /obj/effect/decal))
override[EXAMINE_POSITION_BEFORE] = " blood-stained "
should_override = TRUE
if(should_override)
. = override.Join("")
///Generate the full examine string of this atom (including icon for goonchat)
/atom/proc/get_examine_string(mob/user, thats = FALSE)
return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
/atom/proc/examine(mob/user)
. = list("[get_examine_string(user, TRUE)].[desc ? "<hr>" : null]")
if(desc)
. += desc
if(custom_materials)
. += "<hr>"
var/list/materials_list = list()
for(var/i in custom_materials)
var/datum/material/M = i
materials_list += "[M.name]"
. += "<u>It is made out of [english_list(materials_list)]</u>."
if(reagents)
. += "<hr>"
if(reagents.reagents_holder_flags & TRANSPARENT)
. += "It contains:"
if(length(reagents.reagent_list))
if(user.can_see_reagents()) //Show each individual reagent
for(var/datum/reagent/R in reagents.reagent_list)
. += "[R.volume] units of [R.name]"
else //Otherwise, just show the total volume
var/total_volume = 0
for(var/datum/reagent/R in reagents.reagent_list)
total_volume += R.volume
. += "[total_volume] units of various reagents"
else
. += "Nothing."
else if(reagents.reagents_holder_flags & AMOUNT_VISIBLE)
if(reagents.total_volume)
. += "<span class='notice'>It has [reagents.total_volume] unit\s left.</span>"
else
. += "<span class='danger'>It's empty.</span>"
SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
/**
* Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_TIME (default 1.5 seconds)
*
* This is where you can put extra information on something that may be superfluous or not important in critical gameplay
* moments, while allowing people to manually double-examine to take a closer look
*
* Produces a signal [COMSIG_PARENT_EXAMINE_MORE]
*/
/atom/proc/examine_more(mob/user)
. = list()
SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .)
if(!LAZYLEN(.)) // lol ..length
return list("<span class='notice'><i>You examine [src] closer, but find nothing of interest...</i></span>")
/**
* Updates the appearence of the icon
*
* Mostly delegates to update_name, update_desc, and update_icon
*
* Arguments:
* - updates: A set of bitflags dictating what should be updated. Defaults to [ALL]
*/
/atom/proc/update_appearance(updates=ALL)
//SHOULD_NOT_SLEEP(TRUE)
//SHOULD_CALL_PARENT(TRUE)
. = NONE
updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_APPEARANCE, updates)
if(updates & UPDATE_NAME)
. |= update_name(updates)
if(updates & UPDATE_DESC)
. |= update_desc(updates)
if(updates & UPDATE_ICON)
. |= update_icon(updates)
/// Updates the name of the atom
/atom/proc/update_name(updates=ALL)
//SHOULD_CALL_PARENT(TRUE)
return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_NAME, updates)
/// Updates the description of the atom
/atom/proc/update_desc(updates=ALL)
//SHOULD_CALL_PARENT(TRUE)
return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_DESC, updates)
/// Updates the icon of the atom
/atom/proc/update_icon(updates=ALL)
SIGNAL_HANDLER
//SHOULD_CALL_PARENT(TRUE)
. = NONE
updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates)
if(updates & UPDATE_ICON_STATE)
update_icon_state()
. |= UPDATE_ICON_STATE
if(updates & UPDATE_OVERLAYS)
if(LAZYLEN(managed_vis_overlays))
SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
var/list/new_overlays = update_overlays(updates)
if(managed_overlays)
cut_overlay(managed_overlays)
managed_overlays = null
if(length(new_overlays))
if (length(new_overlays) == 1)
managed_overlays = new_overlays[1]
else
managed_overlays = new_overlays
add_overlay(new_overlays)
. |= UPDATE_OVERLAYS
. |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
/// Updates the icon state of the atom
/atom/proc/update_icon_state()
//SHOULD_CALL_PARENT(TRUE)
return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
/**
* Builds a list of overlays for the atom, this will not apply them.
* If you need to update overlays, use [update_icon(UPDATE_OVERLAYS)],
* This proc is intended to be overridden.
*/
/atom/proc/update_overlays()
SHOULD_CALL_PARENT(TRUE)
. = list()
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
/atom/proc/relaymove(mob/living/user)
if(!istype(user))
return //why are you buckling nonliving mobs to atoms?
if(user.buckle_message_cooldown <= world.time)
user.buckle_message_cooldown = world.time + 50
to_chat(user, "<span class='warning'>You can't move while buckled to [src]!</span>")
/atom/proc/contents_explosion(severity, target, origin)
return //For handling the effects of explosions on contents that would not normally be effected
/atom/proc/ex_act(severity, target, origin)
set waitfor = FALSE
contents_explosion(severity, target, origin)
SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
/**
* Called when a wave explosion hits this atom. Do not override this.
*
* Returns explosion power to "allow through".
*/
/atom/proc/wave_explode(power, datum/wave_explosion/explosion, dir)
set waitfor = FALSE
// SHOULD_NOT_SLEEP(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_WAVE_EX_ACT, args)
. = wave_ex_act(power, explosion, dir) // this must happen first for stuff like destruction/damage to tick.
if(isnull(.))
stack_trace("wave_ex_act on [type] failed to return a number. defaulting to no blocking.")
return power
if((explosion_flags & EXPLOSION_FLAG_DENSITY_DEPENDENT) && !density)
return power // no block
else if((explosion_flags & EXPLOSION_FLAG_HARD_OBSTACLE) && !QDELETED(src))
return FALSE // fully blocked
/**
* Called when a wave explosion hits this atom.
*
* Returns explosion power to "allow through". Standard handling and flag overrides in [wave_explode()].
*/
/atom/proc/wave_ex_act(power, datum/wave_explosion/explosion, dir)
// SHOULD_NOT_SLEEP(TRUE)
return power * wave_explosion_multiply - wave_explosion_block
/atom/proc/blob_act(obj/structure/blob/B)
SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
return
/atom/proc/fire_act(exposed_temperature, exposed_volume)
SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
return
/**
* React to being hit by a thrown object
*
* Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense
* and under normal gravity.
*
* Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is
* deleted shortly after hitting something (during explosions or other massive events that
* throw lots of items around - singularity being a notable example)
*/
/atom/proc/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
SEND_SIGNAL(src, COMSIG_ATOM_HITBY, hitting_atom, skipcatch, hitpush, blocked, throwingdatum)
if(density && !has_gravity(hitting_atom)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
addtimer(CALLBACK(src, PROC_REF(hitby_react), hitting_atom), 2)
/**
* We have have actually hit the passed in atom
*
* Default behaviour is to move back from the item that hit us
*/
/atom/proc/hitby_react(atom/movable/harmed_atom)
if(harmed_atom && isturf(harmed_atom.loc))
step(harmed_atom, turn(harmed_atom.dir, 180))
/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube)
return
//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/blood_id = get_blood_id()
if(!(blood_id in GLOB.blood_reagent_types))
return
return list("color" = BLOOD_COLOR_HUMAN, "blendmode" = BLEND_MULTIPLY, "ANIMAL DNA" = "Y-")
/mob/living/carbon/get_blood_dna_list()
var/blood_id = get_blood_id()
if(!(blood_id in GLOB.blood_reagent_types))
return
var/list/blood_dna = list()
if(dna)
blood_dna["color"] = dna.species.exotic_blood_color //so when combined, the list grows with the number of colors
blood_dna["blendmode"] = dna.species.exotic_blood_blend_mode
blood_dna[dna.unique_enzymes] = dna.blood_type
else
blood_dna["color"] = BLOOD_COLOR_HUMAN
blood_dna["blendmode"] = BLEND_MULTIPLY
blood_dna["UNKNOWN DNA"] = "X*"
return blood_dna
/mob/living/carbon/alien/get_blood_dna_list()
return list("color" = BLOOD_COLOR_XENO, "blendmode" = BLEND_MULTIPLY, "UNKNOWN DNA" = "X*")
//to add a mob's dna info into an object's blood_DNA list.
/atom/proc/transfer_mob_blood_dna(mob/living/L)
// Returns 0 if we have that blood already
var/new_blood_dna = L.get_blood_dna_list()
if(!new_blood_dna)
return FALSE
LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it.
var/old_length = blood_DNA.len
blood_DNA |= new_blood_dna
var/changed = FALSE
if(!blood_DNA["color"])
blood_DNA["color"] = new_blood_dna["color"]
changed = TRUE
else
var/old = blood_DNA["color"]
blood_DNA["color"] = BlendRGB(blood_DNA["color"], new_blood_dna["color"])
changed = old != blood_DNA["color"]
blood_DNA["blendmode"] = new_blood_dna["blendmode"]
if(blood_DNA.len == old_length)
return FALSE
return changed
//to add blood dna info to the object's blood_DNA list
/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases)
LAZYINITLIST(blood_DNA)
var/old_length = blood_DNA.len
blood_DNA |= blood_dna
if(blood_DNA.len > old_length)
. = TRUE
//some new blood DNA was added
if(!blood_dna["color"])
return
if(!blood_DNA["color"])
blood_DNA["color"] = blood_dna["color"]
else
blood_DNA["color"] = BlendRGB(blood_DNA["color"], blood_dna["color"])
blood_DNA["blendmode"] = blood_dna["blendmode"]
//to add blood from a mob onto something, and transfer their dna info
/atom/proc/add_mob_blood(mob/living/M)
var/list/blood_dna = M.get_blood_dna_list()
if(!blood_dna)
return FALSE
return add_blood_DNA(blood_dna, M.diseases)
//to add blood onto something, with blood dna info to include.
/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
return FALSE
/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
return transfer_blood_dna(blood_dna, diseases)
/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
. = ..()
if(!.)
return
add_blood_overlay()
/obj/item/proc/add_blood_overlay()
if(!blood_DNA.len)
return
if(initial(icon) && initial(icon_state))
blood_splatter_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
blood_splatter_icon.Blend(blood_DNA_to_color(), ICON_MULTIPLY)
add_overlay(blood_splatter_icon)
/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
. = ..()
transfer_blood = rand(2, 4)
/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src
if(!B)
B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases)
B.transfer_blood_dna(blood_dna, diseases) //give blood info to the blood decal.
return TRUE //we bloodied the floor
/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
if(head)
head.add_blood_DNA(blood_dna, diseases)
update_inv_head()
else if(wear_mask)
wear_mask.add_blood_DNA(blood_dna, diseases)
update_inv_wear_mask()
if(wear_neck)
wear_neck.add_blood_DNA(blood_dna, diseases)
update_inv_neck()
if(wear_suit)
wear_suit.add_blood_DNA(blood_dna, diseases)
update_inv_wear_suit()
else if(w_uniform)
w_uniform.add_blood_DNA(blood_dna, diseases)
update_inv_w_uniform()
//skyrat edit
else if(w_underwear)
w_underwear.add_blood_DNA(blood_dna, diseases)
update_inv_w_underwear()
else if(w_socks)
w_socks.add_blood_DNA(blood_dna, diseases)
update_inv_w_socks()
else if(w_shirt)
w_shirt.add_blood_DNA(blood_dna, diseases)
update_inv_w_shirt()
else if(wrists)
wrists.add_blood_DNA(blood_dna, diseases)
update_inv_wrists()
//
if(gloves)
var/obj/item/clothing/gloves/G = gloves
G.add_blood_DNA(blood_dna, diseases)
else
transfer_blood_dna(blood_dna, diseases)
bloody_hands = rand(2, 4)
update_inv_gloves() //handles bloody hands overlays and updating
return TRUE
/atom/proc/blood_DNA_to_color()
return (blood_DNA && blood_DNA["color"]) || BLOOD_COLOR_HUMAN
/atom/proc/blood_DNA_to_blend()
if(blood_DNA && !isnull(blood_DNA["blendmode"]))
return blood_DNA["blendmode"]
return BLEND_MULTIPLY
/atom/proc/clean_blood()
. = blood_DNA? TRUE : FALSE
blood_DNA = null
/atom/proc/wash_cream()
return TRUE
/atom/proc/isinspace()
if(isspaceturf(get_turf(src)))
return TRUE
else
return FALSE
/atom/proc/handle_fall()
return
/atom/proc/singularity_act()
return
/atom/proc/singularity_pull(obj/singularity/S, current_size)
SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size)
/atom/proc/acid_act(acidpwr, acid_volume)
SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume)
/atom/proc/emag_act()
return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
/atom/proc/rad_act(strength)
var/turf/open/pool/PL = get_turf(src)
if(istype(PL))
if(PL.filled == TRUE)
strength *= 0.15
SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength)
/atom/proc/narsie_act()
SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT)
/atom/proc/ratvar_act()
SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT)
/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
return FALSE
/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode)
return FALSE
/**
* Respond to a electric bolt action on our item
*
* Default behaviour is to return, we define here to allow for cleaner code later on
*/
/atom/proc/zap_act(power, zap_flags, shocked_targets)
return
/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user)
if(GetComponent(/datum/component/storage))
return component_storage_contents_dump_act(src_object, user)
return FALSE
/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user)
var/list/things = src_object.contents()
var/datum/progressbar/progress = new(user, things.len, src)
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
if(STR == src_object)
progress.end_progress()
return
while(do_after(user, 1 SECONDS, src, NONE, FALSE, CALLBACK(STR, TYPE_PROC_REF(/datum/component/storage, handle_mass_item_insertion), things, src_object, user, progress)))
stoplag(1)
progress.end_progress()
to_chat(user, "<span class='notice'>You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.</span>")
if(user.active_storage) //refresh the HUD to show the transfered contents
user.active_storage.ui_show(user)
return TRUE
/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
return null
//This proc is called on the location of an atom when the atom is Destroy()'d
/atom/proc/handle_atom_del(atom/A)
SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A)
//called when the turf the atom resides on is ChangeTurfed
/atom/proc/HandleTurfChange(turf/T)
for(var/a in src)
var/atom/A = a
A.HandleTurfChange(T)
//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
/atom/proc/setDir(newdir, ismousemovement=FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
dir = newdir
//If a mob logouts/logins in side of an object you can use this proc
/atom/proc/on_log(login)
if(loc)
loc.on_log(login)
/*
Atom Colour Priority System
A System that gives finer control over which atom colour to colour the atom with.
The "highest priority" one is always displayed as opposed to the default of
"whichever was set last is displayed"
*/
/*
Adds an instance of colour_type to the atom's atom_colours list
*/
/atom/proc/add_atom_colour(coloration, colour_priority)
if(!atom_colours || !atom_colours.len)
atom_colours = list()
atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
if(!coloration)
return
if(colour_priority > atom_colours.len)
return
atom_colours[colour_priority] = coloration
update_atom_colour()
/*
Removes an instance of colour_type from the atom's atom_colours list
*/
/atom/proc/remove_atom_colour(colour_priority, coloration)
if(!atom_colours)
atom_colours = list()
atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
if(colour_priority > atom_colours.len)
return
if(coloration && atom_colours[colour_priority] != coloration)
return //if we don't have the expected color (for a specific priority) to remove, do nothing
atom_colours[colour_priority] = null
update_atom_colour()
/*
Resets the atom's color to null, and then sets it to the highest priority
colour available
*/
/atom/proc/update_atom_colour()
if(!atom_colours)
atom_colours = list()
atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
color = null
for(var/C in atom_colours)
if(islist(C))
var/list/L = C
if(L.len)
color = L
return
else if(C)
color = C
return
/atom/vv_edit_var(var_name, var_value)
if(!GLOB.Debug2)
flags_1 |= ADMIN_SPAWNED_1
. = ..()
switch(var_name)
if(NAMEOF(src, color))
add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
/atom/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---------")
if(!ismovable(src))
var/turf/curturf = get_turf(src)
if(curturf)
. += "<option value='?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]'>Jump To</option>"
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform")
VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
// VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate")
VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
// VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
/atom/vv_do_topic(list/href_list)
. = ..()
if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT))
if(!reagents)
var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num
if(amount)
create_reagents(amount)
if(reagents)
var/chosen_id = choose_reagent_id(usr)
if(chosen_id)
var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num
if(amount)
reagents.add_reagent(chosen_id, amount)
log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]")
message_admins("<span class='notice'>[key_name(usr)] has added [amount] units of [chosen_id] to [src]</span>")
if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN))
usr.client.cmd_admin_explosion(src)
if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN))
usr.client.cmd_admin_emp(src)
if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT))
var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate")
var/matrix/M = transform
switch(result)
if("Scale")
var/x = input(usr, "Choose x mod","Transform Mod") as null|num
var/y = input(usr, "Choose y mod","Transform Mod") as null|num
if(!isnull(x) && !isnull(y))
transform = M.Scale(x,y)
if("Translate")
var/x = input(usr, "Choose x mod","Transform Mod") as null|num
var/y = input(usr, "Choose y mod","Transform Mod") as null|num
if(!isnull(x) && !isnull(y))
transform = M.Translate(x,y)
if("Rotate")
var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num
if(!isnull(angle))
transform = M.Turn(angle)
if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT))
var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
if(newname)
vv_auto_rename(newname)
if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
var/client/C = usr.client
C?.open_filter_editor(src)
/atom/vv_get_header()
. = ..()
var/refid = REF(src)
. += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "<b id='name'>[src]</b>")]"
. += "<br><font size='1'><a href='?_src_=vars;[HrefToken()];rotatedatum=[refid];rotatedir=left'><<</a> <a href='?_src_=vars;[HrefToken()];datumedit=[refid];varnameedit=dir' id='dir'>[dir2text(dir) || dir]</a> <a href='?_src_=vars;[HrefToken()];rotatedatum=[refid];rotatedir=right'>>></a></font>"
/atom/proc/drop_location()
var/atom/L = loc
if(!L)
return null
return L.AllowDrop() ? L : L.drop_location()
/atom/proc/vv_auto_rename(newname)
name = newname
/atom/Entered(atom/movable/AM, atom/oldLoc)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc)
SEND_SIGNAL(AM, COMSIG_ATOM_ENTERING, src, oldLoc)
/atom/Exit(atom/movable/AM, atom/newLoc)
. = ..()
if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT)
return FALSE
/atom/Exited(atom/movable/AM, atom/newLoc)
SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc)
/atom/proc/return_temperature()
return
// Tool behavior procedure. Redirects to tool-specific procs by default.
// You can override it to catch all tool interactions, for use in complex deconstruction procs.
// Just don't forget to return ..() in the end.
/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type)
switch(tool_type)
if(TOOL_CROWBAR)
return crowbar_act(user, I)
if(TOOL_MULTITOOL)
return multitool_act(user, I)
if(TOOL_SCREWDRIVER)
return screwdriver_act(user, I)
if(TOOL_WRENCH)
return wrench_act(user, I)
if(TOOL_WIRECUTTER)
return wirecutter_act(user, I)
if(TOOL_WELDER)
return welder_act(user, I)
if(TOOL_ANALYZER)
return analyzer_act(user, I)
// Tool-specific behavior procs. To be overridden in subtypes.
/atom/proc/crowbar_act(mob/living/user, obj/item/I)
return
/atom/proc/multitool_act(mob/living/user, obj/item/I)
return
/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE)
if(!I.tool_behaviour == TOOL_MULTITOOL)
if(user && !silent)
to_chat(user, "<span class='warning'>[I] has no data buffer!</span>")
return FALSE
return TRUE
/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I)
/atom/proc/wrench_act(mob/living/user, obj/item/I)
return
/atom/proc/wirecutter_act(mob/living/user, obj/item/I)
return
/atom/proc/welder_act(mob/living/user, obj/item/I)
return
/atom/proc/analyzer_act(mob/living/user, obj/item/I)
return
/atom/proc/GenerateTag()
return
/**
* Called after a shuttle is loaded **from map template initially**.
*
* @params
* * port - Mobile port/shuttle
* * dock - Stationary dock the shuttle's at
* * idnum - ID number of the shuttle
*/
/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
return
// Generic logging helper
/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
if(!log_globally)
return
var/log_text = "[key_name(src)] [message] [loc_name(src)]"
switch(message_type)
if(LOG_ATTACK)
log_attack(log_text)
if(LOG_SAY)
log_say(log_text)
if(LOG_WHISPER)
log_whisper(log_text)
if(LOG_EMOTE)
log_emote(log_text)
if(LOG_SUBTLER)
log_subtler(log_text)
if(LOG_DSAY)
log_dsay(log_text)
if(LOG_PDA)
log_pda(log_text)
if(LOG_CHAT)
log_chat(log_text)
if(LOG_COMMENT)
log_comment(log_text)
if(LOG_TELECOMMS)
log_telecomms(log_text)
if(LOG_OOC)
log_ooc(log_text)
if(LOG_ADMIN)
log_admin(log_text)
if(LOG_ADMIN_PRIVATE)
log_admin_private(log_text)
if(LOG_ASAY)
log_adminsay(log_text)
if(LOG_OWNERSHIP)
log_game(log_text)
if(LOG_GAME)
log_game(log_text)
if(LOG_MECHA)
log_mecha(log_text)
if(LOG_SHUTTLE)
log_shuttle(log_text)
if(LOG_ECON)
log_econ(log_text)
else
stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
log_game(log_text)
// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
var/prefix = tag ? "([tag]) " : ""
var/suffix = forced_by ? " FORCED by [forced_by]" : ""
log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
// Helper for logging of messages with only one sender and receiver
/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
if(!tag)
stack_trace("Unspecified tag for private message")
tag = "UNKNOWN"
source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
if(source != target)
target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
/**
* Log a combat message in the attack log
*
* Arguments:
* * atom/user - argument is the actor performing the action
* * atom/target - argument is the target of the action
* * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.)
* * atom/object - is a tool with which the action was made (usually an item)
* * addition - is any additional text, which will be appended to the rest of the log line
*/
/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
var/ssource = key_name(user)
var/starget = key_name(target)
var/mob/living/living_target = target
var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
var/sobject = ""
if(object)
sobject = " with [key_name(object)]"
var/saddition = ""
if(addition)
saddition = " [addition]"
var/postfix = "[sobject][saddition][hp]"
var/message = "has [what_done] [starget][postfix]"
user.log_message(message, LOG_ATTACK, color="red")
if(user != target)
var/reverse_message = "has been [what_done] by [ssource][postfix]"
target.log_message(reverse_message, LOG_VICTIM, color="orange", log_globally=FALSE)
/**
* log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over
*
* Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time
* TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority
*
* Arguments:
* * victim- The guy who got wounded
* * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it
* * dealt_damage- How much damage is associated with the attack that dealt with this wound.
* * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack
* * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present
* * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods
*/
/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll)
if(QDELETED(victim) || !suffered_wound)
return
var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote?
if(dealt_damage)
message += " | Damage: [dealt_damage]"
// The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
if(base_roll)
message += " (rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])"
if(dealt_wound_bonus)
message += " | WB: [dealt_wound_bonus]"
if(dealt_bare_wound_bonus)
message += " | BWB: [dealt_bare_wound_bonus]"
victim.log_message(message, LOG_ATTACK, color="blue")
/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
p["priority"] = priority
filter_data[name] = p
update_filters()
/atom/proc/update_filters()
filters = null
filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE)
for(var/f in filter_data)
var/list/data = filter_data[f]
var/list/arguments = data.Copy()
arguments -= "priority"
filters += filter(arglist(arguments))
UNSETEMPTY(filter_data)
/atom/proc/transition_filter(name, time, list/new_params, easing, loop, parallel = TRUE)
var/filter = get_filter(name)
if(!filter)
return
var/list/old_filter_data = filter_data[name]
var/list/params = old_filter_data.Copy()
for(var/thing in new_params)
params[thing] = new_params[thing]
animate(filter, new_params, time = time, easing = easing, loop = loop, flags = (parallel ? ANIMATION_PARALLEL : 0))
for(var/param in params)
filter_data[name][param] = params[param]
/atom/proc/change_filter_priority(name, new_priority)
if(!filter_data || !filter_data[name])
return
filter_data[name]["priority"] = new_priority
update_filters()
/obj/item/update_filters()
. = ..()
update_action_buttons()
/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
return filters[filter_data.Find(name)]
/// Returns the indice in filters of the given filter name.
/// If it is not found, returns null.
/atom/proc/get_filter_index(name)
return filter_data?.Find(name)
/atom/proc/remove_filter(name_or_names)
if(!filter_data)
return
var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names)
for(var/name in names)
if(filter_data[name])
filter_data -= name
update_filters()
/atom/proc/clear_filters()
filter_data = null
filters = null
/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
. |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels)
///Sets the custom materials for an item.
/atom/proc/set_custom_materials(list/materials, multiplier = 1)
if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways
for(var/i in custom_materials)
var/datum/material/custom_material = SSmaterials.GetMaterialRef(i)
custom_material.on_removed(src, custom_materials[i], material_flags) //Remove the current materials
if(!length(materials))
custom_materials = null
return
if(material_flags)
for(var/x in materials)
var/datum/material/custom_material = SSmaterials.GetMaterialRef(x)
custom_material.on_applied(src, materials[x] * multiplier * material_modifier, material_flags)
custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier)
/**
* 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
*
* Gravity situations:
* * No gravity if you're not in a turf
* * No gravity if this atom is in is a space turf
* * 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/T)
if(!T || !isturf(T))
T = get_turf(src)
if(!T)
return FALSE
var/list/forced_gravity = list()
SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity)
if(!forced_gravity.len)
SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
if(forced_gravity.len)
var/max_grav
for(var/i in forced_gravity)
max_grav = max(max_grav, i)
return max_grav
if(isspaceturf(T)) // Turf never has gravity
return FALSE
var/area/A = get_area(T)
if(A.has_gravity) // Areas which always has gravity
return A.has_gravity
else
// There's a gravity generator on our z level
if(GLOB.gravity_generators["[T.z]"])
var/max_grav = 0
for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"])
max_grav = max(G.setting,max_grav)
return max_grav
return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY)
/**
* Causes effects when the atom gets hit by a rust effect from heretics
*
* Override this if you want custom behaviour in whatever gets hit by the rust
*/
/atom/proc/rust_heretic_act()
return
/**
* 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/setOpened()
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/setClosed()
return
//Update the screentip to reflect what we're hoverin over
/atom/MouseEntered(location, control, params)
. = ..()
var/mob/user = usr
if(isnull(user))
return
if(!GET_CLIENT(user))
return
// Screentips
var/datum/hud/active_hud = user.hud_used
if(!active_hud)
return
var/screentips_enabled = user.client.prefs.screentip_pref
if(screentips_enabled == SCREENTIP_PREFERENCE_DISABLED || (flags_1 & NO_SCREENTIPS_1))
active_hud.screentip_text.maptext = ""
return
active_hud.screentip_text.maptext_y = 10 // 10px lines us up with the action buttons top left corner
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 = ""
if ((isliving(user) || isovermind(user) || isaicamera(user)) && (user.client.prefs.screentip_pref != SCREENTIP_PREFERENCE_NO_CONTEXT))
var/obj/item/held_item = user.get_active_held_item()
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 = user.client.prefs.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>"
//first extra line pushes atom name line up 10px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
active_hud.screentip_text.maptext_y = -1 + (extra_lines - 1) * -9
if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
active_hud.screentip_text.maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
active_hud.screentip_text.maptext = "<span class='context' style='text-align: center; color: [user.client.prefs.screentip_color]'>[name][extra_context]</span>"