/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 = "[M.name]: [text]" 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, "You topple as \the [src] moves under you!") if(prob(25)) var/damage = rand(15,30) var/mob/living/carbon/human/H = M if(!istype(H)) to_chat(H, "You land heavily!") 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, "You land heavily on your [affecting.name]!") affecting.take_damage(damage, 0) if(affecting.parent) affecting.parent.add_autopsy_data("Misadventure", damage) else to_chat(H, "You land heavily!") 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) "))