mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 07:38:05 +01:00
Merge remote-tracking branch 'tgstation/master' into upstream-6-2-2026
This commit is contained in:
@@ -629,8 +629,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0)
|
||||
fishing_modifier = -21 //it only lives for 25 seconds, so we make them worth it.
|
||||
custom_materials = null
|
||||
|
||||
/obj/structure/chair/mime/wrench_act_secondary(mob/living/user, obj/item/weapon)
|
||||
return NONE
|
||||
/obj/structure/chair/mime/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/tool_blocker, TOOL_WRENCH, TOOL_ACT_SECONDARY)
|
||||
|
||||
/obj/structure/chair/mime/post_buckle_mob(mob/living/M)
|
||||
M.add_offsets(type, z_add = 5)
|
||||
|
||||
@@ -159,11 +159,9 @@
|
||||
content.forceMove(folding_bodybag)
|
||||
if(isliving(content))
|
||||
to_chat(content, span_userdanger("You're suddenly forced into a tiny, compressed space!"))
|
||||
if(iscarbon(content))
|
||||
var/mob/living/carbon/mob = content
|
||||
if (mob.dna?.get_mutation(/datum/mutation/dwarfism))
|
||||
max_weight_of_contents = max(WEIGHT_CLASS_NORMAL, max_weight_of_contents)
|
||||
continue
|
||||
if(HAS_TRAIT(content, TRAIT_DWARF))
|
||||
max_weight_of_contents = max(WEIGHT_CLASS_NORMAL, max_weight_of_contents)
|
||||
continue
|
||||
if(!isitem(content))
|
||||
max_weight_of_contents = max(WEIGHT_CLASS_BULKY, max_weight_of_contents)
|
||||
continue
|
||||
@@ -297,8 +295,8 @@
|
||||
air_contents.temperature = T20C
|
||||
|
||||
air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen)
|
||||
air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD
|
||||
air_contents.gases[/datum/gas/nitrogen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD
|
||||
air_contents.adjust_gas(/datum/gas/oxygen, (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
|
||||
air_contents.adjust_gas(/datum/gas/nitrogen, (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
|
||||
|
||||
/obj/structure/closet/body_bag/environmental/nanotrasen
|
||||
name = "elite environmental protection bag"
|
||||
@@ -428,8 +426,8 @@
|
||||
air_contents.temperature = T20C
|
||||
|
||||
air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide)
|
||||
air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD
|
||||
air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD
|
||||
air_contents.adjust_gas(/datum/gas/oxygen, (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
|
||||
air_contents.adjust_gas(/datum/gas/nitrous_oxide, (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
|
||||
|
||||
/obj/structure/closet/body_bag/environmental/hardlight
|
||||
name = "hardlight bodybag"
|
||||
@@ -509,11 +507,10 @@
|
||||
var/inner_temp = T0C - 60
|
||||
air_contents = null
|
||||
air_contents = new(mol_count)
|
||||
air_contents.temperature = inner_temp
|
||||
air_contents.set_temperature(inner_temp)
|
||||
|
||||
air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen)
|
||||
air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE * mol_count) / (R_IDEAL_GAS_EQUATION * inner_temp) * O2STANDARD
|
||||
air_contents.gases[/datum/gas/nitrogen][MOLES] = (ONE_ATMOSPHERE * mol_count) / (R_IDEAL_GAS_EQUATION * inner_temp) * N2STANDARD
|
||||
air_contents.set_gas(/datum/gas/oxygen, ((ONE_ATMOSPHERE * mol_count) / (R_IDEAL_GAS_EQUATION * inner_temp) * O2STANDARD))
|
||||
air_contents.set_gas(/datum/gas/nitrogen, ((ONE_ATMOSPHERE * mol_count) / (R_IDEAL_GAS_EQUATION * inner_temp) * N2STANDARD))
|
||||
|
||||
/obj/structure/closet/body_bag/environmental/stasis/examine_status(mob/user)
|
||||
switch(100 * get_integrity_percentage())
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
new /obj/item/radio/weather_monitor (src)
|
||||
new /obj/item/radio/headset/heads/qm(src)
|
||||
new /obj/item/megaphone/cargo(src)
|
||||
new /obj/item/assembly/flash/handheld(src)
|
||||
new /obj/item/tank/internals/emergency_oxygen(src)
|
||||
new /obj/item/universal_scanner(src)
|
||||
new /obj/item/door_remote/quartermaster(src)
|
||||
|
||||
@@ -210,6 +210,9 @@
|
||||
req_one_access = list(ACCESS_BRIG)
|
||||
var/id = null
|
||||
|
||||
/obj/structure/closet/secure_closet/brig/holodeck
|
||||
req_one_access = COMMON_ACCESS
|
||||
|
||||
/obj/structure/closet/secure_closet/brig/genpop
|
||||
name = "genpop storage locker"
|
||||
desc = "Used for storing the belongings of genpop's tourists visiting the locals."
|
||||
|
||||
+7
-1
@@ -76,7 +76,7 @@
|
||||
) = 11,
|
||||
|
||||
list( // Mobs
|
||||
/mob/living/basic/bot/honkbot = 5,
|
||||
/mob/living/basic/bot/secbot/honkbot = 5,
|
||||
/mob/living/basic/pet/gondola = 2,
|
||||
/obj/effect/spawner/abandoned_crate/bloodroaches = 1,
|
||||
) = 8,
|
||||
@@ -188,6 +188,12 @@
|
||||
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/structure/closet/crate/secure/loot/multitool_act_secondary(mob/living/user, obj/item/tool)
|
||||
if(!locked)
|
||||
return
|
||||
attack_hand(user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/// Implements bulls and cows algorithm to compare guess against actual code
|
||||
/obj/structure/closet/crate/secure/loot/proc/bulls_and_cows(guess)
|
||||
var/bulls = 0
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@
|
||||
|
||||
/obj/effect/spawner/abandoned_crate/space_suit
|
||||
loot = list(
|
||||
/obj/item/borg/upgrade/modkit/aoe/mobs = 1,
|
||||
/obj/item/borg/upgrade/modkit/cooldown/aoe/mobs = 1,
|
||||
/obj/item/clothing/suit/space = 1,
|
||||
/obj/item/clothing/head/helmet/space = 1
|
||||
)
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
/obj/structure/displaycase/trophy/proc/toggle_historian_mode(mob/user)
|
||||
historian_mode = !historian_mode
|
||||
balloon_alert(user, "[historian_mode ? "enabled" : "disabled"] historian mode.")
|
||||
playsound(src, 'sound/machines/beep/twobeep.ogg', vary = 50)
|
||||
playsound(src, 'sound/machines/beep/twobeep.ogg', 10, vary = 50)
|
||||
SStgui.update_uis(src)
|
||||
|
||||
/obj/structure/displaycase/trophy/toggle_lock(mob/user)
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
/obj/structure/chair/e_chair/Initialize(mapload)
|
||||
. = ..()
|
||||
var/obj/item/assembly/shock_kit/stored_kit = new(contents)
|
||||
var/mutable_appearance/export_to_component = mutable_appearance('icons/obj/chairs.dmi', "echair_over", OBJ_LAYER, src, appearance_flags = KEEP_APART)
|
||||
export_to_component = color_atom_overlay(export_to_component)
|
||||
AddComponent(/datum/component/electrified_buckle, (SHOCK_REQUIREMENT_ITEM | SHOCK_REQUIREMENT_LIVE_CABLE | SHOCK_REQUIREMENT_SIGNAL_RECEIVED_TOGGLE), stored_kit, list(export_to_component))
|
||||
|
||||
AddComponent(\
|
||||
/datum/component/electrified_buckle,\
|
||||
overlays_to_add = list(export_to_component),\
|
||||
shock_flags = (SHOCK_NOGLOVES)\
|
||||
)
|
||||
|
||||
/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers)
|
||||
if(W.tool_behaviour == TOOL_WRENCH)
|
||||
|
||||
@@ -1059,9 +1059,82 @@
|
||||
|
||||
/obj/structure/flora/rock/pile/style_random/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "lavarocks[rand(1, 3)]"
|
||||
icon_state = "lavarocks[pick(3;1,3;2,1;3)]"
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flora/rock/pile/siderite
|
||||
icon_state = "lavarocks_siderite1"
|
||||
|
||||
/obj/structure/flora/rock/pile/siderite/get_potential_products()
|
||||
return list(/obj/item/stack/ore/glass/siderite = 1)
|
||||
|
||||
/obj/structure/flora/rock/pile/siderite/style_2
|
||||
icon_state = "lavarocks_siderite2"
|
||||
|
||||
/obj/structure/flora/rock/pile/siderite/style_3
|
||||
icon_state = "lavarocks_siderite3"
|
||||
|
||||
/obj/structure/flora/rock/pile/siderite/style_random/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "lavarocks_siderite[pick(3;1,3;2,1;3)]"
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flora/rock/pile/shale
|
||||
icon_state = "lavarocks_shale1"
|
||||
|
||||
/obj/structure/flora/rock/pile/shale/style_2
|
||||
icon_state = "lavarocks_shale2"
|
||||
|
||||
/obj/structure/flora/rock/pile/shale/style_3
|
||||
icon_state = "lavarocks_shale3"
|
||||
|
||||
/obj/structure/flora/rock/pile/shale/style_random/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "lavarocks_shale[pick(3;1,3;2,1;3)]"
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flora/rock/siderite_growth
|
||||
name = "siderite growth"
|
||||
desc = "A natural stalagmite formed of siderite, with thin pointy strands of dirty metal sticking out of the top."
|
||||
icon_state = "siderite_growth1"
|
||||
base_icon_state = "siderite_growth"
|
||||
icon = 'icons/obj/mining_zones/terrain.dmi'
|
||||
harvest_message_med = "You finish mining the growth."
|
||||
|
||||
/obj/structure/flora/rock/siderite_growth/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "[base_icon_state][pick(3;1,3;2,1;3)]"
|
||||
update_appearance()
|
||||
AddComponent(/datum/component/seethrough, SEE_THROUGH_MAP_DEFAULT)
|
||||
|
||||
/obj/structure/flora/rock/siderite_growth/get_potential_products()
|
||||
return list(/obj/item/stack/ore/glass/siderite = 1)
|
||||
|
||||
/obj/structure/flora/rock/volcano
|
||||
name = "volcanic pore"
|
||||
desc = "A miniature volcano-like rock formed of the lava slowly pouring from it."
|
||||
icon_state = "volcano_1"
|
||||
base_icon_state = "volcano"
|
||||
harvest_message_med = "You finish mining the pore buildup."
|
||||
light_range = 3
|
||||
light_power = 2.5
|
||||
light_color = LIGHT_COLOR_LAVA
|
||||
|
||||
/obj/structure/flora/rock/volcano/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "[base_icon_state]_[rand(1, 5)]"
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/flora/rock/volcano/update_overlays()
|
||||
. = ..()
|
||||
. += emissive_appearance(icon, "[icon_state]_e", src, alpha = 200)
|
||||
|
||||
/obj/structure/flora/rock/volcano/after_harvest(user)
|
||||
var/turf/open/our_turf = loc
|
||||
if (istype(our_turf))
|
||||
our_turf.ChangeTurf(/turf/open/lava/smooth, flags = CHANGETURF_INHERIT_AIR)
|
||||
return ..()
|
||||
|
||||
/obj/structure/flora/rock/pile/jungle
|
||||
icon_state = "rock1"
|
||||
icon = 'icons/obj/fluff/flora/jungleflora.dmi'
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
/obj/structure/grille/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/atmos_sensitive, mapload)
|
||||
register_context()
|
||||
|
||||
/obj/structure/grille/Destroy()
|
||||
update_cable_icons_on_turf(get_turf(src))
|
||||
@@ -59,9 +60,19 @@
|
||||
if(resistance_flags & INDESTRUCTIBLE)
|
||||
return
|
||||
if(anchored)
|
||||
. += span_notice("It's secured in place with <b>screws</b>. The rods look like they could be <b>cut</b> through.")
|
||||
. += span_notice("It's secured in place with [EXAMINE_HINT("screws")]. The rods look like they could be [EXAMINE_HINT("cut")] through.")
|
||||
else
|
||||
. += span_notice("The anchoring screws are <i>unscrewed</i>. The rods look like they could be <b>cut</b> through.")
|
||||
. += span_notice("The anchoring screws are [EXAMINE_HINT("unscrewed")]. The rods look like they could be [EXAMINE_HINT("cut")] through.")
|
||||
|
||||
/obj/structure/grille/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
. = ..()
|
||||
if(held_item?.tool_behaviour == TOOL_WIRECUTTER)
|
||||
context[SCREENTIP_CONTEXT_RMB] = "Deconstruct"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
if(held_item?.tool_behaviour == TOOL_SCREWDRIVER)
|
||||
context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unanchor" : "Anchor"]"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
return .
|
||||
|
||||
/obj/structure/grille/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
|
||||
switch(the_rcd.mode)
|
||||
@@ -199,7 +210,7 @@
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/structure/grille/wirecutter_act(mob/living/user, obj/item/tool)
|
||||
/obj/structure/grille/wirecutter_act_secondary(mob/living/user, obj/item/tool)
|
||||
add_fingerprint(user)
|
||||
if(shock(user, 100))
|
||||
return
|
||||
|
||||
@@ -244,8 +244,7 @@
|
||||
var/datum/species/S = victim.dna.species
|
||||
|
||||
if (istype(S))
|
||||
victim.cut_overlays()
|
||||
victim.update_body_parts_head_only()
|
||||
victim.render_only_head()
|
||||
victim.remove_overlay(BODY_ADJ_LAYER)
|
||||
victim.pixel_y += -GUILLOTINE_HEAD_OFFSET // Offset their body so it looks like they're in the guillotine
|
||||
victim.layer += GUILLOTINE_LAYER_DIFF
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
return
|
||||
|
||||
to_chat(user, span_notice("You discovered the geyser and mark it on the GPS system!"))
|
||||
playsound(src, 'sound/machines/beep/twobeep_high.ogg', 30)
|
||||
SEND_SIGNAL(user, COMSIG_LIVING_DISCOVERED_GEYSER, src)
|
||||
if(discovery_message)
|
||||
to_chat(user, discovery_message)
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
//Necropolis Tendrils, which spawn lavaland monsters and break into a chasm when killed
|
||||
/obj/structure/spawner/lavaland
|
||||
name = "necropolis tendril"
|
||||
desc = "A vile tendril of corruption, originating deep underground. Terrible monsters are pouring out of it."
|
||||
|
||||
icon = 'icons/mob/simple/lavaland/nest.dmi'
|
||||
icon_state = "tendril"
|
||||
|
||||
faction = list(FACTION_MINING, FACTION_ASHWALKER)
|
||||
max_mobs = 3
|
||||
max_integrity = 250
|
||||
mob_types = list(/mob/living/basic/mining/watcher)
|
||||
|
||||
move_resist=INFINITY // just killing it tears a massive hole in the ground, let's not move it
|
||||
anchored = TRUE
|
||||
resistance_flags = FIRE_PROOF | LAVA_PROOF
|
||||
var/obj/effect/light_emitter/tendril/emitted_light
|
||||
scanner_taggable = TRUE
|
||||
mob_gps_id = "WT"
|
||||
spawner_gps_id = "Necropolis Tendril"
|
||||
|
||||
/obj/structure/spawner/lavaland/goliath
|
||||
mob_types = list(/mob/living/basic/mining/goliath)
|
||||
mob_gps_id = "GL"
|
||||
|
||||
/obj/structure/spawner/lavaland/legion
|
||||
mob_types = list(/mob/living/basic/mining/legion/spawner_made)
|
||||
mob_gps_id = "LG"
|
||||
|
||||
/obj/structure/spawner/lavaland/icewatcher
|
||||
mob_types = list(/mob/living/basic/mining/watcher/icewing)
|
||||
mob_gps_id = "WT|I" // icewing
|
||||
|
||||
GLOBAL_LIST_INIT(tendrils, list())
|
||||
/obj/structure/spawner/lavaland/Initialize(mapload)
|
||||
. = ..()
|
||||
emitted_light = new(loc)
|
||||
for(var/F in RANGE_TURFS(1, src))
|
||||
if(ismineralturf(F))
|
||||
var/turf/closed/mineral/M = F
|
||||
M.ScrapeAway(null, CHANGETURF_IGNORE_AIR)
|
||||
AddComponent(/datum/component/gps, "Eerie Signal")
|
||||
GLOB.tendrils += src
|
||||
|
||||
/obj/structure/spawner/lavaland/atom_deconstruct(disassembled)
|
||||
new /obj/effect/collapse(loc)
|
||||
|
||||
/obj/structure/spawner/lavaland/examine(mob/user)
|
||||
var/list/examine_messages = ..()
|
||||
examine_messages += span_notice("Once this thing gets hurts enough, it triggers a violent final retaliation.")
|
||||
examine_messages += span_notice("You'll only have a few moments to run up, grab some loot with an open hand, and get out with it.")
|
||||
return examine_messages
|
||||
|
||||
/obj/structure/spawner/lavaland/Destroy()
|
||||
var/last_tendril = TRUE
|
||||
if(GLOB.tendrils.len>1)
|
||||
last_tendril = FALSE
|
||||
|
||||
if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1))
|
||||
if(SSachievements.achievements_enabled)
|
||||
for(var/mob/living/L in view(7,src))
|
||||
if(L.stat || !L.client)
|
||||
continue
|
||||
L.client.give_award(/datum/award/achievement/boss/tendril_exterminator, L)
|
||||
L.client.give_award(/datum/award/score/tendril_score, L) //Progresses score by one
|
||||
GLOB.tendrils -= src
|
||||
QDEL_NULL(emitted_light)
|
||||
return ..()
|
||||
|
||||
/obj/effect/light_emitter/tendril
|
||||
set_luminosity = 4
|
||||
set_cap = 2.5
|
||||
light_color = LIGHT_COLOR_LAVA
|
||||
|
||||
/obj/effect/collapse
|
||||
name = "collapsing necropolis tendril"
|
||||
desc = "Get your loot and get clear!"
|
||||
layer = TABLE_LAYER
|
||||
icon = 'icons/mob/simple/lavaland/nest.dmi'
|
||||
icon_state = "tendril"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
/// weakref list of which mobs have gotten their loot from this effect.
|
||||
var/list/collected = list()
|
||||
/// a bit of light as to make less unfair deaths from the chasm
|
||||
var/obj/effect/light_emitter/tendril/emitted_light
|
||||
|
||||
/obj/effect/collapse/Initialize(mapload)
|
||||
. = ..()
|
||||
emitted_light = new(loc)
|
||||
visible_message(span_bolddanger("The tendril writhes in fury as the earth around it begins to crack and break apart! Get back!"))
|
||||
balloon_alert_to_viewers("interact to grab loot before collapse!", vision_distance = 7)
|
||||
playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, FALSE, 50, TRUE, TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(collapse)), 5 SECONDS)
|
||||
|
||||
/obj/effect/collapse/examine(mob/user)
|
||||
var/list/examine_messages = ..()
|
||||
if(isliving(user))
|
||||
if(has_collected(user))
|
||||
examine_messages += span_boldnotice("You've grabbed what you can, now get out!")
|
||||
else
|
||||
examine_messages += span_boldnotice("You might have some time to grab some goodies with an open hand before it collapses!")
|
||||
return examine_messages
|
||||
|
||||
/obj/effect/collapse/attack_hand(mob/living/collector, list/modifiers)
|
||||
. = ..()
|
||||
if(has_collected(collector))
|
||||
to_chat(collector, span_danger("You've already gotten some loot, just get out of there with it!"))
|
||||
return
|
||||
visible_message(span_warning("Something falls free of the tendril!"))
|
||||
var/obj/structure/closet/crate/necropolis/tendril/loot = new /obj/structure/closet/crate/necropolis/tendril(loc)
|
||||
collector.start_pulling(loot)
|
||||
collected += WEAKREF(collector)
|
||||
|
||||
/obj/effect/collapse/Destroy()
|
||||
collected.Cut()
|
||||
QDEL_NULL(emitted_light)
|
||||
return ..()
|
||||
|
||||
///Helper proc that resolves weakrefs to determine if collector is in collected list, returning a boolean.
|
||||
/obj/effect/collapse/proc/has_collected(mob/collector)
|
||||
for(var/datum/weakref/weakref as anything in collected)
|
||||
var/mob/living/resolved = weakref.resolve()
|
||||
//it could have been collector, it could not have been, we don't care
|
||||
if(!resolved)
|
||||
continue
|
||||
if(resolved == collector)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/effect/collapse/proc/collapse()
|
||||
for(var/mob/M in range(7,src))
|
||||
shake_camera(M, 15, 1)
|
||||
playsound(get_turf(src),'sound/effects/explosion/explosionfar.ogg', 200, TRUE)
|
||||
visible_message(span_bolddanger("The tendril falls inward, the ground around it widening into a yawning chasm!"))
|
||||
for(var/turf/T in RANGE_TURFS(2,src))
|
||||
if(HAS_TRAIT(T, TRAIT_NO_TERRAFORM))
|
||||
continue
|
||||
if(!T.density)
|
||||
T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR)
|
||||
qdel(src)
|
||||
@@ -1,4 +1,4 @@
|
||||
#define MAX_ARTIFACT_ROLL_CHANCE 10
|
||||
#define ARTIFACT_ROLL_CHANCE 7
|
||||
#define MINERAL_TYPE_OPTIONS_RANDOM 4
|
||||
#define OVERLAY_OFFSET_START 0
|
||||
#define OVERLAY_OFFSET_EACH 5
|
||||
@@ -72,7 +72,7 @@
|
||||
/// What base icon_state do we use for this vent's boulders?
|
||||
var/boulder_icon_state = "boulder"
|
||||
/// Percent chance that this vent will produce an artifact boulder.
|
||||
var/artifact_chance = 0
|
||||
var/artifact_chance = ARTIFACT_ROLL_CHANCE
|
||||
/// We use a cooldown to prevent the wave defense from being started multiple times.
|
||||
COOLDOWN_DECLARE(wave_cooldown)
|
||||
/// We use a cooldown to prevent players from tapping boulders rapidly from vents.
|
||||
@@ -140,6 +140,7 @@
|
||||
. = ..()
|
||||
if(HAS_TRAIT(user, TRAIT_BOULDER_BREAKER))
|
||||
produce_boulder(TRUE)
|
||||
return TRUE
|
||||
|
||||
/obj/structure/ore_vent/is_buckle_possible(mob/living/target, force, check_loc)
|
||||
. = ..()
|
||||
@@ -160,6 +161,8 @@
|
||||
. += span_notice("This vent produces [span_bold("large")] boulders containing [ore_string]")
|
||||
else
|
||||
. += span_notice("This vent can be scanned with a [span_bold("Mining Scanner")].")
|
||||
if(artifact_chance)
|
||||
. += span_notice("This vent has a low chance to produce an [span_bold("artifact boulder.")] These may contain rare minerals or strange artifacts.")
|
||||
|
||||
/obj/structure/ore_vent/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
|
||||
if(is_type_in_list(held_item, scanning_equipment))
|
||||
@@ -221,8 +224,8 @@
|
||||
* This also means a large boulder can highroll a boulder with a full stack of 50 sheets of material.
|
||||
* @params ore_floor The number of minerals already rolled. Used to scale the logarithmic function.
|
||||
*/
|
||||
/obj/structure/ore_vent/proc/ore_quantity_function(ore_floor)
|
||||
return SHEET_MATERIAL_AMOUNT * max(round(boulder_size * (log(rand(1 + ore_floor, 4 + ore_floor)) ** -1)), 1)
|
||||
/obj/structure/ore_vent/proc/ore_quantity_function(ore_floor, ore_per_size = HALF_SHEET_MATERIAL_AMOUNT)
|
||||
return ore_per_size * max(round(boulder_size * (log(rand(1 + ore_floor, 4 + ore_floor)) ** -1)), 1)
|
||||
|
||||
/**
|
||||
* This confirms that the user wants to start the wave defense event, and that they can start it.
|
||||
@@ -357,7 +360,7 @@
|
||||
miner.mind?.adjust_experience(/datum/skill/mining, MINING_SKILL_BOULDER_SIZE_XP * boulder_size)
|
||||
if(!user_id_card)
|
||||
continue
|
||||
var/point_reward_val = (MINER_POINT_MULTIPLIER * boulder_size) - MINER_POINT_MULTIPLIER // We remove the base value of discovering the vent
|
||||
var/point_reward_val = (MINER_POINT_MULTIPLIER * (boulder_size + 2)) - MINER_POINT_MULTIPLIER // We remove the base value of discovering the vent
|
||||
if(user_id_card.registered_account)
|
||||
user_id_card.registered_account.mining_points += point_reward_val
|
||||
user_id_card.registered_account.bank_card_talk("You have been awarded [point_reward_val] mining points for your efforts.")
|
||||
@@ -525,6 +528,11 @@
|
||||
var/atom/movable/flick_visual/visual = flick_overlay_view(mutable_appearance('icons/effects/vent_overlays.dmi', selected_mat.name), 4.5 SECONDS)
|
||||
animate(visual, alpha = 0, time = 4.5 SECONDS, easing = CIRCULAR_EASING|EASE_IN)
|
||||
|
||||
if(artifact_chance)
|
||||
var/atom/movable/flick_visual/rare = flick_overlay_view(mutable_appearance('icons/effects/vent_overlays.dmi', "rare_ore"), 4.5 SECONDS)
|
||||
animate(rare, alpha = 0, time = 4.5 SECONDS, easing = CIRCULAR_EASING|EASE_IN)
|
||||
|
||||
|
||||
/**
|
||||
* Here is where we handle producing a new boulder, based on the qualities of this ore vent.
|
||||
* Returns the boulder produced.
|
||||
@@ -540,7 +548,8 @@
|
||||
//produce the boulder
|
||||
var/obj/item/boulder/new_rock
|
||||
if(prob(artifact_chance))
|
||||
new_rock = new /obj/item/boulder/artifact(loc)
|
||||
var/picked_artifact = pick(typesof(/obj/item/boulder/artifact))
|
||||
new_rock = new picked_artifact(loc)
|
||||
else
|
||||
new_rock = new /obj/item/boulder(loc)
|
||||
Shake(duration = 1.5 SECONDS)
|
||||
@@ -645,6 +654,10 @@
|
||||
/datum/material/glass = 1,
|
||||
)
|
||||
|
||||
/obj/structure/ore_vent/starter_resources/Initialize(mapload)
|
||||
. = ..()
|
||||
generate_description()
|
||||
|
||||
/obj/structure/ore_vent/random
|
||||
// Todo: determine if we need a boulder_size default thats unique from the override performed in vent_size_setup.
|
||||
|
||||
@@ -653,7 +666,6 @@
|
||||
if(!unique_vent && !mapload)
|
||||
generate_mineral_breakdown(map_loading = mapload) //Default to random mineral breakdowns, unless this is a unique vent or we're still setting up default vent distribution.
|
||||
generate_description()
|
||||
artifact_chance = rand(0, MAX_ARTIFACT_ROLL_CHANCE)
|
||||
if(!mapload)
|
||||
vent_size_setup(random = TRUE) // We only do this here specific to random distribution ore vents, and within mapload we handle this manually within SSore_generation.
|
||||
|
||||
@@ -699,9 +711,9 @@
|
||||
/datum/material/titanium = 1,
|
||||
/datum/material/silver = 1,
|
||||
/datum/material/gold = 1,
|
||||
/datum/material/diamond = 1,
|
||||
/datum/material/diamond = 0.1,
|
||||
/datum/material/uranium = 1,
|
||||
/datum/material/bluespace = 1,
|
||||
/datum/material/bluespace = 0.1,
|
||||
/datum/material/plastic = 1,
|
||||
)
|
||||
defending_mobs = list(
|
||||
@@ -758,6 +770,41 @@
|
||||
/mob/living/simple_animal/hostile/megafauna/colossus,
|
||||
)
|
||||
|
||||
/obj/structure/ore_vent/debug
|
||||
name = "debug ore vent"
|
||||
desc = "How the hell did you get this?."
|
||||
tapped = TRUE
|
||||
discovered = TRUE
|
||||
unique_vent = TRUE
|
||||
color = "#ff00f2"
|
||||
boulder_size = BOULDER_SIZE_SMALL
|
||||
mineral_breakdown = list(
|
||||
/datum/material/iron = 1,
|
||||
)
|
||||
|
||||
/obj/structure/ore_vent/debug/attack_hand(mob/living/user, list/modifiers)
|
||||
. = ..()
|
||||
var/datum/material/choice = tgui_input_list(user, "Choose a material to add/remove.", "New material", subtypesof(/datum/material))
|
||||
if(!choice)
|
||||
return
|
||||
if(mineral_breakdown[choice])
|
||||
mineral_breakdown -= choice
|
||||
balloon_alert_to_viewers("removed [choice::name]")
|
||||
return
|
||||
mineral_breakdown += choice
|
||||
balloon_alert_to_viewers("added [choice::name]")
|
||||
var/value = tgui_input_number(user, "What weight should it have?", "ore pickweight", 1, 100, 1)
|
||||
mineral_breakdown[choice] = value
|
||||
balloon_alert_to_viewers("weighting of [value] added")
|
||||
generate_description()
|
||||
|
||||
/obj/structure/ore_vent/debug/attack_hand_secondary(mob/user, list/modifiers)
|
||||
. = ..()
|
||||
var/choice = tgui_input_list(user, "Choose a vent size.", "New size", list(SMALL_VENT_TYPE, MEDIUM_VENT_TYPE, LARGE_VENT_TYPE))
|
||||
if(!choice)
|
||||
return
|
||||
vent_size_setup(random = FALSE, force_size = choice, map_loading = FALSE)
|
||||
|
||||
/obj/effect/landmark/mining_center
|
||||
name = "Mining Epicenter"
|
||||
icon_state = "mining_epicenter"
|
||||
@@ -772,7 +819,7 @@
|
||||
GLOB.mining_center += loc
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
#undef MAX_ARTIFACT_ROLL_CHANCE
|
||||
#undef ARTIFACT_ROLL_CHANCE
|
||||
#undef MINERAL_TYPE_OPTIONS_RANDOM
|
||||
#undef OVERLAY_OFFSET_START
|
||||
#undef OVERLAY_OFFSET_EACH
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#define PRIDE_MIRROR_OPTIONS list(CHANGE_HAIR, CHANGE_BEARD, CHANGE_RACE, CHANGE_SEX, CHANGE_EYES)
|
||||
#define MAGIC_MIRROR_OPTIONS list(CHANGE_HAIR, CHANGE_BEARD, CHANGE_RACE, CHANGE_SEX, CHANGE_EYES, CHANGE_NAME)
|
||||
|
||||
// Chance for the mirror to be haunted at creation
|
||||
#define ROUNDSTART_CURSED_CHANCE 0.2
|
||||
|
||||
/obj/structure/mirror
|
||||
name = "mirror"
|
||||
desc = "Mirror mirror on the wall, who's the most robust of them all?"
|
||||
@@ -29,7 +32,8 @@
|
||||
///Can this mirror be removed from walls with tools?
|
||||
var/deconstructable = TRUE
|
||||
var/list/mirror_options = INERT_MIRROR_OPTIONS
|
||||
|
||||
// Can a revenant be imprisoned in this mirror?
|
||||
var/cursable = TRUE
|
||||
///Flags this race must have to be selectable with this type of mirror.
|
||||
var/race_flags = MIRROR_MAGIC
|
||||
///List of all Races that can be chosen, decided by its Initialize.
|
||||
@@ -57,6 +61,8 @@
|
||||
)
|
||||
if(mapload)
|
||||
find_and_mount_on_atom()
|
||||
if(prob(ROUNDSTART_CURSED_CHANCE) && cursable)
|
||||
AddComponent(/datum/component/revenant_prison, create_on_release = TRUE)
|
||||
update_choices()
|
||||
register_context()
|
||||
|
||||
@@ -72,10 +78,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
|
||||
|
||||
/obj/structure/mirror/broken
|
||||
icon_state = "mirror_broke"
|
||||
cursable = FALSE
|
||||
broken = TRUE
|
||||
desc = "Oh no, seven years of bad luck!"
|
||||
|
||||
/obj/structure/mirror/broken/Initialize(mapload)
|
||||
. = ..()
|
||||
atom_break(null, mapload)
|
||||
|
||||
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
|
||||
|
||||
@@ -262,7 +270,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
|
||||
return
|
||||
user.set_eye_color(sanitize_hexcolor(new_eye_color))
|
||||
user.dna.update_ui_block(/datum/dna_block/identity/eye_colors)
|
||||
user.update_body()
|
||||
user.update_eyes()
|
||||
to_chat(user, span_notice("You gaze at your new eyes with your new eyes. Perfect!"))
|
||||
|
||||
/obj/structure/mirror/examine(mob/user)
|
||||
@@ -302,13 +310,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
|
||||
to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters..."))
|
||||
unlucky_dude.AddComponent(/datum/component/omen, incidents_left = 7)
|
||||
|
||||
/obj/structure/mirror/atom_break(damage_flag, mapload)
|
||||
/obj/structure/mirror/atom_break(damage_flag)
|
||||
. = ..()
|
||||
if(broken)
|
||||
return
|
||||
icon_state = "mirror_broke"
|
||||
if(!mapload)
|
||||
playsound(src, SFX_SHATTER, 70, TRUE)
|
||||
playsound(src, SFX_SHATTER, 70, TRUE)
|
||||
if(desc == initial(desc))
|
||||
desc = "Oh no, seven years of bad luck!"
|
||||
broken = TRUE
|
||||
@@ -361,6 +368,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
|
||||
icon_state = "magic_mirror"
|
||||
mirror_options = MAGIC_MIRROR_OPTIONS
|
||||
deconstructable = FALSE
|
||||
cursable = FALSE
|
||||
|
||||
/obj/structure/mirror/magic/Initialize(mapload)
|
||||
. = ..()
|
||||
@@ -463,3 +471,5 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28)
|
||||
#undef INERT_MIRROR_OPTIONS
|
||||
#undef PRIDE_MIRROR_OPTIONS
|
||||
#undef MAGIC_MIRROR_OPTIONS
|
||||
|
||||
#undef ROUNDSTART_CURSED_CHANCE
|
||||
|
||||
@@ -463,35 +463,28 @@ GLOBAL_LIST_EMPTY(crematoriums)
|
||||
/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user)
|
||||
if(locked)
|
||||
return //don't let you cremate something twice or w/e
|
||||
|
||||
// Make sure we don't delete the actual morgue and its tray
|
||||
var/list/conts = get_all_contents() - src - connected
|
||||
|
||||
if(!conts.len)
|
||||
if(!length(conts))
|
||||
audible_message(span_hear("You hear a hollow crackle."))
|
||||
return
|
||||
|
||||
else
|
||||
audible_message(span_hear("You hear a roar as the crematorium activates."))
|
||||
audible_message(span_hear("You hear a roar as the crematorium activates."))
|
||||
locked = TRUE
|
||||
update_appearance()
|
||||
|
||||
locked = TRUE
|
||||
update_appearance()
|
||||
for(var/mob/living/victim in conts)
|
||||
if(victim.incorporeal_move) //can't cook revenants!
|
||||
continue
|
||||
|
||||
for(var/mob/living/M in conts)
|
||||
if(M.incorporeal_move) //can't cook revenants!
|
||||
continue
|
||||
if (M.stat != DEAD)
|
||||
M.emote("scream")
|
||||
if(user)
|
||||
log_combat(user, M, "cremated")
|
||||
else
|
||||
M.log_message("was cremated", LOG_ATTACK)
|
||||
if (victim.stat != DEAD)
|
||||
victim.emote("scream")
|
||||
|
||||
if(user.stat != DEAD)
|
||||
user.investigate_log("has died from being cremated.", INVESTIGATE_DEATHS)
|
||||
M.death(TRUE)
|
||||
if(!QDELETED(M)) //some animals get automatically deleted on death.
|
||||
M.ghostize()
|
||||
qdel(M)
|
||||
if(user)
|
||||
log_combat(user, victim, "cremated")
|
||||
else
|
||||
victim.log_message("was cremated", LOG_ATTACK)
|
||||
|
||||
for(var/obj/O in conts) //conts defined above, ignores crematorium and tray
|
||||
// SKYRAT EDIT ADDITION
|
||||
@@ -502,15 +495,32 @@ GLOBAL_LIST_EMPTY(crematoriums)
|
||||
continue
|
||||
qdel(O)
|
||||
|
||||
if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up
|
||||
new/obj/effect/decal/cleanable/ash(src)
|
||||
if(user.stat != DEAD)
|
||||
user.investigate_log("has died from being cremated.", INVESTIGATE_DEATHS)
|
||||
|
||||
sleep(3 SECONDS)
|
||||
victim.death(TRUE)
|
||||
if(!QDELETED(victim)) //some animals get automatically deleted on death.
|
||||
victim.ghostize()
|
||||
qdel(victim)
|
||||
|
||||
if(!QDELETED(src))
|
||||
locked = FALSE
|
||||
update_appearance()
|
||||
playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people
|
||||
for(var/obj/to_destroy in conts) // conts defined above, ignores crematorium and tray
|
||||
// Indestructible atoms should not be destroyed
|
||||
if(istype(to_destroy, /obj/effect/dummy/phased_mob) || (to_destroy.resistance_flags & INDESTRUCTIBLE))
|
||||
continue
|
||||
qdel(to_destroy)
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(unlock)), 3 SECONDS)
|
||||
|
||||
/obj/structure/bodycontainer/crematorium/proc/unlock()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
|
||||
if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir)) // As to prevent pile-up
|
||||
new /obj/effect/decal/cleanable/ash(src)
|
||||
|
||||
locked = FALSE
|
||||
update_appearance()
|
||||
playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) // You horrible people
|
||||
|
||||
/obj/structure/bodycontainer/crematorium/creamatorium
|
||||
name = "creamatorium"
|
||||
@@ -584,8 +594,8 @@ GLOBAL_LIST_EMPTY(crematoriums)
|
||||
if(!istype(O, /obj/structure/closet/body_bag))
|
||||
return
|
||||
else
|
||||
var/mob/M = O
|
||||
if(M.buckled)
|
||||
var/mob/victim = O
|
||||
if(victim.buckled)
|
||||
return
|
||||
O.forceMove(src.loc)
|
||||
if (user != O)
|
||||
|
||||
@@ -77,6 +77,7 @@ GLOBAL_LIST_INIT(mystery_magic, list(
|
||||
/obj/item/gun/magic/wand/door,
|
||||
/obj/item/gun/magic/wand/nothing,
|
||||
/obj/item/storage/belt/wands/full,
|
||||
/obj/item/storage/belt/wands/full/discount,
|
||||
/obj/item/gun/magic/staff/healing,
|
||||
/obj/item/gun/magic/staff/chaos,
|
||||
/obj/item/gun/magic/staff/door,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
///Should we leave a brain behind when the statue is wrecked?
|
||||
var/brain = TRUE
|
||||
///Time left before the petrification ends and we let the mob free
|
||||
var/timer = 48 SECONDS
|
||||
var/timer = 8 MINUTES
|
||||
///The mob that got medusa'd
|
||||
var/mob/living/petrified_mob
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
/obj/structure/statue/petrified/process(seconds_per_tick)
|
||||
if(!petrified_mob)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
timer -= seconds_per_tick
|
||||
timer -= seconds_per_tick SECONDS
|
||||
petrified_mob.Stun(4 SECONDS) //So they can't do anything while petrified
|
||||
if(timer <= 0)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
#define INVESTIGATE_SIGNBOARD "signboard"
|
||||
#define SIGNBOARD_WIDTH (ICON_SIZE_X * 3.5)
|
||||
#define SIGNBOARD_HEIGHT (ICON_SIZE_Y * 2.5)
|
||||
#define MAX_SIGN_LEN 106
|
||||
|
||||
/obj/structure/signboard
|
||||
name = "sign"
|
||||
desc = "A foldable sign."
|
||||
icon = 'icons/obj/signboards.dmi'
|
||||
icon_state = "sign"
|
||||
base_icon_state = "sign"
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_REQUIRES_DEXTERITY
|
||||
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 5)
|
||||
/// The image holding this sign's text
|
||||
var/image/text_image
|
||||
/// The current text written on the sign
|
||||
var/sign_text
|
||||
/// The maximum length of text that can be input onto the sign.
|
||||
var/max_length = MAX_SIGN_LEN
|
||||
/// If true, the text cannot be changed by players.
|
||||
var/locked = FALSE
|
||||
/// If text should be shown while unanchored.
|
||||
var/show_while_unanchored = FALSE
|
||||
/// If TRUE, the sign can be edited without a pen.
|
||||
var/edit_by_hand = FALSE
|
||||
|
||||
/obj/structure/signboard/Initialize(mapload)
|
||||
. = ..()
|
||||
if(sign_text)
|
||||
set_text(sign_text, force = TRUE)
|
||||
investigate_log("had its text set on load to \"[sign_text]\"", INVESTIGATE_SIGNBOARD)
|
||||
update_appearance()
|
||||
register_context()
|
||||
|
||||
/obj/structure/signboard/Destroy(force)
|
||||
QDEL_NULL(text_image)
|
||||
return ..()
|
||||
|
||||
/obj/structure/signboard/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
. = ..()
|
||||
if(is_locked(user))
|
||||
return
|
||||
if(held_item?.tool_behaviour == TOOL_WRENCH)
|
||||
context[SCREENTIP_CONTEXT_LMB] = anchored ? "Unsecure" : "Secure"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
if((edit_by_hand || istype(held_item, /obj/item/pen)) && (anchored || show_while_unanchored))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Set Displayed Text"
|
||||
if(sign_text)
|
||||
context[SCREENTIP_CONTEXT_ALT_RMB] = "Clear Sign"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
/obj/structure/signboard/examine(mob/user)
|
||||
. = ..()
|
||||
if(!edit_by_hand)
|
||||
. += span_notice("You can write on the sign with a <b>pen.</b>")
|
||||
if(anchored)
|
||||
. += span_notice("It's secured to the floor, you could use a <b>wrench</b> to unsecure and move it.")
|
||||
else
|
||||
. += span_notice("It's unsecured, you could use a <b>wrench</b> to secure it in place.")
|
||||
if(sign_text)
|
||||
. += span_boldnotice("\nIt currently displays the following:")
|
||||
. += span_info(html_encode(sign_text))
|
||||
else
|
||||
. += span_notice("\nIt's blank.")
|
||||
|
||||
/obj/structure/signboard/update_icon_state()
|
||||
. = ..()
|
||||
icon_state = "[base_icon_state][sign_text ? "" : "_blank"]"
|
||||
|
||||
/obj/structure/signboard/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, sign_text))
|
||||
if(!set_text(var_value, force = TRUE))
|
||||
return FALSE
|
||||
datum_flags |= DF_VAR_EDITED
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/structure/signboard/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
if(!istype(tool, /obj/item/pen))
|
||||
return NONE
|
||||
try_set_text(user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/structure/signboard/attack_hand(mob/living/user, list/modifiers)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(!edit_by_hand && !user.is_holding_item_of_type(/obj/item/pen))
|
||||
balloon_alert(user, "need a pen!")
|
||||
return TRUE
|
||||
if(try_set_text(user))
|
||||
return TRUE
|
||||
|
||||
/obj/structure/signboard/proc/try_set_text(mob/living/user)
|
||||
. = FALSE
|
||||
if(!anchored && !show_while_unanchored)
|
||||
return FALSE
|
||||
if(check_locked(user))
|
||||
return FALSE
|
||||
var/new_text = tgui_input_text(
|
||||
user,
|
||||
message = "What would you like to set this sign's text to?",
|
||||
title = full_capitalize(name),
|
||||
default = sign_text,
|
||||
max_length = max_length,
|
||||
multiline = TRUE,
|
||||
encode = FALSE,
|
||||
)
|
||||
if(QDELETED(src) || !new_text || check_locked(user))
|
||||
return FALSE
|
||||
var/list/filter_result = CAN_BYPASS_FILTER(user) ? null : is_ic_filtered(new_text)
|
||||
if(filter_result)
|
||||
REPORT_CHAT_FILTER_TO_USER(user, filter_result)
|
||||
return FALSE
|
||||
var/list/soft_filter_result = CAN_BYPASS_FILTER(user) ? null : is_soft_ic_filtered(new_text)
|
||||
if(soft_filter_result)
|
||||
if(tgui_alert(user, "Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
|
||||
return FALSE
|
||||
message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" when writing to the sign at [ADMIN_VERBOSEJMP(src)], they may be using a disallowed term. Sign text: \"[html_encode(new_text)]\"")
|
||||
log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" when writing to the sign at [loc_name(src)], they may be using a disallowed term. Sign text: \"[new_text]\"")
|
||||
if(set_text(new_text))
|
||||
balloon_alert(user, "set text")
|
||||
investigate_log("([key_name(user)]) set text to \"[sign_text || "(none)"]\"", INVESTIGATE_SIGNBOARD)
|
||||
return TRUE
|
||||
|
||||
/obj/structure/signboard/click_alt_secondary(mob/user)
|
||||
. = ..()
|
||||
if(!sign_text || !can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY))
|
||||
return
|
||||
if(!edit_by_hand && !user.is_holding_item_of_type(/obj/item/pen))
|
||||
balloon_alert(user, "need a pen!")
|
||||
return
|
||||
if(check_locked(user))
|
||||
return
|
||||
if(set_text(null))
|
||||
balloon_alert(user, "cleared text")
|
||||
investigate_log("([key_name(user)]) cleared the text", INVESTIGATE_SIGNBOARD)
|
||||
|
||||
/obj/structure/signboard/wrench_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
if(!anchored || !check_locked(user))
|
||||
default_unfasten_wrench(user, tool)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/structure/signboard/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
|
||||
if(!same_z_layer)
|
||||
SET_PLANE_EXPLICIT(text_image, ABOVE_GAME_PLANE, src)
|
||||
return ..()
|
||||
|
||||
/obj/structure/signboard/set_anchored(anchorvalue)
|
||||
. = ..()
|
||||
update_text()
|
||||
|
||||
/obj/structure/signboard/proc/is_locked(mob/user)
|
||||
if(isAdminGhostAI(user))
|
||||
return FALSE
|
||||
return locked
|
||||
|
||||
/obj/structure/signboard/proc/check_locked(mob/user, silent = FALSE)
|
||||
. = is_locked(user)
|
||||
if(. && !silent)
|
||||
balloon_alert(user, "locked!")
|
||||
|
||||
/obj/structure/signboard/proc/should_display_text()
|
||||
if(QDELETED(src) || !isturf(loc) || !sign_text)
|
||||
return FALSE
|
||||
if(!anchored && !show_while_unanchored)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/structure/signboard/MouseEntered(location, control, params)
|
||||
. = ..()
|
||||
if(QDELETED(src) || !should_display_text())
|
||||
return
|
||||
usr.client.images |= text_image
|
||||
|
||||
/obj/structure/signboard/MouseExited(location, control, params)
|
||||
. = ..()
|
||||
usr.client.images -= text_image
|
||||
|
||||
/// Creates [text_image] if it doesn't exist, and sets its maptext to [sign_text]
|
||||
/obj/structure/signboard/proc/update_text()
|
||||
PROTECTED_PROC(TRUE) // Use set_text instead
|
||||
var/safe_width = src.bound_width || ICON_SIZE_X
|
||||
var/safe_height = src.bound_height || ICON_SIZE_Y
|
||||
var/text_html = MAPTEXT_GRAND9K("<span style='text-align: center; line-height: 1'>[html_encode(sign_text)]</span>")
|
||||
if(!text_image)
|
||||
text_image = new
|
||||
SET_PLANE_EXPLICIT(text_image, ABOVE_GAME_PLANE, src)
|
||||
text_image.alpha = 192
|
||||
text_image.loc = src
|
||||
text_image.maptext_x = (SIGNBOARD_WIDTH - safe_width) * -0.5
|
||||
text_image.maptext_y = safe_height
|
||||
text_image.maptext_width = SIGNBOARD_WIDTH
|
||||
text_image.maptext_height = SIGNBOARD_HEIGHT
|
||||
text_image.maptext = text_html
|
||||
|
||||
/obj/structure/signboard/proc/set_text(new_text, force = FALSE)
|
||||
. = FALSE
|
||||
if(QDELETED(src) || (locked && !force))
|
||||
return
|
||||
if(!istext(new_text) && !isnull(new_text))
|
||||
CRASH("Attempted to set invalid signtext: [new_text]")
|
||||
. = TRUE
|
||||
sign_text = trim(new_text, max_length)
|
||||
update_text()
|
||||
update_appearance()
|
||||
|
||||
#undef MAX_SIGN_LEN
|
||||
#undef SIGNBOARD_HEIGHT
|
||||
#undef SIGNBOARD_WIDTH
|
||||
@@ -0,0 +1,239 @@
|
||||
/obj/structure/signboard/holosign
|
||||
name = "holographic sign"
|
||||
desc = "A holographic signboard, projecting text above it."
|
||||
icon_state = "holographic_sign"
|
||||
base_icon_state = "holographic_sign"
|
||||
layer = ABOVE_MOB_LAYER
|
||||
density = FALSE
|
||||
edit_by_hand = TRUE
|
||||
show_while_unanchored = TRUE
|
||||
light_range = MINIMUM_USEFUL_LIGHT_RANGE
|
||||
light_power = 0.3
|
||||
light_color = COLOR_CARP_TEAL
|
||||
light_on = FALSE
|
||||
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5.05, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.7)
|
||||
/// If set, only IDs with this name can (un)lock the sign.
|
||||
var/registered_owner
|
||||
/// The current color of the sign.
|
||||
/// The sign will be greyscale if this is set.
|
||||
var/current_color
|
||||
|
||||
/obj/structure/signboard/holosign/Initialize(mapload)
|
||||
. = ..()
|
||||
if(current_color)
|
||||
INVOKE_ASYNC(src, PROC_REF(set_color), current_color)
|
||||
AddComponent(/datum/component/usb_port, list(
|
||||
/obj/item/circuit_component/holo_signboard,
|
||||
))
|
||||
|
||||
/obj/structure/signboard/holosign/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
. = ..()
|
||||
var/locked = is_locked(user)
|
||||
if(istype(held_item, /obj/item/card/emag))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Short Out Locking Mechanisms"
|
||||
. = CONTEXTUAL_SCREENTIP_SET
|
||||
else if(!locked && istype(held_item?.GetID(), /obj/item/usb_cable))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Connect USB Cable"
|
||||
else if(!locked && istype(held_item?.GetID(), /obj/item/card/id))
|
||||
context[SCREENTIP_CONTEXT_LMB] = registered_owner ? "Remove ID Lock" : "Lock To ID"
|
||||
. = CONTEXTUAL_SCREENTIP_SET
|
||||
if(!locked)
|
||||
context[SCREENTIP_CONTEXT_RMB] = "Set Sign Color"
|
||||
. = CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
/obj/structure/signboard/holosign/update_icon_state()
|
||||
base_icon_state = current_color ? "[initial(base_icon_state)]_greyscale" : initial(base_icon_state)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
icon_state += "_emag"
|
||||
|
||||
/obj/structure/signboard/holosign/examine(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
. += span_warning("<br>Its locking mechanisms appear to be shorted out!")
|
||||
else if(registered_owner)
|
||||
. += span_info("<br>It is locked to the ID of [span_name(registered_owner)].")
|
||||
|
||||
/obj/structure/signboard/holosign/update_overlays()
|
||||
. = ..()
|
||||
if(sign_text)
|
||||
. += emissive_appearance(icon, "holographic_sign_e", src)
|
||||
|
||||
/obj/structure/signboard/holosign/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, color) || var_name == NAMEOF(src, current_color))
|
||||
INVOKE_ASYNC(src, PROC_REF(set_color), var_value)
|
||||
datum_flags |= DF_VAR_EDITED
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/structure/signboard/holosign/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
var/obj/item/item = astype(tool, /obj/item)
|
||||
var/obj/item/card/id/id = item?.GetID()
|
||||
if(!istype(id) || !can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY))
|
||||
return NONE
|
||||
|
||||
var/trimmed_id_name = trimtext(id.registered_name)
|
||||
if(!trimmed_id_name)
|
||||
balloon_alert(user, "no name on id!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(obj_flags & EMAGGED)
|
||||
balloon_alert(user, "lock shorted out!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(registered_owner)
|
||||
if(!check_locked(user))
|
||||
registered_owner = null
|
||||
balloon_alert(user, "id lock removed")
|
||||
investigate_log("([key_name(user)]) removed id lock", INVESTIGATE_SIGNBOARD)
|
||||
else
|
||||
registered_owner = trimmed_id_name
|
||||
balloon_alert(user, "locked to id")
|
||||
investigate_log("([key_name(user)]) added id lock for \"[registered_owner]\"", INVESTIGATE_SIGNBOARD)
|
||||
update_appearance()
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/structure/signboard/holosign/is_locked(mob/living/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(registered_owner && isliving(user))
|
||||
var/obj/item/card/id/id = user.get_idcard()
|
||||
if(!istype(id) || QDELING(id))
|
||||
return TRUE
|
||||
return !cmptext(trimtext(id.registered_name), registered_owner)
|
||||
|
||||
/obj/structure/signboard/holosign/set_text(new_text, force)
|
||||
. = ..()
|
||||
set_light(l_on = !!sign_text)
|
||||
|
||||
/obj/structure/signboard/holosign/attack_hand_secondary(mob/user, list/modifiers)
|
||||
. = ..()
|
||||
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
||||
return
|
||||
if(try_set_color(user))
|
||||
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/obj/structure/signboard/holosign/proc/try_set_color(mob/user)
|
||||
. = TRUE
|
||||
if(!can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY))
|
||||
return FALSE
|
||||
if(check_locked(user))
|
||||
return
|
||||
var/new_color = sanitize_color(tgui_color_picker(user, "Set Sign Color", full_capitalize(name), current_color))
|
||||
if(new_color && is_color_dark_with_saturation(new_color, 25))
|
||||
balloon_alert(user, "color too dark!")
|
||||
return
|
||||
if(check_locked(user))
|
||||
return
|
||||
INVOKE_ASYNC(src, PROC_REF(set_color), new_color)
|
||||
if(new_color)
|
||||
balloon_alert(user, "set color to [new_color]")
|
||||
investigate_log("([key_name(user)]) set the color to [new_color || "(none)"]", INVESTIGATE_SIGNBOARD)
|
||||
else
|
||||
balloon_alert(user, "unset color")
|
||||
investigate_log("([key_name(user)]) cleared the color", INVESTIGATE_SIGNBOARD)
|
||||
|
||||
/obj/structure/signboard/holosign/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
if(obj_flags & EMAGGED)
|
||||
return FALSE
|
||||
playsound(src, SFX_SPARKS, vol = 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
do_sparks(3, cardinal_only = FALSE, source = src)
|
||||
balloon_alert(user, "lock broken")
|
||||
investigate_log("was emagged by [key_name(user)] (previous owner: [registered_owner || "(none)"])", INVESTIGATE_SIGNBOARD)
|
||||
registered_owner = null
|
||||
obj_flags |= EMAGGED
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/signboard/holosign/proc/sanitize_color(color)
|
||||
. = sanitize_hexcolor(color)
|
||||
if(!. || . == COLOR_BLACK)
|
||||
return null
|
||||
|
||||
/obj/structure/signboard/holosign/proc/set_color(new_color)
|
||||
new_color = sanitize_color(new_color)
|
||||
if(!new_color)
|
||||
current_color = null
|
||||
remove_atom_colour(FIXED_COLOUR_PRIORITY)
|
||||
else
|
||||
current_color = new_color
|
||||
add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
|
||||
set_light(l_color = current_color || src::light_color)
|
||||
update_appearance()
|
||||
|
||||
/obj/item/circuit_component/holo_signboard
|
||||
display_name = "Holographic Signboard"
|
||||
desc = "Output text to a signboard, insert <br> in the message field to linebreak. Set the color to 0, 0, 0 to reset to default."
|
||||
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
|
||||
|
||||
var/datum/port/input/message
|
||||
var/datum/port/input/clear
|
||||
|
||||
var/datum/port/output/fail_reason
|
||||
var/datum/port/output/on_fail
|
||||
|
||||
var/datum/port/input/red
|
||||
var/datum/port/input/green
|
||||
var/datum/port/input/blue
|
||||
var/datum/port/input/set_color
|
||||
|
||||
var/obj/structure/signboard/holosign/connected_display
|
||||
|
||||
/obj/item/circuit_component/holo_signboard/populate_ports()
|
||||
message = add_input_port("Message", PORT_TYPE_STRING)
|
||||
clear = add_input_port("Clear", PORT_TYPE_SIGNAL, trigger = PROC_REF(clear_received))
|
||||
|
||||
fail_reason = add_output_port("Fail Reason", PORT_TYPE_STRING)
|
||||
on_fail = add_output_port("Failed", PORT_TYPE_SIGNAL)
|
||||
|
||||
red = add_input_port("Red", PORT_TYPE_NUMBER)
|
||||
green = add_input_port("Green", PORT_TYPE_NUMBER)
|
||||
blue = add_input_port("Blue", PORT_TYPE_NUMBER)
|
||||
set_color = add_input_port("Set Color", PORT_TYPE_SIGNAL, trigger = PROC_REF(color_received))
|
||||
|
||||
/obj/item/circuit_component/holo_signboard/register_usb_parent(atom/movable/shell)
|
||||
. = ..()
|
||||
if(istype(shell, /obj/structure/signboard/holosign))
|
||||
connected_display = shell
|
||||
|
||||
/obj/item/circuit_component/holo_signboard/input_received(datum/port/input/port)
|
||||
if(!connected_display)
|
||||
return
|
||||
if(length(message.value) > connected_display.max_length) //5000 is a hell of a lot longer than 144.
|
||||
fail_reason.set_output("Too long ([length(message.value)]/[connected_display.max_length]).")
|
||||
on_fail.set_output(COMPONENT_SIGNAL)
|
||||
return
|
||||
if(is_ic_filtered(message.value))
|
||||
fail_reason.set_output("Prohibited content.")
|
||||
on_fail.set_output(COMPONENT_SIGNAL)
|
||||
return
|
||||
|
||||
var/edited_message = replacetextEx_char(message.value, "<br>", "\n")
|
||||
if(connected_display.set_text(edited_message))
|
||||
investigate_log("Circuit USB ([parent.get_creator()]) set text to \"[connected_display.sign_text || "(none)"]\"", INVESTIGATE_SIGNBOARD)
|
||||
if(is_soft_ic_filtered(message.value))
|
||||
message_admins("A circuit component (by [parent.get_creator_admin()]) added a soft filtered message to a signboard. [ADMIN_COORDJMP(src)]")
|
||||
else
|
||||
fail_reason.set_output("Connection refused by external endpoint.")
|
||||
on_fail.set_output(COMPONENT_SIGNAL)
|
||||
|
||||
/obj/item/circuit_component/holo_signboard/proc/clear_received(datum/port/input/port)
|
||||
if(!connected_display.set_text(null))
|
||||
fail_reason.set_output("Connection refused by external endpoint.")
|
||||
on_fail.set_output(COMPONENT_SIGNAL)
|
||||
|
||||
/obj/item/circuit_component/holo_signboard/proc/color_received(datum/port/input/port)
|
||||
red.set_value(clamp(red.value, 0, 255))
|
||||
blue.set_value(clamp(blue.value, 0, 255))
|
||||
green.set_value(clamp(green.value, 0, 255))
|
||||
var/signboard_color = rgb(red.value, green.value, blue.value)
|
||||
if(signboard_color && signboard_color != rgb(0, 0, 0) && is_color_dark_with_saturation(signboard_color, 25))
|
||||
fail_reason.set_output("Color too dark to display.")
|
||||
on_fail.set_output(COMPONENT_SIGNAL)
|
||||
return
|
||||
connected_display.set_color(signboard_color) //doesnt have a return so no need to check and error
|
||||
investigate_log("Circuit USB ([parent.get_creator()]) set the color to [signboard_color || "(none)"]", INVESTIGATE_SIGNBOARD)
|
||||
|
||||
/// Given a color in the format of "#RRGGBB", will return if the color
|
||||
/// is dark. Value is mixed with Saturation and Brightness from HSV.
|
||||
/proc/is_color_dark_with_saturation(color, threshold = 25)
|
||||
var/hsl = rgb2num(color, COLORSPACE_HSL)
|
||||
return hsl[3] < threshold
|
||||
@@ -1,14 +1,15 @@
|
||||
/obj/structure/sign/clock
|
||||
name = "wall clock"
|
||||
desc = "It's your run-of-the-mill wall clock showing both the local Coalition Standard Time and the galactic Treaty Coordinated Time. Perfect for staring at instead of working."
|
||||
desc = "It's your run-of-the-mill wall clock showing both the local Nanotrasen Standard Time and the galactic Treaty Coordinated Time. Perfect for staring at instead of working."
|
||||
icon_state = "clock"
|
||||
|
||||
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/clock, 32)
|
||||
|
||||
/obj/structure/sign/clock/examine(mob/user)
|
||||
. = ..()
|
||||
. += span_info("The current CST (local) time is: [station_time_timestamp()].")
|
||||
. += span_info("The current TCT (galactic) time is: [time2text(world.realtime, "hh:mm:ss", 0)].")
|
||||
. += span_info("The current NST (local) time is: [server_timestamp(ic_time = TRUE, twelve_hour_clock = user.client?.prefs.read_preference(/datum/preference/toggle/twelve_hour))].")
|
||||
if(user.is_literate())
|
||||
. += span_info("That means it is currently [round_timestamp()] into the shift.")
|
||||
|
||||
/obj/structure/sign/calendar
|
||||
name = "wall calendar"
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
return FALSE
|
||||
new /obj/item/flamethrower(get_turf(src))
|
||||
var/obj/item/tank/internals/plasma/ptank = new /obj/item/tank/internals/plasma(get_turf(src))
|
||||
ptank.air_contents.gases[/datum/gas/plasma][MOLES] = (0)
|
||||
ptank.air_contents.set_gas(/datum/gas/plasma, 0)
|
||||
drop_custom_materials()
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
|
||||
/obj/structure/transit_tube_pod/Initialize(mapload)
|
||||
. = ..()
|
||||
air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen)
|
||||
air_contents.gases[/datum/gas/oxygen][MOLES] = MOLES_O2STANDARD
|
||||
air_contents.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD
|
||||
var/list/new_gases = list(/datum/gas/oxygen = MOLES_O2STANDARD, /datum/gas/nitrogen = MOLES_N2STANDARD)
|
||||
air_contents.adjust_multiple_gases(new_gases)
|
||||
air_contents.temperature = T20C
|
||||
|
||||
/obj/structure/transit_tube_pod/Destroy()
|
||||
|
||||
@@ -849,17 +849,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw
|
||||
flags_1 = PREVENT_CLICK_UNDER_1
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
|
||||
/obj/structure/window/reinforced/shuttle/indestructible/welder_act(mob/living/user, obj/item/tool)
|
||||
return NONE
|
||||
|
||||
/obj/structure/window/reinforced/shuttle/indestructible/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
return NONE
|
||||
|
||||
/obj/structure/window/reinforced/shuttle/indestructible/wrench_act(mob/living/user, obj/item/tool)
|
||||
return NONE
|
||||
|
||||
/obj/structure/window/reinforced/shuttle/indestructible/crowbar_act(mob/living/user, obj/item/tool)
|
||||
return NONE
|
||||
/obj/structure/window/reinforced/shuttle/indestructible/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/tool_blocker, TOOL_WELDER, TOOL_ACT_PRIMARY)
|
||||
AddElement(/datum/element/tool_blocker, TOOL_SCREWDRIVER, TOOL_ACT_PRIMARY)
|
||||
AddElement(/datum/element/tool_blocker, TOOL_WRENCH, TOOL_ACT_PRIMARY)
|
||||
AddElement(/datum/element/tool_blocker, TOOL_CROWBAR, TOOL_ACT_PRIMARY)
|
||||
|
||||
/obj/structure/window/reinforced/plasma/plastitanium
|
||||
name = "plastitanium window"
|
||||
|
||||
Reference in New Issue
Block a user