Files

1121 lines
39 KiB
Plaintext

/obj
layer = OBJ_LAYER
plane = OBJ_PLANE
pass_flags_self = ATOM_PASS_OVERHEAD_THROW
animate_movement = SLIDE_STEPS
rad_flags = NONE
integrity_enabled = TRUE
armor_type = /datum/armor/object/default
// Name says everything. I'm crying.
var/temporary_legacy_dont_auto_handle_obj_damage_for_mechs = FALSE
//? Flags
/// object flags, see __DEFINES/_flags/obj_flags.dm
var/obj_flags = OBJ_MELEE_TARGETABLE | OBJ_RANGE_TARGETABLE
//? Access - see [modules/jobs/access.dm]
/// If set, all of these accesses are needed to access this object.
// todo: getter / setter, enforced cached & deduped lists
var/list/req_access
/// If set, at least one of these accesses are needed to access this object.
// todo: getter / setter, enforced cached & deduped lists
var/list/req_one_access
//? Climbing
/// people can climb onto us
var/climb_allowed = FALSE
/// people are allowed to knock climbers off of us
var/climb_knockable = TRUE
/// list of people currently climbing on us
/// currently, only /mob/living is allowed to climb
var/list/mob/living/climbing
/// nominal climb delay before modifiers
var/climb_delay = 3.5 SECONDS
//* Coloration
/// coloration mode
var/coloration_mode = COLORATION_MODE_NONE
/// coloration:
/// in MULTIPLY / MATRIX, this is unused as we just use color var
/// in RG, RB, GB, RGB, OVERLAYS, GAGS modes, this is a concat'd string of #aabbcc#112233... for the n colors in this.
var/coloration
/// coloration amount
///
/// only used in:
/// * overlays mode
/// * later, GAGS mode
var/coloration_amount
//? Depth
/// logical depth in pixels. people can freely run from high to low objects without being blocked.
///
/// negative values are ignored as turfs are assumed to be depth 0
/// unless we change that in the future
var/depth_level = 28
/// contributes to depth when we're on a turf
var/depth_projected = FALSE
//* Economy *//
/// economic category for objects
var/economic_category_obj = ECONOMIC_CATEGORY_OBJ_DEFAULT
/// intrinsic worth without accounting containing reagents / materials - applies in static and dynamic mode.
var/worth_intrinsic = 0
/// intrinsic elasticity as factor, 2 = 2x easy to inflate market
///
/// * set this to a multiple of WORTH_ELASTICITY_DEFAULT if possible.
var/worth_elasticity = WORTH_ELASTICITY_DEFAULT
/// default worth flags to use when buying
var/worth_buy_flags = GET_WORTH_INTRINSIC | GET_WORTH_CONTAINING
/// default worth flags to use when selling
var/worth_sell_flags = GET_WORTH_INTRINSIC | GET_WORTH_CONTAINING
/**
* * DANGER * - do not touch this variable unless you know what you are doing.
*
* This signifies that procs have a non-negligible randomization on a *freshly-spawned* instance of this object.
* This is not the case for most closets / lockers / crates / storage that spawn with items.
* In those cases, use the other variables to control its static worth.
*
* This means that things like cargo should avoid "intuiting" the value of this object
* through initial()'s alone.
*/
var/worth_dynamic = FALSE
//? Integrity
integrity = 200
integrity_max = 200
integrity_failure = 50
/// Standard integrity examine
var/integrity_examine = TRUE
//? Materials
/// Material amounts in us
/// For sheets, this represents the per-sheet amount.
/// material id = amount
/// * This may be a typelist, use is_typelist to check.
/// * Always use get_base_materials to get this list unless you know what you're doing.
/// * This may use typepath keys at compile time, but is immediately converted to material IDs on boot.
/// * This does not include material parts.
var/list/materials_base
/// material parts - lets us track what we're made of
/// this is either a lazy key-value list of material keys to instances,
/// or a single material instance
/// or null for defaults.
/// ! This must be set for anything using the materials system.
/// ! This is what determines how many, and if something uses the material parts system.
/// * This may be a typelist, use is_typelist to check.
/// * Use [MATERIAL_DEFAULT_DISABLED] if something doesn't use material parts system.
/// * Use [MATERIAL_DEFAULT_ABSTRACTED] if something uses the abstraction API to implement material parts themselves.
/// * Use [MATERIAL_DEFAULT_NONE] if something uses material parts system, but has only one material with default of null.
/// * This may use typepath keys or material IDs at compile time / on map, but is immediately converted to material instances on boot.
/// * Always use [get_material_parts] to get this list unless you know what you're doing.
/// * This var should never be changed from a list to a normal value or vice versa at runtime,
/// as we use this to detect which material update proc to call!
var/list/material_parts = MATERIAL_DEFAULT_DISABLED
/// part constraints - lets us track what we need to be made of
/// this is either a lazy key-value list of material keys to floats (representing constraint bitfields)
/// or a single float (representing constraint bitfield).
/// or null, if we don't care about constraints
/// ! This is what determines what constraints are used by the material parts system.
/// * Use null if something doesn't use material parts system, or if something uses the abstraction API to implement material parts themselves.
/// * This var should never be changed from a list to a normal value or vice versa at runtime, again, like material_parts
/// as it is closely linked to material_parts
var/tmp/list/material_constraints = null
/// material costs - lets us track the costs of what we're made of.
/// this is either a lazy key-value list of material keys to cost in cm3,
/// or a single number.
/// * This may be a typelist, use is_typelist to check.
/// * Always use [get_material_part_costs] to get this list unless you know what you're doing.
/// * This may use typepath keys at compile time, but is immediately converted to material IDs on boot.
/// * This should still be set even if you are implementing material_parts yourself!
// todo: abstraction API for this when we need it.
var/tmp/list/material_costs
/// material part considered primary.
var/tmp/material_primary
/// make the actual materials multiplied by this amount. used by lathes to prevent duping with efficiency upgrades.
/// * checked in get_materials().
var/material_multiplier = 1
//* Persistence *//
// todo: we need a version and entity string ID system for update durability!!
/// persistence state flags
var/obj_persist_status = NONE
/// if set, we persist via static object persistence. this is our ID and must be unique for a given map level.
/// will override and prevent dynamic persistence.
var/obj_persist_static_id
/// static namespacing / binding mode
var/obj_persist_static_mode = OBJ_PERSIST_STATIC_MODE_MAP
/// on static map/level bind mode, this is to determine which level/map we're bound to
/// once bound, even if we go to another level, we are treated as this level.
/// binding is done during load.
/// * this variable is not visible and should not be edited in the map editor.
var/tmp/obj_persist_static_bound_id
/// if set, we are currently dynamically persisting. this is our ID and must be unique for a given map level.
/// this id will not collide with static id.
/// * this variable is not visible and should not be edited in the map editor.
var/tmp/obj_persist_dynamic_id
/// dynamic persistence state flags
/// * this variable is not visible and should not be edited in the map editor.
var/tmp/obj_persist_dynamic_status = NONE
//* Rotation
/// rotation behavior flags
var/obj_rotation_flags = NONE
//? Sounds
/// volume when breaking out using resist process
var/breakout_sound = 'sound/effects/grillehit.ogg'
/// volume when breaking out using resist process
var/breakout_volume = 100
//? Systems - naming convention is 'obj_[system]'
/// cell slot system
var/datum/object_system/cell_slot/obj_cell_slot
/// storage system
var/datum/object_system/storage/obj_storage
//* Underfloor *//
/// do we hide underfloor?
///
/// * uses OBJ_UNDERFLOOR_* defines
/// * gets set_underfloor(is_underfloor = TRUE | FALSE) called on init or turf init
/// * we are assumed to not be underfloor when we are first made
/// * if you want a var to track this make one yourself; we don't have one for memory concerns.
var/hides_underfloor = OBJ_UNDERFLOOR_UNSUPPORTED
/// call update icon after update_hiding_underfloor()
///
/// * update_icon() called regardless of [hides_underfloor_defaulting] if TRUE
var/hides_underfloor_update_icon = FALSE
/// are we fully INVISIBILITY_ABSTRACT while hidden?
///
/// this has implications.
/// * range(), view(), and others will not target things that are abstract-invisible
/// * verbs don't work at all (not a bad thing necessarily)
/// * basically this thing acts like it's not there
///
/// this is sometimes desired for things like mostly impactless objects for performance,
/// but is not always desireable
///
/// as an example, if the codebase's t-ray scanners use range(), you're not going to get anything
/// that is abstract-invisible on a scan.
///
/// * if this is FALSE, we use [INVISIBILITY_UNDERFLOOR]
/// * automatic invisibility updates require [hides_underfloor_defaulting]
var/hides_underfloor_invisibility_abstract = FALSE
/// perform default behavior if hiding underfloor?
var/hides_underfloor_defaulting = TRUE
//? misc / legacy
/// Set when a player renames a renamable object.
var/renamed_by_player = FALSE
var/w_class // Size of the object.
var/pry = 0 //Used in attackby() to open doors
//! LEGACY: DO NOT USE
var/in_use = 0 // If we have a user using us, this will be set on. We will check if the user has stopped using us, and thus stop updating and LAGGING EVERYTHING!
var/show_messages
var/preserve_item = 0 //whether this object is preserved when its owner goes into cryo-storage, gateway, etc
var/can_speak = 0 //For MMIs and admin trickery. If an object has a brainmob in its contents, set this to 1 to allow it to speak.
var/show_examine = TRUE // Does this pop up on a mob when the mob is examined?
var/register_as_dangerous_object = FALSE // Should this tell its turf that it is dangerous automatically?
/obj/Initialize(mapload)
if(register_as_dangerous_object)
register_dangerous_to_step()
. = ..()
// cache base materials if it's not modified
if(!isnull(materials_base) && !(obj_flags & OBJ_MATERIALS_MODIFIED))
if(has_typelist(NAMEOF(src, materials_base)))
materials_base = get_typelist(NAMEOF(src, materials_base))
else
// preprocess
materials_base = SSmaterials.preprocess_kv_keys_to_ids(materials_base)
materials_base = typelist(NAMEOF(src, materials_base), materials_base)
// cache material costs if it's not modified
if(islist(material_costs))
if(has_typelist(NAMEOF(src, material_costs)))
material_costs = get_typelist(NAMEOF(src, material_costs))
else
// preprocess
material_costs = SSmaterials.preprocess_kv_keys_to_ids(material_costs)
material_costs = typelist(NAMEOF(src, material_costs), material_costs)
// cache material constraints if it's not modified
if(islist(material_constraints))
if(has_typelist(NAMEOF(src, material_constraints)))
material_constraints = get_typelist(NAMEOF(src, material_constraints))
else
material_constraints = typelist(NAMEOF(src, material_constraints), material_constraints)
// initialize material parts system
if(material_parts != MATERIAL_DEFAULT_DISABLED)
// process material parts only if it wasn't set already
// this allows children of /obj to modify their material parts prior to init.
if(islist(material_parts) && !(obj_flags & OBJ_MATERIAL_PARTS_MODIFIED))
if(has_typelist(material_parts))
material_parts = get_typelist(material_parts)
else
// preprocess
material_parts = SSmaterials.preprocess_kv_values_to_ids(material_parts)
material_parts = typelist(NAMEOF(src, material_parts), material_parts)
// init material parts only if it wasn't initialized already
if(!(obj_flags & OBJ_MATERIAL_INITIALIZED))
init_material_parts()
if(hides_underfloor != OBJ_UNDERFLOOR_UNSUPPORTED && isturf(loc))
initialize_hiding_underfloor(mapload)
/obj/Destroy()
for(var/datum/prototype/material_trait/trait as anything in material_traits)
trait.on_remove(src, material_traits[trait])
if(IS_TICKING_MATERIALS(src))
STOP_TICKING_MATERIALS(src)
if(register_as_dangerous_object)
unregister_dangerous_to_step()
SStgui.close_uis(src)
SSnanoui.close_uis(src)
QDEL_NULL(obj_cell_slot)
QDEL_NULL(obj_storage)
return ..()
/obj/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
obj_storage?.on_parent_moved(old_loc, forced)
if(register_as_dangerous_object)
var/turf/old_turf = get_turf(old_loc)
var/turf/new_turf = get_turf(src)
if(old_turf != new_turf)
old_turf?.unregister_dangerous_object(src)
new_turf?.register_dangerous_object(src)
/obj/worth_contents(flags)
. = ..()
if(obj_storage?.use_worth_containing)
for(var/obj/item/item in obj_storage.contents())
. += item
/obj/proc/updateUsrDialog()
if(in_use)
var/is_in_use = 0
var/list/nearby = viewers(1, src)
for(var/mob/M in nearby)
if ((M.client && M.machine == src))
is_in_use = 1
src.attack_hand(M)
if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot))
if (!(usr in nearby))
if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
is_in_use = 1
src.attack_ai(usr)
// check for MUTATION_TELEKINESIS users
if (istype(usr, /mob/living/carbon/human))
var/mob/living/carbon/human/H = usr
if(H.get_held_item_of_type(/obj/item/tk_grab))
if(!(H in nearby))
if(H.client && H.machine==src)
is_in_use = 1
src.attack_hand(H)
in_use = is_in_use
/obj/proc/updateDialog()
// Check that people are actually using the machine. If not, don't update anymore.
if(in_use)
var/list/nearby = viewers(1, src)
var/is_in_use = 0
for(var/mob/M in nearby)
if ((M.client && M.machine == src))
is_in_use = 1
src.interact(M)
var/ai_in_use = AutoUpdateAI(src)
if(!ai_in_use && !is_in_use)
in_use = 0
/obj/attack_ghost(mob/user)
nano_ui_interact(user)
ui_interact(user)
. = ..()
/mob/proc/unset_machine()
machine = null
/mob/proc/set_machine(var/obj/O)
if(src.machine)
unset_machine()
src.machine = O
if(istype(O))
O.in_use = 1
/obj/item/proc/updateSelfDialog()
var/mob/M = src.loc
if(istype(M) && M.client && M.machine == src)
src.attack_self(M)
/obj/proc/hear_talk(mob/M as mob, text, verb, datum/prototype/language/speaking)
if(talking_atom)
talking_atom.catchMessage(text, M)
/*
var/mob/mo = locate(/mob) in src
if(mo)
var/rendered = "<span class='game say'><span class='name'>[M.name]: </span> <span class='message'>[text]</span></span>"
mo.show_message(rendered, 2)
*/
return
/obj/proc/hear_signlang(mob/M as mob, text, verb, datum/prototype/language/speaking) // Saycode gets worse every day.
return FALSE
/obj/proc/see_emote(mob/M as mob, text, var/emote_type)
return
// Used to mark a turf as containing objects that are dangerous to step onto.
/obj/proc/register_dangerous_to_step()
var/turf/T = get_turf(src)
if(T)
T.register_dangerous_object(src)
/obj/proc/unregister_dangerous_to_step()
var/turf/T = get_turf(src)
if(T)
T.unregister_dangerous_object(src)
// Test for if stepping on a tile containing this obj is safe to do, used for things like landmines and cliffs.
/obj/proc/is_safe_to_step(mob/living/L)
return TRUE
//* Cells / Inducers *//
/**
* get cell slot
*/
/obj/get_cell(inducer)
. = ..()
if(.)
return
if(obj_cell_slot?.primary && !isnull(obj_cell_slot.cell) && (!inducer || obj_cell_slot.receive_inducer))
return obj_cell_slot.cell
/obj/inducer_scan(obj/item/inducer/I, list/things_to_induce, inducer_flags)
. = ..()
if(!isnull(obj_cell_slot?.cell) && !obj_cell_slot.primary && obj_cell_slot.receive_inducer)
things_to_induce += obj_cell_slot.cell
//* Climbing *//
/obj/alt_clicked_on(mob/user, location, control, list/params)
if(obj_storage?.allow_open_via_alt_click && user.Reachability(src))
obj_storage.auto_handle_interacted_open(new /datum/event_args/actor(user))
return TRUE
return ..()
/obj/MouseDroppedOn(atom/dropping, mob/user, proximity, params)
if(drag_drop_climb_interaction(user, dropping))
return CLICKCHAIN_DO_NOT_PROPAGATE
return ..()
/obj/OnMouseDrop(atom/over, mob/user, proximity, params)
if(!isnull(obj_storage) && proximity)
// clickdrag to self storage open
if(obj_storage.allow_open_via_clickdrag_to_self && ismob(over) && over == user)
if(obj_storage.auto_handle_interacted_open(new /datum/event_args/actor(user)))
return CLICKCHAIN_DO_NOT_PROPAGATE
// clickdrag to other obj transfer
if(obj_storage.allow_outbound_mass_transfer && obj_storage.allow_clickdrag_mass_transfer && isobj(over))
var/obj/object = over
if(object.obj_storage.allow_inbound_mass_transfer)
obj_storage.auto_handle_interacted_mass_transfer(new /datum/event_args/actor(user), object.obj_storage)
return CLICKCHAIN_DO_NOT_PROPAGATE
// clickdrag to ground mass dumping
if(obj_storage.allow_quick_empty_via_clickdrag && obj_storage.allow_quick_empty && isturf(over))
var/turf/turf = over
obj_storage.auto_handle_interacted_mass_dumping(new /datum/event_args/actor(user), turf)
return CLICKCHAIN_DO_NOT_PROPAGATE
return ..()
/obj/proc/drag_drop_climb_interaction(mob/user, atom/dropping)
if(!climb_allowed)
return FALSE
if(user != dropping)
return FALSE
// todo: better hinting to user for this
if(buckle_allowed && user.a_intent != INTENT_GRAB)
return FALSE
if(!user.Adjacent(src))
return FALSE
. = TRUE
INVOKE_ASYNC(src, PROC_REF(attempt_climb_on), user)
/obj/proc/attempt_climb_on(mob/living/climber, delay_mod = 1)
if(!istype(climber))
return FALSE
if(!allow_climb_on(climber))
climber.action_feedback(SPAN_WARNING("You can't climb onto [src]!"), src)
return FALSE
if(INTERACTING_WITH_FOR(climber, src, INTERACTING_FOR_CLIMB))
return FALSE
climber.visible_action_feedback(
target = src,
visible_hard = SPAN_WARNING("[climber] starts climbing onto \the [src]!"),
hard_range = MESSAGE_RANGE_COMBAT_LOUD)
START_INTERACTING_WITH(climber, src, INTERACTING_FOR_CLIMB)
LAZYDISTINCTADD(climbing, climber)
. = do_after(climber, climb_delay * delay_mod, src, mobility_flags = MOBILITY_CAN_MOVE | MOBILITY_CAN_STAND | MOBILITY_IS_STANDING)
if(!INTERACTING_WITH_FOR(climber, src, INTERACTING_FOR_CLIMB))
. = FALSE
LAZYREMOVE(climbing, climber)
STOP_INTERACTING_WITH(climber, src, INTERACTING_FOR_CLIMB)
if(!. || !allow_climb_on(climber))
climber.action_feedback(SPAN_WARNING("You couldn't climb onto [src]!"), src)
return FALSE
do_climb_on(climber)
/obj/proc/allow_climb_on(mob/living/climber)
if(!istype(climber))
return FALSE
if(!climb_allowed)
return FALSE
if(!climber.Adjacent(src))
return FALSE
return TRUE
/obj/proc/do_climb_on(mob/living/climber)
climber.visible_message(SPAN_WARNING("[climber] climbs onto \the [src]!"))
// all this effort just to avoid a splurtstation railing spare ID speedrun incident
var/old_depth = climber.depth_current
if(climber.depth_current < depth_level)
// basically, we don't force move them, we just elevate them to our level
// if something else blocks them, L + ratio + get parried
climber.change_depth(depth_level)
if(!step_towards(climber, do_climb_target(climber)))
climber.change_depth(old_depth)
/obj/proc/do_climb_target(mob/living/climber)
return get_turf(src)
/obj/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(user.a_intent == INTENT_HARM)
return ..()
. = ..()
if(.)
return
if(length(climbing) && user.a_intent == INTENT_DISARM)
user.visible_message(SPAN_WARNING("[user] slams against \the [src]!"))
user.do_attack_animation(src)
shake_climbers()
return TRUE
/obj/proc/shake_climbers()
for(var/mob/living/climber as anything in climbing)
climber.afflict_knockdown(1 SECONDS)
climber.visible_message(SPAN_WARNING("[climber] is toppled off of \the [src]!"))
STOP_INTERACTING_WITH(climber, src, INTERACTING_FOR_CLIMB)
climbing = null
// disabled old, but fun code that knocks people on their ass if something is shaken without climbers
// being ontop of it
// consider re-enabling this sometime.
/*
for(var/mob/living/M in get_turf(src))
if(M.lying) return //No spamming this on people.
M.afflict_paralyze(20 * 3)
to_chat(M, "<span class='danger'>You topple as \the [src] moves under you!</span>")
if(prob(25))
var/damage = rand(15,30)
var/mob/living/carbon/human/H = M
if(!istype(H))
to_chat(H, "<span class='danger'>You land heavily!</span>")
M.adjustBruteLoss(damage)
return
var/obj/item/organ/external/affecting
switch(pick(list("ankle","wrist","head","knee","elbow")))
if("ankle")
affecting = H.get_organ(pick(BP_L_FOOT, BP_R_FOOT))
if("knee")
affecting = H.get_organ(pick(BP_L_LEG, BP_R_LEG))
if("wrist")
affecting = H.get_organ(pick(BP_L_HAND, BP_R_HAND))
if("elbow")
affecting = H.get_organ(pick(BP_L_ARM, BP_R_ARM))
if("head")
affecting = H.get_organ(BP_HEAD)
if(affecting)
to_chat(M, "<span class='danger'>You land heavily on your [affecting.name]!</span>")
affecting.take_damage(damage, 0)
if(affecting.parent)
affecting.parent.add_autopsy_data("Misadventure", damage)
else
to_chat(H, "<span class='danger'>You land heavily!</span>")
H.adjustBruteLoss(damage)
H.update_damage_overlay()
H.update_health()
*/
//* Coloration *//
/obj/proc/amount_coloration()
switch(coloration_mode)
if(COLORATION_MODE_NONE)
return 0
if(COLORATION_MODE_MATRIX, COLORATION_MODE_MULTIPLY)
return 1
if(COLORATION_MODE_RB_MATRIX)
return 2
if(COLORATION_MODE_RG_MATRIX)
return 2
if(COLORATION_MODE_GB_MATRIX)
return 2
if(COLORATION_MODE_RGB_MATRIX)
return 3
return coloration_amount
/obj/proc/set_coloration_matrix(list/color_matrix)
if(coloration_mode != COLORATION_MODE_MATRIX)
return
color = color_matrix
// well, we can't pack a matrix :/
coloration = null
/obj/proc/set_coloration_parts(list/colors)
switch(coloration_mode)
if(COLORATION_MODE_MATRIX, COLORATION_MODE_MULTIPLY)
ASSERT(length(colors) == 1)
color = colors[1]
if(COLORATION_MODE_RG_MATRIX)
ASSERT(length(colors) == 2)
var/list/red_decoded = rgb2num(colors[1])
var/list/green_decoded = rgb2num(colors[2])
color = list(
red_decoded[1] / 255, red_decoded[2] / 255, red_decoded[3] / 255, 0,
green_decoded[1] / 255, green_decoded[2] / 255, green_decoded[3] / 255, 0,
0, 0, 0, 0,
0, 0, 0, 1,
)
if(COLORATION_MODE_GB_MATRIX)
ASSERT(length(colors) == 2)
var/list/green_decoded = rgb2num(colors[1])
var/list/blue_decoded = rgb2num(colors[2])
color = list(
0, 0, 0, 0,
green_decoded[1] / 255, green_decoded[2] / 255, green_decoded[3] / 255, 0,
blue_decoded[1] / 255, blue_decoded[2] / 255, blue_decoded[3] / 255, 0,
0, 0, 0, 1,
)
if(COLORATION_MODE_RB_MATRIX)
ASSERT(length(colors) == 2)
var/list/red_decoded = rgb2num(colors[1])
var/list/blue_decoded = rgb2num(colors[2])
color = list(
red_decoded[1] / 255, red_decoded[2] / 255, red_decoded[3] / 255, 0,
0, 0, 0, 0,
blue_decoded[1] / 255, blue_decoded[2] / 255, blue_decoded[3] / 255, 0,
0, 0, 0, 1,
)
if(COLORATION_MODE_RGB_MATRIX)
ASSERT(length(colors) == 3)
var/list/red_decoded = rgb2num(colors[1])
var/list/green_decoded = rgb2num(colors[2])
var/list/blue_decoded = rgb2num(colors[3])
color = list(
red_decoded[1] / 255, red_decoded[2] / 255, red_decoded[3] / 255, 0,
green_decoded[1] / 255, green_decoded[2] / 255, green_decoded[3] / 255, 0,
blue_decoded[1] / 255, blue_decoded[2] / 255, blue_decoded[3] / 255, 0,
0, 0, 0, 1,
)
if(COLORATION_MODE_OVERLAYS)
ASSERT(length(colors) == coloration_amount)
// todo: implement; we'll probably have to hook both update_overlays as well as
// todo: something in [code/modules/mob/inventory/rendering.dm].
pass()
if(COLORATION_MODE_NONE)
// why are we here?
ASSERT(!length(colors))
color = null
coloration = pack_coloration_string(colors)
/obj/proc/set_coloration_packed(packed_colors)
var/list/unpacked = unpack_coloration_string(packed_colors)
return set_coloration_parts(unpacked)
/obj/proc/get_coloration_parts()
if(!(coloration_mode & COLORATION_MODES_COMPLEX))
switch(coloration_mode)
if(COLORATION_MODE_MULTIPLY)
// matrices are unsupported
return list(islist(color)? "#ffffff" : color)
if(COLORATION_MODE_MATRIX)
// unsupported
return list("#ffffff")
return list()
return unpack_coloration_string(coloration)
/obj/proc/get_coloration_packed()
switch(coloration_mode)
if(COLORATION_MODE_MULTIPLY)
// matrices are unsupported
return islist(color)? "#ffffff" : color
if(COLORATION_MODE_MATRIX)
// unsupported
return "#ffffff"
return coloration
//* EMP *//
/obj/emp_act(severity)
. = ..()
if(obj_cell_slot?.receive_emp)
obj_cell_slot?.cell?.emp_act(severity)
if(obj_storage?.pass_emp_inside)
// ugh
if(obj_storage.pass_emp_weaken && severity < 4)
var/pass_severity = severity - 1
for(var/atom/movable/inside in obj_storage.contents())
inside.emp_act(pass_severity)
else
for(var/atom/movable/inside in obj_storage.contents())
inside.emp_act(severity)
//* Inventory *//
/obj/AllowDrop()
if(!isnull(obj_storage))
return TRUE
return ..()
/obj/clean_radiation(str, mul, cheap)
if(obj_storage?.pass_clean_radiation_inside)
for(var/atom/movable/AM as anything in contents)
AM.clean_radiation(str, mul, cheap)
return ..()
/obj/on_contents_item_new(obj/item/item)
. = ..()
if(!isnull(obj_storage?.indirection))
return
obj_storage?.on_contents_item_new(item)
/obj/on_contents_weight_class_change(obj/item/item, old_weight_class, new_weight_class)
. = ..()
if(!isnull(obj_storage?.indirection))
return
obj_storage?.on_contents_weight_class_change(item, old_weight_class, new_weight_class)
/obj/on_contents_weight_volume_change(obj/item/item, old_weight_volume, new_weight_volume)
. = ..()
if(!isnull(obj_storage?.indirection))
return
obj_storage?.on_contents_weight_volume_change(item, old_weight_volume, new_weight_volume)
/obj/on_contents_weight_change(obj/item/item, old_weight, new_weight)
. = ..()
if(!isnull(obj_storage?.indirection))
return
obj_storage?.on_contents_weight_change(item, old_weight, new_weight)
/**
* Returns stuff considered to be inside this object's inventory.
*
* Do not return ourselves or there will be an infinite loop in many callers!
*
* @return list()
*/
/obj/proc/return_inventory()
return isnull(obj_storage)? list() : obj_storage.contents()
//* Examine *//
/obj/examine(mob/user, dist)
. = ..()
if(integrity_examine)
. += examine_integrity(user)
var/list/parts = get_material_parts()
for(var/key in parts)
var/datum/prototype/material/mat = parts[key]
if(isnull(mat)) // 'none' option
continue
. += "Its [key] is made out of [mat.display_name]"
if((obj_persist_dynamic_id || obj_persist_static_id) && !(obj_persist_status & OBJ_PERSIST_STATUS_NO_EXAMINE))
. += SPAN_BOLDNOTICE("This entity is a persistent entity; it may be preserved into future rounds.")
// todo: context + construction (tool) examines at some point need a better system
if(obj_rotation_flags & OBJ_ROTATION_ENABLED)
. += SPAN_NOTICE("This entity can be rotated[(obj_rotation_flags & OBJ_ROTATION_NO_ANCHOR_CHECK)? "" : " while unanchored"] via context menu (alt click while adjacent).")
if(dist <= 1)
obj_storage?.handle_storage_examine(.)
/obj/proc/examine_integrity(mob/user)
. = list()
if(!integrity_enabled)
return
if(integrity == integrity_max)
. += SPAN_NOTICE("It looks fully intact.")
else if(atom_flags & ATOM_BROKEN)
. += SPAN_BOLDWARNING("It's broken and falling apart!")
else
var/perc = percent_integrity()
if(perc > 0.75)
. += SPAN_NOTICE("It looks a bit dented.")
else if(perc > 0.5)
. += SPAN_WARNING("It looks damaged.")
else if(perc > 0.25)
. += SPAN_RED("It looks severely damaged.")
else
. += SPAN_BOLDWARNING("It's barely able to hold itself together!")
//* Movement *//
/obj/Exited(atom/movable/AM, atom/newLoc)
. = ..()
if(isitem(AM) && !isnull(obj_storage) && isnull(obj_storage.indirection))
obj_storage.on_item_exited(AM)
/obj/Entered(atom/movable/AM, atom/oldLoc)
. = ..()
if(isitem(AM) && !isnull(obj_storage) && isnull(obj_storage.indirection))
obj_storage.on_item_entered(AM)
//* Orientation *//
/**
* Standard wallmount orientation: face away
*/
/obj/proc/auto_orient_wallmount_single(shift_amount)
for(var/dir in GLOB.cardinal)
if(get_step(src, dir)?.get_wallmount_anchor())
setDir(turn(dir, 180))
return
switch(dir)
if(SOUTH)
set_base_pixel_x(0)
set_base_pixel_y(shift_amount)
if(NORTH)
set_base_pixel_x(0)
set_base_pixel_y(-shift_amount)
if(WEST)
set_base_pixel_x(shift_amount)
set_base_pixel_y(0)
if(EAST)
set_base_pixel_x(-shift_amount)
set_base_pixel_y(0)
/**
* Standard wallmount orientation: face away
*
* Directly sets dir without setDir()
*/
/obj/proc/auto_orient_wallmount_single_preinit(shift_amount)
for(var/dir in GLOB.cardinal)
if(get_step(src, dir)?.get_wallmount_anchor())
src.dir = turn(dir, 180)
break
switch(dir)
if(SOUTH)
set_base_pixel_x(0)
set_base_pixel_y(shift_amount)
if(NORTH)
set_base_pixel_x(0)
set_base_pixel_y(-shift_amount)
if(WEST)
set_base_pixel_x(shift_amount)
set_base_pixel_y(0)
if(EAST)
set_base_pixel_x(-shift_amount)
set_base_pixel_y(0)
//* Resists *//
/**
* called when something tries to resist out from inside us.
*
* @return TRUE if something was done to start to resist / as a resist actino, FALSE if something trivial was done / nothing was done.
*/
/obj/proc/contents_resist(mob/escapee)
SHOULD_NOT_SLEEP(TRUE)
return FALSE
/**
* Invoke asynchronously from contents_resist.
*
* @return TRUE / FALSE based on if they started an action.
*/
/obj/proc/contents_resist_sequence(mob/escapee, time = 2 MINUTES, interval = 5 SECONDS)
set waitfor = FALSE
if(INTERACTING_WITH_FOR(escapee, src, INTERACTING_FOR_RESIST))
return FALSE
. = TRUE
_contents_resist_sequence(arglist(args))
/obj/proc/_contents_resist_sequence(mob/escapee, time, interval)
START_INTERACTING_WITH(escapee, src, INTERACTING_FOR_RESIST)
if(!contents_resist_step(escapee, 0))
return FALSE
. = TRUE
// todo: mobility flags
var/extra_time = MODULUS_F(time, interval)
var/i
for(i in 1 to round(time / interval))
if(!do_after(escapee, interval, mobility_flags = MOBILITY_CAN_RESIST))
return FALSE
if(escapee.loc != src)
return FALSE
if(!contents_resist_step(escapee, i))
return FALSE
if(breakout_sound)
playsound(src, breakout_sound, breakout_volume, 1)
if(!do_after(escapee, extra_time, mobility_flags = MOBILITY_CAN_RESIST))
return FALSE
if(!contents_resist_step(escapee, i + 1, TRUE))
return FALSE
STOP_INTERACTING_WITH(escapee, src, INTERACTING_FOR_RESIST)
if(.)
contents_resist_finish(escapee)
/**
* called on interval step of contents_request_sequence
* use this to cancel and open if we're already open / whatever.
*
* @return TRUE / FALSE to keep resisting or not
*/
/obj/proc/contents_resist_step(mob/escapee, iteration, finishing)
contents_resist_shake()
return TRUE
/**
* Called when contents_resist_sequence finishes successfully.
*/
/obj/proc/contents_resist_finish(mob/escapee)
return
/**
* called to shake during contents resist
*/
/obj/proc/contents_resist_shake()
var/init_px = pixel_x
var/shake_dir = pick(-1, 1)
animate(src, transform=turn(matrix(), 8*shake_dir), pixel_x=init_px + 2*shake_dir, time=1)
animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING)
//* Rotation *//
/obj/proc/allow_rotation(datum/event_args/actor/actor, clockwise, silent)
if(!(obj_rotation_flags & OBJ_ROTATION_NO_ANCHOR_CHECK) && anchored)
if(!silent)
actor.chat_feedback(
SPAN_WARNING("[src] is anchored to the ground!"),
target = src,
)
return FALSE
if(!(obj_rotation_flags & OBJ_ROTATION_BIDIRECTIONAL) && (clockwise ^ !(obj_rotation_flags & OBJ_ROTATION_CCW)))
if(!silent)
actor.chat_feedback(
SPAN_WARNING("[src] doesn't rotate in that direction."),
target = src,
)
return FALSE
return TRUE
/obj/proc/handle_rotation(datum/event_args/actor/actor, clockwise, silent, suppressed)
if(!allow_rotation(actor, clockwise, silent))
return FALSE
setDir(turn(dir, clockwise? -90 : 90))
if(!suppressed)
actor.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[actor.performer] rotates [src]."),
audible = SPAN_NOTICE("You hear something being pivoted."),
visible_self = SPAN_NOTICE("You spin [src] [clockwise? "clockwise" : "counterclockwise"]."),
)
return TRUE
//* Tool System *//
/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args)
if(isnull(obj_cell_slot) || !obj_cell_slot.remove_tool_behavior || !obj_cell_slot.interaction_active(e_args.performer))
return ..()
. = list()
LAZYSET(.[obj_cell_slot.remove_tool_behavior], "remove cell", dyntool_image_backward(obj_cell_slot.remove_tool_behavior))
return merge_double_lazy_assoc_list(..(), .)
/obj/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint)
if(isnull(obj_cell_slot) || (obj_cell_slot.remove_tool_behavior != function) || !obj_cell_slot.interaction_active(e_args.performer))
return ..()
if(isnull(obj_cell_slot.cell))
e_args.chat_feedback(SPAN_WARNING("[src] has no cell in it."))
return CLICKCHAIN_DO_NOT_PROPAGATE
log_construction(e_args, src, "removing cell")
e_args.visible_feedback(
target = src,
range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[e_args.performer] starts removing the cell from [src]."),
audible = SPAN_NOTICE("You hear fasteners being undone."),
otherwise_self = SPAN_NOTICE("You start removing the cell from [src]."),
)
if(!use_tool(function, I, e_args, flags, obj_cell_slot.remove_tool_time, 1))
return CLICKCHAIN_DO_NOT_PROPAGATE
log_construction(e_args, src, "removed cell")
e_args.visible_feedback(
target = src,
range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[e_args.performer] removes the cell from [src]."),
audible = SPAN_NOTICE("You hear fasteners falling out and something being removed."),
otherwise_self = SPAN_NOTICE("You remove the cell from [src]."),
)
return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
//* Underfloor *//
/**
* sets our hides_underfloor
*/
/obj/proc/set_hides_underfloor(new_value, mapload)
switch(new_value)
if(OBJ_UNDERFLOOR_IF_CREATED_UNCOVERED, OBJ_UNDERFLOOR_UNLESS_PLACED_ONTOP)
var/turf/where_we_are = loc
if(istype(where_we_are) && where_we_are.hides_underfloor_objects())
new_value = OBJ_UNDERFLOOR_INACTIVE
else
new_value = OBJ_UNDERFLOOR_NEVER
hides_underfloor = new_value
reconsider_hiding_underfloor()
/**
* called to inform us we should / shouldn't be underfloor
*
* * this must be idempotent. we can get called at will by reconsider_hiding_underfloor().
* * we are assumed to not be underfloor when we are first made
* * that means this is called during Initialize() if and only if we need to be hiding underfloor
*/
/obj/proc/update_hiding_underfloor(new_value)
if(hides_underfloor_defaulting)
invisibility = new_value? (hides_underfloor_invisibility_abstract? INVISIBILITY_ABSTRACT : INVISIBILITY_UNDERFLOOR) : 0
if(hides_underfloor_update_icon)
update_icon()
if(hides_underfloor != OBJ_UNDERFLOOR_NEVER)
hides_underfloor = new_value ? OBJ_UNDERFLOOR_ACTIVE : OBJ_UNDERFLOOR_INACTIVE
return TRUE
/**
* Checks if we will hide underfloor if covered
*
* * Only valid after Initialize().
*/
/obj/proc/will_hide_underfloor()
switch(hides_underfloor)
if(OBJ_UNDERFLOOR_ACTIVE, OBJ_UNDERFLOOR_INACTIVE)
return TRUE
if(OBJ_UNDERFLOOR_UNSUPPORTED, OBJ_UNDERFLOOR_NEVER)
return FALSE
else
CRASH("Proc will_hide_underfloor() called witohut a valid underfloor value. Are you calling it too early in init?")
/**
* Checks if we're hiding underfloor
*/
/obj/proc/is_hidden_underfloor()
return hides_underfloor == OBJ_UNDERFLOOR_ACTIVE
/**
* called at init
*
* todo: should this be called from obj init? we can probably shave a few centiseconds off init if it was on turf
* as we wouldn't need to keep calling hides_underfloor_objects()
*/
/obj/proc/initialize_hiding_underfloor(mapload)
switch(hides_underfloor)
if(OBJ_UNDERFLOOR_IF_CREATED_UNCOVERED, OBJ_UNDERFLOOR_UNLESS_PLACED_ONTOP)
var/turf/where_we_are = loc
var/hide_anyways = (hides_underfloor == OBJ_UNDERFLOOR_UNLESS_PLACED_ONTOP) && mapload
var/we_are_hidden = where_we_are?.hides_underfloor_objects()
if(istype(where_we_are) && (hide_anyways || !we_are_hidden))
if(!mapload && we_are_hidden)
update_hiding_underfloor(TRUE)
else
hides_underfloor = OBJ_UNDERFLOOR_INACTIVE
else
hides_underfloor = OBJ_UNDERFLOOR_NEVER
if(OBJ_UNDERFLOOR_ACTIVE, OBJ_UNDERFLOOR_INACTIVE)
var/turf/where_we_are = loc
if(!mapload && istype(where_we_are) && where_we_are.hides_underfloor_objects())
update_hiding_underfloor(TRUE)
else
hides_underfloor = OBJ_UNDERFLOOR_INACTIVE
/**
* called to re-call update_hiding_underfloor
*/
/obj/proc/reconsider_hiding_underfloor()
var/turf/where_we_are = loc
var/turf_will_cover = istype(where_we_are) && where_we_are.hides_underfloor_objects()
switch(hides_underfloor)
if(OBJ_UNDERFLOOR_INACTIVE, OBJ_UNDERFLOOR_ACTIVE)
update_hiding_underfloor(turf_will_cover)
if(OBJ_UNDERFLOOR_NEVER)
update_hiding_underfloor(FALSE)
else
// we're way beyond initialize, so..
hides_underfloor = OBJ_UNDERFLOOR_UNSUPPORTED
//* VV hooks *//
/obj/vv_edit_var(var_name, var_value, mass_edit, raw_edit)
. = ..()
switch(var_name)
if(NAMEOF(src, hides_underfloor))
set_hides_underfloor(var_value)
/obj/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---")
VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type")
/obj/vv_do_topic(list/href_list)
. = ..()
if(!.)
return
if(href_list[VV_HK_MASS_DEL_TYPE])
if(!check_rights(R_DEBUG|R_SERVER))
return
var/action_type = tgui_alert(usr, "Strict type ([type]) or type and all subtypes?",,list("Strict type","Type and subtypes","Cancel"))
if(action_type == "Cancel" || !action_type)
return
if(tgui_alert(usr, "Are you really sure you want to delete all objects of type [type]?",,list("Yes","No")) != "Yes")
return
if(tgui_alert(usr, "Second confirmation required. Delete?",,list("Yes","No")) != "Yes")
return
var/O_type = type
switch(action_type)
if("Strict type")
var/i = 0
for(var/obj/Obj in world)
if(Obj.type == O_type)
i++
qdel(Obj)
CHECK_TICK
if(!i)
to_chat(usr, "No objects of this type exist")
return
log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ")
message_admins(SPAN_NOTICE("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) "))
if("Type and subtypes")
var/i = 0
for(var/obj/Obj in world)
if(istype(Obj,O_type))
i++
qdel(Obj)
CHECK_TICK
if(!i)
to_chat(usr, "No objects of this type exist")
return
log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ")
message_admins(SPAN_NOTICE("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) "))