From 586e2e6c4d4363c160cc0b2c24b5fd79ffc8c228 Mon Sep 17 00:00:00 2001
From: Kyani <65205627+EmeraldCandy@users.noreply.github.com>
Date: Fri, 13 Jun 2025 15:40:29 -0400
Subject: [PATCH] Job Perks - True Professionals (#29478)
* ddsadsa
ddsadsa
* moderate again
* revert
* medical first steps
* more work
* lots more work done
* more progress
* finished up
* attack chain linter
* attack chain update
* attack chain work
* fixes + chemist update
* gives comments
---
code/__HELPERS/trait_helpers.dm | 59 +++++++++++++++++++
code/_onclick/item_attack.dm | 11 +++-
code/datums/diseases/_MobProcs.dm | 7 +++
code/game/jobs/job/engineering_jobs.dm | 11 ++++
code/game/jobs/job/job.dm | 20 +++++++
code/game/jobs/job/medical_jobs.dm | 30 ++++++++++
code/game/jobs/job/science_jobs.dm | 12 ++++
code/game/jobs/job/security_jobs.dm | 20 +++++++
code/game/jobs/job/supervisor.dm | 41 +++++++++++++
code/game/jobs/job/support.dm | 28 ++++++++-
code/game/objects/items/granters/_granters.dm | 22 ++++---
.../objects/items/weapons/extinguisher.dm | 3 +-
code/game/objects/items/weapons/manuals.dm | 22 +++++++
code/game/objects/items/weapons/mop.dm | 5 +-
.../structures/crates_lockers/closets.dm | 16 ++---
code/modules/crafting/craft.dm | 4 +-
code/modules/hydroponics/hydroponics_tray.dm | 4 ++
code/modules/library/book.dm | 42 ++++++++-----
.../mob/living/carbon/human/human_life.dm | 15 +++--
.../mob/living/carbon/human/human_mob.dm | 42 +++++++++++++
code/modules/mob/living/living.dm | 8 ++-
code/modules/mob/living/living_defines.dm | 3 +
code/modules/mob/living/living_say.dm | 5 +-
.../mob/living/silicon/robot/robot_mob.dm | 33 +++++++++--
code/modules/power/apc/apc.dm | 31 +++++++++-
code/modules/recycling/sortingmachinery.dm | 9 ++-
code/modules/smithing/smith_item.dm | 5 ++
.../modules/smithing/smithed_items/inserts.dm | 17 ++++++
code/modules/smithing/smithed_items/lens.dm | 18 ++++++
.../smithing/smithed_items/tool_bits.dm | 18 ++++++
code/modules/vehicle/ambulance.dm | 48 +++++++++++++++
31 files changed, 553 insertions(+), 56 deletions(-)
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index 0f2fd485598..771d639c3fc 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -345,6 +345,64 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Does this person produce loud runechat
#define TRAIT_LOUD "loud"
+//***** JOB TRAITS *****//
+
+// med traits
+
+/// allows user to do_after to give bone and IB locations
+#define TRAIT_MED_EXAMINE "med_examine"
+/// Grants a 85% chance to NOT catch a disease when contract() is called. You'll probably still catch it though with exposure
+#define TRAIT_GERMOPHOBE "germophobe"
+/// user wont vomit around corpses
+#define TRAIT_CORPSE_RESIST "corpse_resist"
+/// user will take 25% less tox damage
+#define TRAIT_QUICK_HEATER "quick_heater"
+/// user can drive the ambulance cart to push people out of the way
+#define TRAIT_SPEED_DEMON "speed_demon"
+
+// sci traits
+
+/// user can craft things from the crafting menu in 1/4 time
+#define TRAIT_CRAFTY "crafty"
+/// User is alloted a higher genetics budget
+#define TRAIT_GENETIC_BUDGET "genetic_budget"
+/// allows the user to quick-charge borgs by using a battery on them
+#define TRAIT_CYBORG_SPECIALIST "cyborg_specialist"
+
+// service traits
+
+/// Allows user to pick weeds with their bare hands
+#define TRAIT_GREEN_THUMB "green_thumb"
+/// Allows user to use mops faster, and have galoshes slow less
+#define TRAIT_JANITOR "janitor"
+
+// Engi traits
+
+/// Allows user to to quick-repair unlocked APCs with a frame and board, and quick-swap cells.alist
+#define TRAIT_ELECTRICAL_SPECIALIST "electrical_specialist"
+/// Allows user to spray extinguishers further, and gives precisions = 1 during spray
+#define TRAIT_FIRE_FIGHTER "fire_fighter"
+
+// cargo traits
+
+/// allows the user to stuff more items in lockers/crates and wrap them faster
+#define TRAIT_PACK_RAT "pack_rat"
+/// Allows the user to ????
+#define TRAIT_SMITH "tait_smith"
+/// Allows faster butchering times
+#define TRAIT_BUTCHER "butcher"
+
+// sec traits
+
+/// Gives the user a new tab for being able to open up space law/sop book pages
+#define TRAIT_JUDICIAL "judicial"
+
+// command traits
+
+/// Allows user to drink coffee to point rapidly to get others to do what you want
+#define TRAIT_COFFEE_SNOB "coffee_snob"
+
+
//****** OBJ TRAITS *****//
///An /obj that should not increase the "depth" of the search for adjacency,
@@ -384,6 +442,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define GHOSTED "isghost"
#define GHOST_ROLE "ghost_role"
#define ORGAN_TRAIT "organ"
+#define JOB_TRAIT "job_trait"
// unique trait sources
#define STATUE_MUTE "statue"
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 7e934b6e022..a9046b91000 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -69,9 +69,14 @@
if(temperature && A.reagents && !ismob(A) && !istype(A, /obj/item/clothing/mask/cigarette))
var/reagent_temp = A.reagents.chem_temp
var/time = (reagent_temp / 10) / (temperature / 1000)
- if(do_after_once(user, time, TRUE, user, TRUE, attempt_cancel_message = "You stop heating up [A]."))
- to_chat(user, "You heat [A] with [src].")
- A.reagents.temperature_reagents(temperature)
+ if(user.mind && HAS_TRAIT(user.mind, TRAIT_QUICK_HEATER))
+ while(do_after_once(user, time, TRUE, user, TRUE, attempt_cancel_message = "You stop heating up [A]."))
+ to_chat(user, "You heat [A] with [src].")
+ A.reagents.temperature_reagents(temperature)
+ else
+ if(do_after_once(user, time, TRUE, user, TRUE, attempt_cancel_message = "You stop heating up [A]."))
+ to_chat(user, "You heat [A] with [src].")
+ A.reagents.temperature_reagents(temperature)
/**
* Called when mob `user` is hitting us with an item `attacking`.
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
index 5e299ada323..0a74ed74024 100644
--- a/code/datums/diseases/_MobProcs.dm
+++ b/code/datums/diseases/_MobProcs.dm
@@ -60,6 +60,13 @@
if(!CanContractDisease(D))
return 0
+ if(src.mind)
+ if(HAS_TRAIT(src.mind, TRAIT_GERMOPHOBE) && prob(85))
+ log_debug("failed to contract disease")
+ return 0
+ else
+ log_debug("contracted a disease!")
+
var/obj/item/clothing/Cl = null
var/passed = 1
diff --git a/code/game/jobs/job/engineering_jobs.dm b/code/game/jobs/job/engineering_jobs.dm
index d5db8c18b78..d0e8583c936 100644
--- a/code/game/jobs/job/engineering_jobs.dm
+++ b/code/game/jobs/job/engineering_jobs.dm
@@ -59,6 +59,10 @@
dufflebag = /obj/item/storage/backpack/duffel/engineering
box = /obj/item/storage/box/engineer
+/datum/outfit/job/chief_engineer/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_ELECTRICAL_SPECIALIST, JOB_TRAIT)
+
/datum/job/engineer
title = "Station Engineer"
flag = JOB_ENGINEER
@@ -102,6 +106,10 @@
dufflebag = /obj/item/storage/backpack/duffel/engineering
box = /obj/item/storage/box/engineer
+/datum/outfit/job/engineer/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_ELECTRICAL_SPECIALIST, JOB_TRAIT)
+
/datum/job/atmos
title = "Life Support Specialist"
flag = JOB_ATMOSTECH
@@ -142,3 +150,6 @@
dufflebag = /obj/item/storage/backpack/duffel/atmos
box = /obj/item/storage/box/engineer
+/datum/outfit/job/atmos/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_FIRE_FIGHTER, JOB_TRAIT)
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 68f6a822cf9..c40fee49cf5 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -304,3 +304,23 @@
/// Used to give the gaze ability to NTReps and IAAs
/datum/outfit/job/proc/give_gaze(mob/living/carbon/human/user)
user.AddSpell(new /datum/spell/inspectors_gaze(null))
+
+/// Gives the imaginary space law booklet verb
+/mob/living/carbon/human/proc/space_law()
+ set name = "Open Space Law"
+ set desc = "Open a memorized version of the space law booklet."
+ set category = "Space Law"
+
+ var/obj/item/book/manual/wiki/security_space_law/imaginary/book = new()
+ if(!put_in_any_hand_if_possible(book))
+ QDEL_NULL(book)
+
+/// Gives the imaginary legal sop booklet verb
+/mob/living/carbon/human/proc/sop_legal()
+ set name = "Open Legal SOP"
+ set desc = "Open a memorized version of the legal SOP booklet."
+ set category = "Space Law"
+
+ var/obj/item/book/manual/wiki/sop_legal/imaginary/book = new()
+ if(!put_in_any_hand_if_possible(book))
+ QDEL_NULL(book)
diff --git a/code/game/jobs/job/medical_jobs.dm b/code/game/jobs/job/medical_jobs.dm
index eed32e7dfe5..8160d45f5c5 100644
--- a/code/game/jobs/job/medical_jobs.dm
+++ b/code/game/jobs/job/medical_jobs.dm
@@ -56,6 +56,11 @@
satchel = /obj/item/storage/backpack/satchel_med
dufflebag = /obj/item/storage/backpack/duffel/medical
+
+/datum/outfit/job/cmo/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_MED_EXAMINE, JOB_TRAIT)
+
/datum/job/doctor
title = "Medical Doctor"
flag = JOB_DOCTOR
@@ -94,6 +99,10 @@
satchel = /obj/item/storage/backpack/satchel_med
dufflebag = /obj/item/storage/backpack/duffel/medical
+/datum/outfit/job/doctor/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_MED_EXAMINE, JOB_TRAIT)
+
/datum/job/coroner
title = "Coroner"
flag = JOB_CORONER
@@ -137,6 +146,10 @@
/obj/item/healthanalyzer = 1,
/obj/item/storage/box/bodybags = 1)
+/datum/outfit/job/coroner/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_CORPSE_RESIST, JOB_TRAIT)
+
/datum/outfit/job/doctor/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(H.mind && H.mind.role_alt_title)
@@ -194,6 +207,10 @@
satchel = /obj/item/storage/backpack/satchel_chem
dufflebag = /obj/item/storage/backpack/duffel/chemistry
+/datum/outfit/job/chemist/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_QUICK_HEATER, JOB_TRAIT)
+
/datum/job/geneticist
title = "Geneticist"
flag = JOB_GENETICIST
@@ -231,6 +248,10 @@
satchel = /obj/item/storage/backpack/satchel_gen
dufflebag = /obj/item/storage/backpack/duffel/genetics
+/datum/outfit/job/geneticist/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_GENETIC_BUDGET, JOB_TRAIT)
+
/datum/job/virologist
title = "Virologist"
flag = JOB_VIROLOGIST
@@ -255,6 +276,7 @@
)
outfit = /datum/outfit/job/virologist
+
/datum/outfit/job/virologist
name = "Virologist"
jobtype = /datum/job/virologist
@@ -272,6 +294,10 @@
satchel = /obj/item/storage/backpack/satchel_vir
dufflebag = /obj/item/storage/backpack/duffel/virology
+/datum/outfit/job/virologist/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_GERMOPHOBE, JOB_TRAIT)
+
/datum/job/psychiatrist
title = "Psychiatrist"
flag = JOB_PSYCHIATRIST
@@ -362,3 +388,7 @@
satchel = /obj/item/storage/backpack/satchel_med
dufflebag = /obj/item/storage/backpack/duffel/medical
box = /obj/item/storage/box/engineer
+
+/datum/outfit/job/paramedic/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_SPEED_DEMON, JOB_TRAIT)
diff --git a/code/game/jobs/job/science_jobs.dm b/code/game/jobs/job/science_jobs.dm
index 87e76f75072..0b119fd74fe 100644
--- a/code/game/jobs/job/science_jobs.dm
+++ b/code/game/jobs/job/science_jobs.dm
@@ -65,6 +65,10 @@
satchel = /obj/item/storage/backpack/satchel_tox
dufflebag = /obj/item/storage/backpack/duffel/science
+/datum/outfit/job/rd/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_CRAFTY, JOB_TRAIT)
+
/datum/job/scientist
title = "Scientist"
flag = JOB_SCIENTIST
@@ -107,6 +111,10 @@
satchel = /obj/item/storage/backpack/satchel_tox
dufflebag = /obj/item/storage/backpack/duffel/science
+/datum/outfit/job/scientist/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_CRAFTY, JOB_TRAIT)
+
/datum/job/xenobiologist
title = "Xenobiologist"
flag = JOB_XENOBIOLOGIST
@@ -205,3 +213,7 @@
backpack = /obj/item/storage/backpack/robotics
satchel = /obj/item/storage/backpack/satchel_robo
dufflebag = /obj/item/storage/backpack/duffel/robotics
+
+/datum/outfit/job/roboticist/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_CYBORG_SPECIALIST, JOB_TRAIT)
diff --git a/code/game/jobs/job/security_jobs.dm b/code/game/jobs/job/security_jobs.dm
index 383841ec667..f560857512b 100644
--- a/code/game/jobs/job/security_jobs.dm
+++ b/code/game/jobs/job/security_jobs.dm
@@ -70,6 +70,11 @@
satchel = /obj/item/storage/backpack/satchel_sec
dufflebag = /obj/item/storage/backpack/duffel/security
+/datum/outfit/job/hos/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
+
/datum/job/warden
title = "Warden"
flag = JOB_WARDEN
@@ -121,6 +126,11 @@
satchel = /obj/item/storage/backpack/satchel_sec
dufflebag = /obj/item/storage/backpack/duffel/security
+/datum/outfit/job/warden/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
+
/datum/job/detective
title = "Detective"
flag = JOB_DETECTIVE
@@ -173,6 +183,12 @@
bio_chips = list(/obj/item/bio_chip/mindshield)
+/datum/outfit/job/detective/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_CORPSE_RESIST, JOB_TRAIT) // too cool to vomit
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
+
/datum/outfit/job/detective/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(H.mind && H.mind.role_alt_title)
@@ -236,3 +252,7 @@
satchel = /obj/item/storage/backpack/satchel_sec
dufflebag = /obj/item/storage/backpack/duffel/security
+/datum/outfit/job/officer/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm
index 5810625a5fe..29b5a91f705 100644
--- a/code/game/jobs/job/supervisor.dm
+++ b/code/game/jobs/job/supervisor.dm
@@ -53,6 +53,10 @@
U.accessories += M
M.on_attached(U)
+/datum/outfit/job/captain/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ H.AddSpell(new /datum/spell/big_voice)
+
/datum/job/hop
title = "Head of Personnel"
flag = JOB_HOP
@@ -200,6 +204,7 @@
/datum/outfit/job/nanotrasenrep/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
+ ADD_TRAIT(H.mind, TRAIT_COFFEE_SNOB, JOB_TRAIT)
if(visualsOnly)
return
@@ -314,6 +319,12 @@
satchel = /obj/item/storage/backpack/satchel_sec
dufflebag = /obj/item/storage/backpack/duffel/security
+/datum/outfit/job/judge/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
+ ADD_TRAIT(H.mind, TRAIT_COFFEE_SNOB, JOB_TRAIT)
+
/datum/job/iaa
title = "Internal Affairs Agent"
flag = JOB_INTERNAL_AFFAIRS
@@ -364,6 +375,8 @@
/datum/outfit/job/iaa/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
+ add_verb(H, /mob/living/carbon/human/proc/sop_legal)
+ add_verb(H, /mob/living/carbon/human/proc/space_law)
if(visualsOnly)
return
@@ -440,3 +453,31 @@
/datum/outfit/job/nct/on_mind_initialize(mob/living/carbon/human/H)
. = ..()
H.mind.offstation_role = TRUE
+
+/datum/spell/big_voice
+ name = "Speak with Authority"
+ desc = "Speak with a COMMANDING AUTHORITY against those you govorn."
+ base_cooldown = 1 MINUTES
+ action_background_icon_state = "bg_default"
+ action_icon = 'icons/obj/clothing/accessories.dmi'
+ action_icon_state = "gold"
+ sound = null
+ invocation_type = "none"
+ invocation = null
+ clothes_req = FALSE
+
+/datum/spell/big_voice/create_new_targeting()
+ return new /datum/spell_targeting/self
+
+/datum/spell/big_voice/cast(list/targets, mob/living/user)
+ var/say_message = tgui_input_text(user, "Message:", "Speak With Authority")
+ if(isnull(say_message))
+ revert_cast()
+ else
+ if(user.big_voice != 2)
+ user.big_voice = 1
+ user.say(say_message)
+ user.big_voice = 0
+ else
+ user.say(say_message)
+ cooldown_handler.start_recharge()
diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm
index 31226f2a598..198462f810d 100644
--- a/code/game/jobs/job/support.dm
+++ b/code/game/jobs/job/support.dm
@@ -54,7 +54,9 @@
/obj/item/melee/classic_baton/telescopic = 1
)
-
+/datum/outfit/job/qm/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_PACK_RAT, JOB_TRAIT)
/datum/job/cargo_tech
title = "Cargo Technician"
@@ -88,6 +90,10 @@
id = /obj/item/card/id/supply
pda = /obj/item/pda/cargo
+/datum/outfit/job/cargo_tech/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_PACK_RAT, JOB_TRAIT)
+
/datum/job/smith
title = "Smith"
flag = JOB_SMITH
@@ -122,6 +128,10 @@
id = /obj/item/card/id/smith
pda = /obj/item/pda/cargo
+/datum/outfit/job/smith/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_SMITH, JOB_TRAIT)
+
/datum/job/mining
title = "Shaft Miner"
flag = JOB_MINER
@@ -164,6 +174,10 @@
satchel = /obj/item/storage/backpack/satchel/explorer
box = /obj/item/storage/box/survival_mining
+/datum/outfit/job/mining/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_BUTCHER, JOB_TRAIT)
+
/datum/outfit/job/mining/equipped
name = "Shaft Miner"
@@ -234,6 +248,10 @@
satchel = /obj/item/storage/backpack/satchel/explorer
box = /obj/item/storage/box/survival_mining
+/datum/outfit/job/explorer/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_BUTCHER, JOB_TRAIT)
+
//Food
/datum/job/bartender
title = "Bartender"
@@ -353,7 +371,9 @@
satchel = /obj/item/storage/backpack/satchel_hyd
dufflebag = /obj/item/storage/backpack/duffel/hydro
-
+/datum/outfit/job/hydro/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_GREEN_THUMB, JOB_TRAIT)
//Griff //BS12 EDIT
@@ -545,6 +565,10 @@
return
ADD_TRAIT(H, TRAIT_NEVER_MISSES_DISPOSALS, ROUNDSTART_TRAIT)
+/datum/outfit/job/janitor/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ ADD_TRAIT(H.mind, TRAIT_JANITOR, JOB_TRAIT)
+
//More or less assistants
/datum/job/librarian
title = "Librarian"
diff --git a/code/game/objects/items/granters/_granters.dm b/code/game/objects/items/granters/_granters.dm
index a3954dcf40d..e5ba5715e04 100644
--- a/code/game/objects/items/granters/_granters.dm
+++ b/code/game/objects/items/granters/_granters.dm
@@ -21,21 +21,27 @@
'sound/effects/pageturn3.ogg'
)
-/obj/item/book/granter/attack_self__legacy__attackchain(mob/living/user)
+/obj/item/book/granter/Initialize(mapload, datum/cachedbook/CB, _copyright, _protected)
+ . = ..()
+ RegisterSignal(src, COMSIG_ACTIVATE_SELF, TYPE_PROC_REF(/datum, signal_cancel_activate_self))
+
+/obj/item/book/granter/activate_self(mob/user)
+ if(..())
+ return
if(reading)
to_chat(user, "You're already reading this!")
- return FALSE
+ return FINISH_ATTACK
if(!user.has_vision())
to_chat(user, "You are blind and can't read anything!")
- return FALSE
+ return FINISH_ATTACK
if(!isliving(user))
- return FALSE
+ return FINISH_ATTACK
if(!can_learn(user))
- return FALSE
+ return FINISH_ATTACK
if(uses <= 0)
recoil(user)
- return FALSE
+ return FINISH_ATTACK
on_reading_start(user)
reading = TRUE
@@ -43,14 +49,12 @@
if(!turn_page(user))
on_reading_stopped(user)
reading = FALSE
- return
+ return CONTINUE_ATTACK
if(do_after(user, reading_time, src))
uses--
on_reading_finished(user)
reading = FALSE
- return TRUE
-
/// Called when the user starts to read the granter.
/obj/item/book/granter/proc/on_reading_start(mob/living/user)
to_chat(user, "You start reading [name]...")
diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm
index 4a02bed7e86..f796266f1e4 100644
--- a/code/game/objects/items/weapons/extinguisher.dm
+++ b/code/game/objects/items/weapons/extinguisher.dm
@@ -143,7 +143,8 @@
INVOKE_ASYNC(src, PROC_REF(buckled_speed_move), user.buckled, direction)
else
user.newtonian_move(turn(direction, 180))
-
+ if(user.mind && HAS_TRAIT(user.mind, TRAIT_FIRE_FIGHTER))
+ precision = TRUE
var/turf/T = get_turf(A)
var/turf/T1 = get_step(T, turn(direction, 90))
var/turf/T2 = get_step(T, turn(direction, -90))
diff --git a/code/game/objects/items/weapons/manuals.dm b/code/game/objects/items/weapons/manuals.dm
index 840dcf8af8f..a7af5c55252 100644
--- a/code/game/objects/items/weapons/manuals.dm
+++ b/code/game/objects/items/weapons/manuals.dm
@@ -928,6 +928,17 @@
title = "Space Law"
wiki_article_title = "Space_law"
+/obj/item/book/manual/wiki/security_space_law/imaginary
+ name = "Imaginary Space Law Manual"
+ desc = "A set of memorized Nanotrasen guidelines for keeping law and order on their space stations."
+ flags = DROPDEL | ABSTRACT | NOBLUDGEON
+ imaginary = TRUE
+
+/obj/item/book/manual/wiki/security_space_law/imaginary/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_SKIP_EXAMINE, INNATE_TRAIT)
+ ADD_TRAIT(src, TRAIT_NO_STRIP, INNATE_TRAIT)
+
/obj/item/book/manual/wiki/security_space_law/black
name = "Space Law - Limited Edition"
desc = "A leather-bound, immaculately-written copy of JUSTICE."
@@ -958,6 +969,17 @@
title = "Legal Standard Operating Procedures"
wiki_article_title = "Legal_Standard_Operating_Procedure"
+/obj/item/book/manual/wiki/sop_legal/imaginary
+ name = "Imaginary Legal SOP Manual"
+ desc = "A set of memorized Nanotrasen guidelines aiming at the safe conduct of all legal activities."
+ flags = DROPDEL | ABSTRACT | NOBLUDGEON
+ imaginary = TRUE
+
+/obj/item/book/manual/wiki/sop_legal/imaginary/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_SKIP_EXAMINE, INNATE_TRAIT)
+ ADD_TRAIT(src, TRAIT_NO_STRIP, INNATE_TRAIT)
+
/obj/item/book/manual/wiki/sop_supply
name = "Supply Standard Operating Procedures"
desc = "A set of guidelines aiming at the safe conduct of all supply activities."
diff --git a/code/game/objects/items/weapons/mop.dm b/code/game/objects/items/weapons/mop.dm
index 7d6a684df85..05b92c8b2ac 100644
--- a/code/game/objects/items/weapons/mop.dm
+++ b/code/game/objects/items/weapons/mop.dm
@@ -71,7 +71,10 @@
if(world.time > mop_sound_cooldown)
playsound(loc, pick('sound/weapons/mopping1.ogg', 'sound/weapons/mopping2.ogg'), 30, TRUE, -1)
mop_sound_cooldown = world.time + MOP_SOUND_CD
- target.cleaning_act(user, src, mopspeed, text_verb = "mop", text_description = ".")
+ if(user.mind && HAS_TRAIT(user, TRAIT_JANITOR))
+ target.cleaning_act(user, src, mopspeed / 2, text_verb = "mop", text_description = ".")
+ else
+ target.cleaning_act(user, src, mopspeed, text_verb = "mop", text_description = ".")
return ITEM_INTERACT_COMPLETE
/obj/item/mop/can_clean()
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 6f11c416704..dac03e147c6 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -218,30 +218,32 @@
update_appearance()
return TRUE
-/obj/structure/closet/proc/close()
+/obj/structure/closet/proc/close(mob/user)
if(!opened)
return FALSE
if(!can_close())
return FALSE
var/itemcount = 0
-
+ var/temp_capacity = storage_capacity
+ if(user && user.mind && HAS_TRAIT(user.mind, TRAIT_PACK_RAT))
+ temp_capacity *= 1.5
//Cham Projector Exception
for(var/obj/effect/dummy/chameleon/AD in loc)
- if(itemcount >= storage_capacity)
+ if(itemcount >= temp_capacity)
break
AD.forceMove(src)
itemcount++
for(var/obj/item/I in loc)
- if(itemcount >= storage_capacity)
+ if(itemcount >= temp_capacity)
break
if(!I.anchored)
I.forceMove(src)
itemcount++
for(var/mob/M in loc)
- if(itemcount >= storage_capacity)
+ if(itemcount >= temp_capacity)
break
if(isobserver(M))
continue
@@ -264,7 +266,7 @@
return TRUE
/obj/structure/closet/proc/toggle(mob/user)
- if(!(opened ? close() : open()))
+ if(!(opened ? close(user) : open()))
to_chat(user, "It won't budge!")
/obj/structure/closet/proc/bust_open()
@@ -498,7 +500,7 @@
if(opened && can_close())
target.forceMove(src)
visible_message("[attacker] shoves [target] inside [src]!", "You hear a thud, and something clangs shut.")
- close()
+ close(attacker)
add_attack_logs(attacker, target, "shoved into [src]")
return TRUE
diff --git a/code/modules/crafting/craft.dm b/code/modules/crafting/craft.dm
index d56cd287322..b27dafe2209 100644
--- a/code/modules/crafting/craft.dm
+++ b/code/modules/crafting/craft.dm
@@ -166,7 +166,9 @@
return ", missing tool."
if(!check_pathtools(user, recipe, contents))
return ", missing tool."
-
+ var/craft_time = recipe.time
+ if(HAS_TRAIT(user.mind, TRAIT_CRAFTY))
+ craft_time /= 3
if(!do_after(user, recipe.time, target = user))
return "."
contents = get_surroundings(user)
diff --git a/code/modules/hydroponics/hydroponics_tray.dm b/code/modules/hydroponics/hydroponics_tray.dm
index ba7c6a6bc81..7b2cb64a435 100644
--- a/code/modules/hydroponics/hydroponics_tray.dm
+++ b/code/modules/hydroponics/hydroponics_tray.dm
@@ -936,6 +936,10 @@
update_state()
plant_hud_set_status()
plant_hud_set_health()
+ else if(user.mind && HAS_TRAIT(user.mind, TRAIT_GREEN_THUMB) && weedlevel > 0)
+ user.visible_message("[user] uproots the weeds.", "You pluck the weeds from [src] with your hands.")
+ adjustWeeds(-10)
+ update_state()
else
examine(user)
diff --git a/code/modules/library/book.dm b/code/modules/library/book.dm
index 016a5fb8136..a2c1e104fc2 100644
--- a/code/modules/library/book.dm
+++ b/code/modules/library/book.dm
@@ -23,6 +23,7 @@
resistance_flags = FLAMMABLE
drop_sound = 'sound/items/handling/book_drop.ogg'
pickup_sound = 'sound/items/handling/book_pickup.ogg'
+ new_attack_chain = TRUE
///Title & Real name of the book
var/title
@@ -40,6 +41,8 @@
var/pages = list()
///What page is the book currently opened to? Page 0 - Intro Page | Page 1-5 - Content Pages
var/current_page = 0
+ ///Is this item imaginary and not able to interact with anything else?
+ var/imaginary = FALSE
///Book UI Popup Height
var/book_height = 400
@@ -74,37 +77,48 @@
icon_state = "book[rand(1,8)]"
-/obj/item/book/attack__legacy__attackchain(mob/M, mob/living/user)
+/obj/item/book/attack(mob/M, mob/living/user)
+ if(imaginary)
+ return FINISH_ATTACK
if(user.a_intent == INTENT_HELP)
force = 0
attack_verb = list("educated")
else
force = initial(force)
attack_verb = list("bashed", "whacked")
- ..()
+ return ..()
-/obj/item/book/attack_self__legacy__attackchain(mob/user)
+/obj/item/book/activate_self(mob/user)
+ if(..())
+ return
+ if(imaginary)
+ read_book(user)
+ return FINISH_ATTACK
if(carved)
//Attempt to remove inserted object, if none found, remind user that someone vandalized their book (Bastards)!
if(!remove_stored_item(user, TRUE))
to_chat(user, "The pages of [title] have been cut out!")
- return
+ return FINISH_ATTACK
user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.")
read_book(user)
+ return ..()
-/obj/item/book/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(is_pen(I))
+/obj/item/book/item_interaction(mob/living/user, obj/item/used, list/modifiers)
+ if(imaginary)
+ return ITEM_INTERACT_COMPLETE
+ if(is_pen(used))
edit_book(user)
- else if(istype(I, /obj/item/barcodescanner))
- var/obj/item/barcodescanner/scanner = I
+ else if(istype(used, /obj/item/barcodescanner))
+ var/obj/item/barcodescanner/scanner = used
scanner.scanBook(src, user) //abstraction and proper scoping ftw | did you know barcode scanner code used to be here?
- return
- else if(I.sharp && !carved) //don't use sharp objects on your books if you don't want to carve out all of its pages kids!
- carve_book(user, I)
- else if(store_item(I, user))
- return
+ return ITEM_INTERACT_COMPLETE
+ else if(used.sharp && !carved) //don't use sharp objects on your books if you don't want to carve out all of its pages kids!
+ carve_book(user, used)
+ return ITEM_INTERACT_COMPLETE
+ else if(store_item(used, user))
+ return ITEM_INTERACT_COMPLETE
else
- ..()
+ return ..()
/obj/item/book/examine(mob/user)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/human_life.dm b/code/modules/mob/living/carbon/human/human_life.dm
index 7c524d33edf..b450e7e007f 100644
--- a/code/modules/mob/living/carbon/human/human_life.dm
+++ b/code/modules/mob/living/carbon/human/human_life.dm
@@ -109,20 +109,22 @@
for(var/mutation_type in active_mutations)
var/datum/mutation/mutation = GLOB.dna_mutations[mutation_type]
mutation.on_life(src)
-
- if(!ignore_gene_stability && gene_stability < GENETIC_DAMAGE_STAGE_1)
+ var/gene_bonus
+ if(mind && HAS_TRAIT(mind, TRAIT_GENETIC_BUDGET))
+ gene_bonus = 5
+ if(!ignore_gene_stability && gene_stability < GENETIC_DAMAGE_STAGE_1 - gene_bonus)
var/instability = DEFAULT_GENE_STABILITY - gene_stability
if(prob(instability * 0.1))
adjustFireLoss(min(10, instability * 0.67))
to_chat(src, "You feel like your skin is burning and bubbling off!")
- if(gene_stability < GENETIC_DAMAGE_STAGE_2)
+ if(gene_stability < GENETIC_DAMAGE_STAGE_2 - gene_bonus)
if(prob(instability * 0.83))
adjustCloneLoss(min(8, instability * 0.05))
to_chat(src, "You feel as if your body is warping.")
if(prob(instability * 0.1))
adjustToxLoss(min(10, instability * 0.67))
to_chat(src, "You feel weak and nauseous.")
- if(gene_stability < GENETIC_DAMAGE_STAGE_3 && prob(1))
+ if(gene_stability < (GENETIC_DAMAGE_STAGE_3 - gene_bonus) && prob(1))
to_chat(src, "You feel incredibly sick... Something isn't right!")
spawn(300)
if(gene_stability < GENETIC_DAMAGE_STAGE_3)
@@ -895,8 +897,9 @@
if(HAS_TRAIT(H, TRAIT_NOBREATH))
continue //no puking if you can't smell!
// Humans can lack a mind datum, y'know
- if(H.mind && (H.mind.assigned_role == "Detective" || H.mind.assigned_role == "Coroner"))
- continue //too cool for puke
+ if(H.mind && HAS_TRAIT(H.mind, TRAIT_CORPSE_RESIST))
+ to_chat(H, "You smell something rotting nearby.")
+ continue
to_chat(H, "You smell something foul...")
H.fakevomit()
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index 7ddc06cc77f..39bcd00c6f7 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -2174,3 +2174,45 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
return FALSE
return TRUE
+
+/mob/living/carbon/human/AltClick(mob/user, modifiers)
+ . = ..()
+ if(user.stat || user.restrained() || (!in_range(src, user)))
+ return
+
+ if(user.mind && !HAS_TRAIT(user.mind, TRAIT_MED_EXAMINE))
+ return
+
+ user.visible_message("[user] begins to thoroughly examine [src].")
+ if(do_after_once(user, 12 SECONDS, target = src, allow_moving = FALSE, attempt_cancel_message = "You couldn't get a good look at [src]!"))
+ var/list/missing = list("head", "chest", "groin", "l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot")
+ var/list/analysis = list()
+ for(var/obj/item/organ/external/E in src.bodyparts)
+ missing -= E.limb_name
+ if(E.status & ORGAN_DEAD)
+ analysis += "You conclude [src]'s [E.name] is dead."
+ if(E.status & ORGAN_INT_BLEEDING)
+ analysis += "You conclude [src]'s [E.name] has internal bleeding."
+ if(E.status & ORGAN_BURNT)
+ analysis += "You conclude [src]'s [E.name] has been critically burned."
+ if(E.status & ORGAN_BROKEN)
+ if(!E.broken_description)
+ analysis += "You conclude [src]'s [E.name] is broken."
+ else
+ analysis += "You conclude [src]'s [E.name] has a [E.broken_description]."
+ to_chat(user, chat_box_healthscan(analysis.Join("
")))
+
+/mob/living/carbon/human/pointed(atom/A as mob|obj|turf in view())
+ set name = "Point To"
+ set category = null
+ if(next_move >= world.time)
+ return
+
+ if(istype(A, /obj/effect/temp_visual/point) || istype(A, /atom/movable/emissive_blocker))
+ return FALSE
+ if(mind && HAS_TRAIT(mind, TRAIT_COFFEE_SNOB) && reagents.has_reagent("coffee"))
+ changeNext_move(CLICK_CD_POINT / 3)
+ else
+ changeNext_move(CLICK_CD_POINT)
+
+ DEFAULT_QUEUE_OR_CALL_VERB(VERB_CALLBACK(src, PROC_REF(run_pointed), A))
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 7367e882886..2e1283bad08 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -941,8 +941,12 @@
if(user.a_intent == INTENT_HARM && stat == DEAD && butcher_results && I.sharp) //can we butcher it?
to_chat(user, "You begin to butcher [src]...")
playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1)
- if(do_mob(user, src, 8 SECONDS) && Adjacent(I))
- harvest(user)
+ if(user.mind && HAS_TRAIT(user.mind, TRAIT_BUTCHER))
+ if(do_mob(user, src, 3 SECONDS) && Adjacent(I))
+ harvest(user)
+ else
+ if(do_mob(user, src, 8 SECONDS) && Adjacent(I))
+ harvest(user)
return TRUE
/mob/living/proc/harvest(mob/living/user)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index cf18665b4b5..f9357358420 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -104,6 +104,9 @@
/// Famous last words -- if succumbing, what the user's last words were
var/last_words
+ /// Does this mob talk with a BIG VOICE. 0 off, 1 single use, 2 multi-use
+ var/big_voice = 0
+
///This variable is the chance for a mob to automatically dodge a bullet. Useful for admins, and applied to some mobs by default, such as the malfunctioning drone mobs.
var/advanced_bullet_dodge_chance = 0
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index c2974a2a9b5..8db87c4fc1b 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -105,7 +105,7 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
return returns
-/mob/living/say(message, verb = null, sanitize = TRUE, ignore_speech_problems = FALSE, ignore_atmospherics = FALSE, ignore_languages = FALSE, automatic = FALSE)
+/mob/living/say(message, verb = null, sanitize = TRUE, ignore_speech_problems = FALSE, ignore_atmospherics = FALSE, ignore_languages = FALSE, automatic = FALSE, bigvoice = FALSE)
if(client)
if(check_mute(client.ckey, MUTE_IC))
to_chat(src, "You cannot speak in IC (Muted).")
@@ -139,6 +139,9 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
message = trim_left(message)
+ if(big_voice)
+ message = "[message]"
+
//parse the language code and consume it
var/list/message_pieces = list()
if(ignore_languages)
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index 61b30cc067c..b27f15db510 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -130,6 +130,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/base_icon = ""
/// If set to TRUE, the robot's 3 module slots will progressively become unusable as they take damage.
var/modules_break = TRUE
+ /// Is the robot already being charged by a roboticist?
+ var/being_charged
/// Maximum brightness of a robot's headlamp. Set as a var for easy adjusting.
var/lamp_max = 10
@@ -923,6 +925,33 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/item_interaction(mob/living/user, obj/item/W, list/modifiers)
// Check if the user is trying to insert another component like a radio, actuator, armor etc.
+ if(istype(W, /obj/item/stock_parts/cell) && user.mind && HAS_TRAIT(user.mind, TRAIT_CYBORG_SPECIALIST) && !opened && user.a_intent != INTENT_HARM)
+ var/obj/item/stock_parts/cell/donor = W
+ if(being_charged)
+ to_chat(user, "You are already charging [src]!")
+ return ITEM_INTERACT_COMPLETE
+ if(donor.charge == 0)
+ to_chat(user, "[donor] has no charge to donate!")
+ return ITEM_INTERACT_COMPLETE
+ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
+ being_charged = TRUE
+ while(do_after(user, 0.5 SECONDS, target = src))
+ var/cell_difference = cell.maxcharge - cell.charge
+ if(donor.charge >= 500 && cell_difference >= 500)
+ cell.charge += 500
+ donor.charge -= 500
+ else if(donor.charge <= cell_difference)
+ cell.charge += donor.charge
+ donor.charge = 0
+ else if(donor.charge > cell_difference)
+ cell.charge = cell.maxcharge
+ donor.charge -= cell_difference
+ if(donor.charge == 0 || cell.charge >= cell.maxcharge)
+ donor.update_icon(UPDATE_OVERLAYS)
+ break
+ cell.update_icon(UPDATE_OVERLAYS)
+ being_charged = FALSE
+ return ITEM_INTERACT_COMPLETE
if(istype(W, /obj/item/robot_parts/robot_component) && opened)
for(var/V in components)
var/datum/robot_component/C = components[V]
@@ -1129,10 +1158,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
C.uninstall()
thing.forceMove(loc)
-
-
-
-
/mob/living/silicon/robot/attacked_by(obj/item/I, mob/living/user, def_zone)
if(I.force && I.damtype != STAMINA && stat != DEAD) //only sparks if real damage is dealt.
spark_system.start()
diff --git a/code/modules/power/apc/apc.dm b/code/modules/power/apc/apc.dm
index 83762150494..e825830a176 100644
--- a/code/modules/power/apc/apc.dm
+++ b/code/modules/power/apc/apc.dm
@@ -331,8 +331,33 @@
// Adding APC electronics.
if(istype(used, /obj/item/apc_electronics) && opened)
if(has_electronics())
- to_chat(user, "[src] already contains APC electronics!")
- return ITEM_INTERACT_COMPLETE
+ if(user.mind && HAS_TRAIT(user.mind, TRAIT_ELECTRICAL_SPECIALIST))
+ if(stat & BROKEN)
+ to_chat(user, "[src] is damaged! You must repair the frame before you can install [used]!")
+ return ITEM_INTERACT_COMPLETE
+ if(malfhack)
+ malfai = null
+ malfhack = FALSE
+ user.visible_message(\
+ "[name] has discarded the strangely programmed APC electronics from [src]!",
+ "You discarded the strangely programmed board.",
+ "You hear metallic levering."
+ )
+ else
+ user.visible_message(
+ "[user] exchanges out broken the APC electronics inside [src]!",
+ "You carefully remove the charred electronics, replacing it with a functional board.",
+ "You hear metallic levering and a crack, followed by a gentle click.")
+ playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
+ qdel(used)
+ electronics_state = APC_ELECTRONICS_INSTALLED
+ locked = FALSE
+ stat &= ~MAINT
+ update_icon()
+ return ITEM_INTERACT_COMPLETE
+ else
+ to_chat(user, "[src] already contains APC electronics!")
+ return ITEM_INTERACT_COMPLETE
if(stat & BROKEN)
to_chat(user, "[src] is damaged! You must repair the frame before you can install [used]!")
@@ -368,7 +393,7 @@
update_icon()
return ITEM_INTERACT_COMPLETE
- if(has_electronics())
+ if(has_electronics() && user.mind && !HAS_TRAIT(user.mind, TRAIT_ELECTRICAL_SPECIALIST))
to_chat(user, "You cannot repair [src] until you remove the electronics!")
return ITEM_INTERACT_COMPLETE
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index 1a391158a0c..00a8ed58fa6 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -231,8 +231,13 @@
if(amount < 3)
to_chat(user, "You need more paper.")
return
- if(!do_after_once(user, 1.5 SECONDS, target = C) || C.opened || !use(3)) // Checking these again since it's after a delay
- return
+ // Checking these again since it's after a delay
+ if(user.mind && HAS_TRAIT(user.mind, TRAIT_PACK_RAT))
+ if(!do_after_once(user, 0.75 SECONDS, target = C) || C.opened || !use(3))
+ return
+ else
+ if(!do_after_once(user, 2 SECONDS, target = C) || C.opened || !use(3))
+ return
var/obj/structure/big_delivery/P = new(get_turf(C))
P.wrapped = C
C.loc = P
diff --git a/code/modules/smithing/smith_item.dm b/code/modules/smithing/smith_item.dm
index 636e07b0ccf..84b21e62d59 100644
--- a/code/modules/smithing/smith_item.dm
+++ b/code/modules/smithing/smith_item.dm
@@ -41,3 +41,8 @@
name = "[quality.name] " + name
return
name = "[quality.name] [material.name] [initial(name)]"
+
+/obj/item/smithed_item/examine(mob/user)
+ . = ..()
+ if(HAS_TRAIT(user.mind, TRAIT_SMITH))
+ . += "You can use ALT + Click while in-hand to determine specific statistics of this item."
diff --git a/code/modules/smithing/smithed_items/inserts.dm b/code/modules/smithing/smithed_items/inserts.dm
index 64df3631286..6047bd154c7 100644
--- a/code/modules/smithing/smithed_items/inserts.dm
+++ b/code/modules/smithing/smithed_items/inserts.dm
@@ -168,3 +168,20 @@
siemens_coeff = 1
quality = /datum/smith_quality/masterwork
material = /datum/smith_material/platinum
+
+/obj/item/smithed_item/insert/AltClick(mob/user, modifiers)
+ if(!HAS_TRAIT(user.mind, TRAIT_SMITH))
+ return
+ if(do_after_once(user, 3 SECONDS, target = src, allow_moving = TRUE, must_be_held = TRUE))
+ var/compiled_message = "\
+ You determine the following properties on [src]:
\
+ Brute Armor mod: [brute_armor]
\
+ Burn Armor mod: [burn_armor]
\
+ Laser Armor mod: [laser_armor]
\
+ Explosive Resistance mod: [explosive_armor]
\
+ Movement Speed mod: [movement_speed_mod]
\
+ Heat Insulation mod: [heat_insulation]
\
+ Siemens Coefficient mod: [siemens_coeff]
\
+ Radiation Resistance mod: [radiation_armor]
\
+ "
+ to_chat(user, compiled_message)
diff --git a/code/modules/smithing/smithed_items/lens.dm b/code/modules/smithing/smithed_items/lens.dm
index eb325a3f595..52aded1eb4b 100644
--- a/code/modules/smithing/smithed_items/lens.dm
+++ b/code/modules/smithing/smithed_items/lens.dm
@@ -148,3 +148,21 @@
durability = 3000
quality = /datum/smith_quality/masterwork
material = /datum/smith_material/platinum
+
+/obj/item/smithed_item/lens/AltClick(mob/user, modifiers)
+ if(!HAS_TRAIT(user.mind, TRAIT_SMITH))
+ return
+ if(do_after_once(user, 3 SECONDS, target = src, allow_moving = TRUE, must_be_held = TRUE))
+ var/compiled_message = "\
+ You determine the following properties on [src]:
\
+ Base Laser Speed mod: [base_laser_speed_mult]
\
+ Base Power Draw mod: [base_power_mult]
\
+ Base Damage mod: [base_damage_mult]
\
+ Base Fire Rate mod: [base_fire_rate_mult]
\
+ Laser Speed Multiplier: [laser_speed_mult]
\
+ Power Draw Multiplier: [power_mult]
\
+ Damage multiplier: [damage_mult]
\
+ Fire Rate Multiplier: [fire_rate_mult]
\
+ Durability: [durability]
\
+ "
+ to_chat(user, compiled_message)
diff --git a/code/modules/smithing/smithed_items/tool_bits.dm b/code/modules/smithing/smithed_items/tool_bits.dm
index b8866e35fc1..e3d428ca619 100644
--- a/code/modules/smithing/smithed_items/tool_bits.dm
+++ b/code/modules/smithing/smithed_items/tool_bits.dm
@@ -150,3 +150,21 @@
durability = 300
quality = /datum/smith_quality/masterwork
material = /datum/smith_material/platinum
+
+/obj/item/smithed_item/tool_bit/AltClick(mob/user, modifiers)
+ if(!HAS_TRAIT(user.mind, TRAIT_SMITH))
+ return
+ if(do_after_once(user, 3 SECONDS, target = src, allow_moving = TRUE, must_be_held = TRUE))
+ var/compiled_message = "\
+ You determine the following properties on [src]:
\
+ Base Speed mod: [base_speed_mod]
\
+ Base Efficiency mod: [base_efficiency_mod]
\
+ Base Productivity mod: [base_productivity_mod]
\
+ Speed Multiplier: [speed_mod]
\
+ Efficiency Multiplier: [efficiency_mod]
\
+ Productivity Multiplier: [productivity_mod]
\
+ Failure Rate: [failure_rate]
\
+ Size Mod: [size_mod]
\
+ Durability: [durability]
\
+ "
+ to_chat(user, compiled_message)
diff --git a/code/modules/vehicle/ambulance.dm b/code/modules/vehicle/ambulance.dm
index 12f543cb644..1238a0de208 100644
--- a/code/modules/vehicle/ambulance.dm
+++ b/code/modules/vehicle/ambulance.dm
@@ -3,6 +3,7 @@
desc = "This is what the paramedic uses to run over people they need to take to medbay."
icon_state = "docwagon2"
key_type = /obj/item/key/ambulance
+ var/siren_on = FALSE
var/obj/structure/bed/amb_trolley/bed = null
var/datum/action/ambulance_alarm/AA
var/datum/looping_sound/ambulance_alarm/soundloop
@@ -41,9 +42,11 @@
cooldown = world.time
if(A.soundloop.muted)
+ A.siren_on = TRUE
A.soundloop.start()
A.set_light(4,3,"#F70027")
else
+ A.siren_on = FALSE
A.soundloop.stop()
A.set_light(0)
@@ -104,6 +107,51 @@
var/mob/living/buckled_mob = m
buckled_mob.setDir(Dir)
+/obj/vehicle/ambulance/Bump(atom/movable/M)
+ if(has_buckled_mobs())
+ for(var/mob/living/L in buckled_mobs)
+ if(L.mind && HAS_TRAIT(L.mind, TRAIT_SPEED_DEMON))
+ if(isobj(M))
+ var/obj/O = M
+ if(!O.anchored)
+ step(M, dir)
+ else if(ismob(M) && siren_on)
+ run_over(M)
+ break
+
+/obj/vehicle/ambulance/proc/run_over(mob/living/M)
+ var/directional_blocked = FALSE
+ var/turf/T = get_step(M.loc, turn(dir, 90))
+ if(check_density(T))
+ T = get_step(M.loc, turn(dir, -90))
+ if(check_density(T))
+ directional_blocked = TRUE
+ else
+ step(M, turn(dir, -90))
+ else
+ step(M, turn(dir, 90))
+ playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE)
+ if(directional_blocked || (emagged == TRUE && installed_vtec == TRUE)) // GET OUT OF THE WAY, ASSHOLE!
+ M.KnockDown(2 SECONDS)
+
+/obj/vehicle/ambulance/proc/check_density(turf/T)
+ if(T.density)
+ return TRUE
+ for(var/atom/movable/thing in T.contents)
+ if(thing.density)
+ return TRUE
+ return FALSE
+
+/obj/vehicle/ambulance/emag_act(mob/user)
+ emagged = TRUE
+ visible_message("[src] sputters and fizzles as the safeties are shorted out!")
+ do_sparks(3, 0, src)
+
+/obj/vehicle/ambulance/examine(mob/user)
+ . = ..()
+ if(emagged && in_range(src, user))
+ . += "The safeties seem to have been shorted out!"
+
/obj/structure/bed/amb_trolley
name = "ambulance train trolley"
icon = 'icons/vehicles/CargoTrain.dmi'