diff --git a/code/__defines/armor.dm b/code/__defines/armor.dm
index 155ccb22f4ac..c43e7cc2f263 100644
--- a/code/__defines/armor.dm
+++ b/code/__defines/armor.dm
@@ -33,6 +33,7 @@
#define ARMOR_RAD_MINOR 10
#define ARMOR_RAD_SMALL 25
#define ARMOR_RAD_RESISTANT 40
+#define ARMOR_RAD_LARGE 60
#define ARMOR_RAD_SHIELDED 100
#define ARMOR_BOMB_MINOR 10
diff --git a/code/_global_vars/lists/flavor.dm b/code/_global_vars/lists/flavor.dm
index 80c11924a919..7a21627335d2 100644
--- a/code/_global_vars/lists/flavor.dm
+++ b/code/_global_vars/lists/flavor.dm
@@ -118,6 +118,7 @@ GLOBAL_GETTER(cable_colors, /list, SetupCableColors())
. = list()
var/list/valid_cable_coils = typesof(/obj/item/stack/cable_coil) - typesof(
+ /obj/item/stack/cable_coil/five,
/obj/item/stack/cable_coil/single,
/obj/item/stack/cable_coil/cut,
/obj/item/stack/cable_coil/cyborg,
diff --git a/code/_global_vars/sound.dm b/code/_global_vars/sound.dm
index bab80ae12bee..fec9867b5f09 100644
--- a/code/_global_vars/sound.dm
+++ b/code/_global_vars/sound.dm
@@ -128,4 +128,11 @@ var/global/list/sweeping_sound = list(
'sound/foley/sweeping5.ogg',
'sound/foley/sweeping6.ogg',
'sound/foley/sweeping7.ogg',
-)
\ No newline at end of file
+)
+
+var/global/list/ricochet_sound = list(
+ 'sound/weapons/guns/ricochet1.ogg',
+ 'sound/weapons/guns/ricochet2.ogg',
+ 'sound/weapons/guns/ricochet3.ogg',
+ 'sound/weapons/guns/ricochet4.ogg'
+)
diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm
index 2cd597169bd9..ed43cf149ee3 100644
--- a/code/_helpers/type2type.dm
+++ b/code/_helpers/type2type.dm
@@ -1,13 +1,20 @@
/*
* Holds procs designed to change one type of value, into another.
* Contains:
- * text2list & list2text
+ * alist2list
* file2list
* angle2dir
* angle2text
- * worldtime2text
*/
+// This proc does not support converting numerically indexed alists to assoc lists.
+/proc/alist2list(alist/input)
+ . = list()
+ for(var/k,v in input)
+ if(isnum(k))
+ CRASH("Numeric index passed to alist2list()!")
+ .[k] = v
+
// Splits the text of a file at seperator and returns them in a list.
/proc/file2list(filename, seperator = "\n")
return splittext(safe_file2text(filename), seperator)
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 51398a81da00..727108ce080a 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -52,7 +52,6 @@
#define ui_movi "RIGHT-2:24,BOTTOM:5"
#define ui_attack_selector "RIGHT-2:27,BOTTOM+2:9"
#define ui_zonesel "RIGHT-1:28,BOTTOM:5"
-#define ui_stamina "RIGHT-2:24,BOTTOM:8"
#define ui_borg_module "RIGHT-1:28,BOTTOM+1:7"
diff --git a/code/_onclick/hud/hud_types/_hud.dm b/code/_onclick/hud/hud_types/_hud.dm
index 9e65b258399d..5a64f04f09a9 100644
--- a/code/_onclick/hud/hud_types/_hud.dm
+++ b/code/_onclick/hud/hud_types/_hud.dm
@@ -92,6 +92,9 @@
var/action_buttons_hidden = FALSE
var/obj/screen/action_button/hide_toggle/hide_actions_toggle
+ var/const/HAND_UI_PER_ROW = 4
+ var/const/HAND_UI_INITIAL_Y_OFFSET = 21
+
// TODO: declify these.
VAR_PROTECTED/gun_mode_toggle_type
VAR_PRIVATE/obj/screen/gun/mode/gun_mode_toggle
@@ -351,11 +354,10 @@
qdel(inv_box)
// Rebuild offsets for the hand elements.
- var/const/elems_per_row = 4
- var/hand_y_offset = 21
+ var/hand_y_offset = HAND_UI_INITIAL_Y_OFFSET
var/list/elements = hud_elements_hands?.Copy()
while(length(elements))
- var/copy_index = min(length(elements), elems_per_row)+1
+ var/copy_index = min(length(elements), HAND_UI_PER_ROW)+1
var/list/sublist = elements.Copy(1, copy_index)
elements.Cut(1, copy_index)
var/hand_x_offset = (world.icon_size/2) * (1 - length(sublist))
@@ -389,6 +391,7 @@
if(mymob.client)
mymob.client.screen |= swap_elem
+ refresh_element(HUD_STAMINA)
update_hand_elements()
return TRUE
diff --git a/code/_onclick/hud/screen/screen_stamina.dm b/code/_onclick/hud/screen/screen_stamina.dm
index 5d4f34b32da9..851fdf1d8682 100644
--- a/code/_onclick/hud/screen/screen_stamina.dm
+++ b/code/_onclick/hud/screen/screen_stamina.dm
@@ -1,24 +1,41 @@
/obj/screen/stamina
name = "stamina"
- icon = 'icons/effects/progressbar.dmi'
- icon_state = "prog_bar_100"
+ icon = 'icons/effects/staminabar.dmi'
+ icon_state = "bar"
invisibility = INVISIBILITY_MAXIMUM
- screen_loc = ui_stamina
use_supplied_ui_color = FALSE
use_supplied_ui_alpha = FALSE
use_supplied_ui_icon = FALSE
requires_ui_style = FALSE
layer = HUD_BASE_LAYER + 0.1 // needs to layer over the movement intent element
+ var/const/STAMINA_STATE_PERIOD = 5
+
+/obj/screen/stamina/Initialize(mapload, mob/_owner, decl/ui_style/ui_style, ui_color, ui_alpha, ui_cat)
+ . = ..()
+ update_icon()
/obj/screen/stamina/on_update_icon()
. = ..()
var/mob/living/owner = owner_ref?.resolve()
if(!istype(owner))
set_invisibility(INVISIBILITY_MAXIMUM)
+ return
+
+ var/hand_row_offset = /datum/hud::HAND_UI_INITIAL_Y_OFFSET + (ceil(length(owner.get_held_item_slots()) / /datum/hud::HAND_UI_PER_ROW) * world.icon_size) + 16
+ screen_loc = "CENTER:-32,BOTTOM:[hand_row_offset]"
+
+ var/stamina = owner.get_stamina()
+ cut_overlays()
+ if(stamina < 100)
+ set_invisibility(INVISIBILITY_NONE)
+ var/stamina_amt = floor(stamina/STAMINA_STATE_PERIOD)*STAMINA_STATE_PERIOD
+ var/bar_overlay_state = "bar_[stamina_amt]"
+ if(stamina_amt > 0 && stamina <= 25)
+ bar_overlay_state = "[bar_overlay_state]_fail"
+ var/image/bar_overlay = image(icon = icon, icon_state = bar_overlay_state)
+ bar_overlay.appearance_flags |= RESET_COLOR
+ bar_overlay.color = COLOR_WHITE
+ add_overlay(bar_overlay)
else
- var/stamina = owner.get_stamina()
- if(stamina < 100)
- set_invisibility(INVISIBILITY_NONE)
- icon_state = "prog_bar_[floor(stamina/5)*5][(stamina >= 5) && (stamina <= 25) ? "_fail" : null]"
- else
- set_invisibility(INVISIBILITY_MAXIMUM)
+ set_invisibility(INVISIBILITY_MAXIMUM)
+ compile_overlays()
\ No newline at end of file
diff --git a/code/controllers/subsystems/initialization/persistence.dm b/code/controllers/subsystems/initialization/persistence.dm
index c1119f8ca859..767d46bbf0cc 100644
--- a/code/controllers/subsystems/initialization/persistence.dm
+++ b/code/controllers/subsystems/initialization/persistence.dm
@@ -38,11 +38,19 @@ SUBSYSTEM_DEF(persistence)
var/turf/T = get_turf(value)
if(!T)
- return
+ return FALSE
- var/area/A = get_area(T)
- if(!A || (A.area_flags & AREA_FLAG_IS_NOT_PERSISTENT))
- return
+ var/decl/persistence_handler/handler = RESOLVE_TO_DECL(track_type)
+ if(!istype(handler))
+ return FALSE
+
+ if(handler.station_restricted && (!T || !(T.z in SSmapping.station_levels) ))
+ return FALSE
+
+ if(handler.area_restricted)
+ var/area/A = get_area(T)
+ if(!A || (A.area_flags & AREA_FLAG_IS_NOT_PERSISTENT))
+ return FALSE
var/datum/level_data/level = SSmapping.levels_by_z[T.z]
if(!istype(level) || !level.permit_persistence)
diff --git a/code/datums/composite_sounds/vehicle_engine.dm b/code/datums/composite_sounds/vehicle_engine.dm
new file mode 100644
index 000000000000..824d0044134b
--- /dev/null
+++ b/code/datums/composite_sounds/vehicle_engine.dm
@@ -0,0 +1,7 @@
+/datum/composite_sound/vehicle_engine
+ start_sound = 'sound/machines/vehicle/engine_start.ogg'
+ start_length = 2
+ mid_sounds = list('sound/machines/vehicle/engine_mid.ogg'=1)
+ mid_length = 6
+ end_sound = 'sound/machines/vehicle/engine_end.ogg'
+ play_volume = 20
diff --git a/code/datums/repositories/atom_info.dm b/code/datums/repositories/atom_info.dm
index a84f10736b76..2cfa4bd9ff3e 100644
--- a/code/datums/repositories/atom_info.dm
+++ b/code/datums/repositories/atom_info.dm
@@ -1,13 +1,14 @@
var/global/repository/atom_info/atom_info_repository = new()
/repository/atom_info
- var/list/matter_cache = list()
+ var/list/matter_cache = list()
var/list/combined_worth_cache = list()
- var/list/single_worth_cache = list()
- var/list/name_cache = list()
- var/list/description_cache = list()
- var/list/matter_mult_cache = list()
- var/list/origin_tech_cache = list()
+ var/list/single_worth_cache = list()
+ var/list/name_cache = list()
+ var/list/description_cache = list()
+ var/list/matter_mult_cache = list()
+ var/list/origin_tech_cache = list()
+ var/list/appearance_cache = list()
/repository/atom_info/proc/create_key_for(var/_path, var/_mat, var/_amount)
. = "[_path]"
@@ -24,7 +25,7 @@ var/global/repository/atom_info/atom_info_repository = new()
else
. = new _path
-/repository/atom_info/proc/update_cached_info_for(var/_path, var/_mat, var/_amount, var/key)
+/repository/atom_info/proc/update_cached_info_for(var/_path, var/_mat, var/_amount, var/key, var/cache_appearance = FALSE)
var/atom/instance
if(!matter_cache[key])
instance = get_instance_of(_path, _mat, _amount)
@@ -41,6 +42,9 @@ var/global/repository/atom_info/atom_info_repository = new()
if(!description_cache[key])
instance = instance || get_instance_of(_path, _mat, _amount)
description_cache[key] = instance.desc
+ if(cache_appearance && !appearance_cache[key])
+ instance = instance || get_instance_of(_path, _mat, _amount)
+ appearance_cache[key] = instance.appearance
if(!matter_mult_cache[key] && ispath(_path, /obj))
var/obj/obj_instance = instance || get_instance_of(_path, _mat, _amount)
matter_mult_cache[key] = obj_instance.get_matter_amount_modifier()
@@ -84,4 +88,10 @@ var/global/repository/atom_info/atom_info_repository = new()
/repository/atom_info/proc/get_origin_tech_for(var/_path, var/_mat, var/_amount)
var/key = create_key_for(_path, _mat, _amount)
update_cached_info_for(_path, _mat, _amount, key)
- . = origin_tech_cache[key]
\ No newline at end of file
+ . = origin_tech_cache[key]
+
+// Bespoke proc; only cache appearance if and when this proc is called, not more generally.
+/repository/atom_info/proc/get_appearance_of(var/_path, var/_mat, var/_amount)
+ var/key = create_key_for(_path, _mat, _amount)
+ update_cached_info_for(_path, _mat, _amount, key, cache_appearance = TRUE)
+ . = appearance_cache[key]
diff --git a/code/datums/supplypacks/operations.dm b/code/datums/supplypacks/operations.dm
index 09c51789dc3c..0ec89477c5e1 100644
--- a/code/datums/supplypacks/operations.dm
+++ b/code/datums/supplypacks/operations.dm
@@ -3,13 +3,13 @@
/decl/hierarchy/supply_pack/operations/cargotrain
name = "Equipment - Cargo Train Tug"
- contains = list(/obj/vehicle/train/cargo/engine)
+ contains = list(/obj/vehicle/train/engine)
containertype = /obj/structure/largecrate
containername = "cargo train tug crate"
/decl/hierarchy/supply_pack/operations/cargotrailer
name = "Equipment - Cargo Train Trolley"
- contains = list(/obj/vehicle/train/cargo/trolley)
+ contains = list(/obj/vehicle/train/trolley)
containertype = /obj/structure/largecrate
containername = "cargo train trolley crate"
diff --git a/code/datums/supplypacks/science.dm b/code/datums/supplypacks/science.dm
index d56132bab632..13e8cd330f2e 100644
--- a/code/datums/supplypacks/science.dm
+++ b/code/datums/supplypacks/science.dm
@@ -66,12 +66,3 @@
name = "Gear - Illumination grenades"
contains = list(/obj/item/grenade/light = 8)
containername = "illumination grenade crate"
-
-/decl/hierarchy/supply_pack/science/stasis_cages
- name = "Stasis Cage"
- contains = list(
- /obj/structure/stasis_cage = 1
- )
- containertype = /obj/structure/closet/crate/large
- containername = "stasis cage crate"
- access = access_xenofauna
diff --git a/code/datums/supplypacks/supplypack.dm b/code/datums/supplypacks/supplypack.dm
index 319d1dee4089..7544b406d35f 100644
--- a/code/datums/supplypacks/supplypack.dm
+++ b/code/datums/supplypacks/supplypack.dm
@@ -19,13 +19,16 @@ var/global/list/cargoprices = list()
. = ..() // make sure children are set up
if(is_category())
return // don't do any of this for categories
+ var/total_contained = 0
+ for(var/entry in contains)
+ total_contained += max(1, contains[entry])
if(!num_contained)
- for(var/entry in contains)
- num_contained += max(1, contains[entry])
+ num_contained = total_contained
if(isnull(cost))
cost = 0
for(var/entry in contains)
cost += atom_info_repository.get_combined_worth_for(entry) * max(1, contains[entry])
+ cost *= num_contained / total_contained // if you get a random selection, it costs the expected value rather than the total worth. gambling!
cost += containertype ? atom_info_repository.get_single_worth_for(containertype) : 0
cost = max(1, NONUNIT_CEILING((cost * WORTH_TO_SUPPLY_POINTS_CONSTANT * SSsupply.price_markup), WORTH_TO_SUPPLY_POINTS_ROUND_CONSTANT))
global.cargoprices[name] = cost
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 28a1dc589ec6..e698467579de 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -742,17 +742,19 @@
- `post_climb_check?`: If we should check if the user can continue climbing
- Return: `TRUE` if they can climb, otherwise `FALSE`
*/
-/atom/proc/can_climb(var/mob/living/user, post_climb_check=0)
+/atom/proc/can_climb(mob/living/user, post_climb_check = FALSE, silent = FALSE)
if (!(atom_flags & ATOM_FLAG_CLIMBABLE) || !user.can_touch(src) || (!post_climb_check && climbers && (user in climbers)))
return FALSE
if (!user.Adjacent(src))
- to_chat(user, "You can't climb there, the way is blocked.")
+ if(!silent)
+ to_chat(user, SPAN_WARNING("You can't climb there, the way is blocked."))
return FALSE
var/obj/occupied = turf_is_crowded(user)
if(occupied)
- to_chat(user, "There's \a [occupied] in the way.")
+ if(!silent)
+ to_chat(user, SPAN_WARNING("There's \a [occupied] in the way."))
return FALSE
return TRUE
@@ -1065,3 +1067,18 @@
if(blood_color)
return FONT_COLORED(blood_color, "stained")
return null
+
+// Used to mark a turf as containing objects that are dangerous to step onto.
+/atom/proc/register_dangerous_to_step()
+ var/turf/T = get_turf(src)
+ if(T)
+ T.register_dangerous_object(src)
+
+/atom/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.
+/atom/proc/is_safe_to_step(mob/living/stepper)
+ return TRUE
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 94c8602eefee..628b5880042e 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -628,3 +628,7 @@
/atom/movable/proc/is_valid_merchant_pad_target()
return simulated
+
+// TODO reimplement this properly.
+/atom/movable/proc/is_incorporeal()
+ return !simulated
diff --git a/code/game/jobs/access_datum.dm b/code/game/jobs/access_datum.dm
index 1ed16660eeb9..0d6369221804 100644
--- a/code/game/jobs/access_datum.dm
+++ b/code/game/jobs/access_datum.dm
@@ -268,12 +268,6 @@ var/global/const/access_research = "ACCESS_RESEARCH" //47
desc = "Science"
region = ACCESS_REGION_RESEARCH
-var/global/const/access_explorer = "ACCESS_EXPLORER"
-/datum/access/explorer
- id = access_explorer
- desc = "Explorer"
- region = ACCESS_REGION_GENERAL
-
var/global/const/access_mining = "ACCESS_MINING" //48
/datum/access/mining
id = access_mining
@@ -310,12 +304,6 @@ var/global/const/access_xenobiology = "ACCESS_XENOBIO" //55
desc = "Xenobiology Lab"
region = ACCESS_REGION_RESEARCH
-var/global/const/access_xenofauna = "ACCESS_XENOFAUNA"
-/datum/access/xenofauna
- id = access_xenofauna
- desc = "Xenfauna Lab"
- region = ACCESS_REGION_RESEARCH
-
var/global/const/access_ce = "ACCESS_CHIEF_ENGINEER" //56
/datum/access/ce
id = access_ce
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index eab4f5d6445b..464042c168e3 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -161,7 +161,7 @@
TLV["temperature"] = list(T0C-26, T0C, T0C+40, T0C+66) // K
var/decl/environment_data/env_info = GET_DECL(environment_type)
- for(var/g in decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ for(var/g in get_filterable_material_types())
if(!env_info.important_gasses[g])
trace_gas += g
// not everything in these lists is a subtype of /decl/material/gas, so:
diff --git a/code/game/machinery/atmoalter/scrubber.dm b/code/game/machinery/atmoalter/scrubber.dm
index b6989e112bdd..80e331893ec2 100644
--- a/code/game/machinery/atmoalter/scrubber.dm
+++ b/code/game/machinery/atmoalter/scrubber.dm
@@ -24,7 +24,7 @@
. = ..()
if(!scrubbing_gas)
scrubbing_gas = list()
- for(var/g in decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ for(var/g in get_filterable_material_types())
if(g != /decl/material/gas/oxygen && g != /decl/material/gas/nitrogen)
scrubbing_gas += g
diff --git a/code/game/objects/__objs.dm b/code/game/objects/__objs.dm
index 01cc88b50e31..123678e0cee3 100644
--- a/code/game/objects/__objs.dm
+++ b/code/game/objects/__objs.dm
@@ -493,4 +493,3 @@
if(anchored)
return FALSE
return ..()
-
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
index 52c615b4daeb..1bf090caf26e 100644
--- a/code/game/objects/effects/effect_system.dm
+++ b/code/game/objects/effects/effect_system.dm
@@ -280,6 +280,59 @@ steam.start() -- spawns the effect
ADJ_STATUS(M, STAT_ASLEEP, 1)
M.cough()
+/////////////////////////////////////////////
+// 'Elemental' smoke
+/////////////////////////////////////////////
+/obj/effect/effect/smoke/elemental
+ name = "cloud"
+ desc = "A cloud of some kind that seems really generic and boring."
+ opacity = FALSE
+ abstract_type = /obj/effect/effect/smoke/elemental
+ var/strength = 5 // How much damage to do inside each affect()
+
+/obj/effect/effect/smoke/elemental/Initialize()
+ START_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/effect/effect/smoke/elemental/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/effect/effect/smoke/elemental/Move(atom/old_loc, direction, forced = FALSE)
+ . = ..()
+ if(.)
+ for(var/mob/living/victim in range(1, src))
+ affect(victim)
+
+/obj/effect/effect/smoke/elemental/Process()
+ for(var/mob/living/victim in range(1, src))
+ affect(victim)
+
+/obj/effect/effect/smoke/elemental/proc/affect(mob/living/victim)
+ return
+
+/obj/effect/effect/smoke/elemental/fire
+ name = "burning cloud"
+ desc = "A cloud of something that is on fire."
+ color = COLOR_ORANGE
+ light_color = "#ff0000"
+ light_range = 2
+ light_power = 5
+
+/obj/effect/effect/smoke/elemental/fire/affect(mob/living/victim)
+ victim.take_damage(strength, BURN)
+ victim.ignite_fire()
+
+/obj/effect/effect/smoke/elemental/mist
+ name = "misty cloud"
+ desc = "A cloud filled with water vapor."
+ color = "#ccffff"
+ alpha = 128
+ strength = 1
+
+/obj/effect/effect/smoke/elemental/mist/affect(mob/living/victim)
+ victim.extinguish_fire()
+
/////////////////////////////////////////////
// Mustard Gas
/////////////////////////////////////////////
@@ -358,6 +411,11 @@ steam.start() -- spawns the effect
/datum/effect/effect/system/smoke_spread/sleepy
smoke_type = /obj/effect/effect/smoke/sleepy
+/datum/effect/effect/system/smoke_spread/fire
+ smoke_type = /obj/effect/effect/smoke/elemental/fire
+
+/datum/effect/effect/system/smoke_spread/mist
+ smoke_type = /obj/effect/effect/smoke/elemental/mist
/datum/effect/effect/system/smoke_spread/mustard
smoke_type = /obj/effect/effect/smoke/mustard
diff --git a/code/game/objects/effects/map_effect/_map_effect.dm b/code/game/objects/effects/map_effect/_map_effect.dm
new file mode 100644
index 000000000000..50cad4fa38c5
--- /dev/null
+++ b/code/game/objects/effects/map_effect/_map_effect.dm
@@ -0,0 +1,31 @@
+/obj/abstract/map_effect
+ icon = 'icons/effects/map_effects.dmi'
+
+ // Below vars concern check_for_player_proximity() and is used to not waste effort if nobody is around to appreciate the effects.
+ /// If true, the game will not try to suppress this from firing if nobody is around to see it.
+ var/always_run = FALSE
+ /// How many tiles a mob with a client must be for this to run.
+ var/proximity_needed = 12
+ /// If true, ghosts won't satisfy the above requirement.
+ var/ignore_ghosts = FALSE
+ /// If true, AFK people (5 minutes) won't satisfy it as well.
+ var/ignore_afk = TRUE
+ /// How long until we check for players again.
+ var/retry_delay = 5 SECONDS
+ /// Next time we're going to do ACTUAL WORK
+ var/next_attempt = 0
+
+// Helper proc to optimize the use of effects by making sure they do not run if nobody is around to perceive it.
+/obj/abstract/map_effect/proc/check_for_player_proximity(radius = 12, ignore_ghosts = FALSE, ignore_afk = TRUE)
+ if(!z)
+ return FALSE
+ for(var/mob/player as anything in player_list)
+ if(player.z != z)
+ continue
+ if(ignore_ghosts && isobserver(player))
+ continue
+ if(ignore_afk && player.client && player.client.is_afk(5 MINUTES))
+ continue
+ if(get_dist(player, src) <= radius)
+ return TRUE
+ return FALSE
diff --git a/code/game/objects/effects/map_effect/interval/_interval.dm b/code/game/objects/effects/map_effect/interval/_interval.dm
new file mode 100644
index 000000000000..ba898570c761
--- /dev/null
+++ b/code/game/objects/effects/map_effect/interval/_interval.dm
@@ -0,0 +1,32 @@
+// Base type for effects that run on variable intervals.
+/obj/abstract/map_effect/interval
+ var/interval_lower_bound = 5 SECONDS // Lower number for how often the map_effect will trigger.
+ var/interval_upper_bound = 5 SECONDS // Higher number for above.
+
+/obj/abstract/map_effect/interval/Initialize()
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/abstract/map_effect/interval/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+// Override this for the specific thing to do.
+/obj/abstract/map_effect/interval/proc/trigger_map_effect()
+ return
+
+// Handles the delay and making sure it doesn't run when it would be bad.
+/obj/abstract/map_effect/interval/Process()
+
+ //Not yet!
+ if(world.time < next_attempt)
+ return
+
+ // Check to see if we're useful first.
+ if(!always_run && !check_for_player_proximity(proximity_needed, ignore_ghosts, ignore_afk))
+ next_attempt = world.time + retry_delay
+ return
+
+ // Hey there's someone nearby.
+ next_attempt = world.time + rand(interval_lower_bound, interval_upper_bound)
+ trigger_map_effect()
diff --git a/code/game/objects/effects/map_effect/interval/effect_emitter.dm b/code/game/objects/effects/map_effect/interval/effect_emitter.dm
new file mode 100644
index 000000000000..0daf63388e09
--- /dev/null
+++ b/code/game/objects/effects/map_effect/interval/effect_emitter.dm
@@ -0,0 +1,63 @@
+/obj/abstract/map_effect/interval/effect_emitter
+ /// Effect system attached. Set to type to create in Initialize().
+ var/datum/effect/effect/system/effect_system = null
+ /// How many effect objects to create on each interval. Note that there's a hard cap on certain effect_systems.
+ var/effect_amount = 10
+ /// If true, effects only move in cardinal directions.
+ var/effect_cardinals_only = FALSE
+ /// If set, effects emitted will always move in this direction.
+ var/effect_forced_dir
+
+/obj/abstract/map_effect/interval/effect_emitter/Initialize()
+ if(ispath(effect_system))
+ effect_system = new effect_system()
+ if(!istype(effect_system))
+ return INITIALIZE_HINT_QDEL
+ effect_system.attach(src)
+ return ..()
+
+/obj/abstract/map_effect/interval/effect_emitter/interval/Destroy()
+ QDEL_NULL(effect_system)
+ return ..()
+
+
+/obj/abstract/map_effect/interval/effect_emitter/trigger_map_effect()
+ to_world("[type]: effect firing")
+ if(istype(effect_system) && !QDELETED(src))
+ effect_system.set_up(effect_amount, effect_cardinals_only, src.loc, effect_forced_dir)
+ effect_system.start()
+
+// Makes sparks.
+/obj/abstract/map_effect/interval/effect_emitter/sparks
+ name = "spark emitter"
+ icon_state = "spark_emitter"
+ effect_system = /datum/effect/effect/system/spark_spread
+ interval_lower_bound = 3 SECONDS
+ interval_upper_bound = 7 SECONDS
+
+// Makes ""steam"" that looks like fire extinguisher water except it does nothing.
+/obj/abstract/map_effect/interval/effect_emitter/steam
+ name = "steam emitter"
+ icon_state = "smoke_emitter"
+ effect_system = /datum/effect/effect/system/steam_spread
+
+// Creates smoke clouds every so often.
+/obj/abstract/map_effect/interval/effect_emitter/smoke
+ name = "smoke emitter"
+ icon_state = "smoke_emitter"
+ effect_system = /datum/effect/effect/system/smoke_spread
+ interval_lower_bound = 1 SECOND
+ interval_upper_bound = 1 SECOND
+ effect_amount = 2
+
+/obj/abstract/map_effect/interval/effect_emitter/smoke/mist
+ name = "mist smoke emitter"
+ effect_system = /datum/effect/effect/system/smoke_spread/mist
+
+/obj/abstract/map_effect/interval/effect_emitter/smoke/bad
+ name = "bad smoke emitter"
+ effect_system = /datum/effect/effect/system/smoke_spread/bad
+
+/obj/abstract/map_effect/interval/effect_emitter/smoke/fire
+ name = "fire smoke emitter"
+ effect_system = /datum/effect/effect/system/smoke_spread/fire
diff --git a/code/game/objects/effects/map_effect/interval/screen_shaker.dm b/code/game/objects/effects/map_effect/interval/screen_shaker.dm
new file mode 100644
index 000000000000..724bb7a256df
--- /dev/null
+++ b/code/game/objects/effects/map_effect/interval/screen_shaker.dm
@@ -0,0 +1,17 @@
+/obj/abstract/map_effect/interval/screen_shaker
+ name = "screen shaker"
+ icon_state = "screen_shaker"
+ interval_lower_bound = 1 SECOND
+ interval_upper_bound = 2 SECONDS
+
+ /// How far the shaking effect extends to. By default it is one screen length.
+ var/shake_radius = 7
+ /// How long the shaking lasts.
+ var/shake_duration = 2
+ /// How much it shakes.
+ var/shake_strength = 1
+
+/obj/abstract/map_effect/interval/screen_shaker/trigger_map_effect()
+ for(var/mob/player in player_list)
+ if(player.z == z && get_dist(src, player) <= shake_radius)
+ shake_camera(player, shake_duration, shake_strength)
diff --git a/code/game/objects/effects/map_effect/interval/sound_emitter.dm b/code/game/objects/effects/map_effect/interval/sound_emitter.dm
new file mode 100644
index 000000000000..058434882b32
--- /dev/null
+++ b/code/game/objects/effects/map_effect/interval/sound_emitter.dm
@@ -0,0 +1,51 @@
+/obj/abstract/map_effect/interval/sound_emitter
+// Plays a sound at its location every so often.
+ name = "sound emitter"
+ icon_state = "sound_emitter"
+ /// How loud the sound is. 0 is silent, and 100 is loudest. Please be reasonable with the volume. Note that things like vacuum may affect the volume heard by other mobs.
+ var/sound_volume = 50
+ /// If the sound will sound somewhat different each time. If a specific frequency is desired, sound_frequency must also be set.
+ var/sound_frequency_variance = TRUE
+ /// Set to make sounds heard from farther away than normal.
+ var/sound_extra_range = 0
+ /// Within the 'fallout distance', the sound stays at the same volume, otherwise it attenuates. Higher numbers make the sound fade out more slowly with distance.
+ var/sound_fallout = 0
+ /// If true, sounds will not be distorted due to the current area's 'sound environment'. It DOES NOT make the sound have a constant volume or z-level wide range, despite the misleading name.
+ var/sound_global = FALSE
+ /// Sets a specific custom frequency. sound_frequency_variance must be true as well. If sound_frequency is null, but sound_frequency_variance is true, a semi-random frequency will be chosen to the sound each time.
+ var/sound_frequency = null
+ /// Whether or not clients with the ambience preference disabled will hear this sound.
+ var/sound_is_ambience = TRUE
+ /// If false, walls will completely muffle the sound.
+ var/sound_ignore_walls = TRUE
+
+/obj/abstract/map_effect/interval/sound_emitter/proc/get_sounds_to_play()
+ return
+
+/obj/abstract/map_effect/interval/sound_emitter/trigger_map_effect()
+ playsound(
+ src,
+ pick(get_sounds_to_play()),
+ sound_volume,
+ sound_frequency_variance,
+ sound_extra_range,
+ sound_fallout,
+ sound_global,
+ sound_frequency,
+ sound_is_ambience,
+ sound_ignore_walls
+ )
+ ..()
+
+/obj/abstract/map_effect/interval/sound_emitter/footsteps_wood
+ interval_lower_bound = 5 SECONDS
+ interval_upper_bound = 30 SECONDS
+
+/obj/abstract/map_effect/interval/sound_emitter/footsteps_wood/get_sounds_to_play()
+ var/static/list/sounds_to_play = list(
+ 'sound/effects/footstep/wood1.ogg',
+ 'sound/effects/footstep/wood5.ogg',
+ 'sound/effects/footstep/floor1.ogg',
+ 'sound/effects/footstep/floor5.ogg'
+ )
+ return sounds_to_play
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
deleted file mode 100644
index ac59e8322d4a..000000000000
--- a/code/game/objects/effects/mines.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-/obj/effect/mine
- name = "Mine"
- desc = "I Better stay away from that thing."
- density = TRUE
- anchored = TRUE
- layer = OBJ_LAYER
- icon = 'icons/obj/items/weapon/landmine.dmi'
- icon_state = "uglymine"
- var/triggerproc = PROC_REF(explode) // the proc that's called when the mine is triggered
- var/triggered = 0
-
-/obj/effect/mine/Initialize()
- . = ..()
- icon_state = "uglyminearmed"
-
-/obj/effect/mine/Crossed(atom/movable/AM)
- if(!isobserver(AM))
- Bumped(AM)
-
-/obj/effect/mine/Bumped(mob/M)
-
- if(triggered) return
-
- if(ishuman(M))
- visible_message(SPAN_DANGER("\The [M] triggered \the [src]!"))
- triggered = 1
- call(src,triggerproc)(M)
-
-/obj/effect/mine/proc/triggerrad(obj)
- spark_at(src, cardinal_only = TRUE)
- if(ismob(obj))
- var/mob/victim = obj
- victim.radiation += 50
- if(ismob(obj))
- var/mob/mob = obj
- mob.add_genetic_condition(pick(decls_repository.get_decls_of_type(/decl/genetic_condition/disability)))
- qdel(src)
-
-/obj/effect/mine/proc/triggerstun(obj)
- if(ismob(obj))
- var/mob/M = obj
- SET_STATUS_MAX(M, STAT_STUN, 30)
- spark_at(src, cardinal_only = TRUE)
- qdel(src)
-
-/obj/effect/mine/proc/triggern2o(obj)
- //example: n2o triggerproc
- //note: im lazy
-
- for (var/turf/target in range(1,src))
- if(target.simulated && !target.blocks_air)
- target.assume_gas(/decl/material/gas/nitrous_oxide, 30)
-
- qdel(src)
-
-/obj/effect/mine/proc/triggerflame(obj)
- for (var/turf/target in range(1,src))
- if(target.simulated && !target.blocks_air)
- target.assume_gas(/decl/material/gas/hydrogen, 30)
- target.hotspot_expose(1000, CELL_VOLUME)
-
- qdel(src)
-
-/obj/effect/mine/proc/triggerkick(obj)
- spark_at(src, cardinal_only = TRUE)
- if(ismob(obj))
- var/mob/victim = obj
- qdel(victim.client)
- qdel(src)
-
-/obj/effect/mine/proc/explode(obj)
- explosion(loc, 0, 1, 2, 3)
- qdel(src)
-
-/obj/effect/mine/dnascramble
- name = "Radiation Mine"
- icon_state = "uglymine"
- triggerproc = PROC_REF(triggerrad)
-
-/obj/effect/mine/flame
- name = "Incendiary Mine"
- icon_state = "uglymine"
- triggerproc = PROC_REF(triggerflame)
-
-/obj/effect/mine/kick
- name = "Kick Mine"
- icon_state = "uglymine"
- triggerproc = PROC_REF(triggerkick)
-
-/obj/effect/mine/n2o
- name = "N2O Mine"
- icon_state = "uglymine"
- triggerproc = PROC_REF(triggern2o)
-
-/obj/effect/mine/stun
- name = "Stun Mine"
- icon_state = "uglymine"
- triggerproc = PROC_REF(triggerstun)
diff --git a/code/game/objects/effects/mines/_mine.dm b/code/game/objects/effects/mines/_mine.dm
new file mode 100644
index 000000000000..a377a9d82869
--- /dev/null
+++ b/code/game/objects/effects/mines/_mine.dm
@@ -0,0 +1,202 @@
+/obj/item/mine
+ name = "mine"
+ desc = "A small landmine."
+ density = FALSE
+ anchored = FALSE
+ icon = 'icons/obj/mine.dmi'
+ icon_state = "mine"
+
+ var/actual_name
+ var/actual_desc
+ var/actual_icon_state
+ var/hidden_alpha = 255
+
+ var/panel_open = FALSE
+ var/armed = FALSE
+ var/triggering = FALSE
+ var/datum/mine_payload/payload = /datum/mine_payload/explosive
+
+/obj/item/mine/Initialize()
+ . = ..()
+ if(ispath(payload))
+ payload = new payload
+ register_dangerous_to_step()
+
+ // We store and hide our appearance if we're armed, to avoid people gaming mines via desc.
+ actual_name = name
+ actual_desc = desc
+ actual_icon_state = icon_state
+ update_icon()
+
+/obj/item/mine/Destroy()
+ if(istype(payload))
+ QDEL_NULL(payload)
+ unregister_dangerous_to_step()
+ return ..()
+
+/obj/item/mine/on_update_icon()
+ . = ..()
+ alpha = initial(alpha)
+ cut_overlays()
+ if(panel_open)
+ add_overlay("[icon_state]_open")
+ else if(armed)
+ add_overlay("[icon_state]_armed")
+ alpha = hidden_alpha
+ else
+ add_overlay("[icon_state]_safe")
+
+/obj/item/mine/attack_self(mob/user) // You do not want to move or throw a land mine while priming it... Explosives + Sudden Movement = Bad Times
+ if(armed)
+ to_chat(user, SPAN_WARNING("\The [src] is already armed!"))
+ return TRUE
+ add_fingerprint(user)
+ msg_admin_attack("[key_name_admin(user)] armed \the [src]")
+ user.visible_message(
+ SPAN_DANGER("\The [user] starts arming \the [src]."),
+ SPAN_DANGER("You start arming \the [src]. Hold still!")
+ )
+
+ if(user.do_skilled(10 SECONDS, SKILL_DEVICES, src))
+ playsound(src, 'sound/weapons/armbomb.ogg', 75, 1, -3)
+ prime(user)
+ else if(prob(user.skill_fail_chance(SKILL_DEVICES, 50, SKILL_ADEPT)))
+ visible_message(
+ SPAN_DANGER("\The [user] accidentally triggers \the [src]!"),
+ SPAN_DANGER("You accidentally trigger \the [src]!")
+ )
+ prime(user)
+ trigger_payload(user)
+ else
+ to_chat(user, SPAN_WARNING("You fumble with \the [src], but thankfully manage not to set it off prematurely."))
+ return TRUE
+
+// debug proc, replace with proper disarm minigame
+/obj/item/mine/proc/disarm()
+ armed = FALSE
+ triggering = FALSE
+ anchored = FALSE
+ name = actual_name
+ desc = actual_desc
+ icon_state = actual_icon_state
+ hidden_alpha = 255
+ update_icon()
+
+/obj/item/mine/attack_hand(mob/living/user)
+ if(armed)
+ trigger_payload()
+ return TRUE
+ return ..()
+
+/obj/item/mine/attackby(obj/item/W, mob/living/user)
+
+ if(IS_SCREWDRIVER(W))
+ if(W.do_tool_interaction(TOOL_SCREWDRIVER, user, src, 15 SECONDS, start_message = "carefully adjusting \the [src]'s casing", check_skill = SKILL_DEVICES))
+ panel_open = !panel_open
+ visible_message(SPAN_NOTICE("\The [user] carefully [(panel_open ? "opens" : "closes")] the casing of \the [src]."))
+ update_icon()
+ else if(armed)
+ if(prob(user.skill_fail_chance(SKILL_DEVICES, 75, SKILL_PROF)))
+ to_chat(user, SPAN_DANGER("You set off \the [src]!"))
+ trigger_payload(user)
+ else
+ to_chat(user, SPAN_WARNING("You fumble with \the [src], but thankfully manage not to set it off prematurely."))
+ return TRUE
+
+ if(armed)
+ if(panel_open && IS_WIRECUTTER(W))
+ if(W.do_tool_interaction(TOOL_WIRECUTTERS, user, src, 30 SECONDS, start_message = "painstakingly disarming \the [src]", check_skill = SKILL_DEVICES))
+ visible_message(SPAN_NOTICE("\The [user] disarms \the [src]!"))
+ disarm()
+ return TRUE
+ if(armed) // checking again in case the do_after() stacks
+ if(prob(user.skill_fail_chance(SKILL_DEVICES, 75, SKILL_PROF)))
+ to_chat(user, SPAN_DANGER("You set off \the [src]!"))
+ trigger_payload(user)
+ else
+ to_chat(user, SPAN_WARNING("You fumble with \the [src], but thankfully manage not to set it off prematurely."))
+ return TRUE
+
+ return ..()
+
+/obj/item/mine/proc/prime(mob/user)
+
+ if(armed)
+ return
+
+ if(user)
+ visible_message(SPAN_NOTICE("\The [src] beeps as the priming sequence completes."))
+ user.drop_from_inventory(src, get_turf(user))
+ add_fingerprint(user)
+
+ anchored = TRUE
+ armed = TRUE
+
+ if(istype(loc, /turf/floor) && prob(65))
+ var/turf/floor/floor = loc
+ var/decl/flooring/flooring = floor.get_topmost_flooring()
+ if(flooring.can_conceal_hazards)
+ hidden_alpha = pick(50, 90, 120)
+
+ name = "mine"
+ desc = "A small landmine."
+ icon_state = "mine"
+ update_icon()
+
+/obj/item/mine/forceMove()
+ var/turf/old_turf = get_turf(loc)
+ . = ..()
+ if(.)
+ 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/item/mine/Move()
+ var/turf/old_turf = get_turf(loc)
+ . = ..()
+ if(.)
+ 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/item/mine/proc/trigger_payload(var/mob/living/M)
+ if(!triggering && payload && armed)
+ triggering = TRUE
+ if(ismob(loc))
+ var/mob/holder = loc
+ holder.drop_from_inventory(src)
+ visible_message("\The [src] goes off!")
+ payload.trigger_payload(src, M)
+ disarm() // the mine can be reused if the payload doesn't destroy it.
+ return TRUE
+ return FALSE
+
+/obj/item/mine/bullet_act()
+ if(prob(50))
+ trigger_payload()
+ if(!QDELETED(src))
+ ..()
+
+/obj/item/mine/explosion_act(severity)
+ if(severity <= 2 || prob(50))
+ trigger_payload()
+ if(!QDELETED(src))
+ . = ..()
+
+/obj/item/mine/Crossed(atom/movable/AM)
+ . = ..()
+ if(istype(AM) && !AM.is_incorporeal())
+ Bumped(AM)
+
+/obj/item/mine/Bumped(atom/movable/AM)
+ . = ..()
+ if(armed && !QDELETED(src) && !is_safe_to_step(AM))
+ trigger_payload(AM)
+
+// This tells AI mobs to not be dumb and step on mines willingly.
+/obj/item/mine/is_safe_to_step(mob/living/stepper)
+ if(!armed)
+ return TRUE
+ return !armed || stepper.can_overcome_gravity()
diff --git a/code/game/objects/effects/mines/_mine_payload.dm b/code/game/objects/effects/mines/_mine_payload.dm
new file mode 100644
index 000000000000..99139be493d7
--- /dev/null
+++ b/code/game/objects/effects/mines/_mine_payload.dm
@@ -0,0 +1,22 @@
+/datum/mine_payload
+ var/do_sparks = TRUE
+ var/destroy_self_on_trigger = TRUE
+
+/datum/mine_payload/proc/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ if(do_sparks)
+ var/datum/effect/effect/system/spark_spread/s = new
+ s.set_up(3, 1, owner)
+ s.start()
+ if(destroy_self_on_trigger)
+ if(!QDELETED(owner))
+ QDEL_IN(owner, 1)
+ else
+ owner.disarm() // some mines can be reused
+
+/datum/mine_payload/proc/remove_from_mine()
+ return
+
+/datum/mine_payload/explosive/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ owner.visible_message("\The [owner] detonates!")
+ explosion(owner.loc, 0, 2, 3, 4) //land mines are dangerous, folks.
diff --git a/code/game/objects/effects/mines/mine_assembly.dm b/code/game/objects/effects/mines/mine_assembly.dm
new file mode 100644
index 000000000000..6a40fa0ae30d
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_assembly.dm
@@ -0,0 +1,67 @@
+/obj/item/mine/assembly
+ name = "mine assembly"
+ desc = "A small pressure-triggered device. Accepts grenades and tank transfer valves."
+ payload = null
+
+ var/static/list/accepts_items = list(
+ /obj/item/transfer_valve = /datum/mine_payload/assembly/tank_transfer_valve,
+ /obj/item/grenade = /datum/mine_payload/assembly/grenade
+ )
+
+/obj/item/mine/assembly/mapped
+ armed = TRUE
+
+/obj/item/mine/assembly/attackby(obj/item/W, mob/living/user)
+ if(!armed && !triggering)
+ var/datum/mine_payload/assembly/attached_payload = payload
+ if(attached_payload?.attached)
+ if(IS_SCREWDRIVER(W))
+ to_chat(user, "You disconnect \the [src]'s [attached_payload.attached.name] and remove it.")
+ attached_payload.attached.forceMove(get_turf(user))
+ payload.remove_from_mine()
+ QDEL_NULL(payload)
+ else
+ for(var/loadtype in accepts_items)
+ if(istype(W, loadtype))
+ user.drop_from_inventory(W)
+ W.forceMove(src)
+ var/payload_type = accepts_items[loadtype]
+ attached_payload = new payload_type
+ attached_payload.attached = W
+ payload = attached_payload
+ return TRUE
+ return ..()
+
+/datum/mine_payload/assembly
+ var/obj/item/attached
+
+/datum/mine_payload/assembly/New(var/obj/item/_attaching)
+ ..()
+ attached = _attaching
+
+/datum/mine_payload/assembly/Destroy()
+ QDEL_NULL(attached)
+ . = ..()
+
+/datum/mine_payload/assembly/remove_from_mine()
+ attached = null
+
+/datum/mine_payload/assembly/tank_transfer_valve/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(istype(attached, /obj/item/transfer_valve))
+ var/obj/item/transfer_valve/ttv = attached
+ ttv.forceMove(get_turf(owner))
+ ttv.toggle_valve()
+ remove_from_mine()
+
+/datum/mine_payload/assembly/grenade/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(istype(attached, /obj/item/grenade))
+ var/obj/item/grenade/grenade = attached
+ grenade.forceMove(get_turf(owner))
+ if(ismob(trigger))
+ var/mob/victim = trigger
+ if(victim.ckey)
+ msg_admin_attack("[key_name_admin(victim)] stepped on \a [owner], triggering [grenade]")
+ grenade.activate()
+ remove_from_mine()
diff --git a/code/game/objects/effects/mines/mine_emp.dm b/code/game/objects/effects/mines/mine_emp.dm
new file mode 100644
index 000000000000..5f8a54ad12f8
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_emp.dm
@@ -0,0 +1,12 @@
+/obj/item/mine/emp
+ name = "\improper EMP mine"
+ desc = "A small explosive mine with a lightning bolt symbol on the side."
+ payload = /datum/mine_payload/emp
+
+/obj/item/mine/emp/mapped
+ armed = TRUE
+
+/datum/mine_payload/emp/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ owner.visible_message("\The [owner] flashes violently before disintegrating!")
+ empulse(owner.loc, 2, 4, 7, 10, 1) // As strong as an EMP grenade
diff --git a/code/game/objects/effects/mines/mine_frag.dm b/code/game/objects/effects/mines/mine_frag.dm
new file mode 100644
index 000000000000..de11402ca5a2
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_frag.dm
@@ -0,0 +1,23 @@
+/obj/item/mine/frag
+ name = "fragmentation mine"
+ desc = "A small explosive mine with 'FRAG' and a grenade symbol on the side."
+ payload = /datum/mine_payload/frag
+
+/obj/item/mine/frag/mapped
+ armed = TRUE
+
+/datum/mine_payload/frag
+ var/fragment_types = list(/obj/item/projectile/bullet/pellet/fragment)
+ var/num_fragments = 20 //total number of fragments produced by the grenade
+ //The radius of the circle used to launch projectiles. Lower values mean less projectiles are used but if set too low gaps may appear in the spread pattern
+ var/spread_range = 7
+ var/explosion_size = 3
+
+/datum/mine_payload/frag/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ owner.visible_message("\The [owner] detonates!")
+ var/turf/O = get_turf(owner)
+ if(O)
+ owner.fragmentate(O, num_fragments, spread_range, fragment_types)
+ if(explosion_size)
+ explosion(O, -1, -1, round(explosion_size/2), explosion_size, FALSE)
diff --git a/code/game/objects/effects/mines/mine_incendiary.dm b/code/game/objects/effects/mines/mine_incendiary.dm
new file mode 100644
index 000000000000..f8b8a49ab2d6
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_incendiary.dm
@@ -0,0 +1,16 @@
+/obj/item/mine/incendiary
+ name = "incendiary mine"
+ desc = "A small explosive mine with a fire symbol on the side."
+ payload = /datum/mine_payload/incendiary
+
+/obj/item/mine/incendiary/mapped
+ armed = TRUE
+
+/datum/mine_payload/incendiary/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ for(var/turf/floor/target in range(1, owner))
+ if(!target.blocks_air)
+ target.assume_gas(/decl/material/gas/hydrogen, 10)
+ target.assume_gas(/decl/material/gas/oxygen, 5)
+ target.hotspot_expose(1000, CELL_VOLUME)
+ owner.visible_message("\The [owner] spews a cloud of flaming gas!")
diff --git a/code/game/objects/effects/mines/mine_kick.dm b/code/game/objects/effects/mines/mine_kick.dm
new file mode 100644
index 000000000000..ab38a2a2fb31
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_kick.dm
@@ -0,0 +1,17 @@
+/obj/item/mine/kick
+ name = "kick mine"
+ desc = "Concentrated war crimes. Handle with care."
+ payload = /datum/mine_payload/kick
+
+/obj/item/mine/kick/mapped
+ armed = TRUE
+
+/datum/mine_payload/kick/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(isexosuit(trigger))
+ var/mob/living/exosuit/mech = trigger
+ for(var/mob/pilot in mech.pilots)
+ qdel(pilot.client)
+ if(ismob(trigger))
+ var/mob/M = trigger
+ qdel(M.client)
diff --git a/code/game/objects/effects/mines/mine_napalm.dm b/code/game/objects/effects/mines/mine_napalm.dm
new file mode 100644
index 000000000000..6bceace3f0e6
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_napalm.dm
@@ -0,0 +1,15 @@
+/obj/item/mine/napalm
+ name = "napalm mine"
+ desc = "A small explosive mine with a fire symbol on the side."
+ payload = /datum/mine_payload/napalm
+
+/obj/item/mine/napalm/mapped
+ armed = TRUE
+
+/datum/mine_payload/napalm/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(isliving(trigger))
+ var/mob/living/M = trigger
+ M.adjust_fire_intensity(5)
+ M.fire_act()
+ owner.visible_message(SPAN_DANGER("\The [owner] bursts into flames!"))
diff --git a/code/game/objects/effects/mines/mine_radiation.dm b/code/game/objects/effects/mines/mine_radiation.dm
new file mode 100644
index 000000000000..08b00d2a6594
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_radiation.dm
@@ -0,0 +1,14 @@
+/obj/item/mine/radiation
+ name = "radiation mine"
+ desc = "A small explosive mine with a radiation symbol on the side."
+ payload = /datum/mine_payload/radiation
+
+/obj/item/mine/radiation/mapped
+ armed = TRUE
+
+/datum/mine_payload/radiation/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(isliving(trigger))
+ var/mob/living/victim = trigger
+ victim.apply_random_mutation(50)
+ owner.visible_message(SPAN_DANGER("\The [owner] flashes violently before disintegrating!"))
diff --git a/code/game/objects/effects/mines/mine_sleeping.dm b/code/game/objects/effects/mines/mine_sleeping.dm
new file mode 100644
index 000000000000..b6d5ad9a0dee
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_sleeping.dm
@@ -0,0 +1,14 @@
+/obj/item/mine/sleeping
+ name = "nitrous oxide mine"
+ desc = "A small explosive mine with three Z's on the side."
+ payload = /datum/mine_payload/sleeping
+
+/obj/item/mine/sleeping/mapped
+ armed = TRUE
+
+/datum/mine_payload/sleeping/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ for (var/turf/floor/target in range(1, owner))
+ if(!target.blocks_air)
+ target.assume_gas(/decl/material/gas/nitrous_oxide, 30)
+ owner.visible_message("\The [owner] sprays a cloud of gas!")
diff --git a/code/game/objects/effects/mines/mine_stun.dm b/code/game/objects/effects/mines/mine_stun.dm
new file mode 100644
index 000000000000..26b5704b304b
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_stun.dm
@@ -0,0 +1,14 @@
+/obj/item/mine/stun
+ name = "stun mine"
+ desc = "A small explosive mine with a lightning bolt symbol on the side."
+ payload = /datum/mine_payload/stun
+
+/obj/item/mine/stun/mapped
+ armed = TRUE
+
+/datum/mine_payload/stun/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ if(ismob(trigger))
+ var/mob/M = trigger
+ SET_STATUS_MAX(M, STAT_STUN, 30)
+ owner.visible_message("\The [owner] flashes violently before disintegrating!")
diff --git a/code/game/objects/effects/mines/mine_training.dm b/code/game/objects/effects/mines/mine_training.dm
new file mode 100644
index 000000000000..13b23f31945c
--- /dev/null
+++ b/code/game/objects/effects/mines/mine_training.dm
@@ -0,0 +1,15 @@
+/obj/item/mine/training
+ name = "training mine"
+ desc = "A mine with its payload removed, for EOD training and demonstrations."
+ payload = /datum/mine_payload/training
+
+/obj/item/mine/training/mapped
+ armed = TRUE
+
+/datum/mine_payload/training
+ do_sparks = FALSE
+ destroy_self_on_trigger = FALSE
+
+/datum/mine_payload/training/trigger_payload(var/obj/item/mine/owner, var/atom/trigger)
+ ..()
+ owner.visible_message("\The [owner]'s light flashes rapidly as it 'explodes'.")
diff --git a/code/game/objects/effects/temporary.dm b/code/game/objects/effects/temporary.dm
index b56d38275f17..c5efa1c7e916 100644
--- a/code/game/objects/effects/temporary.dm
+++ b/code/game/objects/effects/temporary.dm
@@ -1,6 +1,7 @@
//temporary visual effects
/obj/effect/temp_visual
icon_state = "nothing"
+ icon = 'icons/effects/effects.dmi'
anchored = TRUE
layer = ABOVE_HUMAN_LAYER
mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
@@ -14,12 +15,10 @@
QDEL_IN(src, duration)
/obj/effect/temp_visual/emp_burst
- icon = 'icons/effects/effects.dmi'
icon_state = "empdisable"
/obj/effect/temp_visual/emppulse
name = "electromagnetic pulse"
- icon = 'icons/effects/effects.dmi'
icon_state = "emppulse"
duration = 2 SECONDS
@@ -49,4 +48,52 @@
target_pixel_x = 16
if(set_dir & WEST)
target_pixel_x = -16
- animate(src, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = duration)
\ No newline at end of file
+ animate(src, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = duration)
+
+/obj/effect/temp_visual/impact_effect
+ plane = ABOVE_LIGHTING_PLANE
+ layer = ABOVE_LIGHTING_LAYER // So they're visible even in a shootout in maint.
+ duration = 5
+ icon_state = "impact_bullet"
+ icon = 'icons/effects/impact_effects.dmi'
+
+/obj/effect/temp_visual/impact_effect/Initialize(mapload, obj/item/projectile/P, _x, _y)
+ default_pixel_x = _x
+ default_pixel_y = _y
+ pixel_x = default_pixel_x
+ pixel_y = default_pixel_y
+ . = ..()
+
+/obj/effect/temp_visual/impact_effect/red_laser
+ icon_state = "impact_laser"
+ duration = 4
+
+/obj/effect/temp_visual/impact_effect/blue_laser
+ icon_state = "impact_laser_blue"
+ duration = 4
+
+/obj/effect/temp_visual/impact_effect/green_laser
+ icon_state = "impact_laser_green"
+ duration = 4
+
+/obj/effect/temp_visual/impact_effect/purple_laser
+ icon_state = "impact_laser_purple"
+ duration = 4
+
+// Colors itself based on the projectile.
+// Checks light_color and color.
+/obj/effect/temp_visual/impact_effect/monochrome_laser
+ icon_state = "impact_laser_monochrome"
+ duration = 4
+
+/obj/effect/temp_visual/impact_effect/monochrome_laser/Initialize(mapload, obj/item/projectile/P, x, y)
+ if(istype(P))
+ if(P.light_color)
+ color = P.light_color
+ else if(P.color)
+ color = P.color
+ return ..()
+
+/obj/effect/temp_visual/impact_effect/ion
+ icon_state = "shieldsparkles"
+ duration = 6
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index fe665eacab52..2b33e1440cb0 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -1,6 +1,7 @@
var/global/list/all_gps_units = list()
/obj/item/gps
- name = "global coordinate system"
+ name = "global positioning system"
+ base_name = "global positioning system"
desc = "A handheld relay used to triangulate the approximate coordinates of the device in spacetime."
icon = 'icons/obj/items/device/locator.dmi'
icon_state = ICON_STATE_WORLD
@@ -24,21 +25,36 @@ var/global/list/all_gps_units = list()
var/can_hide_signal = FALSE // If it can toggle the above var.
var/is_special_gps_marker = FALSE // How the GPS marker should be handled.
+ var/tag_category // Any special category for this tracker to sit in (used by xenofauna tags)
+ var/list/tag_categories // Any special categories this tracker should show (used in xenofauna GPS)
+
var/mob/holder
var/is_in_processing_list = FALSE
var/list/tracking_devices
var/list/showing_tracked_names
- var/obj/compass_holder/compass
+ VAR_PRIVATE/obj/compass_holder/_compass
var/list/decals
+
/obj/item/gps/Initialize()
global.all_gps_units += src
. = ..()
- name = "[initial(name)] ([gps_tag])"
events_repository.register(/decl/observ/moved, src, src, PROC_REF(update_holder))
- compass = new(src)
+ create_compass()
update_holder()
update_icon()
+ update_name()
+
+/obj/item/gps/proc/set_gps_tag(_tag)
+ _tag = sanitize(_tag)
+ if(istext(_tag) && length(_tag) > 0 && gps_tag != _tag)
+ gps_tag = _tag
+ update_name()
+
+/obj/item/gps/update_name()
+ . = ..()
+ if(gps_tag)
+ SetName("[base_name] ([gps_tag])")
/obj/item/gps/get_examine_strings(mob/user, distance, infix, suffix)
. = ..()
@@ -60,7 +76,8 @@ var/global/list/all_gps_units = list()
if(holder && (force_clear || loc != holder))
moved_event.unregister(holder, src)
dir_set_event.unregister(holder, src)
- holder.client?.screen -= compass
+ if(_compass)
+ holder.client?.screen -= _compass
holder = null
if(!force_clear && ismob(loc))
@@ -72,16 +89,16 @@ var/global/list/all_gps_units = list()
if(!is_in_processing_list)
START_PROCESSING(SSobj, src)
is_in_processing_list = TRUE
- if(holder.client)
+ if(holder.client && _compass)
if(check_visible_to_holder())
- holder.client.screen |= compass
+ holder.client.screen |= _compass
else
- holder.client.screen -= compass
+ holder.client.screen -= _compass
else
STOP_PROCESSING(SSobj, src)
is_in_processing_list = FALSE
- if(holder?.client)
- holder.client.screen -= compass
+ if(holder?.client && _compass)
+ holder.client.screen -= _compass
/obj/item/gps/equipped_robot()
. = ..()
@@ -105,13 +122,12 @@ var/global/list/all_gps_units = list()
global.all_gps_units -= src
events_repository.unregister(/decl/observ/moved, src, src, PROC_REF(update_holder))
update_holder(force_clear = TRUE)
- QDEL_NULL(compass)
+ QDEL_NULL(_compass)
return ..()
/obj/item/gps/proc/can_track(var/obj/item/gps/other, var/reachable_z_levels)
- if(!other.tracking || other.emped || other.hide_signal)
+ if(!other.tracking || other.emped || other.hide_signal || (other.tag_category && !(other.tag_category in tag_categories)))
return FALSE
-
var/turf/origin = get_turf(src)
var/turf/target = get_turf(other)
if(!istype(origin) || !istype(target))
@@ -131,9 +147,12 @@ var/global/list/all_gps_units = list()
LAZYDISTINCTADD(reachable_z_levels, adding_sites)
return (target.z in reachable_z_levels)
+/obj/item/gps/proc/create_compass()
+ _compass ||= new(src)
+
/obj/item/gps/proc/update_compass(var/update_compass_icon)
- compass.hide_waypoints(FALSE)
+ _compass?.hide_waypoints(FALSE)
var/turf/my_turf = get_turf(src)
for(var/thing in tracking_devices)
@@ -143,14 +162,15 @@ var/global/list/all_gps_units = list()
LAZYREMOVE(showing_tracked_names, thing)
continue
- var/turf/gps_turf = get_turf(gps)
- var/gps_tag = LAZYACCESS(showing_tracked_names, thing) ? gps.gps_tag : null
- if(istype(gps_turf))
- compass.set_waypoint("\ref[gps]", gps_tag, gps_turf.x, gps_turf.y, gps_turf.z, LAZYACCESS(tracking_devices, "\ref[gps]"))
- if(can_track(gps) && my_turf && gps_turf != my_turf)
- compass.show_waypoint("\ref[gps]")
+ if(_compass)
+ var/turf/gps_turf = get_turf(gps)
+ var/use_gps_tag = LAZYACCESS(showing_tracked_names, thing) ? gps.gps_tag : null
+ if(istype(gps_turf))
+ _compass.set_waypoint("\ref[gps]", use_gps_tag, gps_turf.x, gps_turf.y, gps_turf.z, LAZYACCESS(tracking_devices, "\ref[gps]"))
+ if(can_track(gps) && my_turf && gps_turf != my_turf)
+ _compass.show_waypoint("\ref[gps]")
- compass.rebuild_overlay_lists(update_compass_icon)
+ _compass?.rebuild_overlay_lists(update_compass_icon)
/obj/item/gps/proc/toggle_tracking(var/mob/user, var/silent)
@@ -321,7 +341,7 @@ var/global/list/all_gps_units = list()
if(href_list["stop_track"])
var/gps_ref = href_list["stop_track"]
var/obj/item/gps/gps = locate(gps_ref)
- compass.clear_waypoint(gps_ref)
+ _compass?.clear_waypoint(gps_ref)
LAZYREMOVE(tracking_devices, gps_ref)
LAZYREMOVE(showing_tracked_names, gps_ref)
if(istype(gps) && !QDELETED(gps))
@@ -353,8 +373,7 @@ var/global/list/all_gps_units = list()
var/a = input("Please enter desired tag.", name, gps_tag) as text
a = uppertext(copytext(sanitize(a), 1, 11))
if(CanInteract(user, topic_state))
- gps_tag = a
- name = "[initial(name)] ([gps_tag])"
+ set_gps_tag(a)
to_chat(user, SPAN_NOTICE("You set your GPS's tag to '[gps_tag]'."))
. = TOPIC_REFRESH
@@ -398,6 +417,7 @@ var/global/list/all_gps_units = list()
// Department subtypes.
/obj/item/gps/mining
+ gps_tag = "MIN0"
color = "#c08f45"
decals = list(
"stripe-outside" = "#702e98",
@@ -405,6 +425,7 @@ var/global/list/all_gps_units = list()
)
/obj/item/gps/science
+ gps_tag = "SCI0"
color = "#dbcfdf"
decals = list(
"stripe-outside" = "#cc33ff",
@@ -412,17 +433,25 @@ var/global/list/all_gps_units = list()
)
/obj/item/gps/medical
+ gps_tag = "MED0"
color = "#ebebeb"
decals = list(
"stripe-outside" = "#6ab8fe",
"stripe-inside" = "#339efe"
)
-/obj/item/gps/explorer
- color = "#4a4a4a"
+/obj/item/gps/security
+ gps_tag = "SEC0"
+ color = "#5c0000"
decals = list(
- "stripe-outside" = "#500677",
- "stripe-inside" = "#68099e"
+ "stripe-outside" = "#ff0000",
+ "stripe-inside" = "#800000"
+ )
+
+/obj/item/gps/security/hos
+ decals = list(
+ "stripe-outside" = "#ffae00",
+ "stripe-inside" = "#9e7900"
)
/obj/item/gps/xenofauna
diff --git a/code/game/objects/items/devices/radio/headsets_shared.dm b/code/game/objects/items/devices/radio/headsets_shared.dm
index ed6bfeb87d4f..475a5e515909 100644
--- a/code/game/objects/items/devices/radio/headsets_shared.dm
+++ b/code/game/objects/items/devices/radio/headsets_shared.dm
@@ -140,8 +140,7 @@
access_research,
access_medical,
access_cargo,
- access_bar,
- access_explorer
+ access_bar
)
/obj/item/radio/headset/heads/hop
@@ -156,8 +155,7 @@
access_cargo,
access_bridge,
access_security,
- access_mining,
- access_explorer
+ access_mining
)
/obj/item/radio/headset/heads/hos
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index cbe87ec2d299..f2a7ee2d6d44 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -8,14 +8,14 @@
/obj/item/borg/overdrive
name = "overdrive"
icon = 'icons/obj/signs/warnings.dmi'
- icon_state = "shock"
+ icon_state = "shock-large"
/**********************************************************************
HUD/SIGHT things
***********************************************************************/
/obj/item/borg/sight
icon = 'icons/obj/signs/warnings.dmi'
- icon_state = "secureareaold"
+ icon_state = "securearea-large"
var/sight_mode = null
var/glasses_hud_type
diff --git a/code/game/objects/items/weapons/material/knives.dm b/code/game/objects/items/weapons/material/knives.dm
index fc19d7e17dd6..20fec64b1b05 100644
--- a/code/game/objects/items/weapons/material/knives.dm
+++ b/code/game/objects/items/weapons/material/knives.dm
@@ -129,4 +129,4 @@
name = "lightweight utility knife"
desc = "A lightweight utility knife made out of a titanium alloy."
material = /decl/material/solid/metal/titanium
- draw_handle = FALSE
\ No newline at end of file
+ draw_handle = FALSE
diff --git a/code/game/objects/items/weapons/material/swiss.dm b/code/game/objects/items/weapons/material/swiss.dm
index 33db97ea49cd..b91369cf2356 100644
--- a/code/game/objects/items/weapons/material/swiss.dm
+++ b/code/game/objects/items/weapons/material/swiss.dm
@@ -1,14 +1,3 @@
-#define SWISSKNF_CLOSED "Close"
-#define SWISSKNF_LBLADE "Large Blade"
-#define SWISSKNF_SBLADE "Small Blade"
-#define SWISSKNF_CLIFTER "Cap Lifter-Screwdriver"
-#define SWISSKNF_COPENER "Can Opener-Screwdriver"
-#define SWISSKNF_CSCREW "Corkscrew"
-#define SWISSKNF_GBLADE "Glass Cutter"
-#define SWISSKNF_WCUTTER "Wirecutters"
-#define SWISSKNF_WBLADE "Wood Saw"
-#define SWISSKNF_CROWBAR "Pry Bar"
-
/obj/item/knife/folding/swiss
name = "combi-knife"
desc = "A small, colourable, multi-purpose folding knife."
@@ -17,6 +6,17 @@
material = /decl/material/solid/metal/steel
material_alteration = MAT_FLAG_ALTERATION_COLOR | MAT_FLAG_ALTERATION_NAME
+ var/const/SWISSKNF_CLOSED = "Close"
+ var/const/SWISSKNF_LBLADE = "Large Blade"
+ var/const/SWISSKNF_SBLADE = "Small Blade"
+ var/const/SWISSKNF_CLIFTER = "Cap Lifter-Screwdriver"
+ var/const/SWISSKNF_COPENER = "Can Opener-Screwdriver"
+ var/const/SWISSKNF_CSCREW = "Corkscrew"
+ var/const/SWISSKNF_GBLADE = "Glass Cutter"
+ var/const/SWISSKNF_WCUTTER = "Wirecutters"
+ var/const/SWISSKNF_WBLADE = "Wood Saw"
+ var/const/SWISSKNF_CROWBAR = "Pry Bar"
+
var/active_tool = SWISSKNF_CLOSED
var/list/tools = list(SWISSKNF_LBLADE, SWISSKNF_CLIFTER, SWISSKNF_COPENER)
var/static/list/sharp_tools = list(SWISSKNF_LBLADE, SWISSKNF_SBLADE, SWISSKNF_GBLADE, SWISSKNF_WBLADE)
@@ -151,25 +151,8 @@
handle_color = COLOR_AMBER
tools = list(SWISSKNF_LBLADE, SWISSKNF_SBLADE, SWISSKNF_CLIFTER, SWISSKNF_COPENER, SWISSKNF_WBLADE, SWISSKNF_WCUTTER)
-/obj/item/knife/folding/swiss/explorer
- name = "explorer's combi-knife"
- desc = "A small, purple, multi-purpose folding knife. This one adds a wood saw and prybar."
- handle_color = COLOR_PURPLE
- tools = list(SWISSKNF_LBLADE, SWISSKNF_SBLADE, SWISSKNF_CLIFTER, SWISSKNF_COPENER, SWISSKNF_WBLADE, SWISSKNF_CROWBAR)
-
/obj/item/knife/folding/swiss/loot
name = "black combi-knife"
desc = "A small, silver, multi-purpose folding knife. This one adds a small blade and corkscrew."
handle_color = COLOR_GRAY40
tools = list(SWISSKNF_LBLADE, SWISSKNF_SBLADE, SWISSKNF_CLIFTER, SWISSKNF_COPENER, SWISSKNF_CSCREW)
-
-#undef SWISSKNF_CLOSED
-#undef SWISSKNF_LBLADE
-#undef SWISSKNF_SBLADE
-#undef SWISSKNF_CLIFTER
-#undef SWISSKNF_COPENER
-#undef SWISSKNF_CSCREW
-#undef SWISSKNF_GBLADE
-#undef SWISSKNF_WCUTTER
-#undef SWISSKNF_WBLADE
-#undef SWISSKNF_CROWBAR
diff --git a/code/game/objects/random/subtypes/mobs.dm b/code/game/objects/random/subtypes/mobs.dm
index 03e86f8f5f4c..d26977a5bf19 100644
--- a/code/game/objects/random/subtypes/mobs.dm
+++ b/code/game/objects/random/subtypes/mobs.dm
@@ -49,3 +49,26 @@
/mob/living/simple_animal/hostile/scarybat/cave = 4
)
return spawnable_choices
+
+/obj/random/hostile/hivebot
+ name = "Random Hivebot"
+ icon = /mob/living/simple_animal/hostile/hivebot::icon
+ icon_state = /mob/living/simple_animal/hostile/hivebot::icon_state
+
+/obj/random/hostile/hivebot/spawn_choices()
+ var/static/list/spawnable_choices = typesof(/mob/living/simple_animal/hostile/hivebot)
+ return spawnable_choices
+
+/obj/random/hostile/hivebot/melee
+ name = "Random Melee Hivebot"
+
+/obj/random/hostile/hivebot/melee/spawn_choices()
+ var/static/list/spawnable_choices = typesof(/mob/living/simple_animal/hostile/hivebot/melee)
+ return spawnable_choices
+
+/obj/random/hostile/hivebot/ranged
+ name = "Random Ranged Hivebot"
+
+/obj/random/hostile/hivebot/ranged/spawn_choices()
+ var/static/list/spawnable_choices = typesof(/mob/living/simple_animal/hostile/hivebot/ranged)
+ return spawnable_choices
diff --git a/code/game/objects/random/subtypes/salvage.dm b/code/game/objects/random/subtypes/salvage.dm
new file mode 100644
index 000000000000..298557addcac
--- /dev/null
+++ b/code/game/objects/random/subtypes/salvage.dm
@@ -0,0 +1,128 @@
+/obj/random/scrapped_gun
+ name = "random scrapped gun"
+ icon = /obj/item/gun/projectile/automatic/assault_rifle::icon
+ icon_state = /obj/item/gun/projectile/automatic/assault_rifle::icon_state
+
+/obj/random/scrapped_gun/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/random/scrapped_pistol = 10,
+ /obj/random/scrapped_smg = 5,
+ /obj/random/scrapped_laser = 5,
+ /obj/random/scrapped_shotgun = 3,
+ /obj/random/scrapped_ionrifle = 3,
+ /obj/random/scrapped_assault = 1,
+ /obj/random/scrapped_flechette = 1,
+ /obj/random/scrapped_grenadelauncher = 1,
+ /obj/random/scrapped_dartgun = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_assault
+ name = "random scrapped assault rifle"
+ icon = /obj/item/gun/projectile/automatic/assault_rifle::icon
+ icon_state = /obj/item/gun/projectile/automatic/assault_rifle::icon_state
+
+/obj/random/scrapped_assault/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/ballistic/assault = 10,
+ /obj/item/gun/projectile/automatic/assault_rifle = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_pistol
+ name = "random scrapped pistol"
+ icon = /obj/item/gun/projectile/pistol::icon
+ icon_state = /obj/item/gun/projectile/pistol::icon_state
+
+/obj/random/scrapped_pistol/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/ballistic/pistol = 10,
+ /obj/item/gun/projectile/pistol = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_ionrifle
+ name = "random scrapped ion rifle"
+ icon = /obj/item/gun/energy/ionrifle::icon
+ icon_state = /obj/item/gun/energy/ionrifle::icon_state
+
+/obj/random/scrapped_ionrifle/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/energy/ionrifle = 10,
+ /obj/item/gun/energy/ionrifle = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_grenadelauncher
+ name = "random scrapped grenade launcher"
+ icon = /obj/item/gun/launcher/grenade::icon
+ icon_state = /obj/item/gun/launcher/grenade::icon_state
+
+/obj/random/scrapped_grenadelauncher/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/launcher/grenade = 10,
+ /obj/item/gun/launcher/grenade = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_laser
+ name = "random scrapped laser rifle"
+ icon = /obj/item/gun/energy/laser::icon
+ icon_state = /obj/item/gun/energy/laser::icon_state
+
+/obj/random/scrapped_laser/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/energy/laserrifle = 10,
+ /obj/item/gun/energy/laser = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_smg
+ name = "random scrapped submachine gun"
+ icon = /obj/item/gun/projectile/automatic/smg::icon
+ icon_state = /obj/item/gun/projectile/automatic/smg::icon_state
+
+/obj/random/scrapped_smg/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/ballistic/smg = 10,
+ /obj/item/gun/projectile/automatic/smg = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_shotgun
+ name = "random scrapped shotgun"
+ icon = /obj/item/gun/projectile/shotgun/pump::icon
+ icon_state = /obj/item/gun/projectile/shotgun/pump::icon_state
+
+/obj/random/scrapped_shotgun/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/ballistic/shotgun_pump = 10,
+ /obj/item/salvage/ballistic/shotgun_doublebarrel = 10,
+ /obj/item/gun/projectile/shotgun/pump = 1,
+ /obj/item/gun/projectile/shotgun/doublebarrel = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_dartgun
+ name = "random scrapped dartgun"
+ icon = /obj/item/gun/projectile/dartgun::icon
+ icon_state = /obj/item/gun/projectile/dartgun::icon_state
+
+/obj/random/scrapped_dartgun/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/launcher/dartgun = 10,
+ /obj/item/gun/projectile/dartgun = 1
+ )
+ return spawn_choices
+
+/obj/random/scrapped_flechette
+ name = "random scrapped flechette rifle"
+ icon = /obj/item/gun/magnetic/railgun/flechette::icon
+ icon_state = /obj/item/gun/magnetic/railgun/flechette::icon_state
+
+/obj/random/scrapped_flechette/spawn_choices()
+ var/static/list/spawn_choices = list(
+ /obj/item/salvage/magnetic/flechette = 10,
+ /obj/item/gun/magnetic/railgun/flechette = 1
+ )
+ return spawn_choices
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index 3f5b94ea0716..8598d9db8d55 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -60,7 +60,6 @@
/obj/structure/catwalk/can_climb_from_below(var/mob/climber)
return TRUE
-
/obj/structure/catwalk/proc/redraw_nearby_catwalks()
for(var/direction in global.alldirs)
var/obj/structure/catwalk/L = locate() in get_step(src, direction)
diff --git a/code/game/objects/structures/cliffs.dm b/code/game/objects/structures/cliffs.dm
new file mode 100644
index 000000000000..5745913052ab
--- /dev/null
+++ b/code/game/objects/structures/cliffs.dm
@@ -0,0 +1,300 @@
+/*
+Cliffs give a visual illusion of depth by separating two places while presenting a 'top' and 'bottom' side.
+
+Ported from Polaris.
+
+Mobs moving into a cliff from the bottom side will simply bump into it and be denied moving into the tile,
+where as mobs moving into a cliff from the top side will 'fall' off the cliff, forcing them to the bottom, causing significant damage and stunning them.
+
+Mobs can climb this while wearing climbing equipment by clickdragging themselves onto a cliff, as if it were a table.
+
+Flying mobs can pass over all cliffs with no risk of falling.
+
+Projectiles and thrown objects can pass, however if moving upwards, there is a chance for it to be stopped by the cliff.
+This makes fighting something that is on top of a cliff more challenging.
+
+As a note, dir points upwards, e.g. pointing WEST means the left side is 'up', and the right side is 'down'.
+
+When mapping these in, be sure to give at least a one tile clearance, as NORTH facing cliffs expand to
+two tiles on initialization, and which way a cliff is facing may change during maploading.
+*/
+
+/obj/structure/cliff
+ name = "cliff"
+ desc = "A steep rock ledge. You might be able to climb it if you feel bold enough."
+ icon = 'icons/obj/structures/cliffs.dmi'
+ anchored = TRUE
+ density = TRUE
+ opacity = FALSE
+ atom_flags = ATOM_FLAG_CLIMBABLE
+ appearance_flags = KEEP_TOGETHER
+ climb_speed_mult = 2
+
+ var/icon_variant = null // Used to make cliffs less repetitive by having a selection of sprites to display.
+ var/corner = FALSE // Used for icon things.
+ var/ramp = FALSE // Ditto.
+ var/bottom = FALSE // Used for 'bottom' typed cliffs, to avoid infinite cliffs, and for icons.
+
+ var/is_double_cliff = FALSE // Set to true when making the two-tile cliffs, used for projectile checks.
+ var/uphill_penalty = 30 // Odds of a projectile not making it up the cliff.
+
+/obj/structure/cliff/Initialize()
+ . = ..()
+ register_dangerous_to_step()
+
+/obj/structure/cliff/Destroy()
+ unregister_dangerous_to_step()
+ if(is_double_cliff && !bottom)
+ var/turf/other = get_step(src, SOUTH)
+ if(istype(other))
+ for(var/obj/structure/cliff/bottom/bottom in other)
+ qdel(bottom)
+ . = ..()
+
+/obj/structure/cliff/get_examine_hints(mob/user, distance, infix, suffix)
+ . = ..()
+ var/static/desc_string = "Walking off the edge of a cliff while on top will cause you to fall off, causing severe injury.
\
+ You can climb this cliff if wearing special climbing equipment, by click-dragging yourself onto the cliff.
\
+ Projectiles traveling up a cliff may hit the cliff instead, making it more difficult to fight something \
+ on top."
+ LAZYADD(., desc_string)
+
+/obj/structure/cliff/Move()
+ var/turf/old_turf = get_turf(src)
+ . = ..()
+ if(.)
+ var/turf/new_turf = get_turf(src)
+ if(old_turf != new_turf)
+ old_turf.unregister_dangerous_object(src)
+ new_turf.register_dangerous_object(src)
+
+// These arrange their sprites at runtime, as opposed to being statically placed in the map file.
+/obj/structure/cliff/automatic
+ icon_state = "cliffbuilder"
+ dir = NORTH
+
+/obj/structure/cliff/automatic/corner
+ icon_state = "cliffbuilder-corner"
+ dir = NORTHEAST
+ corner = TRUE
+
+// Tiny part that doesn't block, used for making 'ramps'.
+/obj/structure/cliff/automatic/ramp
+ icon_state = "cliffbuilder-ramp"
+ dir = NORTHEAST
+ density = FALSE
+ ramp = TRUE
+
+// Made automatically as needed by automatic cliffs.
+/obj/structure/cliff/bottom
+ bottom = TRUE
+ is_spawnable_type = FALSE
+
+/obj/structure/cliff/automatic/Initialize()
+ ..()
+ return INITIALIZE_HINT_LATELOAD
+
+// Paranoid about the maploader, direction is very important to cliffs, since they may get bigger if initialized while facing NORTH.
+/obj/structure/cliff/automatic/LateInitialize()
+ if(dir in global.cardinal)
+ icon_variant = pick("a", "b", "c")
+
+ if(dir & NORTH && !bottom) // North-facing cliffs require more cliffs to be made.
+ make_bottom()
+
+ update_icon()
+
+/obj/structure/cliff/proc/make_bottom()
+ // First, make sure there's room to put the bottom side.
+ var/turf/turf = locate(x, y - 1, z)
+ if(!istype(turf))
+ return FALSE
+
+ // Now make the bottom cliff have mostly the same variables.
+ var/obj/structure/cliff/bottom/bottom_cliff = new(turf)
+ is_double_cliff = TRUE
+ climb_speed_mult /= 2 // Since there are two cliffs to climb when going north, both take half the time.
+
+ bottom_cliff.dir = dir
+ bottom_cliff.is_double_cliff = TRUE
+ bottom_cliff.climb_speed_mult = climb_speed_mult
+ bottom_cliff.icon_variant = icon_variant
+ bottom_cliff.corner = corner
+ bottom_cliff.ramp = ramp
+ bottom_cliff.layer = layer - 0.1
+ bottom_cliff.density = density
+ bottom_cliff.update_icon()
+
+/obj/structure/cliff/set_dir(new_dir)
+ ..()
+ update_icon()
+
+/obj/structure/cliff/on_update_icon()
+ icon_state = "cliff-[dir][icon_variant][bottom ? "-bottom" : ""][corner ? "-corner" : ""][ramp ? "-ramp" : ""]"
+
+ // Now for making the top-side look like a different turf.
+ var/turf/turf = get_step(src, dir)
+ if(!istype(turf))
+ return
+
+ underlays.Cut()
+ var/subtraction_icon_state = "[icon_state]-subtract"
+ if(turf && (check_state_in_icon(subtraction_icon_state, icon)))
+ var/image/subtract = image(icon, subtraction_icon_state)
+ subtract.blend_mode = BLEND_SUBTRACT
+ underlays += subtract
+
+// Movement-related code.
+/obj/structure/cliff/CanPass(atom/movable/mover, turf/target)
+ if(isliving(mover))
+ var/mob/living/faller = mover
+ if(faller.can_overcome_gravity()) // Flying mobs can always pass.
+ return TRUE
+ return ..()
+
+ else if(!istype(mover, /obj/item/projectile) && !mover.throwing) // 'sliding' objects can fall / bump into cliffs.
+ return ..()
+
+ // Projectiles and objects flying 'upward' have a chance to hit the cliff instead, wasting the shot.
+ else if(istype(mover, /obj))
+ var/obj/O = mover
+ if(check_shield_arc(src, dir, O)) // This is actually for mobs but it will work for our purposes as well.
+ if(prob(uphill_penalty / (1 + is_double_cliff) )) // Firing upwards facing NORTH means it will likely have to pass through two cliffs, so the chance is halved.
+ return FALSE
+ return TRUE
+
+/obj/structure/cliff/Bumped(atom/movable/mover)
+ if(!istype(mover, /obj/item/projectile) && !mover.throwing && should_fall(mover))
+ fall_off_cliff(mover)
+ return
+ ..()
+
+/obj/structure/cliff/proc/should_fall(atom/movable/mover)
+ if(isliving(mover))
+ var/mob/living/faller = mover
+ if(faller.can_overcome_gravity())
+ return FALSE
+ var/turf/turf = get_turf(mover)
+ if(turf && get_dir(turf, loc) & global.reverse_dir[dir]) // dir points 'up' the cliff, e.g. cliff pointing NORTH will cause someone to fall if moving SOUTH into it.
+ return TRUE
+ return FALSE
+
+/obj/structure/cliff/proc/fall_off_cliff(atom/movable/mover)
+ . = FALSE
+
+ var/mob/living/faller
+ if(isliving(mover))
+ faller = mover
+ var/turf/turf = get_step(src, global.reverse_dir[dir])
+ var/displaced = FALSE
+
+ if(dir in list(EAST, WEST)) // Apply an offset if flying sideways, to help maintain the illusion of depth.
+ for(var/i = 1 to 2)
+ var/turf/new_T = locate(turf.x, turf.y - i, turf.z)
+ if(!new_T || locate(/obj/structure/cliff) in new_T)
+ break
+ turf = new_T
+ displaced = TRUE
+
+ if(!istype(turf))
+ return
+
+ var/safe_fall = FALSE
+ if(istype(faller))
+ safe_fall = faller.can_overcome_gravity()
+ else if(istype(mover, /obj/vehicle/bike))
+ var/obj/vehicle/bike/Bi = mover
+ if(Bi.on)
+ safe_fall = TRUE
+
+ // Buckled people can't react to save themselves, if they're not on a vehicle.
+ if(!istype(mover, /obj/vehicle) && !isexosuit(mover) && !faller && mover.buckled_mob)
+ faller = mover.buckled_mob
+
+ if(safe_fall)
+ visible_message(SPAN_NOTICE("\The [mover] glides down from \the [src]."))
+ else
+ visible_message(SPAN_DANGER("\The [mover] falls off \the [src]!"))
+
+ mover.forceMove(turf)
+
+ var/harm = !is_double_cliff ? 1 : 0.5
+ if(!safe_fall)
+ // Do the actual hurting. Double cliffs do halved damage due to them most likely hitting twice.
+ if(faller)
+ SET_STATUS_MAX(faller, STAT_WEAK, (5 * harm))
+
+ if(istype(mover, /obj/vehicle))
+ var/obj/vehicle/vehicle = mover
+ vehicle.take_damage(40 * harm)
+ vehicle.visible_message(SPAN_WARNING("\The [vehicle] absorbs some of the impact, damaging it."))
+ harm = round(harm * 0.5)
+ if(vehicle.buckled_mob)
+ var/damage = clamp(vehicle.buckled_mob.get_max_health() * 0.4, 20, 100)
+ vehicle.buckled_mob.take_damage(damage * harm, BRUTE, inflicter = src)
+ shake_camera(vehicle.buckled_mob, 1, 1)
+ else if(isexosuit(mover))
+ var/mob/living/exosuit/Mech = mover
+ harm = round(harm * 0.5)
+ var/list/passengers = list()
+ for(var/mob/living/passenger in Mech.pilots)
+ passengers |= passenger
+ passenger.take_damage(clamp(faller.get_max_health() * 0.4, 10, 50) * harm, BRUTE, inflicter = src)
+ shake_camera(passenger, 1, 1)
+ to_chat(passenger, SPAN_DANGER("\The [Mech] shakes, bouncing you violently!"))
+ Mech.take_damage(clamp(Mech.get_max_health() * 0.4 * harm, 50, 300))
+ if(QDELETED(Mech) && length(passengers)) // Damage caused the mech to explode, or otherwise vanish.
+ for(var/mob/living/victim in passengers)
+ to_chat(victim, SPAN_DANGER("The exosuit shears apart around you, throwing you from the debris!"))
+ victim.throw_at_random(FALSE,2,1, 32)
+
+ playsound(mover, 'sound/effects/break_stone.ogg', 70, 1)
+
+ var/fall_time = 3
+ if(displaced) // Make the fall look more natural when falling sideways.
+ mover.pixel_z = 32 * 2
+ animate(mover, pixel_z = 0, time = fall_time)
+
+ sleep(fall_time) // A brief delay inbetween the two sounds helps sell the 'ouch' effect.
+
+ if(QDELETED(src) || QDELETED(mover) || QDELETED(turf))
+ return
+
+ if(safe_fall)
+ visible_message(SPAN_NOTICE("\The [mover] lands on \the [turf]."))
+ playsound(mover, "rustle", 25, 1)
+ return
+
+ playsound(mover, "punch", 70, 1)
+
+ visible_message(SPAN_DANGER("\The [mover] hits \the [turf]!"))
+
+ if(faller)
+ // The bigger they are, the harder they fall.
+ // They will take at least 20 damage at the minimum, and tries to scale up to 40% of their max health.
+ // This scaling is capped at 100 total damage, which occurs if the thing that fell has more than 250 health.
+ faller.take_damage(clamp(faller.get_max_health() * 0.4, 20, 100) * harm, BRUTE, ran_zone(), inflicter = src)
+ shake_camera(faller, 1, 1)
+
+ // Now fall off more cliffs below this one if they exist.
+ var/obj/structure/cliff/bottom_cliff = locate() in turf
+ if(bottom_cliff && !QDELETED(mover)) // Exosuits are deleted when destroyed. This is to prevent phantom exosuits.
+ visible_message(SPAN_DANGER("\The [mover] rolls down towards \the [bottom_cliff]!"))
+ addtimer(CALLBACK(bottom_cliff, TYPE_PROC_REF(/obj/structure/cliff, fall_off_cliff), mover), 5)
+
+/obj/structure/cliff/can_climb(mob/living/user, post_climb_check = FALSE, silent = FALSE)
+ // Cliff climbing requires climbing gear.
+ if(ishuman(user))
+ var/mob/living/human/H = user
+ var/obj/item/clothing/shoes/shoes = H.get_equipped_item(slot_shoes_str)
+ if(shoes?.rock_climbing)
+ return ..() // Do the other checks too.
+ if(!silent)
+ to_chat(user, SPAN_WARNING("\The [src] is too steep to climb unassisted."))
+ return FALSE
+
+// This tells AI mobs to not be dumb and step off cliffs willingly.
+/obj/structure/cliff/is_safe_to_step(mob/living/stepper)
+ if(should_fall(stepper))
+ return FALSE
+ return ..()
diff --git a/code/game/objects/structures/fishtanks.dm b/code/game/objects/structures/fishtanks.dm
index c9ab4bc5f72e..2e828ed6e803 100644
--- a/code/game/objects/structures/fishtanks.dm
+++ b/code/game/objects/structures/fishtanks.dm
@@ -138,25 +138,25 @@ var/global/list/global/aquarium_states_and_layers = list(
for(var/atom/movable/AM in get_contained_external_atoms())
add_overlay(AM)
-/obj/structure/glass_tank/can_climb(var/mob/living/user, post_climb_check=0)
+/obj/structure/glass_tank/can_climb(mob/living/user, post_climb_check = FALSE, silent = FALSE)
if (!user.can_touch(src) || !(atom_flags & ATOM_FLAG_CLIMBABLE) || (!post_climb_check && (user in climbers)))
- return 0
-
+ return FALSE
if (!Adjacent(user))
- to_chat(user, SPAN_DANGER("You can't climb there, the way is blocked."))
- return 0
-
+ if(!silent)
+ to_chat(user, SPAN_WARNING("You can't climb there, the way is blocked."))
+ return FALSE
var/obj/occupied = turf_is_crowded()
if(occupied)
- to_chat(user, SPAN_DANGER("There's \a [occupied] in the way."))
- return 0
- return 1
+ if(!silent)
+ to_chat(user, SPAN_WARNING("There's \a [occupied] in the way."))
+ return FALSE
+ return TRUE
/obj/structure/glass_tank/do_climb(var/mob/living/user)
if(!istype(user) || !can_climb(user))
return
user.visible_message(SPAN_WARNING("\The [user] starts climbing into \the [src]!"))
- if(!do_after(user,50))
+ if(!do_after(user, 5 SECONDS))
return
if (!can_climb(user))
return
diff --git a/code/game/objects/structures/railing.dm b/code/game/objects/structures/railing.dm
index b04f59f9a150..003d51d5b13d 100644
--- a/code/game/objects/structures/railing.dm
+++ b/code/game/objects/structures/railing.dm
@@ -314,13 +314,13 @@ WOOD_RAILING_SUBTYPE(yew)
if(!QDELETED(src))
qdel(src)
-/obj/structure/railing/can_climb(var/mob/living/user, post_climb_check=0)
- . = ..()
- if(. && get_turf(user) == get_turf(src))
+/obj/structure/railing/can_climb(mob/living/user, post_climb_check = FALSE, silent = FALSE)
+ if((. = ..()) && get_turf(user) == get_turf(src))
var/turf/T = get_step(src, dir)
if(T.turf_is_crowded(user))
- to_chat(user, "You can't climb there, the way is blocked.")
- return 0
+ if(!silent)
+ to_chat(user, SPAN_WARNING("You can't climb there, the way is blocked."))
+ return FALSE
/obj/structure/railing/do_climb(var/mob/living/user)
. = ..()
diff --git a/code/game/objects/structures/signs.dm b/code/game/objects/structures/signs/_signs.dm
similarity index 90%
rename from code/game/objects/structures/signs.dm
rename to code/game/objects/structures/signs/_signs.dm
index 0b1245e25d7c..2241befe6254 100644
--- a/code/game/objects/structures/signs.dm
+++ b/code/game/objects/structures/signs/_signs.dm
@@ -87,7 +87,7 @@
///A wall mountable sign structure
/obj/structure/sign
name = "sign"
- icon = 'icons/obj/signs/warnings.dmi'
+ icon = 'icons/obj/signs/signs.dmi'
anchored = TRUE
opacity = FALSE
density = FALSE
@@ -134,3 +134,18 @@
/obj/structure/sign/double/handle_default_screwdriver_attackby(mob/user, obj/item/screwdriver)
return FALSE
+
+/obj/structure/sign/clock
+ name = "clock"
+ desc = "It's a functionally useless print-out of a clock face."
+ icon_state = "clock"
+
+/obj/structure/sign/calendar
+ name = "calendar"
+ desc = "It's a functionally useless print-out of a calendar."
+ icon_state = "calendar"
+
+/obj/structure/sign/periodic_table
+ name = "periodic table"
+ desc = "It's an old, outdated copy of the periodic table of elements."
+ icon_state = "periodic"
diff --git a/code/game/objects/structures/signs/bar_signs.dm b/code/game/objects/structures/signs/bar_signs.dm
index 51396fea7bbf..1d53cf2fa3a8 100644
--- a/code/game/objects/structures/signs/bar_signs.dm
+++ b/code/game/objects/structures/signs/bar_signs.dm
@@ -1,4 +1,3 @@
-
/obj/structure/sign/double/maltesefalcon
name = "The Maltese Falcon"
desc = "The Maltese Falcon, Space Bar and Grill."
@@ -13,3 +12,4 @@
/obj/structure/sign/double/maltesefalcon/right
icon_state = "maltesefalcon-right"
+
diff --git a/code/game/objects/structures/signs/decks.dm b/code/game/objects/structures/signs/decks.dm
new file mode 100644
index 000000000000..3fc6dadc59fd
--- /dev/null
+++ b/code/game/objects/structures/signs/decks.dm
@@ -0,0 +1,95 @@
+/////////////////////////////////////////////////////
+// Deck Signs
+/////////////////////////////////////////////////////
+
+///A sign for indicating what level is the current level vertically
+/obj/structure/sign/deck
+ abstract_type = /obj/structure/sign/deck
+ name = "current level sign"
+ desc = "A sign indicating on what level the observer is currently on."
+ icon = 'icons/obj/signs/decks.dmi'
+
+/////////////////////////////////////////////////////
+// Deck Signs Definition
+/////////////////////////////////////////////////////
+
+/obj/structure/sign/deck/bridge
+ name = "\improper Bridge Deck"
+ icon_state = "deck-b"
+
+/obj/structure/sign/deck/first
+ name = "\improper First Deck"
+ icon_state = "deck-1"
+
+/obj/structure/sign/deck/second
+ name = "\improper Second Deck"
+ icon_state = "deck-2"
+
+/obj/structure/sign/deck/third
+ name = "\improper Third Deck"
+ icon_state = "deck-3"
+
+/obj/structure/sign/deck/fourth
+ name = "\improper Fourth Deck"
+ icon_state = "deck-4"
+
+/obj/structure/sign/deck/fifth
+ name = "\improper Fifth Deck"
+ icon_state = "deck-5"
+
+/obj/structure/sign/deck/bridge/large
+ icon_state = "deck-b-large"
+
+/obj/structure/sign/deck/first/large
+ icon_state = "deck-1-large"
+
+/obj/structure/sign/deck/second/large
+ icon_state = "deck-2-large"
+
+/obj/structure/sign/deck/third/large
+ icon_state = "deck-3-large"
+
+/obj/structure/sign/deck/fourth/large
+ icon_state = "deck-4-large"
+
+/obj/structure/sign/deck/level_basement
+ name = "\improper Basemenet Level"
+ icon_state = "level-b"
+
+/obj/structure/sign/deck/level_one
+ name = "\improper Level One"
+ icon_state = "level-1"
+
+/obj/structure/sign/deck/level_two
+ name = "\improper Level Two"
+ icon_state = "level-2"
+
+/obj/structure/sign/deck/level_three
+ name = "\improper Level Three"
+ icon_state = "level-3"
+
+/obj/structure/sign/deck/level_four
+ name = "\improper Level Four"
+ icon_state = "level-4"
+
+/obj/structure/sign/deck/level_ground
+ name = "\improper Ground Level"
+ icon_state = "level-g"
+
+/obj/structure/sign/deck/level_basement/large
+ icon_state = "level-b-large"
+
+/obj/structure/sign/deck/level_one/large
+ icon_state = "level-1-large"
+
+/obj/structure/sign/deck/level_two/large
+ icon_state = "level-2-large"
+
+/obj/structure/sign/deck/level_three/large
+ icon_state = "level-3-large"
+
+/obj/structure/sign/deck/level_four/large
+ icon_state = "level-4-large"
+
+/obj/structure/sign/deck/level_ground/large
+ icon_state = "level-g-large"
diff --git a/code/game/objects/structures/signs/department_signs.dm b/code/game/objects/structures/signs/departments.dm
similarity index 77%
rename from code/game/objects/structures/signs/department_signs.dm
rename to code/game/objects/structures/signs/departments.dm
index 92fa661be71e..0018ae5d6a88 100644
--- a/code/game/objects/structures/signs/department_signs.dm
+++ b/code/game/objects/structures/signs/departments.dm
@@ -4,7 +4,7 @@
/obj/structure/sign/department
abstract_type = /obj/structure/sign/department
- icon = 'icons/obj/signs/slim_location_signs.dmi'
+ icon = 'icons/obj/signs/departments.dmi'
///////////////////////////////////////////////////////////////////////////////////
// Department Sign Definitions
@@ -18,36 +18,37 @@
/obj/structure/sign/department/science_2
name = "\improper RESEARCH"
desc = "A sign labelling an area where research is performed."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "science2"
+/obj/structure/sign/department/science_3
+ icon_state = "science1"
+
/obj/structure/sign/department/xenobio_1
name = "\improper XENOBIOLOGY"
desc = "A sign labelling an area as a place where xenobiological entities are researched."
icon_state = "xenobio"
+/obj/structure/sign/department/xenobio_1/large
+ icon_state = "xenobio-large"
+
/obj/structure/sign/department/xenobio_2
name = "\improper XENOBIOLOGY"
desc = "A sign labelling an area as a place where xenobiological entities are researched."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "xenobio2"
/obj/structure/sign/department/xenobio_3
name = "\improper XENOBIOLOGY"
desc = "A sign labelling an area as a place where xenobiological entities are researched."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "xenobio3"
/obj/structure/sign/department/xenobio_4
name = "\improper XENOBIOLOGY"
desc = "A sign labelling an area as a place where xenobiological entities are researched."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "xenobio4"
/obj/structure/sign/department/xenoarch
name = "\improper XENOARCHAEOLOGY"
desc = "A sign labelling an area as a place where xenoarchaeological finds are researched."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "xenobio4"
/obj/structure/sign/department/chemistry
@@ -55,16 +56,20 @@
desc = "A sign labelling an area containing chemical equipment."
icon_state = "chemistry"
+/obj/structure/sign/department/chemistry/alt_1
+ icon_state = "chemistry1"
+
+/obj/structure/sign/department/chemistry/alt_2
+ icon_state = "chemistry2"
+
/obj/structure/sign/department/xenoflora
name = "\improper XENOFLORA"
desc = "A sign labelling an area as a place where xenobiological plants are researched."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "hydro4"
/obj/structure/sign/department/botany
name = "\improper BOTANY"
- desc = "A warning sign which reads 'BOTANY!'."
- icon = 'icons/obj/signs/location_signs.dmi'
+ desc = "A warning sign which reads 'BOTANY'."
icon_state = "hydro3"
/obj/structure/sign/department/hydro
@@ -75,19 +80,30 @@
/obj/structure/sign/department/hydrostorage
name = "\improper HYDROPONICS STORAGE"
desc = "A sign labelling an area as a place where plant growing supplies are kept."
- icon = 'icons/obj/signs/location_signs.dmi'
icon_state = "hydro3"
+/obj/structure/sign/department/hydro/alt_1
+ icon_state = "hydro1"
+
+/obj/structure/sign/department/hydro/alt_2
+ icon_state = "hydro2"
+
/obj/structure/sign/department/janitor
name = "\improper JANITORIAL CLOSET"
desc = "A sign indicating a room used to store cleaning supplies."
icon_state = "janitor"
+/obj/structure/sign/department/janitor/alt
+ icon_state = "custodian"
+
/obj/structure/sign/department/engineering
name = "\improper ENGINEERING"
desc = "A sign labelling an area as the Engineering department."
icon_state = "engineering"
+/obj/structure/sign/department/engineering/engine
+ icon_state = "engine"
+
/obj/structure/sign/department/telecomms
name = "\improper TELECOMMUNICATIONS"
desc = "A sign labelling an area as the Telecommunications room."
@@ -98,11 +114,17 @@
desc = "A sign labelling the area as a cargo bay."
icon_state = "cargo"
+/obj/structure/sign/department/cargo/large
+ icon_state = "cargo-large"
+
/obj/structure/sign/department/mail_delivery
name = "\improper MAIL DELIVERY"
desc = "A sign labelling a mail delivery point."
icon_state = "mail"
+/obj/structure/sign/department/mail_delivery/large
+ icon_state = "mail-large"
+
/obj/structure/sign/department/bridge
name = "\improper BRIDGE"
desc = "A sign indicating the Bridge. Not the kind you cross rivers with, the other kind."
@@ -121,6 +143,9 @@
/obj/structure/sign/department/security/alt
icon_state = "sec_cuff"
+/obj/structure/sign/department/security/large
+ icon_state = "security"
+
/obj/structure/sign/department/eva
name = "\improper EVA"
desc = "A sign indicating this is where Extra Vehicular Activity equipment is stored."
@@ -141,41 +166,44 @@
desc = "A sign that lets you know that this is where you want to be when the station is full of holes and on fire."
icon_state = "evac"
-/obj/structure/sign/department/watercloset
- name = "\improper BATHROOMS"
+/obj/structure/sign/department/evac/large
+ icon_state = "evac-large"
+
+/obj/structure/sign/department/restroom
+ name = "restroom"
desc = "Need to take a piss? You've come to the right place."
icon_state = "watercloset"
+/obj/structure/sign/department/restroom/alt
+ icon_state = "restroom"
+
/obj/structure/sign/department/examroom
- name = "\improper Exam Room"
+ name = "exam room"
icon_state = "examroom"
-/obj/structure/sign/department/redcross
+/obj/structure/sign/department/examroom/large
+ icon_state = "examroom-large"
+
+/obj/structure/sign/department/cross
name = "medbay"
desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here."
- icon = 'icons/obj/signs/medical.dmi'
icon_state = "redcross"
-/obj/structure/sign/department/greencross
- name = "medbay"
- desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here."
- icon = 'icons/obj/signs/medical.dmi'
+/obj/structure/sign/department/cross/green
icon_state = "greencross"
-/obj/structure/sign/department/bluecross_1
- name = "infirmary"
- desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here."
- icon = 'icons/obj/signs/medical.dmi'
+/obj/structure/sign/department/cross/blue
icon_state = "bluecross"
-/obj/structure/sign/department/bluecross_2
- name = "infirmary"
- desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here."
- icon = 'icons/obj/signs/medical.dmi'
+/obj/structure/sign/department/cross/blue2
icon_state = "bluecross2"
-/obj/structure/sign/department/star_of_life
+/obj/structure/sign/department/cross/star_of_life
name = "emergency"
desc = "The blue six-pointed star with a rod of Asclepius is the intergalactic symbol of emergency medical services."
- icon = 'icons/obj/signs/medical.dmi'
- icon_state = "staroflife"
\ No newline at end of file
+ icon_state = "staroflife"
+
+/obj/structure/sign/department/chapel
+ name = "\improper CHAPEL"
+ desc = "A sign labelling this area as the Chapel."
+ icon_state = "holy"
diff --git a/code/game/objects/structures/signs/direction_signs.dm b/code/game/objects/structures/signs/direction_signs.dm
deleted file mode 100644
index 1b331afc9941..000000000000
--- a/code/game/objects/structures/signs/direction_signs.dm
+++ /dev/null
@@ -1,120 +0,0 @@
-/////////////////////////////////////////////////////
-//Direction Signs
-/////////////////////////////////////////////////////
-
-///Signs for showing the way to passerby. The dir of the sign is the direction it points towards. The icon of the sign itself is always south facing.
-/obj/structure/sign/directions
- name = "direction sign"
- desc = "A direction sign, claiming to know the way."
- icon = 'icons/obj/signs/directions.dmi'
- icon_state = "direction"
- //Direction signs are always meant to face south! The arrow on the sign matches the direction it points to.
- directional_offset = @'{"NORTH":{"y":32}, "SOUTH":{"y":32}, "WEST":{"y":32}, "EAST":{"y":32}}'
-
-/obj/structure/sign/directions/update_description()
- desc = "A direction sign, pointing out \the [name] is [global.dir_name(dir)]."
-
-/////////////////////////////////////////////////////
-//Direction Signs Definition
-/////////////////////////////////////////////////////
-
-/obj/structure/sign/directions/science
- name = "\improper Research Division"
- icon_state = "direction_sci"
-
-/obj/structure/sign/directions/engineering
- name = "\improper Engineering Bay"
- icon_state = "direction_eng"
-
-/obj/structure/sign/directions/security
- name = "\improper Security Wing"
- icon_state = "direction_sec"
-
-/obj/structure/sign/directions/medical
- name = "\improper Medical Bay"
- icon_state = "direction_med"
-
-/obj/structure/sign/directions/evac
- name = "\improper Evacuation Wing"
- icon_state = "direction_evac"
-
-/obj/structure/sign/directions/bridge
- name = "\improper Bridge"
- icon_state = "direction_bridge"
-
-/obj/structure/sign/directions/supply
- name = "\improper Supply Office"
- icon_state = "direction_supply"
-
-/obj/structure/sign/directions/infirmary
- name = "\improper Infirmary"
- icon_state = "direction_infirm"
-
-/obj/structure/sign/directions/pods
- name = "\improper ESCAPE PODS"
- icon_state = "direction_pods"
-
-/obj/structure/sign/directions/cryo
- name = "\improper Cryogenic Storage"
- icon_state = "direction_cryo"
-
-
-/////////////////////////////////////////////////////
-// Hangar Signs
-/////////////////////////////////////////////////////
-/obj/structure/sign/hangar
- abstract_type = /obj/structure/sign/hangar
- name = "hangar sign"
- desc = "A sign indicating which hangar the observer is near."
- icon = 'icons/obj/signs/hangars.dmi'
-
-/obj/structure/sign/hangar/one
- name = "\improper Hangar One"
- icon_state = "hangar-1"
-
-/obj/structure/sign/hangar/two
- name = "\improper Hangar Two"
- icon_state = "hangar-2"
-
-/obj/structure/sign/hangar/three
- name = "\improper Hangar Three"
- icon_state = "hangar-3"
-
-/////////////////////////////////////////////////////
-// Deck Signs
-/////////////////////////////////////////////////////
-
-///A sign for indicating what level is the current level vertically
-/obj/structure/sign/deck
- abstract_type = /obj/structure/sign/deck
- name = "current level sign"
- desc = "A sign indicating on what level the observer is currently on."
- icon = 'icons/obj/signs/slim_decks.dmi'
-
-/////////////////////////////////////////////////////
-// Deck Signs Definition
-/////////////////////////////////////////////////////
-
-/obj/structure/sign/deck/bridge
- name = "\improper Bridge Deck"
- icon_state = "deck-b"
-
-/obj/structure/sign/deck/first
- name = "\improper First Deck"
- icon_state = "deck-1"
-
-/obj/structure/sign/deck/second
- name = "\improper Second Deck"
- icon_state = "deck-2"
-
-/obj/structure/sign/deck/third
- name = "\improper Third Deck"
- icon_state = "deck-3"
-
-/obj/structure/sign/deck/fourth
- name = "\improper Fourth Deck"
- icon_state = "deck-4"
-
-/obj/structure/sign/deck/fifth
- name = "\improper Fifth Deck"
- icon_state = "deck-5"
\ No newline at end of file
diff --git a/code/game/objects/structures/signs/directions.dm b/code/game/objects/structures/signs/directions.dm
new file mode 100644
index 000000000000..c89ae69448fb
--- /dev/null
+++ b/code/game/objects/structures/signs/directions.dm
@@ -0,0 +1,282 @@
+/////////////////////////////////////////////////////
+//Direction Signs
+/////////////////////////////////////////////////////
+
+///Signs for showing the way to passerby. The dir of the sign is the direction it points towards. The icon of the sign itself is always south facing.
+/obj/structure/sign/directions
+ name = "direction sign"
+ desc = "A direction sign, claiming to know the way."
+ icon = 'icons/obj/signs/directions.dmi'
+ icon_state = "direction"
+ //Direction signs are always meant to face south! The arrow on the sign matches the direction it points to.
+ directional_offset = @'{"NORTH":{"y":32}, "SOUTH":{"y":32}, "WEST":{"y":32}, "EAST":{"y":32}}'
+
+/obj/structure/sign/directions/update_description()
+ desc = "A direction sign, pointing out \the [name] is [global.dir_name(dir)]."
+
+/////////////////////////////////////////////////////
+//Direction Signs Definition
+/////////////////////////////////////////////////////
+
+/obj/structure/sign/directions/science
+ name = "\improper Research Division"
+ icon_state = "direction_sci"
+
+/obj/structure/sign/directions/science/xeno
+ name = "\improper Xenobiology"
+ icon_state = "direction_xeno"
+
+/obj/structure/sign/directions/science/xenoarch
+ name = "\improper Xenoarchaeology"
+ icon_state = "direction_xenoarch"
+
+/obj/structure/sign/directions/science/xenoflora
+ name = "\improper Xenoflora"
+ icon_state = "direction_xflora"
+
+/obj/structure/sign/directions/science/xenobiology
+ icon_state = "direction_xbio"
+
+/obj/structure/sign/directions/science/exploration
+ name = "\improper Exploration"
+ icon_state = "direction_explo"
+
+/obj/structure/sign/directions/science/toxins
+ name = "\improper Toxins"
+ icon_state = "direction_toxins"
+
+/obj/structure/sign/directions/science/robotics
+ name = "\improper Robotics"
+ icon_state = "direction_robotics"
+
+/obj/structure/sign/directions/science/rnd
+ name = "\improper Research and Development"
+ icon_state = "direction_rnd"
+
+/obj/structure/sign/directions/engineering
+ name = "\improper Engineering Bay"
+ icon_state = "direction_eng"
+
+/obj/structure/sign/directions/engineering/solars
+ name = "\improper Solar Array"
+ icon_state = "direction_solar"
+
+/obj/structure/sign/directions/engineering/engeqp
+ name = "\improper Engineering Equipment"
+ icon_state = "direction_engeqp"
+
+/obj/structure/sign/directions/engineering/reactor
+ name = "\improper Reactor Core"
+ icon_state = "direction_core"
+
+/obj/structure/sign/directions/engineering/atmospherics
+ name = "\improper Atmospherics"
+ icon_state = "direction_atmos"
+
+/obj/structure/sign/directions/cargo
+ name = "\improper Cargo"
+ icon_state = "direction_crg"
+
+/obj/structure/sign/directions/cargo/supply
+ name = "\improper Supply Office"
+ icon_state = "direction_supply"
+
+/obj/structure/sign/directions/cargo/mining
+ name = "\improper Mining"
+ icon_state = "direction_mining"
+
+/obj/structure/sign/directions/cargo/refinery
+ name = "\improper Refinery"
+ icon_state = "direction_refinery"
+
+/obj/structure/sign/directions/security
+ name = "\improper Security Wing"
+ icon_state = "direction_sec"
+
+/obj/structure/sign/directions/security/interrogation
+ name = "\improper Interrogation"
+ icon_state = "direction_interrogation"
+
+/obj/structure/sign/directions/security/internal_affairs
+ name = "\improper Internal Affairs"
+ icon_state = "direction_intaff"
+
+/obj/structure/sign/directions/security/forensics
+ name = "\improper Forensics"
+ icon_state = "direction_forensics"
+
+/obj/structure/sign/directions/security/forensics/alt
+ name = "\improper Forensics Laboratory"
+ icon_state = "direction_lab"
+
+/obj/structure/sign/directions/security/brig
+ name = "\improper Security Brig"
+ icon_state = "direction_brig"
+
+/obj/structure/sign/directions/security/armory
+ name = "\improper Security Armory"
+ icon_state = "direction_armory"
+
+/obj/structure/sign/directions/security/seceqp
+ name = "\improper Security Equipment"
+ icon_state = "direction_seceqp"
+
+/obj/structure/sign/directions/medical
+ name = "\improper Medical Bay"
+ icon_state = "direction_med"
+
+/obj/structure/sign/directions/medical/morgue
+ name = "\improper Morgue"
+ icon_state = "direction_morgue"
+
+/obj/structure/sign/directions/medical/equipment
+ name = "\improper Medical Equipment"
+ icon_state = "direction_medeqp"
+
+/obj/structure/sign/directions/medical/virology
+ name = "\improper Virology"
+ icon_state = "direction_viro"
+
+/obj/structure/sign/directions/medical/surgery
+ name = "\improper Surgery"
+ icon_state = "direction_surgery"
+
+/obj/structure/sign/directions/medical/operating_1
+ name = "\improper Operating Theatre 1"
+ icon_state = "direction_op1"
+
+/obj/structure/sign/directions/medical/operating_2
+ name = "\improper Operating Theatre 2"
+ icon_state = "direction_op2"
+
+/obj/structure/sign/directions/medical/cloning
+ name = "\improper Cloning"
+ icon_state = "direction_cloning"
+
+/obj/structure/sign/directions/medical/resleeve
+ name = "\improper Resleeving"
+ icon_state = "direction_resleeve"
+
+/obj/structure/sign/directions/medical/chemlab
+ name = "\improper Chemistry Laboratory"
+ icon_state = "direction_chemlab"
+
+/obj/structure/sign/directions/evac
+ name = "\improper Evacuation Wing"
+ icon_state = "direction_evac"
+
+/obj/structure/sign/directions/bridge
+ name = "\improper Bridge"
+ icon_state = "direction_bridge"
+
+/obj/structure/sign/directions/infirmary
+ name = "\improper Infirmary"
+ icon_state = "direction_infirm"
+
+/obj/structure/sign/directions/pods
+ name = "\improper Escape Pods"
+ icon_state = "direction_pods"
+
+/obj/structure/sign/directions/cryo
+ name = "\improper Cryogenic Storage"
+ icon_state = "direction_cryo"
+
+/obj/structure/sign/directions/elevator
+ name = "\improper Elevator"
+ icon_state = "direction_elv"
+
+/obj/structure/sign/directions/command
+ name = "\improper Command"
+ icon_state = "direction_command"
+
+/obj/structure/sign/directions/dorms
+ name = "\improper Dormitories"
+ icon_state = "direction_dorms"
+
+/obj/structure/sign/directions/teleporter
+ name = "\improper Teleporter"
+ icon_state = "direction_teleport"
+
+/obj/structure/sign/directions/roomnum
+ name = "\improper Private Room"
+ icon_state = "direction_roomnum" // TODO: move this to signs.dmi or something.
+
+/obj/structure/sign/directions/recreation
+ name = "\improper Recreation"
+ icon_state = "direction_recreation"
+
+/obj/structure/sign/directions/pool
+ name = "\improper Pool"
+ icon_state = "direction_pool"
+
+/obj/structure/sign/directions/janitor
+ name = "\improper Custodial Office"
+ icon_state = "direction_janitor"
+
+/obj/structure/sign/directions/eva
+ name = "\improper EVA"
+ icon_state = "direction_eva"
+
+/obj/structure/sign/directions/bar
+ name = "\improper Bar"
+ icon_state = "direction_bar"
+
+/obj/structure/sign/directions/ai_core
+ name = "\improper AI Core"
+ icon_state = "direction_ai_core"
+
+/obj/structure/sign/directions/gravity
+ name = "\improper Gravity Management"
+ icon_state = "direction_grav"
+
+/obj/structure/sign/directions/telecomms
+ name = "\improper Telecommunications"
+ icon_state = "direction_tcomms"
+
+/obj/structure/sign/directions/kitchen
+ name = "\improper Kitchen"
+ icon_state = "direction_kitchen"
+
+/obj/structure/sign/directions/tram
+ name = "\improper Transit"
+ icon_state = "direction_tram"
+
+/obj/structure/sign/directions/chapel
+ name = "\improper Chapel"
+ icon_state = "direction_chapel"
+
+/obj/structure/sign/directions/library
+ name = "\improper Library"
+ icon_state = "direction_library"
+
+/obj/structure/sign/directions/dock
+ name = "\improper Dock"
+ icon_state = "direction_dock"
+
+/obj/structure/sign/directions/gym
+ name = "\improper Gymnasium"
+ icon_state = "direction_gym"
+
+/obj/structure/sign/directions/exit
+ name = "\improper Emergency Exit"
+ icon_state = "exit_sign"
+
+/obj/structure/sign/directions/stairs
+ name = "\improper Stairwell"
+ icon_state = "stairwell"
+
+/obj/structure/sign/directions/stairs/up
+ icon_state = "stairs_up"
+
+/obj/structure/sign/directions/stairs/down
+ icon_state = "stairs_down"
+
+/obj/structure/sign/directions/ladder
+ name = "\improper Ladder"
+ icon_state = "ladderwell"
+
+/obj/structure/sign/directions/ladder/up
+ icon_state = "ladder_up"
+
+/obj/structure/sign/directions/ladder/down
+ icon_state = "ladder_down"
diff --git a/code/game/objects/structures/signs/flags.dm b/code/game/objects/structures/signs/flags.dm
new file mode 100644
index 000000000000..8fce965bc1aa
--- /dev/null
+++ b/code/game/objects/structures/signs/flags.dm
@@ -0,0 +1,27 @@
+/obj/structure/sign/double/flag
+ name = "flag"
+ desc = "A plain flag."
+ icon = 'icons/obj/signs/flags.dmi'
+ icon_state = "flag"
+/obj/structure/sign/double/flag/left
+ icon_state = "flag_l"
+/obj/structure/sign/double/flag/right
+ icon_state = "flag_r"
+
+/obj/structure/sign/double/flag/pirate
+ name = "pirate flag"
+ desc = "Yarr harr, fiddle de dee!"
+ icon_state = "pirate"
+/obj/structure/sign/double/flag/pirate/left
+ icon_state = "pirate_l"
+/obj/structure/sign/double/flag/pirate/right
+ icon_state = "pirate_r"
+
+/obj/structure/sign/double/flag/catpirate
+ name = "cat pirate flag"
+ desc = "Nyarr harr, fiddle de dee!"
+ icon_state = "catpirate"
+/obj/structure/sign/double/flag/catpirate/left
+ icon_state = "catpirate_l"
+/obj/structure/sign/double/flag/catpirate/right
+ icon_state = "catpirate_r"
diff --git a/code/game/objects/structures/signs/hangar.dm b/code/game/objects/structures/signs/hangar.dm
new file mode 100644
index 000000000000..0ad300afe060
--- /dev/null
+++ b/code/game/objects/structures/signs/hangar.dm
@@ -0,0 +1,20 @@
+/////////////////////////////////////////////////////
+// Hangar Signs
+/////////////////////////////////////////////////////
+/obj/structure/sign/hangar
+ abstract_type = /obj/structure/sign/hangar
+ name = "hangar sign"
+ desc = "A sign indicating which hangar the observer is near."
+ icon = 'icons/obj/signs/hangars.dmi'
+
+/obj/structure/sign/hangar/one
+ name = "\improper Hangar One"
+ icon_state = "hangar-1"
+
+/obj/structure/sign/hangar/two
+ name = "\improper Hangar Two"
+ icon_state = "hangar-2"
+
+/obj/structure/sign/hangar/three
+ name = "\improper Hangar Three"
+ icon_state = "hangar-3"
diff --git a/code/game/objects/structures/signs/levels.dm b/code/game/objects/structures/signs/levels.dm
new file mode 100644
index 000000000000..214b43bc6508
--- /dev/null
+++ b/code/game/objects/structures/signs/levels.dm
@@ -0,0 +1,232 @@
+/obj/structure/sign/levels
+ icon = 'icons/obj/signs/levels.dmi'
+ icon_state = "level"
+ //Level signs are always meant to face south! The arrow on the sign matches the direction it points to.
+ directional_offset = @'{"NORTH":{"y":32}, "SOUTH":{"y":32}, "WEST":{"y":32}, "EAST":{"y":32}}'
+
+/obj/structure/sign/levels/update_description()
+ desc = "A sign indicating your position within \the [name]."
+
+/obj/structure/sign/levels/engineering
+ name = "\improper Engineering"
+ icon_state = "level_eng"
+
+/obj/structure/sign/levels/engineering/core
+ name = "\improper Reactor Core"
+ icon_state = "level_core"
+
+/obj/structure/sign/levels/engineering/solar
+ name = "\improper Solar Array"
+ icon_state = "level_solar"
+
+/obj/structure/sign/levels/engineering/atmos
+ name = "\improper Atmospherics"
+ icon_state = "level_atmos"
+
+/obj/structure/sign/levels/engineering/gravity
+ name = "\improper Gravity Control"
+ icon_state = "level_grav"
+
+/obj/structure/sign/levels/engineering/equipment
+ name = "\improper Engineering Equipment"
+ icon_state = "level_engeqp"
+
+/obj/structure/sign/levels/medical
+ name = "\improper Medical"
+ icon_state = "level_med"
+
+/obj/structure/sign/levels/medical/virology
+ name = "\improper Virology"
+ icon_state = "level_viro"
+
+/obj/structure/sign/levels/medical/morgue
+ name = "\improper Morgue"
+ icon_state = "level_morgue"
+
+/obj/structure/sign/levels/medical/surgery
+ name = "\improper Surgery"
+ icon_state = "level_surgery"
+
+/obj/structure/sign/levels/medical/cloning
+ name = "\improper Cloning"
+ icon_state = "level_cloning"
+
+/obj/structure/sign/levels/medical/resleeve
+ name = "\improper Resleeving"
+ icon_state = "level_resleeve"
+
+/obj/structure/sign/levels/medical/chemlab
+ name = "\improper Chemistry"
+ icon_state = "level_chemlab"
+
+/obj/structure/sign/levels/medical/equipment
+ name = "\improper Medical Equipment"
+ icon_state = "level_medeqp"
+
+/obj/structure/sign/levels/medical/operating_1
+ name = "\improper Operating Theatre 1"
+ icon_state = "level_op1"
+
+/obj/structure/sign/levels/medical/operating_2
+ name = "\improper Operating Theatre 2"
+ icon_state = "level_op2"
+
+/obj/structure/sign/levels/security
+ name = "\improper Security"
+ icon_state = "level_sec"
+
+/obj/structure/sign/levels/security/seceqp
+ name = "\improper Security Equipment"
+ icon_state = "level_seceqp"
+
+/obj/structure/sign/levels/security/interrogation
+ name = "\improper Interrogation"
+ icon_state = "level_interrogation"
+
+/obj/structure/sign/levels/security/forensics
+ name = "\improper Forensics"
+ icon_state = "level_forensics"
+
+/obj/structure/sign/levels/security/brig
+ name = "\improper Security Brig"
+ icon_state = "level_brig"
+
+/obj/structure/sign/levels/security/armory
+ name = "\improper Security Armory"
+ icon_state = "level_armory"
+
+/obj/structure/sign/levels/security/internalaffairs
+ name = "\improper Internal Affairs"
+ icon_state = "level_intaff"
+
+/obj/structure/sign/levels/cryo
+ name = "\improper Cryogenics"
+ icon_state = "level_cry"
+
+/obj/structure/sign/levels/evac
+ name = "\improper Evac Wing"
+ icon_state = "level_evac"
+
+/obj/structure/sign/levels/eva
+ name = "\improper EVA"
+ icon_state = "level_eva"
+
+/obj/structure/sign/levels/command
+ name = "\improper Command"
+ icon_state = "level_command"
+
+/obj/structure/sign/levels/science
+ name = "\improper Research Wing"
+ icon_state = "level_sci"
+
+/obj/structure/sign/levels/science/xenoflora
+ name = "\improper Xenoflora"
+ icon_state = "level_xflora"
+
+/obj/structure/sign/levels/science/xenobiology
+ name = "\improper Xenobiology"
+ icon_state = "level_xbio"
+
+/obj/structure/sign/levels/science/exploration
+ name = "\improper Exploration"
+ icon_state = "level_explo"
+
+/obj/structure/sign/levels/science/robotics
+ name = "\improper Robotics"
+ icon_state = "level_robotics"
+
+/obj/structure/sign/levels/science/toxins
+ name = "\improper Toxins"
+ icon_state = "level_toxins"
+
+/obj/structure/sign/levels/science/xenoarch
+ name = "\improper Xenoarchaeology"
+ icon_state = "level_xenoarch"
+
+/obj/structure/sign/levels/science/rnd
+ name = "\improper Research and Development"
+ icon_state = "level_rnd"
+
+/obj/structure/sign/levels/dorms
+ name = "\improper Dormitories"
+ icon_state = "level_dorms"
+
+/obj/structure/sign/levels/cargo
+ name = "\improper Cargo"
+ icon_state = "level_crg"
+
+/obj/structure/sign/levels/cargo/mining
+ name = "\improper Mining"
+ icon_state = "level_mining"
+
+/obj/structure/sign/levels/cargo/refinery
+ name = "\improper Refinery"
+ icon_state = "level_refinery"
+
+/obj/structure/sign/levels/recreation
+ name = "\improper Recreation"
+ icon_state = "level_recreation"
+
+/obj/structure/sign/levels/laboratory
+ name = "\improper Laboratory"
+ icon_state = "level_lab"
+
+/obj/structure/sign/levels/xeno
+ name = "\improper Xenobiology"
+ icon_state = "level_xeno"
+
+/obj/structure/sign/levels/ai_core
+ name = "\improper AI Core"
+ icon_state = "level_ai_core"
+
+/obj/structure/sign/levels/bridge
+ name = "\improper Bridge"
+ icon_state = "level_bridge"
+
+/obj/structure/sign/levels/teleporter
+ name = "\improper Teleporter"
+ icon_state = "level_teleport"
+
+/obj/structure/sign/levels/telecomms
+ name = "\improper Telecommunications"
+ icon_state = "level_tcomms"
+
+/obj/structure/sign/levels/elevator
+ name = "\improper Elevator"
+ icon_state = "level_elv"
+
+/obj/structure/sign/levels/bar
+ name = "\improper Bar"
+ icon_state = "level_bar"
+
+/obj/structure/sign/levels/kitchen
+ name = "\improper Kitchen"
+ icon_state = "level_kitchen"
+
+/obj/structure/sign/levels/tram
+ name = "\improper Tram"
+ icon_state = "level_tram"
+
+/obj/structure/sign/levels/janitor
+ name = "\improper Janitor"
+ icon_state = "level_janitor"
+
+/obj/structure/sign/levels/chapel
+ name = "\improper Chapel"
+ icon_state = "level_chapel"
+
+/obj/structure/sign/levels/library
+ name = "\improper Library"
+ icon_state = "level_library"
+
+/obj/structure/sign/levels/dock
+ name = "\improper Docks"
+ icon_state = "level_dock"
+
+/obj/structure/sign/levels/gym
+ name = "\improper Gymnasium"
+ icon_state = "level_gym"
+
+/obj/structure/sign/levels/pool
+ name = "\improper Pool"
+ icon_state = "level_pool"
diff --git a/code/game/objects/structures/signs/plaques.dm b/code/game/objects/structures/signs/plaques.dm
index 19d72c4e1efc..4a4257437d12 100644
--- a/code/game/objects/structures/signs/plaques.dm
+++ b/code/game/objects/structures/signs/plaques.dm
@@ -19,6 +19,10 @@
/obj/structure/sign/plaque/dark
icon_state = "darkplaque"
+/obj/structure/sign/plaque/floor
+ desc = "A floor-mounted commemorative plaque."
+ icon_state = "floorplaque"
+
/obj/structure/sign/plaque/golden
name = "The Most Robust Men Award for Robustness"
desc = "To be Robust is not an action or a way of life, but a mental state. Only those with the force of Will strong enough to act during a crisis, saving friend from foe, are truly Robust. Stay Robust my friends."
diff --git a/code/game/objects/structures/signs/warning_signs.dm b/code/game/objects/structures/signs/warning_signs.dm
index 7165a62c8a04..2bcb946dec88 100644
--- a/code/game/objects/structures/signs/warning_signs.dm
+++ b/code/game/objects/structures/signs/warning_signs.dm
@@ -6,7 +6,7 @@
/obj/structure/sign/warning
name = "\improper WARNING"
desc = "You've been warned!"
- icon = 'icons/obj/signs/slim_warnings.dmi'
+ icon = 'icons/obj/signs/warnings.dmi'
icon_state = "securearea"
directional_offset = @'{"NORTH":{"y":-32}, "SOUTH":{"y":32}, "WEST":{"x":34}, "EAST":{"x":-34}}'
@@ -28,25 +28,28 @@
name = "\improper EXTERNAL AIRLOCK"
icon_state = "doors"
-/obj/structure/sign/warning/evac
- name = "\improper KEEP CLEAR: EVAC DOCKING AREA"
- icon = 'icons/obj/signs/warnings.dmi'
- icon_state = "evac"
+/obj/structure/sign/warning/airlock/large
+ icon_state = "doors-large"
+
+/obj/structure/sign/warning/pods
+ name = "\improper WARNING: ESCAPE POD DOCKING AREA"
+ icon_state = "pods"
/obj/structure/sign/warning/deathsposal
name = "\improper DISPOSAL LEADS TO SPACE"
- icon = 'icons/obj/signs/warnings.dmi'
icon_state = "deathsposal"
/obj/structure/sign/warning/shock
name = "\improper HIGH VOLTAGE"
- icon = 'icons/obj/signs/warnings.dmi'
icon_state = "shock"
/obj/structure/sign/warning/compressed_gas
name = "\improper COMPRESSED GAS"
icon_state = "hikpa"
+/obj/structure/sign/warning/compressed_gas/large
+ icon_state = "hikpa-large"
+
/obj/structure/sign/warning/docking_area
name = "\improper KEEP CLEAR: DOCKING AREA"
@@ -57,19 +60,24 @@
name = "\improper MOVING PARTS"
icon_state = "movingparts"
+/obj/structure/sign/warning/moving_parts/large
+ icon_state = "movingparts-large"
+
/obj/structure/sign/warning/nosmoking_1
name = "\improper NO SMOKING"
icon_state = "nosmoking"
+/obj/structure/sign/warning/nosmoking_1/large
+ icon_state = "nosmoking-large"
+
/obj/structure/sign/warning/nosmoking_2
name = "\improper NO SMOKING"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "nosmoking2"
/obj/structure/sign/warning/nosmoking_burned
name = "\improper NO SMOKING"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "nosmoking2_b"
+
/obj/structure/sign/warning/nosmoking_burned/update_description()
. = ..()
desc += " It looks charred."
@@ -77,22 +85,37 @@
/obj/structure/sign/warning/smoking
name = "\improper SMOKING"
icon_state = "smoking"
+
/obj/structure/sign/warning/smoking/update_description()
. = ..()
desc += " Hell yeah."
+/obj/structure/sign/warning/smoking/large
+ icon_state = "smoking-large"
+
/obj/structure/sign/warning/secure_area
name = "\improper SECURE AREA"
icon_state = "securearea2"
+/obj/structure/sign/warning/secure_area/large
+ icon_state = "securearea2-large"
+
+/obj/structure/sign/warning/large
+ icon_state = "securearea-large"
+
/obj/structure/sign/warning/armory
name = "\improper ARMORY"
icon_state = "armory"
+/obj/structure/sign/warning/armory/large
+ icon_state = "armory-large"
+
/obj/structure/sign/warning/server_room
name = "\improper SERVER ROOM"
icon_state = "server"
+/obj/structure/sign/warning/server_room/large
+ icon_state = "server-large"
///////////////////////////////////////////////////////////////////////////////////
// Hazard Sign Definitions
@@ -100,13 +123,15 @@
/obj/structure/sign/warning/biohazard
name = "\improper BIOHAZARD"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "bio"
/obj/structure/sign/warning/radioactive
name = "\improper RADIOACTIVE AREA"
icon_state = "radiation"
+/obj/structure/sign/warning/radioactive/large
+ icon_state = "radiation-large"
+
/obj/structure/sign/warning/radioactive/alt
name = "\improper IONIZING RADIATION"
icon_state = "radiation_2"
@@ -115,10 +140,16 @@
name = "\improper DANGER: FIRE"
icon_state = "fire"
+/obj/structure/sign/warning/fire/large
+ icon_state = "fire-large"
+
/obj/structure/sign/warning/high_voltage
name = "\improper HIGH VOLTAGE"
icon_state = "shock"
+/obj/structure/sign/warning/high_voltage/large
+ icon_state = "shock-large"
+
/obj/structure/sign/warning/hot_exhaust
name = "\improper HOT EXHAUST"
icon_state = "fire"
@@ -132,17 +163,14 @@
/obj/structure/sign/warning/bomb_range
name = "\improper BOMB RANGE"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "blast"
/obj/structure/sign/warning/fall
name = "\improper FALL HAZARD"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "falling"
/obj/structure/sign/warning/lethal_turrets
name = "\improper LETHAL TURRETS"
- icon = 'icons/obj/signs/warnings.dmi' //This one is a full-size sign
icon_state = "turrets"
/obj/structure/sign/warning/lethal_turrets/update_description()
@@ -156,6 +184,9 @@
name = "\improper HARD VACUUM AHEAD"
icon_state = "space"
+/obj/structure/sign/warning/vacuum/large
+ icon_state = "space-large"
+
/obj/structure/sign/warning/vent_port
name = "\improper EJECTION/VENTING PORT"
@@ -165,14 +196,54 @@
/obj/structure/sign/warning/mass_spectrometry
name = "\improper MASS SPECTROMETRY"
-//legacy stuff - The exodus map still uses it
-/obj/structure/sign/warning/pods
- name = "\improper ESCAPE PODS"
- icon = 'icons/obj/signs/directions.dmi'
- icon_state = "podsnorth"
-/obj/structure/sign/warning/pods/south
- icon_state = "podssouth"
-/obj/structure/sign/warning/pods/east
- icon_state = "podseast"
-/obj/structure/sign/warning/pods/west
- icon_state = "podswest"
\ No newline at end of file
+/obj/structure/sign/warning/acid
+ name = "\improper WARNING: CORROSIVE MATERIALS"
+ icon_state = "acid"
+
+/obj/structure/sign/warning/cold
+ name = "\improper WARNING: LOW TEMPERATURES"
+ icon_state = "cold"
+
+/obj/structure/sign/warning/lava
+ name = "\improper WARNING: MOLTEN ROCK"
+ icon_state = "lava"
+
+/obj/structure/sign/warning/malfunction
+ name = "\improper IN CASE OF MALFUNCTION"
+ icon_state = "rogueai"
+
+/obj/structure/sign/warning/explosives
+ name = "\improper WARNING: HIGH EXPLOSIVES"
+ icon_state = "explosives"
+
+/obj/structure/sign/warning/chemicals
+ name = "\improper WARNING: RISK OF CHEMICAL EXPOSURE"
+ icon_state = "chemdiamond"
+
+/obj/structure/sign/warning/atmos_co2
+ name = "\improper WARNING: CO2"
+ icon_state = "atmos_co2"
+
+/obj/structure/sign/warning/atmos_n2o
+ name = "\improper WARNING: N2O"
+ icon_state = "atmos_n2o"
+
+/obj/structure/sign/warning/atmos_phoron
+ name = "\improper WARNING: EXOTIC MATTER"
+ icon_state = "atmos_phoron"
+
+/obj/structure/sign/warning/atmos_o2
+ name = "\improper WARNING: O2"
+ icon_state = "atmos_o2"
+
+/obj/structure/sign/warning/atmos_air
+ name = "\improper WARNING: PRESSURIZED AIR"
+ icon_state = "atmos_air"
+
+/obj/structure/sign/warning/atmos_n2
+ name = "\improper WARNING: N2"
+ icon_state = "atmos_n2"
+
+/obj/structure/sign/warning/atmos_waste
+ name = "\improper WARNING: WASTE UNDER PRESSURE"
+ icon_state = "atmos_waste"
diff --git a/code/game/objects/structures/stasis_cage.dm b/code/game/objects/structures/stasis_cage.dm
deleted file mode 100644
index 5474cb223cf5..000000000000
--- a/code/game/objects/structures/stasis_cage.dm
+++ /dev/null
@@ -1,93 +0,0 @@
-/obj/structure/stasis_cage
- name = "stasis cage"
- desc = "A high-tech animal cage, designed to keep contained fauna docile and safe."
- icon = 'icons/obj/stasis_cage.dmi'
- icon_state = "stasis_cage"
- density = TRUE
- layer = ABOVE_OBJ_LAYER
-
- var/mob/living/simple_animal/contained
-
-/obj/structure/stasis_cage/Initialize()
- . = ..()
-
- var/mob/living/simple_animal/A = locate() in loc
- if(A)
- contain(A)
-
-/obj/structure/stasis_cage/attackby(obj/item/used_item, mob/user)
- if(contained && istype(used_item, /obj/item/scanner/xenobio))
- return contained.attackby(used_item, user)
- . = ..()
-
-/obj/structure/stasis_cage/attack_hand(var/mob/user)
- if(!user.check_dexterity(DEXTERITY_SIMPLE_MACHINES, TRUE))
- return ..()
- try_release(user)
- return TRUE
-
-/obj/structure/stasis_cage/attack_robot(var/mob/user)
- if(CanPhysicallyInteract(user))
- try_release(user)
- return TRUE
-
-/obj/structure/stasis_cage/proc/try_release(mob/user)
- if(!contained)
- to_chat(user, SPAN_NOTICE("There's no animals inside \the [src]"))
- return
- user.visible_message("[user] begins undoing the locks and latches on \the [src].")
- if(do_after(user, 20, src))
- user.visible_message("[user] releases \the [contained] from \the [src]!")
- release()
-
-/obj/structure/stasis_cage/on_update_icon()
- ..()
- if(contained)
- icon_state = "[initial(icon_state)]_on"
- else
- icon_state = initial(icon_state)
-
-/obj/structure/stasis_cage/get_examine_strings(mob/user, distance, infix, suffix)
- . = ..()
- if(contained)
- . += "\The [contained] is kept inside."
-
-/obj/structure/stasis_cage/proc/contain(var/mob/living/simple_animal/animal)
- if(contained || !istype(animal))
- return
-
- contained = animal
- animal.forceMove(src)
- animal.in_stasis = 1
- update_icon()
-
-/obj/structure/stasis_cage/proc/release()
- if(!contained)
- return
-
- contained.dropInto(src)
- contained.in_stasis = 0
- contained = null
- update_icon()
-
-/obj/structure/stasis_cage/Destroy()
- release()
- return ..()
-
-/mob/living/simple_animal/handle_mouse_drop(atom/over, mob/user, params)
- if(istype(over, /obj/structure/stasis_cage))
- var/obj/structure/stasis_cage/cage = over
- if(!stat && !istype(buckled, /obj/effect/energy_net))
- to_chat(user, SPAN_WARNING("It's going to be difficult to convince \the [src] to move into \the [cage] without capturing it in a net."))
- return TRUE
- user.visible_message( \
- SPAN_NOTICE("\The [user] begins stuffing \the [src] into \the [cage]."), \
- SPAN_NOTICE("You begin stuffing \the [src] into \the [cage]."))
- Bumped(user)
- if(do_after(user, 20, cage))
- cage.visible_message( \
- SPAN_NOTICE("\The [user] has stuffed \the [src] into \the [cage]."), \
- SPAN_NOTICE("You have stuffed \the [src] into \the [cage]."))
- cage.contain(src)
- return TRUE
- . = ..()
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 3f0d24378d25..6aa5066cea21 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -141,29 +141,29 @@ var/global/const/FALLOFF_SOUNDS = 0.5
/proc/get_sfx(soundin)
if(istext(soundin))
switch(soundin)
- if ("shatter") soundin = pick(global.shatter_sound)
- if ("explosion") soundin = pick(global.explosion_sound)
- if ("sparks") soundin = pick(global.spark_sound)
- if ("rustle") soundin = pick(global.rustle_sound)
- if ("punch") soundin = pick(global.punch_sound)
+ if ("shatter") soundin = pick(global.shatter_sound)
+ if ("explosion") soundin = pick(global.explosion_sound)
+ if ("sparks") soundin = pick(global.spark_sound)
+ if ("rustle") soundin = pick(global.rustle_sound)
+ if ("punch") soundin = pick(global.punch_sound)
if ("light_strike") soundin = pick(global.light_strike_sound)
- if ("clownstep") soundin = pick(global.clown_sound)
- if ("swing_hit") soundin = pick(global.swing_hit_sound)
- if ("hiss") soundin = pick(global.hiss_sound)
- if ("pageturn") soundin = pick(global.page_sound)
- if ("fracture") soundin = pick(global.fracture_sound)
- if ("light_bic") soundin = pick(global.lighter_sound)
- if ("keyboard") soundin = pick(global.keyboard_sound)
- if ("keystroke") soundin = pick(global.keystroke_sound)
- if ("switch") soundin = pick(global.switch_sound)
- if ("button") soundin = pick(global.button_sound)
- if ("chop") soundin = pick(global.chop_sound)
- if ("glasscrack") soundin = pick(global.glasscrack_sound)
- if ("tray_hit") soundin = pick(global.tray_hit_sound)
- if ("sweeping") soundin = pick(global.sweeping_sound)
+ if ("clownstep") soundin = pick(global.clown_sound)
+ if ("swing_hit") soundin = pick(global.swing_hit_sound)
+ if ("hiss") soundin = pick(global.hiss_sound)
+ if ("pageturn") soundin = pick(global.page_sound)
+ if ("fracture") soundin = pick(global.fracture_sound)
+ if ("light_bic") soundin = pick(global.lighter_sound)
+ if ("keyboard") soundin = pick(global.keyboard_sound)
+ if ("keystroke") soundin = pick(global.keystroke_sound)
+ if ("switch") soundin = pick(global.switch_sound)
+ if ("button") soundin = pick(global.button_sound)
+ if ("chop") soundin = pick(global.chop_sound)
+ if ("glasscrack") soundin = pick(global.glasscrack_sound)
+ if ("tray_hit") soundin = pick(global.tray_hit_sound)
+ if ("sweeping") soundin = pick(global.sweeping_sound)
+ if("ricochet") soundin = pick(global.ricochet_sound)
return soundin
-
///Volume to play DTMF key sounds at. They're pretty loud, so 15 is fine.
#define VOL_DTMF_KEY 15
diff --git a/code/game/turfs/flooring/_flooring.dm b/code/game/turfs/flooring/_flooring.dm
index 57f759c0bdce..f30adf01de71 100644
--- a/code/game/turfs/flooring/_flooring.dm
+++ b/code/game/turfs/flooring/_flooring.dm
@@ -27,6 +27,7 @@ var/global/list/flooring_cache = list()
var/damage_temperature
var/icon_edge_layer = FLOOR_EDGE_NONE
var/has_environment_proc
+ var/can_conceal_hazards = FALSE
/// Unbuildable if not set. Must be /obj/item/stack.
var/build_type
@@ -393,3 +394,6 @@ var/global/list/flooring_cache = list()
/// contaminant is, optionally, the material of the coating that wants to be added.
/decl/flooring/proc/can_show_coating_footprints(turf/target, decl/material/contaminant)
return TRUE
+
+/decl/flooring/proc/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle::base_speed
diff --git a/code/game/turfs/flooring/_flooring_decals.dm b/code/game/turfs/flooring/_flooring_decals.dm
index 50ca2d36ab71..b1847ad05ab8 100644
--- a/code/game/turfs/flooring/_flooring_decals.dm
+++ b/code/game/turfs/flooring/_flooring_decals.dm
@@ -1221,7 +1221,7 @@ var/global/list/floor_decals = list()
/obj/effect/floor_decal/solarpanel
icon_state = "solarpanel"
-/obj/effect/floor_decal/snow
+/obj/effect/floor_decal/snow_floor
icon = 'icons/turf/overlays.dmi'
icon_state = "snowfloor"
@@ -1634,4 +1634,34 @@ var/global/list/floor_decals = list()
/obj/effect/floor_decal/corner_oldtile/green/full
name = "corner oldtile full"
- icon_state = "corner_oldtile_full"
\ No newline at end of file
+ icon_state = "corner_oldtile_full"
+
+// Decorative overlays.
+/obj/effect/floor_decal/vines
+ name = "vines"
+ desc = "A tangle of plant growth."
+ icon_state = "vines"
+
+/obj/effect/floor_decal/vines/top
+ icon_state = "vines_top"
+
+/obj/effect/floor_decal/vines/mid
+ icon_state = "vines_mid"
+
+/obj/effect/floor_decal/vines/bottom
+ icon_state = "vines_bottom"
+
+/obj/effect/floor_decal/snow
+ name = "snow"
+ desc = "A fine dusting of snow."
+ icon_state = "snowy"
+
+/obj/effect/floor_decal/rust
+ name = "rust"
+ desc = "A clumpy area of rust."
+ icon_state = "rusted"
+
+/obj/effect/floor_decal/floornums
+ name = "floor marker"
+ desc = "A number corresponding to the position of this floor."
+ icon_state = "floornums"
diff --git a/code/game/turfs/flooring/flooring_concrete.dm b/code/game/turfs/flooring/flooring_concrete.dm
index e5d6edd5f174..89ed297a1f7a 100644
--- a/code/game/turfs/flooring/flooring_concrete.dm
+++ b/code/game/turfs/flooring/flooring_concrete.dm
@@ -7,6 +7,7 @@
force_material = /decl/material/solid/stone/concrete
constructed = TRUE
uid = "floor_concrete"
+ can_conceal_hazards = TRUE
/decl/flooring/concrete/reinforced
name = "reinforced concrete"
diff --git a/code/game/turfs/flooring/flooring_grass.dm b/code/game/turfs/flooring/flooring_grass.dm
index dc53aaf46808..f51600fa91dc 100644
--- a/code/game/turfs/flooring/flooring_grass.dm
+++ b/code/game/turfs/flooring/flooring_grass.dm
@@ -14,6 +14,8 @@
force_material = /decl/material/solid/organic/plantmatter/grass
growth_value = 1.2 // Shouldn't really matter since you can't plant on grass, it turns to dirt first.
uid = "floor_grass"
+ can_conceal_hazards = TRUE
+
var/harvestable = FALSE
/decl/flooring/grass/fire_act(turf/floor/target, datum/gas_mixture/air, exposed_temperature, exposed_volume)
@@ -48,6 +50,9 @@
return TRUE
return ..()
+/decl/flooring/grass/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return 1
+
/decl/flooring/grass/fake
desc = "Do they smoke grass out in space, Bowie? Or do they smoke AstroTurf?"
icon = 'icons/turf/flooring/fakegrass.dmi'
@@ -56,3 +61,6 @@
build_type = /obj/item/stack/tile/grass
force_material = /decl/material/solid/organic/plastic
uid = "floor_grass_fake"
+
+/decl/flooring/grass/fake/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle::base_speed
diff --git a/code/game/turfs/flooring/flooring_mud.dm b/code/game/turfs/flooring/flooring_mud.dm
index fd2ccbaf7042..e7f26e889bec 100644
--- a/code/game/turfs/flooring/flooring_mud.dm
+++ b/code/game/turfs/flooring/flooring_mud.dm
@@ -34,6 +34,9 @@
return FALSE
return ..()
+/decl/flooring/mud/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle.vehicle_transit_type == vehicle::VEHICLE_SNOWMOBILE ? 1.4 : 1.5
+
/decl/flooring/dry_mud
name = "dry mud"
desc = "This was once mud, but forgot to keep hydrated."
@@ -48,6 +51,9 @@
force_material = /decl/material/solid/soil
uid = "floor_dry_mud"
+/decl/flooring/dry_mud/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return 1
+
/decl/flooring/dry_mud/fluid_act(turf/floor/target, datum/reagents/fluids)
if(target.get_topmost_flooring() == src)
target.set_flooring(/decl/flooring/mud)
@@ -70,6 +76,7 @@
force_material = /decl/material/solid/soil
growth_value = 1
uid = "floor_dirt"
+ can_conceal_hazards = TRUE
/decl/flooring/dirt/fluid_act(turf/floor/target, datum/reagents/fluids)
if(target.get_topmost_flooring() == src)
@@ -79,3 +86,6 @@
target.set_base_flooring(/decl/flooring/mud)
. = TRUE
return . || ..()
+
+/decl/flooring/dirt/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return 1
diff --git a/code/game/turfs/flooring/flooring_natural.dm b/code/game/turfs/flooring/flooring_natural.dm
index d05fdf330100..932289909a91 100644
--- a/code/game/turfs/flooring/flooring_natural.dm
+++ b/code/game/turfs/flooring/flooring_natural.dm
@@ -73,6 +73,9 @@
force_material = /decl/material/solid/ice
uid = "floor_ice"
+/decl/flooring/ice/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle.vehicle_transit_type == vehicle::VEHICLE_SNOWMOBILE ? 0.8 : ..()
+
/decl/flooring/ice/update_turf_icon(turf/floor/target)
. = ..()
if(istype(target))
diff --git a/code/game/turfs/flooring/flooring_rock.dm b/code/game/turfs/flooring/flooring_rock.dm
index 95a57226afe1..94227641346c 100644
--- a/code/game/turfs/flooring/flooring_rock.dm
+++ b/code/game/turfs/flooring/flooring_rock.dm
@@ -15,3 +15,6 @@
ASSERT(turf_material?.adjective_name)
target.SetName("[turf_material.adjective_name] [name]")
target.desc = "An expanse of bare [turf_material.solid_name]."
+
+/decl/flooring/rock/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle.vehicle_transit_type == vehicle::VEHICLE_SNOWMOBILE ? 1.5 : ..()
diff --git a/code/game/turfs/flooring/flooring_snow.dm b/code/game/turfs/flooring/flooring_snow.dm
index 6b28bb27374a..72362928c06e 100644
--- a/code/game/turfs/flooring/flooring_snow.dm
+++ b/code/game/turfs/flooring/flooring_snow.dm
@@ -13,6 +13,7 @@
print_type = /obj/effect/footprints
drop_material_on_remove = TRUE
uid = "floor_snow"
+ can_conceal_hazards = TRUE
/decl/flooring/snow/get_movement_delay(var/travel_dir, var/mob/mover)
. = ..()
@@ -47,6 +48,9 @@
return FALSE
return ..()
+/decl/flooring/snow/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ return vehicle.vehicle_transit_type == vehicle::VEHICLE_SNOWMOBILE ? 0.8 : 1.7
+
/decl/flooring/permafrost
name = "permafrost"
desc = "A stretch of frozen soil that hasn't seen a thaw for many seasons."
@@ -55,8 +59,16 @@
force_material = /decl/material/solid/ice
uid = "floor_permafrost"
+/decl/flooring/permafrost/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ if(holographic)
+ return vehicle::base_speed
+ if(vehicle.vehicle_transit_type == vehicle::VEHICLE_SNOWMOBILE)
+ return 0.8
+ return ..()
+
/decl/flooring/snow/fake
name = "holosnow"
desc = "Not quite the same as snow on an entertainment terminal, but close."
holographic = TRUE
uid = "floor_snow_fake"
+
diff --git a/code/game/turfs/floors/_floor.dm b/code/game/turfs/floors/_floor.dm
index 6739729434c2..ece70741f5c2 100644
--- a/code/game/turfs/floors/_floor.dm
+++ b/code/game/turfs/floors/_floor.dm
@@ -199,3 +199,9 @@
/turf/floor/can_show_coating_footprints(decl/material/contaminant = null)
return ..() && get_topmost_flooring()?.can_show_coating_footprints(src, contaminant)
+
+/turf/floor/proc/get_vehicle_transit_delay(obj/vehicle/vehicle)
+ var/decl/flooring/terrain = get_topmost_flooring()
+ if(!istype(vehicle) || QDELETED(vehicle) || !istype(terrain) || vehicle.vehicle_transit_type == vehicle::VEHICLE_GENERIC)
+ return vehicle::base_speed
+ return terrain.get_vehicle_transit_delay(vehicle)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index fdc9684e137f..d2b367402d54 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -7,6 +7,9 @@
temperature_sensitive = TRUE
atom_flags = ATOM_FLAG_OPEN_CONTAINER
+ // Linear lazylist of weakrefs to dangerous things on this turf.
+ var/list/dangerous_objects
+
/// Will participate in ZAS, join zones, etc.
var/zone_membership_candidate = FALSE
/// Will participate in external atmosphere simulation if the turf is outside and no zone is set.
@@ -937,3 +940,24 @@
/turf/take_vaporized_reagent(reagent, amount)
return assume_gas(reagent, round(amount / REAGENT_UNITS_PER_GAS_MOLE))
+
+// Tells the turf that it currently contains something that automated movement should consider if planning to enter the tile.
+// This uses lazy list macros to reduce memory footprint, since for 99% of turfs the list would've been empty anyways.
+/turf/proc/register_dangerous_object(atom/thing)
+ if(!istype(thing))
+ return FALSE
+ LAZYDISTINCTADD(dangerous_objects, weakref(thing))
+
+// Similar to above, for when the dangerous object stops being dangerous/gets deleted/moved/etc.
+/turf/proc/unregister_dangerous_object(atom/thing)
+ if(!istype(thing))
+ return FALSE
+ LAZYREMOVE(dangerous_objects, weakref(thing))
+
+/turf/proc/is_safe_to_enter(mob/living/stepper)
+ if(LAZYLEN(dangerous_objects))
+ for(var/weakref/ref in dangerous_objects)
+ var/atom/thing = ref.resolve()
+ if(istype(thing) && !QDELETED(thing) && !thing.is_safe_to_step(stepper))
+ return FALSE
+ return TRUE
diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm
index 5c849feec9a2..ee3311a90cb5 100644
--- a/code/game/turfs/turf_changing.dm
+++ b/code/game/turfs/turf_changing.dm
@@ -80,6 +80,7 @@
var/old_ambient_light_old_r = ambient_light_old_r
var/old_ambient_light_old_g = ambient_light_old_g
var/old_ambient_light_old_b = ambient_light_old_b
+ var/old_dangerous_objects = dangerous_objects
var/old_zone_membership_candidate = zone_membership_candidate
@@ -104,6 +105,7 @@
// Set our observation bookkeeping lists back.
changed_turf.event_listeners = old_event_listeners
changed_turf._listening_to = old_listening_to
+ changed_turf.dangerous_objects = old_dangerous_objects
changed_turf.affecting_heat_sources = old_affecting_heat_sources
diff --git a/code/game/turfs/walls/_wall.dm b/code/game/turfs/walls/_wall.dm
index 1d26dbb74a65..3706a99fcfa1 100644
--- a/code/game/turfs/walls/_wall.dm
+++ b/code/game/turfs/walls/_wall.dm
@@ -129,8 +129,10 @@ var/global/list/wall_fullblend_objects = list(
var/proj_damage = Proj.get_structure_damage()
- if(Proj.ricochet_sounds && prob(15))
- playsound(src, pick(Proj.ricochet_sounds), 100, 1)
+ if(prob(15))
+ var/list/ricochet_sounds = Proj.get_ricochet_sounds()
+ if(length(ricochet_sounds))
+ playsound(src, pick(ricochet_sounds), 100, 1)
if(reinf_material)
if(Proj.atom_damage_type == BURN)
diff --git a/code/modules/admin/verbs/grief_fixers.dm b/code/modules/admin/verbs/grief_fixers.dm
index 9288165c9b4a..7fd10e57eb41 100644
--- a/code/modules/admin/verbs/grief_fixers.dm
+++ b/code/modules/admin/verbs/grief_fixers.dm
@@ -55,9 +55,8 @@
/decl/atmos_grief_fix_step/reset_turfs/act()
var/list/unsorted_overlays = list()
- var/list/all_gasses = decls_repository.get_decls_of_subtype(/decl/material/gas)
- for(var/id in all_gasses)
- var/decl/material/mat = all_gasses[id]
+ for(var/id,m in get_filterable_material_types())
+ var/decl/material/mat = m
unsorted_overlays |= mat.gas_tile_overlay
for(var/turf/T in world)
diff --git a/code/modules/atmospherics/components/unary/vent_scrubber.dm b/code/modules/atmospherics/components/unary/vent_scrubber.dm
index 2a2aac82fd5e..58fdc8f1717e 100644
--- a/code/modules/atmospherics/components/unary/vent_scrubber.dm
+++ b/code/modules/atmospherics/components/unary/vent_scrubber.dm
@@ -64,7 +64,7 @@
id_tag = "[sequential_id("obj/machinery")]"
if(!scrubbing_gas)
scrubbing_gas = list()
- for(var/g in decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ for(var/g in get_filterable_material_types())
if(g != /decl/material/gas/oxygen && g != /decl/material/gas/nitrogen)
scrubbing_gas += g
. = ..()
diff --git a/code/modules/client/preference_setup/loadout/lists/footwear.dm b/code/modules/client/preference_setup/loadout/lists/footwear.dm
index 72760aca59c6..b9990a778bac 100644
--- a/code/modules/client/preference_setup/loadout/lists/footwear.dm
+++ b/code/modules/client/preference_setup/loadout/lists/footwear.dm
@@ -26,7 +26,8 @@
/obj/item/clothing/shoes/workboots,
/obj/item/clothing/shoes/jackboots/duty,
/obj/item/clothing/shoes/jackboots/jungleboots,
- /obj/item/clothing/shoes/jackboots/desertboots
+ /obj/item/clothing/shoes/jackboots/desertboots,
+ /obj/item/clothing/shoes/winterboots
)
/decl/loadout_option/shoes/color
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index 50adae6297ca..eb812a3b3b06 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -332,8 +332,14 @@ CHAMELEON_VERB(/obj/item/clothing/chameleon, "Change Accessory Appearance")
P.icon = initial(copy_projectile.icon)
P.icon_state = initial(copy_projectile.icon_state)
P.pass_flags = initial(copy_projectile.pass_flags)
+ P.fire_sound = initial(copy_projectile.fire_sound)
+ P.silenced = copy_projectile.silenced
+ P.hitsound = initial(copy_projectile.hitsound)
+ P.hitsound_non_mob = initial(copy_projectile.hitsound_non_mob)
P.hitscan = initial(copy_projectile.hitscan)
P.step_delay = initial(copy_projectile.step_delay)
+ P.speed = initial(copy_projectile.speed)
+ P.range = initial(copy_projectile.range)
P.muzzle_type = initial(copy_projectile.muzzle_type)
P.tracer_type = initial(copy_projectile.tracer_type)
P.impact_type = initial(copy_projectile.impact_type)
diff --git a/code/modules/clothing/permits/_permit.dm b/code/modules/clothing/permits/_permit.dm
index 1f42fce9e5cf..e3bb8aadda70 100644
--- a/code/modules/clothing/permits/_permit.dm
+++ b/code/modules/clothing/permits/_permit.dm
@@ -66,11 +66,6 @@
name = "bar shotgun permit"
desc = "A card indicating that the owner is allowed to carry a shotgun in the bar."
-/obj/item/clothing/permit/gun/planetside
- name = "planetside weapon permit"
- desc = "A card indicating that the owner is allowed to carry a weapon while on the surface."
- detail_color = COLOR_PALE_PINK
-
/obj/item/clothing/permit/gun/paramedic
name = "paramedic weapon permit"
desc = "A card indicating that the owner is allowed to carry a weapon while on EVA retrieval missions."
@@ -80,7 +75,3 @@
name = "holy weapon permit"
desc = "A card indicating that the owner is allowed to carry a weapon for religious rites and purposes."
detail_color = COLOR_GRAY15
-
-/obj/item/clothing/permit/gun/planetside/exploration
- name = "explorer weapon permit"
- desc = "A card indicating that the owner is allowed to carry weaponry during active exploration missions."
\ No newline at end of file
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 589a7f0d79e1..8b90dd6119a0 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -17,6 +17,7 @@
fallback_slot = slot_shoes_str
_base_attack_force = 5
+ var/rock_climbing = FALSE
var/can_fit_under_magboots = TRUE
var/can_add_cuffs = TRUE
var/obj/item/handcuffs/attached_cuffs = null
diff --git a/code/modules/clothing/suits/_suit_hood.dm b/code/modules/clothing/suits/_suit_hood.dm
index dc9dd9cad93a..42e02944dd17 100644
--- a/code/modules/clothing/suits/_suit_hood.dm
+++ b/code/modules/clothing/suits/_suit_hood.dm
@@ -1,21 +1,6 @@
/obj/item/clothing/suit
var/obj/item/clothing/head/hood
-/obj/item/clothing/suit/get_hood()
- if(istype(hood))
- return hood
- return ..()
-
-/obj/item/clothing/suit/set_material(new_material)
- . = ..()
- if(. && istype(hood))
- hood.set_material(new_material)
-
-/obj/item/clothing/suit/set_color(new_color)
- . = ..()
- if(. && istype(hood))
- hood.set_color(new_color)
-
/obj/item/clothing/suit/Initialize()
if(ispath(hood))
hood = new hood(src)
@@ -41,6 +26,26 @@
QDEL_NULL(hood)
return ..()
+/obj/item/clothing/suit/get_contained_external_atoms()
+ . = ..()
+ if(hood && .)
+ LAZYREMOVE(., hood)
+
+/obj/item/clothing/suit/get_hood()
+ if(istype(hood))
+ return hood
+ return ..()
+
+/obj/item/clothing/suit/set_material(new_material)
+ . = ..()
+ if(. && istype(hood))
+ hood.set_material(new_material)
+
+/obj/item/clothing/suit/set_color(new_color)
+ . = ..()
+ if(. && istype(hood))
+ hood.set_color(new_color)
+
/obj/item/clothing/suit/equipped(mob/user, slot)
if(slot != slot_wear_suit_str)
remove_hood()
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index 39f8dc118d92..b32701c519b4 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -8,6 +8,37 @@ var/global/regex/actual_error_file_line
log_world("\[[time_stamp()]] Uncaught exception: [E]")
return ..()
+ //this is snowflake because of a byond bug (ID:2306577), do not attempt to call non-builtin procs in this block OR BEFORE IT
+ if(copytext(E.name, 1, 32) == "Maximum recursion level reached")//32 == length() of that string + 1
+ var/list/proc_path_to_count = list()
+ var/crashed = FALSE
+ try
+ var/callee/stack_entry = caller
+ while(!isnull(stack_entry))
+ proc_path_to_count[stack_entry.proc] += 1
+ stack_entry = stack_entry.caller
+ catch
+ //union job. avoids crashing the stack again
+ //I just do not trust this construct to work reliably
+ crashed = TRUE
+
+ var/list/split = splittext(E.desc, "\n")
+ for (var/i in 1 to split.len)
+ if (split[i] != "" || copytext(split[1], 1, 2) != " ")
+ split[i] = " [split[i]]"
+ split += "--Stack Info [crashed ? "(Crashed, may be missing info)" : ""]:"
+ for(var/path in proc_path_to_count)
+ split += " [path] = [proc_path_to_count[path]]"
+ E.desc = jointext(split, "\n")
+ // expanding the first line of log_world to avoid hitting the stack limit again
+ to_file(world.log, "\[[time2text(station_time_in_ticks, "hh:mm:ss")]] Runtime Error: [E.name]\n[E.desc]")
+ //log to world while intentionally triggering the byond bug. this does not DO anything, it just errors
+ //(seemingly because log_info_line is deep enough to hit the raised limit 516.1667 introduced)
+ log_world("runtime error: [E.name]\n[E.desc]\n[log_info_line(usr)]\n[log_info_line(src)]")
+ //if we got to here without silently ending, the byond bug has been fixed.
+ log_world("The \"bug\" with recursion runtimes has been fixed. Please remove the snowflake check from world/Error in [__FILE__]:[__LINE__]")
+ return //this will never happen.
+
if (!global.actual_error_file_line)
global.actual_error_file_line = regex("^%% (.*?),(.*?) %% ")
diff --git a/code/modules/fabrication/designs/general/designs_arms_ammo.dm b/code/modules/fabrication/designs/general/designs_arms_ammo.dm
index eb92c1ef0f3f..2fd82b739386 100644
--- a/code/modules/fabrication/designs/general/designs_arms_ammo.dm
+++ b/code/modules/fabrication/designs/general/designs_arms_ammo.dm
@@ -81,4 +81,8 @@
/datum/fabricator_recipe/arms_ammo/hidden/speedloader_laser
name = "ammunition (speedloader, laserbulb)"
- path = /obj/item/ammo_magazine/speedloader/laser_revolver
\ No newline at end of file
+ path = /obj/item/ammo_magazine/speedloader/laser_revolver
+
+/datum/fabricator_recipe/arms_ammo/hidden/mine_assembly
+ name = "mine assembly"
+ path = /obj/item/mine/assembly
diff --git a/code/modules/fabrication/designs/general/designs_devices_components.dm b/code/modules/fabrication/designs/general/designs_devices_components.dm
index aa961aef563e..dd732867b353 100644
--- a/code/modules/fabrication/designs/general/designs_devices_components.dm
+++ b/code/modules/fabrication/designs/general/designs_devices_components.dm
@@ -5,9 +5,6 @@
/datum/fabricator_recipe/device_component/keyboard
path = /obj/item/stock_parts/keyboard
-/datum/fabricator_recipe/device_component/cataloguer
- path = /obj/item/cataloguer
-
/datum/fabricator_recipe/device_component/pda
path = /obj/item/modular_computer/pda
diff --git a/code/modules/item_effects/item_effect_charges.dm b/code/modules/item_effects/item_effect_charges.dm
index 06e8126b16fe..ff68fdb348eb 100644
--- a/code/modules/item_effects/item_effect_charges.dm
+++ b/code/modules/item_effects/item_effect_charges.dm
@@ -12,52 +12,6 @@
/decl/item_effect/charges/on_examined(obj/item/item, mob/user)
to_chat(user, SPAN_NOTICE("\The [item] has [item.get_item_effect_parameter(src, IE_CAT_RANGED, IE_PAR_USES) || 0] charge\s of [effect_descriptor] left."))
-/obj/item/projectile/fireball
- name = "fireball"
- icon_state = "fireball"
- fire_sound = 'sound/effects/bamf.ogg'
- damage = 20
- atom_damage_type = BURN
- damage_flags = DAM_DISPERSED // burn all over
- var/fire_lifetime = 2 SECONDS
- var/fire_temperature = (288 CELSIUS) / 0.9 + 1 // hot enough to ignite wood! divided by 0.9 and plus one to ensure we can light firepits
-
-/obj/effect/fake_fire/variable
- name = "fire"
- anchored = TRUE
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
- firelevel = 1
- pressure = ONE_ATMOSPHERE
-
-/obj/effect/fake_fire/variable/Initialize(ml, new_temperature, new_lifetime)
- lifetime = new_lifetime
- last_temperature = new_temperature
- return ..()
-
-// we deal our damage via fire_act, not via direct burn damage. our burn damage is specifically for mobs
-/obj/item/projectile/fireball/get_structure_damage()
- return 0
-
-/obj/item/projectile/fireball/on_impact(var/atom/A)
- . = ..()
- var/obj/effect/fake_fire/fire = new /obj/effect/fake_fire/variable(get_turf(A), fire_temperature, fire_lifetime)
- fire.Process() // process at least once!
- qdel_self()
-
-/obj/item/projectile/fireball/after_move()
- . = ..()
- if(!loc)
- return
- for(var/mob/living/victim in loc)
- if(!victim.simulated)
- continue
- victim.FireBurn(1, fire_temperature, ONE_ATMOSPHERE)
- loc.fire_act(1, fire_temperature, ONE_ATMOSPHERE)
- for(var/atom/burned in loc)
- if(!burned.simulated || burned == src)
- continue
- burned.fire_act(1, fire_temperature, ONE_ATMOSPHERE)
-
// Example effect; casts a fireball n times.
/decl/item_effect/charges/fireball
effect_descriptor = "fireball"
diff --git a/code/modules/lighting/_lighting_defs.dm b/code/modules/lighting/_lighting_defs.dm
index f43ef762a6b3..a49d409fba9a 100644
--- a/code/modules/lighting/_lighting_defs.dm
+++ b/code/modules/lighting/_lighting_defs.dm
@@ -7,27 +7,39 @@
// As such this all gets counted as a single line.
// The braces and semicolons are there to be able to do this on a single line.
-#define APPLY_CORNER(C,now,Tx,Ty,hdiff) \
- . = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
- var/OLD = effect_str[C]; \
- effect_str[C] = .; \
- C.update_lumcount \
- ( \
- (. * lum_r) - (OLD * applied_lum_r), \
- (. * lum_g) - (OLD * applied_lum_g), \
- (. * lum_b) - (OLD * applied_lum_b), \
- now \
+#define APPLY_CORNER(C,now,Tx,Ty,hdiff) \
+ . = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
+ var/OLD = effect_str[C]; \
+ effect_str[C] = .; \
+ C.update_lumcount \
+ ( \
+ (. * lum_r) - (OLD * applied_lum_r), \
+ (. * lum_g) - (OLD * applied_lum_g), \
+ (. * lum_b) - (OLD * applied_lum_b), \
+ now \
+ );
+
+// Like APPLY_CORNER, but very slightly faster, for the cases where we know there's no previous value.
+#define INIT_CORNER(C,now,Tx,Ty,hdiff) \
+ . = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
+ effect_str[C] = .; \
+ C.update_lumcount \
+ ( \
+ . * lum_r, \
+ . * lum_g, \
+ . * lum_b, \
+ now \
);
// I don't need to explain what this does, do I?
-#define REMOVE_CORNER(C,now) \
- . = -effect_str[C]; \
- C.update_lumcount \
- ( \
- . * applied_lum_r, \
- . * applied_lum_g, \
- . * applied_lum_b, \
- now \
+#define REMOVE_CORNER(C,now) \
+ . = -effect_str[C]; \
+ C.update_lumcount \
+ ( \
+ . * applied_lum_r, \
+ . * applied_lum_g, \
+ . * applied_lum_b, \
+ now \
);
// Converts two Z levels into a height value for LUM_FALLOFF or HEIGHT_FALLOFF.
@@ -41,3 +53,12 @@
corner_height = LIGHTING_HEIGHT; \
} \
APPLY_CORNER(C, now, Sx, Sy, corner_height);
+
+#define INIT_CORNER_BY_HEIGHT(now) \
+ if (C.z != Sz) { \
+ corner_height = CALCULATE_CORNER_HEIGHT(C.z, Sz); \
+ } \
+ else { \
+ corner_height = LIGHTING_HEIGHT; \
+ } \
+ INIT_CORNER(C, now, Sx, Sy, corner_height);
\ No newline at end of file
diff --git a/code/modules/lighting/ambient_turf.dm b/code/modules/lighting/ambient_turf.dm
index 4b34c863ed90..60992f1fcb5d 100644
--- a/code/modules/lighting/ambient_turf.dm
+++ b/code/modules/lighting/ambient_turf.dm
@@ -4,9 +4,6 @@
/// The power of the above is multiplied by this. Setting too high may drown out normal lights on the same turf.
var/ambient_light_multiplier = 0.3
- /// If this is TRUE, an above turf's ambient light is affecting this turf.
- var/tmp/ambient_has_indirect = FALSE
-
// Record-keeping, do not touch -- that means you, admins.
var/tmp/ambient_active = FALSE //! Do we have non-zero ambient light? Use [TURF_IS_AMBIENT_LIT] instead of reading this directly.
var/tmp/ambient_light_old_r = 0
diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm
index 639b0a023872..ea6f99e9a915 100644
--- a/code/modules/lighting/lighting_corner.dm
+++ b/code/modules/lighting/lighting_corner.dm
@@ -272,8 +272,6 @@ var/global/list/REVERSE_LIGHTING_CORNER_DIAGONAL = list(0, 0, 0, 0, 3, 4, 0, 0,
// We init before Z-Mimic, cannot rely on above/below.
while ((T = GET_BELOW(T)) && ((below.t1?.z_flags | below.t2?.z_flags | below.t3?.z_flags | below.t4?.z_flags) & ZM_ALLOW_LIGHTING) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
- T.ambient_has_indirect = TRUE
-
if (!T.corners || !T.corners[Ti])
T.generate_missing_corners()
diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm
index 5ebc210383ea..84bccc897573 100644
--- a/code/modules/lighting/lighting_source.dm
+++ b/code/modules/lighting/lighting_source.dm
@@ -225,7 +225,7 @@
REMOVE_CORNER(C,now)
effect_str[C] = 0
- var/actual_range = light_range
+ var/actual_range = (light_angle && facing_opaque) ? light_range * LIGHTING_BLOCKED_FACTOR : light_range
var/Sx = pixel_turf.x
var/Sy = pixel_turf.y
@@ -335,7 +335,6 @@
var/list/datum/lighting_corner/corners = list()
var/list/turf/turfs = list()
- var/thing
var/datum/lighting_corner/C
var/turf/T
var/list/Tcorners
@@ -358,8 +357,9 @@
if ((DETERMINANT(limit_a_x, limit_a_y, test_x, test_y) > 0) || DETERMINANT(test_x, test_y, limit_b_x, limit_b_y) > 0)
continue
- if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_source_solo || T.light_source_multi)
- Tcorners = T.corners
+ Tcorners = T.corners
+ // These checks are inlined from generate_missing_corners. They must be kept in sync.
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_source_solo || T.light_source_multi || (T.z_flags & ZM_ALLOW_LIGHTING))
if (!T.lighting_corners_initialised)
T.lighting_corners_initialised = TRUE
@@ -373,11 +373,11 @@
Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i], i)
- if (!T.has_opaque_atom)
- for (var/v in 1 to 4)
- var/val = Tcorners[v]
- if (val)
- corners[val] = 0
+ if (Tcorners && !T.has_opaque_atom)
+ for (var/v in 1 to 4)
+ var/val = Tcorners[v]
+ if (val)
+ corners[val] = 0
turfs += T
@@ -390,39 +390,34 @@
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
affecting_turfs += L
- for (thing in L)
- T = thing
+ for (T as anything in L)
LAZYADD(T.affecting_lights, src)
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
affecting_turfs -= L
- for (thing in L)
- T = thing
+ for (T as anything in L)
LAZYREMOVE(T.affecting_lights, src)
LAZYINITLIST(effect_str)
if (needs_update == LIGHTING_VIS_UPDATE)
- for (thing in corners - effect_str)
- C = thing
+ for (C as anything in corners - effect_str) // newly added corners
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
- APPLY_CORNER_BY_HEIGHT(now)
+ INIT_CORNER_BY_HEIGHT(now)
else
L = corners - effect_str
- for (thing in L)
- C = thing
+ for (C as anything in L)
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
- APPLY_CORNER_BY_HEIGHT(now)
+ INIT_CORNER_BY_HEIGHT(now)
- for (thing in corners - L)
- C = thing
+ for (C as anything in corners - L)
if (!C.active)
effect_str[C] = 0
continue
@@ -430,8 +425,7 @@
APPLY_CORNER_BY_HEIGHT(now)
L = effect_str - corners
- for (thing in L)
- C = thing
+ for (C as anything in L)
REMOVE_CORNER(C, now)
LAZYREMOVE(C.affecting, src)
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index 08d7fadca5c6..5543d1166c1b 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -143,7 +143,8 @@
// This is inlined in lighting_source.dm.
// Update it too if you change this.
/turf/proc/generate_missing_corners()
- if (!TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) && !light_source_solo && !light_source_multi && !(z_flags & ZM_ALLOW_LIGHTING) && !ambient_light && !ambient_has_indirect)
+ // If a turf is dynamically lit, has a light source, or mimics lighting, it needs to have corners created.
+ if (!TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) && !light_source_solo && !light_source_multi && !(z_flags & ZM_ALLOW_LIGHTING))
return
lighting_corners_initialised = TRUE
diff --git a/code/modules/maps/template_types/random_exoplanet/fauna_generator.dm b/code/modules/maps/template_types/random_exoplanet/fauna_generator.dm
index c3b05683b86b..08cbad00e174 100644
--- a/code/modules/maps/template_types/random_exoplanet/fauna_generator.dm
+++ b/code/modules/maps/template_types/random_exoplanet/fauna_generator.dm
@@ -126,7 +126,7 @@
/datum/fauna_generator/proc/generate_breathable_gases(var/datum/gas_mixture/atmosphere, var/list/breath_gases, var/list/toxic_gases)
//Set up gases for living things
- var/list/all_gasses = decls_repository.get_decl_paths_of_subtype(/decl/material/gas)
+ var/list/all_gasses = get_filterable_material_types(as_list = TRUE)
if(!length(breath_gases))
var/list/goodgases = all_gasses.Copy()
var/gasnum = min(rand(1,3), goodgases.len)
diff --git a/code/modules/maps/template_types/random_exoplanet/planet_themes/robotic_guardians.dm b/code/modules/maps/template_types/random_exoplanet/planet_themes/robotic_guardians.dm
index 3e8c60a5d1a7..d9d6608eac8a 100644
--- a/code/modules/maps/template_types/random_exoplanet/planet_themes/robotic_guardians.dm
+++ b/code/modules/maps/template_types/random_exoplanet/planet_themes/robotic_guardians.dm
@@ -2,7 +2,7 @@
name = "Robotic Guardians"
var/list/guardian_types = list(
/mob/living/simple_animal/hostile/hivebot,
- /mob/living/simple_animal/hostile/hivebot/range,
+ /mob/living/simple_animal/hostile/hivebot/ranged,
/mob/living/simple_animal/hostile/viscerator/hive
)
var/list/mega_guardian_types = list(
@@ -24,4 +24,4 @@
A.faction = "Ancient Guardian"
/datum/exoplanet_theme/robotic_guardians/get_sensor_data()
- return "Movement without corresponding lifesigns detected on the surface."
\ No newline at end of file
+ return "Movement without corresponding lifesigns detected on the surface."
diff --git a/code/modules/materials/_materials.dm b/code/modules/materials/_materials.dm
index 2ed880fd3b89..920e63862dc2 100644
--- a/code/modules/materials/_materials.dm
+++ b/code/modules/materials/_materials.dm
@@ -1,3 +1,20 @@
+var/global/alist/_filterable_mats_alist
+var/global/list/_filterable_mats_list
+
+/proc/get_filterable_material_types(as_list = FALSE)
+
+ if(isnull(_filterable_mats_alist))
+ _filterable_mats_alist = alist()
+ for(var/decl/material/mat in decls_repository.get_decls_of_subtype_unassociated(/decl/material))
+ if(!isnull(mat.boiling_point))
+ _filterable_mats_alist[mat.type] = mat
+
+ if(as_list)
+ if(isnull(_filterable_mats_list))
+ _filterable_mats_list = alist2list(_filterable_mats_alist)
+ return _filterable_mats_list
+ return _filterable_mats_alist
+
/*
MATERIAL DECLS
This data is used by various parts of the game for basic physical properties and behaviors
diff --git a/code/modules/materials/material_debris_fragment.dm b/code/modules/materials/material_debris_fragment.dm
new file mode 100644
index 000000000000..d34e944b6dc3
--- /dev/null
+++ b/code/modules/materials/material_debris_fragment.dm
@@ -0,0 +1,47 @@
+/obj/item/debris/salvage
+ abstract_type = /obj/item/debris/salvage
+ icon_state = ICON_STATE_WORLD
+ material_alteration = MAT_FLAG_ALTERATION_NONE
+ is_spawnable_type = TRUE
+
+/obj/item/debris/salvage/metal
+ name = "fragment"
+ desc = "A large, ragged chunk of some worked material."
+ icon = 'icons/obj/debris_metal.dmi'
+ material_alteration = MAT_FLAG_ALTERATION_ALL
+ material = /decl/material/solid/metal/steel
+
+/obj/item/debris/salvage/metal/Initialize(ml, material_key)
+ . = ..()
+ icon_state = "[icon_state][rand(0, 4)]"
+
+/obj/item/debris/salvage/metal/plasteel
+ material = /decl/material/solid/metal/plasteel
+
+/obj/item/debris/salvage/circuit
+ name = "broken circuit"
+ desc = "A burned-out circuitboard. Only good for the base materials now."
+ icon = 'icons/obj/debris_circuit.dmi'
+ material = /decl/material/solid/fiberglass
+ matter = list(
+ /decl/material/solid/organic/plastic = MATTER_AMOUNT_REINFORCEMENT,
+ /decl/material/solid/metal/gold = MATTER_AMOUNT_TRACE
+ )
+
+/obj/item/debris/salvage/circuit/Initialize(ml, material_key)
+ . = ..()
+ icon_state = "[icon_state][rand(0, 3)]"
+
+/obj/item/debris/salvage/device
+ name = "broken device"
+ desc = "A destroyed device of some kind. Only good for recycling now."
+ icon = 'icons/obj/debris_device.dmi'
+ material = /decl/material/solid/metal/aluminium
+ matter = list(
+ /decl/material/solid/fiberglass = MATTER_AMOUNT_REINFORCEMENT,
+ /decl/material/solid/metal/gold = MATTER_AMOUNT_TRACE
+ )
+
+/obj/item/debris/salvage/device/Initialize(ml, material_key)
+ . = ..()
+ icon_state = "[icon_state][rand(0, 3)]"
diff --git a/code/modules/mob/living/human/human.dm b/code/modules/mob/living/human/human.dm
index f83f689f0392..ee819f1252e6 100644
--- a/code/modules/mob/living/human/human.dm
+++ b/code/modules/mob/living/human/human.dm
@@ -1145,3 +1145,7 @@
robolimb_count++
full_prosthetic = robolimb_count > 0 && (robolimb_count == LAZYLEN(limbs)) //If no organs, no way to tell
return full_prosthetic
+
+// Don't tag your crewmates please.
+/mob/living/human/is_tagging_suitable()
+ return FALSE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 956ae903000c..b6f121f4403b 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2001,3 +2001,17 @@ default behaviour is:
/mob/living/is_cloaked()
return has_mob_modifier(/decl/mob_modifier/cloaked)
+
+/mob/living/proc/apply_random_mutation(radiation_amount)
+ set_unique_enzymes(num2text(random_id(/mob, 1000000, 9999999)))
+ if(prob(98))
+ add_genetic_condition(pick(decls_repository.get_decls_of_type(/decl/genetic_condition/disability)))
+ else
+ add_genetic_condition(pick(decls_repository.get_decls_of_type(/decl/genetic_condition/superpower)))
+ if(radiation_amount)
+ apply_damage(radiation_amount, IRRADIATE, armor_pen = 100)
+
+// Used by specimen taggers to avoid tagging/overwriting players or named mobs like Runtime.
+/mob/living/proc/is_tagging_suitable()
+ return !key && !client
+
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index a870b6c2c263..e94c963c06fd 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -28,7 +28,7 @@
visible_message(SPAN_WARNING("[src] triggers their deadman's switch!"))
signaler.signal()
//Armor
- var/damage = P.damage
+ var/damage = P.get_projectile_damage(src)
var/flags = P.damage_flags()
var/damaged
if(!P.nodamage)
@@ -42,7 +42,8 @@
// For visuals and blood splatters etc
/mob/living/proc/bullet_impact_visuals(var/obj/item/projectile/P, var/def_zone, var/damage)
- var/list/impact_sounds = LAZYACCESS(P.impact_sounds, get_bullet_impact_effect_type(def_zone))
+ var/list/all_impact_sounds = P.get_impact_sounds()
+ var/list/impact_sounds = LAZYACCESS(all_impact_sounds, get_bullet_impact_effect_type(def_zone))
if(length(impact_sounds))
playsound(src, pick(impact_sounds), 75)
if(get_bullet_impact_effect_type(def_zone) != BULLET_IMPACT_MEAT)
diff --git a/code/modules/mob/living/silicon/ai/ai_radio.dm b/code/modules/mob/living/silicon/ai/ai_radio.dm
index 7eff03a67438..7a195abc9bac 100644
--- a/code/modules/mob/living/silicon/ai/ai_radio.dm
+++ b/code/modules/mob/living/silicon/ai/ai_radio.dm
@@ -23,6 +23,5 @@
access_medical,
access_cargo,
access_bar,
- access_ai_upload,
- access_explorer
+ access_ai_upload
)
diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm
index c8da4e16f144..de977e45c639 100644
--- a/code/modules/mob/living/silicon/robot/robot_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_items.dm
@@ -193,7 +193,7 @@
name = "personal shielding"
desc = "A powerful experimental module that turns aside or absorbs incoming attacks at the cost of charge."
icon = 'icons/obj/signs/warnings.dmi'
- icon_state = "shock"
+ icon_state = "shock-large"
var/shield_level = 0.5 //Percentage of damage absorbed by the shield.
/obj/item/borg/combat/shield/verb/set_shield_level()
@@ -209,7 +209,7 @@
name = "mobility module"
desc = "By retracting limbs and tucking in its head, a combat android can roll at high speeds."
icon = 'icons/obj/signs/warnings.dmi'
- icon_state = "shock"
+ icon_state = "shock-large"
/obj/item/inflatable_dispenser
name = "inflatables dispenser"
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 36f1ba370c4c..b5508d6d2217 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -127,9 +127,9 @@
if(!Proj.nodamage)
switch(Proj.atom_damage_type)
if(BRUTE)
- take_damage(Proj.damage)
+ take_damage(Proj.get_projectile_damage(src))
if(BURN)
- take_damage(Proj.damage, BURN)
+ take_damage(Proj.get_projectile_damage(src), BURN)
Proj.on_hit(src,100) //wow this is a terrible hack
return 100
diff --git a/code/modules/mob/living/simple_animal/_simple_animal.dm b/code/modules/mob/living/simple_animal/_simple_animal.dm
index a099671239ce..da62a72958eb 100644
--- a/code/modules/mob/living/simple_animal/_simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/_simple_animal.dm
@@ -1,4 +1,3 @@
-
/mob/living/simple_animal
name = "animal"
max_health = 20
@@ -72,9 +71,6 @@
var/bleed_colour = COLOR_BLOOD_HUMAN
var/can_bleed = TRUE
- // contained in a cage
- var/in_stasis = 0
-
//for simple animals with abilities, mostly megafauna
var/ability_cooldown
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 99e38a6baa01..cfaaab05eb04 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -194,6 +194,9 @@
butchery_data = /decl/butchery_data/animal/cat/black
holder_type = /obj/item/holder/runtime
+/mob/living/simple_animal/passive/cat/fluff/runtime/is_tagging_suitable()
+ return FALSE
+
/obj/item/holder/runtime
origin_tech = @'{"programming":1,"biotech":1}'
diff --git a/code/modules/mob/living/simple_animal/friendly/possum.dm b/code/modules/mob/living/simple_animal/friendly/possum.dm
index ef8c318ade27..9b77858189e3 100644
--- a/code/modules/mob/living/simple_animal/friendly/possum.dm
+++ b/code/modules/mob/living/simple_animal/friendly/possum.dm
@@ -106,6 +106,9 @@
can_buckle = TRUE
var/aaa_words = list("delaminat", "meteor", "fire", "breach")
+/mob/living/simple_animal/opossum/poppy/is_tagging_suitable()
+ return FALSE
+
/mob/living/simple_animal/opossum/poppy/hear_broadcast(decl/language/language, mob/speaker, speaker_name, message)
. = ..()
addtimer(CALLBACK(src, PROC_REF(check_keywords), message), rand(1 SECOND, 3 SECONDS))
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/_hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/_hivebot.dm
index d958928874ef..6566f241e2f0 100644
--- a/code/modules/mob/living/simple_animal/hostile/hivebots/_hivebot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hivebots/_hivebot.dm
@@ -1,11 +1,9 @@
/mob/living/simple_animal/hostile/hivebot
name = "hivebot"
desc = "A junky looking robot with four spiky legs."
- icon = 'icons/mob/simple_animal/hivebot.dmi'
+ icon = 'icons/mob/simple_animal/hivebots/hivebot_green.dmi'
max_health = 55
natural_weapon = /obj/item/natural_weapon/drone_slicer
- projectilesound = 'sound/weapons/gunshot/gunshot_pistol.ogg'
- projectiletype = /obj/item/projectile/beam/smalllaser
faction = "hivebot"
min_gas = null
max_gas = null
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm
index ead786ab340c..4dec711741ca 100644
--- a/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm
@@ -5,7 +5,7 @@
/mob/living/simple_animal/hostile/hivebot/mega
name = "hivemind"
desc = "A huge quadruped robot equipped with a myriad of weaponry."
- icon = 'icons/mob/simple_animal/megabot.dmi'
+ icon = 'icons/mob/simple_animal/hivebots/megabot.dmi'
max_health = 440
natural_weapon = /obj/item/natural_weapon/circular_saw
natural_armor = list(
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/melee/_melee.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/melee/_melee.dm
new file mode 100644
index 000000000000..42d3f588fbec
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/hivebots/melee/_melee.dm
@@ -0,0 +1,20 @@
+/mob/living/simple_animal/hostile/hivebot/melee
+ natural_weapon = /obj/item/natural_weapon/drone_slicer/prod
+ projectiletype = null // To force the AI to melee.
+ base_movement_delay = 10
+
+/obj/item/natural_weapon/drone_slicer/prod
+ name = "hivebot prod"
+ attack_verb = list("prodded")
+ hitsound = 'sound/weapons/Egloves.ogg'
+
+/mob/living/simple_animal/hostile/hivebot/melee/has_ranged_attack()
+ return FALSE
+
+// This one is tanky by having a massive amount of health.
+/mob/living/simple_animal/hostile/hivebot/melee/meatshield
+ name = "bulky hivebot"
+ desc = "A large robot."
+ max_health = 300
+ icon_scale_x = 1.1
+ icon_scale_y = 1.1
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/melee/armored.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/melee/armored.dm
new file mode 100644
index 000000000000..6556736ec9c0
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/hivebots/melee/armored.dm
@@ -0,0 +1,61 @@
+// This one is tanky by having armor.
+/mob/living/simple_animal/hostile/hivebot/melee/armored
+ name = "armored hivebot"
+ desc = "A robot clad in heavy armor."
+ icon = 'icons/mob/simple_animal/hivebots/hivebot_yellow.dmi'
+ max_health = 150
+ icon_scale_x = 1.1
+ icon_scale_y = 1.1
+ natural_armor = list(
+ (ARMOR_MELEE) = ARMOR_MELEE_MAJOR,
+ (ARMOR_BULLET) = ARMOR_BALLISTIC_PISTOL,
+ (ARMOR_LASER) = ARMOR_LASER_HANDGUNS,
+ (ARMOR_ENERGY) = ARMOR_ENERGY_RESISTANT,
+ (ARMOR_BOMB) = ARMOR_BOMB_PADDED,
+ (ARMOR_BIO) = ARMOR_BIO_SHIELDED,
+ (ARMOR_RAD) = ARMOR_RAD_SHIELDED
+ )
+
+/mob/living/simple_animal/hostile/hivebot/melee/armored/anti_melee
+ name = "riot hivebot"
+ desc = "A robot specialized in close quarters combat."
+ natural_armor = list(
+ (ARMOR_MELEE) = ARMOR_MELEE_VERY_HIGH,
+ (ARMOR_BIO) = ARMOR_BIO_SHIELDED,
+ (ARMOR_RAD) = ARMOR_RAD_SHIELDED
+ )
+
+/mob/living/simple_animal/hostile/hivebot/melee/armored/anti_bullet
+ name = "bulletproof hivebot"
+ desc = "A robot specialized in ballistic defense."
+ natural_armor = list(
+ (ARMOR_BULLET) = ARMOR_BALLISTIC_RIFLE,
+ (ARMOR_BIO) = ARMOR_BIO_SHIELDED,
+ (ARMOR_RAD) = ARMOR_RAD_SHIELDED
+ )
+
+/mob/living/simple_animal/hostile/hivebot/melee/armored/anti_laser
+ name = "ablative hivebot"
+ desc = "A robot specialized in photonic defense."
+ natural_armor = list(
+ (ARMOR_LASER) = ARMOR_LASER_RIFLES,
+ (ARMOR_BIO) = ARMOR_BIO_SHIELDED,
+ (ARMOR_RAD) = ARMOR_RAD_SHIELDED
+ )
+ var/reflect_chance = 40
+
+// Ablative Hivebots can reflect lasers just like humans.
+/mob/living/simple_animal/hostile/hivebot/melee/armored/anti_laser/bullet_act(obj/item/projectile/P)
+ if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam))
+ var/reflect_prob = reflect_chance - round(P.damage/3)
+ if(prob(reflect_prob))
+ visible_message(
+ SPAN_DANGER("\The [P] is reflected by \the [src]'s armor!"),
+ SPAN_DANGER("\The [P] gets reflected by \the [src]'s armor!")
+ )
+ if(P.starting)
+ var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ P.redirect(new_x, new_y, get_turf(src), src)
+ return PROJECTILE_CONTINUE
+ return (..(P))
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/range.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/range.dm
deleted file mode 100644
index 3d1ecb678aab..000000000000
--- a/code/modules/mob/living/simple_animal/hostile/hivebots/range.dm
+++ /dev/null
@@ -1,6 +0,0 @@
-/mob/living/simple_animal/hostile/hivebot/range
- desc = "A junky looking robot with four spiky legs. It's equipped with some kind of small-bore gun."
- base_movement_delay = 2
-
-/mob/living/simple_animal/hostile/hivebot/range/has_ranged_attack()
- return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/ranged/_ranged.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/ranged/_ranged.dm
new file mode 100644
index 000000000000..800b7b0fe90f
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/hivebots/ranged/_ranged.dm
@@ -0,0 +1,29 @@
+/mob/living/simple_animal/hostile/hivebot/ranged
+ desc = "A junky looking robot with four spiky legs. It's equipped with some kind of small-bore gun."
+ base_movement_delay = 2
+ icon = 'icons/mob/simple_animal/hivebots/hivebot_white.dmi'
+ projectiletype = /obj/item/projectile/bullet/pellet
+ projectilesound = 'sound/weapons/gunshot/gunshot_pistol.ogg'
+
+/mob/living/simple_animal/hostile/hivebot/ranged/has_ranged_attack()
+ return TRUE
+
+/mob/living/simple_animal/hostile/hivebot/ranged/rapid
+ name = "rapid hivebot"
+ desc = "A robot with a crude but deadly integrated rifle."
+ attack_delay = 5 // Two attacks a second or so.
+ burst_projectile = TRUE
+
+/mob/living/simple_animal/hostile/hivebot/ranged/laser
+ name = "laser hivebot"
+ desc = "A robot with a photonic weapon integrated into itself."
+ projectiletype = /obj/item/projectile/beam/blue
+ projectilesound = 'sound/weapons/Laser.ogg'
+
+/mob/living/simple_animal/hostile/hivebot/ranged/heat
+ name = "ember hivebot"
+ desc = "A robot that appears to utilize fire to cook their enemies."
+ icon_state = "red"
+ icon = 'icons/mob/simple_animal/hivebots/hivebot_red.dmi'
+ projectiletype = /obj/item/projectile/fireball
+ projectilesound = 'sound/effects/bamf.ogg'
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/rapid.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/rapid.dm
deleted file mode 100644
index 6cdc6ffbd322..000000000000
--- a/code/modules/mob/living/simple_animal/hostile/hivebots/rapid.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-/mob/living/simple_animal/hostile/hivebot/rapid
- burst_projectile = TRUE
-
-/mob/living/simple_animal/hostile/hivebot/rapid/has_ranged_attack()
- return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/strong.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/strong.dm
deleted file mode 100644
index 4105339c6bd1..000000000000
--- a/code/modules/mob/living/simple_animal/hostile/hivebots/strong.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-/mob/living/simple_animal/hostile/hivebot/strong
- desc = "A junky looking robot with four spiky legs - this one has thick armour plating."
- max_health = 120
- natural_armor = list(
- ARMOR_MELEE = ARMOR_MELEE_RESISTANT
- )
- ai = /datum/mob_controller/aggressive/hivebot_strong
-
-/datum/mob_controller/aggressive/hivebot_strong
- can_escape_buckles = TRUE
-
-/mob/living/simple_animal/hostile/hivebot/strong/has_ranged_attack()
- return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm
index 3e450fd82b2c..97345aeccd78 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm
@@ -4,7 +4,6 @@
name = "combat drone"
desc = "An automated combat drone armed with state of the art weaponry and shielding."
icon = 'icons/mob/simple_animal/drone_combat.dmi'
- burst_projectile = 0
max_health = 300
move_intents = list(
/decl/move_intent/walk/animal_slow,
diff --git a/code/modules/mob/living/simple_animal/passive/mouse.dm b/code/modules/mob/living/simple_animal/passive/mouse.dm
index 72d3815596e2..5139242d27ed 100644
--- a/code/modules/mob/living/simple_animal/passive/mouse.dm
+++ b/code/modules/mob/living/simple_animal/passive/mouse.dm
@@ -117,6 +117,9 @@
SetName(initial(name))
real_name = name
+/mob/living/simple_animal/passive/mouse/brown/Tom/is_tagging_suitable()
+ return FALSE
+
// rats, they're the rats (from Polaris)
/mob/living/simple_animal/passive/mouse/rat
name = "rat"
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index dadb2de1b99d..e0952f1222c5 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -713,7 +713,7 @@ var/global/const/ACTION_DANGER_ALL = 2
return TRUE
if(!anchored && istype(over, /obj/vehicle/train))
var/obj/vehicle/train/beep = over
- if(!beep.load(src))
+ if(!beep.load_onto_vehicle(src))
to_chat(user, SPAN_WARNING("You were unable to load \the [src] onto \the [over]."))
return TRUE
. = ..()
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 27ae2d426d93..360f26b75557 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -5,6 +5,16 @@
if(DoMove(direction, src) & MOVEMENT_HANDLED)
return TRUE // Doesn't necessarily mean the atom physically moved
+/mob/living/SelfMove(var/direction)
+ // If on walk intent, don't willingly step into hazardous tiles.
+ // Unless the walker is confused.
+ var/turf/destination = get_step(src, direction)
+ if(istype(destination) && MOVING_DELIBERATELY(src) && !HAS_STATUS(src, STAT_CONFUSE))
+ if(!destination.is_safe_to_enter(src))
+ to_chat(src, SPAN_WARNING("\The [destination] is dangerous to move into."))
+ return FALSE // In case any code wants to know if movement happened.
+ return ..() // Parent call should make the mob move.
+
/mob/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
. = current_posture.prone || ..() || !mover.density
diff --git a/code/modules/modular_computers/networking/network_cable.dm b/code/modules/modular_computers/networking/network_cable.dm
index 9522183087de..4ea853d32197 100644
--- a/code/modules/modular_computers/networking/network_cable.dm
+++ b/code/modules/modular_computers/networking/network_cable.dm
@@ -156,7 +156,8 @@
randpixel = 2
amount = 20
max_amount = 20
- singular_name = "length"
+ singular_name = "length of network cable"
+ plural_name = "lengths of network cable"
w_class = ITEM_SIZE_NORMAL
throw_speed = 2
throw_range = 5
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index 4240ed507b22..923bfe97a847 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -48,29 +48,27 @@
/mob/proc/can_overcome_gravity()
return FALSE
-/mob/living/human/can_overcome_gravity()
+/mob/living/can_overcome_gravity()
//First do species check
- if(species && species.can_overcome_gravity(src))
- return 1
- else
- var/turf/T = loc
- if(((T.get_physical_height() + T.get_fluid_depth()) >= FLUID_DEEP) || T.get_fluid_depth() >= FLUID_MAX_DEPTH)
- if(can_float())
- return 1
-
- for(var/atom/a in src.loc)
- if(a.atom_flags & ATOM_FLAG_CLIMBABLE)
- return 1
-
- //Last check, list of items that could plausibly be used to climb but aren't climbable themselves
- var/list/objects_to_stand_on = list(
- /obj/item/stool,
- /obj/structure/bed,
- )
- for(var/type in objects_to_stand_on)
- if(locate(type) in src.loc)
- return 1
- return 0
+ var/decl/species/my_species = get_species()
+ if(my_species?.can_overcome_gravity(src))
+ return TRUE
+ var/turf/T = loc
+ if(((T.get_physical_height() + T.get_fluid_depth()) >= FLUID_DEEP) || T.get_fluid_depth() >= FLUID_MAX_DEPTH)
+ if(can_float())
+ return TRUE
+ for(var/atom/climbable in src.loc)
+ if((climbable.atom_flags & ATOM_FLAG_CLIMBABLE) && climbable.can_climb(src, silent = TRUE))
+ return TRUE
+ //Last check, list of items that could plausibly be used to climb but aren't climbable themselves
+ var/static/list/objects_to_stand_on = list(
+ /obj/item/stool,
+ /obj/structure/bed,
+ )
+ for(var/type in objects_to_stand_on)
+ if(locate(type) in src.loc)
+ return TRUE
+ return FALSE
//FALLING STUFF
diff --git a/code/modules/persistence/persistence_datum.dm b/code/modules/persistence/persistence_datum.dm
index 08acd039c57a..fe95bf8248f3 100644
--- a/code/modules/persistence/persistence_datum.dm
+++ b/code/modules/persistence/persistence_datum.dm
@@ -11,6 +11,8 @@
var/has_admin_data // If set, shows up on the admin persistence panel.
var/ignore_area_flags = FALSE // Set to TRUE to skip area flag checks such as nonpersistent areas.
var/ignore_invalid_loc = FALSE // Set to TRUE to skip checking for a non-null station turf for the entry.
+ var/area_restricted = TRUE // Can this item persist outside of a flagged area?
+ var/station_restricted = TRUE // Can this item persist outside of a station level?
/decl/persistence_handler/proc/SetFilename()
if(name)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index c1ad6452b563..7067ecc7974b 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -515,7 +515,8 @@ var/global/list/obj/structure/cable/all_cables = list()
color = COLOR_MAROON
paint_color = COLOR_MAROON
desc = "A coil of wiring, suitable for both delicate electronics and heavy-duty power supply."
- singular_name = "length"
+ singular_name = "length of cable"
+ plural_name = "lengths of cable"
w_class = ITEM_SIZE_NORMAL
throw_speed = 2
throw_range = 5
@@ -620,11 +621,11 @@ var/global/list/obj/structure/cable/all_cables = list()
if(distance > 1)
return
if(get_amount() == 1)
- . += "\A [singular_name] of cable."
+ . += "\A [singular_name]."
else if(get_amount() == 2)
- . += "Two [plural_name] of cable."
+ . += "Two [plural_name]."
else
- . += "A coil of power cable. There are [get_amount()] [plural_name] of cable in the coil."
+ . += "A coil of power cable. There are [get_amount()] [plural_name] in the coil."
/obj/item/stack/cable_coil/verb/make_restraint()
set name = "Make Cable Restraints"
@@ -634,11 +635,11 @@ var/global/list/obj/structure/cable/all_cables = list()
if(ishuman(M) && !M.incapacitated())
if(!isturf(usr.loc)) return
if(!src.use(15))
- to_chat(usr, SPAN_WARNING("You need at least 15 [plural_name] of cable to make restraints!"))
+ to_chat(usr, SPAN_WARNING("You need at least 15 [plural_name] to make restraints!"))
return
var/obj/item/handcuffs/cable/B = new /obj/item/handcuffs/cable(usr.loc)
B.set_color(color)
- to_chat(usr, SPAN_NOTICE("You wind some [plural_name] of cable together to make some restraints."))
+ to_chat(usr, SPAN_NOTICE("You wind some [plural_name] together to make some restraints."))
else
to_chat(usr, SPAN_NOTICE("You cannot do that."))
@@ -676,7 +677,7 @@ var/global/list/obj/structure/cable/all_cables = list()
return
if(get_amount() < 1) // Out of cable
- to_chat(user, SPAN_WARNING("There is no [plural_name] of cable left."))
+ to_chat(user, SPAN_WARNING("There is no [plural_name] left."))
return
if(get_dist(F,user) > 1) // Too far
@@ -696,7 +697,7 @@ var/global/list/obj/structure/cable/all_cables = list()
var/end_dir = 0
if(istype(F) && F.is_open())
if(!can_use(2))
- to_chat(user, SPAN_WARNING("You don't have enough [plural_name] of cable to do this!"))
+ to_chat(user, SPAN_WARNING("You don't have enough [plural_name] to do this!"))
return
end_dir = DOWN
@@ -840,6 +841,8 @@ var/global/list/obj/structure/cable/all_cables = list()
//////////////////////////////
// Misc.
/////////////////////////////
+/obj/item/stack/cable_coil/five
+ amount = 5
/obj/item/stack/cable_coil/cut
item_state = "coil2"
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 91c1085e4327..13b739cf041a 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -46,6 +46,8 @@
pickup_sound = 'sound/foley/pickup2.ogg'
can_be_twohanded = TRUE // also checks one_hand_penalty
needs_attack_dexterity = DEXTERITY_WEAPONS
+ wieldsound = 'sound/weapons/TargetOn.ogg'
+ unwieldsound = 'sound/weapons/TargetOff.ogg'
var/fire_verb = "fire"
var/waterproof = FALSE
@@ -94,6 +96,7 @@
/obj/item/gun/Initialize()
// must have firemodes initialized prior to any update_icon_calls
// including reconsider_single_icon(), which is done in ..()
+ LAZYINITLIST(firemodes)
for(var/i in 1 to firemodes.len)
firemodes[i] = new /datum/firemode(src, firemodes[i])
. = ..()
@@ -544,7 +547,7 @@
shot_sound = P.fire_sound
shot_sound_vol = P.fire_sound_vol
if(silencer)
- shot_sound_vol = 10
+ shot_sound_vol = P.fire_sound_vol_silenced
playsound(firer, shot_sound, shot_sound_vol, 1)
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index f7d2efa622be..7671a3a759ca 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -5,7 +5,7 @@ var/global/list/registered_cyborg_weapons = list()
name = "energy gun"
desc = "A basic energy-based gun."
icon = 'icons/obj/guns/basic_energy.dmi'
- icon_state = "energy"
+ icon_state = ICON_STATE_WORLD
fire_sound = 'sound/weapons/Taser.ogg'
fire_sound_text = "laser blast"
accuracy = 1
diff --git a/code/modules/projectiles/guns/launcher/grenade_launcher.dm b/code/modules/projectiles/guns/launcher/grenade_launcher.dm
index 1e1e7f0cda25..2fc0015f2667 100644
--- a/code/modules/projectiles/guns/launcher/grenade_launcher.dm
+++ b/code/modules/projectiles/guns/launcher/grenade_launcher.dm
@@ -108,9 +108,38 @@
return FALSE
return TRUE
-// For uplink purchase, comes loaded with a random assortment of grenades
+/obj/item/gun/launcher/grenade/loaded
+ var/initial_load_type
+
/obj/item/gun/launcher/grenade/loaded/Initialize()
. = ..()
+ if(initial_load_type)
+ chambered = new initial_load_type(src)
+ LAZYINITLIST(grenades)
+ for(var/i = 1 to max_grenades)
+ grenades += new initial_load_type(src)
+
+/obj/item/gun/launcher/grenade/loaded/anti_photon
+ initial_load_type = /obj/item/grenade/anti_photon
+
+/obj/item/gun/launcher/grenade/loaded/smoke
+ initial_load_type = /obj/item/grenade/smokebomb
+
+/obj/item/gun/launcher/grenade/loaded/teargas
+ initial_load_type = /obj/item/grenade/chem_grenade/teargas
+
+/obj/item/gun/launcher/grenade/loaded/flashbang
+ initial_load_type = /obj/item/grenade/flashbang
+
+/obj/item/gun/launcher/grenade/loaded/emp
+ initial_load_type = /obj/item/grenade/empgrenade
+
+/obj/item/gun/launcher/grenade/loaded/frag
+ initial_load_type = /obj/item/grenade/frag/shell
+
+// For uplink purchase, comes loaded with a random assortment of grenades
+/obj/item/gun/launcher/grenade/random/Initialize()
+ . = ..()
var/list/grenade_types = list(
/obj/item/grenade/anti_photon = 2,
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 4feed2fd06c9..d36ec1783cc9 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -25,6 +25,10 @@
var/proj_trail_icon_state = "trail"
/// Any extant trail effects.
var/list/proj_trails
+ /// An effect to spawn when a non-hitscan projectile collides with a target.
+ var/impact_effect_type
+ /// A sound to play when striking a non-mob (hitsound is used for mobs)
+ var/hitsound_non_mob
var/bumped = 0 //Prevents it from hitting more than one guy at once
var/def_zone = "" //Aiming at
@@ -73,6 +77,7 @@
var/fire_sound
var/fire_sound_vol = 50
+ var/fire_sound_vol_silenced = 10
var/miss_sounds
var/ricochet_sounds
var/list/impact_sounds //for different categories, IMPACT_MEAT etc
@@ -131,6 +136,10 @@
//called when the projectile stops flying because it collided with something
/obj/item/projectile/proc/on_impact(var/atom/A)
+
+ impact_sounds(A)
+ impact_visuals(A)
+
if(damage && atom_damage_type == BURN)
var/turf/T = get_turf(A)
if(T)
@@ -208,6 +217,7 @@
//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
/obj/item/projectile/proc/attack_mob(var/mob/living/target_mob, var/distance, var/special_miss_modifier=0)
+ SHOULD_CALL_PARENT(TRUE)
if(!istype(target_mob))
return
@@ -234,13 +244,18 @@
if(result == PROJECTILE_FORCE_MISS)
if(!silenced)
target_mob.visible_message("\The [src] misses [target_mob] narrowly!")
+ var/list/miss_sounds = get_miss_sounds()
if(LAZYLEN(miss_sounds))
playsound(target_mob.loc, pick(miss_sounds), 60, 1)
return FALSE
//hit messages
if(silenced)
- to_chat(target_mob, "You've been hit in the [parse_zone(def_zone)] by \the [src]!")
+ to_chat(target_mob, SPAN_DANGER("You've been hit in the [parse_zone(def_zone)] by \the [src]!"))
+ if(hitsound)
+ var/impact_volume = get_impact_volume_by_damage()
+ if(impact_volume)
+ playsound(target_mob, hitsound, impact_volume, 1, -1)
else
target_mob.visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter
@@ -672,3 +687,61 @@
/obj/item/projectile/proc/update_effect(var/obj/effect/projectile/effect)
return
+
+/obj/item/projectile/proc/get_projectile_damage(mob/living/target)
+ return damage
+
+// Makes a brief effect sprite appear when the projectile hits something solid.
+/obj/item/projectile/proc/impact_visuals(atom/A, hit_x, hit_y)
+ // Hitscan things have their own impact sprite.
+ if(!impact_effect_type || hitscan)
+ return
+ if(isnull(hit_x) && isnull(hit_y))
+ if(trajectory)
+ // Effect goes where the projectile 'stopped'.
+ hit_x = A.pixel_x + trajectory.return_px()
+ hit_y = A.pixel_y + trajectory.return_py()
+ else if(A == original)
+ // Otherwise it goes where the person who fired clicked.
+ hit_x = A.pixel_x + p_x - 16
+ hit_y = A.pixel_y + p_y - 16
+ else
+ // Otherwise it'll be random.
+ hit_x = A.pixel_x + rand(-8, 8)
+ hit_y = A.pixel_y + rand(-8, 8)
+ new impact_effect_type(get_turf(A), src, hit_x, hit_y)
+
+/obj/item/projectile/proc/get_impact_volume_by_damage()
+ if(damage || agony)
+ var/value_to_use = damage > agony ? damage : agony
+ // Multiply projectile damage by 1.2, then CLAMP the value between 30 and 100.
+ // This was 0.67 but in practice it made all projectiles that did 45 or less damage play at 30,
+ // which is hard to hear over the gunshots, and is rather rare for a projectile to do that much.
+ return clamp((value_to_use) * 1.2, 30, 100)
+ return 50 //if the projectile doesn't do damage or agony, play its hitsound at 50% volume.
+
+/obj/item/projectile/proc/impact_sounds(atom/A)
+
+ var/play_volume = clamp(get_impact_volume_by_damage() + 20, 0, 100)
+ if(play_volume <= 0)
+ return
+ if(silenced)
+ play_volume = min(play_volume, 5)
+
+ var/play_sound
+ if(ismob(A)) // Mob sounds are handled in attack_mob().
+ play_sound = hitsound
+ else
+ play_sound = hitsound_non_mob
+ if(!play_sound)
+ return
+ playsound(A, play_sound, play_volume, 1, -1)
+
+/obj/item/projectile/proc/get_miss_sounds()
+ return
+
+/obj/item/projectile/proc/get_ricochet_sounds()
+ return
+
+/obj/item/projectile/proc/get_impact_sounds()
+ return
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index 8df181e9195e..1c8b74eeb566 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -3,7 +3,6 @@
icon_state = "laser"
temperature = T0C + 300
fire_sound='sound/weapons/Laser.ogg'
- impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_LASER_MEAT, BULLET_IMPACT_METAL = SOUNDS_LASER_METAL)
pass_flags = PASS_FLAG_TABLE | PASS_FLAG_GLASS | PASS_FLAG_GRILLE
damage = 40
atom_damage_type = BURN
@@ -14,11 +13,26 @@
invisibility = INVISIBILITY_ABSTRACT //beam projectiles are invisible as they are rendered by the effect engine
penetration_modifier = 0.3
distance_falloff = 2.5
+ hitsound = 'sound/weapons/sear.ogg'
+ hitsound_non_mob = 'sound/weapons/searwall.ogg'
muzzle_type = /obj/effect/projectile/muzzle/laser
tracer_type = /obj/effect/projectile/tracer/laser
impact_type = /obj/effect/projectile/impact/laser
+/obj/item/projectile/beam/get_impact_sounds()
+ var/static/list/impact_sounds = list(
+ (BULLET_IMPACT_MEAT) = SOUNDS_LASER_MEAT,
+ (BULLET_IMPACT_METAL) = SOUNDS_LASER_METAL
+ )
+ return impact_sounds
+
+/obj/item/projectile/beam/blue
+ damage = 30
+ muzzle_type = /obj/effect/projectile/muzzle/laser/blue
+ tracer_type = /obj/effect/projectile/tracer/laser/blue
+ impact_type = /obj/effect/projectile/impact/laser/blue
+
/obj/item/projectile/beam/megabot
damage = 45
distance_falloff = 0.5
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index b80940577088..e94f5926a344 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -9,14 +9,31 @@
embed = 1
space_knockback = 1
penetration_modifier = 1.0
+ impact_effect_type = /obj/effect/temp_visual/impact_effect
+ muzzle_type = /obj/effect/projectile/muzzle/bullet
+ hitsound_non_mob = "ricochet"
+
var/mob_passthrough_check = 0
var/caliber
- muzzle_type = /obj/effect/projectile/muzzle/bullet
- miss_sounds = list('sound/weapons/guns/miss1.ogg','sound/weapons/guns/miss2.ogg','sound/weapons/guns/miss3.ogg','sound/weapons/guns/miss4.ogg')
- ricochet_sounds = list('sound/weapons/guns/ricochet1.ogg', 'sound/weapons/guns/ricochet2.ogg',
- 'sound/weapons/guns/ricochet3.ogg', 'sound/weapons/guns/ricochet4.ogg')
- impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL)
+/obj/item/projectile/bullet/get_miss_sounds()
+ var/static/list/miss_sounds = list(
+ 'sound/weapons/guns/miss1.ogg',
+ 'sound/weapons/guns/miss2.ogg',
+ 'sound/weapons/guns/miss3.ogg',
+ 'sound/weapons/guns/miss4.ogg'
+ )
+
+/obj/item/projectile/bullet/get_ricochet_sounds()
+ return global.ricochet_sound
+
+/obj/item/projectile/bullet/get_impact_sounds()
+
+ var/static/list/impact_sounds = list(
+ (BULLET_IMPACT_MEAT) = SOUNDS_BULLET_MEAT,
+ (BULLET_IMPACT_METAL) = SOUNDS_BULLET_METAL
+ )
+ return impact_sounds
/obj/item/projectile/bullet/get_autopsy_descriptors()
. = ..()
@@ -34,7 +51,6 @@
else
mob_passthrough_check = 0
. = ..()
-
if(. == 1 && isliving(target_mob))
var/mob/living/squish = target_mob
if(!squish.isSynthetic())
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm
index eb0cc1835e48..e08ffc8d1f01 100644
--- a/code/modules/projectiles/projectile/energy.dm
+++ b/code/modules/projectiles/projectile/energy.dm
@@ -6,6 +6,9 @@
atom_damage_type = BURN
damage_flags = 0
distance_falloff = 2.5
+ impact_effect_type = /obj/effect/temp_visual/impact_effect
+ hitsound_non_mob = 'sound/weapons/searwall.ogg'
+ hitsound = 'sound/weapons/zapbang.ogg'
//releases a burst of light on impact or after travelling a distance
/obj/item/projectile/energy/flash
@@ -16,6 +19,7 @@
agony = 20
life_span = 15 //if the shell hasn't hit anything after travelling this far it just explodes.
muzzle_type = /obj/effect/projectile/muzzle/bullet
+ hitsound_non_mob = null
var/flash_range = 1
var/brightness = 7
var/light_flash_color = COLOR_WHITE
@@ -106,6 +110,7 @@
damage = 30
atom_damage_type = CLONE
irradiate = 40
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/monochrome_laser
/obj/item/projectile/energy/dart
name = "dart"
@@ -142,6 +147,7 @@
damage = 20
atom_damage_type = TOX
irradiate = 20
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/monochrome_laser
/obj/item/projectile/energy/plasmastun
name = "plasma pulse"
diff --git a/code/modules/projectiles/projectile/fireball.dm b/code/modules/projectiles/projectile/fireball.dm
new file mode 100644
index 000000000000..6221b8bf1348
--- /dev/null
+++ b/code/modules/projectiles/projectile/fireball.dm
@@ -0,0 +1,45 @@
+/obj/item/projectile/fireball
+ name = "fireball"
+ icon_state = "fireball"
+ fire_sound = 'sound/effects/bamf.ogg'
+ damage = 20
+ atom_damage_type = BURN
+ damage_flags = DAM_DISPERSED // burn all over
+ var/fire_lifetime = 2 SECONDS
+ var/fire_temperature = (288 CELSIUS) / 0.9 + 1 // hot enough to ignite wood! divided by 0.9 and plus one to ensure we can light firepits
+
+/obj/effect/fake_fire/variable
+ name = "fire"
+ anchored = TRUE
+ mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+ firelevel = 1
+ pressure = ONE_ATMOSPHERE
+
+/obj/effect/fake_fire/variable/Initialize(ml, new_temperature, new_lifetime)
+ lifetime = new_lifetime
+ last_temperature = new_temperature
+ return ..()
+
+// we deal our damage via fire_act, not via direct burn damage. our burn damage is specifically for mobs
+/obj/item/projectile/fireball/get_structure_damage()
+ return 0
+
+/obj/item/projectile/fireball/on_impact(var/atom/A)
+ . = ..()
+ var/obj/effect/fake_fire/fire = new /obj/effect/fake_fire/variable(get_turf(A), fire_temperature, fire_lifetime)
+ fire.Process() // process at least once!
+ qdel_self()
+
+/obj/item/projectile/fireball/after_move()
+ . = ..()
+ if(!loc)
+ return
+ for(var/mob/living/victim in loc)
+ if(!victim.simulated)
+ continue
+ victim.FireBurn(1, fire_temperature, ONE_ATMOSPHERE)
+ loc.fire_act(1, fire_temperature, ONE_ATMOSPHERE)
+ for(var/atom/burned in loc)
+ if(!burned.simulated || burned == src)
+ continue
+ burned.fire_act(1, fire_temperature, ONE_ATMOSPHERE)
diff --git a/code/modules/projectiles/projectile/force.dm b/code/modules/projectiles/projectile/force.dm
index a3c2e748d0b9..e0b8ea2745f9 100644
--- a/code/modules/projectiles/projectile/force.dm
+++ b/code/modules/projectiles/projectile/force.dm
@@ -5,6 +5,8 @@
damage = 20
atom_damage_type = BURN
damage_flags = 0
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
+ hitsound_non_mob = 'sound/weapons/searwall.ogg'
/obj/item/projectile/forcebolt/strong
name = "force bolt"
diff --git a/code/modules/projectiles/projectile/pellets.dm b/code/modules/projectiles/projectile/pellets.dm
index c4a244c802ac..5c19804a9c2f 100644
--- a/code/modules/projectiles/projectile/pellets.dm
+++ b/code/modules/projectiles/projectile/pellets.dm
@@ -17,7 +17,9 @@
return max(pellets - pellet_loss, 1)
/obj/item/projectile/bullet/pellet/attack_mob(var/mob/target_mob, var/distance, var/miss_modifier)
- if (pellets < 0) return 1
+ SHOULD_CALL_PARENT(FALSE)
+ if (pellets < 0)
+ return 1
var/total_pellets = get_pellets(distance)
var/spread = max(base_spread - (spread_step*distance), 0)
@@ -39,7 +41,9 @@
if (..()) hits++
def_zone = old_zone //restore the original zone the projectile was aimed at
- pellets -= hits //each hit reduces the number of pellets left
+ if(hits > 0)
+ pellets -= hits //each hit reduces the number of pellets left
+
if (hits >= total_pellets || pellets <= 0)
return 1
return 0
diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm
index d648f6d70c3f..d7ee296a8f14 100644
--- a/code/modules/projectiles/projectile/special.dm
+++ b/code/modules/projectiles/projectile/special.dm
@@ -6,6 +6,9 @@
atom_damage_type = BURN
damage_flags = 0
nodamage = 1
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/ion
+ hitsound_non_mob = 'sound/weapons/searwall.ogg'
+ hitsound = 'sound/weapons/ionrifle.ogg'
var/heavy_effect_range = 1
var/light_effect_range = 2
@@ -51,6 +54,7 @@
atom_damage_type = BURN
damage_flags = 0
nodamage = 1
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/monochrome_laser
var/firing_temperature = 300
/obj/item/projectile/temp/on_hit(var/atom/target, var/blocked = 0)//These two could likely check temp protection on the mob
@@ -88,6 +92,7 @@
damage = 0
atom_damage_type = TOX
nodamage = 1
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/monochrome_laser
/obj/item/projectile/energy/floramut/on_hit(var/atom/target, var/blocked = 0)
if(!isliving(target))
@@ -131,6 +136,7 @@
damage = 0
atom_damage_type = TOX
nodamage = 1
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/monochrome_laser
/obj/item/projectile/energy/florayield/on_hit(var/atom/target, var/blocked = 0)
if(!isliving(target))
diff --git a/code/modules/projectiles/projectile/trace.dm b/code/modules/projectiles/projectile/trace.dm
index a7e1f58c34fa..6abad14dbe3d 100644
--- a/code/modules/projectiles/projectile/trace.dm
+++ b/code/modules/projectiles/projectile/trace.dm
@@ -32,4 +32,5 @@
return ..()
/obj/item/projectile/test/attack_mob()
- return
\ No newline at end of file
+ SHOULD_CALL_PARENT(FALSE)
+ return
diff --git a/code/modules/random_map/dungeon/predefined.dm b/code/modules/random_map/dungeon/predefined.dm
index c6b536cc2e54..91b22c5c42eb 100644
--- a/code/modules/random_map/dungeon/predefined.dm
+++ b/code/modules/random_map/dungeon/predefined.dm
@@ -5,7 +5,7 @@
room_theme_rare = list(/datum/room_theme/metal = 1, /datum/room_theme = 3, /datum/room_theme/metal/secure = 1)
monsters_common = list(/mob/living/simple_animal/hostile/carp = 50, /mob/living/simple_animal/hostile/carp/pike = 1)
- monsters_uncommon = list(/mob/living/simple_animal/hostile/hivebot = 10, /mob/living/simple_animal/hostile/hivebot/strong = 1)
+ monsters_uncommon = list(/mob/living/simple_animal/hostile/hivebot = 10, /mob/living/simple_animal/hostile/hivebot/melee/armored = 1)
/datum/random_map/winding_dungeon/premade/New(var/tx, var/ty, var/tz, var/tlx, var/tly, var/do_not_apply, var/do_not_announce, var/used_area)
loot_common = subtypesof(/obj/item/energy_blade/sword) + subtypesof(/obj/item/baton) + subtypesof(/obj/item/food) + subtypesof(/obj/item/chems/condiment) + subtypesof(/obj/item/chems/drinks)
diff --git a/code/modules/reagents/chems/chems_compounds.dm b/code/modules/reagents/chems/chems_compounds.dm
index cb305d78e23c..f080406738f4 100644
--- a/code/modules/reagents/chems/chems_compounds.dm
+++ b/code/modules/reagents/chems/chems_compounds.dm
@@ -263,12 +263,7 @@
if(!M.has_genetic_information())
return
if(prob(removed * 0.1)) // Approx. one mutation per 10 injected/20 ingested/30 touching units
- M.set_unique_enzymes(num2text(random_id(/mob, 1000000, 9999999)))
- if(prob(98))
- M.add_genetic_condition(pick(decls_repository.get_decls_of_type(/decl/genetic_condition/disability)))
- else
- M.add_genetic_condition(pick(decls_repository.get_decls_of_type(/decl/genetic_condition/superpower)))
- M.apply_damage(10 * removed, IRRADIATE, armor_pen = 100)
+ M.apply_random_mutation(10 * removed)
/decl/material/liquid/lactate
name = "lactate"
diff --git a/code/modules/salvage/salvage.dm b/code/modules/salvage/salvage.dm
new file mode 100644
index 000000000000..ecf3196f8834
--- /dev/null
+++ b/code/modules/salvage/salvage.dm
@@ -0,0 +1,131 @@
+/obj/item/salvage
+ desc = "The remains of an unfortunate device."
+ transform_animate_time = 0
+ icon = 'icons/obj/modules/module_id.dmi'
+ icon_state = ICON_STATE_WORLD
+ abstract_type = /obj/item/salvage
+ var/work_skill = SKILL_DEVICES
+ var/obj/item/salvaged_type
+ var/list/repairs_required = list()
+ var/do_rotation = TRUE
+
+/obj/item/salvage/Initialize(var/ml, var/path)
+ . = ..(ml)
+ if(!ispath(salvaged_type, /obj/item))
+ return INITIALIZE_HINT_QDEL
+ // TODO: grab partial initial matter from the salvage type.
+ icon_rotation = rand(-45, 45)
+ name = "[pick("busted", "broken", "shattered", "scrapped")] [salvaged_type::name]"
+ w_class = salvaged_type::w_class
+
+ var/list/all_repair_options = get_repair_options()
+ var/list/selected_options = list()
+ for(var/opt_type in all_repair_options)
+ var/decl/salvage_repair_option/opt = RESOLVE_TO_DECL(opt_type)
+ if(istype(opt) && prob(opt.selection_prob))
+ selected_options += opt
+ if(!length(selected_options))
+ selected_options += RESOLVE_TO_DECL(pick(all_repair_options))
+ for(var/decl/salvage_repair_option/opt in selected_options)
+ repairs_required += opt.create_salvage_requirement()
+
+ update_icon()
+
+/obj/item/salvage/proc/get_repair_options()
+ return subtypesof(/decl/salvage_repair_option/material_sheet)
+
+/obj/item/salvage/attackby(obj/item/used_item, mob/user)
+
+ // Find an appropriate repair (finished or not)
+ var/datum/salvage_repair_requirement/opt
+ for(var/datum/salvage_repair_requirement/rep in repairs_required)
+ if(!istype(used_item, rep.repair_type))
+ continue
+ if(istype(used_item, /obj/item/stack/material) && !istype(used_item.material, rep.repair_material))
+ continue
+ if(!opt || (opt.repair_amount == 0 && rep.repair_amount > 0))
+ opt = rep
+
+ // Apply the repair.
+ if(opt)
+ if(opt.repair_amount <= 0)
+ to_chat(user, SPAN_WARNING("\The [src] does not need further repair with \the [used_item]."))
+ else if(user.do_skilled((5 + rand(10)) SECONDS, work_skill, src) && !QDELETED(opt) && opt.repair_amount > 0)
+ if(istype(used_item, /obj/item/stack))
+ var/obj/item/stack/stack = used_item
+ var/use_amt = min(opt.repair_amount, stack.get_amount())
+ stack.use(use_amt)
+ opt.repair_amount -= use_amt
+ else if(user.try_unequip(used_item))
+ qdel(used_item)
+ opt.repair_amount--
+ // TODO: transfer material from the donor item to this item.
+ check_repair_completion(user)
+ return TRUE
+
+ . = ..()
+
+/obj/item/salvage/get_examine_hints(mob/user, distance, infix, suffix)
+ . = ..()
+ . += SPAN_NOTICE("It requires the following items to be fully repaired:")
+ for(var/datum/salvage_repair_requirement/rep in repairs_required)
+ if(rep.repair_amount <= 0)
+ continue
+ var/is_mat_stack = ispath(rep.repair_type, /obj/item/stack/material) && rep.repair_material
+ var/obj/item/repair_type = rep.repair_type
+ var/repair_thing = is_mat_stack ? atom_info_repository.get_name_for(repair_type, rep.repair_material) : atom_info_repository.get_name_for(repair_type)
+ if(ispath(rep.repair_type, /obj/item/stack))
+ var/obj/item/stack/stack = rep.repair_type
+ repair_thing = rep.repair_amount == 1 ? stack::singular_name : stack::plural_name
+ if(is_mat_stack)
+ var/decl/material/repair_mat = GET_DECL(rep.repair_material)
+ repair_thing = "[repair_mat.solid_name] [repair_thing]"
+ else if(rep.repair_amount > 1)
+ repair_thing = text_make_plural(repair_thing)
+ . += SPAN_NOTICE("- [rep.repair_amount] [repair_thing]")
+
+/obj/item/salvage/proc/check_repair_completion(mob/user)
+
+ for(var/datum/salvage_repair_requirement/rep in repairs_required)
+ if(rep.repair_amount > 0)
+ if(user)
+ to_chat(user, SPAN_NOTICE("You mend some of the damage to \the [src], but further repair is required."))
+ return
+
+ var/obj/item/created = new salvaged_type(get_turf(src))
+ if(isitem(created))
+ if(created.name_prefix)
+ created.name_prefix = "[created.name_prefix] [pick("salvaged", "restored", "old", "worn")]" // enormous salvaged pipe wrench
+ else
+ created.name_prefix = pick("salvaged", "restored", "old", "worn") // salvaged laser rifle
+ created.update_name()
+
+ var/atom/created_loc = loc
+ qdel(src)
+
+ if(user)
+ to_chat(user, SPAN_NOTICE("You finish repairing \the [created]!"))
+
+ if(user && created_loc == user)
+ user.put_in_hands(created)
+ else
+ created.forceMove(created_loc)
+
+/obj/item/salvage/on_update_icon()
+ SHOULD_CALL_PARENT(FALSE)
+ if(!salvaged_type)
+ return
+ var/old_name = name
+ var/old_desc = desc
+ var/old_pixel_x = pixel_x
+ var/old_pixel_y = pixel_y
+ var/old_plane = plane
+ var/old_layer = layer
+ appearance = atom_info_repository.get_appearance_of(salvaged_type)
+ name = old_name
+ desc = old_desc
+ pixel_x = old_pixel_x
+ pixel_y = old_pixel_y
+ plane = old_plane
+ layer = old_layer
+ update_transform()
diff --git a/code/modules/salvage/salvage_ballistic.dm b/code/modules/salvage/salvage_ballistic.dm
new file mode 100644
index 000000000000..904f6d0276c9
--- /dev/null
+++ b/code/modules/salvage/salvage_ballistic.dm
@@ -0,0 +1,24 @@
+/obj/item/salvage/ballistic
+ name = "broken ballistic weapon"
+ icon = /obj/item/gun/projectile/automatic/assault_rifle::icon
+ icon_state = /obj/item/gun/projectile/automatic/assault_rifle::icon_state
+ abstract_type = /obj/item/salvage/ballistic
+
+/obj/item/salvage/ballistic/get_repair_options()
+ return ..() + /decl/salvage_repair_option/component
+
+/obj/item/salvage/ballistic/assault
+ salvaged_type = /obj/item/gun/projectile/automatic/assault_rifle
+
+/obj/item/salvage/ballistic/pistol
+ salvaged_type = /obj/item/gun/projectile/pistol
+
+/obj/item/salvage/ballistic/smg
+ salvaged_type = /obj/item/gun/projectile/automatic/smg
+
+/obj/item/salvage/ballistic/shotgun_pump
+ salvaged_type = /obj/item/gun/projectile/shotgun/pump
+
+/obj/item/salvage/ballistic/shotgun_doublebarrel
+ salvaged_type = /obj/item/gun/projectile/shotgun/doublebarrel
+
diff --git a/code/modules/salvage/salvage_energy.dm b/code/modules/salvage/salvage_energy.dm
new file mode 100644
index 000000000000..3075a04bd8fa
--- /dev/null
+++ b/code/modules/salvage/salvage_energy.dm
@@ -0,0 +1,17 @@
+/obj/item/salvage/energy
+ name = "broken energy weapon"
+ icon = /obj/item/gun/energy/laser::icon
+ icon_state = /obj/item/gun/energy/laser::icon_state
+ abstract_type = /obj/item/salvage/energy
+
+/obj/item/salvage/energy/get_repair_options()
+ return ..() + /decl/salvage_repair_option/energy
+
+/obj/item/salvage/energy/ionrifle
+ salvaged_type = /obj/item/gun/energy/ionrifle
+
+/obj/item/salvage/energy/laserrifle
+ salvaged_type = /obj/item/gun/energy/laser
+
+/obj/item/salvage/energy/laser_retro
+ salvaged_type = /obj/item/gun/energy/captain // TODO: swap to retro laser when merged
diff --git a/code/modules/salvage/salvage_launcher.dm b/code/modules/salvage/salvage_launcher.dm
new file mode 100644
index 000000000000..571f69b6a136
--- /dev/null
+++ b/code/modules/salvage/salvage_launcher.dm
@@ -0,0 +1,14 @@
+/obj/item/salvage/launcher
+ name = "broken grenade launcher"
+ icon = /obj/item/gun/launcher/grenade::icon
+ icon_state = /obj/item/gun/launcher/grenade::icon_state
+ abstract_type = /obj/item/salvage/launcher
+
+/obj/item/salvage/launcher/get_repair_options()
+ return ..() + /decl/salvage_repair_option/launcher
+
+/obj/item/salvage/launcher/grenade
+ salvaged_type = /obj/item/gun/launcher/grenade
+
+/obj/item/salvage/launcher/dartgun
+ salvaged_type = /obj/item/gun/projectile/dartgun
diff --git a/code/modules/salvage/salvage_magnetic.dm b/code/modules/salvage/salvage_magnetic.dm
new file mode 100644
index 000000000000..78b8bcf47baa
--- /dev/null
+++ b/code/modules/salvage/salvage_magnetic.dm
@@ -0,0 +1,12 @@
+/obj/item/salvage/magnetic
+ name = "broken magnetic weapon"
+ icon = /obj/item/gun/magnetic/railgun/flechette::icon
+ icon_state = /obj/item/gun/magnetic/railgun/flechette::icon_state
+ abstract_type = /obj/item/salvage/magnetic
+
+/obj/item/salvage/magnetic/get_repair_options()
+ return ..() + /decl/salvage_repair_option/magnetic
+
+/obj/item/salvage/magnetic/flechette
+ salvaged_type = /obj/item/gun/magnetic/railgun/flechette
+
diff --git a/code/modules/salvage/salvage_repair_option.dm b/code/modules/salvage/salvage_repair_option.dm
new file mode 100644
index 000000000000..f63d88afc611
--- /dev/null
+++ b/code/modules/salvage/salvage_repair_option.dm
@@ -0,0 +1,90 @@
+/decl/salvage_repair_option
+ abstract_type = /decl/salvage_repair_option
+ var/selection_prob = 0
+ var/list/selection_types
+ var/list/selection_materials
+ var/selection_min_amount = 1
+ var/selection_max_amount = 3
+
+/decl/salvage_repair_option/validate()
+ . = ..()
+ if(!islist(selection_types) || !length(selection_types))
+ . += "null, empty or malformed selection_types list"
+ else
+ for(var/selection_type in selection_types)
+ if(!ispath(selection_type))
+ . += "non-path selection type: '[selection_type]'"
+ else if(ispath(selection_type, /obj/item/stack/material) && (!islist(selection_materials) || !length(selection_materials)))
+ . += "material stack '[selection_type]' in selection_types, but selection_materials is empty/malformed"
+ for(var/selection_material in selection_materials)
+ if(!ispath(selection_material, /decl/material))
+ . += "non-material path '[selection_material]' in selection_materials"
+
+/decl/salvage_repair_option/proc/create_salvage_requirement()
+ var/use_type = pick(selection_types)
+ var/use_mat = ispath(use_type, /obj/item/stack/material) ? pick(selection_materials) : null
+ return new /datum/salvage_repair_requirement(use_type, use_mat, rand(selection_min_amount, selection_max_amount))
+
+/decl/salvage_repair_option/component
+ selection_prob = 30
+ selection_types = list(
+ /obj/item/stock_parts/manipulator
+ )
+
+/decl/salvage_repair_option/material_sheet
+ selection_prob = 40
+ abstract_type = /decl/salvage_repair_option/material_sheet
+
+/decl/salvage_repair_option/material_sheet/plastic
+ selection_types = list(
+ /obj/item/stack/material/panel
+ )
+ selection_materials = list(
+ /decl/material/solid/organic/plastic
+ )
+
+/decl/salvage_repair_option/material_sheet/glass
+ selection_types = list(
+ /obj/item/stack/material/pane
+ )
+ selection_materials = list(
+ /decl/material/solid/glass
+ )
+
+/decl/salvage_repair_option/material_sheet/plasteel
+ selection_types = list(
+ /obj/item/stack/material/sheet/reinforced
+ )
+ selection_materials = list(
+ /decl/material/solid/metal/plasteel
+ )
+
+/decl/salvage_repair_option/launcher
+ selection_prob = 50
+ selection_materials = list(
+ /decl/material/solid/metal/steel
+ )
+ selection_types = list(
+ /obj/item/stack/tape_roll,
+ /obj/item/stack/material/rods,
+ /obj/item/handcuffs/cable
+ )
+ selection_max_amount = 1
+
+/decl/salvage_repair_option/energy
+ selection_prob = 25
+ selection_types = list(
+ /obj/item/stack/cable_coil,
+ /obj/item/stock_parts/scanning_module,
+ /obj/item/stock_parts/capacitor
+ )
+ selection_max_amount = 1
+
+/decl/salvage_repair_option/magnetic
+ selection_prob = 70
+ selection_types = list(
+ /obj/item/stock_parts/smes_coil,
+ /obj/item/assembly/prox_sensor,
+ /obj/item/stock_parts/circuitboard/apc
+ )
+ selection_max_amount = 1
diff --git a/code/modules/salvage/salvage_repair_requirement.dm b/code/modules/salvage/salvage_repair_requirement.dm
new file mode 100644
index 000000000000..fdf524bb61e4
--- /dev/null
+++ b/code/modules/salvage/salvage_repair_requirement.dm
@@ -0,0 +1,9 @@
+/datum/salvage_repair_requirement
+ var/repair_type
+ var/repair_material
+ var/repair_amount
+
+/datum/salvage_repair_requirement/New(_type, _mat, _amt)
+ repair_amount = _amt
+ repair_material = _mat
+ repair_type = _type
diff --git a/code/modules/salvage/structure.dm b/code/modules/salvage/structure.dm
new file mode 100644
index 000000000000..1d71f206157a
--- /dev/null
+++ b/code/modules/salvage/structure.dm
@@ -0,0 +1,23 @@
+/obj/structure/salvage
+ icon = 'icons/obj/structures/salvage.dmi'
+ abstract_type = /obj/structure/salvage
+ tool_interaction_flags = TOOL_INTERACTION_DECONSTRUCT
+ material = /decl/material/solid/metal/steel
+ var/frame_type = /obj/machinery/constructable_frame/machine_frame
+ var/work_skill = SKILL_CONSTRUCTION
+
+/obj/structure/salvage/proc/get_salvageable_components()
+ return
+
+/obj/structure/salvage/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+ name_prefix = pick("broken", "ruined", "destroyed", "slagged", "damaged")
+
+/obj/structure/salvage/create_dismantled_products(turf/T)
+ . = ..()
+ if(frame_type)
+ new frame_type(T)
+ var/list/salvageable_components = get_salvageable_components()
+ for(var/comp in salvageable_components)
+ if(!salvageable_components[comp] || prob(salvageable_components[comp]))
+ new comp(T)
diff --git a/code/modules/salvage/structure_computer.dm b/code/modules/salvage/structure_computer.dm
new file mode 100644
index 000000000000..12b9f700eee3
--- /dev/null
+++ b/code/modules/salvage/structure_computer.dm
@@ -0,0 +1,116 @@
+/obj/structure/salvage/computer
+ name = "computer"
+ desc = "A defunct computer. There might still be useful components inside."
+ icon_state = "computer0"
+
+/obj/structure/salvage/computer/Initialize()
+ . = ..()
+ icon_state = "computer[rand(0,7)]"
+
+/obj/structure/salvage/computer/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stack/material/pane/mapped/glass/five = 90,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/network_card/advanced = 20,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/capacitor/adv = 30,
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/server
+ name = "server"
+ desc = "A damaged, broken server. There might still be useful components inside."
+ icon_state = "server0"
+
+/obj/structure/salvage/server/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+
+/obj/structure/salvage/server/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stack/material/pane/mapped/glass/five = 90,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/subspace/amplifier = 40,
+ /obj/item/stock_parts/subspace/amplifier = 40,
+ /obj/item/stock_parts/subspace/analyzer = 40,
+ /obj/item/stock_parts/subspace/analyzer = 40,
+ /obj/item/stock_parts/subspace/ansible = 40,
+ /obj/item/stock_parts/subspace/ansible = 40,
+ /obj/item/stock_parts/subspace/transmitter = 40,
+ /obj/item/stock_parts/subspace/transmitter = 40,
+ /obj/item/stock_parts/subspace/crystal = 30,
+ /obj/item/stock_parts/subspace/crystal = 30,
+ /obj/item/stock_parts/computer/network_card/advanced = 20
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/computer_old
+ name = "computer"
+ desc = "A defunct computer. There might still be useful components inside."
+ icon_state = "os-computer"
+
+/obj/structure/salvage/computer_old/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stack/material/pane/mapped/glass/five = 90,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/computer/processor_unit/photonic = 40,
+ /obj/item/stock_parts/computer/processor_unit/photonic = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/network_card/advanced = 40
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/data
+ name = "data storage"
+ desc = "An old, battered, broken data storage rack. There might still be useful components inside."
+ icon_state = "data0"
+
+/obj/structure/salvage/data/Initialize()
+ . = ..()
+ icon_state = "data[rand(0,1)]"
+
+/obj/structure/salvage/data/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stack/material/pane/mapped/glass/five = 90,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive = 50,
+ /obj/item/stock_parts/computer/hard_drive/advanced = 30,
+ /obj/item/stock_parts/computer/hard_drive/advanced = 30,
+ /obj/item/stock_parts/computer/network_card/advanced = 20
+ )
+ return salvageable_parts
diff --git a/code/modules/salvage/structure_console.dm b/code/modules/salvage/structure_console.dm
new file mode 100644
index 000000000000..7f31e5be6ad6
--- /dev/null
+++ b/code/modules/salvage/structure_console.dm
@@ -0,0 +1,35 @@
+/obj/structure/salvage/console
+ name = "console"
+ desc = "A beat-up old console. Might still have some useful components inside."
+ icon_state = "os_console"
+
+/obj/structure/salvage/console/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/computer/processor_unit/small = 40,
+ /obj/item/stock_parts/computer/processor_unit/photonic = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/network_card/advanced = 40
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/console/broken
+ icon_state = "os_console_broken"
+
+/obj/structure/salvage/console/broken/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/capacitor = 60,
+ /obj/item/stock_parts/computer/processor_unit = 40,
+ /obj/item/stock_parts/computer/processor_unit/photonic = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/network_card/advanced = 40
+ )
+ return salvageable_parts
diff --git a/code/modules/salvage/structure_machine.dm b/code/modules/salvage/structure_machine.dm
new file mode 100644
index 000000000000..62fa97c6bbfc
--- /dev/null
+++ b/code/modules/salvage/structure_machine.dm
@@ -0,0 +1,92 @@
+
+/obj/structure/salvage/fabricator
+ name = "fabricator"
+ desc = "A busted, defunct fabricator. There might still be useful components or materials inside."
+ icon_state = "autolathe"
+
+/obj/structure/salvage/fabricator/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 80,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/capacitor = 40,
+ /obj/item/stock_parts/scanning_module = 40,
+ /obj/item/stock_parts/manipulator = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/capacitor/adv = 20,
+ /obj/item/stock_parts/micro_laser/high = 20,
+ /obj/item/stock_parts/micro_laser/high = 20,
+ /obj/item/stock_parts/matter_bin/adv = 20,
+ /obj/item/stock_parts/matter_bin/adv = 20,
+ /obj/item/stack/material/sheet/mapped/steel/twenty = 40,
+ /obj/item/stack/material/pane/mapped/glass/twenty = 40,
+ /obj/item/stack/material/panel/mapped/plastic/twenty = 40,
+ /obj/item/stack/material/sheet/reinforced/mapped/plasteel/ten = 40,
+ /obj/item/stack/material/ingot/mapped/silver/ten = 20,
+ /obj/item/stack/material/ingot/mapped/gold/ten = 20
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/machine
+ name = "machine"
+ desc = "A badly-damaged machine of some kind. There might still be some usable components inside."
+ icon_state = "machine1"
+
+/obj/structure/salvage/machine/Initialize()
+ . = ..()
+ icon_state = "machine[rand(0,6)]"
+
+/obj/structure/salvage/machine/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 80,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/capacitor = 40,
+ /obj/item/stock_parts/capacitor = 40,
+ /obj/item/stock_parts/scanning_module = 40,
+ /obj/item/stock_parts/scanning_module = 40,
+ /obj/item/stock_parts/manipulator = 40,
+ /obj/item/stock_parts/manipulator = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/capacitor/adv = 20,
+ /obj/item/stock_parts/scanning_module/adv = 20,
+ /obj/item/stock_parts/manipulator/nano = 20,
+ /obj/item/stock_parts/micro_laser/high = 20,
+ /obj/item/stock_parts/matter_bin/adv = 20
+ )
+ return salvageable_parts
+
+/obj/structure/salvage/machine_old
+ name = "machine"
+ desc = "A badly-damaged machine of some kind. There might still be some usable components inside."
+ icon_state = "os-machine"
+
+/obj/structure/salvage/machine_old/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 80,
+ /obj/item/stock_parts/capacitor = 40,
+ /obj/item/stock_parts/capacitor = 40,
+ /obj/item/stock_parts/scanning_module = 40,
+ /obj/item/stock_parts/scanning_module = 40,
+ /obj/item/stock_parts/manipulator = 40,
+ /obj/item/stock_parts/manipulator = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/micro_laser = 40,
+ /obj/item/stock_parts/matter_bin = 40,
+ /obj/item/stock_parts/matter_bin = 40
+ )
+ return salvageable_parts
diff --git a/code/modules/salvage/structure_misc.dm b/code/modules/salvage/structure_misc.dm
new file mode 100644
index 000000000000..e1515b69c93b
--- /dev/null
+++ b/code/modules/salvage/structure_misc.dm
@@ -0,0 +1,27 @@
+/obj/structure/salvage/implant_container
+ name = "old container"
+ icon_state = "implant_container0"
+
+/obj/structure/salvage/implant_container/Initialize()
+ . = ..()
+ icon_state = "implant_container[rand(0,1)]"
+
+/obj/structure/salvage/implant_container/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil/five = 80,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/implant/death_alarm = 15,
+ /obj/item/implant/explosive = 10,
+ /obj/item/implant/freedom = 5,
+ /obj/item/implant/tracking = 10,
+ /obj/item/implant/chem = 10,
+ /obj/item/implantcase = 30,
+ /obj/item/implanter = 30,
+ /obj/item/stack/material/sheet/mapped/steel/ten = 30,
+ /obj/item/stack/material/pane/mapped/glass/ten = 30,
+ /obj/item/stack/material/ingot/mapped/silver/ten = 30
+ )
+ return salvageable_parts
diff --git a/code/modules/salvage/structure_terminal.dm b/code/modules/salvage/structure_terminal.dm
new file mode 100644
index 000000000000..6e2ef10be5c5
--- /dev/null
+++ b/code/modules/salvage/structure_terminal.dm
@@ -0,0 +1,29 @@
+/obj/structure/salvage/personal
+ name = "personal terminal"
+ desc = "An unusable personal terminal. There might still be useful components inside."
+ icon_state = "personal0"
+
+/obj/structure/salvage/personal/Initialize(ml, _mat, _reinf_mat)
+ . = ..()
+ icon_state = "personal[rand(0,12)]"
+
+/obj/structure/salvage/personal/get_salvageable_components()
+ var/static/list/salvageable_parts = list(
+ /obj/item/stock_parts/console_screen = 90,
+ /obj/item/stack/cable_coil/five = 90,
+ /obj/item/stack/material/pane/mapped/glass/five = 70,
+ /obj/item/debris/salvage/circuit = 60,
+ /obj/item/debris/salvage/metal = 60,
+ /obj/item/debris/salvage/metal/plasteel = 15,
+ /obj/item/stock_parts/computer/network_card = 60,
+ /obj/item/stock_parts/computer/network_card = 40,
+ /obj/item/stock_parts/computer/network_card/advanced = 40,
+ /obj/item/stock_parts/computer/card_slot = 40,
+ /obj/item/stock_parts/computer/processor_unit = 60,
+ /obj/item/stock_parts/computer/processor_unit/small = 50,
+ /obj/item/stock_parts/computer/processor_unit/photonic = 40,
+ /obj/item/stock_parts/computer/processor_unit/photonic/small = 30,
+ /obj/item/stock_parts/computer/hard_drive = 60,
+ /obj/item/stock_parts/computer/hard_drive/advanced = 40
+ )
+ return salvageable_parts
diff --git a/code/modules/scanners/mining.dm b/code/modules/scanners/mining.dm
index 2b9095692b27..9da785d77ced 100644
--- a/code/modules/scanners/mining.dm
+++ b/code/modules/scanners/mining.dm
@@ -15,13 +15,12 @@
origin_tech = @'{"magnets":1,"engineering":1}'
use_delay = 50
printout_color = "#fff7f0"
- var/survey_data = 0
-
scan_sound = 'sound/effects/ping.ogg'
+ var/survey_data = 0
/obj/item/scanner/mining/get_examine_strings(mob/user, distance, infix, suffix)
. = ..()
- . += "A tiny indicator on \the [src] shows it holds [survey_data] good explorer point\s."
+ . += "A tiny indicator on \the [src] shows it holds [survey_data] survey point\s."
/obj/item/scanner/mining/is_valid_scan_target(turf/T)
return istype(T)
@@ -39,13 +38,13 @@
if(scan_results[2])
survey_data += scan_results[2]
playsound(loc, 'sound/machines/ping.ogg', 40, 1)
- to_chat(user, SPAN_NOTICE("New survey data stored - earned [scan_results[2]] GEP."))
+ to_chat(user, SPAN_NOTICE("New survey data stored - earned [scan_results[2]] survey points."))
/obj/item/scanner/mining/proc/put_disk_in_hand(var/mob/M)
if(!survey_data)
to_chat(M, SPAN_WARNING("There is no survey data stored on \the [src]."))
return FALSE
- visible_message(SPAN_NOTICE("\The [src] spits out a disk containing [survey_data] GEP."))
+ visible_message(SPAN_NOTICE("\The [src] spits out a disk containing [survey_data] survey points."))
var/obj/item/disk/survey/D = new(get_turf(src))
D.data = survey_data
survey_data = 0
diff --git a/code/modules/scanners/xenobio.dm b/code/modules/scanners/xenobio.dm
index 3fc507c50dec..b7afc80b7a63 100644
--- a/code/modules/scanners/xenobio.dm
+++ b/code/modules/scanners/xenobio.dm
@@ -20,9 +20,6 @@
/obj/item/scanner/xenobio/is_valid_scan_target(atom/O)
if(is_type_in_list(O, valid_targets))
return TRUE
- if(istype(O, /obj/structure/stasis_cage))
- var/obj/structure/stasis_cage/cagie = O
- return !!cagie.contained
return FALSE
/obj/item/scanner/xenobio/scan(mob/O, mob/user)
diff --git a/code/modules/species/species.dm b/code/modules/species/species.dm
index 157801fcf74e..8224ac158376 100644
--- a/code/modules/species/species.dm
+++ b/code/modules/species/species.dm
@@ -404,23 +404,14 @@ var/global/const/DEFAULT_SPECIES_HEALTH = 200
if(taste_sensitivity < 0)
. += "taste_sensitivity ([taste_sensitivity]) was negative"
-/decl/species/proc/equip_survival_gear(mob/living/wearer, extended)
-
- var/box_type
- var/decl/survival_box_option/chosen_survival_box = wearer?.client?.prefs.survival_box_choice
- if(chosen_survival_box?.box_type)
- box_type = chosen_survival_box?.box_type
- else if(extended)
- box_type = /obj/item/box/engineer
+/decl/species/proc/equip_survival_gear(mob/living/wearer, box_type = /obj/item/box/survival)
+ if(!box_type)
+ return
+ var/obj/item/backpack/backpack = wearer.get_equipped_item(slot_back_str)
+ if(istype(backpack))
+ wearer.equip_to_slot_or_del(new box_type(backpack), slot_in_backpack_str)
else
- box_type = /obj/item/box/survival
-
- if(box_type)
- var/obj/item/backpack/backpack = wearer.get_equipped_item(slot_back_str)
- if(istype(backpack))
- wearer.equip_to_slot_or_del(new box_type(backpack), slot_in_backpack_str)
- else
- wearer.put_in_hands_or_del(new box_type(wearer))
+ wearer.put_in_hands_or_del(new box_type(wearer))
/decl/species/proc/get_manual_dexterity(var/mob/living/human/H)
. = manual_dexterity
diff --git a/code/modules/vehicles/bike.dm b/code/modules/vehicles/bike.dm
index 7522827dc86f..49e6274920d1 100644
--- a/code/modules/vehicles/bike.dm
+++ b/code/modules/vehicles/bike.dm
@@ -32,7 +32,7 @@
update_icon()
/obj/vehicle/bike/user_buckle_mob(mob/living/M, mob/user)
- return load(M)
+ return load_onto_vehicle(M)
/obj/vehicle/bike/verb/toggle()
set name = "Toggle Engine"
@@ -89,11 +89,12 @@
qdel(trail)
trail = null
-/obj/vehicle/bike/load(var/atom/movable/loading)
+/obj/vehicle/bike/load_onto_vehicle(var/atom/movable/loading)
+ if(!isliving(loading))
+ return FALSE
var/mob/living/M = loading
- if(!istype(M)) return 0
if(M.buckled || M.anchored || M.restrained() || !Adjacent(M) || !M.Adjacent(src))
- return 0
+ return FALSE
return ..(M)
/obj/vehicle/bike/emp_act(var/severity)
@@ -124,14 +125,14 @@
/obj/vehicle/bike/receive_mouse_drop(atom/dropping, mob/user, params)
. = ..()
if(!. && istype(dropping, /atom/movable))
- if(!load(dropping))
+ if(!load_onto_vehicle(dropping))
to_chat(user, SPAN_WARNING("You were unable to load \the [dropping] onto \the [src]."))
return TRUE
/obj/vehicle/bike/attack_hand(var/mob/user)
if(user != load)
return ..()
- unload(load)
+ unload_from_vehicle(load)
to_chat(user, "You unbuckle yourself from \the [src].")
return TRUE
@@ -139,7 +140,7 @@
if(user != load || !on)
return
if(user.incapacitated())
- unload(user)
+ unload_from_vehicle(user)
visible_message("\The [user] falls off \the [src]!")
return
return Move(get_step(src, direction))
diff --git a/code/modules/vehicles/cargo_train.dm b/code/modules/vehicles/cargo_train.dm
index f828d2f4e1c5..4fcb9ee46ce8 100644
--- a/code/modules/vehicles/cargo_train.dm
+++ b/code/modules/vehicles/cargo_train.dm
@@ -1,18 +1,21 @@
-/obj/vehicle/train/cargo/engine
+/obj/vehicle/train/engine
name = "cargo train tug"
- desc = "A rideable electric car designed for pulling cargo trolleys."
+ desc = "A ridable electric car designed for pulling cargo trolleys."
icon = 'icons/obj/vehicles.dmi'
icon_state = "cargo_engine"
on = 0
powered = 1
locked = 0
+
load_item_visible = 1
load_offset_x = 0
buckle_pixel_shift = list("x" = 0, "y" = 0, "z" = 7)
charge_use = 1 KILOWATTS
active_engines = 1
+
var/car_limit = 3 //how many cars an engine can pull before performance degrades
- var/obj/item/key/cargo_train/key
+ var/obj/item/key/key
+ var/key_type = /obj/item/key/cargo_train
/obj/item/key/cargo_train
desc = "A small key on a yellow fob reading \"Choo Choo!\"."
@@ -24,34 +27,24 @@
icon_state = "train_keys"
w_class = ITEM_SIZE_TINY
-/obj/vehicle/train/cargo/trolley
- name = "cargo train trolley"
- icon = 'icons/obj/vehicles.dmi'
- icon_state = "cargo_trailer"
- anchored = FALSE
- passenger_allowed = 0
- locked = 0
- buckle_pixel_shift = list("x" = 0, "y" = 0, "z" = 8)
-
- load_item_visible = 1
- load_offset_x = 0
- load_offset_y = 4
-
-
//-------------------------------------------
// Standard procs
//-------------------------------------------
-/obj/vehicle/train/cargo/engine/Initialize()
+/obj/vehicle/train/engine/Initialize()
. = ..()
cell = new /obj/item/cell/high(src)
- key = new(src)
- var/image/I = new(icon = icon, icon_state = "cargo_engine_overlay")
+ key = new key_type(src)
+ update_icon()
+ turn_off() //so engine verbs are correctly set
+
+/obj/vehicle/train/engine/on_update_icon()
+ . = ..()
+ var/image/I = image(icon, "cargo_engine_overlay")
I.plane = plane
I.layer = layer
- overlays += I
- turn_off() //so engine verbs are correctly set
+ set_overlays(I)
-/obj/vehicle/train/cargo/engine/Move(var/turf/destination)
+/obj/vehicle/train/engine/Move(var/turf/destination)
if(on && cell.charge < (charge_use * CELLRATE))
turn_off()
update_stats()
@@ -67,46 +60,23 @@
return ..()
-/obj/vehicle/train/cargo/trolley/attackby(obj/item/used_item, mob/user)
- if(open && IS_WIRECUTTER(used_item))
- passenger_allowed = !passenger_allowed
- user.visible_message("[user] [passenger_allowed ? "cuts" : "mends"] a cable in [src].","You [passenger_allowed ? "cut" : "mend"] the load limiter cable.")
- return TRUE
- return ..()
-
-/obj/vehicle/train/cargo/engine/attackby(obj/item/used_item, mob/user)
- if(istype(used_item, /obj/item/key/cargo_train))
+/obj/vehicle/train/engine/attackby(obj/item/used_item, mob/user)
+ if(istype(used_item, key_type))
if(!key && user.try_unequip(used_item, src))
key = used_item
- verbs += /obj/vehicle/train/cargo/engine/verb/remove_key
+ verbs |= /obj/vehicle/train/engine/verb/remove_key
return TRUE
return ..()
-//cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead
-/obj/vehicle/train/cargo/bullet_act(var/obj/item/projectile/Proj)
- if(buckled_mob && prob(70))
- buckled_mob.bullet_act(Proj)
- return
- ..()
-
-/obj/vehicle/train/cargo/on_update_icon()
- if(open)
- icon_state = initial(icon_state) + "_open"
- else
- icon_state = initial(icon_state)
-
-/obj/vehicle/train/cargo/trolley/insert_cell(var/obj/item/cell/cell, var/mob/living/human/H)
- return
-
-/obj/vehicle/train/cargo/engine/insert_cell(var/obj/item/cell/cell, var/mob/living/human/H)
+/obj/vehicle/train/engine/insert_cell(var/obj/item/cell/cell, var/mob/living/human/H)
..()
update_stats()
-/obj/vehicle/train/cargo/engine/remove_cell(var/mob/living/human/H)
+/obj/vehicle/train/engine/remove_cell(var/mob/living/human/H)
..()
update_stats()
-/obj/vehicle/train/cargo/engine/Bump(atom/Obstacle)
+/obj/vehicle/train/engine/Bump(atom/Obstacle)
var/obj/machinery/door/D = Obstacle
var/mob/living/human/H = load
if(istype(D) && istype(H))
@@ -114,52 +84,47 @@
..()
-/obj/vehicle/train/cargo/trolley/Bump(atom/Obstacle)
- if(!lead)
- return //so people can't knock others over by pushing a trolley around
- ..()
-
//-------------------------------------------
// Train procs
//-------------------------------------------
-/obj/vehicle/train/cargo/engine/turn_on()
+/obj/vehicle/train/engine/turn_on()
if(!key)
return
+ if(!cell)
+ return
+ ..()
+ update_stats()
+ if(on)
+ verbs |= /obj/vehicle/train/engine/verb/stop_engine
+ verbs -= /obj/vehicle/train/engine/verb/start_engine
else
- ..()
- update_stats()
-
- verbs -= /obj/vehicle/train/cargo/engine/verb/stop_engine
- verbs -= /obj/vehicle/train/cargo/engine/verb/start_engine
-
- if(on)
- verbs += /obj/vehicle/train/cargo/engine/verb/stop_engine
- else
- verbs += /obj/vehicle/train/cargo/engine/verb/start_engine
+ verbs |= /obj/vehicle/train/engine/verb/start_engine
+ verbs -= /obj/vehicle/train/engine/verb/stop_engine
-/obj/vehicle/train/cargo/engine/turn_off()
+/obj/vehicle/train/engine/turn_off()
..()
+ if(!on)
+ verbs |= /obj/vehicle/train/engine/verb/start_engine
+ verbs -= /obj/vehicle/train/engine/verb/stop_engine
+ else
+ verbs |= /obj/vehicle/train/engine/verb/stop_engine
+ verbs -= /obj/vehicle/train/engine/verb/start_engine
- verbs -= /obj/vehicle/train/cargo/engine/verb/stop_engine
- verbs -= /obj/vehicle/train/cargo/engine/verb/start_engine
- if(!on)
- verbs += /obj/vehicle/train/cargo/engine/verb/start_engine
+/obj/vehicle/train/engine/on_update_icon()
+ if(open)
+ icon_state = initial(icon_state) + "_open"
else
- verbs += /obj/vehicle/train/cargo/engine/verb/stop_engine
+ icon_state = initial(icon_state)
-/obj/vehicle/train/cargo/crossed_mob(var/mob/living/victim)
+/obj/vehicle/train/engine/crossed_mob(var/mob/living/victim)
victim.apply_effects(5, 5)
for(var/i = 1 to rand(1,5))
var/obj/item/organ/external/E = pick(victim.get_external_organs())
if(E)
victim.apply_damage(rand(5,10), BRUTE, E.organ_tag)
-/obj/vehicle/train/cargo/trolley/crossed_mob(var/mob/living/victim)
- ..()
- attack_log += text("\[[time_stamp()]\] ran over [victim.name] ([victim.ckey])")
-
-/obj/vehicle/train/cargo/engine/crossed_mob(var/mob/living/victim)
+/obj/vehicle/train/engine/crossed_mob(var/mob/living/victim)
..()
if(is_train_head() && ishuman(load))
var/mob/living/human/D = load
@@ -170,161 +135,96 @@
else
attack_log += text("\[[time_stamp()]\] ran over [victim.name] ([victim.ckey])")
-
//-------------------------------------------
// Interaction procs
//-------------------------------------------
-/obj/vehicle/train/cargo/engine/relaymove(mob/user, direction)
+/obj/vehicle/train/engine/relaymove(mob/user, direction)
if(user != load || user.incapacitated())
- return 0
-
+ return FALSE
if(is_train_head())
if(direction == global.reverse_dir[dir] && tow)
- return 0
+ return FALSE
if(Move(get_step(src, direction)))
- return 1
- return 0
- else
- return ..()
+ return TRUE
+ return FALSE
+ return ..()
-/obj/vehicle/train/cargo/engine/get_examine_strings(mob/user, distance, infix, suffix)
+/obj/vehicle/train/engine/get_examine_strings(mob/user, distance, infix, suffix)
. = ..()
if(distance <= 1)
. += "The power light is [on ? "on" : "off"].\nThere are[key ? "" : " no"] keys in the ignition."
. += "The charge meter reads [cell? round(cell.percent(), 0.01) : 0]%"
-/obj/vehicle/train/cargo/engine/verb/start_engine()
+/obj/vehicle/train/engine/verb/start_engine()
set name = "Start engine"
- set category = "Object"
+ set category = "Vehicle"
set src in view(0)
if(!ishuman(usr))
return
if(on)
- to_chat(usr, "The engine is already running.")
+ to_chat(usr, SPAN_WARNING("The engine is already running."))
return
turn_on()
if (on)
- to_chat(usr, "You start [src]'s engine.")
+ to_chat(usr, SPAN_NOTICE("You start \the [src]'s engine."))
else
- if(cell.charge < charge_use)
- to_chat(usr, "[src] is out of power.")
+ if(!cell)
+ to_chat(usr, SPAN_NOTICE("\The [src] doesn't appear to have a power cell!"))
+ else if(cell.charge < charge_use)
+ to_chat(usr, SPAN_NOTICE("\The [src] is out of power."))
else
- to_chat(usr, "[src]'s engine won't start.")
+ to_chat(usr, SPAN_NOTICE("\The [src]'s engine won't start."))
-/obj/vehicle/train/cargo/engine/verb/stop_engine()
+/obj/vehicle/train/engine/verb/stop_engine()
set name = "Stop engine"
- set category = "Object"
+ set category = "Vehicle"
set src in view(0)
if(!ishuman(usr))
return
if(!on)
- to_chat(usr, "The engine is already stopped.")
+ to_chat(usr, SPAN_WARNING("The engine is already stopped."))
return
turn_off()
if (!on)
- to_chat(usr, "You stop [src]'s engine.")
+ to_chat(usr, SPAN_NOTICE("You stop [src]'s engine."))
-/obj/vehicle/train/cargo/engine/verb/remove_key()
+/obj/vehicle/train/engine/verb/remove_key()
set name = "Remove key"
- set category = "Object"
+ set category = "Vehicle"
set src in view(0)
- if(!ishuman(usr))
- return
-
- if(!key || (load && load != usr))
+ if(!isliving(usr) || !key || (load && load != usr))
return
if(on)
turn_off()
+ var/mob/living/user = usr
+ key.dropInto(get_turf(user))
usr.put_in_hands(key)
key = null
+ verbs -= /obj/vehicle/train/engine/verb/remove_key
- verbs -= /obj/vehicle/train/cargo/engine/verb/remove_key
-
-//-------------------------------------------
-// Loading/unloading procs
-//-------------------------------------------
-/obj/vehicle/train/cargo/trolley/load(var/atom/movable/loading)
- if(ismob(loading) && !passenger_allowed)
- return 0
- if(!istype(loading,/obj/machinery) && !istype(loading,/obj/structure/closet) && !istype(loading,/obj/structure/largecrate) && !istype(loading,/obj/structure/reagent_dispensers) && !istype(loading,/obj/structure/ore_box) && !ishuman(loading))
- return 0
-
- //if there are any items you don't want to be able to interact with, add them to this check
- // ~no more shielded, emitter armed death trains
- if(istype(loading, /obj/machinery))
- load_object(loading)
- else
- ..()
-
- if(load)
- return 1
-
-/obj/vehicle/train/cargo/engine/load(var/atom/movable/loading)
- if(!ishuman(loading))
- return 0
-
- return ..()
-
-//Load the object "inside" the trolley and add an overlay of it.
-//This prevents the object from being interacted with until it has
-// been unloaded. A dummy object is loaded instead so the loading
-// code knows to handle it correctly.
-/obj/vehicle/train/cargo/trolley/proc/load_object(var/atom/movable/loading)
- if(!isturf(loading.loc)) //To prevent loading things from someone's inventory, which wouldn't get handled properly.
- return 0
- if(load || loading.anchored)
- return 0
-
- var/datum/vehicle_dummy_load/dummy_load = new()
- load = dummy_load
-
- if(!load)
- return
- dummy_load.actual_load = loading
- loading.forceMove(src)
-
- if(load_item_visible)
- loading.pixel_x += load_offset_x
- loading.pixel_y += load_offset_y
- loading.plane = plane
- loading.layer = VEHICLE_LOAD_LAYER
-
- overlays += loading
-
- //we can set these back now since we have already cloned the icon into the overlay
- loading.pixel_x = initial(loading.pixel_x)
- loading.pixel_y = initial(loading.pixel_y)
- loading.reset_plane_and_layer()
-
-/obj/vehicle/train/cargo/trolley/unload(var/mob/user, var/direction)
- if(istype(load, /datum/vehicle_dummy_load))
- var/datum/vehicle_dummy_load/dummy_load = load
- load = dummy_load.actual_load
- dummy_load.actual_load = null
- qdel(dummy_load)
- overlays.Cut()
- ..()
+/obj/vehicle/train/engine/load_onto_vehicle(var/atom/movable/loading, var/mob/user)
+ return istype(loading, /mob/living/human) && ..()
//-------------------------------------------
// Latching/unlatching procs
//-------------------------------------------
-/obj/vehicle/train/cargo/engine/latch(obj/vehicle/train/T, mob/user)
+/obj/vehicle/train/engine/latch(obj/vehicle/train/T, mob/user)
if(!istype(T) || !Adjacent(T))
return 0
//if we are attaching a trolley to an engine we don't care what direction
// it is in and it should probably be attached with the engine in the lead
- if(istype(T, /obj/vehicle/train/cargo/trolley))
+ if(istype(T, /obj/vehicle/train/trolley))
T.attach_to(src, user)
else
var/T_dir = get_dir(src, T) //figure out where T is wrt src
@@ -345,24 +245,40 @@
// more engines increases this limit by car_limit per
// engine.
//-------------------------------------------------------
-/obj/vehicle/train/cargo/engine/update_car(var/train_length, var/active_engines)
- src.train_length = train_length
- src.active_engines = active_engines
+/obj/vehicle/train/engine/update_vehicle_move_delay(atom/prev_loc)
+ ..()
+ if(is_train_head() && on)
+ move_delay = max(move_delay, (-car_limit * active_engines) + train_length - active_engines) //limits base overweight so you can't overspeed trains
+ move_delay *= (1 / max(1, active_engines)) * 2 //overweight penalty (scaled by the number of engines)
- //Update move delay
- if(!is_train_head() || !on)
- move_delay = initial(move_delay) //so that engines that have been turned off don't lag behind
- else
- move_delay = max(0, (-car_limit * active_engines) + train_length - active_engines) // limits base overweight so you cant overspeed trains
- move_delay *= (1 / max(1, active_engines)) * 2 // overweight penalty (scaled by the number of engines)
- move_delay += get_config_value(/decl/config/num/movement_run) // base reference speed
- move_delay *= 1.1 // makes cargo trains 10% slower than running when not overweight
+/obj/vehicle/train/engine/get_alt_interactions(mob/user)
+ . = ..()
+ LAZYADD(., /decl/interaction_handler/train/toggle_ignition)
+ if(key)
+ LAZYADD(., /decl/interaction_handler/train/remove_key)
-/obj/vehicle/train/cargo/trolley/update_car(var/train_length, var/active_engines)
- src.train_length = train_length
- src.active_engines = active_engines
+/decl/interaction_handler/train
+ abstract_type = /decl/interaction_handler/train
+ expected_target_type = /obj/vehicle/train/engine
- if(!lead && !tow)
- anchored = FALSE
+/decl/interaction_handler/train/toggle_ignition
+ name = "Toggle Ignition"
+
+/decl/interaction_handler/train/toggle_ignition/invoked(atom/target, mob/user, obj/item/prop)
+ var/obj/vehicle/train/engine/train = target
+ if(train.on)
+ train.stop_engine()
else
- anchored = TRUE
+ train.start_engine()
+
+/decl/interaction_handler/train/remove_key
+ name = "Remove Key"
+
+/decl/interaction_handler/train/remove_key/is_possible(atom/target, mob/user, obj/item/prop)
+ if((. = ..()))
+ var/obj/vehicle/train/engine/train = target
+ return train.key
+
+/decl/interaction_handler/train/remove_key/invoked(atom/target, mob/user, obj/item/prop)
+ var/obj/vehicle/train/engine/train = target
+ train.remove_key()
diff --git a/code/modules/vehicles/cargo_trolley.dm b/code/modules/vehicles/cargo_trolley.dm
new file mode 100644
index 000000000000..f41875d4cd96
--- /dev/null
+++ b/code/modules/vehicles/cargo_trolley.dm
@@ -0,0 +1,103 @@
+/obj/vehicle/train/trolley
+ name = "cargo train trolley"
+ desc = "A large, flat platform made for putting things on."
+ icon = 'icons/obj/vehicles.dmi'
+ icon_state = "cargo_trailer"
+ anchored = FALSE
+ passenger_allowed = 0
+ locked = 0
+ buckle_pixel_shift = list("x" = 0, "y" = 0, "z" = 8)
+
+ load_item_visible = 1
+ load_offset_x = 0
+ load_offset_y = 4
+
+ var/static/list/can_load_types = list(
+ /obj/machinery,
+ /obj/structure/closet,
+ /obj/structure/largecrate,
+ /obj/structure/reagent_dispensers,
+ /obj/structure/ore_box,
+ /mob/living/human
+ )
+
+/obj/vehicle/train/trolley/insert_cell(var/obj/item/cell/cell, var/mob/living/human/H)
+ return
+
+//-------------------------------------------
+// Loading/unloading procs
+//-------------------------------------------
+/obj/vehicle/train/trolley/load_onto_vehicle(var/atom/movable/loading, var/mob/user)
+ if(ismob(loading) && !passenger_allowed)
+ return 0
+ if(!is_type_in_list(loading, can_load_types))
+ return 0
+ //if there are any items you don't want to be able to interact with, add them to this check
+ // ~no more shielded, emitter armed death trains
+ if(istype(loading, /obj/machinery))
+ load_object(loading)
+ else
+ ..(loading, user)
+ return !!load
+
+//Load the object "inside" the trolley and add an overlay of it.
+//This prevents the object from being interacted with until it has
+// been unloaded. A dummy object is loaded instead so the loading
+// code knows to handle it correctly.
+/obj/vehicle/train/trolley/proc/load_object(var/atom/movable/loading)
+ //To prevent loading things from someone's inventory, which wouldn't get handled properly.
+ if(!isturf(loading.loc) || load || loading.anchored)
+ return 0
+ var/datum/vehicle_dummy_load/dummy_load = new
+ dummy_load.actual_load = loading
+ load = dummy_load
+ loading.forceMove(src)
+ update_icon()
+
+/obj/vehicle/train/trolley/unload_from_vehicle(var/mob/user, var/direction)
+ if(istype(load, /datum/vehicle_dummy_load))
+ var/datum/vehicle_dummy_load/dummy_load = load
+ load = dummy_load.actual_load
+ dummy_load.actual_load = null
+ qdel(dummy_load)
+ update_icon()
+ ..()
+
+/obj/vehicle/train/trolley/on_update_icon()
+ cut_overlays()
+ var/datum/vehicle_dummy_load/dummy_load = load
+ if(istype(dummy_load) && dummy_load.actual_load && load_item_visible)
+ var/atom/movable/loading = dummy_load.actual_load
+ loading.pixel_x += load_offset_x
+ loading.pixel_y += load_offset_y
+ loading.plane = plane
+ loading.layer = VEHICLE_LOAD_LAYER
+ add_overlay(loading)
+ compile_overlays() // We want to reset the pixel values on our load after this.
+ //we can set these back now since we have already cloned the icon into the overlay
+ loading.pixel_x = initial(loading.pixel_x)
+ loading.pixel_y = initial(loading.pixel_y)
+ loading.layer = initial(loading.layer)
+
+/obj/vehicle/train/trolley/update_car(var/train_length, var/active_engines)
+ ..()
+ anchored = lead || tow
+
+/obj/vehicle/train/trolley/Bump(atom/Obstacle)
+ if(!lead)
+ return //so people can't knock others over by pushing a trolley around
+ ..()
+
+/obj/vehicle/train/trolley/attackby(obj/item/used_item, mob/user)
+ if(open && IS_WIRECUTTER(used_item))
+ passenger_allowed = !passenger_allowed
+ user.visible_message(
+ SPAN_NOTICE("\The [user] [passenger_allowed ? "cuts" : "mends"] a cable in [src]."),
+ SPAN_NOTICE("You [passenger_allowed ? "cut" : "mend"] the load limiter cable.")
+ )
+ return TRUE
+ return ..()
+
+/obj/vehicle/train/trolley/crossed_mob(var/mob/living/victim)
+ ..()
+ attack_log += text("\[[time_stamp()]\] ran over [victim.name] ([victim.ckey])")
diff --git a/code/modules/vehicles/quad_bike.dm b/code/modules/vehicles/quad_bike.dm
new file mode 100644
index 000000000000..3d01876bc161
--- /dev/null
+++ b/code/modules/vehicles/quad_bike.dm
@@ -0,0 +1,198 @@
+/obj/vehicle/train/engine/quadbike //It's a train engine, so it can tow trailers.
+ name = "electric all terrain vehicle"
+ desc = "A ridable electric ATV designed for all terrain. Except space."
+ icon = 'icons/obj/vehicles_64x64.dmi'
+ icon_state = "quad"
+ on = 0
+ powered = 1
+ locked = 0
+ load_item_visible = 1
+ load_offset_x = 0
+ buckle_pixel_shift = list("x" = 0, "y" = 0, "z" = 5)
+ pixel_x = -16
+ base_speed = 0.45
+ car_limit = 1 //It gets a trailer. That's about it.
+ active_engines = 1
+ key_type = /obj/item/key/quadbike
+ paint_color = "#ffffff"
+ layer = OBJ_LAYER
+ vehicle_transit_type = VEHICLE_QUADBIKE
+
+ var/frame_state = "quad" //Custom-item proofing!
+ var/paint_base = 'icons/obj/vehicles_64x64.dmi'
+ var/custom_frame = FALSE
+ var/datum/composite_sound/vehicle_engine/soundloop
+
+/obj/vehicle/train/engine/quadbike/Initialize()
+ cell = new /obj/item/cell/high(src)
+ key = new key_type(src)
+ soundloop = new(list(src), FALSE)
+ . = ..()
+ turn_off()
+ update_icon()
+
+/obj/vehicle/train/engine/quadbike/built/Initialize()
+ key = new key_type(src)
+ . = ..()
+ turn_off()
+
+/obj/vehicle/train/engine/quadbike/random/Initialize()
+ paint_color = rgb(rand(1,255),rand(1,255),rand(1,255))
+ . = ..()
+
+/obj/vehicle/train/engine/quadbike/Destroy()
+ QDEL_NULL(soundloop)
+ return ..()
+
+/obj/item/key/quadbike
+ name = "key"
+ desc = "A keyring with a small steel key, and a blue fob reading \"ZOOM!\"."
+ icon = 'icons/obj/vehicles.dmi'
+ icon_state = "quad_keys"
+ w_class = ITEM_SIZE_TINY
+
+/obj/vehicle/train/engine/quadbike/forceMove(turf/destination)
+ var/atom/old_loc = loc
+ if((. = ..()))
+ update_vehicle_move_delay(old_loc)
+ handle_vehicle_icon()
+
+/obj/vehicle/train/engine/quadbike/Move(turf/destination)
+ var/atom/old_loc = loc
+ if((. = ..()))
+ update_vehicle_move_delay(old_loc)
+ handle_vehicle_icon()
+
+/obj/vehicle/train/engine/quadbike/update_vehicle_move_delay(atom/prev_loc)
+ ..()
+ update_car(train_length, active_engines)
+
+/obj/vehicle/train/engine/quadbike/proc/handle_vehicle_icon()
+ switch(dir) //Due to being a Big Boy sprite, it has to have special pixel shifting to look 'normal' when being driven.
+ if(1)
+ pixel_y = -6
+ if(2)
+ pixel_y = -6
+ if(4)
+ pixel_y = 0
+ if(8)
+ pixel_y = 0
+
+/obj/vehicle/train/engine/quadbike/attackby(obj/item/used_item, mob/user)
+ if(istype(used_item, /obj/item/multitool) && open)
+ var/new_paint = input("Please select a paint color.", "Trailer Color", paint_color) as color|null
+ if(new_paint && !QDELETED(src) && !QDELETED(used_item) && !QDELETED(user) && !user.incapacitated() && user.get_active_held_item() == used_item)
+ paint_color = new_paint
+ update_icon()
+ return TRUE
+ return ..()
+
+/obj/vehicle/train/engine/quadbike/on_update_icon()
+ ..()
+ cut_overlays()
+
+ if(custom_frame)
+ var/image/Bodypaint = new(icon = 'icons/obj/custom_items_vehicle.dmi', icon_state = "[frame_state]_a")
+ Bodypaint.layer = layer
+ Bodypaint.color = paint_color
+ add_overlay(Bodypaint)
+
+ var/image/Overmob = new(icon = 'icons/obj/custom_items_vehicle.dmi', icon_state = "[frame_state]_overlay") //over mobs
+ var/image/Overmob_color = new(icon = 'icons/obj/custom_items_vehicle.dmi', icon_state = "[frame_state]_overlay_a") //over the over mobs, gives the color.
+ Overmob.layer = layer + 0.2
+ Overmob_color.layer = layer + 0.2
+ Overmob_color.color = paint_color
+ add_overlay(Overmob)
+ add_overlay(Overmob_color)
+ return
+
+ var/image/Bodypaint = new(icon = paint_base, icon_state = "[frame_state]_a", layer = src.layer)
+ Bodypaint.color = paint_color
+ add_overlay(Bodypaint)
+
+ var/image/Overmob = new(icon = paint_base, icon_state = "[frame_state]_overlay", layer = src.layer + 0.2) //over mobs
+ var/image/Overmob_color = new(icon = paint_base, icon_state = "[frame_state]_overlay_a", layer = src.layer + 0.2) //over the over mobs, gives the color.
+ Overmob.layer = ABOVE_HUMAN_LAYER
+ Overmob_color.layer = ABOVE_HUMAN_LAYER
+ Overmob_color.color = paint_color
+
+ add_overlay(Overmob)
+ add_overlay(Overmob_color)
+
+/obj/vehicle/train/engine/quadbike/Bump(atom/Obstacle)
+ if(!istype(Obstacle, /atom/movable))
+ return
+ var/atom/movable/A = Obstacle
+
+ if(!A.anchored)
+ var/turf/T = get_step(A, dir)
+ if(isturf(T))
+ A.Move(T) //bump things away when hit
+
+ if(istype(A, /mob/living))
+ var/mob/living/M = A
+ visible_message(SPAN_DANGER("\The [src] knocks over \the [M]!"))
+ M.apply_effects(2, 2) // Knock people down for a short moment
+ M.apply_damages(8 / move_delay) // Smaller amount of damage than a tug, since this will always be possible because Quads don't have safeties.
+ var/list/throw_dirs = all_throw_dirs.Copy()
+ if(!emagged) // By the power of Bumpers TM, it won't throw them ahead of the quad's path unless it's emagged or the person turns.
+ take_damage(round(M.mob_size / 2))
+ throw_dirs -= dir
+ throw_dirs -= get_dir(M, src) //Don't throw it AT the quad either.
+ else
+ take_damage(round(M.mob_size / 4)) // Less damage if they actually put the point in to emag it.
+ var/turf/T2 = get_step(A, pick(throw_dirs))
+ M.throw_at(T2, 1, 1, src)
+ if(isliving(load))
+ var/mob/living/D = load
+ to_chat(D, SPAN_DANGER("You hit \the [M]!"))
+ admin_attack_log(D, M, "Ran over with [src.name]")
+
+/obj/vehicle/train/engine/quadbike/crossed_mob(mob/living/victim)
+ . = ..()
+ var/list/throw_dirs = all_throw_dirs.Copy()
+ if(!emagged)
+ throw_dirs -= dir
+ if(tow)
+ throw_dirs -= get_dir(victim, tow) //Don't throw it at the trailer either.
+ var/turf/T = get_step(victim, pick(throw_dirs))
+ victim.throw_at(T, 1, 1, src)
+
+/obj/vehicle/train/engine/quadbike/turn_on()
+ ..()
+ if(on)
+ visible_message(SPAN_NOTICE("\The [src] rumbles to life."), "You hear something rumble deeply.")
+ soundloop.start()
+
+/obj/vehicle/train/engine/quadbike/turn_off()
+ if(on)
+ visible_message(SPAN_NOTICE("\The [src] putters before turning off."), "You hear something putter slowly.")
+ soundloop.stop()
+ ..()
+
+/obj/vehicle/train/engine/quadbike/snowmobile
+ name = "snowmobile"
+ desc = "An electric snowmobile for traversing snow and ice with ease! Other terrain, not so much."
+ icon = 'icons/obj/vehicles.dmi'
+ icon_state = "snowmobile"
+ load_item_visible = 1
+ base_speed = 0.6
+ car_limit = 0
+ key_type = /obj/item/key/snowmobile
+ frame_state = "snowmobile"
+ paint_base = 'icons/obj/vehicles.dmi'
+ pixel_x = 0
+ water_delay = 6
+
+/obj/item/key/snowmobile
+ name = "key"
+ desc = "A keyring with an ice-blue fob reading \"CHILL\"."
+ icon = 'icons/obj/vehicles.dmi'
+ icon_state = "sno_keys"
+
+/obj/vehicle/train/engine/quadbike/snowmobile/random/Initialize()
+ paint_color = rgb(rand(1,255),rand(1,255),rand(1,255))
+ . = ..()
+
+/obj/vehicle/train/engine/quadbike/snowmobile/handle_vehicle_icon()
+ return
diff --git a/code/modules/vehicles/quad_trailer.dm b/code/modules/vehicles/quad_trailer.dm
new file mode 100644
index 000000000000..a730ab54b656
--- /dev/null
+++ b/code/modules/vehicles/quad_trailer.dm
@@ -0,0 +1,108 @@
+/*
+ * Trailer bits and bobs.
+ */
+/obj/vehicle/train/trolley/trailer
+ name = "all terrain trailer"
+ icon = 'icons/obj/vehicles_64x64.dmi'
+ icon_state = "quadtrailer"
+ anchored = FALSE
+ passenger_allowed = 1
+ buckle_lying = 1
+ locked = 0
+ load_item_visible = 1
+ load_offset_x = 0
+ load_offset_y = 13
+ buckle_pixel_shift = list("x" = 0, "y" = 0, "z" = 16)
+ pixel_x = -16
+ paint_color = "#ffffff"
+ var/mob_offset_y = 16
+
+/obj/vehicle/train/trolley/trailer/random/Initialize()
+ paint_color = rgb(rand(1,255),rand(1,255),rand(1,255))
+ . = ..()
+
+/obj/vehicle/train/trolley/trailer/proc/update_load()
+ if(load)
+ var/y_offset = load_offset_y
+ if(istype(load, /mob/living))
+ y_offset = mob_offset_y
+ load.pixel_x = (initial(load.pixel_x) + 16 + load_offset_x + pixel_x) //Base location for the sprite, plus 16 to center it on the 'base' sprite of the trailer, plus the x shift of the trailer, then shift it by the same pixel_x as the trailer to track it.
+ load.pixel_y = (initial(load.pixel_y) + y_offset + pixel_y) //Same as the above.
+ return 1
+ return 0
+/obj/vehicle/train/trolley/trailer/Initialize()
+ . = ..()
+ update_icon()
+
+/obj/vehicle/train/trolley/trailer/Move()
+ var/atom/old_loc = loc
+ if((. = ..()))
+ update_trolley_offset(old_loc)
+
+/obj/vehicle/train/trolley/trailer/forceMove()
+ var/atom/old_loc = loc
+ if((. = ..()))
+ update_trolley_offset(old_loc)
+
+/obj/vehicle/train/trolley/trailer/proc/update_trolley_offset(var/atom/old_loc)
+ if(lead)
+ switch(dir) //Due to being a Big Boy sprite, it has to have special pixel shifting to look 'normal'.
+ if(1)
+ default_pixel_y = -10
+ default_pixel_x = -16
+ if(2)
+ default_pixel_y = 0
+ default_pixel_x = -16
+ if(4)
+ default_pixel_y = 0
+ default_pixel_x = -25
+ if(8)
+ default_pixel_y = 0
+ default_pixel_x = -5
+ else
+ default_pixel_x = initial(default_pixel_x)
+ default_pixel_y = initial(default_pixel_y)
+ reset_offsets(0)
+ update_load()
+
+/obj/vehicle/train/trolley/trailer/Bump(atom/Obstacle)
+ if(!istype(Obstacle, /atom/movable))
+ return
+
+ var/atom/movable/A = Obstacle
+ if(!A.anchored)
+ var/turf/T = get_step(A, dir)
+ if(isturf(T))
+ A.Move(T) //bump things away when hit
+
+ if(istype(A, /mob/living))
+ var/mob/living/M = A
+ visible_message(SPAN_DANGER("\The [src] knocks over \the [M]!"))
+ M.apply_effects(1, 1)
+ M.apply_damages(8 / move_delay)
+ if(load)
+ M.apply_damages(4/move_delay)
+ var/list/throw_dirs = all_throw_dirs.Copy()
+ if(!emagged)
+ throw_dirs -= dir
+ var/turf/T2 = get_step(A, pick(throw_dirs))
+ M.throw_at(T2, 1, 1, src)
+ if(isliving(load))
+ var/mob/living/D = load
+ to_chat(D, SPAN_DANGER("You hit \the [M]!"))
+ admin_attack_log(D, M, "Ran over with \the [src]")
+
+/obj/vehicle/train/trolley/trailer/on_update_icon()
+ ..()
+ var/image/Bodypaint = new(icon = icon, icon_state = "[initial(icon_state)]_a", layer = src.layer)
+ Bodypaint.color = paint_color
+ set_overlays(Bodypaint)
+
+/obj/vehicle/train/trolley/trailer/attackby(obj/item/W as obj, mob/user as mob)
+ if(istype(W, /obj/item/multitool) && open)
+ var/new_paint = input("Please select paint color.", "Paint Color", paint_color) as color|null
+ if(new_paint)
+ paint_color = new_paint
+ update_icon()
+ return
+ ..()
diff --git a/code/modules/vehicles/train.dm b/code/modules/vehicles/train.dm
index 0cb43501e1e8..59665c6b326f 100644
--- a/code/modules/vehicles/train.dm
+++ b/code/modules/vehicles/train.dm
@@ -5,17 +5,18 @@
max_health = 100
fire_dam_coeff = 0.7
brute_dam_coeff = 0.5
+ layer = ABOVE_HUMAN_LAYER
var/passenger_allowed = 1
-
var/active_engines = 0
var/train_length = 0
-
var/obj/vehicle/train/lead
var/obj/vehicle/train/tow
+ var/static/list/all_throw_dirs = list(NORTH, SOUTH, EAST, WEST, NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST)
+
/obj/vehicle/train/user_buckle_mob(mob/living/M, mob/user)
- return load(M)
+ return load_onto_vehicle(M)
//-------------------------------------------
// Standard procs
@@ -95,7 +96,7 @@
return 1
return 0
- unload(user, direction)
+ unload_from_vehicle(user, direction)
to_chat(user, "You climb down from [src].")
return 1
@@ -110,7 +111,7 @@
/obj/vehicle/train/receive_mouse_drop(atom/dropping, mob/user, params)
. = ..()
if(!. && istype(dropping, /atom/movable))
- if(!load(dropping))
+ if(!load_onto_vehicle(dropping))
to_chat(user, SPAN_WARNING("You were unable to load \the [dropping] onto \the [src]."))
return TRUE
@@ -121,9 +122,9 @@
if(user != load && (user in src))
user.forceMove(loc)
else if(load)
- unload(user)
+ unload_from_vehicle(user)
else if(!load && !user.buckled)
- load(user)
+ load_onto_vehicle(user)
return TRUE
/obj/vehicle/train/verb/unlatch_v()
@@ -235,5 +236,7 @@
T.update_car(train_length, active_engines)
T = T.lead
-/obj/vehicle/train/proc/update_car(var/train_length, var/active_engines)
- return
+/obj/vehicle/train/proc/update_car(var/_train_length, var/_active_engines)
+ SHOULD_CALL_PARENT(TRUE)
+ train_length = _train_length
+ active_engines = _active_engines
diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm
index 111eb94818e2..6086aa1fab26 100644
--- a/code/modules/vehicles/vehicle.dm
+++ b/code/modules/vehicles/vehicle.dm
@@ -6,7 +6,7 @@
/obj/vehicle
name = "vehicle"
icon = 'icons/obj/vehicles.dmi'
- layer = ABOVE_HUMAN_LAYER
+ layer = OBJ_LAYER
density = TRUE
anchored = TRUE
animate_movement=1
@@ -17,6 +17,11 @@
buckle_movable = 1
buckle_lying = 0
+ var/const/VEHICLE_GENERIC = 1
+ var/const/VEHICLE_QUADBIKE = 2
+ var/const/VEHICLE_SNOWMOBILE = 3
+
+ var/vehicle_transit_type = VEHICLE_GENERIC
var/attack_log = null
var/on = 0
var/fire_dam_coeff = 1.0
@@ -26,7 +31,13 @@
var/stat = 0
var/emagged = 0
var/powered = 0 //set if vehicle is powered and should use fuel when moving
- var/move_delay = 1 //set this to limit the speed of the vehicle
+
+ /// How long a single move takes with this vehicle.
+ var/move_delay = 1
+ /// The base delay of a move with this vehicle, assuming no terrain modifiers. If null, uses default running
+ var/base_speed
+ /// Speed when a location is flooded.
+ var/water_delay = 4
var/obj/item/cell/cell
var/charge_use = 200 // W
@@ -35,10 +46,31 @@
var/load_item_visible = 1 //set if the loaded item should be overlayed on the vehicle sprite
var/load_offset_x = 0 //pixel_x offset for item overlay
var/load_offset_y = 0 //pixel_y offset for item overlay
-
//-------------------------------------------
// Standard procs
//-------------------------------------------
+/obj/vehicle/Initialize(mapload)
+ update_vehicle_move_delay(null)
+ base_speed ||= get_config_value(/decl/config/num/movement_run)
+ . = ..()
+
+/obj/vehicle/proc/update_vehicle_move_delay(atom/prev_loc)
+
+ var/turf/floor/prev_turf = prev_loc
+ var/turf/floor/this_turf = loc
+ if(istype(prev_turf) && istype(this_turf) && this_turf.get_topmost_flooring() == prev_turf.get_topmost_flooring() && this_turf.check_fluid_depth(FLUID_SHALLOW) == prev_turf.check_fluid_depth(FLUID_SHALLOW))
+ return // Same speed if terrain type doesn't change
+
+ var/terrain_mod
+ if(loc?.check_fluid_depth(FLUID_SHALLOW))
+ terrain_mod = water_delay
+ else if(istype(this_turf))
+ terrain_mod = this_turf.get_vehicle_transit_delay(src)
+
+ if(isnull(terrain_mod))
+ move_delay = base_speed
+ else
+ move_delay = base_speed * terrain_mod
/obj/vehicle/Move()
if(world.time > l_move_time + move_delay)
@@ -162,7 +194,7 @@
/obj/vehicle/unbuckle_mob(mob/user)
. = ..(user)
if(load == .)
- unload(.)
+ unload_from_vehicle(.)
//-------------------------------------------
// Vehicle procs
@@ -207,7 +239,7 @@
var/mob/living/M = load
M.apply_effects(5, 5)
- unload()
+ unload_from_vehicle()
new /obj/effect/gibspawner/robot(my_turf)
new /obj/effect/decal/cleanable/blood/oil(src.loc)
@@ -261,7 +293,7 @@
// the vehicle load() definition before
// calling this parent proc.
//-------------------------------------------
-/obj/vehicle/proc/load(var/atom/movable/loading)
+/obj/vehicle/proc/load_onto_vehicle(var/atom/movable/loading)
//This loads objects onto the vehicle so they can still be interacted with.
//Define allowed items for loading in specific vehicle definitions.
if(!isturf(loading.loc)) //To prevent loading things from someone's inventory, which wouldn't get handled properly.
@@ -293,7 +325,7 @@
return 1
-/obj/vehicle/proc/unload(var/mob/user, var/direction)
+/obj/vehicle/proc/unload_from_vehicle(var/mob/user, var/direction)
if(!load)
return
diff --git a/code/modules/xenoarcheaology/artifacts/effects/gas_generation.dm b/code/modules/xenoarcheaology/artifacts/effects/gas_generation.dm
index 33ba58783526..95569c0bf38a 100644
--- a/code/modules/xenoarcheaology/artifacts/effects/gas_generation.dm
+++ b/code/modules/xenoarcheaology/artifacts/effects/gas_generation.dm
@@ -5,7 +5,7 @@
/datum/artifact_effect/gas/New()
..()
if(!spawned_gas)
- spawned_gas = pick(decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ spawned_gas = pick(get_filterable_material_types(as_list = TRUE))
operation_type = pick((XA_EFFECT_TOUCH), (XA_EFFECT_AURA))
origin_type = XA_EFFECT_SYNTH
diff --git a/code/modules/xenoarcheaology/artifacts/triggers/gas.dm b/code/modules/xenoarcheaology/artifacts/triggers/gas.dm
index 678dcc16af07..1dfa739951ba 100644
--- a/code/modules/xenoarcheaology/artifacts/triggers/gas.dm
+++ b/code/modules/xenoarcheaology/artifacts/triggers/gas.dm
@@ -5,7 +5,7 @@
/datum/artifact_trigger/gas/New()
if(!gas_needed)
- gas_needed = list(pick(decls_repository.get_decl_paths_of_subtype(/decl/material/gas)) = rand(1,10))
+ gas_needed = list(pick(get_filterable_material_types(as_list = TRUE)) = rand(1,10))
var/decl/material/gas/gas = GET_DECL(gas_needed[1])
name = "concentration of [gas.name]"
diff --git a/code/modules/xenoarcheaology/finds/find_types/mundane.dm b/code/modules/xenoarcheaology/finds/find_types/mundane.dm
index 7d1ea51ec777..211c5733dc4d 100644
--- a/code/modules/xenoarcheaology/finds/find_types/mundane.dm
+++ b/code/modules/xenoarcheaology/finds/find_types/mundane.dm
@@ -22,7 +22,7 @@
"It's like no [item_type] you've ever seen before.",
"It's a mystery how anyone is supposed to eat with this.",
"You wonder what the creator's mouth was shaped like.")
-
+
// Coin
/decl/archaeological_find/coin
item_type = "coin"
@@ -56,7 +56,7 @@
/decl/archaeological_find/tank/spawn_item(atom/loc)
var/obj/item/tank/new_item = ..()
new_item.air_contents.gas.Cut()
- new_item.air_contents.adjust_gas(pick(decls_repository.get_decl_paths_of_subtype(/decl/material/gas)),15)
+ new_item.air_contents.adjust_gas(pick(get_filterable_material_types(as_list = TRUE)),15)
return new_item
/decl/archaeological_find/tank/generate_name()
diff --git a/code/unit_tests/atmospherics_tests.dm b/code/unit_tests/atmospherics_tests.dm
index 0fa4febe7d55..e28fcc06a378 100644
--- a/code/unit_tests/atmospherics_tests.dm
+++ b/code/unit_tests/atmospherics_tests.dm
@@ -39,7 +39,7 @@
/datum/unit_test/atmos_machinery/proc/check_moles_conserved(var/case_name, var/list/before_gas_mixes, var/list/after_gas_mixes)
var/failed = FALSE
- for(var/gasid in decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ for(var/gasid in get_filterable_material_types())
var/before = 0
for(var/gasmix in before_gas_mixes)
var/datum/gas_mixture/G = before_gas_mixes[gasmix]
@@ -195,7 +195,7 @@
name = "ATMOS MACHINERY: scrub_gas() Conserves Moles"
/datum/unit_test/atmos_machinery/conserve_moles/scrub_gas/start_test()
- var/list/filtering = decls_repository.get_decl_paths_of_subtype(/decl/material/gas)
+ var/list/filtering = get_filterable_material_types(as_list = TRUE)
for(var/case_name in test_cases)
var/gas_mix_data = test_cases[case_name]
var/list/before_gas_mixes = create_gas_mixes(gas_mix_data)
@@ -211,7 +211,7 @@
name = "ATMOS MACHINERY: filter_gas() Conserves Moles"
/datum/unit_test/atmos_machinery/conserve_moles/filter_gas/start_test()
- var/list/filtering = decls_repository.get_decl_paths_of_subtype(/decl/material/gas)
+ var/list/filtering = get_filterable_material_types(as_list = TRUE)
for(var/case_name in test_cases)
var/gas_mix_data = test_cases[case_name]
var/list/before_gas_mixes = create_gas_mixes(gas_mix_data)
@@ -231,7 +231,7 @@
var/list/after_gas_mixes = create_gas_mixes(gas_mix_data)
var/list/filtering = list()
- for(var/gasid in decls_repository.get_decl_paths_of_subtype(/decl/material/gas))
+ for(var/gasid in get_filterable_material_types())
filtering[gasid] = after_gas_mixes["sink"] //just filter everything to sink
filter_gas_multi(null, filtering, after_gas_mixes["source"], after_gas_mixes["sink"], null, INFINITY)
@@ -250,7 +250,7 @@
var/list/after_gas_mixes = create_gas_mixes(gas_mix_data)
var/list/mix_sources = list()
- var/list/all_gasses = decls_repository.get_decl_paths_of_subtype(/decl/material/gas)
+ var/list/all_gasses = get_filterable_material_types(as_list = TRUE)
var/gas_count = length(all_gasses)
for(var/gasid in all_gasses)
var/datum/gas_mixture/mix_source = after_gas_mixes["sink"]
diff --git a/code/unit_tests/icon_tests.dm b/code/unit_tests/icon_tests.dm
index 279daba8a344..89814527ca65 100644
--- a/code/unit_tests/icon_tests.dm
+++ b/code/unit_tests/icon_tests.dm
@@ -85,42 +85,59 @@
return 1
/datum/unit_test/icon_test/signs_shall_have_existing_icon_states
- name = "ICON STATE: Signs shall have existing icon states"
- var/list/skip_types = list(
- // Posters use a decl to set their icon and handle their own validation.
- /obj/structure/sign/poster
- )
+ name = "ICON STATE: Sign Subtypes Shall Have Existing Icon States"
/datum/unit_test/icon_test/signs_shall_have_existing_icon_states/start_test()
var/list/failures = list()
- for(var/sign_type in typesof(/obj/structure/sign))
- var/obj/structure/sign/sign = sign_type
- if(TYPE_IS_ABSTRACT(sign))
- continue
+ var/static/list/skip_icon_state_checks = list(
+ // Posters use a decl to set their icon and handle their own validation.
+ /obj/structure/sign/poster
+ )
+
+ var/list/icon_states_to_find = list()
+ for(var/obj/structure/sign/sign as anything in typesof(/obj/structure/sign))
var/skip = FALSE
- for(var/skip_type in skip_types)
- if(ispath(sign_type, skip_type))
+ for(var/skip_type in skip_icon_state_checks)
+ if(ispath(sign, skip_type))
skip = TRUE
break
if(skip)
continue
- var/check_state = initial(sign.icon_state)
- if(!check_state)
- failures += "[sign] - null icon_state"
- continue
- var/check_icon = initial(sign.icon)
- if(!check_icon)
- failures += "[sign] - null icon_state"
+ var/sign_state = sign::icon_state
+ var/sign_icon = sign::icon
+
+ if(!(sign_icon in icon_states_to_find))
+ icon_states_to_find[sign_icon] = icon_states(sign_icon) || list()
+ icon_states_to_find[sign_icon] -= sign_state
+
+ if(TYPE_IS_ABSTRACT(sign))
continue
- if(!check_state_in_icon(check_state, check_icon))
- failures += "[sign] - missing icon_state '[check_state]' in icon '[check_icon]"
- if(failures.len)
- fail("Signs with missing icon states:\n\t-[jointext(failures, "\n\t-")]")
+
+ if(!sign_icon)
+ failures += "[sign] - missing icon"
+ else if(!istext(sign_state))
+ failures += "[sign] - missing or invalid icon_state"
+ else if(!check_state_in_icon(sign_state, sign_icon))
+ failures += "[sign] - missing icon_state '[sign_state]' from icon '[sign_icon]'"
+
+ var/static/list/skip_extraneous_state_checks = list(
+ // Barsign icon_state is set by user, skip testing it here.
+ 'icons/obj/barsigns.dmi'
+ )
+
+ for(var/sign_icon in icon_states_to_find)
+ var/list/remaining = icon_states_to_find[sign_icon]
+ if(!(sign_icon in skip_extraneous_state_checks) && length(remaining))
+ failures += "[sign_icon] - unused icon_states: [jointext(remaining, ", ")]"
+
+ if(length(failures))
+ fail("[length(failures)] issue\s with sign icons or icon states:\n[jointext(failures, "\n")]")
else
- pass("All signs have valid icon states.")
+ pass("All signs have valid icon states and no extraneous icon states.")
+
return 1
/datum/unit_test/icon_test/random_spawners_shall_have_existing_icon_states
diff --git a/html/changelog.html b/html/changelog.html
index fb311db0cf29..9e0ead821a49 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -52,6 +52,12 @@
-->