"
- if(inserted_id)
- dat += text("[inserted_id] ")
- dat += text("Collected Points: [inserted_id.points]. Reset. ")
- dat += text("Card goal: [inserted_id.goal]. Set ")
- dat += text("Space Law recommends quotas of 100 points per minute they would normally serve in the brig. ")
- else
- dat += text("Insert Prisoner ID. ")
- dat += "
Prisoner Implant Management
"
- dat += "Chemical Implants "
- var/turf/Tr = null
- for(var/obj/item/implant/chem/C in GLOB.tracked_chem_implants)
- Tr = get_turf(C)
- if((Tr) && (Tr.z != src.z))
- continue//Out of range
- if(!C.imp_in)
- continue
- dat += "ID: [C.imp_in.name] | Remaining Units: [C.reagents.total_volume] "
- dat += "| Inject: "
- dat += "((1))"
- dat += "((5))"
- dat += "((10)) "
- dat += "******************************** "
- dat += "Tracking Implants "
- for(var/obj/item/implant/tracking/T in GLOB.tracked_implants)
- if(!isliving(T.imp_in))
- continue
- Tr = get_turf(T)
- if((Tr) && (Tr.z != src.z))
- continue//Out of range
-
- var/loc_display = "Unknown"
- var/mob/living/M = T.imp_in
- if(is_station_level(Tr.z) && !isspaceturf(M.loc))
- var/turf/mob_loc = get_turf(M)
- loc_display = mob_loc.loc
-
- dat += "ID: [T.imp_in.name] | Location: [loc_display] "
- dat += "(Message Holder) | "
- dat += "******************************** "
- dat += "Lock Console"
- var/datum/browser/popup = new(user, "computer", "Prisoner Management Console", 400, 500)
- popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
- popup.open()
- return
-
-/obj/machinery/computer/prisoner/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/card/id))
- return attack_hand(user)
- else
- return ..()
-
-/obj/machinery/computer/prisoner/process()
- if(!..())
- src.updateDialog()
- return
-
-
-/obj/machinery/computer/prisoner/Topic(href, href_list)
- if(..())
- return
- if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
- usr.set_machine(src)
-
- if(href_list["id"])
- if(href_list["id"] =="insert" && !inserted_id)
- var/obj/item/card/id/prisoner/I = usr.is_holding_item_of_type(/obj/item/card/id/prisoner)
- if(I)
- if(!usr.transferItemToLoc(I, src))
- return
- inserted_id = I
- else
- to_chat(usr, "No valid ID.")
- else if(inserted_id)
- switch(href_list["id"])
- if("eject")
- inserted_id.forceMove(drop_location())
- inserted_id.verb_pickup()
- inserted_id = null
- if("reset")
- inserted_id.points = 0
- if("setgoal")
- var/num = round(input(usr, "Choose prisoner's goal:", "Input an Integer", null) as num|null)
- if(num >= 0)
- num = min(num,1000) //Cap the quota to the equivilent of 10 minutes.
- inserted_id.goal = num
- else if(href_list["inject1"])
- var/obj/item/implant/I = locate(href_list["inject1"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(1)
- else if(href_list["inject5"])
- var/obj/item/implant/I = locate(href_list["inject5"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(5)
-
- else if(href_list["inject10"])
- var/obj/item/implant/I = locate(href_list["inject10"]) in GLOB.tracked_chem_implants
- if(I && istype(I))
- I.activate(10)
-
- else if(href_list["lock"])
- if(src.allowed(usr))
- screen = !screen
- else
- to_chat(usr, "Unauthorized Access.")
-
- else if(href_list["warn"])
- var/warning = copytext(sanitize(input(usr,"Message:","Enter your message here!","")),1,MAX_MESSAGE_LEN)
- if(!warning)
- return
- var/obj/item/implant/I = locate(href_list["warn"]) in GLOB.tracked_implants
- if(I && istype(I) && I.imp_in)
- var/mob/living/R = I.imp_in
- to_chat(R, "You hear a voice in your head saying: '[warning]'")
- log_directed_talk(usr, R, warning, LOG_SAY, "implant message")
-
- src.add_fingerprint(usr)
- src.updateUsrDialog()
- return
diff --git a/code/game/machinery/computer/prisoner/_prisoner.dm b/code/game/machinery/computer/prisoner/_prisoner.dm
new file mode 100644
index 0000000000..d07c351a22
--- /dev/null
+++ b/code/game/machinery/computer/prisoner/_prisoner.dm
@@ -0,0 +1,52 @@
+/obj/machinery/computer/prisoner
+ var/obj/item/card/id/prisoner/contained_id
+
+/obj/machinery/computer/prisoner/Destroy()
+ if(contained_id)
+ contained_id.forceMove(get_turf(src))
+ return ..()
+
+
+/obj/machinery/computer/prisoner/examine(mob/user)
+ . = ..()
+ if(contained_id)
+ . += "Alt-click to eject the ID card."
+
+
+
+/obj/machinery/computer/prisoner/AltClick(mob/user)
+ id_eject(user)
+ return ..()
+
+/obj/machinery/computer/prisoner/proc/id_insert(mob/user, obj/item/card/id/prisoner/P)
+ if(istype(P))
+ if(contained_id)
+ to_chat(user, "There's already an ID card in the console!")
+ return
+ if(!user.transferItemToLoc(P, src))
+ return
+ contained_id = P
+ user.visible_message("[user] inserts an ID card into the console.", \
+ "You insert the ID card into the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+
+/obj/machinery/computer/prisoner/proc/id_eject(mob/user)
+ if(!contained_id)
+ to_chat(user, "There's no ID card in the console!")
+ return
+ else
+ contained_id.forceMove(drop_location())
+ if(!issilicon(user) && Adjacent(user))
+ user.put_in_hands(contained_id)
+ contained_id = null
+ user.visible_message("[user] gets an ID card from the console.", \
+ "You get the ID card from the console.")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ updateUsrDialog()
+
+/obj/machinery/computer/prisoner/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/card/id/prisoner))
+ id_insert(user, I)
+ else
+ return ..()
diff --git a/code/game/machinery/computer/prisoner/gulag_teleporter.dm b/code/game/machinery/computer/prisoner/gulag_teleporter.dm
new file mode 100644
index 0000000000..f6e164efe9
--- /dev/null
+++ b/code/game/machinery/computer/prisoner/gulag_teleporter.dm
@@ -0,0 +1,142 @@
+//computer that handle the points and teleports the prisoner
+/obj/machinery/computer/prisoner/gulag_teleporter_computer
+ name = "labor camp teleporter console"
+ desc = "Used to send criminals to the Labor Camp."
+ icon_screen = "explosive"
+ icon_keyboard = "security_key"
+ req_access = list(ACCESS_ARMORY)
+ circuit = /obj/item/circuitboard/computer/gulag_teleporter_console
+ var/default_goal = 200
+ var/obj/machinery/gulag_teleporter/teleporter = null
+ var/obj/structure/gulag_beacon/beacon = null
+ var/mob/living/carbon/human/prisoner = null
+ var/datum/data/record/temporary_record = null
+
+ light_color = LIGHT_COLOR_RED
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/Initialize()
+ . = ..()
+ scan_machinery()
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
+ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "gulag_console", name, 455, 440, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_data(mob/user)
+ var/list/data = list()
+
+ var/list/prisoner_list = list()
+ var/can_teleport = FALSE
+
+ if(teleporter && (teleporter.occupant && ishuman(teleporter.occupant)))
+ prisoner = teleporter.occupant
+ prisoner_list["name"] = prisoner.real_name
+ if(contained_id)
+ can_teleport = TRUE
+ if(!isnull(GLOB.data_core.general))
+ for(var/r in GLOB.data_core.security)
+ var/datum/data/record/R = r
+ if(R.fields["name"] == prisoner_list["name"])
+ temporary_record = R
+ prisoner_list["crimstat"] = temporary_record.fields["criminal"]
+
+ data["prisoner"] = prisoner_list
+
+ if(teleporter)
+ data["teleporter"] = teleporter
+ data["teleporter_location"] = "([teleporter.x], [teleporter.y], [teleporter.z])"
+ data["teleporter_lock"] = teleporter.locked
+ data["teleporter_state_open"] = teleporter.state_open
+ if(beacon)
+ data["beacon"] = beacon
+ data["beacon_location"] = "([beacon.x], [beacon.y], [beacon.z])"
+ if(contained_id)
+ data["id"] = contained_id
+ data["id_name"] = contained_id.registered_name
+ data["goal"] = contained_id.goal
+ data["can_teleport"] = can_teleport
+
+ return data
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_act(action, list/params)
+ if(isliving(usr))
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ if(..())
+ return
+ if(!allowed(usr))
+ to_chat(usr, "Access denied.")
+ return
+ switch(action)
+ if("scan_teleporter")
+ teleporter = findteleporter()
+ if("scan_beacon")
+ beacon = findbeacon()
+ if("handle_id")
+ if(contained_id)
+ id_eject(usr)
+ else
+ id_insert(usr)
+ if("set_goal")
+ var/new_goal = input("Set the amount of points:", "Points", contained_id.goal) as num|null
+ if(!isnum(new_goal))
+ return
+ if(!new_goal)
+ new_goal = default_goal
+ if (new_goal > 1000)
+ to_chat(usr, "The entered amount of points is too large. Points have instead been set to the maximum allowed amount.")
+ contained_id.goal = CLAMP(new_goal, 0, 1000) //maximum 1000 points
+ if("toggle_open")
+ if(teleporter.locked)
+ to_chat(usr, "The teleporter is locked")
+ return
+ teleporter.toggle_open()
+ if("teleporter_lock")
+ if(teleporter.state_open)
+ to_chat(usr, "Close the teleporter before locking!")
+ return
+ teleporter.locked = !teleporter.locked
+ if("teleport")
+ if(!teleporter || !beacon)
+ return
+ addtimer(CALLBACK(src, .proc/teleport, usr), 5)
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/proc/scan_machinery()
+ teleporter = findteleporter()
+ beacon = findbeacon()
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/proc/findteleporter()
+ var/obj/machinery/gulag_teleporter/teleporterf = null
+
+ for(var/direction in GLOB.cardinals)
+ teleporterf = locate(/obj/machinery/gulag_teleporter, get_step(src, direction))
+ if(teleporterf && teleporterf.is_operational())
+ return teleporterf
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/proc/findbeacon()
+ return locate(/obj/structure/gulag_beacon)
+
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/proc/teleport(mob/user)
+ if(!contained_id) //incase the ID was removed after the transfer timer was set.
+ say("Warning: Unable to transfer prisoner without a valid Prisoner ID inserted!")
+ return
+ var/id_goal_not_set
+ if(!contained_id.goal)
+ id_goal_not_set = TRUE
+ contained_id.goal = default_goal
+ say("[contained_id]'s ID card goal defaulting to [contained_id.goal] points.")
+ log_game("[key_name(user)] teleported [key_name(prisoner)] to the Labor Camp [COORD(beacon)] for [id_goal_not_set ? "default goal of ":""][contained_id.goal] points.")
+ teleporter.handle_prisoner(contained_id, temporary_record)
+ playsound(src, 'sound/weapons/emitter.ogg', 50, 1)
+ prisoner.forceMove(get_turf(beacon))
+ prisoner.Stun(40) // small travel dizziness
+ to_chat(prisoner, "The teleportation makes you a little dizzy.")
+ new /obj/effect/particle_effect/sparks(get_turf(prisoner))
+ playsound(src, "sparks", 50, 1)
+ if(teleporter.locked)
+ teleporter.locked = FALSE
+ teleporter.toggle_open()
+ contained_id = null
+ temporary_record = null
diff --git a/code/game/machinery/computer/prisoner/management.dm b/code/game/machinery/computer/prisoner/management.dm
new file mode 100644
index 0000000000..e231a1748a
--- /dev/null
+++ b/code/game/machinery/computer/prisoner/management.dm
@@ -0,0 +1,139 @@
+
+/obj/machinery/computer/prisoner/management
+ name = "prisoner management console"
+ desc = "Used to manage tracking implants placed inside criminals."
+ icon_screen = "explosive"
+ icon_keyboard = "security_key"
+ req_access = list(ACCESS_BRIG)
+ var/id = 0
+ var/temp = null
+ var/status = 0
+ var/timeleft = 60
+ var/stop = 0
+ var/screen = 0 // 0 - No Access Denied, 1 - Access allowed
+ circuit = /obj/item/circuitboard/computer/prisoner
+
+ light_color = LIGHT_COLOR_RED
+
+/obj/machinery/computer/prisoner/management/ui_interact(mob/user)
+ . = ..()
+ if(isliving(user))
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ var/dat = ""
+ if(screen == 0)
+ dat += "{Log In}"
+ else if(screen == 1)
+ dat += "
Prisoner ID Management
"
+ if(contained_id)
+ dat += text("[contained_id] ")
+ dat += text("Collected Points: [contained_id.points]. Reset. ")
+ dat += text("Card goal: [contained_id.goal]. Set ")
+ dat += text("Space Law recommends quotas of 100 points per minute they would normally serve in the brig. ")
+ else
+ dat += text("Insert Prisoner ID. ")
+ dat += "
Prisoner Implant Management
"
+ dat += "Chemical Implants "
+ var/turf/Tr = null
+ for(var/obj/item/implant/chem/C in GLOB.tracked_chem_implants)
+ Tr = get_turf(C)
+ if((Tr) && (Tr.z != src.z))
+ continue//Out of range
+ if(!C.imp_in)
+ continue
+ dat += "ID: [C.imp_in.name] | Remaining Units: [C.reagents.total_volume] "
+ dat += "| Inject: "
+ dat += "((1))"
+ dat += "((5))"
+ dat += "((10)) "
+ dat += "******************************** "
+ dat += "Tracking Implants "
+ for(var/obj/item/implant/tracking/T in GLOB.tracked_implants)
+ if(!isliving(T.imp_in))
+ continue
+ Tr = get_turf(T)
+ if((Tr) && (Tr.z != src.z))
+ continue//Out of range
+
+ var/loc_display = "Unknown"
+ var/mob/living/M = T.imp_in
+ if(is_station_level(Tr.z) && !isspaceturf(M.loc))
+ var/turf/mob_loc = get_turf(M)
+ loc_display = mob_loc.loc
+
+ dat += "ID: [T.imp_in.name] | Location: [loc_display] "
+ dat += "(Message Holder) | "
+ dat += "******************************** "
+ dat += "{Log Out}"
+ var/datum/browser/popup = new(user, "computer", "Prisoner Management Console", 400, 500)
+ popup.set_content(dat)
+ popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
+ popup.open()
+ return
+
+/obj/machinery/computer/prisoner/management/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/card/id))
+ if(screen)
+ id_insert(user)
+ else
+ to_chat(user, "Unauthorized access.")
+ else
+ return ..()
+
+/obj/machinery/computer/prisoner/management/process()
+ if(!..())
+ src.updateDialog()
+ return
+
+/obj/machinery/computer/prisoner/management/Topic(href, href_list)
+ if(..())
+ return
+ if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
+ usr.set_machine(src)
+
+ if(href_list["id"])
+ if(href_list["id"] =="insert" && !contained_id)
+ id_insert(usr)
+ else if(contained_id)
+ switch(href_list["id"])
+ if("eject")
+ id_eject(usr)
+ if("reset")
+ contained_id.points = 0
+ if("setgoal")
+ var/num = round(input(usr, "Choose prisoner's goal:", "Input an Integer", null) as num|null)
+ if(num >= 0)
+ num = min(num,1000) //Cap the quota to the equivilent of 10 minutes.
+ contained_id.goal = num
+ else if(href_list["inject1"])
+ var/obj/item/implant/I = locate(href_list["inject1"]) in GLOB.tracked_chem_implants
+ if(I && istype(I))
+ I.activate(1)
+ else if(href_list["inject5"])
+ var/obj/item/implant/I = locate(href_list["inject5"]) in GLOB.tracked_chem_implants
+ if(I && istype(I))
+ I.activate(5)
+ else if(href_list["inject10"])
+ var/obj/item/implant/I = locate(href_list["inject10"]) in GLOB.tracked_chem_implants
+ if(I && istype(I))
+ I.activate(10)
+
+ else if(href_list["lock"])
+ if(allowed(usr))
+ screen = !screen
+ playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
+ else
+ to_chat(usr, "Unauthorized access.")
+
+ else if(href_list["warn"])
+ var/warning = copytext(sanitize(input(usr,"Message:","Enter your message here!","")),1,MAX_MESSAGE_LEN)
+ if(!warning)
+ return
+ var/obj/item/implant/I = locate(href_list["warn"]) in GLOB.tracked_implants
+ if(I && istype(I) && I.imp_in)
+ var/mob/living/R = I.imp_in
+ to_chat(R, "You hear a voice in your head saying: '[warning]'")
+ log_directed_talk(usr, R, warning, LOG_SAY, "implant message")
+
+ src.add_fingerprint(usr)
+ src.updateUsrDialog()
+ return
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 607f8dbe78..1823e34100 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -5,13 +5,10 @@
icon_keyboard = "security_key"
req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
circuit = /obj/item/circuitboard/computer/secure_data
- var/obj/item/card/id/scan = null
- var/authenticated = null
var/rank = null
var/screen = null
var/datum/data/record/active1 = null
var/datum/data/record/active2 = null
- var/a_id = null
var/temp = null
var/printing = null
var/can_change_id = 0
@@ -23,11 +20,6 @@
light_color = LIGHT_COLOR_RED
-/obj/machinery/computer/secure_data/examine(mob/user)
- ..()
- if(scan)
- to_chat(user, "Alt-click to eject the ID card.")
-
/obj/machinery/computer/secure_data/syndie
icon_keyboard = "syndie_key"
@@ -40,32 +32,19 @@
clockwork = TRUE //it'd look weird
pass_flags = PASSTABLE
-/obj/machinery/computer/secure_data/attackby(obj/item/O, mob/user, params)
- if(istype(O, /obj/item/card/id))
- if(!scan)
- if(!user.transferItemToLoc(O, src))
- return
- scan = O
- to_chat(user, "You insert [O].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- updateUsrDialog()
- else
- to_chat(user, "There's already an ID card in the console.")
- else
- return ..()
-
//Someone needs to break down the dat += into chunks instead of long ass lines.
/obj/machinery/computer/secure_data/ui_interact(mob/user)
. = ..()
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
if(src.z > 6)
to_chat(user, "Unable to establish a connection: \black You're too far away from the station!")
return
var/dat
if(temp)
- dat = text("[]
�%@%(*$%&(�&?*(%&�/{}"
- var/time = 100 * length(linkedServer.decryptkey)
- addtimer(CALLBACK(src, .proc/UnmagConsole), time)
- message = rebootmsg
- else
+ if(isnull(linkedServer))
to_chat(user, "A no server error appears on the screen.")
+ return
+ obj_flags |= EMAGGED
+ screen = 2
+ spark_system.set_up(5, 0, src)
+ spark_system.start()
+ var/obj/item/paper/monitorkey/MK = new(loc, linkedServer)
+ // Will help make emagging the console not so easy to get away with.
+ MK.info += "
�%@%(*$%&(�&?*(%&�/{}"
+ var/time = 100 * length(linkedServer.decryptkey)
+ addtimer(CALLBACK(src, .proc/UnmagConsole), time)
+ message = rebootmsg
+ return TRUE
/obj/machinery/computer/message_monitor/New()
. = ..()
@@ -419,6 +421,7 @@
"name" = "[customsender]",
"job" = "[customjob]",
"message" = custommessage,
+ "emoji_message" = emoji_parse(custommessage),
"targets" = list("[customrecepient.owner] ([customrecepient.ownjob])")
))
// this will log the signal and transmit it to the target
diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm
index cc390e1fc0..56870c5198 100644
--- a/code/game/machinery/telecomms/machines/message_server.dm
+++ b/code/game/machinery/telecomms/machines/message_server.dm
@@ -106,10 +106,11 @@
return "Everyone"
return data["targets"][1]
-/datum/signal/subspace/pda/proc/format_message()
+/datum/signal/subspace/pda/proc/format_message(emojify = FALSE)
+ var/message = emojify ? data["emoji_message"] : data["message"]
if (logged && data["photo"])
- return "\"[data["message"]]\" (Photo)"
- return "\"[data["message"]]\""
+ return "\"[message]\" (Photo)"
+ return "\"[message]\""
/datum/signal/subspace/pda/broadcast()
if (!logged) // Can only go through if a message server logs it
diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm
index f9fa2544b8..564f308df3 100644
--- a/code/game/mecha/combat/gygax.dm
+++ b/code/game/mecha/combat/gygax.dm
@@ -6,6 +6,7 @@
dir_in = 1 //Facing North.
max_integrity = 250
deflect_chance = 5
+ force = 20
armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
max_temperature = 25000
infra_luminosity = 6
@@ -13,6 +14,7 @@
internal_damage_threshold = 35
max_equip = 3
step_energy_drain = 3
+ leg_overload_coeff = 300
/obj/mecha/combat/gygax/dark
desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications."
@@ -20,6 +22,7 @@
icon_state = "darkgygax"
max_integrity = 300
deflect_chance = 15
+ force = 25
armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
max_temperature = 35000
leg_overload_coeff = 100
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index 01e304872f..e1dc80911f 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -64,10 +64,15 @@
/obj/machinery/mecha_part_fabricator/emag_act()
+ . = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
req_access = list()
+ INVOKE_ASYNC(src, .proc/error_action_sucessful)
+ return TRUE
+
+/obj/machinery/mecha_part_fabricator/proc/error_action_sucessful()
say("DB error \[Code 0x00F1\]")
sleep(10)
say("Attempting auto-repair...")
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index cc69b88cc4..9f072c48b6 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -42,6 +42,9 @@
/obj/effect/decal/cleanable/glass/ex_act()
qdel(src)
+/obj/effect/decal/cleanable/glass/plasma
+ icon_state = "plasmatiny"
+
/obj/effect/decal/cleanable/dirt
name = "dirt"
desc = "Someone should clean that up."
diff --git a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
index 1708753dc1..1e755bc7e9 100644
--- a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
+++ b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
@@ -36,4 +36,203 @@
/obj/effect/turf_decal/tile/neutral
name = "neutral corner"
color = "#D4D4D4"
- alpha = 50
\ No newline at end of file
+ alpha = 50
+
+/obj/effect/turf_decal/trimline
+ layer = TURF_PLATING_DECAL_LAYER
+ alpha = 110
+ icon_state = "trimline_box"
+
+/obj/effect/turf_decal/trimline/white
+ color = "#FFFFFF"
+
+/obj/effect/turf_decal/trimline/white/line
+ name = "trim decal"
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/white/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/white/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/white/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/white/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/red
+ color = "#DE3A3A"
+
+/obj/effect/turf_decal/trimline/red/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/red/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/red/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/red/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/red/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/green
+ color = "#9FED58"
+
+/obj/effect/turf_decal/trimline/green/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/green/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/green/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/green/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/green/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/blue
+ color = "#52B4E9"
+
+/obj/effect/turf_decal/trimline/blue/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/blue/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/blue/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/blue/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/blue/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/yellow
+ color = "#EFB341"
+
+/obj/effect/turf_decal/trimline/yellow/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/yellow/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/yellow/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/yellow/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/yellow/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/purple
+ color = "#D381C9"
+
+/obj/effect/turf_decal/trimline/purple/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/purple/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/purple/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/purple/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/purple/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/brown
+ color = "#A46106"
+
+/obj/effect/turf_decal/trimline/brown/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/brown/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/brown/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/brown/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/end
+ icon_state = "trimline_end_fill"
+
+/obj/effect/turf_decal/trimline/neutral
+ color = "#D4D4D4"
+ alpha = 50
+
+/obj/effect/turf_decal/trimline/neutral/line
+ icon_state = "trimline"
+
+/obj/effect/turf_decal/trimline/neutral/corner
+ icon_state = "trimline_corner"
+
+/obj/effect/turf_decal/trimline/neutral/end
+ icon_state = "trimline_end"
+
+/obj/effect/turf_decal/trimline/neutral/filled
+ icon_state = "trimline_box_fill"
+
+/obj/effect/turf_decal/trimline/neutral/filled/line
+ icon_state = "trimline_fill"
+
+/obj/effect/turf_decal/trimline/neutral/filled/corner
+ icon_state = "trimline_corner_fill"
+
+/obj/effect/turf_decal/trimline/brown/filled/end
+ icon_state = "trimline_end_fill"
\ No newline at end of file
diff --git a/code/game/objects/effects/spawners/bundle.dm b/code/game/objects/effects/spawners/bundle.dm
index 2fe8d2a460..b9acba70d9 100644
--- a/code/game/objects/effects/spawners/bundle.dm
+++ b/code/game/objects/effects/spawners/bundle.dm
@@ -133,7 +133,7 @@
/obj/effect/spawner/bundle/costume/holiday_priest
name = "holiday priest costume spawner"
items = list(
- /obj/item/clothing/suit/holidaypriest)
+ /obj/item/clothing/suit/chaplain/holidaypriest)
/obj/effect/spawner/bundle/costume/marisawizard
name = "marisa wizard costume spawner"
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 566a64577d..c98cef2b87 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -68,6 +68,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/strip_delay = 40 //In deciseconds, how long an item takes to remove from another person
var/breakouttime = 0
var/list/materials
+ var/reskinned = FALSE
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/list/species_exception = null // list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index 1ff3c7df9f..cf706359f7 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -37,6 +37,7 @@ RLD
var/has_ammobar = FALSE //controls whether or not does update_icon apply ammo indicator overlays
var/ammo_sections = 10 //amount of divisions in the ammo indicator overlay/number of ammo indicator states
var/custom_range = 7
+ var/upgrade = FALSE
/obj/item/construction/Initialize()
. = ..()
@@ -82,6 +83,11 @@ RLD
loaded = loadwithsheets(W, sheetmultiplier * 0.25, user) // 1 matter for 1 floortile, as 4 tiles are produced from 1 metal
if(loaded)
to_chat(user, "[src] now holds [matter]/[max_matter] matter-units.")
+ else if(istype(W, /obj/item/rcd_upgrade))
+ to_chat(user, "You upgrade the RCD with the [W]!")
+ upgrade = TRUE
+ playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
+ qdel(W)
else
return ..()
update_icon() //ensures that ammo counters (if present) get updated
@@ -148,6 +154,7 @@ RLD
has_ammobar = TRUE
var/mode = 1
var/ranged = FALSE
+ var/computer_dir = 1
var/airlock_type = /obj/machinery/door/airlock
var/airlock_glass = FALSE // So the floor's rcd_act knows how much ammo to use
var/window_type = /obj/structure/window/fulltile
@@ -270,6 +277,28 @@ RLD
return FALSE
return TRUE
+/obj/item/construction/rcd/proc/change_computer_dir(mob/user)
+ if(!user)
+ return
+ var/list/computer_dirs = list(
+ "NORTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "cnorth"),
+ "EAST" = image(icon = 'icons/mob/radial.dmi', icon_state = "ceast"),
+ "SOUTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "csouth"),
+ "WEST" = image(icon = 'icons/mob/radial.dmi', icon_state = "cwest")
+ )
+ var/computerdirs = show_radial_menu(user, src, computer_dirs, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+ if(!check_menu(user))
+ return
+ switch(computerdirs)
+ if("NORTH")
+ computer_dir = 1
+ if("EAST")
+ computer_dir = 4
+ if("SOUTH")
+ computer_dir = 2
+ if("WEST")
+ computer_dir = 8
+
/obj/item/construction/rcd/proc/change_airlock_setting(mob/user)
if(!user)
return
@@ -434,10 +463,15 @@ RLD
..()
var/list/choices = list(
"Airlock" = image(icon = 'icons/mob/radial.dmi', icon_state = "airlock"),
- "Deconstruct" = image(icon= 'icons/mob/radial.dmi', icon_state = "delete"),
"Grilles & Windows" = image(icon = 'icons/mob/radial.dmi', icon_state = "grillewindow"),
"Floors & Walls" = image(icon = 'icons/mob/radial.dmi', icon_state = "wallfloor")
)
+ if(upgrade)
+ choices += list(
+ "Deconstruct" = image(icon= 'icons/mob/radial.dmi', icon_state = "delete"),
+ "Machine Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "machine"),
+ "Computer Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "computer_dir"),
+ )
if(mode == RCD_AIRLOCK)
choices += list(
"Change Access" = image(icon = 'icons/mob/radial.dmi', icon_state = "access"),
@@ -459,6 +493,12 @@ RLD
mode = RCD_DECONSTRUCT
if("Grilles & Windows")
mode = RCD_WINDOWGRILLE
+ if("Machine Frames")
+ mode = RCD_MACHINE
+ if("Computer Frames")
+ mode = RCD_COMPUTER
+ change_computer_dir(user)
+ return
if("Change Access")
change_airlock_access(user)
return
@@ -511,6 +551,7 @@ RLD
no_ammo_message = "Insufficient charge."
desc = "A device used to rapidly build walls and floors."
canRturf = TRUE
+ upgrade = TRUE
/obj/item/construction/rcd/borg/useResource(amount, mob/user)
@@ -542,6 +583,9 @@ RLD
/obj/item/construction/rcd/loaded
matter = 160
+/obj/item/construction/rcd/loaded/upgraded
+ upgrade = TRUE
+
/obj/item/construction/rcd/combat
name = "Combat RCD"
desc = "A device used to rapidly build and deconstruct. Reload with metal, plasteel, glass or compressed matter cartridges. This RCD has been upgraded to be able to remove Rwalls!"
@@ -582,7 +626,7 @@ RLD
name = "admin RCD"
max_matter = INFINITY
matter = INFINITY
-
+ upgrade = TRUE
// Ranged RCD
@@ -776,6 +820,12 @@ RLD
return TRUE
return FALSE
+/obj/item/rcd_upgrade
+ name = "RCD advanced design disk"
+ desc = "It contains the design for machine frames, computer frames, and deconstruction."
+ icon = 'icons/obj/module.dmi'
+ icon_state = "datadisk3"
+
#undef GLOW_MODE
#undef LIGHT_MODE
#undef REMOVE_MODE
diff --git a/code/game/objects/items/RSF.dm b/code/game/objects/items/RSF.dm
index f0d15cc3a2..9c343c2e06 100644
--- a/code/game/objects/items/RSF.dm
+++ b/code/game/objects/items/RSF.dm
@@ -134,12 +134,14 @@ RSF
return
/obj/item/cookiesynth/emag_act(mob/user)
+ . = ..()
obj_flags ^= EMAGGED
if(obj_flags & EMAGGED)
to_chat(user, "You short out [src]'s reagent safety checker!")
else
to_chat(user, "You reset [src]'s reagent safety checker!")
- toxin = 0
+ toxin = FALSE
+ return TRUE
/obj/item/cookiesynth/attack_self(mob/user)
var/mob/living/silicon/robot/P = null
@@ -180,7 +182,7 @@ RSF
to_chat(user, "Fabricating Cookie..")
var/obj/item/reagent_containers/food/snacks/cookie/S = new /obj/item/reagent_containers/food/snacks/cookie(T)
if(toxin)
- S.reagents.add_reagent("chloralhydratedelayed", 10)
+ S.reagents.add_reagent("chloralhydrate", 10)
if (iscyborg(user))
var/mob/living/silicon/robot/R = user
R.cell.charge -= 100
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index 96bbe759ca..05ffcbf2fd 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -93,34 +93,22 @@
/obj/item/card/emag/afterattack(atom/target, mob/user, proximity)
. = ..()
var/atom/A = target
- if(!proximity && prox_check)
+ if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A)))
+ return
+ if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod)))
return
- //Citadel changes: modular code misfiring, so we're bypassing into main code.
if(!uses)
user.visible_message("[src] emits a weak spark. It's burnt out!")
playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
return
else if(uses <= 3)
playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead
-
- if(isturf(A))
+ if(!A.emag_act(user))
return
- if(istype(A,/obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))
- A.emag_act(user)
- uses = max(uses - 1, 0)
- if(!uses)
- user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
- playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
- if(istype(A,/obj/item/storage))
- return
- if(!(isobj(A) || issilicon(A) || isbot(A) || isdrone(A)))
- return
- else
- A.emag_act(user)
- uses = max(uses - 1, 0)
- if(!uses)
- user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
- playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
+ uses = max(uses - 1, 0)
+ if(!uses)
+ user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.")
+ playsound(src, 'sound/effects/light_flicker.ogg', 100, 1)
/obj/item/card/emagfake
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index 000c52ae43..09a128c558 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -764,20 +764,22 @@ CIGARETTE PACKETS ARE IN FANCY.DM
..()
/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY.
- if(screw)
- if(!(obj_flags & EMAGGED))
- cut_overlays()
- obj_flags |= EMAGGED
- super = FALSE
- to_chat(user, "You maximize the voltage of [src].")
- add_overlay("vapeopen_high")
- var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect
- sp.set_up(5, 1, src)
- sp.start()
- else
- to_chat(user, "[src] is already emagged!")
- else
+ . = ..()
+ if(!screw)
to_chat(user, "You need to open the cap to do that.")
+ return
+ if(obj_flags & EMAGGED)
+ to_chat(user, "[src] is already emagged!")
+ return
+ cut_overlays()
+ obj_flags |= EMAGGED
+ super = FALSE
+ to_chat(user, "You maximize the voltage of [src].")
+ add_overlay("vapeopen_high")
+ var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect
+ sp.set_up(5, 1, src)
+ sp.start()
+ return TRUE
/obj/item/clothing/mask/vape/attack_self(mob/user)
if(reagents.total_volume > 0)
diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm
index 7d2ffe0a02..c260a95afd 100644
--- a/code/game/objects/items/circuitboards/computer_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm
@@ -162,10 +162,11 @@
/obj/item/circuitboard/computer/prisoner
name = "Prisoner Management Console (Computer Board)"
- build_path = /obj/machinery/computer/prisoner
+ build_path = /obj/machinery/computer/prisoner/management
+
/obj/item/circuitboard/computer/gulag_teleporter_console
name = "Labor Camp teleporter console (Computer Board)"
- build_path = /obj/machinery/computer/gulag_teleporter_computer
+ build_path = /obj/machinery/computer/prisoner/gulag_teleporter_computer
/obj/item/circuitboard/computer/rdconsole/production
name = "R&D Console Production Only (Computer Board)"
@@ -217,10 +218,13 @@
to_chat(user, "The spectrum chip is unresponsive.")
/obj/item/circuitboard/computer/cargo/emag_act(mob/living/user)
- if(!(obj_flags & EMAGGED))
- contraband = TRUE
- obj_flags |= EMAGGED
- to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.")
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return
+ contraband = TRUE
+ obj_flags |= EMAGGED
+ to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.")
+ return TRUE
/obj/item/circuitboard/computer/cargo/express
name = "Express Supply Console (Computer Board)"
@@ -234,8 +238,12 @@
obj_flags &= ~EMAGGED
/obj/item/circuitboard/computer/cargo/express/emag_act(mob/living/user)
- to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.")
- obj_flags |= EMAGGED
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+ if(obj_flags & EMAGGED)
+ return
+ to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.")
+ obj_flags |= EMAGGED
+ return TRUE
/obj/item/circuitboard/computer/cargo/request
name = "Supply Request Console (Computer Board)"
@@ -366,4 +374,4 @@
/obj/item/circuitboard/computer/nanite_cloud_controller
name = "Nanite Cloud Control (Computer Board)"
- build_path = /obj/machinery/computer/nanite_cloud_controller
\ No newline at end of file
+ build_path = /obj/machinery/computer/nanite_cloud_controller
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 99d6c874e8..db26f643b5 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -1,6 +1,9 @@
#define RANDOM_GRAFFITI "Random Graffiti"
#define RANDOM_LETTER "Random Letter"
+#define RANDOM_PUNCTUATION "Random Punctuation"
#define RANDOM_NUMBER "Random Number"
+#define RANDOM_SYMBOL "Random Symbol"
+#define RANDOM_DRAWING "Random Drawing"
#define RANDOM_ORIENTED "Random Oriented"
#define RANDOM_RUNE "Random Rune"
#define RANDOM_ANY "Random Anything"
@@ -32,16 +35,16 @@
var/drawtype
var/text_buffer = ""
- var/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","arrow","star","poseur tag","prolizard","antilizard", "tile") //cit edit
- var/list/letters = list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")
- var/list/numerals = list("0","1","2","3","4","5","6","7","8","9")
- var/list/oriented = list("arrow","body") // These turn to face the same way as the drawer
- var/list/runes = list("rune1","rune2","rune3","rune4","rune5","rune6")
- var/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED,
- RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER)
- var/list/graffiti_large_h = list("yiffhell", "secborg", "paint")
+ var/static/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","star","poseur tag","prolizard","antilizard", "tile")
+ var/static/list/symbols = list("danger","firedanger","electricdanger","biohazard","radiation","safe","evac","space","med","trade","shop","food","peace","like","skull","nay","heart","credit")
+ var/static/list/drawings = list("smallbrush","brush","largebrush","splatter","snake","stickman","carp","ghost","clown","taser","disk","fireaxe","toolbox","corgi","cat","toilet","blueprint","beepsky","scroll","bottle","shotgun")
+ var/static/list/oriented = list("arrow","line","thinline","shortline","body","chevron","footprint","clawprint","pawprint") // These turn to face the same way as the drawer
+ var/static/list/runes = list("rune1","rune2","rune3","rune4","rune5","rune6")
+ var/static/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED,
+ RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER, RANDOM_SYMBOL, RANDOM_PUNCTUATION, RANDOM_DRAWING)
+ var/static/list/graffiti_large_h = list("yiffhell", "secborg", "paint")
- var/list/all_drawables
+ var/static/list/all_drawables = graffiti + symbols + drawings + oriented + runes + graffiti_large_h
var/paint_mode = PAINT_NORMAL
@@ -54,8 +57,6 @@
var/instant = FALSE
var/self_contained = TRUE // If it deletes itself when it is empty
- var/list/validSurfaces = list(/turf/open/floor)
-
var/edible = TRUE // That doesn't mean eating it is a good idea
var/list/reagent_contents = list("nutriment" = 1)
@@ -71,6 +72,9 @@
var/datum/team/gang/gang //For marking territory.
var/gang_tag_delay = 30 //this is the delay for gang mode tag applications on anything that gang = true on.
+/obj/item/toy/crayon/proc/isValidSurface(surface)
+ return istype(surface, /turf/open/floor)
+
/obj/item/toy/crayon/suicide_act(mob/user)
user.visible_message("[user] is jamming [src] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!")
return (BRUTELOSS|OXYLOSS)
@@ -81,7 +85,6 @@
if(name == "crayon")
name = "[item_color] crayon"
- all_drawables = graffiti + letters + numerals + oriented + runes + graffiti_large_h
drawtype = pick(all_drawables)
refill()
@@ -153,55 +156,62 @@
to_chat(user, "The cap on [src] is now [is_capped ? "on" : "off"].")
update_icon()
-/obj/item/toy/crayon/ui_data()
- var/list/data = list()
- data["drawables"] = list()
- var/list/D = data["drawables"]
+/obj/item/toy/crayon/proc/staticDrawables()
+
+ . = list()
var/list/g_items = list()
- D += list(list("name" = "Graffiti", "items" = g_items))
+ . += list(list("name" = "Graffiti", "items" = g_items))
for(var/g in graffiti)
g_items += list(list("item" = g))
var/list/glh_items = list()
- D += list(list("name" = "Graffiti Large Horizontal", "items" = glh_items))
+ . += list(list("name" = "Graffiti Large Horizontal", "items" = glh_items))
for(var/glh in graffiti_large_h)
glh_items += list(list("item" = glh))
- var/list/L_items = list()
- D += list(list("name" = "Letters", "items" = L_items))
- for(var/L in letters)
- L_items += list(list("item" = L))
+ var/list/S_items = list()
+ . += list(list("name" = "Symbols", "items" = S_items))
+ for(var/S in symbols)
+ S_items += list(list("item" = S))
- var/list/N_items = list()
- D += list(list(name = "Numerals", "items" = N_items))
- for(var/N in numerals)
- N_items += list(list("item" = N))
+ var/list/D_items = list()
+ . += list(list("name" = "Drawings", "items" = D_items))
+ for(var/D in drawings)
+ D_items += list(list("item" = D))
var/list/O_items = list()
- D += list(list(name = "Oriented", "items" = O_items))
+ . += list(list(name = "Oriented", "items" = O_items))
for(var/O in oriented)
O_items += list(list("item" = O))
var/list/R_items = list()
- D += list(list(name = "Runes", "items" = R_items))
+ . += list(list(name = "Runes", "items" = R_items))
for(var/R in runes)
R_items += list(list("item" = R))
var/list/rand_items = list()
- D += list(list(name = "Random", "items" = rand_items))
+ . += list(list(name = "Random", "items" = rand_items))
for(var/i in randoms)
rand_items += list(list("item" = i))
- data["selected_stencil"] = drawtype
- data["text_buffer"] = text_buffer
- data["has_cap"] = has_cap
- data["is_capped"] = is_capped
- data["can_change_colour"] = can_change_colour
- data["current_colour"] = paint_color
+/obj/item/toy/crayon/ui_data()
- return data
+ var/static/list/crayon_drawables
+
+ if (!crayon_drawables)
+ crayon_drawables = staticDrawables()
+
+ . = list()
+ .["drawables"] = crayon_drawables
+ .["selected_stencil"] = drawtype
+ .["text_buffer"] = text_buffer
+
+ .["has_cap"] = has_cap
+ .["is_capped"] = is_capped
+ .["can_change_colour"] = can_change_colour
+ .["current_colour"] = paint_color
/obj/item/toy/crayon/ui_act(action, list/params)
if(..())
@@ -216,6 +226,7 @@
if(stencil in all_drawables + randoms)
drawtype = stencil
. = TRUE
+ text_buffer = ""
if(stencil in graffiti_large_h)
paint_mode = PAINT_LARGE_HORIZONTAL
text_buffer = ""
@@ -235,18 +246,16 @@
update_icon()
/obj/item/toy/crayon/proc/crayon_text_strip(text)
- var/list/base = string2charlist(lowertext(text))
- var/list/out = list()
- for(var/a in base)
- if(a in (letters|numerals))
- out += a
- return jointext(out,"")
+ var/static/regex/crayon_r = new /regex(@"[^\w!?,.=%#&+\/\-]")
+ return replacetext(lowertext(text), crayon_r, "")
/obj/item/toy/crayon/afterattack(atom/target, mob/user, proximity, params)
. = ..()
if(!proximity || !check_allowed_items(target))
return
+ var/static/list/punctuation = list("!","?",".",",","/","+","-","=","%","#","&")
+
var/cost = 1
if(paint_mode == PAINT_LARGE_HORIZONTAL)
cost = 5
@@ -264,13 +273,19 @@
if(istype(target, /obj/effect/decal/cleanable))
target = target.loc
- if(!is_type_in_list(target,validSurfaces))
+ if(!isValidSurface(target))
return
var/drawing = drawtype
switch(drawtype)
if(RANDOM_LETTER)
- drawing = pick(letters)
+ drawing = ascii2text(rand(97, 122)) // a-z
+ if(RANDOM_PUNCTUATION)
+ drawing = pick(punctuation)
+ if(RANDOM_SYMBOL)
+ drawing = pick(symbols)
+ if(RANDOM_DRAWING)
+ drawing = pick(drawings)
if(RANDOM_GRAFFITI)
drawing = pick(graffiti)
if(RANDOM_RUNE)
@@ -278,17 +293,23 @@
if(RANDOM_ORIENTED)
drawing = pick(oriented)
if(RANDOM_NUMBER)
- drawing = pick(numerals)
+ drawing = ascii2text(rand(48, 57)) // 0-9
if(RANDOM_ANY)
drawing = pick(all_drawables)
var/temp = "rune"
- if(drawing in letters)
+ if(is_alpha(drawing))
temp = "letter"
- else if(drawing in graffiti)
- temp = "graffiti"
- else if(drawing in numerals)
+ else if(is_digit(drawing))
temp = "number"
+ else if(drawing in punctuation)
+ temp = "punctuation mark"
+ else if(drawing in symbols)
+ temp = "symbol"
+ else if(drawing in drawings)
+ temp = "drawing"
+ else if(drawing in graffiti|oriented)
+ temp = "graffiti"
// If a gang member is using a gang spraycan, it'll behave differently
var/gang_mode = FALSE
@@ -338,7 +359,7 @@
return
if(length(text_buffer))
- drawing = copytext(text_buffer,1,2)
+ drawing = text_buffer[1]
var/list/turf/affected_turfs = list()
@@ -362,7 +383,7 @@
if(PAINT_LARGE_HORIZONTAL)
var/turf/left = locate(target.x-1,target.y,target.z)
var/turf/right = locate(target.x+1,target.y,target.z)
- if(is_type_in_list(left, validSurfaces) && is_type_in_list(right, validSurfaces))
+ if(isValidSurface(left) && isValidSurface(right))
var/obj/effect/decal/cleanable/crayon/C = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON)
C.add_hiddenprint(user)
affected_turfs += left
@@ -377,8 +398,9 @@
else
to_chat(user, "You spray a [temp] on \the [target.name]")
- if(length(text_buffer))
+ if(length(text_buffer) > 1)
text_buffer = copytext(text_buffer,2)
+ SStgui.update_uis(src)
if(post_noise)
audible_message("You hear spraying.")
@@ -516,7 +538,7 @@
charges = -1
-/obj/item/toy/crayon/rainbow/afterattack(atom/target, mob/user, proximity)
+/obj/item/toy/crayon/rainbow/afterattack(atom/target, mob/user, proximity, params)
paint_color = rgb(rand(0,255), rand(0,255), rand(0,255))
. = ..()
@@ -591,12 +613,14 @@
can_change_colour = TRUE
gang = TRUE //Gang check is true for all things upon the honored hierarchy of spraycans, except those that are FALSE.
- validSurfaces = list(/turf/open/floor, /turf/closed/wall)
reagent_contents = list("welding_fuel" = 1, "ethanol" = 1)
pre_noise = TRUE
post_noise = FALSE
+/obj/item/toy/crayon/spraycan/isValidSurface(surface)
+ return (istype(surface, /turf/open/floor) || istype(surface, /turf/closed/wall))
+
/obj/item/toy/crayon/spraycan/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
if(is_capped || !actually_paints)
@@ -622,8 +646,8 @@
return (OXYLOSS)
-/obj/item/toy/crayon/spraycan/New()
- ..()
+/obj/item/toy/crayon/spraycan/Initialize()
+ . = ..()
// If default crayon red colour, pick a more fun spraycan colour
if(!paint_color)
paint_color = pick("#DA0000","#FF9300","#FFF200","#A8E61D","#00B7EF",
@@ -640,7 +664,7 @@
to_chat(user, "It is empty.")
to_chat(user, "Alt-click [src] to [ is_capped ? "take the cap off" : "put the cap on"].")
-/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user, proximity)
+/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user, proximity, params)
if(!proximity)
return
@@ -656,8 +680,7 @@
playsound(user.loc, 'sound/effects/spray.ogg', 25, 1, 5)
var/mob/living/carbon/C = target
- user.visible_message("[user] sprays [src] into the face of [target]!")
- to_chat(target, "[user] sprays [src] into your face!")
+ C.visible_message("[user] sprays [src] into the face of [C]!", "[user] sprays [src] into your face!")
if(C.client)
C.blur_eyes(3)
@@ -678,13 +701,14 @@
return
- if(istype(target, /obj/structure/window))
+ if(isobj(target))
if(actually_paints)
target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
- if(color_hex2num(paint_color) < 255)
- target.set_opacity(255)
- else
- target.set_opacity(initial(target.opacity))
+ if(istype(target, /obj/structure/window))
+ if(color_hex2num(paint_color) < 255)
+ target.set_opacity(255)
+ else
+ target.set_opacity(initial(target.opacity))
. = use_charges(user, 2)
var/fraction = min(1, . / reagents.maximum_volume)
reagents.reaction(target, TOUCH, fraction * volume_multiplier)
@@ -692,6 +716,7 @@
if(pre_noise || post_noise)
playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5)
+ user.visible_message("[user] coats [target] with spray paint!", "You coat [target] with spray paint.")
return
. = ..()
@@ -709,7 +734,7 @@
desc = "A metallic container containing shiny synthesised paint."
charges = -1
-/obj/item/toy/crayon/spraycan/borg/afterattack(atom/target,mob/user,proximity)
+/obj/item/toy/crayon/spraycan/borg/afterattack(atom/target,mob/user,proximity, params)
var/diff = ..()
if(!iscyborg(user))
to_chat(user, "How did you get this?")
@@ -754,7 +779,9 @@
reagent_contents = list("lube" = 1, "banana" = 1)
volume_multiplier = 5
- validSurfaces = list(/turf/open/floor)
+
+/obj/item/toy/crayon/spraycan/lubecan/isValidSurface(surface)
+ return istype(surface, /turf/open/floor)
/obj/item/toy/crayon/spraycan/mimecan
name = "silent spraycan"
@@ -794,6 +821,9 @@
#undef RANDOM_GRAFFITI
#undef RANDOM_LETTER
+#undef RANDOM_PUNCTUATION
+#undef RANDOM_SYMBOL
+#undef RANDOM_DRAWING
#undef RANDOM_NUMBER
#undef RANDOM_ORIENTED
#undef RANDOM_RUNE
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index 55e75b3992..5bef86c6d2 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -140,12 +140,10 @@
return ..()
/obj/item/defibrillator/emag_act(mob/user)
- if(safety)
- safety = FALSE
- to_chat(user, "You silently disable [src]'s safety protocols with the cryptographic sequencer.")
- else
- safety = TRUE
- to_chat(user, "You silently enable [src]'s safety protocols with the cryptographic sequencer.")
+ . = ..()
+ safety = !safety
+ to_chat(user, "You silently [safety ? "enable" : "disable"] [src]'s safety protocols with the cryptographic sequencer.")
+ return TRUE
/obj/item/defibrillator/emp_act(severity)
. = ..()
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 45baa542d4..b0494539ce 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -714,6 +714,7 @@ GLOBAL_LIST_EMPTY(PDAs)
return
if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY))
return
+ var/emoji_message = emoji_parse(message)
if(prob(1))
message += "\nSent from my PDA"
// Send the signal
@@ -734,7 +735,8 @@ GLOBAL_LIST_EMPTY(PDAs)
"name" = "[owner]",
"job" = "[ownjob]",
"message" = message,
- "targets" = string_targets
+ "targets" = string_targets,
+ "emoji_message" = emoji_message
))
if (picture)
signal.data["photo"] = picture
@@ -751,13 +753,13 @@ GLOBAL_LIST_EMPTY(PDAs)
// Log it in our logs
tnote += "→ To [target_text]: [signal.format_message()] "
// Show it to ghosts
- var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]"
+ var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message(TRUE)]"
for(var/mob/M in GLOB.player_list)
if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA))
to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]")
// Log in the talk log
user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]")
- to_chat(user, "Message sent to [target_text]: \"[message]\"")
+ to_chat(user, "Message sent to [target_text]: \"[emoji_message]\"")
if (!silent)
playsound(src, 'sound/machines/terminal_success.ogg', 15, 1)
// Reset the photo
@@ -787,7 +789,7 @@ GLOBAL_LIST_EMPTY(PDAs)
hrefstart = ""
hrefend = ""
- to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message()] (Reply)")
+ to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)")
update_icon(TRUE)
diff --git a/code/game/objects/items/devices/geiger_counter.dm b/code/game/objects/items/devices/geiger_counter.dm
index 7823e570e0..38133e37bc 100644
--- a/code/game/objects/items/devices/geiger_counter.dm
+++ b/code/game/objects/items/devices/geiger_counter.dm
@@ -192,13 +192,15 @@
update_icon()
/obj/item/geiger_counter/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
if(scanning)
to_chat(user, "Turn off [src] before you perform this action!")
- return 0
+ return
to_chat(user, "You override [src]'s radiation storing protocols. It will now generate small doses of radiation, and stored rads are now projected into creatures you scan.")
obj_flags |= EMAGGED
+ return TRUE
/obj/item/geiger_counter/cyborg
var/datum/component/mobhook
diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm
index 6615ebe8a8..1e31f14cfd 100644
--- a/code/game/objects/items/devices/lightreplacer.dm
+++ b/code/game/objects/items/devices/lightreplacer.dm
@@ -149,9 +149,11 @@
to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "")
/obj/item/lightreplacer/emag_act()
+ . = ..()
if(obj_flags & EMAGGED)
return
Emag()
+ return TRUE
/obj/item/lightreplacer/attack_self(mob/user)
to_chat(user, status_string())
diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm
index 347bb6894d..915fcac504 100644
--- a/code/game/objects/items/devices/megaphone.dm
+++ b/code/game/objects/items/devices/megaphone.dm
@@ -38,11 +38,13 @@
speech_args[SPEECH_SPANS] |= voicespan
/obj/item/megaphone/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
to_chat(user, "You overload \the [src]'s voice synthesizer.")
obj_flags |= EMAGGED
voicespan = list(SPAN_REALLYBIG, "userdanger")
+ return TRUE
/obj/item/megaphone/sec
name = "security megaphone"
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index a3ab9153ce..207497922e 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -32,6 +32,24 @@
var/datum/integrated_io/selected_io = null //functional for integrated circuits.
var/mode = 0
+/obj/item/multitool/chaplain
+ name = "\improper hypertool"
+ desc = "Used for pulsing wires to test which to cut. Also emits microwaves to fry some brains!"
+ damtype = BRAIN
+ force = 18
+ armour_penetration = 35
+ hitsound = 'sound/effects/sparks4.ogg'
+ var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+ throw_speed = 3
+ throw_range = 4
+ throwforce = 10
+ obj_flags = UNIQUE_RENAME
+
+/obj/item/multitool/chaplain/Initialize()
+ . = ..()
+ AddComponent(/datum/component/anti_magic, TRUE, TRUE)
+
/obj/item/multitool/examine(mob/user)
..()
if(selected_io)
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 71687f0d5a..e4e16fbfb4 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -440,7 +440,15 @@
oneuse = FALSE
remarks = list("So that is how icing is made!", "Placing fruit on top? How simple...", "Huh layering cake seems harder then this...", "This book smells like candy", "A clown must have made this page, or they forgot to spell check it before printing...", "Wait, a way to cook slime to be safe?")
-//Later content when I have free time - Trilby Date:02-Aug-2019
+/obj/item/book/granter/crafting_recipe/coldcooking //IceCream
+ name = "Cooking with Ice"
+ desc = "A cook book that teaches you many old icecream treats."
+ crafting_recipe_types = list(/datum/crafting_recipe/food/banana_split, /datum/crafting_recipe/food/root_float, /datum/crafting_recipe/food/bluecharrie_float, /datum/crafting_recipe/food/charrie_float)
+ icon_state = "cooking_learing_ice"
+ oneuse = FALSE
+ remarks = list("Looks like these would sell much better in a plasma fire...", "Using glass bowls rather then cones?", "Mixing soda and ice-cream?", "Tall glasses with of liquids and solids...", "Just add a bit of icecream and cherry on top?")
+
+//Later content when I have free time - Trilby Date:24-Aug-2019
/obj/item/book/granter/crafting_recipe/under_the_oven //Illegal cook book
name = "Under The Oven"
@@ -449,11 +457,3 @@
icon_state = "cooking_learing_illegal"
oneuse = FALSE
remarks = list()
-
-/obj/item/book/granter/crafting_recipe/coldcooking //IceCream
- name = "Cooking with Ice"
- desc = "A cook book that teaches you many old icecream treats."
- crafting_recipe_types = list()
- icon_state = "cooking_learing_ice"
- oneuse = FALSE
- remarks = list()
\ No newline at end of file
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index a306b48385..246dd77684 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -1,5 +1,6 @@
/obj/item/restraints
breakouttime = 600
+ var/demoralize_criminals = TRUE // checked on carbon/carbon.dm to decide wheter to apply the handcuffed negative moodlet or not.
/obj/item/restraints/suicide_act(mob/living/carbon/user)
user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -220,6 +221,7 @@
name = "fake handcuffs"
desc = "Fake handcuffs meant for gag purposes."
breakouttime = 10 //Deciseconds = 1s
+ demoralize_criminals = FALSE
/obj/item/restraints/handcuffs/fake/kinky
name = "kinky handcuffs"
diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm
index c6f246ab97..3be57d23f1 100644
--- a/code/game/objects/items/his_grace.dm
+++ b/code/game/objects/items/his_grace.dm
@@ -12,7 +12,7 @@
lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
icon = 'icons/obj/items_and_weapons.dmi'
- w_class = WEIGHT_CLASS_GIGANTIC
+ w_class = WEIGHT_CLASS_BULKY
force = 12
total_mass = TOTAL_MASS_NORMAL_ITEM // average toolbox
attack_verb = list("robusted")
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index ad4384f9f5..5e8250ea00 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -34,6 +34,7 @@
desc = "God wills it!"
icon_state = "knight_templar"
item_state = "knight_templar"
+ allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
// CITADEL CHANGES: More variants
/obj/item/clothing/suit/armor/riot/chaplain/teutonic
@@ -122,7 +123,6 @@
icon_state = "studentuni"
item_state = "studentuni"
body_parts_covered = ARMS|CHEST
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
/obj/item/clothing/head/helmet/chaplain/cage
name = "cage"
@@ -166,7 +166,6 @@
icon_state = "witchhunter"
item_state = "witchhunter"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
/obj/item/clothing/head/helmet/chaplain/witchunter_hat
name = "witchunter hat"
@@ -191,7 +190,7 @@
icon_state = "chaplain_hoodie"
item_state = "chaplain_hoodie"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
hoodtype = /obj/item/clothing/head/hooded/chaplain_hood
/obj/item/clothing/head/hooded/chaplain_hood
@@ -229,8 +228,8 @@
throwforce = 10
w_class = WEIGHT_CLASS_TINY
obj_flags = UNIQUE_RENAME
- var/reskinned = FALSE
var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/nullrod/Initialize()
. = ..()
@@ -247,10 +246,8 @@
/obj/item/nullrod/proc/reskin_holy_weapon(mob/M)
if(GLOB.holy_weapon_type)
return
- var/obj/item/nullrod/holy_weapon
- var/list/holy_weapons_list = typesof(/obj/item/nullrod) + list(
- /obj/item/melee/transforming/energy/sword/cx/chaplain
- )
+ var/obj/item/holy_weapon
+ var/list/holy_weapons_list = subtypesof(/obj/item/nullrod) + list(HOLY_WEAPONS)
var/list/display_names = list()
for(var/V in holy_weapons_list)
var/obj/item/nullrod/rodtype = V
@@ -273,6 +270,13 @@
qdel(src)
M.put_in_active_hand(holy_weapon)
+/obj/item/nullrod/proc/jedi_spin(mob/living/user)
+ for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH))
+ user.setDir(i)
+ if(i == WEST)
+ user.emote("flip")
+ sleep(1)
+
/obj/item/nullrod/godhand
icon_state = "disintegrate"
item_state = "disintegrate"
@@ -328,7 +332,6 @@
sharpness = IS_SHARP
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/nullrod/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(attack_type == PROJECTILE_ATTACK)
@@ -522,7 +525,6 @@
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_HUGE
attack_verb = list("smashed", "bashed", "hammered", "crunched")
- total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
/obj/item/nullrod/chainsaw
name = "chainsaw hand"
@@ -585,6 +587,7 @@
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
+ force = 12
reach = 2
attack_verb = list("whipped", "lashed")
hitsound = 'sound/weapons/chainhit.ogg'
@@ -661,6 +664,44 @@
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+/obj/item/nullrod/claymore/bostaff/attack(mob/target, mob/living/user)
+ add_fingerprint(user)
+ if((HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
+ to_chat(user, "You club yourself over the head with [src].")
+ user.Knockdown(60)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.apply_damage(2*force, BRUTE, BODY_ZONE_HEAD)
+ else
+ user.take_bodypart_damage(2*force)
+ return
+ if(iscyborg(target))
+ return ..()
+ if(!isliving(target))
+ return ..()
+ var/mob/living/carbon/C = target
+ if(C.stat || C.health < 0 || C.staminaloss > 130 )
+ to_chat(user, "It would be dishonorable to attack a foe while they cannot retaliate.")
+ return
+ if(user.a_intent == INTENT_DISARM)
+ if(!ishuman(target))
+ return ..()
+ var/mob/living/carbon/human/H = target
+ var/list/fluffmessages = list("[user] clubs [H] with [src]!", \
+ "[user] smacks [H] with the butt of [src]!", \
+ "[user] broadsides [H] with [src]!", \
+ "[user] smashes [H]'s head with [src]!", \
+ "[user] beats [H] with front of [src]!", \
+ "[user] twirls and slams [H] with [src]!")
+ H.visible_message("[pick(fluffmessages)]", \
+ "[pick(fluffmessages)]")
+ playsound(get_turf(user), 'sound/effects/woodhit.ogg', 75, 1, -1)
+ H.adjustStaminaLoss(rand(12,18))
+ if(prob(25))
+ (INVOKE_ASYNC(src, .proc/jedi_spin, user))
+ else
+ return ..()
+
/obj/item/nullrod/tribal_knife
icon_state = "crysknife"
item_state = "crysknife"
@@ -674,7 +715,6 @@
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
item_flags = SLOWS_WHILE_IN_HAND
- total_mass = TOTAL_MASS_NORMAL_ITEM
/obj/item/nullrod/tribal_knife/Initialize(mapload)
. = ..()
@@ -704,8 +744,8 @@
name = "egyptian staff"
desc = "A tutorial in mummification is carved into the staff. You could probably craft the wraps if you had some cloth."
icon = 'icons/obj/guns/magic.dmi'
- icon_state = "pharoah_sceptre"
- item_state = "pharoah_sceptre"
+ icon_state = "pharaoh_sceptre"
+ item_state = "pharaoh_sceptre"
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
w_class = WEIGHT_CLASS_NORMAL
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index cdb9c146e2..10b84917bb 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -180,8 +180,13 @@
slot_flags = ITEM_SLOT_BELT
force = 12 //9 hit crit
w_class = WEIGHT_CLASS_NORMAL
- var/cooldown = 0
+ var/cooldown = 13
var/on = TRUE
+ var/last_hit = 0
+ var/stun_stam_cost_coeff = 1.25
+ var/hardstun_ds = 1
+ var/softstun_ds = 0
+ var/stam_dmg = 30
/obj/item/melee/classic_baton/attack(mob/living/target, mob/living/user)
if(!on)
@@ -207,12 +212,10 @@
if(!isliving(target))
return
if (user.a_intent == INTENT_HARM)
- if(!..())
- return
- if(!iscyborg(target))
+ if(!..() || !iscyborg(target))
return
else
- if(cooldown <= world.time)
+ if(last_hit < world.time)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if (H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
@@ -220,7 +223,7 @@
if(check_martial_counter(H, user))
return
playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1)
- target.Knockdown(60)
+ target.Knockdown(softstun_ds, TRUE, FALSE, hardstun_ds, stam_dmg)
log_combat(user, target, "stunned", src)
src.add_fingerprint(user)
target.visible_message("[user] has knocked down [target] with [src]!", \
@@ -229,7 +232,7 @@
target.LAssailant = null
else
target.LAssailant = user
- cooldown = world.time + 40
+ last_hit = world.time + cooldown
user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes swinging batons cost stamina
/obj/item/melee/classic_baton/telescopic
@@ -245,7 +248,7 @@
item_flags = NONE
force = 0
on = FALSE
- total_mass = TOTAL_MASS_SMALL_ITEM
+ total_mass = TOTAL_MASS_NORMAL_ITEM
/obj/item/melee/classic_baton/telescopic/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
diff --git a/code/game/objects/items/paint.dm b/code/game/objects/items/paint.dm
index a6f5830dd4..cc2f5e9be7 100644
--- a/code/game/objects/items/paint.dm
+++ b/code/game/objects/items/paint.dm
@@ -90,14 +90,14 @@
add_fingerprint(user)
-/obj/item/paint/afterattack(turf/target, mob/user, proximity)
+/obj/item/paint/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity)
return
if(paintleft <= 0)
icon_state = "paint_empty"
return
- if(!istype(target) || isspaceturf(target))
+ if(!isturf(target) || isspaceturf(target))
return
var/newcolor = "#" + item_color
target.add_atom_colour(newcolor, WASHABLE_COLOUR_PRIORITY)
@@ -105,12 +105,14 @@
/obj/item/paint/paint_remover
gender = PLURAL
name = "paint remover"
- desc = "Used to remove color from floors and walls."
+ desc = "Used to remove color from anything."
icon_state = "paint_neutral"
-/obj/item/paint/paint_remover/afterattack(turf/target, mob/user, proximity)
+/obj/item/paint/paint_remover/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity)
return
- if(istype(target) && target.color != initial(target.color))
+ if(!isturf(target) || !isobj(target))
+ return
+ if(target.color != initial(target.color))
target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index 40380dfe80..48588cf9f3 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -629,7 +629,7 @@
/obj/item/toy/plush/mothplushie
name = "insect plushie"
- desc = "An adorable stuffed toy that resembles some kind of insect"
+ desc = "An adorable stuffed toy that resembles some kind of insect."
icon_state = "bumble"
item_state = "bumble"
squeak_override = list('modular_citadel/sound/voice/mothsqueak.ogg' = 1)
@@ -773,8 +773,10 @@
item_state = "blep"
/obj/item/toy/plush/mammal/circe
+ desc = "A luxuriously soft toy that resembles a nine-tailed kitsune."
icon_state = "circe"
item_state = "circe"
+ attack_verb = list("medicated", "tailhugged", "kissed")
/obj/item/toy/plush/mammal/robin
icon_state = "robin"
@@ -839,8 +841,10 @@
item_state = "rae"
/obj/item/toy/plush/mammal/zed
+ desc = "A masked stuffed toy that resembles a fierce miner. He even comes with his own little crusher!"
icon_state = "zed"
item_state = "zed"
+ attack_verb = list("ENDED", "CRUSHED", "GNOMED")
/obj/item/toy/plush/mammal/justin
icon_state = "justin"
@@ -852,6 +856,12 @@
item_state = "reece"
attack_verb = list("healed", "cured", "demoted")
+/obj/item/toy/plush/mammal/redwood
+ desc = "An adorable stuffed toy resembling a Nanotrasen Captain. That just happens to be a bunny."
+ icon_state = "redwood"
+ item_state = "redwood"
+ attack_verb = list("ordered", "bapped", "reprimanded")
+
/obj/item/toy/plush/mammal/dog
desc = "An adorable stuffed toy that resembles a canine."
icon_state = "katlin"
@@ -898,6 +908,12 @@
obj_flags = UNIQUE_RENAME
unique_reskin = list("Goodboye" = "fritz", "Badboye" = "fritz_bad")
+/obj/item/toy/plush/mammal/dog/jesse
+ desc = "An adorable wolf toy that resembles a cream-colored wolf. He has a little pride flag!"
+ icon_state = "jesse"
+ item_state = "jesse"
+ attack_verb = list("greeted", "merc'd", "howdy'd")
+
/obj/item/toy/plush/catgirl
name = "feline plushie"
desc = "An adorable stuffed toy that resembles a feline."
@@ -935,3 +951,15 @@
item_state = "fermis"
attack_verb = list("cuddled", "petpatted", "wigglepurred")
squeak_override = list('modular_citadel/sound/voice/merowr.ogg' = 1)
+
+/obj/item/toy/plush/catgirl/mariaf
+ desc = "An adorable stuffed toy that resembles a very tall cat girl."
+ icon_state = "mariaf"
+ item_state = "mariaf"
+ attack_verb = list("hugged", "stabbed", "licked")
+
+/obj/item/toy/plush/catgirl/maya
+ desc = "An adorable stuffed toy that resembles an angry cat girl. She has her own tiny nuke disk!"
+ icon_state = "maya"
+ item_state = "maya"
+ attack_verb = list("nuked", "arrested", "harmbatonned")
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 6d7c3036f9..5454b0fdb8 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -282,11 +282,13 @@
var/cooldown = 0
/obj/item/harmalarm/emag_act(mob/user)
+ . = ..()
obj_flags ^= EMAGGED
if(obj_flags & EMAGGED)
to_chat(user, "You short out the safeties on [src]!")
else
to_chat(user, "You reset the safeties on [src]!")
+ return TRUE
/obj/item/harmalarm/attack_self(mob/user)
var/safety = !(obj_flags & EMAGGED)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index d5806494e6..9c929a6ebf 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -9,7 +9,7 @@
var/locked = FALSE
var/installed = 0
var/require_module = 0
- var/module_type = null
+ var/list/module_type
// if true, is not stored in the robot to be ejected
// if module is reset
var/one_use = FALSE
@@ -18,7 +18,7 @@
if(R.stat == DEAD)
to_chat(user, "[src] will not function on a deceased cyborg.")
return FALSE
- if(module_type && !istype(R.module, module_type))
+ if(module_type && !is_type_in_list(R.module, module_type))
to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
to_chat(user, "There's no mounting point for the module!")
return FALSE
@@ -93,7 +93,6 @@
desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate."
icon_state = "cyborg_upgrade3"
require_module = 1
- //module_type = /obj/item/robot_module/security
/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -141,7 +140,7 @@
desc = "A diamond drill replacement for the mining module's standard drill."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -173,7 +172,7 @@
desc = "A satchel of holding replacement for mining cyborg's ore satchel module."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R)
. = ..()
@@ -200,7 +199,7 @@
desc = "A trash bag of holding replacement for the janiborg's standard trash bag."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor, /obj/item/robot_module/scrubpup)
/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R)
. = ..()
@@ -227,7 +226,7 @@
desc = "An advanced mop replacement for the janiborg's standard mop."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor, /obj/item/robot_module/scrubpup)
/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R)
. = ..()
@@ -276,7 +275,7 @@
icon_state = "ash_plating"
resistance_flags = LAVA_PROOF | FIRE_PROOF
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -405,7 +404,9 @@
to produce more advanced and complex medical reagents."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
var/list/additional_reagents = list()
/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr)
@@ -467,7 +468,9 @@
defibrillator, for on the scene revival."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -489,7 +492,9 @@
out procedures"
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -514,7 +519,7 @@
/obj/item/robot_module/medical,
/obj/item/robot_module/syndicate_medical,
/obj/item/robot_module/medihound,
- /obj/item/robot_module/borgi)
+ /obj/item/robot_module/borgi)
/obj/item/borg/upgrade/advhealth/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -598,7 +603,7 @@
icon = 'icons/obj/storage.dmi'
icon_state = "borgrped"
require_module = TRUE
- module_type = /obj/item/robot_module/engineering
+ module_type = list(/obj/item/robot_module/engineering)
/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -626,7 +631,9 @@
icon = 'icons/obj/device.dmi'
icon_state = "pinpointer_crew"
require_module = TRUE
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical,
+ /obj/item/robot_module/syndicate_medical,
+ /obj/item/robot_module/medihound)
/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -664,3 +671,33 @@
desc = "Allows you to to turn a cyborg into a clown, honk."
icon_state = "cyborg_upgrade3"
new_module = /obj/item/robot_module/clown
+
+// Citadel's Vtech Controller
+/obj/effect/proc_holder/silicon/cyborg/vtecControl
+ name = "vTec Control"
+ desc = "Allows finer-grained control of the vTec speed boost."
+ action_icon = 'icons/mob/actions.dmi'
+ action_icon_state = "Chevron_State_0"
+
+ var/currentState = 0
+ var/maxReduction = 2
+
+
+/obj/effect/proc_holder/silicon/cyborg/vtecControl/Click(mob/living/silicon/robot/user)
+ var/mob/living/silicon/robot/self = usr
+
+ currentState = (currentState + 1) % 3
+
+ if(usr)
+ switch(currentState)
+ if (0)
+ self.speed = maxReduction
+ if (1)
+ self.speed -= maxReduction*0.5
+ if (2)
+ self.speed -= maxReduction*1.25
+
+ action.button_icon_state = "Chevron_State_[currentState]"
+ action.UpdateButtonIcon()
+
+ return
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index cf967e25ba..47c881bbdf 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -23,7 +23,7 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/glass
- grind_results = list("silicon" = 20)
+ grind_results = list(/datum/reagent/silicon = 20)
point_value = 1
/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user)
@@ -87,7 +87,7 @@ GLOBAL_LIST_INIT(pglass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmaglass
- grind_results = list("silicon" = 20, "plasma" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10)
/obj/item/stack/sheet/plasmaglass/fifty
amount = 50
@@ -138,7 +138,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/rglass
- grind_results = list("silicon" = 20, "iron" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10)
point_value = 4
/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params)
@@ -177,11 +177,11 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
singular_name = "reinforced plasma glass sheet"
icon_state = "sheet-prglass"
item_state = "sheet-prglass"
- materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL = MINERAL_MATERIAL_AMOUNT * 0.5,)
+ materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL=MINERAL_MATERIAL_AMOUNT * 0.5,)
armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmarglass
- grind_results = list("silicon" = 20, "plasma" = 10, "iron" = 10)
+ grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10)
point_value = 23
/obj/item/stack/sheet/plasmarglass/Initialize(mapload, new_amount, merge = TRUE)
@@ -243,8 +243,9 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
resistance_flags = ACID_PROOF
armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
max_integrity = 40
- var/cooldown = 0
sharpness = IS_SHARP
+ var/icon_prefix
+
/obj/item/shard/suicide_act(mob/user)
user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.")
@@ -266,9 +267,19 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
if("large")
pixel_x = rand(-5, 5)
pixel_y = rand(-5, 5)
- var/matrix/M = matrix(transform)
- M.Turn(rand(-170, 170))
- transform = M
+ if (icon_prefix)
+ icon_state = "[icon_prefix][icon_state]"
+
+ var/turf/T = get_turf(src)
+ if(T && is_station_level(T.z))
+ SSblackbox.record_feedback("tally", "station_mess_created", 1, name)
+
+/obj/item/shard/Destroy()
+ . = ..()
+
+ var/turf/T = get_turf(src)
+ if(T && is_station_level(T.z))
+ SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name)
/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity)
. = ..()
@@ -298,6 +309,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
return ..()
/obj/item/shard/welder_act(mob/living/user, obj/item/I)
+ ..()
if(I.use_tool(src, user, 0, volume=50))
var/obj/item/stack/sheet/glass/NG = new (user.loc)
for(var/obj/item/stack/sheet/glass/G in user.loc)
@@ -316,4 +328,13 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
playsound(loc, 'sound/effects/glass_step.ogg', 30, 1)
else
playsound(loc, 'sound/effects/glass_step.ogg', 50, 1)
- . = ..()
+ return ..()
+
+/obj/item/shard/plasma
+ name = "purple shard"
+ desc = "A nasty looking shard of plasma glass."
+ force = 6
+ throwforce = 11
+ icon_state = "plasmalarge"
+ materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT)
+ icon_prefix = "plasma"
\ No newline at end of file
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index a57527c1c1..b5d7282b07 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -356,6 +356,7 @@
new /obj/item/cautery(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
new /obj/item/razor(src)
/obj/item/storage/backpack/duffelbag/sec
@@ -377,6 +378,7 @@
new /obj/item/cautery(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/engineering
name = "industrial duffel bag"
@@ -407,6 +409,7 @@
new /obj/item/stack/cable_coil/random(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
+ new /obj/item/pipe_dispenser(src)
/obj/item/storage/backpack/duffelbag/clown
name = "clown's duffel bag"
@@ -469,6 +472,7 @@
new /obj/item/mmi/syndie(src)
new /obj/item/implantcase(src)
new /obj/item/implanter(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/syndie/surgery_adv
name = "advanced surgery duffel bag"
@@ -488,6 +492,7 @@
new /obj/item/mmi/syndie(src)
new /obj/item/implantcase(src)
new /obj/item/implanter(src)
+ new /obj/item/reagent_containers/medspray/sterilizine(src)
/obj/item/storage/backpack/duffelbag/syndie/ammo
name = "ammunition duffel bag"
diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm
index bca13f2a45..ed547bc17d 100644
--- a/code/game/objects/items/storage/briefcase.dm
+++ b/code/game/objects/items/storage/briefcase.dm
@@ -40,9 +40,18 @@
/obj/item/storage/briefcase/lawyer/family
name = "battered briefcase"
- desc = "An old briefcase, this one has seen better days in its time. It's clear they don't make them nowadays as good as they used to. Comes with an added belt clip!"
+ icon_state = "gbriefcase"
+ lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi'
+ desc = "An old briefcase with a golden trim. It's clear they don't make them as good as they used to. Comes with an added belt clip!"
slot_flags = ITEM_SLOT_BELT
+/obj/item/storage/briefcase/lawyer/family/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+ STR.max_combined_w_class = 14
+
/obj/item/storage/briefcase/lawyer/family/PopulateContents()
new /obj/item/stamp/law(src)
new /obj/item/pen/fountain(src)
diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm
index 4924051855..eeebc6f4c5 100644
--- a/code/game/objects/items/storage/lockbox.dm
+++ b/code/game/objects/items/storage/lockbox.dm
@@ -48,14 +48,16 @@
to_chat(user, "It's locked!")
/obj/item/storage/lockbox/emag_act(mob/user)
- if(!broken)
- broken = TRUE
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE)
- desc += "It appears to be broken."
- icon_state = src.icon_broken
- if(user)
- visible_message("\The [src] has been broken by [user] with an electromagnetic card!")
- return
+ . = ..()
+ if(broken)
+ return
+ broken = TRUE
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE)
+ desc += "It appears to be broken."
+ icon_state = src.icon_broken
+ if(user)
+ visible_message("\The [src] has been broken by [user] with an electromagnetic card!")
+ return TRUE
/obj/item/storage/lockbox/Entered()
. = ..()
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 6e41527b24..cf5d685b4a 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -1,7 +1,7 @@
/obj/item/storage/box/syndicate
/obj/item/storage/box/syndicate/PopulateContents()
- switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1)))
+ switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "baseball" = 1, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1)))
if("bloodyspai") // 30 tc now this is more right
new /obj/item/clothing/under/chameleon(src) // 2 tc since it's not the full set
new /obj/item/clothing/mask/chameleon(src) // Goes with above
@@ -52,7 +52,7 @@
new /obj/item/clothing/under/suit_jacket/really_black(src)
new /obj/item/screwdriver/power(src) //2 tc item
- if("murder") // 35 tc now
+ if("murder") // 35 tc
new /obj/item/melee/transforming/energy/sword/saber(src)
new /obj/item/clothing/glasses/thermal/syndi(src)
new /obj/item/card/emag(src)
@@ -62,6 +62,17 @@
new /obj/item/clothing/glasses/phantomthief/syndicate(src)
new /obj/item/reagent_containers/syringe/stimulants(src)
+ if("baseball") // 42~ tc
+ new /obj/item/melee/baseball_bat/ablative/syndi(src) //Lets say 12 tc, lesser sleeping carp
+ new /obj/item/clothing/glasses/sunglasses/garb(src) //Lets say 2 tc
+ new /obj/item/card/emag(src) //6 tc
+ new /obj/item/clothing/shoes/sneakers/noslip(src) //2tc
+ new /obj/item/encryptionkey/syndicate(src) //1tc
+ new /obj/item/autosurgeon/anti_drop(src) //Lets just say 7~
+ new /obj/item/clothing/under/syndicate/baseball(src) //3tc
+ new /obj/item/clothing/head/soft/baseball(src) //Lets say 4 tc
+ new /obj/item/reagent_containers/hypospray/medipen/stimulants/baseball(src) //lets say 5tc
+
if("implant") // 67+ tc holy shit what the fuck this is a lottery disguised as fun boxes isn't it?
new /obj/item/implanter/freedom(src)
new /obj/item/implanter/uplink/precharged(src)
diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm
index c263c2669d..cb5790e45f 100644
--- a/code/game/objects/items/storage/wallets.dm
+++ b/code/game/objects/items/storage/wallets.dm
@@ -36,7 +36,8 @@
/obj/item/reagent_containers/syringe,
/obj/item/screwdriver,
/obj/item/valentine,
- /obj/item/stamp))
+ /obj/item/stamp,
+ /obj/item/key))
/obj/item/storage/wallet/Exited(atom/movable/AM)
. = ..()
@@ -60,7 +61,7 @@
/obj/item/storage/wallet/update_icon()
var/new_state = "wallet"
if(front_id)
- new_state = "wallet_[front_id.icon_state]"
+ new_state = "wallet_id"
if(new_state != icon_state) //avoid so many icon state changes.
icon_state = new_state
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index 70bfebb799..f891a48df6 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -31,7 +31,7 @@
name = "brass crowbar"
desc = "A brass crowbar. It feels faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "crowbar_brass"
+ icon_state = "crowbar_clock"
toolspeed = 0.5
/obj/item/crowbar/bronze
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index 68946f73cd..6cbede78a8 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -81,7 +81,7 @@
name = "brass screwdriver"
desc = "A screwdriver made of brass. The handle feels freezing cold."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "screwdriver_brass"
+ icon_state = "screwdriver_clock"
item_state = "screwdriver_brass"
toolspeed = 0.5
random_color = FALSE
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 9b622a14c4..fb38e4335e 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -360,7 +360,7 @@
name = "brass welding tool"
desc = "A brass welder that seems to constantly refuel itself. It is faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "brasswelder"
+ icon_state = "clockwelder"
item_state = "brasswelder"
/obj/item/weldingtool/bronze
diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm
index 527891afdb..e40ae8bdc1 100644
--- a/code/game/objects/items/tools/wirecutters.dm
+++ b/code/game/objects/items/tools/wirecutters.dm
@@ -63,9 +63,9 @@
/obj/item/wirecutters/brass
name = "brass wirecutters"
- desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch."
+ desc = "A pair of eloquent wirecutters made of brass. The handle feels freezing cold to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "cutters_brass"
+ icon_state = "cutters_clock"
random_color = FALSE
toolspeed = 0.5
diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm
index 8ff960825a..462eb22aaa 100644
--- a/code/game/objects/items/tools/wrench.dm
+++ b/code/game/objects/items/tools/wrench.dm
@@ -32,7 +32,7 @@
name = "brass wrench"
desc = "A brass wrench. It's faintly warm to the touch."
resistance_flags = FIRE_PROOF | ACID_PROOF
- icon_state = "wrench_brass"
+ icon_state = "wrench_clock"
toolspeed = 0.5
/obj/item/wrench/bronze
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 539a60986a..37ab948332 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -44,6 +44,10 @@
icon_state = "plate"
resistance_flags = NONE
+/obj/item/trash/plate/alt
+ desc = "Still some dip left. Sadly still just trash..."
+ icon_state = "plate1"
+
/obj/item/trash/pistachios
name = "pistachios pack"
icon_state = "pistachios_pack"
diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm
index 781a82513e..82bf2c6b5f 100644
--- a/code/game/objects/items/twohanded.dm
+++ b/code/game/objects/items/twohanded.dm
@@ -487,6 +487,7 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
var/obj/item/grenade/explosive = null
var/war_cry = "AAAAARGH!!!"
+ var/icon_prefix = "spearglass"
/obj/item/twohanded/spear/Initialize()
. = ..()
@@ -529,7 +530,7 @@
if(explosive)
icon_state = "spearbomb[wielded]"
else
- icon_state = "spearglass[wielded]"
+ icon_state = "[icon_prefix][wielded]"
/obj/item/twohanded/spear/afterattack(atom/movable/AM, mob/user, proximity)
. = ..()
@@ -556,6 +557,13 @@
src.war_cry = input
/obj/item/twohanded/spear/CheckParts(list/parts_list)
+ var/obj/item/shard/tip = locate() in parts_list
+ if (istype(tip, /obj/item/shard/plasma))
+ force_wielded = 19
+ force_unwielded = 11
+ throwforce = 21
+ icon_prefix = "spearplasma"
+ qdel(tip)
var/obj/item/twohanded/spear/S = locate() in parts_list
if(S)
if(S.explosive)
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 560731edfd..4cb6fc74c0 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -229,7 +229,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/katana/cursed
slot_flags = null
-/obj/item/katana/Initialize()
+/obj/item/katana/cursed/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
@@ -255,7 +255,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear
remove_item_from_storage(user)
- qdel(I)
+ if (!user.transferItemToLoc(I, S))
+ return
+ S.CheckParts(list(I))
qdel(src)
user.put_in_hands(S)
@@ -513,6 +515,19 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/homerun_able = 0
total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google
+/obj/item/melee/baseball_bat/chaplain
+ name = "blessed baseball bat"
+ desc = "There ain't a cult in the league that can withstand a swatter."
+ force = 14
+ throwforce = 14
+ obj_flags = UNIQUE_RENAME
+ var/chaplain_spawnable = TRUE
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+
+/obj/item/melee/baseball_bat/chaplain/Initialize()
+ . = ..()
+ AddComponent(/datum/component/anti_magic, TRUE, TRUE)
+
/obj/item/melee/baseball_bat/homerun
name = "home run bat"
desc = "This thing looks dangerous... Dangerously good at baseball, that is."
@@ -563,6 +578,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1)
return 1
+/obj/item/melee/baseball_bat/ablative/syndi
+ name = "syndicate major league bat"
+ desc = "A metal bat made by the syndicate for the major league team."
+ force = 18 //Spear damage...
+ throwforce = 30
+
/obj/item/melee/flyswatter
name = "flyswatter"
desc = "Useful for killing insects of all sizes."
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
index 1ab28a33de..2093ae5660 100644
--- a/code/game/objects/structures/barsigns.dm
+++ b/code/game/objects/structures/barsigns.dm
@@ -112,12 +112,16 @@
/obj/structure/sign/barsign/emag_act(mob/user)
+ . = ..()
if(broken || (obj_flags & EMAGGED))
to_chat(user, "Nothing interesting happens!")
return
obj_flags |= EMAGGED
to_chat(user, "You emag the barsign. Takeover in progress...")
- sleep(10 SECONDS)
+ addtimer(CALLBACK(src, .proc/syndie_bar_good), 10 SECONDS)
+ return TRUE
+
+/obj/structure/sign/barsign/proc/syndie_bar_good()
set_sign(new /datum/barsign/hiddensigns/syndibarsign)
req_access = list(ACCESS_SYNDICATE)
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index 88d141a9ad..dde9bce1bc 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -489,3 +489,19 @@
. = ..()
if(has_gravity())
playsound(src, 'sound/machines/clockcult/integration_cog_install.ogg', 50, TRUE)
+
+/obj/structure/chair/sofa
+ name = "old ratty sofa"
+ icon_state = "sofamiddle"
+ icon = 'icons/obj/sofa.dmi'
+ buildstackamount = 1
+ item_chair = null
+
+/obj/structure/chair/sofa/left
+ icon_state = "sofaend_left"
+
+/obj/structure/chair/sofa/right
+ icon_state = "sofaend_right"
+
+/obj/structure/chair/sofa/corner
+ icon_state = "sofacorner"
\ No newline at end of file
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index f097d2903a..edcb4a6181 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -1,616 +1,617 @@
-/obj/structure/closet
- name = "closet"
- desc = "It's a basic storage unit."
- icon = 'icons/obj/closet.dmi'
- icon_state = "generic"
- density = TRUE
- layer = BELOW_OBJ_LAYER
- var/icon_door = null
- var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
- var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
- var/opened = FALSE
- var/welded = FALSE
- var/locked = FALSE
- var/large = TRUE
- var/wall_mounted = 0 //never solid (You can always pass over it)
- max_integrity = 200
- integrity_failure = 50
- armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
- var/breakout_time = 1200
- var/message_cooldown
- var/can_weld_shut = TRUE
- var/horizontal = FALSE
- var/allow_objects = FALSE
- var/allow_dense = FALSE
- var/dense_when_open = FALSE //if it's dense when open or not
- var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
- var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
- var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
- var/cutting_tool = /obj/item/weldingtool
- var/open_sound = 'sound/machines/click.ogg'
- var/close_sound = 'sound/machines/click.ogg'
- var/material_drop = /obj/item/stack/sheet/metal
- var/material_drop_amount = 2
- var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
- var/anchorable = TRUE
- var/icon_welded = "welded"
- var/obj/item/electronics/airlock/lockerelectronics //Installed electronics
- var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
- var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus.
- var/obj/structure/closet/eigen_target //Where you go to.
-
-
-/obj/structure/closet/Initialize(mapload)
- . = ..()
- update_icon()
- PopulateContents()
- if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
- take_contents()
- if(secure)
- lockerelectronics = new(src)
- lockerelectronics.accesses = req_access
-
-//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
-/obj/structure/closet/proc/PopulateContents()
- return
-
-/obj/structure/closet/Destroy()
- dump_contents(override = FALSE)
- return ..()
-
-/obj/structure/closet/update_icon()
- cut_overlays()
- if(opened & icon_door_override)
- add_overlay("[icon_door]_open")
- layer = OBJ_LAYER
- return
- else if(opened)
- add_overlay("[icon_state]_open")
- return
- if(icon_door)
- add_overlay("[icon_door]_door")
- else
- layer = BELOW_OBJ_LAYER
- add_overlay("[icon_state]_door")
- if(welded)
- add_overlay("welded")
- if(!secure)
- return
- if(broken)
- add_overlay("off")
- add_overlay("sparking")
- else if(locked)
- add_overlay("locked")
- else
- add_overlay("unlocked")
-
-/obj/structure/closet/examine(mob/user)
- ..()
- if(welded)
- to_chat(user, "It's welded shut.")
- if(anchored)
- to_chat(user, "It is bolted to the ground.")
- if(opened)
- to_chat(user, "The parts are welded together.")
- else if(secure && !opened)
- else if(broken)
- to_chat(user, "The lock is screwed in.")
- else if(secure)
- to_chat(user, "Alt-click to [locked ? "unlock" : "lock"].")
- if(isliving(user))
- var/mob/living/L = user
- if(HAS_TRAIT(L, TRAIT_SKITTISH))
- to_chat(user, "Ctrl-Shift-click [src] to jump inside.")
-
-/obj/structure/closet/CanPass(atom/movable/mover, turf/target)
- if(wall_mounted)
- return TRUE
- return !density
-
-/obj/structure/closet/proc/can_open(mob/living/user)
- if(welded || locked)
- return FALSE
- var/turf/T = get_turf(src)
- for(var/mob/living/L in T)
- if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
- if(user)
- to_chat(user, "There's something large on top of [src], preventing it from opening." )
- return FALSE
- return TRUE
-
-/obj/structure/closet/proc/can_close(mob/living/user)
- var/turf/T = get_turf(src)
- for(var/obj/structure/closet/closet in T)
- if(closet != src && !closet.wall_mounted)
- return FALSE
- for(var/mob/living/L in T)
- if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
- if(user)
- to_chat(user, "There's something too large in [src], preventing it from closing.")
- return FALSE
- return TRUE
-
-/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access
- if(!secure)
- return FALSE
- if(broken)
- to_chat(user, "[src] is broken!")
- return FALSE
- if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics
- to_chat(user, "[src] is missing locker electronics!")
- return FALSE
- if(!check_access)
- return TRUE
- if(allowed(user))
- return TRUE
- to_chat(user, "Access denied.")
-
-/obj/structure/closet/proc/togglelock(mob/living/user)
- add_fingerprint(user)
- if(eigen_target)
- return
- if(opened)
- return
- if(!can_lock(user))
- return
- locked = !locked
- user.visible_message("[user] [locked ? null : "un"]locks [src].",
- "You [locked ? null : "un"]lock [src].")
- update_icon()
-
-/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example
- var/atom/L = drop_location()
- for(var/atom/movable/AM in src)
- if(AM == lockerelectronics && override)
- continue
- AM.forceMove(L)
- if(throwing) // you keep some momentum when getting out of a thrown closet
- step(AM, dir)
- if(throwing)
- throwing.finalize(FALSE)
-
-/obj/structure/closet/proc/take_contents()
- var/atom/L = drop_location()
- for(var/atom/movable/AM in L)
- if(AM != src && insert(AM) == -1) // limit reached
- break
-
-/obj/structure/closet/proc/open(mob/living/user)
- if(opened || !can_open(user))
- return
- playsound(loc, open_sound, 15, 1, -3)
- opened = TRUE
- if(!dense_when_open)
- density = FALSE
- climb_time *= 0.5 //it's faster to climb onto an open thing
- dump_contents()
- update_icon()
- return 1
-
-/obj/structure/closet/proc/insert(atom/movable/AM)
- if(contents.len >= storage_capacity)
- return -1
- if(insertion_allowed(AM))
- if(eigen_teleport) // For teleporting people with linked lockers.
- do_teleport(AM, get_turf(eigen_target), 0)
- if(eigen_target.opened == FALSE)
- eigen_target.bust_open()
- else
- AM.forceMove(src)
- return TRUE
- else
- return FALSE
-
-
-/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
- if(ismob(AM))
- if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
- return FALSE
- var/mob/living/L = AM
- if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
- return FALSE
- if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
- if(horizontal && L.density)
- return FALSE
- if(L.mob_size > max_mob_size)
- return FALSE
- var/mobs_stored = 0
- for(var/mob/living/M in contents)
- if(++mobs_stored >= mob_storage_capacity)
- return FALSE
- L.stop_pulling()
-
- else if(istype(AM, /obj/structure/closet))
- return FALSE
-
- else if(istype(AM, /obj/effect))
- return FALSE
-
- else if(isobj(AM))
- if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs())
- return FALSE
- if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
- return TRUE
- else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
- return FALSE
- else
- return FALSE
-
- return TRUE
-
-/obj/structure/closet/proc/close(mob/living/user)
- if(!opened || !can_close(user))
- return FALSE
- take_contents()
- playsound(loc, close_sound, 15, 1, -3)
- climb_time = initial(climb_time)
- opened = FALSE
- density = TRUE
- update_icon()
- return TRUE
-
-/obj/structure/closet/proc/toggle(mob/living/user)
- if(opened)
- return close(user)
- else
- return open(user)
-
-/obj/structure/closet/proc/bust_open()
- welded = FALSE //applies to all lockers
- locked = FALSE //applies to critter crates and secure lockers only
- broken = TRUE //applies to secure lockers only
- open()
-
-/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E)
- add_fingerprint(user)
- if(lock_in_use)
- to_chat(user, "Wait for work on [src] to be done first!")
- return
- if(secure)
- to_chat(user, "This locker already has a lock!")
- return
- if(broken)
- to_chat(user, "Unscrew the broken lock first!")
- return
- if(!istype(E))
- return
- user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...")
- lock_in_use = TRUE
- playsound(loc, 'sound/items/screwdriver.ogg', 50, 1)
- if(!do_after(user, 60, target = src))
- lock_in_use = FALSE
- return
- lock_in_use = FALSE
- to_chat(user, "You finish the lock on [src]!")
- E.forceMove(src)
- lockerelectronics = E
- req_access = E.accesses
- secure = TRUE
- update_icon()
- return TRUE
-
-/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S)
- if(lock_in_use)
- to_chat(user, "Wait for work on [src] to be done first!")
- return
- if(locked)
- to_chat(user, "Unlock it first!")
- return
- if(!secure)
- to_chat(user, "[src] doesn't have a lock that you can remove!")
- return
- if(!istype(S))
- return
- var/brokenword = broken ? "broken " : null
- user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...")
- playsound(loc, S.usesound, 50, 1)
- lock_in_use = TRUE
- if(!do_after(user, 100 * S.toolspeed, target = src))
- lock_in_use = FALSE
- return
- to_chat(user, "You remove the [brokenword]lock from [src]!")
- if(!QDELETED(lockerelectronics))
- lockerelectronics.add_fingerprint(user)
- lockerelectronics.forceMove(user.loc)
- lockerelectronics = null
- req_access = null
- secure = FALSE
- broken = FALSE
- locked = FALSE
- lock_in_use = FALSE
- update_icon()
- return TRUE
-
-
-/obj/structure/closet/deconstruct(disassembled = TRUE)
- if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1))
- new material_drop(loc, material_drop_amount)
- qdel(src)
-
-/obj/structure/closet/obj_break(damage_flag)
- if(!broken && !(flags_1 & NODECONSTRUCT_1))
- bust_open()
-
-/obj/structure/closet/attackby(obj/item/W, mob/user, params)
- if(user in src)
- return
- if(src.tool_interact(W,user))
- return 1 // No afterattack
- else
- return ..()
-
-/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
- . = TRUE
- if(opened)
- if(istype(W, cutting_tool))
- if(istype(W, /obj/item/weldingtool))
- if(!W.tool_start_check(user, amount=0))
- return
-
- to_chat(user, "You begin cutting \the [src] apart...")
- if(W.use_tool(src, user, 40, volume=50))
- if(eigen_teleport)
- to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!")
- return
- if(!opened)
- return
- user.visible_message("[user] slices apart \the [src].",
- "You cut \the [src] apart with \the [W].",
- "You hear welding.")
- deconstruct(TRUE)
- return
- else // for example cardboard box is cut with wirecutters
- user.visible_message("[user] cut apart \the [src].", \
- "You cut \the [src] apart with \the [W].")
- deconstruct(TRUE)
- return
- if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
- return TRUE
- else if(istype(W, /obj/item/electronics/airlock))
- handle_lock_addition(user, W)
- else if(istype(W, /obj/item/screwdriver))
- handle_lock_removal(user, W)
- else if(istype(W, /obj/item/weldingtool) && can_weld_shut)
- if(!W.tool_start_check(user, amount=0))
- return
-
- to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...")
- if(W.use_tool(src, user, 40, volume=50))
- if(eigen_teleport)
- to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!")
- return
- if(opened)
- return
- welded = !welded
- after_weld(welded)
- user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].",
- "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].",
- "You hear welding.")
- update_icon()
- else if(istype(W, /obj/item/wrench) && anchorable)
- if(isinspace() && !anchored)
- return
- setAnchored(!anchored)
- W.play_tool_sound(src, 75)
- user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
- "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
- "You hear a ratchet.")
- else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON))
- if(W.GetID() || !toggle(user))
- togglelock(user)
- else
- return FALSE
-
-/obj/structure/closet/proc/after_weld(weld_state)
- return
-
-/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user)
- if(!istype(O) || O.anchored || istype(O, /obj/screen))
- return
- if(!istype(user) || user.incapacitated() || user.lying)
- return
- if(!Adjacent(user) || !user.Adjacent(O))
- return
- if(user == O) //try to climb onto it
- return ..()
- if(!opened)
- return
- if(!isturf(O.loc))
- return
-
- var/actuallyismob = 0
- if(isliving(O))
- actuallyismob = 1
- else if(!isitem(O))
- return
- var/turf/T = get_turf(src)
- var/list/targets = list(O, src)
- add_fingerprint(user)
- user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \
- "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \
- "You hear clanging.")
- if(actuallyismob)
- if(do_after_mob(user, targets, 40))
- user.visible_message("[user] stuffs [O] into [src].", \
- "You stuff [O] into [src].", \
- "You hear a loud metal bang.")
- var/mob/living/L = O
- if(!issilicon(L))
- L.Knockdown(40)
- O.forceMove(T)
- close()
- else
- O.forceMove(T)
- return 1
-
-/obj/structure/closet/relaymove(mob/user)
- if(user.stat || !isturf(loc) || !isliving(user))
- return
- if(locked)
- if(message_cooldown <= world.time)
- message_cooldown = world.time + 50
- to_chat(user, "[src]'s door won't budge!")
- return
- container_resist(user)
-
-/obj/structure/closet/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- if(user.lying && get_dist(src, user) > 0)
- return
-
- if(!toggle(user))
- togglelock(user)
-
-/obj/structure/closet/attack_paw(mob/user)
- return attack_hand(user)
-
-/obj/structure/closet/attack_robot(mob/user)
- if(user.Adjacent(src))
- return attack_hand(user)
-
-// tk grab then use on self
-/obj/structure/closet/attack_self_tk(mob/user)
- return attack_hand(user)
-
-/obj/structure/closet/verb/verb_toggleopen()
- set src in oview(1)
- set category = "Object"
- set name = "Toggle Open"
-
- if(!usr.canmove || usr.stat || usr.restrained())
- return
-
- if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
- return attack_hand(usr)
- else
- to_chat(usr, "This mob type can't use this verb.")
-
-// Objects that try to exit a locker by stepping were doing so successfully,
-// and due to an oversight in turf/Enter() were going through walls. That
-// should be independently resolved, but this is also an interesting twist.
-/obj/structure/closet/Exit(atom/movable/AM)
- open()
- if(AM.loc == src)
- return 0
- return 1
-
-/obj/structure/closet/container_resist(mob/living/user)
- if(opened)
- return
- if(ismovableatom(loc))
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
- var/atom/movable/AM = loc
- AM.relay_container_resist(user, src)
- return
- if(!welded && !locked)
- open()
- return
-
- //okay, so the closet is either welded or locked... resist!!!
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
- user.visible_message("[src] begins to shake violently!", \
- "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
- "You hear banging from [src].")
- if(do_after(user,(breakout_time), target = src))
- if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) )
- return
- //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
- user.visible_message("[user] successfully broke out of [src]!",
- "You successfully break out of [src]!")
- bust_open()
- else
- if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
- to_chat(user, "You fail to break out of [src]!")
-
-/obj/structure/closet/AltClick(mob/user)
- ..()
- if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc))
- to_chat(user, "You can't do that right now!")
- return
- togglelock(user)
-
-/obj/structure/closet/CtrlShiftClick(mob/living/user)
- if(!HAS_TRAIT(user, TRAIT_SKITTISH))
- return ..()
- if(!user.canUseTopic(src) || !isturf(user.loc))
- return
- dive_into(user)
-
-/obj/structure/closet/emag_act(mob/user)
- if(secure && !broken)
- user.visible_message("Sparks fly from [src]!",
- "You scramble [src]'s lock, breaking it open!",
- "You hear a faint electrical spark.")
- playsound(src, "sparks", 50, 1)
- broken = TRUE
- locked = FALSE
- if(!QDELETED(lockerelectronics))
- qdel(lockerelectronics)
- lockerelectronics = null
- update_icon()
-
-/obj/structure/closet/get_remote_view_fullscreens(mob/user)
- if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
-
-/obj/structure/closet/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- if (!(. & EMP_PROTECT_CONTENTS))
- for(var/obj/O in src)
- O.emp_act(severity)
- if(!secure || broken)
- return ..()
- if(prob(50 / severity))
- locked = !locked
- update_icon()
- if(prob(20 / severity) && !opened)
- if(!locked)
- open()
- else
- req_access = list()
- req_access += pick(get_all_accesses())
- if(!QDELETED(lockerelectronics))
- lockerelectronics.accesses = req_access
-
-/obj/structure/closet/contents_explosion(severity, target)
- for(var/atom/A in contents)
- A.ex_act(severity, target)
- CHECK_TICK
-
-/obj/structure/closet/singularity_act()
- dump_contents()
- ..()
-
-/obj/structure/closet/AllowDrop()
- return TRUE
-
-
-/obj/structure/closet/return_temperature()
- return
-
-/obj/structure/closet/proc/dive_into(mob/living/user)
- var/turf/T1 = get_turf(user)
- var/turf/T2 = get_turf(src)
- if(!opened)
- if(locked)
- togglelock(user, TRUE)
- if(!open(user))
- to_chat(user, "It won't budge!")
- return
- step_towards(user, T2)
- T1 = get_turf(user)
- if(T1 == T2)
- user.resting = TRUE //so people can jump into crates without slamming the lid on their head
- if(!close(user))
- to_chat(user, "You can't get [src] to close!")
- user.resting = FALSE
- return
- user.resting = FALSE
- togglelock(user)
- T1.visible_message("[user] dives into [src]!")
+/obj/structure/closet
+ name = "closet"
+ desc = "It's a basic storage unit."
+ icon = 'icons/obj/closet.dmi'
+ icon_state = "generic"
+ density = TRUE
+ layer = BELOW_OBJ_LAYER
+ var/icon_door = null
+ var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
+ var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
+ var/opened = FALSE
+ var/welded = FALSE
+ var/locked = FALSE
+ var/large = TRUE
+ var/wall_mounted = 0 //never solid (You can always pass over it)
+ max_integrity = 200
+ integrity_failure = 50
+ armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
+ var/breakout_time = 1200
+ var/message_cooldown
+ var/can_weld_shut = TRUE
+ var/horizontal = FALSE
+ var/allow_objects = FALSE
+ var/allow_dense = FALSE
+ var/dense_when_open = FALSE //if it's dense when open or not
+ var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
+ var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
+ var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
+ var/cutting_tool = /obj/item/weldingtool
+ var/open_sound = 'sound/machines/click.ogg'
+ var/close_sound = 'sound/machines/click.ogg'
+ var/material_drop = /obj/item/stack/sheet/metal
+ var/material_drop_amount = 2
+ var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
+ var/anchorable = TRUE
+ var/icon_welded = "welded"
+ var/obj/item/electronics/airlock/lockerelectronics //Installed electronics
+ var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
+ var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus.
+ var/obj/structure/closet/eigen_target //Where you go to.
+
+
+/obj/structure/closet/Initialize(mapload)
+ . = ..()
+ update_icon()
+ PopulateContents()
+ if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
+ take_contents()
+ if(secure)
+ lockerelectronics = new(src)
+ lockerelectronics.accesses = req_access
+
+//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
+/obj/structure/closet/proc/PopulateContents()
+ return
+
+/obj/structure/closet/Destroy()
+ dump_contents(override = FALSE)
+ return ..()
+
+/obj/structure/closet/update_icon()
+ cut_overlays()
+ if(opened & icon_door_override)
+ add_overlay("[icon_door]_open")
+ layer = OBJ_LAYER
+ return
+ else if(opened)
+ add_overlay("[icon_state]_open")
+ return
+ if(icon_door)
+ add_overlay("[icon_door]_door")
+ else
+ layer = BELOW_OBJ_LAYER
+ add_overlay("[icon_state]_door")
+ if(welded)
+ add_overlay("welded")
+ if(!secure)
+ return
+ if(broken)
+ add_overlay("off")
+ add_overlay("sparking")
+ else if(locked)
+ add_overlay("locked")
+ else
+ add_overlay("unlocked")
+
+/obj/structure/closet/examine(mob/user)
+ ..()
+ if(welded)
+ to_chat(user, "It's welded shut.")
+ if(anchored)
+ to_chat(user, "It is bolted to the ground.")
+ if(opened)
+ to_chat(user, "The parts are welded together.")
+ else if(secure && !opened)
+ else if(broken)
+ to_chat(user, "The lock is screwed in.")
+ else if(secure)
+ to_chat(user, "Alt-click to [locked ? "unlock" : "lock"].")
+ if(isliving(user))
+ var/mob/living/L = user
+ if(HAS_TRAIT(L, TRAIT_SKITTISH))
+ to_chat(user, "Ctrl-Shift-click [src] to jump inside.")
+
+/obj/structure/closet/CanPass(atom/movable/mover, turf/target)
+ if(wall_mounted)
+ return TRUE
+ return !density
+
+/obj/structure/closet/proc/can_open(mob/living/user)
+ if(welded || locked)
+ return FALSE
+ var/turf/T = get_turf(src)
+ for(var/mob/living/L in T)
+ if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
+ if(user)
+ to_chat(user, "There's something large on top of [src], preventing it from opening." )
+ return FALSE
+ return TRUE
+
+/obj/structure/closet/proc/can_close(mob/living/user)
+ var/turf/T = get_turf(src)
+ for(var/obj/structure/closet/closet in T)
+ if(closet != src && !closet.wall_mounted)
+ return FALSE
+ for(var/mob/living/L in T)
+ if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
+ if(user)
+ to_chat(user, "There's something too large in [src], preventing it from closing.")
+ return FALSE
+ return TRUE
+
+/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access
+ if(!secure)
+ return FALSE
+ if(broken)
+ to_chat(user, "[src] is broken!")
+ return FALSE
+ if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics
+ to_chat(user, "[src] is missing locker electronics!")
+ return FALSE
+ if(!check_access)
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ to_chat(user, "Access denied.")
+
+/obj/structure/closet/proc/togglelock(mob/living/user)
+ add_fingerprint(user)
+ if(eigen_target)
+ return
+ if(opened)
+ return
+ if(!can_lock(user))
+ return
+ locked = !locked
+ user.visible_message("[user] [locked ? null : "un"]locks [src].",
+ "You [locked ? null : "un"]lock [src].")
+ update_icon()
+
+/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example
+ var/atom/L = drop_location()
+ for(var/atom/movable/AM in src)
+ if(AM == lockerelectronics && override)
+ continue
+ AM.forceMove(L)
+ if(throwing) // you keep some momentum when getting out of a thrown closet
+ step(AM, dir)
+ if(throwing)
+ throwing.finalize(FALSE)
+
+/obj/structure/closet/proc/take_contents()
+ var/atom/L = drop_location()
+ for(var/atom/movable/AM in L)
+ if(AM != src && insert(AM) == -1) // limit reached
+ break
+
+/obj/structure/closet/proc/open(mob/living/user)
+ if(opened || !can_open(user))
+ return
+ playsound(loc, open_sound, 15, 1, -3)
+ opened = TRUE
+ if(!dense_when_open)
+ density = FALSE
+ climb_time *= 0.5 //it's faster to climb onto an open thing
+ dump_contents()
+ update_icon()
+ return 1
+
+/obj/structure/closet/proc/insert(atom/movable/AM)
+ if(contents.len >= storage_capacity)
+ return -1
+ if(insertion_allowed(AM))
+ if(eigen_teleport) // For teleporting people with linked lockers.
+ do_teleport(AM, get_turf(eigen_target), 0)
+ if(eigen_target.opened == FALSE)
+ eigen_target.bust_open()
+ else
+ AM.forceMove(src)
+ return TRUE
+ else
+ return FALSE
+
+
+/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
+ if(ismob(AM))
+ if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
+ return FALSE
+ var/mob/living/L = AM
+ if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
+ return FALSE
+ if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
+ if(horizontal && L.density)
+ return FALSE
+ if(L.mob_size > max_mob_size)
+ return FALSE
+ var/mobs_stored = 0
+ for(var/mob/living/M in contents)
+ if(++mobs_stored >= mob_storage_capacity)
+ return FALSE
+ L.stop_pulling()
+
+ else if(istype(AM, /obj/structure/closet))
+ return FALSE
+
+ else if(istype(AM, /obj/effect))
+ return FALSE
+
+ else if(isobj(AM))
+ if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs())
+ return FALSE
+ if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
+ return TRUE
+ else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
+ return FALSE
+ else
+ return FALSE
+
+ return TRUE
+
+/obj/structure/closet/proc/close(mob/living/user)
+ if(!opened || !can_close(user))
+ return FALSE
+ take_contents()
+ playsound(loc, close_sound, 15, 1, -3)
+ climb_time = initial(climb_time)
+ opened = FALSE
+ density = TRUE
+ update_icon()
+ return TRUE
+
+/obj/structure/closet/proc/toggle(mob/living/user)
+ if(opened)
+ return close(user)
+ else
+ return open(user)
+
+/obj/structure/closet/proc/bust_open()
+ welded = FALSE //applies to all lockers
+ locked = FALSE //applies to critter crates and secure lockers only
+ broken = TRUE //applies to secure lockers only
+ open()
+
+/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E)
+ add_fingerprint(user)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(secure)
+ to_chat(user, "This locker already has a lock!")
+ return
+ if(broken)
+ to_chat(user, "Unscrew the broken lock first!")
+ return
+ if(!istype(E))
+ return
+ user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...")
+ lock_in_use = TRUE
+ playsound(loc, 'sound/items/screwdriver.ogg', 50, 1)
+ if(!do_after(user, 60, target = src))
+ lock_in_use = FALSE
+ return
+ lock_in_use = FALSE
+ to_chat(user, "You finish the lock on [src]!")
+ E.forceMove(src)
+ lockerelectronics = E
+ req_access = E.accesses
+ secure = TRUE
+ update_icon()
+ return TRUE
+
+/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(locked)
+ to_chat(user, "Unlock it first!")
+ return
+ if(!secure)
+ to_chat(user, "[src] doesn't have a lock that you can remove!")
+ return
+ if(!istype(S))
+ return
+ var/brokenword = broken ? "broken " : null
+ user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...")
+ playsound(loc, S.usesound, 50, 1)
+ lock_in_use = TRUE
+ if(!do_after(user, 100 * S.toolspeed, target = src))
+ lock_in_use = FALSE
+ return
+ to_chat(user, "You remove the [brokenword]lock from [src]!")
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.add_fingerprint(user)
+ lockerelectronics.forceMove(user.loc)
+ lockerelectronics = null
+ req_access = null
+ secure = FALSE
+ broken = FALSE
+ locked = FALSE
+ lock_in_use = FALSE
+ update_icon()
+ return TRUE
+
+
+/obj/structure/closet/deconstruct(disassembled = TRUE)
+ if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1))
+ new material_drop(loc, material_drop_amount)
+ qdel(src)
+
+/obj/structure/closet/obj_break(damage_flag)
+ if(!broken && !(flags_1 & NODECONSTRUCT_1))
+ bust_open()
+
+/obj/structure/closet/attackby(obj/item/W, mob/user, params)
+ if(user in src)
+ return
+ if(src.tool_interact(W,user))
+ return 1 // No afterattack
+ else
+ return ..()
+
+/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
+ . = TRUE
+ if(opened)
+ if(istype(W, cutting_tool))
+ if(istype(W, /obj/item/weldingtool))
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, "You begin cutting \the [src] apart...")
+ if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!")
+ return
+ if(!opened)
+ return
+ user.visible_message("[user] slices apart \the [src].",
+ "You cut \the [src] apart with \the [W].",
+ "You hear welding.")
+ deconstruct(TRUE)
+ return
+ else // for example cardboard box is cut with wirecutters
+ user.visible_message("[user] cut apart \the [src].", \
+ "You cut \the [src] apart with \the [W].")
+ deconstruct(TRUE)
+ return
+ if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
+ return TRUE
+ else if(istype(W, /obj/item/electronics/airlock))
+ handle_lock_addition(user, W)
+ else if(istype(W, /obj/item/screwdriver))
+ handle_lock_removal(user, W)
+ else if(istype(W, /obj/item/weldingtool) && can_weld_shut)
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...")
+ if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!")
+ return
+ if(opened)
+ return
+ welded = !welded
+ after_weld(welded)
+ user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].",
+ "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].",
+ "You hear welding.")
+ update_icon()
+ else if(istype(W, /obj/item/wrench) && anchorable)
+ if(isinspace() && !anchored)
+ return
+ setAnchored(!anchored)
+ W.play_tool_sound(src, 75)
+ user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
+ "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
+ "You hear a ratchet.")
+ else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON))
+ if(W.GetID() || !toggle(user))
+ togglelock(user)
+ else
+ return FALSE
+
+/obj/structure/closet/proc/after_weld(weld_state)
+ return
+
+/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user)
+ if(!istype(O) || O.anchored || istype(O, /obj/screen))
+ return
+ if(!istype(user) || user.incapacitated() || user.lying)
+ return
+ if(!Adjacent(user) || !user.Adjacent(O))
+ return
+ if(user == O) //try to climb onto it
+ return ..()
+ if(!opened)
+ return
+ if(!isturf(O.loc))
+ return
+
+ var/actuallyismob = 0
+ if(isliving(O))
+ actuallyismob = 1
+ else if(!isitem(O))
+ return
+ var/turf/T = get_turf(src)
+ var/list/targets = list(O, src)
+ add_fingerprint(user)
+ user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \
+ "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \
+ "You hear clanging.")
+ if(actuallyismob)
+ if(do_after_mob(user, targets, 40))
+ user.visible_message("[user] stuffs [O] into [src].", \
+ "You stuff [O] into [src].", \
+ "You hear a loud metal bang.")
+ var/mob/living/L = O
+ if(!issilicon(L))
+ L.Knockdown(40)
+ O.forceMove(T)
+ close()
+ else
+ O.forceMove(T)
+ return 1
+
+/obj/structure/closet/relaymove(mob/user)
+ if(user.stat || !isturf(loc) || !isliving(user))
+ return
+ if(locked)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, "[src]'s door won't budge!")
+ return
+ container_resist(user)
+
+/obj/structure/closet/attack_hand(mob/user)
+ . = ..()
+ if(.)
+ return
+ if(user.lying && get_dist(src, user) > 0)
+ return
+
+ if(!toggle(user))
+ togglelock(user)
+
+/obj/structure/closet/attack_paw(mob/user)
+ return attack_hand(user)
+
+/obj/structure/closet/attack_robot(mob/user)
+ if(user.Adjacent(src))
+ return attack_hand(user)
+
+// tk grab then use on self
+/obj/structure/closet/attack_self_tk(mob/user)
+ return attack_hand(user)
+
+/obj/structure/closet/verb/verb_toggleopen()
+ set src in oview(1)
+ set category = "Object"
+ set name = "Toggle Open"
+
+ if(!usr.canmove || usr.stat || usr.restrained())
+ return
+
+ if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
+ return attack_hand(usr)
+ else
+ to_chat(usr, "This mob type can't use this verb.")
+
+// Objects that try to exit a locker by stepping were doing so successfully,
+// and due to an oversight in turf/Enter() were going through walls. That
+// should be independently resolved, but this is also an interesting twist.
+/obj/structure/closet/Exit(atom/movable/AM)
+ open()
+ if(AM.loc == src)
+ return 0
+ return 1
+
+/obj/structure/closet/container_resist(mob/living/user)
+ if(opened)
+ return
+ if(ismovableatom(loc))
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ var/atom/movable/AM = loc
+ AM.relay_container_resist(user, src)
+ return
+ if(!welded && !locked)
+ open()
+ return
+
+ //okay, so the closet is either welded or locked... resist!!!
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message("[src] begins to shake violently!", \
+ "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
+ "You hear banging from [src].")
+ if(do_after(user,(breakout_time), target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) )
+ return
+ //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
+ user.visible_message("[user] successfully broke out of [src]!",
+ "You successfully break out of [src]!")
+ bust_open()
+ else
+ if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
+ to_chat(user, "You fail to break out of [src]!")
+
+/obj/structure/closet/AltClick(mob/user)
+ ..()
+ if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc))
+ to_chat(user, "You can't do that right now!")
+ return
+ togglelock(user)
+
+/obj/structure/closet/CtrlShiftClick(mob/living/user)
+ if(!HAS_TRAIT(user, TRAIT_SKITTISH))
+ return ..()
+ if(!user.canUseTopic(src) || !isturf(user.loc))
+ return
+ dive_into(user)
+
+/obj/structure/closet/emag_act(mob/user)
+ . = ..()
+ if(!secure || broken)
+ return
+ user.visible_message("Sparks fly from [src]!",
+ "You scramble [src]'s lock, breaking it open!",
+ "You hear a faint electrical spark.")
+ playsound(src, "sparks", 50, 1)
+ broken = TRUE
+ locked = FALSE
+ if(!QDELETED(lockerelectronics))
+ QDEL_NULL(lockerelectronics)
+ update_icon()
+
+/obj/structure/closet/get_remote_view_fullscreens(mob/user)
+ if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
+ user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+
+/obj/structure/closet/emp_act(severity)
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ if (!(. & EMP_PROTECT_CONTENTS))
+ for(var/obj/O in src)
+ O.emp_act(severity)
+ if(!secure || broken)
+ return ..()
+ if(prob(50 / severity))
+ locked = !locked
+ update_icon()
+ if(prob(20 / severity) && !opened)
+ if(!locked)
+ open()
+ else
+ req_access = list()
+ req_access += pick(get_all_accesses())
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.accesses = req_access
+
+/obj/structure/closet/contents_explosion(severity, target)
+ for(var/atom/A in contents)
+ A.ex_act(severity, target)
+ CHECK_TICK
+
+/obj/structure/closet/singularity_act()
+ dump_contents()
+ ..()
+
+/obj/structure/closet/AllowDrop()
+ return TRUE
+
+
+/obj/structure/closet/return_temperature()
+ return
+
+/obj/structure/closet/proc/dive_into(mob/living/user)
+ var/turf/T1 = get_turf(user)
+ var/turf/T2 = get_turf(src)
+ if(!opened)
+ if(locked)
+ togglelock(user, TRUE)
+ if(!open(user))
+ to_chat(user, "It won't budge!")
+ return
+ step_towards(user, T2)
+ T1 = get_turf(user)
+ if(T1 == T2)
+ user.resting = TRUE //so people can jump into crates without slamming the lid on their head
+ if(!close(user))
+ to_chat(user, "You can't get [src] to close!")
+ user.resting = FALSE
+ return
+ user.resting = FALSE
+ togglelock(user)
+ T1.visible_message("[user] dives into [src]!")
diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
index d2ab9ea6fb..b49d0a77d5 100644
--- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
@@ -111,9 +111,9 @@
new /obj/item/clothing/accessory/pocketprotector/cosmetology(src)
new /obj/item/clothing/under/rank/chaplain(src)
new /obj/item/clothing/shoes/sneakers/black(src)
- new /obj/item/clothing/suit/nun(src)
+ new /obj/item/clothing/suit/chaplain/nun(src)
new /obj/item/clothing/head/nun_hood(src)
- new /obj/item/clothing/suit/holidaypriest(src)
+ new /obj/item/clothing/suit/chaplain/holidaypriest(src)
new /obj/item/storage/backpack/cultpack(src)
new /obj/item/storage/fancy/candle_box(src)
new /obj/item/storage/fancy/candle_box(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 167823b9be..a7adafdad4 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -33,6 +33,7 @@
new /obj/item/extinguisher/advanced(src)
new /obj/item/storage/photo_album/CE(src)
new /obj/item/storage/lockbox/medal/engineering(src)
+ new /obj/item/construction/rcd/loaded/upgraded(src)
/obj/structure/closet/secure_closet/engineering_electrical
name = "electrical supplies locker"
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index cdca354563..05e62c196f 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -79,4 +79,4 @@
var/n_color = input(H, "Choose your [garment_type]'\s color.", "Character Preference", default_color) as color|null
if(!n_color || !H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return default_color
- return sanitize_hexcolor(n_color)
+ return sanitize_hexcolor(n_color, 3, FALSE, default_color)
diff --git a/code/game/objects/structures/table_frames.dm b/code/game/objects/structures/table_frames.dm
index e979d4f18e..226d279288 100644
--- a/code/game/objects/structures/table_frames.dm
+++ b/code/game/objects/structures/table_frames.dm
@@ -100,6 +100,14 @@
to_chat(user, "You start adding [B] to [src]...")
if(do_after(user, 20, target = src) && B.use(1))
make_new_table(/obj/structure/table/bronze)
+ else if(istype(I, /obj/item/stack/sheet/plasmaglass))
+ var/obj/item/stack/sheet/plasmaglass/G = I
+ if(G.get_amount() < 1)
+ to_chat(user, "You need one plasmaglass sheet to do this!")
+ return
+ to_chat(user, "You start adding [G] to [src]...")
+ if(do_after(user, 20, target = src) && G.use(1))
+ make_new_table(/obj/structure/table/plasmaglass)
else
return ..()
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 8c29c1b2c5..cd9b595f0a 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -115,6 +115,9 @@
log_combat(user, pushed_mob, "placed")
/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob)
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!")
+ return
var/added_passtable = FALSE
if(!pushed_mob.pass_flags & PASSTABLE)
added_passtable = TRUE
@@ -125,9 +128,9 @@
if(pushed_mob.loc != loc) //Something prevented the tabling
return
pushed_mob.Knockdown(40)
- pushed_mob.visible_message("[user] pushes [pushed_mob] onto [src].", \
- "[user] pushes [pushed_mob] onto [src].")
- log_combat(user, pushed_mob, "pushed")
+ pushed_mob.visible_message("[user] slams [pushed_mob] onto [src]!", \
+ "[user] slams you onto [src]!")
+ log_combat(user, pushed_mob, "tabled", null, "onto [src]")
if(!ishuman(pushed_mob))
return
var/mob/living/carbon/human/H = pushed_mob
@@ -258,6 +261,53 @@
for(var/obj/item/shard/S in debris)
S.color = NARSIE_WINDOW_COLOUR
+/*
+ * Plasmaglass tables
+ */
+/obj/structure/table/plasmaglass
+ name = "plasmaglass table"
+ desc = "A glasstable, but it's pink and more sturdy. What will Nanotrasen design next with plasma?"
+ icon = 'icons/obj/smooth_structures/plasmaglass_table.dmi'
+ icon_state = "plasmaglass_table"
+ climbable = TRUE
+ buildstack = /obj/item/stack/sheet/plasmaglass
+ canSmoothWith = null
+ max_integrity = 270
+ resistance_flags = ACID_PROOF
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ var/list/debris = list()
+
+/obj/structure/table/plasmaglass/New()
+ . = ..()
+ debris += new frame
+ debris += new /obj/item/shard/plasma
+
+/obj/structure/table/plasmaglass/Destroy()
+ QDEL_LIST(debris)
+ . = ..()
+
+/obj/structure/table/plasmaglass/proc/check_break(mob/living/M)
+ return
+
+/obj/structure/table/plasmaglass/deconstruct(disassembled = TRUE, wrench_disassembly = 0)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ if(disassembled)
+ ..()
+ return
+ else
+ var/turf/T = get_turf(src)
+ playsound(T, "shatter", 50, 1)
+ for(var/X in debris)
+ var/atom/movable/AM = X
+ AM.forceMove(T)
+ debris -= AM
+ qdel(src)
+
+/obj/structure/table/plasmaglass/narsie_act()
+ color = NARSIE_WINDOW_COLOUR
+ for(var/obj/item/shard/S in debris)
+ S.color = NARSIE_WINDOW_COLOUR
+
/*
* Wooden tables
*/
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index a26d57e3e1..9fc055c2ba 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -281,6 +281,8 @@
. += new /obj/effect/decal/cleanable/glass(location)
if (reinf)
. += new /obj/item/stack/rods(location, (fulltile ? 2 : 1))
+ if (fulltile)
+ . += new /obj/item/shard(location)
/obj/structure/window/proc/can_be_rotated(mob/user,rotation_type)
if(anchored)
@@ -409,6 +411,15 @@
glass_type = /obj/item/stack/sheet/plasmaglass
rad_insulation = RAD_NO_INSULATION
+/obj/structure/window/plasma/spawnDebris(location)
+ . = list()
+ . += new /obj/item/shard/plasma(location)
+ . += new /obj/effect/decal/cleanable/glass/plasma(location)
+ if (reinf)
+ . += new /obj/item/stack/rods(location, (fulltile ? 2 : 1))
+ if (fulltile)
+ . += new /obj/item/shard/plasma(location)
+
/obj/structure/window/plasma/spawner/east
dir = EAST
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index b00efc7ed6..194014b61a 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -236,6 +236,10 @@
return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 33)
if(RCD_WINDOWGRILLE)
return list("mode" = RCD_WINDOWGRILLE, "delay" = 10, "cost" = 4)
+ if(RCD_MACHINE)
+ return list("mode" = RCD_MACHINE, "delay" = 20, "cost" = 25)
+ if(RCD_COMPUTER)
+ return list("mode" = RCD_COMPUTER, "delay" = 20, "cost" = 25)
return FALSE
/turf/open/floor/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
@@ -274,4 +278,20 @@
var/obj/structure/grille/G = new(src)
G.anchored = TRUE
return TRUE
+ if(RCD_MACHINE)
+ if(locate(/obj/structure/frame/machine) in src)
+ return FALSE
+ var/obj/structure/frame/machine/M = new(src)
+ M.state = 2
+ M.icon_state = "box_1"
+ M.anchored = TRUE
+ return TRUE
+ if(RCD_COMPUTER)
+ if(locate(/obj/structure/frame/computer) in src)
+ return FALSE
+ var/obj/structure/frame/computer/C = new(src)
+ C.anchored = TRUE
+ C.setDir(the_rcd.computer_dir)
+ return TRUE
+
return FALSE
diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/simulated/wall/misc_walls.dm
index dfc6972578..8b63d60939 100644
--- a/code/game/turfs/simulated/wall/misc_walls.dm
+++ b/code/game/turfs/simulated/wall/misc_walls.dm
@@ -7,6 +7,7 @@
smooth = SMOOTH_MORE
sheet_type = /obj/item/stack/sheet/runed_metal
sheet_amount = 1
+ explosion_block = 10
girder_type = /obj/structure/girder/cult
/turf/closed/wall/mineral/cult/Initialize()
@@ -49,7 +50,7 @@
/turf/closed/wall/clockwork
name = "clockwork wall"
desc = "A huge chunk of warm metal. The clanging of machinery emanates from within."
- explosion_block = 2
+ explosion_block = 5
hardness = 10
slicing_duration = 80
sheet_type = /obj/item/stack/tile/brass
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index bfbf6b5b79..89e9c33ecc 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -54,12 +54,16 @@
only_current_user_can_interact = TRUE
/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
- return
+ return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
/obj/machinery/vr_sleeper/emag_act(mob/user)
+ . = ..()
+ if(you_die_in_the_game_you_die_for_real)
+ return
you_die_in_the_game_you_die_for_real = TRUE
sparks.start()
addtimer(CALLBACK(src, .proc/emagNotify), 150)
+ return TRUE
/obj/machinery/vr_sleeper/update_icon()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index b400f44b98..555c35980d 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -423,6 +423,25 @@
if(GLOB.master_mode == "secret")
dat += "(Force Secret Mode) "
+ if(GLOB.master_mode == "dynamic")
+ if(SSticker.current_state <= GAME_STATE_PREGAME)
+ dat += "(Force Roundstart Rulesets) "
+ if (GLOB.dynamic_forced_roundstart_ruleset.len > 0)
+ for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
+ dat += {"-> [rule.name] <- "}
+ dat += "(Clear Rulesets) "
+ dat += "(Dynamic mode options) "
+ else if (SSticker.IsRoundInProgress())
+ dat += "(Force Next Latejoin Ruleset) "
+ if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if (mode.forced_latejoin_rule)
+ dat += {"-> [mode.forced_latejoin_rule.name] <- "}
+ dat += "(Execute Midround Ruleset!) "
+ dat += ""
+ if(SSticker.IsRoundInProgress())
+ dat += "(Game Mode Panel) "
+
dat += {"
Create Object
@@ -839,6 +858,44 @@
browser.set_content(dat.Join())
browser.open()
+/datum/admins/proc/dynamic_mode_options(mob/user)
+ var/dat = {"
+
Dynamic Mode Options
+
+
Common options
+ All these options can be changed midround.
+
+ Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"].
+ This will force the round to be extended. No rulesets will be drafted.
+
+ No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"].
+ Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
+
+ Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"].
+ Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
+
+
+ Forced threat level: Current value : [GLOB.dynamic_forced_threat_level].
+ The value threat is set to if it is higher than -1.
+
+ High population limit: Current value : [GLOB.dynamic_high_pop_limit].
+ The threshold at which "high population override" will be in effect.
+
+ Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit].
+ The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
+
Advanced parameters
+ Curve centre: -> [GLOB.dynamic_curve_centre] <-
+ Curve width: -> [GLOB.dynamic_curve_width] <-
+ Latejoin injection delay:
+ Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
+ Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
+ Midround injection delay:
+ Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
+ Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
+ "}
+
+ user << browse(dat, "window=dyn_mode_options;size=900x650")
+
/datum/admins/proc/create_or_modify_area()
set category = "Debug"
set name = "Create or modify area"
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index f66cda42dc..8b6f471e7d 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -34,7 +34,8 @@
H.dna.features["frills"] = pick(GLOB.frills_list)
H.dna.features["spines"] = pick(GLOB.spines_list)
H.dna.features["body_markings"] = pick(GLOB.body_markings_list)
- H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list)
+ H.dna.features["insect_wings"] = pick(GLOB.insect_wings_list)
+ H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_list)
H.update_body()
H.update_hair()
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index d881044757..91df9ef85c 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -291,6 +291,11 @@
else if(href_list["editrights"])
edit_rights_topic(href_list)
+ else if(href_list["gamemode_panel"])
+ if(!check_rights(R_ADMIN))
+ return
+ SSticker.mode.admin_panel()
+
else if(href_list["call_shuttle"])
if(!check_rights(R_ADMIN))
return
@@ -1342,6 +1347,291 @@
else if(href_list["f_secret"])
return HandleFSecret()
+
+ else if(href_list["f_dynamic_roundstart"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null)
+ var/roundstart_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
+ var/datum/dynamic_ruleset/roundstart/newrule = new rule()
+ roundstart_rules[newrule.name] = newrule
+ var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in roundstart_rules
+ if (added_rule)
+ GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule]
+ log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.")
+ message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1)
+ Game()
+
+ else if(href_list["f_dynamic_roundstart_clear"])
+ if(!check_rights(R_ADMIN))
+ return
+ GLOB.dynamic_forced_roundstart_ruleset = list()
+ Game()
+ log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.")
+ message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1)
+
+ else if(href_list["f_dynamic_roundstart_remove"])
+ if(!check_rights(R_ADMIN))
+ return
+ var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"])
+ GLOB.dynamic_forced_roundstart_ruleset -= rule
+ Game()
+ log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.")
+ message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1)
+
+ else if(href_list["f_dynamic_latejoin"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(!SSticker || !SSticker.mode)
+ return alert(usr, "The game must start first.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/latejoin_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin))
+ var/datum/dynamic_ruleset/latejoin/newrule = new rule()
+ latejoin_rules[newrule.name] = newrule
+ var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules
+ if (added_rule)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ mode.forced_latejoin_rule = latejoin_rules[added_rule]
+ log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.")
+ message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1)
+ Game()
+
+ else if(href_list["f_dynamic_latejoin_clear"])
+ if(!check_rights(R_ADMIN))
+ return
+ if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ mode.forced_latejoin_rule = null
+ Game()
+ log_admin("[key_name(usr)] cleared the forced latejoin ruleset.")
+ message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1)
+
+ else if(href_list["f_dynamic_midround"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(!SSticker || !SSticker.mode)
+ return alert(usr, "The game must start first.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/midround_rules = list()
+ for (var/rule in subtypesof(/datum/dynamic_ruleset/midround))
+ var/datum/dynamic_ruleset/midround/newrule = new rule()
+ midround_rules[newrule.name] = rule
+ var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules
+ if (added_rule)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ log_admin("[key_name(usr)] executed the [added_rule] ruleset.")
+ message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1)
+ mode.picking_specific_rule(midround_rules[added_rule],1)
+
+ else if (href_list["f_dynamic_options"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_centre"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num
+ if (new_centre < -5 || new_centre > 5)
+ return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].")
+ message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1)
+ GLOB.dynamic_curve_centre = new_centre
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_width"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num
+ if (new_width < 0.5 || new_width > 4)
+ return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve width to [new_width].")
+ message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1)
+ GLOB.dynamic_curve_width = new_width
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_latejoin_min"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num
+ if(new_min <= 0)
+ return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
+ if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max)
+ return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.")
+ message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1)
+ GLOB.dynamic_latejoin_delay_min = (new_min MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_latejoin_max"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num
+ if(new_max <= 0)
+ return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
+ if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min)
+ return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.")
+ message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1)
+ GLOB.dynamic_latejoin_delay_max = (new_max MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_midround_min"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num
+ if(new_min <= 0)
+ return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
+ if((new_min MINUTES) > GLOB.dynamic_midround_delay_max)
+ return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.")
+ message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1)
+ GLOB.dynamic_midround_delay_min = (new_min MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_roundstart_midround_max"])
+ if(!check_rights(R_ADMIN))
+ return
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+ var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num
+ if(new_max <= 0)
+ return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
+ if((new_max MINUTES) > GLOB.dynamic_midround_delay_max)
+ return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.")
+ message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1)
+ GLOB.dynamic_midround_delay_max = (new_max MINUTES)
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_force_extended"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended
+ log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].")
+ message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_no_stacking"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking
+ log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].")
+ message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_classic_secret"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret
+ log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
+ message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_stacking_limit"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
+ log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].")
+ message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_high_pop_limit"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num
+ if (new_value < 0)
+ return alert(usr, "Only positive values allowed!", null, null, null, null)
+ GLOB.dynamic_high_pop_limit = new_value
+
+ log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
+ message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
+ dynamic_mode_options(usr)
+
+ else if(href_list["f_dynamic_forced_threat"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+
+ if(GLOB.master_mode != "dynamic")
+ return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
+
+ var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num
+ if (new_value > 100)
+ return alert(usr, "The value must be be under 100.", null, null, null, null)
+ GLOB.dynamic_forced_threat_level = new_value
+
+ log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].")
+ message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].")
+ dynamic_mode_options(usr)
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER))
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 8e29b38fe1..0fca957ffe 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -56,6 +56,7 @@
/datum/antagonist/abductor/greet()
to_chat(owner.current, "You are the [owner.special_role]!")
to_chat(owner.current, "With the help of your teammate, kidnap and experiment on station crew members!")
+ to_chat(owner.current, "Try not to disturb the habitat, it could lead to dead specimens.")
to_chat(owner.current, "[greet_text]")
owner.announce_objectives()
diff --git a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm b/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
index 819dbafd6a..98164de099 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_surgery.dm
@@ -1,5 +1,5 @@
/datum/surgery/organ_extraction
- name = "experimental dissection"
+ name = "experimental organ replacement"
steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/incise, /datum/surgery_step/extract_organ, /datum/surgery_step/gland_insert)
possible_locs = list(BODY_ZONE_CHEST)
ignore_clothes = 1
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index 8a3ff2186a..72edb18020 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -167,7 +167,7 @@
/obj/item/organ/heart/gland/pop/activate()
to_chat(owner, "You feel unlike yourself.")
randomize_human(owner)
- var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/moth, /datum/species/fly))
+ var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/insect, /datum/species/fly))
owner.set_species(species)
/obj/item/organ/heart/gland/ventcrawling
diff --git a/code/modules/antagonists/changeling/powers/strained_muscles.dm b/code/modules/antagonists/changeling/powers/strained_muscles.dm
index bdbd38b92d..081b1181dc 100644
--- a/code/modules/antagonists/changeling/powers/strained_muscles.dm
+++ b/code/modules/antagonists/changeling/powers/strained_muscles.dm
@@ -34,6 +34,7 @@
return TRUE
/obj/effect/proc_holder/changeling/strained_muscles/proc/muscle_loop(mob/living/carbon/user)
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
while(active)
ADD_TRAIT(user, TRAIT_GOTTAGOFAST, "changeling_muscles")
if(user.stat != CONSCIOUS || user.staminaloss >= 90)
@@ -41,6 +42,7 @@
to_chat(user, "Our muscles relax without the energy to strengthen them.")
user.Knockdown(40)
REMOVE_TRAIT(user, TRAIT_GOTTAGOFAST, "changeling_muscles")
+ changeling.chem_recharge_slowdown -= 0.5
break
stacks++
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 56b36d13e6..f735d6bb29 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -132,9 +132,9 @@
continue
if(isliving(M.current) && M.current.stat != DEAD)
var/turf/t_turf = isAI(M.current) ? get_step(get_step(src, NORTH),NORTH) : get_turf(src) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
- do_teleport(M, t_turf, channel = TELEPORT_CHANNEL_CULT, forced = TRUE)
- M.current.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
- M.current.clear_fullscreen("flash", 5)
+ do_teleport(M.current, t_turf, channel = TELEPORT_CHANNEL_CULT, forced = TRUE)
+ M.current.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
+ M.current.clear_fullscreen("flash", 5)
playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 50, FALSE)
recalls_remaining--
recalling = FALSE
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 1dbae4ca98..fc4d945d51 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -4,6 +4,7 @@
desc = "Prepare blood magic by carving runes into your flesh. This rite is most effective with an empowering rune"
var/list/spells = list()
var/channeling = FALSE
+ var/holy_dispel = FALSE
/datum/action/innate/cult/blood_magic/Grant()
..()
@@ -33,6 +34,9 @@
B.button.moved = B.button.screen_loc
/datum/action/innate/cult/blood_magic/Activate()
+ if(holy_dispel)
+ to_chat(owner, "Holy water currently scours your body, nullifying the power of the rites!")
+ return
var/rune = FALSE
var/limit = RUNELESS_MAX_BLOODCHARGE
for(var/obj/effect/rune/empower/R in range(1, owner))
@@ -64,7 +68,7 @@
qdel(nullify_spell)
return
BS = possible_spells[entered_spell_name]
- if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (spells.len >= limit))
+ if(QDELETED(src) || owner.incapacitated() || !BS || holy_dispel || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (spells.len >= limit))
return
to_chat(owner,"You begin to carve unnatural symbols into your flesh!")
SEND_SOUND(owner, sound('sound/weapons/slice.ogg',0,1,10))
@@ -73,7 +77,7 @@
else
to_chat(owner, "You are already invoking blood magic!")
return
- if(do_after(owner, 100 - rune*60, target = owner))
+ if(do_after(owner, 100 - rune*60, target = owner) && !holy_dispel)
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
H.bleed(40 - rune*32)
@@ -644,6 +648,11 @@
desc = "A spell that will absorb blood from anything you touch. Touching cultists and constructs can heal them. Clicking the hand will potentially let you focus the spell into something stronger."
color = "#7D1717"
+/obj/item/melee/blood_magic/manipulator/examine(mob/user)
+ . = ..()
+ if(iscultist(user))
+ to_chat(user, "The [name] currently has [uses] blood charges left.")
+
/obj/item/melee/blood_magic/manipulator/afterattack(atom/target, mob/living/carbon/human/user, proximity)
if(proximity)
if(ishuman(target))
@@ -678,9 +687,9 @@
if(ratio>1)
ratio = 1
uses -= round(overall_damage)
- H.visible_message("[H] is fully healed by [H==user ? "[H.p_their()]":"[H]'s"]'s blood magic!")
+ H.visible_message("[H] is fully healed by [H==user ? "[H.p_their()]":"[user]'s"] blood magic!")
else
- H.visible_message("[H] is partially healed by [H==user ? "[H.p_their()]":"[H]'s"] blood magic.")
+ H.visible_message("[H] is partially healed by [H==user ? "[H.p_their()]":"[user]'s"] blood magic.")
uses = 0
ratio *= -1
H.adjustOxyLoss((overall_damage*ratio) * (H.getOxyLoss() / overall_damage), 0)
@@ -762,7 +771,7 @@
switch(choice)
if("Blood Spear (150)")
if(uses < 150)
- to_chat(user, "You need 200 charges to perform this rite.")
+ to_chat(user, "You need 150 charges to perform this rite.")
else
uses -= 150
var/turf/T = get_turf(user)
@@ -778,7 +787,7 @@
"A [rite.name] materializes at your feet.")
if("Blood Bolt Barrage (300)")
if(uses < 300)
- to_chat(user, "You need 400 charges to perform this rite.")
+ to_chat(user, "You need 300 charges to perform this rite.")
else
var/obj/rite = new /obj/item/gun/ballistic/shotgun/boltaction/enchanted/arcane_barrage/blood()
uses -= 300
@@ -790,7 +799,7 @@
qdel(rite)
if("Blood Beam (500)")
if(uses < 500)
- to_chat(user, "You need 600 charges to perform this rite.")
+ to_chat(user, "You need 500 charges to perform this rite.")
else
var/obj/rite = new /obj/item/blood_beam()
uses -= 500
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 25e3663c0b..10759afcd0 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -666,6 +666,7 @@
righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
slot_flags = 0
force = 17
+ force_unwielded = 17
force_wielded = 24
throwforce = 40
throw_speed = 2
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 499d7a861e..f56c6f7fb5 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -73,6 +73,10 @@
animate(src, color = previouscolor, time = 8)
addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8)
+/obj/structure/destructible/cult/proc/check_menu(mob/living/user)
+ if(!user || user.incapacitated() || !iscultist(user) || !anchored || cooldowntime > world.time)
+ return FALSE
+ return TRUE
/obj/structure/destructible/cult/talisman
name = "altar"
@@ -80,9 +84,18 @@
icon_state = "talismanaltar"
break_message = "The altar shatters, leaving only the wailing of the damned!"
-/obj/structure/destructible/cult/talisman/attack_hand(mob/living/user)
+ var/static/image/radial_whetstone = image(icon = 'icons/obj/kitchen.dmi', icon_state = "cult_sharpener")
+ var/static/image/radial_shell = image(icon = 'icons/obj/wizard.dmi', icon_state = "construct-cult")
+ var/static/image/radial_unholy_water = image(icon = 'icons/obj/chemical.dmi', icon_state = "holyflask")
+
+/obj/structure/destructible/cult/talisman/Initialize()
. = ..()
- if(.)
+ radial_unholy_water.color = "#333333"
+
+/obj/structure/destructible/cult/talisman/ui_interact(mob/user)
+ . = ..()
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "You're pretty sure you know exactly what this is used for and you can't seem to touch it.")
@@ -91,22 +104,27 @@
to_chat(user, "You need to anchor [src] to the floor with your dagger first.")
return
if(cooldowntime > world.time)
- to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
+ to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice = alert(user,"You study the schematics etched into the altar...",,"Eldritch Whetstone","Construct Shell","Flask of Unholy Water")
- var/list/pickedtype = list()
+
+ to_chat(user, "You study the schematics etched into the altar...")
+
+ var/list/options = list("Eldritch Whetstone" = radial_whetstone, "Construct Shell" = radial_shell, "Flask of Unholy Water" = radial_unholy_water)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Eldritch Whetstone")
- pickedtype += /obj/item/sharpener/cult
+ reward = /obj/item/sharpener/cult
if("Construct Shell")
- pickedtype += /obj/structure/constructshell
+ reward = /obj/structure/constructshell
if("Flask of Unholy Water")
- pickedtype += /obj/item/reagent_containers/glass/beaker/unholywater
- if(src && !QDELETED(src) && anchored && pickedtype && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/item/reagent_containers/glass/beaker/unholywater
+
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You kneel before the altar and your faith is rewarded with the [choice]!")
+ new reward(get_turf(src))
+ to_chat(user, "You kneel before the altar and your faith is rewarded with the [choice]!")
/obj/structure/destructible/cult/forge
name = "daemon forge"
@@ -116,9 +134,14 @@
light_color = LIGHT_COLOR_LAVA
break_message = "The force breaks apart into shards with a howling scream!"
-/obj/structure/destructible/cult/forge/attack_hand(mob/living/user)
+ var/static/image/radial_flagellant = image(icon = 'icons/obj/clothing/suits.dmi', icon_state = "cultrobes")
+ var/static/image/radial_shielded = image(icon = 'icons/obj/clothing/suits.dmi', icon_state = "cult_armor")
+ var/static/image/radial_mirror = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "mirror_shield")
+
+/obj/structure/destructible/cult/forge/ui_interact(mob/user)
. = ..()
- if(.)
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "The heat radiating from [src] pushes you back.")
@@ -129,24 +152,26 @@
if(cooldowntime > world.time)
to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice
- if(user.mind.has_antag_datum(/datum/antagonist/cult/master))
- choice = alert(user,"You study the schematics etched into the forge...",,"Shielded Robe","Flagellant's Robe","Mirror Shield")
- else
- choice = alert(user,"You study the schematics etched into the forge...",,"Shielded Robe","Flagellant's Robe","Mirror Shield")
- var/list/pickedtype = list()
+
+ to_chat(user, "You study the schematics etched into the forge...")
+
+
+ var/list/options = list("Shielded Robe" = radial_shielded, "Flagellant's Robe" = radial_flagellant, "Mirror Shield" = radial_mirror)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Shielded Robe")
- pickedtype += /obj/item/clothing/suit/hooded/cultrobes/cult_shield
+ reward = /obj/item/clothing/suit/hooded/cultrobes/cult_shield
if("Flagellant's Robe")
- pickedtype += /obj/item/clothing/suit/hooded/cultrobes/berserker
+ reward = /obj/item/clothing/suit/hooded/cultrobes/berserker
if("Mirror Shield")
- pickedtype += /obj/item/shield/mirror
- if(src && !QDELETED(src) && anchored && pickedtype && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/item/shield/mirror
+
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You work the forge as dark knowledge guides your hands, creating the [choice]!")
+ new reward(get_turf(src))
+ to_chat(user, "You work the forge as dark knowledge guides your hands, creating the [choice]!")
@@ -234,9 +259,14 @@
light_color = LIGHT_COLOR_FIRE
break_message = "The books and tomes of the archives burn into ash as the desk shatters!"
-/obj/structure/destructible/cult/tome/attack_hand(mob/living/user)
+ var/static/image/radial_blindfold = image(icon = 'icons/obj/clothing/glasses.dmi', icon_state = "blindfold")
+ var/static/image/radial_curse = image(icon = 'icons/obj/cult.dmi', icon_state ="shuttlecurse")
+ var/static/image/radial_veilwalker = image(icon = 'icons/obj/cult.dmi', icon_state ="shifter")
+
+/obj/structure/destructible/cult/tome/ui_interact(mob/user)
. = ..()
- if(.)
+
+ if(!user.canUseTopic(src, TRUE))
return
if(!iscultist(user))
to_chat(user, "These books won't open and it hurts to even try and read the covers.")
@@ -247,21 +277,27 @@
if(cooldowntime > world.time)
to_chat(user, "The magic in [src] is weak, it will be ready to use again in [DisplayTimeText(cooldowntime - world.time)].")
return
- var/choice = alert(user,"You flip through the black pages of the archives...",,"Zealot's Blindfold","Shuttle Curse","Veil Walker Set")
- var/list/pickedtype = list()
+
+ to_chat(user, "You flip through the black pages of the archives...")
+
+ var/list/options = list("Zealot's Blindfold" = radial_blindfold, "Shuttle Curse" = radial_curse, "Veil Walker Set" = radial_veilwalker)
+ var/choice = show_radial_menu(user, src, options, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+
+ var/reward
switch(choice)
if("Zealot's Blindfold")
- pickedtype += /obj/item/clothing/glasses/hud/health/night/cultblind
+ reward = /obj/item/clothing/glasses/hud/health/night/cultblind
if("Shuttle Curse")
- pickedtype += /obj/item/shuttle_curse
+ reward = /obj/item/shuttle_curse
if("Veil Walker Set")
- pickedtype += /obj/item/cult_shift
- pickedtype += /obj/item/flashlight/flare/culttorch
- if(src && !QDELETED(src) && anchored && pickedtype.len && Adjacent(user) && !user.incapacitated() && iscultist(user) && cooldowntime <= world.time)
+ reward = /obj/effect/spawner/bundle/veil_walker
+ if(!QDELETED(src) && reward && check_menu(user))
cooldowntime = world.time + 2400
- for(var/N in pickedtype)
- new N(get_turf(src))
- to_chat(user, "You summon the [choice] from the archives!")
+ new reward(get_turf(src))
+ to_chat(user, "You summon the [choice] from the archives!")
+
+/obj/effect/spawner/bundle/veil_walker
+ items = list(/obj/item/cult_shift, /obj/item/flashlight/flare/culttorch)
/obj/effect/gateway
name = "gateway"
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index a69694ced9..bfc4955f68 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -61,8 +61,8 @@ Runes can either be invoked by one's self or with many different cultists. Each
if(do_after(user, 15, target = src))
to_chat(user, "You carefully erase the [lowertext(cultist_name)] rune.")
qdel(src)
- else if(istype(I, /obj/item/nullrod))
- user.say("BEGONE FOUL MAGIKS!!", forced = "nullrod")
+ else if(istype(I, /obj/item/storage/book/bible) || istype(I, /obj/item/nullrod))
+ user.say("BEGONE FOUL MAGICKS!!", forced = "bible")
to_chat(user, "You disrupt the magic of [src] with [I].")
qdel(src)
@@ -185,9 +185,6 @@ structure_check() searches for nearby cultist structures required for the invoca
color = RUNE_COLOR_OFFER
req_cultists = 1
rune_in_use = FALSE
- var/mob/living/currentconversionman
- var/conversiontimeout
- var/conversionresult
/obj/effect/rune/convert/do_invoke_glow()
return
@@ -233,37 +230,18 @@ structure_check() searches for nearby cultist structures required for the invoca
addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 5)
Cult_team.check_size() // Triggers the eye glow or aura effects if the cult has grown large enough relative to the crew
rune_in_use = FALSE
+
/obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers)
if(invokers.len < 2)
for(var/M in invokers)
- to_chat(M, "You need at least two invokers to convert [convertee]!")
+ to_chat(M, "You need at least two invokers to convert [convertee]!")
log_game("Offer rune failed - tried conversion with one invoker")
return 0
- if(convertee.anti_magic_check(TRUE, TRUE))
+ if(convertee.anti_magic_check(TRUE, TRUE, FALSE, 0)) //Not chargecost because it can be spammed
for(var/M in invokers)
to_chat(M, "Something is shielding [convertee]'s mind!")
log_game("Offer rune failed - convertee had anti-magic")
return 0
- to_chat(convertee, "The world goes red. All at once you are aware of an evil, eldritch truth taking roots into your mind.\n\
- Click here to become a follower of Nar'sie, or suffer a fate worse than death.")
- INVOKE_ASYNC(src, .proc/optinalert, convertee)
- currentconversionman = convertee
- conversiontimeout = world.time + (14 SECONDS)
- convertee.Stun(140)
- ADD_TRAIT(convertee, TRAIT_MUTE, "conversionrune")
- flash_color(convertee, list("#960000", "#960000", "#960000", rgb(0,0,0)), 50)
- conversionresult = FALSE
- while(world.time < conversiontimeout && convertee && !conversionresult)
- stoplag(1)
- currentconversionman = null
- if(!convertee)
- return FALSE
- REMOVE_TRAIT(convertee, TRAIT_MUTE, "conversionrune")
- if(get_turf(convertee) != get_turf(src))
- return FALSE
- if(!conversionresult)
- do_sacrifice(convertee, invokers, TRUE)
- return FALSE
var/brutedamage = convertee.getBruteLoss()
var/burndamage = convertee.getFireLoss()
if(brutedamage || burndamage)
@@ -275,6 +253,8 @@ structure_check() searches for nearby cultist structures required for the invoca
SSticker.mode.add_cultist(convertee.mind, 1)
new /obj/item/melee/cultblade/dagger(get_turf(src))
convertee.mind.special_role = ROLE_CULTIST
+ to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
+ and something evil takes root.")
to_chat(convertee, "Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve the Geometer above all else. Bring it back.\
")
if(ishuman(convertee))
@@ -284,18 +264,7 @@ structure_check() searches for nearby cultist structures required for the invoca
H.cultslurring = 0
return 1
-/obj/effect/rune/convert/proc/optinalert(mob/living/convertee)
- var/alert = alert(convertee, "Will you embrace the Geometer of Blood or perish in futile resistance?", "Choose your own fate", "Join the Blood Cult", "Suffer a horrible demise")
- if(alert == "Join the Blood Cult")
- signmeup(convertee)
-
-/obj/effect/rune/convert/proc/signmeup(mob/living/convertee)
- if(currentconversionman == usr)
- conversionresult = TRUE
- else
- to_chat(usr, "Your fate has already been set in stone.")
-
-/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers, force_a_sac)
+/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers)
var/mob/living/first_invoker = invokers[1]
if(!first_invoker)
return FALSE
@@ -305,7 +274,7 @@ structure_check() searches for nearby cultist structures required for the invoca
var/big_sac = FALSE
- if(!force_a_sac && (((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && invokers.len < 3)
+ if((((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && invokers.len < 3)
for(var/M in invokers)
to_chat(M, "[sacrificial] is too greatly linked to the world! You need three acolytes!")
log_game("Offer rune failed - not enough acolytes and target is living or sac target")
@@ -345,10 +314,6 @@ structure_check() searches for nearby cultist structures required for the invoca
sacrificial.gib()
return TRUE
-/obj/effect/rune/convert/Topic(href, href_list)
- if(href_list["signmeup"])
- signmeup(usr)
-
/obj/effect/rune/empower
cultist_name = "Empower"
cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost."
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index 53f8aa2da1..bb24af4f7a 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -36,30 +36,42 @@
/datum/antagonist/ert/security // kinda handled by the base template but here for completion
-/datum/antagonist/ert/security/red
+/datum/antagonist/ert/security/amber
outfit = /datum/outfit/ert/security/alert
+/datum/antagonist/ert/security/red
+ outfit = /datum/outfit/ert/security/alert/red
+
/datum/antagonist/ert/engineer
role = "Engineer"
outfit = /datum/outfit/ert/engineer
-/datum/antagonist/ert/engineer/red
+/datum/antagonist/ert/engineer/amber
outfit = /datum/outfit/ert/engineer/alert
+/datum/antagonist/ert/engineer/red
+ outfit = /datum/outfit/ert/engineer/alert/red
+
/datum/antagonist/ert/medic
role = "Medical Officer"
outfit = /datum/outfit/ert/medic
-/datum/antagonist/ert/medic/red
+/datum/antagonist/ert/medic/amber
outfit = /datum/outfit/ert/medic/alert
+/datum/antagonist/ert/medic/red
+ outfit = /datum/outfit/ert/medic/alert/red
+
/datum/antagonist/ert/commander
role = "Commander"
outfit = /datum/outfit/ert/commander
-/datum/antagonist/ert/commander/red
+/datum/antagonist/ert/commander/amber
outfit = /datum/outfit/ert/commander/alert
+/datum/antagonist/ert/commander/red
+ outfit = /datum/outfit/ert/commander/alert/red
+
/datum/antagonist/ert/deathsquad
name = "Deathsquad Trooper"
outfit = /datum/outfit/death_commando
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index 5ad97fb7c8..8e320b2f3e 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -364,15 +364,10 @@
var/list/cached_gases = air.gases
var/temperature = air.temperature
var/pressure = air.return_pressure()
-
var/old_heat_capacity = air.heat_capacity()
var/reaction_efficency = min(1/((pressure/(0.1*ONE_ATMOSPHERE))*(max(cached_gases[/datum/gas/plasma]/cached_gases[/datum/gas/nitrous_oxide],1))),cached_gases[/datum/gas/nitrous_oxide],cached_gases[/datum/gas/plasma]/2)
var/energy_released = 2*reaction_efficency*FIRE_CARBON_ENERGY_RELEASED
- if(cached_gases[/datum/gas/miasma] && cached_gases[/datum/gas/miasma] > 0)
- energy_released /= cached_gases[/datum/gas/miasma]*0.1
- if(cached_gases[/datum/gas/bz] && cached_gases[/datum/gas/bz] > 0)
- energy_released *= cached_gases[/datum/gas/bz]*0.1
- if ((cached_gases[/datum/gas/nitrous_oxide] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma] - (2*reaction_efficency) < 0)) //Shouldn't produce gas from nothing.
+ if ((cached_gases[/datum/gas/nitrous_oxide] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma] - (2*reaction_efficency) < 0) || energy_released <= 0) //Shouldn't produce gas from nothing.
return NO_REACTION
cached_gases[/datum/gas/bz] += reaction_efficency
if(reaction_efficency == cached_gases[/datum/gas/nitrous_oxide])
@@ -381,7 +376,7 @@
cached_gases[/datum/gas/nitrous_oxide] -= reaction_efficency
cached_gases[/datum/gas/plasma] -= 2*reaction_efficency
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, (reaction_efficency**0.5)*BZ_RESEARCH_AMOUNT)
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((reaction_efficency**2)*BZ_RESEARCH_SCALE),BZ_RESEARCH_MAX_AMOUNT)
if(energy_released > 0)
var/new_heat_capacity = air.heat_capacity()
@@ -477,4 +472,4 @@
//Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable
air.temperature += cleaned_air * 0.002
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, cleaned_air*MIASMA_RESEARCH_AMOUNT)//Turns out the burning of miasma is kinda interesting to scientists
\ No newline at end of file
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, cleaned_air*MIASMA_RESEARCH_AMOUNT)//Turns out the burning of miasma is kinda interesting to scientists
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 3bf54a1178..6ca25c0d80 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -1,880 +1,882 @@
-/datum/tlv
- var/min2
- var/min1
- var/max1
- var/max2
-
-/datum/tlv/New(min2 as num, min1 as num, max1 as num, max2 as num)
- if(min2) src.min2 = min2
- if(min1) src.min1 = min1
- if(max1) src.max1 = max1
- if(max2) src.max2 = max2
-
-/datum/tlv/proc/get_danger_level(val as num)
- if(max2 != -1 && val >= max2)
- return 2
- if(min2 != -1 && val <= min2)
- return 2
- if(max1 != -1 && val >= max1)
- return 1
- if(min1 != -1 && val <= min1)
- return 1
- return 0
-
-/datum/tlv/no_checks
- min2 = -1
- min1 = -1
- max1 = -1
- max2 = -1
-
-/datum/tlv/dangerous
- min2 = -1
- min1 = -1
- max1 = 0.2
- max2 = 0.5
-
-/obj/item/electronics/airalarm
- name = "air alarm electronics"
- icon_state = "airalarm_electronics"
-
-/obj/item/wallframe/airalarm
- name = "air alarm frame"
- desc = "Used for building Air Alarms."
- icon = 'icons/obj/monitors.dmi'
- icon_state = "alarm_bitem"
- result_path = /obj/machinery/airalarm
-
-#define AALARM_MODE_SCRUBBING 1
-#define AALARM_MODE_VENTING 2 //makes draught
-#define AALARM_MODE_PANIC 3 //like siphon, but stronger (enables widenet)
-#define AALARM_MODE_REPLACEMENT 4 //sucks off all air, then refill and swithes to scrubbing
-#define AALARM_MODE_OFF 5
-#define AALARM_MODE_FLOOD 6 //Emagged mode; turns off scrubbers and pressure checks on vents
-#define AALARM_MODE_SIPHON 7 //Scrubbers suck air
-#define AALARM_MODE_CONTAMINATED 8 //Turns on all filtering and widenet scrubbing.
-#define AALARM_MODE_REFILL 9 //just like normal, but with triple the air output
-
-#define AALARM_REPORT_TIMEOUT 100
-
-#define AALARM_OVERLAY_OFF "alarm_off"
-#define AALARM_OVERLAY_GREEN "alarm_green"
-#define AALARM_OVERLAY_WARN "alarm_amber"
-#define AALARM_OVERLAY_DANGER "alarm_red"
-
-/obj/machinery/airalarm
- name = "air alarm"
- desc = "A machine that monitors atmosphere levels. Goes off if the area is dangerous."
- icon = 'icons/obj/monitors.dmi'
- icon_state = "alarm0"
- use_power = IDLE_POWER_USE
- idle_power_usage = 4
- active_power_usage = 8
- power_channel = ENVIRON
- req_access = list(ACCESS_ATMOSPHERICS)
- max_integrity = 250
- integrity_failure = 80
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
- resistance_flags = FIRE_PROOF
-
- var/danger_level = 0
- var/mode = AALARM_MODE_SCRUBBING
-
- var/locked = TRUE
- var/aidisabled = 0
- var/shorted = 0
- var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
- var/brightness_on = 1
-
- var/frequency = FREQ_ATMOS_CONTROL
- var/alarm_frequency = FREQ_ATMOS_ALARMS
- var/datum/radio_frequency/radio_connection
-
- var/list/TLV = list( // Breathable air.
- "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa
- "temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66),
- /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
- /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
- /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
- /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5),
- /datum/gas/plasma = new/datum/tlv/dangerous,
- /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
- /datum/gas/bz = new/datum/tlv/dangerous,
- /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
- /datum/gas/water_vapor = new/datum/tlv/dangerous,
- /datum/gas/tritium = new/datum/tlv/dangerous,
- /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects
- /datum/gas/nitryl = new/datum/tlv/dangerous,
- /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
- )
-
-/obj/machinery/airalarm/server // No checks here.
- TLV = list(
- "pressure" = new/datum/tlv/no_checks,
- "temperature" = new/datum/tlv/no_checks,
- /datum/gas/oxygen = new/datum/tlv/no_checks,
- /datum/gas/nitrogen = new/datum/tlv/no_checks,
- /datum/gas/carbon_dioxide = new/datum/tlv/no_checks,
- /datum/gas/miasma = new/datum/tlv/no_checks,
- /datum/gas/plasma = new/datum/tlv/no_checks,
- /datum/gas/nitrous_oxide = new/datum/tlv/no_checks,
- /datum/gas/bz = new/datum/tlv/no_checks,
- /datum/gas/hypernoblium = new/datum/tlv/no_checks,
- /datum/gas/water_vapor = new/datum/tlv/no_checks,
- /datum/gas/tritium = new/datum/tlv/no_checks,
- /datum/gas/stimulum = new/datum/tlv/no_checks,
- /datum/gas/nitryl = new/datum/tlv/no_checks,
- /datum/gas/pluoxium = new/datum/tlv/no_checks
- )
-
-/obj/machinery/airalarm/kitchen_cold_room // Copypasta: to check temperatures.
- TLV = list(
- "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa
- "temperature" = new/datum/tlv(T0C-73.15, T0C-63.15, T0C, T0C+10),
- /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
- /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
- /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
- /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5),
- /datum/gas/plasma = new/datum/tlv/dangerous,
- /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
- /datum/gas/bz = new/datum/tlv/dangerous,
- /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
- /datum/gas/water_vapor = new/datum/tlv/dangerous,
- /datum/gas/tritium = new/datum/tlv/dangerous,
- /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects
- /datum/gas/nitryl = new/datum/tlv/dangerous,
- /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
- )
-
-/obj/machinery/airalarm/unlocked
- locked = FALSE
-
-/obj/machinery/airalarm/engine
- name = "engine air alarm"
- locked = FALSE
- req_access = null
- req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_ENGINE)
-
-/obj/machinery/airalarm/mixingchamber
- name = "chamber air alarm"
- locked = FALSE
- req_access = null
- req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_TOX, ACCESS_TOX_STORAGE)
-
-/obj/machinery/airalarm/all_access
- name = "all-access air alarm"
- desc = "This particular atmos control unit appears to have no access restrictions."
- locked = FALSE
- req_access = null
- req_one_access = null
-
-/obj/machinery/airalarm/syndicate //general syndicate access
- req_access = list(ACCESS_SYNDICATE)
-
-/obj/machinery/airalarm/directional/north //Pixel offsets get overwritten on New()
- dir = SOUTH
- pixel_y = 24
-
-/obj/machinery/airalarm/directional/south
- dir = NORTH
- pixel_y = -24
-
-/obj/machinery/airalarm/directional/east
- dir = WEST
- pixel_x = 24
-
-/obj/machinery/airalarm/directional/west
- dir = EAST
- pixel_x = -24
-
-//all air alarms in area are connected via magic
-/area
- var/list/air_vent_names = list()
- var/list/air_scrub_names = list()
- var/list/air_vent_info = list()
- var/list/air_scrub_info = list()
-
-/obj/machinery/airalarm/Initialize(mapload, ndir, nbuild)
- . = ..()
- wires = new /datum/wires/airalarm(src)
-
- if(ndir)
- setDir(ndir)
-
- if(nbuild)
- buildstage = 0
- panel_open = TRUE
- pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24)
- pixel_y = (dir & 3)? (dir == 1 ? -24 : 24) : 0
-
- if(name == initial(name))
- name = "[get_area_name(src)] Air Alarm"
-
- power_change()
- set_frequency(frequency)
-
-/obj/machinery/airalarm/Destroy()
- SSradio.remove_object(src, frequency)
- qdel(wires)
- wires = null
- return ..()
-
-/obj/machinery/airalarm/examine(mob/user)
- . = ..()
- switch(buildstage)
- if(0)
- to_chat(user, "It is missing air alarm electronics.")
- if(1)
- to_chat(user, "It is missing wiring.")
- if(2)
- to_chat(user, "Alt-click to [locked ? "unlock" : "lock"] the interface.")
-
-/obj/machinery/airalarm/ui_status(mob/user)
- if(user.has_unlimited_silicon_privilege && aidisabled)
- to_chat(user, "AI control has been disabled.")
- else if(!shorted)
- return ..()
- return UI_CLOSE
-
-/obj/machinery/airalarm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "airalarm", name, 440, 650, master_ui, state)
- ui.open()
-
-/obj/machinery/airalarm/ui_data(mob/user)
- var/data = list(
- "locked" = locked,
- "siliconUser" = user.has_unlimited_silicon_privilege,
- "emagged" = (obj_flags & EMAGGED ? 1 : 0),
- "danger_level" = danger_level,
- )
-
- var/area/A = get_area(src)
- data["atmos_alarm"] = A.atmosalm
- data["fire_alarm"] = A.fire
-
- var/turf/T = get_turf(src)
- var/datum/gas_mixture/environment = T.return_air()
- var/datum/tlv/cur_tlv
-
- data["environment_data"] = list()
- var/pressure = environment.return_pressure()
- cur_tlv = TLV["pressure"]
- data["environment_data"] += list(list(
- "name" = "Pressure",
- "value" = pressure,
- "unit" = "kPa",
- "danger_level" = cur_tlv.get_danger_level(pressure)
- ))
- var/temperature = environment.temperature
- cur_tlv = TLV["temperature"]
- data["environment_data"] += list(list(
- "name" = "Temperature",
- "value" = temperature,
- "unit" = "K ([round(temperature - T0C, 0.1)]C)",
- "danger_level" = cur_tlv.get_danger_level(temperature)
- ))
- var/total_moles = environment.total_moles()
- var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
- for(var/gas_id in environment.gases)
- if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
- continue
- cur_tlv = TLV[gas_id]
- data["environment_data"] += list(list(
- "name" = GLOB.meta_gas_names[gas_id],
- "value" = environment.gases[gas_id] / total_moles * 100,
- "unit" = "%",
- "danger_level" = cur_tlv.get_danger_level(environment.gases[gas_id] * partial_pressure)
- ))
-
- if(!locked || user.has_unlimited_silicon_privilege)
- data["vents"] = list()
- for(var/id_tag in A.air_vent_names)
- var/long_name = A.air_vent_names[id_tag]
- var/list/info = A.air_vent_info[id_tag]
- if(!info || info["frequency"] != frequency)
- continue
- data["vents"] += list(list(
- "id_tag" = id_tag,
- "long_name" = sanitize(long_name),
- "power" = info["power"],
- "checks" = info["checks"],
- "excheck" = info["checks"]&1,
- "incheck" = info["checks"]&2,
- "direction" = info["direction"],
- "external" = info["external"],
- "internal" = info["internal"],
- "extdefault"= (info["external"] == ONE_ATMOSPHERE),
- "intdefault"= (info["internal"] == 0)
- ))
- data["scrubbers"] = list()
- for(var/id_tag in A.air_scrub_names)
- var/long_name = A.air_scrub_names[id_tag]
- var/list/info = A.air_scrub_info[id_tag]
- if(!info || info["frequency"] != frequency)
- continue
- data["scrubbers"] += list(list(
- "id_tag" = id_tag,
- "long_name" = sanitize(long_name),
- "power" = info["power"],
- "scrubbing" = info["scrubbing"],
- "widenet" = info["widenet"],
- "filter_types" = info["filter_types"]
- ))
- data["mode"] = mode
- data["modes"] = list()
- data["modes"] += list(list("name" = "Filtering - Scrubs out contaminants", "mode" = AALARM_MODE_SCRUBBING, "selected" = mode == AALARM_MODE_SCRUBBING, "danger" = 0))
- data["modes"] += list(list("name" = "Contaminated - Scrubs out ALL contaminants quickly","mode" = AALARM_MODE_CONTAMINATED, "selected" = mode == AALARM_MODE_CONTAMINATED, "danger" = 0))
- data["modes"] += list(list("name" = "Draught - Siphons out air while replacing", "mode" = AALARM_MODE_VENTING, "selected" = mode == AALARM_MODE_VENTING, "danger" = 0))
- data["modes"] += list(list("name" = "Refill - Triple vent output", "mode" = AALARM_MODE_REFILL, "selected" = mode == AALARM_MODE_REFILL, "danger" = 1))
- data["modes"] += list(list("name" = "Cycle - Siphons air before replacing", "mode" = AALARM_MODE_REPLACEMENT, "selected" = mode == AALARM_MODE_REPLACEMENT, "danger" = 1))
- data["modes"] += list(list("name" = "Siphon - Siphons air out of the room", "mode" = AALARM_MODE_SIPHON, "selected" = mode == AALARM_MODE_SIPHON, "danger" = 1))
- data["modes"] += list(list("name" = "Panic Siphon - Siphons air out of the room quickly","mode" = AALARM_MODE_PANIC, "selected" = mode == AALARM_MODE_PANIC, "danger" = 1))
- data["modes"] += list(list("name" = "Off - Shuts off vents and scrubbers", "mode" = AALARM_MODE_OFF, "selected" = mode == AALARM_MODE_OFF, "danger" = 0))
- if(obj_flags & EMAGGED)
- data["modes"] += list(list("name" = "Flood - Shuts off scrubbers and opens vents", "mode" = AALARM_MODE_FLOOD, "selected" = mode == AALARM_MODE_FLOOD, "danger" = 1))
-
- var/datum/tlv/selected
- var/list/thresholds = list()
-
- selected = TLV["pressure"]
- thresholds += list(list("name" = "Pressure", "settings" = list()))
- thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min2", "selected" = selected.min2))
- thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min1", "selected" = selected.min1))
- thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max1", "selected" = selected.max1))
- thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max2", "selected" = selected.max2))
-
- selected = TLV["temperature"]
- thresholds += list(list("name" = "Temperature", "settings" = list()))
- thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min2", "selected" = selected.min2))
- thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min1", "selected" = selected.min1))
- thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1))
- thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2))
-
- for(var/gas_id in GLOB.meta_gas_names)
- if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
- continue
- selected = TLV[gas_id]
- thresholds += list(list("name" = GLOB.meta_gas_names[gas_id], "settings" = list()))
- thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min2", "selected" = selected.min2))
- thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min1", "selected" = selected.min1))
- thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max1", "selected" = selected.max1))
- thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max2", "selected" = selected.max2))
-
- data["thresholds"] = thresholds
- return data
-
-/obj/machinery/airalarm/ui_act(action, params)
- if(..() || buildstage != 2)
- return
- if((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled))
- return
- var/device_id = params["id_tag"]
- switch(action)
- if("lock")
- if(usr.has_unlimited_silicon_privilege && !wires.is_cut(WIRE_IDSCAN))
- locked = !locked
- . = TRUE
- if("power", "toggle_filter", "widenet", "scrubbing")
- send_signal(device_id, list("[action]" = params["val"]), usr)
- . = TRUE
- if("excheck")
- send_signal(device_id, list("checks" = text2num(params["val"])^1), usr)
- . = TRUE
- if("incheck")
- send_signal(device_id, list("checks" = text2num(params["val"])^2), usr)
- . = TRUE
- if("set_external_pressure", "set_internal_pressure")
- var/area/A = get_area(src)
- var/target = input("New target pressure:", name, A.air_vent_info[device_id][(action == "set_external_pressure" ? "external" : "internal")]) as num|null
- if(!isnull(target) && !..())
- send_signal(device_id, list("[action]" = target), usr)
- . = TRUE
- if("reset_external_pressure")
- send_signal(device_id, list("reset_external_pressure"), usr)
- . = TRUE
- if("reset_internal_pressure")
- send_signal(device_id, list("reset_internal_pressure"), usr)
- . = TRUE
- if("threshold")
- var/env = params["env"]
- if(text2path(env))
- env = text2path(env)
-
- var/name = params["var"]
- var/datum/tlv/tlv = TLV[env]
- if(isnull(tlv))
- return
- var/value = input("New [name] for [env]:", name, tlv.vars[name]) as num|null
- if(!isnull(value) && !..())
- if(value < 0)
- tlv.vars[name] = -1
- else
- tlv.vars[name] = round(value, 0.01)
- investigate_log(" treshold value for [env]:[name] was set to [value] by [key_name(usr)]",INVESTIGATE_ATMOS)
- . = TRUE
- if("mode")
- mode = text2num(params["mode"])
- investigate_log("was turned to [get_mode_name(mode)] mode by [key_name(usr)]",INVESTIGATE_ATMOS)
- apply_mode()
- . = TRUE
- if("alarm")
- var/area/A = get_area(src)
- if(A.atmosalert(2, src))
- post_alert(2)
- . = TRUE
- if("reset")
- var/area/A = get_area(src)
- if(A.atmosalert(0, src))
- post_alert(0)
- . = TRUE
- update_icon()
-
-/obj/machinery/airalarm/proc/reset(wire)
- switch(wire)
- if(WIRE_POWER)
- if(!wires.is_cut(WIRE_POWER))
- shorted = FALSE
- update_icon()
- if(WIRE_AI)
- if(!wires.is_cut(WIRE_AI))
- aidisabled = FALSE
-
-
-/obj/machinery/airalarm/proc/shock(mob/user, prb)
- if((stat & (NOPOWER))) // unpowered, no shock
- return 0
- if(!prob(prb))
- return 0 //you lucked out, no shock for you
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(5, 1, src)
- s.start() //sparks always.
- if (electrocute_mob(user, get_area(src), src, 1, TRUE))
- return 1
- else
- return 0
-
-/obj/machinery/airalarm/proc/refresh_all()
- var/area/A = get_area(src)
- for(var/id_tag in A.air_vent_names)
- var/list/I = A.air_vent_info[id_tag]
- if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
- continue
- send_signal(id_tag, list("status"))
- for(var/id_tag in A.air_scrub_names)
- var/list/I = A.air_scrub_info[id_tag]
- if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
- continue
- send_signal(id_tag, list("status"))
-
-/obj/machinery/airalarm/proc/set_frequency(new_frequency)
- SSradio.remove_object(src, frequency)
- frequency = new_frequency
- radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM)
-
-/obj/machinery/airalarm/proc/send_signal(target, list/command, mob/user)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
- if(!radio_connection)
- return 0
-
- var/datum/signal/signal = new(command)
- signal.data["tag"] = target
- signal.data["sigtype"] = "command"
- signal.data["user"] = user
- radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM)
-
- return 1
-
-/obj/machinery/airalarm/proc/get_mode_name(mode_value)
- switch(mode_value)
- if(AALARM_MODE_SCRUBBING)
- return "Filtering"
- if(AALARM_MODE_CONTAMINATED)
- return "Contaminated"
- if(AALARM_MODE_VENTING)
- return "Draught"
- if(AALARM_MODE_REFILL)
- return "Refill"
- if(AALARM_MODE_PANIC)
- return "Panic Siphon"
- if(AALARM_MODE_REPLACEMENT)
- return "Cycle"
- if(AALARM_MODE_SIPHON)
- return "Siphon"
- if(AALARM_MODE_OFF)
- return "Off"
- if(AALARM_MODE_FLOOD)
- return "Flood"
-
-/obj/machinery/airalarm/proc/apply_mode()
- var/area/A = get_area(src)
- switch(mode)
- if(AALARM_MODE_SCRUBBING)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma),
- "scrubbing" = 1,
- "widenet" = 0,
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 1,
- "checks" = 1,
- "set_external_pressure" = ONE_ATMOSPHERE
- ))
- if(AALARM_MODE_CONTAMINATED)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "set_filters" = list(
- /datum/gas/carbon_dioxide,
- /datum/gas/miasma,
- /datum/gas/plasma,
- /datum/gas/water_vapor,
- /datum/gas/hypernoblium,
- /datum/gas/nitrous_oxide,
- /datum/gas/nitryl,
- /datum/gas/tritium,
- /datum/gas/bz,
- /datum/gas/stimulum,
- /datum/gas/pluoxium
- ),
- "scrubbing" = 1,
- "widenet" = 1,
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 1,
- "checks" = 1,
- "set_external_pressure" = ONE_ATMOSPHERE
- ))
- if(AALARM_MODE_VENTING)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "widenet" = 0,
- "scrubbing" = 0
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 1,
- "checks" = 1,
- "set_external_pressure" = ONE_ATMOSPHERE*2
- ))
- if(AALARM_MODE_REFILL)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma),
- "scrubbing" = 1,
- "widenet" = 0,
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 1,
- "checks" = 1,
- "set_external_pressure" = ONE_ATMOSPHERE * 3
- ))
- if(AALARM_MODE_PANIC,
- AALARM_MODE_REPLACEMENT)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "widenet" = 1,
- "scrubbing" = 0
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 0
- ))
- if(AALARM_MODE_SIPHON)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 1,
- "widenet" = 0,
- "scrubbing" = 0
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 0
- ))
-
- if(AALARM_MODE_OFF)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 0
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 0
- ))
- if(AALARM_MODE_FLOOD)
- for(var/device_id in A.air_scrub_names)
- send_signal(device_id, list(
- "power" = 0
- ))
- for(var/device_id in A.air_vent_names)
- send_signal(device_id, list(
- "power" = 1,
- "checks" = 2,
- "set_internal_pressure" = 0
- ))
-
-/obj/machinery/airalarm/update_icon()
- set_light(0)
- cut_overlays()
- SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
- if(stat & NOPOWER)
- icon_state = "alarm0"
- return
-
- if(stat & BROKEN)
- icon_state = "alarmx"
- return
-
- if(panel_open)
- switch(buildstage)
- if(2)
- icon_state = "alarmx"
- if(1)
- icon_state = "alarm_b2"
- if(0)
- icon_state = "alarm_b1"
- return
-
- icon_state = "alarm1"
- var/overlay_state = AALARM_OVERLAY_OFF
- var/area/A = get_area(src)
- switch(max(danger_level, A.atmosalm))
- if(0)
- add_overlay(AALARM_OVERLAY_GREEN)
- overlay_state = AALARM_OVERLAY_GREEN
- light_color = LIGHT_COLOR_GREEN
- set_light(brightness_on)
- if(1)
- add_overlay(AALARM_OVERLAY_WARN)
- overlay_state = AALARM_OVERLAY_WARN
- light_color = LIGHT_COLOR_LAVA
- set_light(brightness_on)
- if(2)
- add_overlay(AALARM_OVERLAY_DANGER)
- overlay_state = AALARM_OVERLAY_DANGER
- light_color = LIGHT_COLOR_RED
- set_light(brightness_on)
-
- SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- update_light()
-
-/obj/machinery/airalarm/process()
- if((stat & (NOPOWER|BROKEN)) || shorted)
- return
-
- var/turf/location = get_turf(src)
- if(!location)
- return
-
- var/datum/tlv/cur_tlv
-
- var/datum/gas_mixture/environment = location.return_air()
- var/list/env_gases = environment.gases
- var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
-
- cur_tlv = TLV["pressure"]
- var/environment_pressure = environment.return_pressure()
- var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure)
-
- cur_tlv = TLV["temperature"]
- var/temperature_dangerlevel = cur_tlv.get_danger_level(environment.temperature)
-
- var/gas_dangerlevel = 0
- for(var/gas_id in env_gases)
- if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
- continue
- cur_tlv = TLV[gas_id]
- gas_dangerlevel = max(gas_dangerlevel, cur_tlv.get_danger_level(env_gases[gas_id] * partial_pressure))
-
- GAS_GARBAGE_COLLECT(environment.gases)
-
- var/old_danger_level = danger_level
- danger_level = max(pressure_dangerlevel, temperature_dangerlevel, gas_dangerlevel)
-
- if(old_danger_level != danger_level)
- apply_danger_level()
- if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
- mode = AALARM_MODE_SCRUBBING
- apply_mode()
-
- return
-
-
-/obj/machinery/airalarm/proc/post_alert(alert_level)
- var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
-
- if(!frequency)
- return
-
- var/datum/signal/alert_signal = new(list(
- "zone" = get_area_name(src),
- "type" = "Atmospheric"
- ))
- if(alert_level==2)
- alert_signal.data["alert"] = "severe"
- else if (alert_level==1)
- alert_signal.data["alert"] = "minor"
- else if (alert_level==0)
- alert_signal.data["alert"] = "clear"
-
- frequency.post_signal(src, alert_signal, range = -1)
-
-/obj/machinery/airalarm/proc/apply_danger_level()
- var/area/A = get_area(src)
-
- var/new_area_danger_level = 0
- for(var/obj/machinery/airalarm/AA in A)
- if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
- new_area_danger_level = max(new_area_danger_level,AA.danger_level)
- if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state
- post_alert(new_area_danger_level)
-
- update_icon()
-
-/obj/machinery/airalarm/attackby(obj/item/W, mob/user, params)
- switch(buildstage)
- if(2)
- if(istype(W, /obj/item/wirecutters) && panel_open && wires.is_all_cut())
- W.play_tool_sound(src)
- to_chat(user, "You cut the final wires.")
- new /obj/item/stack/cable_coil(loc, 5)
- buildstage = 1
- update_icon()
- return
- else if(istype(W, /obj/item/screwdriver)) // Opening that Air Alarm up.
- W.play_tool_sound(src)
- panel_open = !panel_open
- to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].")
- update_icon()
- return
- else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card
- togglelock(user)
- else if(panel_open && is_wire_tool(W))
- wires.interact(user)
- return
- if(1)
- if(istype(W, /obj/item/crowbar))
- user.visible_message("[user.name] removes the electronics from [src.name].",\
- "You start prying out the circuit...")
- W.play_tool_sound(src)
- if (W.use_tool(src, user, 20))
- if (buildstage == 1)
- to_chat(user, "You remove the air alarm electronics.")
- new /obj/item/electronics/airalarm( src.loc )
- playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
- buildstage = 0
- update_icon()
- return
-
- if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/cable = W
- if(cable.get_amount() < 5)
- to_chat(user, "You need five lengths of cable to wire the air alarm!")
- return
- user.visible_message("[user.name] wires the air alarm.", \
- "You start wiring the air alarm...")
- if (do_after(user, 20, target = src))
- if (cable.get_amount() >= 5 && buildstage == 1)
- cable.use(5)
- to_chat(user, "You wire the air alarm.")
- wires.repair()
- aidisabled = 0
- locked = FALSE
- mode = 1
- shorted = 0
- post_alert(0)
- buildstage = 2
- update_icon()
- return
- if(0)
- if(istype(W, /obj/item/electronics/airalarm))
- if(user.temporarilyRemoveItemFromInventory(W))
- to_chat(user, "You insert the circuit.")
- buildstage = 1
- update_icon()
- qdel(W)
- return
-
- if(istype(W, /obj/item/electroadaptive_pseudocircuit))
- var/obj/item/electroadaptive_pseudocircuit/P = W
- if(!P.adapt_circuit(user, 25))
- return
- user.visible_message("[user] fabricates a circuit and places it into [src].", \
- "You adapt an air alarm circuit and slot it into the assembly.")
- buildstage = 1
- update_icon()
- return
-
- if(istype(W, /obj/item/wrench))
- to_chat(user, "You detach \the [src] from the wall.")
- W.play_tool_sound(src)
- new /obj/item/wallframe/airalarm( user.loc )
- qdel(src)
- return
-
- return ..()
-
-/obj/machinery/airalarm/AltClick(mob/user)
- ..()
- if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
- return
- else
- togglelock(user)
-
-/obj/machinery/airalarm/proc/togglelock(mob/living/user)
- if(stat & (NOPOWER|BROKEN))
- to_chat(user, "It does nothing!")
- else
- if(src.allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
- locked = !locked
- updateUsrDialog()
- to_chat(user, "You [ locked ? "lock" : "unlock"] the air alarm interface.")
- else
- to_chat(user, "Access denied.")
- return
-
-/obj/machinery/airalarm/power_change()
- ..()
- if(stat & NOPOWER)
- set_light(0)
- update_icon()
-
-/obj/machinery/airalarm/emag_act(mob/user)
- if(obj_flags & EMAGGED)
- return
- obj_flags |= EMAGGED
- visible_message("Sparks fly out of [src]!", "You emag [src], disabling its safeties.")
- playsound(src, "sparks", 50, 1)
-
-/obj/machinery/airalarm/obj_break(damage_flag)
- ..()
- update_icon()
- set_light(0)
-
-/obj/machinery/airalarm/deconstruct(disassembled = TRUE)
- if(!(flags_1 & NODECONSTRUCT_1))
- new /obj/item/stack/sheet/metal(loc, 2)
- var/obj/item/I = new /obj/item/electronics/airalarm(loc)
- if(!disassembled)
- I.obj_integrity = I.max_integrity * 0.5
- new /obj/item/stack/cable_coil(loc, 3)
- qdel(src)
-
-#undef AALARM_MODE_SCRUBBING
-#undef AALARM_MODE_VENTING
-#undef AALARM_MODE_PANIC
-#undef AALARM_MODE_REPLACEMENT
-#undef AALARM_MODE_OFF
-#undef AALARM_MODE_FLOOD
-#undef AALARM_MODE_SIPHON
-#undef AALARM_MODE_CONTAMINATED
-#undef AALARM_MODE_REFILL
-#undef AALARM_REPORT_TIMEOUT
+/datum/tlv
+ var/min2
+ var/min1
+ var/max1
+ var/max2
+
+/datum/tlv/New(min2 as num, min1 as num, max1 as num, max2 as num)
+ if(min2) src.min2 = min2
+ if(min1) src.min1 = min1
+ if(max1) src.max1 = max1
+ if(max2) src.max2 = max2
+
+/datum/tlv/proc/get_danger_level(val as num)
+ if(max2 != -1 && val >= max2)
+ return 2
+ if(min2 != -1 && val <= min2)
+ return 2
+ if(max1 != -1 && val >= max1)
+ return 1
+ if(min1 != -1 && val <= min1)
+ return 1
+ return 0
+
+/datum/tlv/no_checks
+ min2 = -1
+ min1 = -1
+ max1 = -1
+ max2 = -1
+
+/datum/tlv/dangerous
+ min2 = -1
+ min1 = -1
+ max1 = 0.2
+ max2 = 0.5
+
+/obj/item/electronics/airalarm
+ name = "air alarm electronics"
+ icon_state = "airalarm_electronics"
+
+/obj/item/wallframe/airalarm
+ name = "air alarm frame"
+ desc = "Used for building Air Alarms."
+ icon = 'icons/obj/monitors.dmi'
+ icon_state = "alarm_bitem"
+ result_path = /obj/machinery/airalarm
+
+#define AALARM_MODE_SCRUBBING 1
+#define AALARM_MODE_VENTING 2 //makes draught
+#define AALARM_MODE_PANIC 3 //like siphon, but stronger (enables widenet)
+#define AALARM_MODE_REPLACEMENT 4 //sucks off all air, then refill and swithes to scrubbing
+#define AALARM_MODE_OFF 5
+#define AALARM_MODE_FLOOD 6 //Emagged mode; turns off scrubbers and pressure checks on vents
+#define AALARM_MODE_SIPHON 7 //Scrubbers suck air
+#define AALARM_MODE_CONTAMINATED 8 //Turns on all filtering and widenet scrubbing.
+#define AALARM_MODE_REFILL 9 //just like normal, but with triple the air output
+
+#define AALARM_REPORT_TIMEOUT 100
+
+#define AALARM_OVERLAY_OFF "alarm_off"
+#define AALARM_OVERLAY_GREEN "alarm_green"
+#define AALARM_OVERLAY_WARN "alarm_amber"
+#define AALARM_OVERLAY_DANGER "alarm_red"
+
+/obj/machinery/airalarm
+ name = "air alarm"
+ desc = "A machine that monitors atmosphere levels. Goes off if the area is dangerous."
+ icon = 'icons/obj/monitors.dmi'
+ icon_state = "alarm0"
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 4
+ active_power_usage = 8
+ power_channel = ENVIRON
+ req_access = list(ACCESS_ATMOSPHERICS)
+ max_integrity = 250
+ integrity_failure = 80
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ resistance_flags = FIRE_PROOF
+
+ var/danger_level = 0
+ var/mode = AALARM_MODE_SCRUBBING
+
+ var/locked = TRUE
+ var/aidisabled = 0
+ var/shorted = 0
+ var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
+ var/brightness_on = 1
+
+ var/frequency = FREQ_ATMOS_CONTROL
+ var/alarm_frequency = FREQ_ATMOS_ALARMS
+ var/datum/radio_frequency/radio_connection
+
+ var/list/TLV = list( // Breathable air.
+ "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa
+ "temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66),
+ /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
+ /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
+ /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
+ /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5),
+ /datum/gas/plasma = new/datum/tlv/dangerous,
+ /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
+ /datum/gas/bz = new/datum/tlv/dangerous,
+ /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
+ /datum/gas/water_vapor = new/datum/tlv/dangerous,
+ /datum/gas/tritium = new/datum/tlv/dangerous,
+ /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects
+ /datum/gas/nitryl = new/datum/tlv/dangerous,
+ /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
+ )
+
+/obj/machinery/airalarm/server // No checks here.
+ TLV = list(
+ "pressure" = new/datum/tlv/no_checks,
+ "temperature" = new/datum/tlv/no_checks,
+ /datum/gas/oxygen = new/datum/tlv/no_checks,
+ /datum/gas/nitrogen = new/datum/tlv/no_checks,
+ /datum/gas/carbon_dioxide = new/datum/tlv/no_checks,
+ /datum/gas/miasma = new/datum/tlv/no_checks,
+ /datum/gas/plasma = new/datum/tlv/no_checks,
+ /datum/gas/nitrous_oxide = new/datum/tlv/no_checks,
+ /datum/gas/bz = new/datum/tlv/no_checks,
+ /datum/gas/hypernoblium = new/datum/tlv/no_checks,
+ /datum/gas/water_vapor = new/datum/tlv/no_checks,
+ /datum/gas/tritium = new/datum/tlv/no_checks,
+ /datum/gas/stimulum = new/datum/tlv/no_checks,
+ /datum/gas/nitryl = new/datum/tlv/no_checks,
+ /datum/gas/pluoxium = new/datum/tlv/no_checks
+ )
+
+/obj/machinery/airalarm/kitchen_cold_room // Copypasta: to check temperatures.
+ TLV = list(
+ "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa
+ "temperature" = new/datum/tlv(T0C-73.15, T0C-63.15, T0C, T0C+10),
+ /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
+ /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
+ /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
+ /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5),
+ /datum/gas/plasma = new/datum/tlv/dangerous,
+ /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
+ /datum/gas/bz = new/datum/tlv/dangerous,
+ /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
+ /datum/gas/water_vapor = new/datum/tlv/dangerous,
+ /datum/gas/tritium = new/datum/tlv/dangerous,
+ /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects
+ /datum/gas/nitryl = new/datum/tlv/dangerous,
+ /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
+ )
+
+/obj/machinery/airalarm/unlocked
+ locked = FALSE
+
+/obj/machinery/airalarm/engine
+ name = "engine air alarm"
+ locked = FALSE
+ req_access = null
+ req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_ENGINE)
+
+/obj/machinery/airalarm/mixingchamber
+ name = "chamber air alarm"
+ locked = FALSE
+ req_access = null
+ req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_TOX, ACCESS_TOX_STORAGE)
+
+/obj/machinery/airalarm/all_access
+ name = "all-access air alarm"
+ desc = "This particular atmos control unit appears to have no access restrictions."
+ locked = FALSE
+ req_access = null
+ req_one_access = null
+
+/obj/machinery/airalarm/syndicate //general syndicate access
+ req_access = list(ACCESS_SYNDICATE)
+
+/obj/machinery/airalarm/directional/north //Pixel offsets get overwritten on New()
+ dir = SOUTH
+ pixel_y = 24
+
+/obj/machinery/airalarm/directional/south
+ dir = NORTH
+ pixel_y = -24
+
+/obj/machinery/airalarm/directional/east
+ dir = WEST
+ pixel_x = 24
+
+/obj/machinery/airalarm/directional/west
+ dir = EAST
+ pixel_x = -24
+
+//all air alarms in area are connected via magic
+/area
+ var/list/air_vent_names = list()
+ var/list/air_scrub_names = list()
+ var/list/air_vent_info = list()
+ var/list/air_scrub_info = list()
+
+/obj/machinery/airalarm/Initialize(mapload, ndir, nbuild)
+ . = ..()
+ wires = new /datum/wires/airalarm(src)
+
+ if(ndir)
+ setDir(ndir)
+
+ if(nbuild)
+ buildstage = 0
+ panel_open = TRUE
+ pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24)
+ pixel_y = (dir & 3)? (dir == 1 ? -24 : 24) : 0
+
+ if(name == initial(name))
+ name = "[get_area_name(src)] Air Alarm"
+
+ power_change()
+ set_frequency(frequency)
+
+/obj/machinery/airalarm/Destroy()
+ SSradio.remove_object(src, frequency)
+ qdel(wires)
+ wires = null
+ return ..()
+
+/obj/machinery/airalarm/examine(mob/user)
+ . = ..()
+ switch(buildstage)
+ if(0)
+ to_chat(user, "It is missing air alarm electronics.")
+ if(1)
+ to_chat(user, "It is missing wiring.")
+ if(2)
+ to_chat(user, "Alt-click to [locked ? "unlock" : "lock"] the interface.")
+
+/obj/machinery/airalarm/ui_status(mob/user)
+ if(user.has_unlimited_silicon_privilege && aidisabled)
+ to_chat(user, "AI control has been disabled.")
+ else if(!shorted)
+ return ..()
+ return UI_CLOSE
+
+/obj/machinery/airalarm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
+ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "airalarm", name, 440, 650, master_ui, state)
+ ui.open()
+
+/obj/machinery/airalarm/ui_data(mob/user)
+ var/data = list(
+ "locked" = locked,
+ "siliconUser" = user.has_unlimited_silicon_privilege,
+ "emagged" = (obj_flags & EMAGGED ? 1 : 0),
+ "danger_level" = danger_level,
+ )
+
+ var/area/A = get_area(src)
+ data["atmos_alarm"] = A.atmosalm
+ data["fire_alarm"] = A.fire
+
+ var/turf/T = get_turf(src)
+ var/datum/gas_mixture/environment = T.return_air()
+ var/datum/tlv/cur_tlv
+
+ data["environment_data"] = list()
+ var/pressure = environment.return_pressure()
+ cur_tlv = TLV["pressure"]
+ data["environment_data"] += list(list(
+ "name" = "Pressure",
+ "value" = pressure,
+ "unit" = "kPa",
+ "danger_level" = cur_tlv.get_danger_level(pressure)
+ ))
+ var/temperature = environment.temperature
+ cur_tlv = TLV["temperature"]
+ data["environment_data"] += list(list(
+ "name" = "Temperature",
+ "value" = temperature,
+ "unit" = "K ([round(temperature - T0C, 0.1)]C)",
+ "danger_level" = cur_tlv.get_danger_level(temperature)
+ ))
+ var/total_moles = environment.total_moles()
+ var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
+ for(var/gas_id in environment.gases)
+ if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
+ continue
+ cur_tlv = TLV[gas_id]
+ data["environment_data"] += list(list(
+ "name" = GLOB.meta_gas_names[gas_id],
+ "value" = environment.gases[gas_id] / total_moles * 100,
+ "unit" = "%",
+ "danger_level" = cur_tlv.get_danger_level(environment.gases[gas_id] * partial_pressure)
+ ))
+
+ if(!locked || user.has_unlimited_silicon_privilege)
+ data["vents"] = list()
+ for(var/id_tag in A.air_vent_names)
+ var/long_name = A.air_vent_names[id_tag]
+ var/list/info = A.air_vent_info[id_tag]
+ if(!info || info["frequency"] != frequency)
+ continue
+ data["vents"] += list(list(
+ "id_tag" = id_tag,
+ "long_name" = sanitize(long_name),
+ "power" = info["power"],
+ "checks" = info["checks"],
+ "excheck" = info["checks"]&1,
+ "incheck" = info["checks"]&2,
+ "direction" = info["direction"],
+ "external" = info["external"],
+ "internal" = info["internal"],
+ "extdefault"= (info["external"] == ONE_ATMOSPHERE),
+ "intdefault"= (info["internal"] == 0)
+ ))
+ data["scrubbers"] = list()
+ for(var/id_tag in A.air_scrub_names)
+ var/long_name = A.air_scrub_names[id_tag]
+ var/list/info = A.air_scrub_info[id_tag]
+ if(!info || info["frequency"] != frequency)
+ continue
+ data["scrubbers"] += list(list(
+ "id_tag" = id_tag,
+ "long_name" = sanitize(long_name),
+ "power" = info["power"],
+ "scrubbing" = info["scrubbing"],
+ "widenet" = info["widenet"],
+ "filter_types" = info["filter_types"]
+ ))
+ data["mode"] = mode
+ data["modes"] = list()
+ data["modes"] += list(list("name" = "Filtering - Scrubs out contaminants", "mode" = AALARM_MODE_SCRUBBING, "selected" = mode == AALARM_MODE_SCRUBBING, "danger" = 0))
+ data["modes"] += list(list("name" = "Contaminated - Scrubs out ALL contaminants quickly","mode" = AALARM_MODE_CONTAMINATED, "selected" = mode == AALARM_MODE_CONTAMINATED, "danger" = 0))
+ data["modes"] += list(list("name" = "Draught - Siphons out air while replacing", "mode" = AALARM_MODE_VENTING, "selected" = mode == AALARM_MODE_VENTING, "danger" = 0))
+ data["modes"] += list(list("name" = "Refill - Triple vent output", "mode" = AALARM_MODE_REFILL, "selected" = mode == AALARM_MODE_REFILL, "danger" = 1))
+ data["modes"] += list(list("name" = "Cycle - Siphons air before replacing", "mode" = AALARM_MODE_REPLACEMENT, "selected" = mode == AALARM_MODE_REPLACEMENT, "danger" = 1))
+ data["modes"] += list(list("name" = "Siphon - Siphons air out of the room", "mode" = AALARM_MODE_SIPHON, "selected" = mode == AALARM_MODE_SIPHON, "danger" = 1))
+ data["modes"] += list(list("name" = "Panic Siphon - Siphons air out of the room quickly","mode" = AALARM_MODE_PANIC, "selected" = mode == AALARM_MODE_PANIC, "danger" = 1))
+ data["modes"] += list(list("name" = "Off - Shuts off vents and scrubbers", "mode" = AALARM_MODE_OFF, "selected" = mode == AALARM_MODE_OFF, "danger" = 0))
+ if(obj_flags & EMAGGED)
+ data["modes"] += list(list("name" = "Flood - Shuts off scrubbers and opens vents", "mode" = AALARM_MODE_FLOOD, "selected" = mode == AALARM_MODE_FLOOD, "danger" = 1))
+
+ var/datum/tlv/selected
+ var/list/thresholds = list()
+
+ selected = TLV["pressure"]
+ thresholds += list(list("name" = "Pressure", "settings" = list()))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min2", "selected" = selected.min2))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min1", "selected" = selected.min1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max1", "selected" = selected.max1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max2", "selected" = selected.max2))
+
+ selected = TLV["temperature"]
+ thresholds += list(list("name" = "Temperature", "settings" = list()))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min2", "selected" = selected.min2))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min1", "selected" = selected.min1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2))
+
+ for(var/gas_id in GLOB.meta_gas_names)
+ if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
+ continue
+ selected = TLV[gas_id]
+ thresholds += list(list("name" = GLOB.meta_gas_names[gas_id], "settings" = list()))
+ thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min2", "selected" = selected.min2))
+ thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min1", "selected" = selected.min1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max1", "selected" = selected.max1))
+ thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max2", "selected" = selected.max2))
+
+ data["thresholds"] = thresholds
+ return data
+
+/obj/machinery/airalarm/ui_act(action, params)
+ if(..() || buildstage != 2)
+ return
+ if((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled))
+ return
+ var/device_id = params["id_tag"]
+ switch(action)
+ if("lock")
+ if(usr.has_unlimited_silicon_privilege && !wires.is_cut(WIRE_IDSCAN))
+ locked = !locked
+ . = TRUE
+ if("power", "toggle_filter", "widenet", "scrubbing")
+ send_signal(device_id, list("[action]" = params["val"]), usr)
+ . = TRUE
+ if("excheck")
+ send_signal(device_id, list("checks" = text2num(params["val"])^1), usr)
+ . = TRUE
+ if("incheck")
+ send_signal(device_id, list("checks" = text2num(params["val"])^2), usr)
+ . = TRUE
+ if("set_external_pressure", "set_internal_pressure")
+ var/area/A = get_area(src)
+ var/target = input("New target pressure:", name, A.air_vent_info[device_id][(action == "set_external_pressure" ? "external" : "internal")]) as num|null
+ if(!isnull(target) && !..())
+ send_signal(device_id, list("[action]" = target), usr)
+ . = TRUE
+ if("reset_external_pressure")
+ send_signal(device_id, list("reset_external_pressure"), usr)
+ . = TRUE
+ if("reset_internal_pressure")
+ send_signal(device_id, list("reset_internal_pressure"), usr)
+ . = TRUE
+ if("threshold")
+ var/env = params["env"]
+ if(text2path(env))
+ env = text2path(env)
+
+ var/name = params["var"]
+ var/datum/tlv/tlv = TLV[env]
+ if(isnull(tlv))
+ return
+ var/value = input("New [name] for [env]:", name, tlv.vars[name]) as num|null
+ if(!isnull(value) && !..())
+ if(value < 0)
+ tlv.vars[name] = -1
+ else
+ tlv.vars[name] = round(value, 0.01)
+ investigate_log(" treshold value for [env]:[name] was set to [value] by [key_name(usr)]",INVESTIGATE_ATMOS)
+ . = TRUE
+ if("mode")
+ mode = text2num(params["mode"])
+ investigate_log("was turned to [get_mode_name(mode)] mode by [key_name(usr)]",INVESTIGATE_ATMOS)
+ apply_mode()
+ . = TRUE
+ if("alarm")
+ var/area/A = get_area(src)
+ if(A.atmosalert(2, src))
+ post_alert(2)
+ . = TRUE
+ if("reset")
+ var/area/A = get_area(src)
+ if(A.atmosalert(0, src))
+ post_alert(0)
+ . = TRUE
+ update_icon()
+
+/obj/machinery/airalarm/proc/reset(wire)
+ switch(wire)
+ if(WIRE_POWER)
+ if(!wires.is_cut(WIRE_POWER))
+ shorted = FALSE
+ update_icon()
+ if(WIRE_AI)
+ if(!wires.is_cut(WIRE_AI))
+ aidisabled = FALSE
+
+
+/obj/machinery/airalarm/proc/shock(mob/user, prb)
+ if((stat & (NOPOWER))) // unpowered, no shock
+ return 0
+ if(!prob(prb))
+ return 0 //you lucked out, no shock for you
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(5, 1, src)
+ s.start() //sparks always.
+ if (electrocute_mob(user, get_area(src), src, 1, TRUE))
+ return 1
+ else
+ return 0
+
+/obj/machinery/airalarm/proc/refresh_all()
+ var/area/A = get_area(src)
+ for(var/id_tag in A.air_vent_names)
+ var/list/I = A.air_vent_info[id_tag]
+ if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
+ continue
+ send_signal(id_tag, list("status"))
+ for(var/id_tag in A.air_scrub_names)
+ var/list/I = A.air_scrub_info[id_tag]
+ if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
+ continue
+ send_signal(id_tag, list("status"))
+
+/obj/machinery/airalarm/proc/set_frequency(new_frequency)
+ SSradio.remove_object(src, frequency)
+ frequency = new_frequency
+ radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM)
+
+/obj/machinery/airalarm/proc/send_signal(target, list/command, mob/user)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
+ if(!radio_connection)
+ return 0
+
+ var/datum/signal/signal = new(command)
+ signal.data["tag"] = target
+ signal.data["sigtype"] = "command"
+ signal.data["user"] = user
+ radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM)
+
+ return 1
+
+/obj/machinery/airalarm/proc/get_mode_name(mode_value)
+ switch(mode_value)
+ if(AALARM_MODE_SCRUBBING)
+ return "Filtering"
+ if(AALARM_MODE_CONTAMINATED)
+ return "Contaminated"
+ if(AALARM_MODE_VENTING)
+ return "Draught"
+ if(AALARM_MODE_REFILL)
+ return "Refill"
+ if(AALARM_MODE_PANIC)
+ return "Panic Siphon"
+ if(AALARM_MODE_REPLACEMENT)
+ return "Cycle"
+ if(AALARM_MODE_SIPHON)
+ return "Siphon"
+ if(AALARM_MODE_OFF)
+ return "Off"
+ if(AALARM_MODE_FLOOD)
+ return "Flood"
+
+/obj/machinery/airalarm/proc/apply_mode()
+ var/area/A = get_area(src)
+ switch(mode)
+ if(AALARM_MODE_SCRUBBING)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma),
+ "scrubbing" = 1,
+ "widenet" = 0,
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "checks" = 1,
+ "set_external_pressure" = ONE_ATMOSPHERE
+ ))
+ if(AALARM_MODE_CONTAMINATED)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "set_filters" = list(
+ /datum/gas/carbon_dioxide,
+ /datum/gas/miasma,
+ /datum/gas/plasma,
+ /datum/gas/water_vapor,
+ /datum/gas/hypernoblium,
+ /datum/gas/nitrous_oxide,
+ /datum/gas/nitryl,
+ /datum/gas/tritium,
+ /datum/gas/bz,
+ /datum/gas/stimulum,
+ /datum/gas/pluoxium
+ ),
+ "scrubbing" = 1,
+ "widenet" = 1,
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "checks" = 1,
+ "set_external_pressure" = ONE_ATMOSPHERE
+ ))
+ if(AALARM_MODE_VENTING)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "widenet" = 0,
+ "scrubbing" = 0
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "checks" = 1,
+ "set_external_pressure" = ONE_ATMOSPHERE*2
+ ))
+ if(AALARM_MODE_REFILL)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma),
+ "scrubbing" = 1,
+ "widenet" = 0,
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "checks" = 1,
+ "set_external_pressure" = ONE_ATMOSPHERE * 3
+ ))
+ if(AALARM_MODE_PANIC,
+ AALARM_MODE_REPLACEMENT)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "widenet" = 1,
+ "scrubbing" = 0
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 0
+ ))
+ if(AALARM_MODE_SIPHON)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "widenet" = 0,
+ "scrubbing" = 0
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 0
+ ))
+
+ if(AALARM_MODE_OFF)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 0
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 0
+ ))
+ if(AALARM_MODE_FLOOD)
+ for(var/device_id in A.air_scrub_names)
+ send_signal(device_id, list(
+ "power" = 0
+ ))
+ for(var/device_id in A.air_vent_names)
+ send_signal(device_id, list(
+ "power" = 1,
+ "checks" = 2,
+ "set_internal_pressure" = 0
+ ))
+
+/obj/machinery/airalarm/update_icon()
+ set_light(0)
+ cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ if(stat & NOPOWER)
+ icon_state = "alarm0"
+ return
+
+ if(stat & BROKEN)
+ icon_state = "alarmx"
+ return
+
+ if(panel_open)
+ switch(buildstage)
+ if(2)
+ icon_state = "alarmx"
+ if(1)
+ icon_state = "alarm_b2"
+ if(0)
+ icon_state = "alarm_b1"
+ return
+
+ icon_state = "alarm1"
+ var/overlay_state = AALARM_OVERLAY_OFF
+ var/area/A = get_area(src)
+ switch(max(danger_level, A.atmosalm))
+ if(0)
+ add_overlay(AALARM_OVERLAY_GREEN)
+ overlay_state = AALARM_OVERLAY_GREEN
+ light_color = LIGHT_COLOR_GREEN
+ set_light(brightness_on)
+ if(1)
+ add_overlay(AALARM_OVERLAY_WARN)
+ overlay_state = AALARM_OVERLAY_WARN
+ light_color = LIGHT_COLOR_LAVA
+ set_light(brightness_on)
+ if(2)
+ add_overlay(AALARM_OVERLAY_DANGER)
+ overlay_state = AALARM_OVERLAY_DANGER
+ light_color = LIGHT_COLOR_RED
+ set_light(brightness_on)
+
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ update_light()
+
+/obj/machinery/airalarm/process()
+ if((stat & (NOPOWER|BROKEN)) || shorted)
+ return
+
+ var/turf/location = get_turf(src)
+ if(!location)
+ return
+
+ var/datum/tlv/cur_tlv
+
+ var/datum/gas_mixture/environment = location.return_air()
+ var/list/env_gases = environment.gases
+ var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
+
+ cur_tlv = TLV["pressure"]
+ var/environment_pressure = environment.return_pressure()
+ var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure)
+
+ cur_tlv = TLV["temperature"]
+ var/temperature_dangerlevel = cur_tlv.get_danger_level(environment.temperature)
+
+ var/gas_dangerlevel = 0
+ for(var/gas_id in env_gases)
+ if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
+ continue
+ cur_tlv = TLV[gas_id]
+ gas_dangerlevel = max(gas_dangerlevel, cur_tlv.get_danger_level(env_gases[gas_id] * partial_pressure))
+
+ GAS_GARBAGE_COLLECT(environment.gases)
+
+ var/old_danger_level = danger_level
+ danger_level = max(pressure_dangerlevel, temperature_dangerlevel, gas_dangerlevel)
+
+ if(old_danger_level != danger_level)
+ apply_danger_level()
+ if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
+ mode = AALARM_MODE_SCRUBBING
+ apply_mode()
+
+ return
+
+
+/obj/machinery/airalarm/proc/post_alert(alert_level)
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
+
+ if(!frequency)
+ return
+
+ var/datum/signal/alert_signal = new(list(
+ "zone" = get_area_name(src),
+ "type" = "Atmospheric"
+ ))
+ if(alert_level==2)
+ alert_signal.data["alert"] = "severe"
+ else if (alert_level==1)
+ alert_signal.data["alert"] = "minor"
+ else if (alert_level==0)
+ alert_signal.data["alert"] = "clear"
+
+ frequency.post_signal(src, alert_signal, range = -1)
+
+/obj/machinery/airalarm/proc/apply_danger_level()
+ var/area/A = get_area(src)
+
+ var/new_area_danger_level = 0
+ for(var/obj/machinery/airalarm/AA in A)
+ if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
+ new_area_danger_level = max(new_area_danger_level,AA.danger_level)
+ if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state
+ post_alert(new_area_danger_level)
+
+ update_icon()
+
+/obj/machinery/airalarm/attackby(obj/item/W, mob/user, params)
+ switch(buildstage)
+ if(2)
+ if(istype(W, /obj/item/wirecutters) && panel_open && wires.is_all_cut())
+ W.play_tool_sound(src)
+ to_chat(user, "You cut the final wires.")
+ new /obj/item/stack/cable_coil(loc, 5)
+ buildstage = 1
+ update_icon()
+ return
+ else if(istype(W, /obj/item/screwdriver)) // Opening that Air Alarm up.
+ W.play_tool_sound(src)
+ panel_open = !panel_open
+ to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].")
+ update_icon()
+ return
+ else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card
+ togglelock(user)
+ else if(panel_open && is_wire_tool(W))
+ wires.interact(user)
+ return
+ if(1)
+ if(istype(W, /obj/item/crowbar))
+ user.visible_message("[user.name] removes the electronics from [src.name].",\
+ "You start prying out the circuit...")
+ W.play_tool_sound(src)
+ if (W.use_tool(src, user, 20))
+ if (buildstage == 1)
+ to_chat(user, "You remove the air alarm electronics.")
+ new /obj/item/electronics/airalarm( src.loc )
+ playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
+ buildstage = 0
+ update_icon()
+ return
+
+ if(istype(W, /obj/item/stack/cable_coil))
+ var/obj/item/stack/cable_coil/cable = W
+ if(cable.get_amount() < 5)
+ to_chat(user, "You need five lengths of cable to wire the air alarm!")
+ return
+ user.visible_message("[user.name] wires the air alarm.", \
+ "You start wiring the air alarm...")
+ if (do_after(user, 20, target = src))
+ if (cable.get_amount() >= 5 && buildstage == 1)
+ cable.use(5)
+ to_chat(user, "You wire the air alarm.")
+ wires.repair()
+ aidisabled = 0
+ locked = FALSE
+ mode = 1
+ shorted = 0
+ post_alert(0)
+ buildstage = 2
+ update_icon()
+ return
+ if(0)
+ if(istype(W, /obj/item/electronics/airalarm))
+ if(user.temporarilyRemoveItemFromInventory(W))
+ to_chat(user, "You insert the circuit.")
+ buildstage = 1
+ update_icon()
+ qdel(W)
+ return
+
+ if(istype(W, /obj/item/electroadaptive_pseudocircuit))
+ var/obj/item/electroadaptive_pseudocircuit/P = W
+ if(!P.adapt_circuit(user, 25))
+ return
+ user.visible_message("[user] fabricates a circuit and places it into [src].", \
+ "You adapt an air alarm circuit and slot it into the assembly.")
+ buildstage = 1
+ update_icon()
+ return
+
+ if(istype(W, /obj/item/wrench))
+ to_chat(user, "You detach \the [src] from the wall.")
+ W.play_tool_sound(src)
+ new /obj/item/wallframe/airalarm( user.loc )
+ qdel(src)
+ return
+
+ return ..()
+
+/obj/machinery/airalarm/AltClick(mob/user)
+ ..()
+ if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
+ return
+ else
+ togglelock(user)
+
+/obj/machinery/airalarm/proc/togglelock(mob/living/user)
+ if(stat & (NOPOWER|BROKEN))
+ to_chat(user, "It does nothing!")
+ else
+ if(src.allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
+ locked = !locked
+ updateUsrDialog()
+ to_chat(user, "You [ locked ? "lock" : "unlock"] the air alarm interface.")
+ else
+ to_chat(user, "Access denied.")
+ return
+
+/obj/machinery/airalarm/power_change()
+ ..()
+ if(stat & NOPOWER)
+ set_light(0)
+ update_icon()
+
+/obj/machinery/airalarm/emag_act(mob/user)
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ visible_message("Sparks fly out of [src]!", "You emag [src], disabling its safeties.")
+ playsound(src, "sparks", 50, 1)
+ return TRUE
+
+/obj/machinery/airalarm/obj_break(damage_flag)
+ ..()
+ update_icon()
+ set_light(0)
+
+/obj/machinery/airalarm/deconstruct(disassembled = TRUE)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ new /obj/item/stack/sheet/metal(loc, 2)
+ var/obj/item/I = new /obj/item/electronics/airalarm(loc)
+ if(!disassembled)
+ I.obj_integrity = I.max_integrity * 0.5
+ new /obj/item/stack/cable_coil(loc, 3)
+ qdel(src)
+
+#undef AALARM_MODE_SCRUBBING
+#undef AALARM_MODE_VENTING
+#undef AALARM_MODE_PANIC
+#undef AALARM_MODE_REPLACEMENT
+#undef AALARM_MODE_OFF
+#undef AALARM_MODE_FLOOD
+#undef AALARM_MODE_SIPHON
+#undef AALARM_MODE_CONTAMINATED
+#undef AALARM_MODE_REFILL
+#undef AALARM_REPORT_TIMEOUT
diff --git a/code/modules/cargo/bounties/reagent.dm b/code/modules/cargo/bounties/reagent.dm
index 3f458e2b12..9f1c76db3d 100644
--- a/code/modules/cargo/bounties/reagent.dm
+++ b/code/modules/cargo/bounties/reagent.dm
@@ -109,10 +109,9 @@ datum/bounty/reagent/complex_drink/New()
/datum/reagent/consumable/ethanol/patron,\
/datum/reagent/consumable/ethanol/quadruple_sec,\
/datum/reagent/consumable/ethanol/quintuple_sec,\
- /datum/reagent/consumable/bluecherryshake,\
/datum/reagent/consumable/doctor_delight,\
/datum/reagent/consumable/ethanol/silencer)
-
+
var/reagent_type = pick(possible_reagents)
wanted_reagent = new reagent_type
name = wanted_reagent.name
diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm
index b5b9a616d0..1a607b4b96 100644
--- a/code/modules/cargo/console.dm
+++ b/code/modules/cargo/console.dm
@@ -36,6 +36,7 @@
cat |= EXPORT_EMAG
/obj/machinery/computer/cargo/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
user.visible_message("[user] swipes a suspicious card through [src]!",
@@ -48,6 +49,8 @@
var/obj/item/circuitboard/computer/cargo/board = circuit
board.contraband = TRUE
board.obj_flags |= EMAGGED
+ req_access = list()
+ return TRUE
/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index 5288c27ff6..0097346a34 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -54,6 +54,7 @@
..()
/obj/machinery/computer/cargo/express/emag_act(mob/living/user)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(obj_flags & EMAGGED)
return
user.visible_message("[user] swipes a suspicious card through [src]!",
@@ -63,6 +64,8 @@
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
packin_up()
+ req_access = list()
+ return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 7216b73af6..a54584d6cc 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -75,3 +75,8 @@
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
+
+ var/client_keysend_amount = 0
+ var/next_keysend_reset = 0
+ var/next_keysend_trip_reset = 0
+ var/keysend_tripped = FALSE
\ No newline at end of file
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 06f23574e1..5442fcb932 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -817,6 +817,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
var/file = GLOB.vox_sounds[name]
Export("##action=load_rsc", file)
stoplag()
+ for (var/name in GLOB.vox_sounds_male)
+ var/file = GLOB.vox_sounds_male[name]
+ Export("##action=load_rsc", file)
+ stoplag()
#endif
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 64a1f48962..ae6de5ba05 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -85,6 +85,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/facial_hair_color = "000" //Facial hair color
var/skin_tone = "caucasian1" //Skin color
var/eye_color = "000" //Eye color
+ var/horn_color = "85615a" //Horn color
var/datum/species/pref_species = new /datum/species/human() //Mutant race
var/list/features = list("mcolor" = "FFF",
"tail_lizard" = "Smooth",
@@ -96,8 +97,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"frills" = "None",
"spines" = "None",
"body_markings" = "None",
- "legs" = "Normal Legs",
- "moth_wings" = "Plain",
+ "legs" = "Plantigrade",
+ "insect_wings" = "Plain",
+ "insect_fluff" = "None",
"mcolor2" = "FFF",
"mcolor3" = "FFF",
"mam_body_markings" = "Plain",
@@ -157,7 +159,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"womb_fluid" = "femcum",
"ipc_screen" = "Sunburst",
"ipc_antenna" = "None",
- "flavor_text" = ""
+ "flavor_text" = "",
+ "meat_type" = "Mammalian"
)
var/list/custom_names = list()
@@ -172,18 +175,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/all_quirks = list()
var/list/character_quirks = list()
- //Jobs, uses bitflags
- var/job_civilian_high = 0
- var/job_civilian_med = 0
- var/job_civilian_low = 0
-
- var/job_medsci_high = 0
- var/job_medsci_med = 0
- var/job_medsci_low = 0
-
- var/job_engsec_high = 0
- var/job_engsec_med = 0
- var/job_engsec_low = 0
+ //Job preferences 2.0 - indexed by job title , no key or value implies never
+ var/list/job_preferences = list()
// Want randomjob if preferences already filled - Donkie
var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants
@@ -241,7 +234,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
#define APPEARANCE_CATEGORY_COLUMN "
"
dat += "Waffle cones:DispenseMakex5 [product_types[CONE_WAFFLE]] cones left. (Ingredients: flour, sugar) "
@@ -207,6 +223,12 @@
if ("strawberry")
desc = "A delicious [cone_type] cone filled with strawberry ice cream. Definitely not made with real strawberries."
reagents.add_reagent("berryjuice", 2)
+ if ("peach")
+ desc = "A delicious [cone_type] cone filled with peach ice cream. Definitely made with real peaches!"
+ reagents.add_reagent("peachjuice", 2)
+ if ("grape")
+ desc = "A delicious [cone_type] cone filled with grape ice cream. Surprisingly, made with real pink grape, likely not real sugarcanes used."
+ reagents.add_reagent("grapejuice", 2)
if ("blue")
desc = "A delicious [cone_type] cone filled with blue ice cream. Made with real... blue?"
reagents.add_reagent("singulo", 2)
@@ -228,6 +250,8 @@
#undef ICECREAM_VANILLA
#undef ICECREAM_CHOCOLATE
#undef ICECREAM_STRAWBERRY
+#undef ICECREAM_PEACH
+#undef ICECREAM_GRAPE
#undef ICECREAM_BLUE
#undef CONE_WAFFLE
#undef CONE_CHOC
diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
index 7c44857b1d..6880260c4c 100644
--- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
@@ -381,6 +381,33 @@
id = "neurotoxin"
results = list("neurotoxin" = 2)
required_reagents = list("gargleblaster" = 1, "morphine" = 1)
+ //FermiChem vars: Easy to make, but hard to make potent
+ OptimalTempMin = 200 // Lower area of bell curve for determining heat based rate reactions
+ OptimalTempMax = 950 // Upper end for above
+ ExplodeTemp = 999 //Temperature at which reaction explodes
+ OptimalpHMin = 4.6 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase)
+ OptimalpHMax = 5.2 // Higest value for above
+ ReactpHLim = 5 // How far out pH wil react, giving impurity place (Exponential phase)
+ CatalystFact = 0 // How much the catalyst affects the reaction (0 = no catalyst)
+ CurveSharpT = 2 // How sharp the temperature exponential curve is (to the power of value)
+ CurveSharppH = 4 // How sharp the pH exponential curve is (to the power of value)
+ ThermicConstant = 10 //Temperature change per 1u produced
+ HIonRelease = 0.02 //pH change per 1u reaction
+ RateUpLim = 5 //Optimal/max rate possible if all conditions are perfect
+ FermiChem = TRUE//If the chemical uses the Fermichem reaction mechanics
+ FermiExplode = FALSE //If the chemical explodes in a special way
+ PurityMin = 0 //The minimum purity something has to be above, otherwise it explodes.
+
+/datum/chemical_reaction/neurotoxin/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ var/datum/reagent/consumable/ethanol/neurotoxin/Nt = locate(/datum/reagent/consumable/ethanol/neurotoxin) in my_atom.reagents.reagent_list
+ var/cached_volume = Nt.volume
+ if(Nt.purity < 0.5)
+ holder.remove_reagent(src.id, cached_volume)
+ holder.add_reagent("neuroweak", cached_volume)
+
+/datum/chemical_reaction/neurotoxin/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)//reduced size
+ volume = volume/10
+ ..()
/datum/chemical_reaction/snowwhite
name = "Snow White"
@@ -454,18 +481,6 @@
results = list("vanillapudding" = 20)
required_reagents = list("vanilla" = 5, "milk" = 5, "eggyolk" = 5)
-/datum/chemical_reaction/cherryshake
- name = "Cherry Shake"
- id = "cherryshake"
- results = list("cherryshake" = 3)
- required_reagents = list("cherryjelly" = 1, "ice" = 1, "cream" = 1)
-
-/datum/chemical_reaction/bluecherryshake
- name = "Blue Cherry Shake"
- id = "bluecherryshake"
- results = list("bluecherryshake" = 3)
- required_reagents = list("bluecherryjelly" = 1, "ice" = 1, "cream" = 1)
-
/datum/chemical_reaction/drunkenblumpkin
name = "Drunken Blumpkin"
id = "drunkenblumpkin"
@@ -794,4 +809,17 @@
name = "Red Queen"
id = "red_queen"
results = list("red_queen" = 10)
- required_reagents = list("tea" = 6, "mercury" = 2, "blackpepper" = 1, "growthserum" = 1)
\ No newline at end of file
+ required_reagents = list("tea" = 6, "mercury" = 2, "blackpepper" = 1, "growthserum" = 1)
+
+/datum/chemical_reaction/catnip_tea
+ name = "Catnip Tea"
+ id = "catnip_tea"
+ results = list("catnip_tea" = 3)
+ required_reagents = list("tea" = 5, "catnip" = 2)
+
+/datum/chemical_reaction/commander_and_chief
+ name = "Commander and Chief"
+ id = "commander_and_chief"
+ results = list("commander_and_chief" = 50)
+ required_reagents = list("alliescocktail" = 50, "champagne" = 20, "doctorsdelight" = 10, "quintuple_sec" = 10, "screwdrivercocktail" = 10)
+ mix_message = "When your powers combine, I am Captain Pl-..."
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
index e26d15dbd3..284affe2ec 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
@@ -169,6 +169,15 @@
result = /obj/item/reagent_containers/food/snacks/store/cake/vanilla_cake
subcategory = CAT_CAKE
+/datum/crafting_recipe/food/peachcake
+ name = "Peach cake"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/reagent_containers/food/snacks/grown/peach = 5
+ )
+ result = /obj/item/reagent_containers/food/snacks/store/cake/peach_cake
+ subcategory = CAT_CAKE
+
/datum/crafting_recipe/food/cak
name = "Living cat/cake hybrid"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
index 73b09df68d..8f4c5d05f1 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
@@ -42,6 +42,52 @@
result = /obj/item/reagent_containers/food/snacks/honkdae
subcategory = CAT_ICE
+/datum/crafting_recipe/food/banana_split
+ name = "Banana Split"
+ always_availible = FALSE
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/icecream = 3,
+ /obj/item/reagent_containers/food/snacks/grown/banana = 1,
+ /obj/item/reagent_containers/food/snacks/grown/cherries = 1,
+ /obj/item/reagent_containers/food/snacks/chocolatebar = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/banana_split
+ subcategory = CAT_ICE
+
+/datum/crafting_recipe/food/root_float
+ name = "Cola Float"
+ always_availible = FALSE
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/icecream = 1,
+ /obj/item/reagent_containers/food/snacks/grown/cherries = 1,
+ /datum/reagent/consumable/space_cola = 10,
+ /obj/item/reagent_containers/food/drinks/drinkingglass = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/cola_float
+ subcategory = CAT_ICE
+
+/datum/crafting_recipe/food/charrie_float
+ name = "Cherry Shake"
+ always_availible = FALSE
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/icecream = 1,
+ /obj/item/reagent_containers/food/snacks/grown/cherries = 3,
+ /obj/item/reagent_containers/food/drinks/drinkingglass = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/charrie_float
+ subcategory = CAT_ICE
+
+/datum/crafting_recipe/food/bluecharrie_float
+ name = "Blue Cherry Shake"
+ always_availible = FALSE
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/icecream = 1,
+ /obj/item/reagent_containers/food/snacks/grown/bluecherries = 3,
+ /obj/item/reagent_containers/food/drinks/drinkingglass = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/bluecharrie_float
+ subcategory = CAT_ICE
+
//////////////////////////SNOW CONES///////////////////////
/datum/crafting_recipe/food/flaverless_sc
@@ -232,6 +278,28 @@
result = /obj/item/reagent_containers/food/snacks/snowcones/honey
subcategory = CAT_ICE
+/datum/crafting_recipe/food/peach_sc
+ name = "Peach snowcone"
+ reqs = list(
+ /obj/item/reagent_containers/food/drinks/sillycup = 1,
+ /datum/reagent/water = 5,
+ /datum/reagent/consumable/ice = 15,
+ /obj/item/reagent_containers/food/snacks/grown/peach = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/snowcones/peach
+ subcategory = CAT_ICE
+
+/datum/crafting_recipe/food/strawberry_sc
+ name = "Strawberry snowcone"
+ reqs = list(
+ /obj/item/reagent_containers/food/drinks/sillycup = 1,
+ /datum/reagent/water = 5,
+ /datum/reagent/consumable/ice = 15,
+ /obj/item/reagent_containers/food/snacks/grown/strawberry = 2
+ )
+ result = /obj/item/reagent_containers/food/snacks/snowcones/strawberry
+ subcategory = CAT_ICE
+
/datum/crafting_recipe/food/honey_sc
name = "Rainbow snowcone"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_meat.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_meat.dm
index 7eff2820c5..d17ccf585e 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_meat.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_meat.dm
@@ -128,6 +128,18 @@
result = /obj/item/reagent_containers/food/snacks/pigblanket
subcategory = CAT_MEAT
+/datum/crafting_recipe/food/corndog
+ name = "Corndog meal"
+ reqs = list(
+ /obj/item/stack/rods = 1,
+ /obj/item/reagent_containers/food/snacks/meat/cutlet = 1,
+ /obj/item/reagent_containers/food/snacks/bun = 1,
+ /datum/reagent/consumable/mustard = 5,
+ /datum/reagent/consumable/ketchup = 5
+ )
+ result = /obj/item/reagent_containers/food/snacks/corndog
+ subcategory = CAT_MEAT
+
/datum/crafting_recipe/food/ratkebab
name = "Rat Kebab"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index 931a78212f..365cf499bb 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -23,7 +23,7 @@
result = /obj/item/reagent_containers/food/snacks/donut
subcategory = CAT_PASTRY
-/datum/crafting_recipe/food/donut
+/datum/crafting_recipe/food/donut/semen
time = 15
name = "Semen donut"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
index 81824dc4b8..30c265e237 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
@@ -214,4 +214,13 @@
/obj/item/slime_extract = 1
)
result = /obj/item/reagent_containers/food/snacks/pie/cocolavatart
+ subcategory = CAT_PIE
+
+/datum/crafting_recipe/food/peachpie
+ name = "Peach Pie"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/pie/plain = 1,
+ /obj/item/reagent_containers/food/snacks/grown/peach = 3
+ )
+ result = /obj/item/reagent_containers/food/snacks/pie/peachpie
subcategory = CAT_PIE
\ No newline at end of file
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 4ffc10f9c2..6cc11afdf5 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -149,6 +149,7 @@
active_power_usage = 50 + spawned.len * 3 + effects.len * 5
/obj/machinery/computer/holodeck/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
if(!LAZYLEN(emag_programs))
@@ -160,6 +161,7 @@
to_chat(user, "Warning. Automatic shutoff and derezing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
log_game("[key_name(user)] emagged the Holodeck Control Console")
nerf(!(obj_flags & EMAGGED))
+ return TRUE
/obj/machinery/computer/holodeck/emp_act(severity)
. = ..()
diff --git a/code/modules/hydroponics/grown/berries.dm b/code/modules/hydroponics/grown/berries.dm
index e9ad49eedd..ef019387e8 100644
--- a/code/modules/hydroponics/grown/berries.dm
+++ b/code/modules/hydroponics/grown/berries.dm
@@ -224,6 +224,10 @@
species = "strawberry"
plantname = "Strawberry Vine"
product = /obj/item/reagent_containers/food/snacks/grown/strawberry
+ growthstages = 6
+ growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
+ icon_grow = "strawberry-grow"
+ icon_dead = "berry-dead"
reagents_add = list("vitamin" = 0.07, "nutriment" = 0.1, "sugar" = 0.2)
mutatelist = list()
diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm
index 107a6a94f9..0902052a11 100644
--- a/code/modules/hydroponics/grown/misc.dm
+++ b/code/modules/hydroponics/grown/misc.dm
@@ -1,7 +1,7 @@
// Starthistle
/obj/item/seeds/starthistle
name = "pack of starthistle seeds"
- desc = "A robust species of weed that often springs up in-between the cracks of spaceship parking lots."
+ desc = "A robust species of weed that often springs up in-between the cracks of spaceship parking lots. Grind down these seeds for a substitution for mustardgrind."
icon_state = "seed-starthistle"
species = "starthistle"
plantname = "Starthistle"
@@ -9,9 +9,10 @@
endurance = 50 // damm pesky weeds
maturation = 5
production = 1
- yield = 2
+ yield = 6
potency = 10
growthstages = 3
+ grind_results = list("mustardgrind" = 1)
growing_icon = 'icons/obj/hydroponics/growing_flowers.dmi'
genes = list(/datum/plant_gene/trait/plant_type/weed_hardy)
mutatelist = list(/obj/item/seeds/harebell)
diff --git a/code/modules/hydroponics/grown/peach.dm b/code/modules/hydroponics/grown/peach.dm
new file mode 100644
index 0000000000..6fbf933bd1
--- /dev/null
+++ b/code/modules/hydroponics/grown/peach.dm
@@ -0,0 +1,27 @@
+// Peach
+/obj/item/seeds/peach
+ name = "pack of peach seeds"
+ desc = "These seeds grow into peach trees."
+ icon_state = "seed-peach"
+ species = "peach"
+ plantname = "Peach Tree"
+ product = /obj/item/reagent_containers/food/snacks/grown/peach
+ lifespan = 65
+ endurance = 40
+ yield = 3
+ growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
+ icon_grow = "peach-grow"
+ icon_dead = "peach-dead"
+ genes = list(/datum/plant_gene/trait/repeated_harvest)
+ reagents_add = list("vitamin" = 0.04, "nutriment" = 0.1)
+
+/obj/item/reagent_containers/food/snacks/grown/peach
+ seed = /obj/item/seeds/peach
+ name = "peach"
+ desc = "It's fuzzy!"
+ icon_state = "peach"
+ filling_color = "#FF4500"
+ bitesize = 25
+ foodtype = FRUIT
+ juice_results = list("peachjuice" = 0)
+ tastes = list("peach" = 1)
diff --git a/code/modules/hydroponics/grown/tea_coffee.dm b/code/modules/hydroponics/grown/tea_coffee.dm
index d9e775acc0..06cbb1df0c 100644
--- a/code/modules/hydroponics/grown/tea_coffee.dm
+++ b/code/modules/hydroponics/grown/tea_coffee.dm
@@ -33,7 +33,7 @@
species = "teaastra"
plantname = "Tea Astra Plant"
product = /obj/item/reagent_containers/food/snacks/grown/tea/astra
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/tea/catnip)
reagents_add = list("synaptizine" = 0.1, "vitamin" = 0.04, "teapowder" = 0.1)
rarity = 20
@@ -44,6 +44,24 @@
filling_color = "#4582B4"
grind_results = list("teapowder" = 0, "salglu_solution" = 0)
+// Kitty drugs
+/obj/item/seeds/tea/catnip
+ name = "pack of catnip seeds"
+ icon_state = "seed-catnip"
+ desc = "Long stocks with flowering tips that has a chemical to make feline attracted to it."
+ species = "catnip"
+ plantname = "Catnip Plant"
+ growthstages = 3
+ product = /obj/item/reagent_containers/food/snacks/grown/tea/catnip
+ reagents_add = list("catnip" = 0.1, "vitamin" = 0.06, "teapowder" = 0.3)
+ rarity = 50
+
+/obj/item/reagent_containers/food/snacks/grown/tea/catnip
+ seed = /obj/item/seeds/tea/catnip
+ name = "Catnip buds"
+ icon_state = "catnip_leaves"
+ filling_color = "#4582B4"
+ grind_results = list("catnp" = 2, "water" = 1)
// Coffee
/obj/item/seeds/coffee
diff --git a/code/modules/integrated_electronics/core/printer.dm b/code/modules/integrated_electronics/core/printer.dm
index de3ade389f..f0aa10f2da 100644
--- a/code/modules/integrated_electronics/core/printer.dm
+++ b/code/modules/integrated_electronics/core/printer.dm
@@ -193,7 +193,7 @@
else if(ispath(build_type, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/IC = SScircuit.cached_components[build_type]
cost = IC.materials[MAT_METAL]
- else if(!build_type in SScircuit.circuit_fabricator_recipe_list["Tools"])
+ else if(!(build_type in SScircuit.circuit_fabricator_recipe_list["Tools"]))
return
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm
index 4b7b175240..f99bf65071 100644
--- a/code/modules/jobs/job_exp.dm
+++ b/code/modules/jobs/job_exp.dm
@@ -8,6 +8,8 @@ GLOBAL_PROTECT(exp_to_update)
return 0
if(!CONFIG_GET(flag/use_exp_tracking))
return 0
+ if(!SSdbcore.Connect())
+ return 0
if(!exp_requirements || !exp_type)
return 0
if(!job_is_xp_locked(src.title))
diff --git a/code/modules/jobs/job_types/job.dm b/code/modules/jobs/job_types/_job.dm
similarity index 88%
rename from code/modules/jobs/job_types/job.dm
rename to code/modules/jobs/job_types/_job.dm
index 9549b6100c..2eeffa8b7a 100644
--- a/code/modules/jobs/job_types/job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -1,240 +1,245 @@
-/datum/job
- //The name of the job
- var/title = "NOPE"
-
- //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access
- var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population)
- var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!)
-
- //Determines who can demote this position
- var/department_head = list()
-
- //Tells the given channels that the given mob is the new department head. See communications.dm for valid channels.
- var/list/head_announce = null
-
- //Bitflags for the job
- var/flag = 0
- var/department_flag = 0
-
- //Players will be allowed to spawn in as jobs that are set to "Station"
- var/faction = "None"
-
- //How many players can be this job
- var/total_positions = 0
-
- //How many players can spawn in as this job
- var/spawn_positions = 0
-
- //How many players have this job
- var/current_positions = 0
-
- //Supervisors, who this person answers to directly
- var/supervisors = ""
-
- //Sellection screen color
- var/selection_color = "#ffffff"
-
-
- //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
- var/req_admin_notify
-
- var/custom_spawn_text
-
- //If you have the use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.)
- var/minimal_player_age = 0
-
- var/outfit = null
-
- var/exp_requirements = 0
-
- var/exp_type = ""
- var/exp_type_department = ""
-
- //The amount of good boy points playing this role will earn you towards a higher chance to roll antagonist next round
- //can be overridden by antag_rep.txt config
- var/antag_rep = 10
-
- var/list/mind_traits // Traits added to the mind of the mob assigned this job
-
- var/list/blacklisted_quirks //list of quirk typepaths blacklisted.
-
-//Only override this proc
-//H is usually a human unless an /equip override transformed it
-/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
- //do actions on H but send messages to M as the key may not have been transferred_yet
- if(mind_traits)
- for(var/t in mind_traits)
- ADD_TRAIT(H.mind, t, JOB_TRAIT)
-
-/datum/job/proc/announce(mob/living/carbon/human/H)
- if(head_announce)
- announce_head(H, head_announce)
-
-/datum/job/proc/override_latejoin_spawn(mob/living/carbon/human/H) //Return TRUE to force latejoining to not automatically place the person in latejoin shuttle/whatever.
- return FALSE
-
-//Used for a special check of whether to allow a client to latejoin as this job.
-/datum/job/proc/special_check_latejoin(client/C)
- return TRUE
-
-/datum/job/proc/GetAntagRep()
- . = CONFIG_GET(keyed_list/antag_rep)[lowertext(title)]
- if(. == null)
- return antag_rep
-
-//Don't override this unless the job transforms into a non-human (Silicons do this for example)
-/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null)
- if(!H)
- return FALSE
-
- if(CONFIG_GET(flag/enforce_human_authority) && (title in GLOB.command_positions))
- if(H.dna.species.id != "human")
- H.set_species(/datum/species/human)
- H.apply_pref_name("human", H.client)
-
- //Equip the rest of the gear
- H.dna.species.before_equip_job(src, H, visualsOnly)
-
- if(outfit_override || outfit)
- H.equipOutfit(outfit_override ? outfit_override : outfit, visualsOnly)
-
- H.dna.species.after_equip_job(src, H, visualsOnly)
-
- if(!visualsOnly && announce)
- announce(H)
-
-/datum/job/proc/get_access()
- if(!config) //Needed for robots.
- return src.minimal_access.Copy()
-
- . = list()
-
- if(CONFIG_GET(flag/jobs_have_minimal_access))
- . = src.minimal_access.Copy()
- else
- . = src.access.Copy()
-
- if(CONFIG_GET(flag/everyone_has_maint_access)) //Config has global maint access set
- . |= list(ACCESS_MAINT_TUNNELS)
-
-/datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
- if(H && GLOB.announcement_systems.len)
- //timer because these should come after the captain announcement
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
-
-//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
-/datum/job/proc/player_old_enough(client/C)
- if(available_in_days(C) == 0)
- return TRUE //Available in 0 days = available right now = player is old enough to play.
- return FALSE
-
-
-/datum/job/proc/available_in_days(client/C)
- if(!C)
- return 0
- if(!CONFIG_GET(flag/use_age_restriction_for_jobs))
- return 0
- if(C.prefs.db_flags & DB_FLAG_EXEMPT)
- return 0
- if(!isnum(C.player_age))
- return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced
- if(!isnum(minimal_player_age))
- return 0
-
- return max(0, minimal_player_age - C.player_age)
-
-/datum/job/proc/config_check()
- return TRUE
-
-/datum/job/proc/map_check()
- return TRUE
-
-
-/datum/outfit/job
- name = "Standard Gear"
-
- var/jobtype = null
-
- uniform = /obj/item/clothing/under/color/grey
- id = /obj/item/card/id
- ears = /obj/item/radio/headset
- belt = /obj/item/pda
- back = /obj/item/storage/backpack
- shoes = /obj/item/clothing/shoes/sneakers/black
-
- var/backpack = /obj/item/storage/backpack
- var/satchel = /obj/item/storage/backpack/satchel
- var/duffelbag = /obj/item/storage/backpack/duffelbag
- var/box = /obj/item/storage/box/survival
-
- var/pda_slot = SLOT_BELT
-
-/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- switch(H.backbag)
- if(GBACKPACK)
- back = /obj/item/storage/backpack //Grey backpack
- if(GSATCHEL)
- back = /obj/item/storage/backpack/satchel //Grey satchel
- if(GDUFFELBAG)
- back = /obj/item/storage/backpack/duffelbag //Grey Duffel bag
- if(LSATCHEL)
- back = /obj/item/storage/backpack/satchel/leather //Leather Satchel
- if(DSATCHEL)
- back = satchel //Department satchel
- if(DDUFFELBAG)
- back = duffelbag //Department duffel bag
- else
- back = backpack //Department backpack
-
- if(box)
- if(!backpack_contents)
- backpack_contents = list()
- backpack_contents.Insert(1, box) // Box always takes a first slot in backpack
- backpack_contents[box] = 1
-
- //converts the uniform string into the path we'll wear, whether it's the skirt or regular variant
- var/holder
- if(H.jumpsuit_style == PREF_SKIRT)
- holder = "[uniform]/skirt"
- if(!text2path(holder))
- holder = "[uniform]"
- else
- holder = "[uniform]"
- uniform = text2path(holder)
-
-/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- if(visualsOnly)
- return
-
- var/datum/job/J = SSjob.GetJobType(jobtype)
- if(!J)
- J = SSjob.GetJob(H.job)
-
- if(H.nameless && J.dresscodecompliant)
- if(J.title in GLOB.command_positions)
- H.real_name = J.title
- else
- H.real_name = "[J.title] #[rand(10000, 99999)]"
-
- var/obj/item/card/id/C = H.wear_id
- if(istype(C))
- C.access = J.get_access()
- shuffle_inplace(C.access) // Shuffle access list to make NTNet passkeys less predictable
- C.registered_name = H.real_name
- C.assignment = J.title
- C.update_label()
- H.sec_hud_set_ID()
-
- var/obj/item/pda/PDA = H.get_item_by_slot(pda_slot)
- if(istype(PDA))
- PDA.owner = H.real_name
- PDA.ownjob = J.title
- PDA.update_label()
-
-/datum/outfit/job/get_chameleon_disguise_info()
- var/list/types = ..()
- types -= /obj/item/storage/backpack //otherwise this will override the actual backpacks
- types += backpack
- types += satchel
- types += duffelbag
- return types
+/datum/job
+ //The name of the job , used for preferences, bans and more. Make sure you know what you're doing before changing this.
+ var/title = "NOPE"
+
+ //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access
+ var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population)
+ var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!)
+
+ //Determines who can demote this position
+ var/department_head = list()
+
+ //Tells the given channels that the given mob is the new department head. See communications.dm for valid channels.
+ var/list/head_announce = null
+
+ //Bitflags for the job
+ var/flag = NONE //Deprecated
+ var/department_flag = NONE //Deprecated
+// var/auto_deadmin_role_flags = NONE
+
+ //Players will be allowed to spawn in as jobs that are set to "Station"
+ var/faction = "None"
+
+ //How many players can be this job
+ var/total_positions = 0
+
+ //How many players can spawn in as this job
+ var/spawn_positions = 0
+
+ //How many players have this job
+ var/current_positions = 0
+
+ //Supervisors, who this person answers to directly
+ var/supervisors = ""
+
+ //Sellection screen color
+ var/selection_color = "#ffffff"
+
+
+ //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
+ var/req_admin_notify
+
+ // This is for Citadel specific tweaks to job notices.
+ var/custom_spawn_text
+
+ //If you have the use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.)
+ var/minimal_player_age = 0
+
+ var/outfit = null
+
+ var/exp_requirements = 0
+
+ var/exp_type = ""
+ var/exp_type_department = ""
+
+ //The amount of good boy points playing this role will earn you towards a higher chance to roll antagonist next round
+ //can be overridden by antag_rep.txt config
+ var/antag_rep = 10
+
+ var/list/mind_traits // Traits added to the mind of the mob assigned this job
+ var/list/blacklisted_quirks //list of quirk typepaths blacklisted.
+
+ var/display_order = JOB_DISPLAY_ORDER_DEFAULT
+
+//Only override this proc
+//H is usually a human unless an /equip override transformed it
+/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
+ //do actions on H but send messages to M as the key may not have been transferred_yet
+ if(mind_traits)
+ for(var/t in mind_traits)
+ ADD_TRAIT(H.mind, t, JOB_TRAIT)
+
+/datum/job/proc/announce(mob/living/carbon/human/H)
+ if(head_announce)
+ announce_head(H, head_announce)
+
+/datum/job/proc/override_latejoin_spawn(mob/living/carbon/human/H) //Return TRUE to force latejoining to not automatically place the person in latejoin shuttle/whatever.
+ return FALSE
+
+//Used for a special check of whether to allow a client to latejoin as this job.
+/datum/job/proc/special_check_latejoin(client/C)
+ return TRUE
+
+/datum/job/proc/GetAntagRep()
+ . = CONFIG_GET(keyed_list/antag_rep)[lowertext(title)]
+ if(. == null)
+ return antag_rep
+
+//Don't override this unless the job transforms into a non-human (Silicons do this for example)
+/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source)
+ if(!H)
+ return FALSE
+
+ if(CONFIG_GET(flag/enforce_human_authority) && (title in GLOB.command_positions))
+ if(H.dna.species.id != "human")
+ H.set_species(/datum/species/human)
+ H.apply_pref_name("human", preference_source)
+
+ //Equip the rest of the gear
+ H.dna.species.before_equip_job(src, H, visualsOnly)
+
+ if(outfit_override || outfit)
+ H.equipOutfit(outfit_override ? outfit_override : outfit, visualsOnly)
+
+ H.dna.species.after_equip_job(src, H, visualsOnly)
+
+ if(!visualsOnly && announce)
+ announce(H)
+
+/datum/job/proc/get_access()
+ if(!config) //Needed for robots.
+ return src.minimal_access.Copy()
+
+ . = list()
+
+ if(CONFIG_GET(flag/jobs_have_minimal_access))
+ . = src.minimal_access.Copy()
+ else
+ . = src.access.Copy()
+
+ if(CONFIG_GET(flag/everyone_has_maint_access)) //Config has global maint access set
+ . |= list(ACCESS_MAINT_TUNNELS)
+
+/datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
+ if(H && GLOB.announcement_systems.len)
+ //timer because these should come after the captain announcement
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
+
+//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
+/datum/job/proc/player_old_enough(client/C)
+ if(available_in_days(C) == 0)
+ return TRUE //Available in 0 days = available right now = player is old enough to play.
+ return FALSE
+
+
+/datum/job/proc/available_in_days(client/C)
+ if(!C)
+ return 0
+ if(!CONFIG_GET(flag/use_age_restriction_for_jobs))
+ return 0
+ if(!SSdbcore.Connect())
+ return 0 //Without a database connection we can't get a player's age so we'll assume they're old enough for all jobs
+ if(C.prefs.db_flags & DB_FLAG_EXEMPT)
+ return 0
+ if(!isnum(minimal_player_age))
+ return 0
+
+ return max(0, minimal_player_age - C.player_age)
+
+/datum/job/proc/config_check()
+ return TRUE
+
+/datum/job/proc/map_check()
+ return TRUE
+
+/datum/job/proc/radio_help_message(mob/M)
+ to_chat(M, "Prefix your message with :h to speak on your department's radio. To see other prefixes, look closely at your headset.")
+
+/datum/outfit/job
+ name = "Standard Gear"
+
+ var/jobtype = null
+
+ uniform = /obj/item/clothing/under/color/grey
+ id = /obj/item/card/id
+ ears = /obj/item/radio/headset
+ belt = /obj/item/pda
+ back = /obj/item/storage/backpack
+ shoes = /obj/item/clothing/shoes/sneakers/black
+ box = /obj/item/storage/box/survival
+
+ var/backpack = /obj/item/storage/backpack
+ var/satchel = /obj/item/storage/backpack/satchel
+ var/duffelbag = /obj/item/storage/backpack/duffelbag
+
+ var/pda_slot = SLOT_BELT
+
+/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ switch(H.backbag)
+ if(GBACKPACK)
+ back = /obj/item/storage/backpack //Grey backpack
+ if(GSATCHEL)
+ back = /obj/item/storage/backpack/satchel //Grey satchel
+ if(GDUFFELBAG)
+ back = /obj/item/storage/backpack/duffelbag //Grey Duffel bag
+ if(LSATCHEL)
+ back = /obj/item/storage/backpack/satchel/leather //Leather Satchel
+ if(DSATCHEL)
+ back = satchel //Department satchel
+ if(DDUFFELBAG)
+ back = duffelbag //Department duffel bag
+ else
+ back = backpack //Department backpack
+
+ //converts the uniform string into the path we'll wear, whether it's the skirt or regular variant
+ var/holder
+ if(H.jumpsuit_style == PREF_SKIRT)
+ holder = "[uniform]/skirt"
+ if(!text2path(holder))
+ holder = "[uniform]"
+ else
+ holder = "[uniform]"
+ uniform = text2path(holder)
+
+/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ if(visualsOnly)
+ return
+
+ var/datum/job/J = SSjob.GetJobType(jobtype)
+ if(!J)
+ J = SSjob.GetJob(H.job)
+
+ if(H.nameless && J.dresscodecompliant)
+ if(J.title in GLOB.command_positions)
+ H.real_name = J.title
+ else
+ H.real_name = "[J.title] #[rand(10000, 99999)]"
+
+ var/obj/item/card/id/C = H.wear_id
+ if(istype(C))
+ C.access = J.get_access()
+ shuffle_inplace(C.access) // Shuffle access list to make NTNet passkeys less predictable
+ C.registered_name = H.real_name
+ C.assignment = J.title
+ C.update_label()
+ H.sec_hud_set_ID()
+
+ var/obj/item/pda/PDA = H.get_item_by_slot(pda_slot)
+ if(istype(PDA))
+ PDA.owner = H.real_name
+ PDA.ownjob = J.title
+ PDA.update_label()
+
+/datum/outfit/job/get_chameleon_disguise_info()
+ var/list/types = ..()
+ types -= /obj/item/storage/backpack //otherwise this will override the actual backpacks
+ types += backpack
+ types += satchel
+ types += duffelbag
+ return types
+
+//Warden and regular officers add this result to their get_access()
+/datum/job/proc/check_config_for_sec_maint()
+ if(CONFIG_GET(flag/security_has_maint_access))
+ return list(ACCESS_MAINT_TUNNELS)
+ return list()
diff --git a/code/modules/jobs/job_types/silicon.dm b/code/modules/jobs/job_types/ai.dm
similarity index 71%
rename from code/modules/jobs/job_types/silicon.dm
rename to code/modules/jobs/job_types/ai.dm
index ab963eb8f3..4bcfab5836 100644
--- a/code/modules/jobs/job_types/silicon.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -1,90 +1,69 @@
-/*
-AI
-*/
-/datum/job/ai
- title = "AI"
- flag = AI_JF
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- selection_color = "#ccffcc"
- supervisors = "your laws"
- req_admin_notify = TRUE
- minimal_player_age = 30
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SILICON
- var/do_special_check = TRUE
-
-/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, outfit_override)
- . = H.AIize(latejoin)
-
-/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
- . = ..()
- if(latejoin)
- var/obj/structure/AIcore/latejoin_inactive/lateJoinCore
- for(var/obj/structure/AIcore/latejoin_inactive/P in GLOB.latejoin_ai_cores)
- if(P.is_available())
- lateJoinCore = P
- GLOB.latejoin_ai_cores -= P
- break
- if(lateJoinCore)
- lateJoinCore.available = FALSE
- H.forceMove(lateJoinCore.loc)
- qdel(lateJoinCore)
- var/mob/living/silicon/ai/AI = H
- AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
- AI.set_core_display_icon(null, M.client)
-
- //we may have been created after our borg
- if(SSticker.current_state == GAME_STATE_SETTING_UP)
- for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
- if(!R.connected_ai)
- R.TryConnectToAI()
-
- if(latejoin)
- announce(AI)
-
-/datum/job/ai/override_latejoin_spawn()
- return TRUE
-
-/datum/job/ai/special_check_latejoin(client/C)
- if(!do_special_check)
- return TRUE
- for(var/i in GLOB.latejoin_ai_cores)
- var/obj/structure/AIcore/latejoin_inactive/LAI = i
- if(istype(LAI))
- if(LAI.is_available())
- return TRUE
- return FALSE
-
-/datum/job/ai/announce(mob/living/silicon/ai/AI)
- . = ..()
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "[AI] has been downloaded to an empty bluespace-networked AI core at [AREACOORD(AI)]."))
-
-/datum/job/ai/config_check()
- return CONFIG_GET(flag/allow_ai)
-
-/*
-Cyborg
-*/
-/datum/job/cyborg
- title = "Cyborg"
- flag = CYBORG
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 0
- spawn_positions = 1
- supervisors = "your laws and the AI" //Nodrak
- selection_color = "#ddffdd"
- minimal_player_age = 21
- exp_requirements = 120
- exp_type = EXP_TYPE_CREW
-
-/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, outfit_override = null)
- return H.Robotize(FALSE, latejoin)
-
-/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M)
- R.updatename(M.client)
- R.gender = NEUTER
+/datum/job/ai
+ title = "AI"
+ flag = AI_JF
+// auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ selection_color = "#ccffcc"
+ supervisors = "your laws"
+ req_admin_notify = TRUE
+ minimal_player_age = 30
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SILICON
+ display_order = JOB_DISPLAY_ORDER_AI
+ var/do_special_check = TRUE
+
+/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
+ if(visualsOnly)
+ CRASH("dynamic preview is unsupported")
+ . = H.AIize(latejoin,preference_source)
+
+/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
+ . = ..()
+ if(latejoin)
+ var/obj/structure/AIcore/latejoin_inactive/lateJoinCore
+ for(var/obj/structure/AIcore/latejoin_inactive/P in GLOB.latejoin_ai_cores)
+ if(P.is_available())
+ lateJoinCore = P
+ GLOB.latejoin_ai_cores -= P
+ break
+ if(lateJoinCore)
+ lateJoinCore.available = FALSE
+ H.forceMove(lateJoinCore.loc)
+ qdel(lateJoinCore)
+ var/mob/living/silicon/ai/AI = H
+ AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
+ AI.set_core_display_icon(null, M.client)
+
+ //we may have been created after our borg
+ if(SSticker.current_state == GAME_STATE_SETTING_UP)
+ for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
+ if(!R.connected_ai)
+ R.TryConnectToAI()
+
+ if(latejoin)
+ announce(AI)
+
+/datum/job/ai/override_latejoin_spawn()
+ return TRUE
+
+/datum/job/ai/special_check_latejoin(client/C)
+ if(!do_special_check)
+ return TRUE
+ for(var/i in GLOB.latejoin_ai_cores)
+ var/obj/structure/AIcore/latejoin_inactive/LAI = i
+ if(istype(LAI))
+ if(LAI.is_available())
+ return TRUE
+ return FALSE
+
+/datum/job/ai/announce(mob/living/silicon/ai/AI)
+ . = ..()
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "[AI] has been downloaded to an empty bluespace-networked AI core at [AREACOORD(AI)]."))
+
+/datum/job/ai/config_check()
+ return CONFIG_GET(flag/allow_ai)
+
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index ce6eea97b0..c04560f849 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -14,7 +14,7 @@ Assistant
minimal_access = list() //See /datum/job/assistant/get_access()
outfit = /datum/outfit/job/assistant
antag_rep = 7
-
+ display_order = JOB_DISPLAY_ORDER_ASSISTANT
/datum/job/assistant/get_access()
if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
new file mode 100644
index 0000000000..93775beca9
--- /dev/null
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -0,0 +1,44 @@
+/datum/job/atmos
+ title = "Atmospheric Technician"
+ flag = ATMOSTECH
+ department_head = list("Chief Engineer")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the chief engineer"
+ selection_color = "#ff9b3d"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/atmos
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
+
+/datum/outfit/job/atmos
+ name = "Atmospheric Technician"
+ jobtype = /datum/job/atmos
+
+ belt = /obj/item/storage/belt/utility/atmostech
+ l_pocket = /obj/item/pda/atmos
+ ears = /obj/item/radio/headset/headset_eng
+ uniform = /obj/item/clothing/under/rank/atmospheric_technician
+ r_pocket = /obj/item/analyzer
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+
+/datum/outfit/job/atmos/rig
+ name = "Atmospheric Technician (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas
+ suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
new file mode 100644
index 0000000000..0ace449757
--- /dev/null
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -0,0 +1,30 @@
+/datum/job/bartender
+ title = "Bartender"
+ flag = BARTENDER
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ exp_type_department = EXP_TYPE_SERVICE // This is so the jobs menu can work properly
+
+ outfit = /datum/outfit/job/bartender
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_BARTENDER
+
+/datum/outfit/job/bartender
+ name = "Bartender"
+ jobtype = /datum/job/bartender
+
+ glasses = /obj/item/clothing/glasses/sunglasses/reagent
+ belt = /obj/item/pda/bar
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/bartender
+ suit = /obj/item/clothing/suit/armor/vest
+ backpack_contents = list(/obj/item/storage/box/beanbag=1,/obj/item/book/granter/action/drink_fling=1)
+ shoes = /obj/item/clothing/shoes/laceup
+
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
new file mode 100644
index 0000000000..e6338d9b0a
--- /dev/null
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -0,0 +1,32 @@
+/datum/job/hydro
+ title = "Botanist"
+ flag = BOTANIST
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+
+ outfit = /datum/outfit/job/botanist
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ display_order = JOB_DISPLAY_ORDER_BOTANIST
+
+/datum/outfit/job/botanist
+ name = "Botanist"
+ jobtype = /datum/job/hydro
+
+ belt = /obj/item/pda/botanist
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/hydroponics
+ suit = /obj/item/clothing/suit/apron
+ gloves =/obj/item/clothing/gloves/botanic_leather
+ suit_store = /obj/item/plant_analyzer
+
+ backpack = /obj/item/storage/backpack/botany
+ satchel = /obj/item/storage/backpack/satchel/hyd
+
+
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
old mode 100755
new mode 100644
index b3c952f536..7e832d6975
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -1,114 +1,66 @@
-/*
-Captain
-*/
-/datum/job/captain
- title = "Captain"
- flag = CAPTAIN
- department_head = list("CentCom")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "Nanotrasen officials and Space law"
- selection_color = "#ccccff"
- req_admin_notify = 1
- minimal_player_age = 14
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/captain
-
- access = list() //See get_access()
- minimal_access = list() //See get_access()
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
-
-/datum/job/captain/get_access()
- return get_all_accesses()
-
-/datum/job/captain/announce(mob/living/carbon/human/H)
- ..()
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.nameless ? "" : "[H.real_name] "]on deck!"))
-
-/datum/outfit/job/captain
- name = "Captain"
- jobtype = /datum/job/captain
-
- id = /obj/item/card/id/gold
- belt = /obj/item/pda/captain
- glasses = /obj/item/clothing/glasses/sunglasses
- ears = /obj/item/radio/headset/heads/captain/alt
- gloves = /obj/item/clothing/gloves/color/captain
- uniform = /obj/item/clothing/under/rank/captain
- suit = /obj/item/clothing/suit/armor/vest/capcarapace
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/caphat
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1)
-
- backpack = /obj/item/storage/backpack/captain
- satchel = /obj/item/storage/backpack/satchel/cap
- duffelbag = /obj/item/storage/backpack/duffelbag/captain
-
- implants = list(/obj/item/implant/mindshield)
- accessory = /obj/item/clothing/accessory/medal/gold/captain
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain)
-
-/datum/outfit/job/captain/hardsuit
- name = "Captain (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas/sechailer
- suit = /obj/item/clothing/suit/space/hardsuit/captain
- suit_store = /obj/item/tank/internals/oxygen
-
-/*
-Head of Personnel
-*/
-/datum/job/hop
- title = "Head of Personnel"
- flag = HOP
- department_head = list("Captain")
- department_flag = CIVILIAN
- head_announce = list(RADIO_CHANNEL_SERVICE)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ddddff"
- req_admin_notify = 1
- minimal_player_age = 10
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SUPPLY
-
- outfit = /datum/outfit/job/hop
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
- ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
- ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
- ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
- ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
- ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
- ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/prosopagnosia, /datum/quirk/insanity)
-
-/datum/outfit/job/hop
- name = "Head of Personnel"
- jobtype = /datum/job/hop
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/hop
- ears = /obj/item/radio/headset/heads/hop
- uniform = /obj/item/clothing/under/rank/head_of_personnel
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/hopcap
- backpack_contents = list(/obj/item/storage/box/ids=1,\
- /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced = 1)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/hop)
+/datum/job/captain
+ title = "Captain"
+ flag = CAPTAIN
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY //:eyes:
+ department_head = list("CentCom")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "Nanotrasen officials and Space law"
+ selection_color = "#aac1ee"
+ req_admin_notify = 1
+ minimal_player_age = 14
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_COMMAND
+
+ outfit = /datum/outfit/job/captain
+
+ access = list() //See get_access()
+ minimal_access = list() //See get_access()
+
+ mind_traits = list(TRAIT_CAPTAIN_METABOLISM)
+// mind_traits = list(TRAIT_DISK_VERIFIER)
+
+ display_order = JOB_DISPLAY_ORDER_CAPTAIN
+
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/job/captain/get_access()
+ return get_all_accesses()
+
+/datum/job/captain/announce(mob/living/carbon/human/H)
+ ..()
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.nameless ? "" : "[H.real_name] "]on deck!"))
+
+/datum/outfit/job/captain
+ name = "Captain"
+ jobtype = /datum/job/captain
+
+ id = /obj/item/card/id/gold
+ belt = /obj/item/pda/captain
+ glasses = /obj/item/clothing/glasses/sunglasses
+ ears = /obj/item/radio/headset/heads/captain/alt
+ gloves = /obj/item/clothing/gloves/color/captain
+ uniform = /obj/item/clothing/under/rank/captain
+ suit = /obj/item/clothing/suit/armor/vest/capcarapace
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/caphat
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1)
+
+ backpack = /obj/item/storage/backpack/captain
+ satchel = /obj/item/storage/backpack/satchel/cap
+ duffelbag = /obj/item/storage/backpack/duffelbag/captain
+
+ implants = list(/obj/item/implant/mindshield)
+ accessory = /obj/item/clothing/accessory/medal/gold/captain
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain)
+
+/datum/outfit/job/captain/hardsuit
+ name = "Captain (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas/sechailer
+ suit = /obj/item/clothing/suit/space/hardsuit/captain
+ suit_store = /obj/item/tank/internals/oxygen
diff --git a/code/modules/jobs/job_types/cargo_service.dm b/code/modules/jobs/job_types/cargo_service.dm
deleted file mode 100644
index 8e24ece655..0000000000
--- a/code/modules/jobs/job_types/cargo_service.dm
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
-Quartermaster
-*/
-/datum/job/qm
- title = "Quartermaster"
- flag = QUARTERMASTER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- head_announce = list(RADIO_CHANNEL_SUPPLY)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#d7b088"
-
- outfit = /datum/outfit/job/quartermaster
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
- minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
-
-/datum/outfit/job/quartermaster
- name = "Quartermaster"
- jobtype = /datum/job/qm
-
- belt = /obj/item/pda/quartermaster
- ears = /obj/item/radio/headset/headset_cargo
- uniform = /obj/item/clothing/under/rank/cargo
- shoes = /obj/item/clothing/shoes/sneakers/brown
- glasses = /obj/item/clothing/glasses/sunglasses
- l_hand = /obj/item/clipboard
-
- chameleon_extras = /obj/item/stamp/qm
-
-/*
-Cargo Technician
-*/
-/datum/job/cargo_tech
- title = "Cargo Technician"
- flag = CARGOTECH
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
-
- outfit = /datum/outfit/job/cargo_tech
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/cargo_tech
- name = "Cargo Technician"
- jobtype = /datum/job/cargo_tech
-
- belt = /obj/item/pda/cargo
- ears = /obj/item/radio/headset/headset_cargo
- uniform = /obj/item/clothing/under/rank/cargotech
- l_hand = /obj/item/export_scanner
-
-/*
-Shaft Miner
-*/
-/datum/job/mining
- title = "Shaft Miner"
- flag = MINER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 3
- supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
- custom_spawn_text = "Remember, you are a miner, not a hunter. Hunting monsters is not a requirement of your job, the only requirement of your job is to provide materials for the station. Don't be afraid to run away if you're inexperienced with fighting the mining area's locals."
-
- outfit = /datum/outfit/job/miner
-
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/miner
- name = "Shaft Miner (Lavaland)"
- jobtype = /datum/job/mining
-
- belt = /obj/item/pda/shaftminer
- ears = /obj/item/radio/headset/headset_cargo/mining
- shoes = /obj/item/clothing/shoes/workboots/mining
- gloves = /obj/item/clothing/gloves/color/black
- uniform = /obj/item/clothing/under/rank/miner/lavaland
- l_pocket = /obj/item/reagent_containers/hypospray/medipen/survival
- r_pocket = /obj/item/storage/bag/ore //causes issues if spawned in backpack
- backpack_contents = list(
- /obj/item/flashlight/seclite=1,\
- /obj/item/kitchen/knife/combat/survival=1,\
- /obj/item/mining_voucher=1,\
- /obj/item/suit_voucher=1,\
- /obj/item/stack/marker_beacon/ten=1)
-
- backpack = /obj/item/storage/backpack/explorer
- satchel = /obj/item/storage/backpack/satchel/explorer
- duffelbag = /obj/item/storage/backpack/duffelbag
- box = /obj/item/storage/box/survival_mining
-
- chameleon_extras = /obj/item/gun/energy/kinetic_accelerator
-
-/datum/outfit/job/miner/asteroid
- name = "Shaft Miner (Asteroid)"
- uniform = /obj/item/clothing/under/rank/miner
- shoes = /obj/item/clothing/shoes/workboots
-
-/datum/outfit/job/miner/equipped
- name = "Shaft Miner (Lavaland + Equipment)"
- suit = /obj/item/clothing/suit/hooded/explorer/standard
- mask = /obj/item/clothing/mask/gas/explorer
- glasses = /obj/item/clothing/glasses/meson
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
- backpack_contents = list(
- /obj/item/flashlight/seclite=1,\
- /obj/item/kitchen/knife/combat/survival=1,
- /obj/item/mining_voucher=1,
- /obj/item/t_scanner/adv_mining_scanner/lesser=1,
- /obj/item/gun/energy/kinetic_accelerator=1,\
- /obj/item/stack/marker_beacon/ten=1)
-
-/datum/outfit/job/miner/equipped/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
- if(istype(H.wear_suit, /obj/item/clothing/suit/hooded))
- var/obj/item/clothing/suit/hooded/S = H.wear_suit
- S.ToggleHood()
-
-/datum/outfit/job/miner/equipped/hardsuit
- name = "Shaft Miner (Equipment + Hardsuit)"
- suit = /obj/item/clothing/suit/space/hardsuit/mining
- mask = /obj/item/clothing/mask/breath
-
-
-/*
-Bartender
-*/
-/datum/job/bartender
- title = "Bartender"
- flag = BARTENDER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
-
- outfit = /datum/outfit/job/bartender
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
-
-
-/datum/outfit/job/bartender
- name = "Bartender"
- jobtype = /datum/job/bartender
-
- glasses = /obj/item/clothing/glasses/sunglasses/reagent
- belt = /obj/item/pda/bar
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/bartender
- suit = /obj/item/clothing/suit/armor/vest
- backpack_contents = list(/obj/item/storage/box/beanbag=1,/obj/item/book/granter/action/drink_fling=1)
- shoes = /obj/item/clothing/shoes/laceup
-
-/*
-Cook
-*/
-/datum/job/cook
- title = "Cook"
- flag = COOK
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
- var/cooks = 0 //Counts cooks amount
-
- outfit = /datum/outfit/job/cook
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/cook
- name = "Cook"
- jobtype = /datum/job/cook
-
- belt = /obj/item/pda/cook
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/chef
- suit = /obj/item/clothing/suit/toggle/chef
- head = /obj/item/clothing/head/chefhat
- mask = /obj/item/clothing/mask/fakemoustache/italian
- backpack_contents = list(/obj/item/sharpener = 1)
-
-/datum/outfit/job/cook/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- var/datum/job/cook/J = SSjob.GetJobType(jobtype)
- if(J) // Fix for runtime caused by invalid job being passed
- if(J.cooks>0)//Cooks
- suit = /obj/item/clothing/suit/apron/chef
- head = /obj/item/clothing/head/soft/mime
- if(!visualsOnly)
- J.cooks++
-
-/datum/outfit/job/cook/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
- var/list/possible_boxes = subtypesof(/obj/item/storage/box/ingredients)
- var/chosen_box = pick(possible_boxes)
- var/obj/item/storage/box/I = new chosen_box(src)
- H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
- var/datum/martial_art/cqc/under_siege/justacook = new
- justacook.teach(H)
-
-/*
-Botanist
-*/
-/datum/job/hydro
- title = "Botanist"
- flag = BOTANIST
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
-
- outfit = /datum/outfit/job/botanist
-
- access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
- // Removed tox and chem access because STOP PISSING OFF THE CHEMIST GUYS
- // Removed medical access because WHAT THE FUCK YOU AREN'T A DOCTOR YOU GROW WHEAT
- // Given Morgue access because they have a viable means of cloning.
-
-
-/datum/outfit/job/botanist
- name = "Botanist"
- jobtype = /datum/job/hydro
-
- belt = /obj/item/pda/botanist
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/hydroponics
- suit = /obj/item/clothing/suit/apron
- gloves =/obj/item/clothing/gloves/botanic_leather
- suit_store = /obj/item/plant_analyzer
-
- backpack = /obj/item/storage/backpack/botany
- satchel = /obj/item/storage/backpack/satchel/hyd
-
-
-/*
-Janitor
-*/
-/datum/job/janitor
- title = "Janitor"
- flag = JANITOR
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#bbe291"
- var/global/janitors = 0
-
- outfit = /datum/outfit/job/janitor
-
- access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/janitor
- name = "Janitor"
- jobtype = /datum/job/janitor
-
- belt = /obj/item/pda/janitor
- ears = /obj/item/radio/headset/headset_srv
- uniform = /obj/item/clothing/under/rank/janitor
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
new file mode 100644
index 0000000000..3ceb29bae2
--- /dev/null
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -0,0 +1,27 @@
+/datum/job/cargo_tech
+ title = "Cargo Technician"
+ flag = CARGOTECH
+ department_head = list("Quartermaster")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 2
+ supervisors = "the quartermaster"
+ selection_color = "#ca8f55"
+
+ outfit = /datum/outfit/job/cargo_tech
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN
+
+/datum/outfit/job/cargo_tech
+ name = "Cargo Technician"
+ jobtype = /datum/job/cargo_tech
+
+ belt = /obj/item/pda/cargo
+ ears = /obj/item/radio/headset/headset_cargo
+ uniform = /obj/item/clothing/under/rank/cargotech
+ l_hand = /obj/item/export_scanner
+
diff --git a/code/modules/jobs/job_types/civilian_chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
similarity index 66%
rename from code/modules/jobs/job_types/civilian_chaplain.dm
rename to code/modules/jobs/job_types/chaplain.dm
index 2d190cfe60..f6648fdf86 100644
--- a/code/modules/jobs/job_types/civilian_chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -1,95 +1,121 @@
-//Due to how large this one is it gets its own file
-/*
-Chaplain
-*/
-/datum/job/chaplain
- title = "Chaplain"
- flag = CHAPLAIN
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/chaplain
-
- access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
- minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
-
-/datum/job/chaplain/after_spawn(mob/living/H, mob/M)
- . = ..()
- if(H.mind)
- H.mind.isholy = TRUE
-
- var/obj/item/storage/book/bible/booze/B = new
-
- if(GLOB.religion)
- B.deity_name = GLOB.deity
- B.name = GLOB.bible_name
- B.icon_state = GLOB.bible_icon_state
- B.item_state = GLOB.bible_item_state
- to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the Chaplain.")
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
- var/nrt = GLOB.holy_weapon_type || /obj/item/nullrod
- var/obj/item/nullrod/N = new nrt(H)
- H.put_in_hands(N)
- return
-
- var/new_religion = "Christianity"
- if(M.client && M.client.prefs.custom_names["religion"])
- new_religion = M.client.prefs.custom_names["religion"]
-
- var/new_deity = "Space Jesus"
- if(M.client && M.client.prefs.custom_names["deity"])
- new_deity = M.client.prefs.custom_names["deity"]
-
- B.deity_name = new_deity
-
-
- switch(lowertext(new_religion))
- if("christianity")
- B.name = pick("The Holy Bible","The Dead Sea Scrolls")
- if("satanism")
- B.name = "The Unholy Bible"
- if("cthulhu")
- B.name = "The Necronomicon"
- if("islam")
- B.name = "Quran"
- if("scientology")
- B.name = pick("The Biography of L. Ron Hubbard","Dianetics")
- if("chaos")
- B.name = "The Book of Lorgar"
- if("imperium")
- B.name = "Uplifting Primer"
- if("toolboxia")
- B.name = "Toolbox Manifesto"
- if("homosexuality")
- B.name = "Guys Gone Wild"
- if("lol", "wtf", "gay", "penis", "ass", "poo", "badmin", "shitmin", "deadmin", "cock", "cocks", "meme", "memes")
- B.name = pick("Woodys Got Wood: The Aftermath", "War of the Cocks", "Sweet Bro and Hella Jef: Expanded Edition")
- H.adjustBrainLoss(100) // starts off retarded as fuck
- if("science")
- B.name = pick("Principle of Relativity", "Quantum Enigma: Physics Encounters Consciousness", "Programming the Universe", "Quantum Physics and Theology", "String Theory for Dummies", "How To: Build Your Own Warp Drive", "The Mysteries of Bluespace", "Playing God: Collector's Edition")
- else
- B.name = "The Holy Book of [new_religion]"
-
- GLOB.religion = new_religion
- GLOB.bible_name = B.name
- GLOB.deity = B.deity_name
-
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
-
- SSblackbox.record_feedback("text", "religion_name", 1, "[new_religion]", 1)
- SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1)
-
-/datum/outfit/job/chaplain
- name = "Chaplain"
- jobtype = /datum/job/chaplain
-
- belt = /obj/item/pda/chaplain
- uniform = /obj/item/clothing/under/rank/chaplain
- backpack_contents = list(/obj/item/camera/spooky = 1)
- backpack = /obj/item/storage/backpack/cultpack
- satchel = /obj/item/storage/backpack/cultpack
+/datum/job/chaplain
+ title = "Chaplain"
+ flag = CHAPLAIN
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/chaplain
+
+ access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
+ minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_CHAPLAIN
+
+
+/datum/job/chaplain/after_spawn(mob/living/H, mob/M)
+ . = ..()
+ if(H.mind)
+ H.mind.isholy = TRUE
+
+ var/obj/item/storage/book/bible/booze/B = new
+
+ if(GLOB.religion)
+ B.deity_name = GLOB.deity
+ B.name = GLOB.bible_name
+ B.icon_state = GLOB.bible_icon_state
+ B.item_state = GLOB.bible_item_state
+ to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the Chaplain.")
+ H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+ var/nrt = GLOB.holy_weapon_type || /obj/item/nullrod
+ var/obj/item/nullrod/N = new nrt(H)
+ H.put_in_hands(N)
+ return
+
+ var/new_religion = DEFAULT_RELIGION
+ if(M.client && M.client.prefs.custom_names["religion"])
+ new_religion = M.client.prefs.custom_names["religion"]
+
+ var/new_deity = DEFAULT_DEITY
+ if(M.client && M.client.prefs.custom_names["deity"])
+ new_deity = M.client.prefs.custom_names["deity"]
+
+ B.deity_name = new_deity
+
+
+ switch(lowertext(new_religion))
+ if("christianity") // DEFAULT_RELIGION
+ B.name = pick("The Holy Bible","The Dead Sea Scrolls")
+ if("buddhism")
+ B.name = "The Sutras"
+ if("clownism","honkmother","honk","honkism","comedy")
+ B.name = pick("The Holy Joke Book", "Just a Prank", "Hymns to the Honkmother")
+ if("chaos")
+ B.name = "The Book of Lorgar"
+ if("cthulhu")
+ B.name = "The Necronomicon"
+ if("hinduism")
+ B.name = "The Vedas"
+ if("homosexuality")
+ B.name = pick("Guys Gone Wild","Coming Out of The Closet")
+ if("imperium")
+ B.name = "Uplifting Primer"
+ if("islam")
+ B.name = "Quran"
+ if("judaism")
+ B.name = "The Torah"
+ if("lampism")
+ B.name = "Fluorescent Incandescence"
+ if("lol", "wtf", "gay", "penis", "ass", "poo", "badmin", "shitmin", "deadmin", "cock", "cocks", "meme", "memes")
+ B.name = pick("Woodys Got Wood: The Aftermath", "War of the Cocks", "Sweet Bro and Hella Jef: Expanded Edition","F.A.T.A.L. Rulebook")
+ H.adjustBrainLoss(100) // starts off retarded as fuck
+ if("monkeyism","apism","gorillism","primatism")
+ B.name = pick("Going Bananas", "Bananas Out For Harambe")
+ if("mormonism")
+ B.name = "The Book of Mormon"
+ if("pastafarianism")
+ B.name = "The Gospel of the Flying Spaghetti Monster"
+ if("rastafarianism","rasta")
+ B.name = "The Holy Piby"
+ if("satanism")
+ B.name = "The Unholy Bible"
+ if("science")
+ B.name = pick("Principle of Relativity", "Quantum Enigma: Physics Encounters Consciousness", "Programming the Universe", "Quantum Physics and Theology", "String Theory for Dummies", "How To: Build Your Own Warp Drive", "The Mysteries of Bluespace", "Playing God: Collector's Edition")
+ if("scientology")
+ B.name = pick("The Biography of L. Ron Hubbard","Dianetics")
+ if("servicianism", "partying")
+ B.name = "The Tenets of Servicia"
+ B.deity_name = pick("Servicia", "Space Bacchus", "Space Dionysus")
+ B.desc = "Happy, Full, Clean. Live it and give it."
+ if("subgenius")
+ B.name = "Book of the SubGenius"
+ if("toolboxia","greytide")
+ B.name = pick("Toolbox Manifesto","iGlove Assistants")
+ if("weeaboo","kawaii")
+ B.name = pick("Fanfiction Compendium","Japanese for Dummies","The Manganomicon","Establishing Your O.T.P")
+ else
+ B.name = "The Holy Book of [new_religion]"
+
+ GLOB.religion = new_religion
+ GLOB.bible_name = B.name
+ GLOB.deity = B.deity_name
+
+ H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+
+ SSblackbox.record_feedback("text", "religion_name", 1, "[new_religion]", 1)
+ SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1)
+
+/datum/outfit/job/chaplain
+ name = "Chaplain"
+ jobtype = /datum/job/chaplain
+
+ belt = /obj/item/pda/chaplain
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/chaplain
+ backpack_contents = list(/obj/item/camera/spooky = 1)
+ backpack = /obj/item/storage/backpack/cultpack
+ satchel = /obj/item/storage/backpack/cultpack
\ No newline at end of file
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
new file mode 100644
index 0000000000..a915d261ed
--- /dev/null
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -0,0 +1,36 @@
+/datum/job/chemist
+ title = "Chemist"
+ flag = CHEMIST
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/chemist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CHEMIST
+
+/datum/outfit/job/chemist
+ name = "Chemist"
+ jobtype = /datum/job/chemist
+
+ glasses = /obj/item/clothing/glasses/science
+ belt = /obj/item/pda/chemist
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/chemist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/chemist
+ backpack = /obj/item/storage/backpack/chemistry
+ satchel = /obj/item/storage/backpack/satchel/chem
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = /obj/item/gun/syringe
+
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
new file mode 100644
index 0000000000..da3f281267
--- /dev/null
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -0,0 +1,64 @@
+/datum/job/chief_engineer
+ title = "Chief Engineer"
+ flag = CHIEF
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = ENGSEC
+ head_announce = list(RADIO_CHANNEL_ENGINEERING)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#ee7400"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_ENGINEERING
+
+ outfit = /datum/outfit/job/ce
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
+ ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
+ ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_CHIEF_ENGINEER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/paraplegic, /datum/quirk/insanity)
+
+/datum/outfit/job/ce
+ name = "Chief Engineer"
+ jobtype = /datum/job/chief_engineer
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/storage/belt/utility/chief/full
+ l_pocket = /obj/item/pda/heads/ce
+ ears = /obj/item/radio/headset/heads/ce
+ uniform = /obj/item/clothing/under/rank/chief_engineer
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hardhat/white
+ gloves = /obj/item/clothing/gloves/color/black/ce
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ chameleon_extras = /obj/item/stamp/ce
+
+/datum/outfit/job/ce/rig
+ name = "Chief Engineer (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/engine/elite
+ shoes = /obj/item/clothing/shoes/magboots/advance
+ suit_store = /obj/item/tank/internals/oxygen
+ glasses = /obj/item/clothing/glasses/meson/engine
+ gloves = /obj/item/clothing/gloves/color/yellow
+ head = null
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
new file mode 100644
index 0000000000..4c7249f048
--- /dev/null
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -0,0 +1,59 @@
+/datum/job/cmo
+ title = "Chief Medical Officer"
+ flag = CMO_JF
+ department_head = list("Captain")
+ department_flag = MEDSCI
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ head_announce = list(RADIO_CHANNEL_MEDICAL)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#509ed1"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_MEDICAL
+
+ outfit = /datum/outfit/job/cmo
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
+ ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
+ ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+
+ display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/cmo
+ name = "Chief Medical Officer"
+ jobtype = /datum/job/cmo
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/cmo
+ l_pocket = /obj/item/pinpointer/crew
+ ears = /obj/item/radio/headset/heads/cmo
+ uniform = /obj/item/clothing/under/rank/chief_medical_officer
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/toggle/labcoat/cmo
+ l_hand = /obj/item/storage/firstaid/regular
+ suit_store = /obj/item/flashlight/pen
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
+
+ backpack = /obj/item/storage/backpack/medic
+ satchel = /obj/item/storage/backpack/satchel/med
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = list(/obj/item/gun/syringe, /obj/item/stamp/cmo)
+
+/datum/outfit/job/cmo/hardsuit
+ name = "Chief Medical Officer (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/medical
+ suit_store = /obj/item/tank/internals/oxygen
+ r_pocket = /obj/item/flashlight/pen
+
diff --git a/code/modules/jobs/job_types/civilian.dm b/code/modules/jobs/job_types/civilian.dm
deleted file mode 100644
index f21ff69e8e..0000000000
--- a/code/modules/jobs/job_types/civilian.dm
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
-Clown
-*/
-/datum/job/clown
- title = "Clown"
- flag = CLOWN
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/clown
-
- access = list(ACCESS_THEATRE)
- minimal_access = list(ACCESS_THEATRE)
-
-/datum/job/clown/after_spawn(mob/living/carbon/human/H, mob/M)
- . = ..()
- H.apply_pref_name("clown", M.client)
-
-/datum/outfit/job/clown
- name = "Clown"
- jobtype = /datum/job/clown
-
- belt = /obj/item/pda/clown
- uniform = /obj/item/clothing/under/rank/clown
- shoes = /obj/item/clothing/shoes/clown_shoes
- mask = /obj/item/clothing/mask/gas/clown_hat
- l_pocket = /obj/item/bikehorn
- backpack_contents = list(
- /obj/item/stamp/clown = 1,
- /obj/item/reagent_containers/spray/waterflower = 1,
- /obj/item/reagent_containers/food/snacks/grown/banana = 1,
- /obj/item/instrument/bikehorn = 1,
- )
-
- implants = list(/obj/item/implant/sad_trombone)
-
- backpack = /obj/item/storage/backpack/clown
- satchel = /obj/item/storage/backpack/clown
- duffelbag = /obj/item/storage/backpack/duffelbag/clown //strangely has a duffel
-
- box = /obj/item/storage/box/hug/survival
-
- chameleon_extras = /obj/item/stamp/clown
-
-
-/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- H.fully_replace_character_name(H.real_name, pick(GLOB.clown_names))
-
-/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- H.dna.add_mutation(CLOWNMUT)
- H.dna.add_mutation(SMILE)
-
-/*
-Mime
-*/
-/datum/job/mime
- title = "Mime"
- flag = MIME
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/mime
-
- access = list(ACCESS_THEATRE)
- minimal_access = list(ACCESS_THEATRE)
-
-/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
- H.apply_pref_name("mime", M.client)
-
-/datum/outfit/job/mime
- name = "Mime"
- jobtype = /datum/job/mime
-
- belt = /obj/item/pda/mime
- uniform = /obj/item/clothing/under/rank/mime
- mask = /obj/item/clothing/mask/gas/mime
- gloves = /obj/item/clothing/gloves/color/white
- head = /obj/item/clothing/head/frenchberet
- suit = /obj/item/clothing/suit/suspenders
- backpack_contents = list(/obj/item/reagent_containers/food/drinks/bottle/bottleofnothing=1)
-
- accessory = /obj/item/clothing/accessory/pocketprotector/cosmetology
- backpack = /obj/item/storage/backpack/mime
- satchel = /obj/item/storage/backpack/mime
-
-
-/datum/outfit/job/mime/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
-
- if(visualsOnly)
- return
-
- if(H.mind)
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null))
- H.mind.miming = 1
-
-/*
-Curator
-*/
-/datum/job/curator
- title = "Curator"
- flag = CURATOR
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
-
- outfit = /datum/outfit/job/curator
-
- access = list(ACCESS_LIBRARY)
- minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION,ACCESS_MINING_STATION)
-
-/datum/outfit/job/curator
- name = "Curator"
- jobtype = /datum/job/curator
-
- belt = /obj/item/pda/curator
- uniform = /obj/item/clothing/under/rank/curator
- l_hand = /obj/item/storage/bag/books
- r_pocket = /obj/item/key/displaycase
- l_pocket = /obj/item/laser_pointer
- accessory = /obj/item/clothing/accessory/pocketprotector/full
- backpack_contents = list(
- /obj/item/melee/curator_whip = 1,
- /obj/item/soapstone = 1,
- /obj/item/barcodescanner = 1
- )
-
-
-/datum/outfit/job/curator/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
-
- if(visualsOnly)
- return
-
- H.grant_all_languages(omnitongue=TRUE)
-/*
-Lawyer
-*/
-/datum/job/lawyer
- title = "Lawyer"
- flag = LAWYER
- department_head = list("Head of Personnel")
- department_flag = CIVILIAN
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the head of personnel"
- selection_color = "#dddddd"
- var/lawyers = 0 //Counts lawyer amount
-
- outfit = /datum/outfit/job/lawyer
-
- access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
- minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
-/datum/outfit/job/lawyer
- name = "Lawyer"
- jobtype = /datum/job/lawyer
-
- belt = /obj/item/pda/lawyer
- ears = /obj/item/radio/headset/headset_sec
- uniform = /obj/item/clothing/under/lawyer/bluesuit
- suit = /obj/item/clothing/suit/toggle/lawyer
- shoes = /obj/item/clothing/shoes/laceup
- l_hand = /obj/item/storage/briefcase/lawyer
- l_pocket = /obj/item/laser_pointer
- r_pocket = /obj/item/clothing/accessory/lawyers_badge
-
- chameleon_extras = /obj/item/stamp/law
-
-
-/datum/outfit/job/lawyer/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- if(visualsOnly)
- return
-
- var/datum/job/lawyer/J = SSjob.GetJobType(jobtype)
- J.lawyers++
- if(J.lawyers>1)
- uniform = /obj/item/clothing/under/lawyer/purpsuit
- suit = /obj/item/clothing/suit/toggle/lawyer/purple
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
new file mode 100644
index 0000000000..d8b88ae871
--- /dev/null
+++ b/code/modules/jobs/job_types/clown.dm
@@ -0,0 +1,58 @@
+/datum/job/clown
+ title = "Clown"
+ flag = CLOWN
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/clown
+
+ access = list(ACCESS_THEATRE)
+ minimal_access = list(ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_CLOWN
+
+
+/datum/job/clown/after_spawn(mob/living/carbon/human/H, mob/M)
+ . = ..()
+ H.apply_pref_name("clown", M.client)
+
+/datum/outfit/job/clown
+ name = "Clown"
+ jobtype = /datum/job/clown
+
+ belt = /obj/item/pda/clown
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/clown
+ shoes = /obj/item/clothing/shoes/clown_shoes
+ mask = /obj/item/clothing/mask/gas/clown_hat
+ l_pocket = /obj/item/bikehorn
+ backpack_contents = list(
+ /obj/item/stamp/clown = 1,
+ /obj/item/reagent_containers/spray/waterflower = 1,
+ /obj/item/reagent_containers/food/snacks/grown/banana = 1,
+ /obj/item/instrument/bikehorn = 1,
+ )
+
+ implants = list(/obj/item/implant/sad_trombone)
+
+ backpack = /obj/item/storage/backpack/clown
+ satchel = /obj/item/storage/backpack/clown
+ duffelbag = /obj/item/storage/backpack/duffelbag/clown //strangely has a duffel
+
+ box = /obj/item/storage/box/hug/survival
+
+ chameleon_extras = /obj/item/stamp/clown
+
+/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+
+ H.fully_replace_character_name(H.real_name, pick(GLOB.clown_names)) //rename the mob AFTER they're equipped so their ID gets updated properly.
+ H.dna.add_mutation(CLOWNMUT)
+ H.dna.add_mutation(SMILE)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
new file mode 100644
index 0000000000..c213d4dffc
--- /dev/null
+++ b/code/modules/jobs/job_types/cook.dm
@@ -0,0 +1,52 @@
+/datum/job/cook
+ title = "Cook"
+ flag = COOK
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ var/cooks = 0 //Counts cooks amount
+
+ outfit = /datum/outfit/job/cook
+
+ access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_COOK
+
+/datum/outfit/job/cook
+ name = "Cook"
+ jobtype = /datum/job/cook
+
+ belt = /obj/item/pda/cook
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/chef
+ suit = /obj/item/clothing/suit/toggle/chef
+ head = /obj/item/clothing/head/chefhat
+ mask = /obj/item/clothing/mask/fakemoustache/italian
+ backpack_contents = list(/obj/item/sharpener = 1)
+
+/datum/outfit/job/cook/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ var/datum/job/cook/J = SSjob.GetJobType(jobtype)
+ if(J) // Fix for runtime caused by invalid job being passed
+ if(J.cooks>0)//Cooks
+ suit = /obj/item/clothing/suit/apron/chef
+ head = /obj/item/clothing/head/soft/mime
+ if(!visualsOnly)
+ J.cooks++
+
+/datum/outfit/job/cook/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+ var/list/possible_boxes = subtypesof(/obj/item/storage/box/ingredients)
+ var/chosen_box = pick(possible_boxes)
+ var/obj/item/storage/box/I = new chosen_box(src)
+ H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
+ var/datum/martial_art/cqc/under_siege/justacook = new
+ justacook.teach(H)
+
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
new file mode 100644
index 0000000000..35fa8483d5
--- /dev/null
+++ b/code/modules/jobs/job_types/curator.dm
@@ -0,0 +1,43 @@
+/datum/job/curator
+ title = "Curator"
+ flag = CURATOR
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/curator
+
+ access = list(ACCESS_LIBRARY)
+ minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
+
+ display_order = JOB_DISPLAY_ORDER_CURATOR
+
+/datum/outfit/job/curator
+ name = "Curator"
+ jobtype = /datum/job/curator
+
+ shoes = /obj/item/clothing/shoes/laceup
+ belt = /obj/item/pda/curator
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/curator
+ l_hand = /obj/item/storage/bag/books
+ r_pocket = /obj/item/key/displaycase
+ l_pocket = /obj/item/laser_pointer
+ accessory = /obj/item/clothing/accessory/pocketprotector/full
+ backpack_contents = list(
+ /obj/item/melee/curator_whip = 1,
+ /obj/item/soapstone = 1,
+ /obj/item/barcodescanner = 1
+ )
+
+/datum/outfit/job/curator/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+
+ if(visualsOnly)
+ return
+
+ H.grant_all_languages(omnitongue=TRUE)
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
new file mode 100644
index 0000000000..29c4c3d833
--- /dev/null
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -0,0 +1,27 @@
+/datum/job/cyborg
+ title = "Cyborg"
+ flag = CYBORG
+// auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 0
+ spawn_positions = 1
+ supervisors = "your laws and the AI" //Nodrak
+ selection_color = "#ddffdd"
+ minimal_player_age = 21
+ exp_requirements = 120
+ exp_type = EXP_TYPE_CREW
+
+ display_order = JOB_DISPLAY_ORDER_CYBORG
+
+/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null)
+ if(visualsOnly)
+ CRASH("dynamic preview is unsupported")
+ return H.Robotize(FALSE, latejoin)
+
+/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M)
+ R.updatename(M.client)
+ R.gender = NEUTER
+
+/datum/job/cyborg/radio_help_message(mob/M)
+ to_chat(M, "Prefix your message with :b to speak with other cyborgs and AI.")
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
new file mode 100644
index 0000000000..27a54fbd1f
--- /dev/null
+++ b/code/modules/jobs/job_types/detective.dm
@@ -0,0 +1,57 @@
+/datum/job/detective
+ title = "Detective"
+ flag = DETECTIVE
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of security"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/detective
+
+ access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_DETECTIVE
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/outfit/job/detective
+ name = "Detective"
+ jobtype = /datum/job/detective
+
+ belt = /obj/item/pda/detective
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/det
+ neck = /obj/item/clothing/neck/tie/black
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/det_suit
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/fedora/det_hat
+ l_pocket = /obj/item/toy/crayon/white
+ r_pocket = /obj/item/lighter
+ backpack_contents = list(/obj/item/storage/box/evidence=1,\
+ /obj/item/detective_scanner=1,\
+ /obj/item/melee/classic_baton=1)
+ mask = /obj/item/clothing/mask/cigarette
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/ballistic/revolver/detective, /obj/item/clothing/glasses/sunglasses)
+
+/datum/outfit/job/detective/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ var/obj/item/clothing/mask/cigarette/cig = H.wear_mask
+ if(istype(cig)) //Some species specfic changes can mess this up (plasmamen)
+ cig.light("")
+
+ if(visualsOnly)
+ return
+
diff --git a/code/modules/jobs/job_types/engineering.dm b/code/modules/jobs/job_types/engineering.dm
deleted file mode 100644
index e65cbab1bd..0000000000
--- a/code/modules/jobs/job_types/engineering.dm
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
-Chief Engineer
-*/
-/datum/job/chief_engineer
- title = "Chief Engineer"
- flag = CHIEF
- department_head = list("Captain")
- department_flag = ENGSEC
- head_announce = list(RADIO_CHANNEL_ENGINEERING)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffeeaa"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_ENGINEERING
-
- outfit = /datum/outfit/job/ce
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA,
- ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
- ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA,
- ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
- ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/paraplegic, /datum/quirk/insanity)
-
-/datum/outfit/job/ce
- name = "Chief Engineer"
- jobtype = /datum/job/chief_engineer
-
- id = /obj/item/card/id/silver
- belt = /obj/item/storage/belt/utility/chief/full
- l_pocket = /obj/item/pda/heads/ce
- ears = /obj/item/radio/headset/heads/ce
- uniform = /obj/item/clothing/under/rank/chief_engineer
- shoes = /obj/item/clothing/shoes/sneakers/brown
- head = /obj/item/clothing/head/hardhat/white
- gloves = /obj/item/clothing/gloves/color/black/ce
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- chameleon_extras = /obj/item/stamp/ce
-
-/datum/outfit/job/ce/rig
- name = "Chief Engineer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/engine/elite
- shoes = /obj/item/clothing/shoes/magboots/advance
- suit_store = /obj/item/tank/internals/oxygen
- glasses = /obj/item/clothing/glasses/meson/engine
- gloves = /obj/item/clothing/gloves/color/yellow
- head = null
- internals_slot = SLOT_S_STORE
-
-
-/*
-Station Engineer
-*/
-/datum/job/engineer
- title = "Station Engineer"
- flag = ENGINEER
- department_head = list("Chief Engineer")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 5
- spawn_positions = 5
- supervisors = "the chief engineer"
- selection_color = "#fff5cc"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/engineer
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/engineer
- name = "Station Engineer"
- jobtype = /datum/job/engineer
-
- belt = /obj/item/storage/belt/utility/full/engi
- l_pocket = /obj/item/pda/engineering
- ears = /obj/item/radio/headset/headset_eng
- uniform = /obj/item/clothing/under/rank/engineer
- shoes = /obj/item/clothing/shoes/workboots
- head = /obj/item/clothing/head/hardhat
- r_pocket = /obj/item/t_scanner
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
-
-/datum/outfit/job/engineer/gloved
- name = "Station Engineer (Gloves)"
- gloves = /obj/item/clothing/gloves/color/yellow
-
-/datum/outfit/job/engineer/gloved/rig
- name = "Station Engineer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/engine
- suit_store = /obj/item/tank/internals/oxygen
- head = null
- internals_slot = SLOT_S_STORE
-
-
-/*
-Atmospheric Technician
-*/
-/datum/job/atmos
- title = "Atmospheric Technician"
- flag = ATMOSTECH
- department_head = list("Chief Engineer")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 3
- spawn_positions = 2
- supervisors = "the chief engineer"
- selection_color = "#fff5cc"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/atmos
-
- access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/atmos
- name = "Atmospheric Technician"
- jobtype = /datum/job/atmos
-
- belt = /obj/item/storage/belt/utility/atmostech
- l_pocket = /obj/item/pda/atmos
- ears = /obj/item/radio/headset/headset_eng
- uniform = /obj/item/clothing/under/rank/atmospheric_technician
- r_pocket = /obj/item/analyzer
-
- backpack = /obj/item/storage/backpack/industrial
- satchel = /obj/item/storage/backpack/satchel/eng
- duffelbag = /obj/item/storage/backpack/duffelbag/engineering
- box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
- backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
-
-/datum/outfit/job/atmos/rig
- name = "Atmospheric Technician (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas
- suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
new file mode 100644
index 0000000000..d7f59ff883
--- /dev/null
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -0,0 +1,35 @@
+/datum/job/geneticist
+ title = "Geneticist"
+ flag = GENETICIST
+ department_head = list("Chief Medical Officer", "Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the chief medical officer and research director"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/geneticist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_GENETICIST
+
+/datum/outfit/job/geneticist
+ name = "Geneticist"
+ jobtype = /datum/job/geneticist
+
+ belt = /obj/item/pda/geneticist
+ ears = /obj/item/radio/headset/headset_medsci
+ uniform = /obj/item/clothing/under/rank/geneticist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/genetics
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/genetics
+ satchel = /obj/item/storage/backpack/satchel/gen
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
new file mode 100644
index 0000000000..e320ce20b4
--- /dev/null
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -0,0 +1,51 @@
+/datum/job/hop
+ title = "Head of Personnel"
+ flag = HOP
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = CIVILIAN
+ head_announce = list(RADIO_CHANNEL_SERVICE)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#3a8529"
+ req_admin_notify = 1
+ minimal_player_age = 10
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SERVICE
+
+ outfit = /datum/outfit/job/hop
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
+ ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
+ ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
+ ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
+ ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
+ ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
+ ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL
+
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/prosopagnosia, /datum/quirk/insanity)
+
+/datum/outfit/job/hop
+ name = "Head of Personnel"
+ jobtype = /datum/job/hop
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/hop
+ ears = /obj/item/radio/headset/heads/hop
+ uniform = /obj/item/clothing/under/rank/head_of_personnel
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hopcap
+ backpack_contents = list(/obj/item/storage/box/ids=1,\
+ /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced = 1)
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/hop)
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
new file mode 100644
index 0000000000..f6b5dbd3ef
--- /dev/null
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -0,0 +1,68 @@
+/datum/job/hos
+ title = "Head of Security"
+ flag = HOS
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY
+ department_head = list("Captain")
+ department_flag = ENGSEC
+ head_announce = list(RADIO_CHANNEL_SECURITY)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#b90000"
+ req_admin_notify = 1
+ minimal_player_age = 14
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SECURITY
+
+ outfit = /datum/outfit/job/hos
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
+ ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
+ ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/insanity)
+
+/datum/outfit/job/hos
+ name = "Head of Security"
+ jobtype = /datum/job/hos
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/hos
+ ears = /obj/item/radio/headset/heads/hos/alt
+ uniform = /obj/item/clothing/under/rank/head_of_security
+ shoes = /obj/item/clothing/shoes/jackboots
+ suit = /obj/item/clothing/suit/armor/hos/trenchcoat
+ gloves = /obj/item/clothing/gloves/color/black/hos
+ head = /obj/item/clothing/head/HoS/beret
+ glasses = /obj/item/clothing/glasses/hud/security/sunglasses
+ suit_store = /obj/item/gun/energy/e_gun
+ r_pocket = /obj/item/assembly/flash/handheld
+ l_pocket = /obj/item/restraints/handcuffs
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/energy/e_gun/hos, /obj/item/stamp/hos)
+
+/datum/outfit/job/hos/hardsuit
+ name = "Head of Security (Hardsuit)"
+
+ mask = /obj/item/clothing/mask/gas/sechailer
+ suit = /obj/item/clothing/suit/space/hardsuit/security/hos
+ suit_store = /obj/item/tank/internals/oxygen
+ backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/gun/energy/e_gun=1)
+
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
new file mode 100644
index 0000000000..d0a06ca0e0
--- /dev/null
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -0,0 +1,27 @@
+/datum/job/janitor
+ title = "Janitor"
+ flag = JANITOR
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#bbe291"
+ var/global/janitors = 0
+
+ outfit = /datum/outfit/job/janitor
+
+ access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_JANITOR
+
+/datum/outfit/job/janitor
+ name = "Janitor"
+ jobtype = /datum/job/janitor
+
+ belt = /obj/item/pda/janitor
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/janitor
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
new file mode 100644
index 0000000000..0b8be52116
--- /dev/null
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -0,0 +1,47 @@
+/datum/job/lawyer
+ title = "Lawyer"
+ flag = LAWYER
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+ var/lawyers = 0 //Counts lawyer amount
+
+ outfit = /datum/outfit/job/lawyer
+
+ access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
+ minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_LAWYER
+
+/datum/outfit/job/lawyer
+ name = "Lawyer"
+ jobtype = /datum/job/lawyer
+
+ belt = /obj/item/pda/lawyer
+ ears = /obj/item/radio/headset/headset_sec
+ uniform = /obj/item/clothing/under/lawyer/bluesuit
+ suit = /obj/item/clothing/suit/toggle/lawyer
+ shoes = /obj/item/clothing/shoes/laceup
+ l_hand = /obj/item/storage/briefcase/lawyer
+ l_pocket = /obj/item/laser_pointer
+ r_pocket = /obj/item/clothing/accessory/lawyers_badge
+
+ chameleon_extras = /obj/item/stamp/law
+
+
+/datum/outfit/job/lawyer/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+
+ var/datum/job/lawyer/J = SSjob.GetJobType(jobtype)
+ J.lawyers++
+ if(J.lawyers>1)
+ uniform = /obj/item/clothing/under/lawyer/purpsuit
+ suit = /obj/item/clothing/suit/toggle/lawyer/purple
diff --git a/code/modules/jobs/job_types/medical.dm b/code/modules/jobs/job_types/medical.dm
deleted file mode 100644
index 9eeb4ab06e..0000000000
--- a/code/modules/jobs/job_types/medical.dm
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
-Chief Medical Officer
-*/
-/datum/job/cmo
- title = "Chief Medical Officer"
- flag = CMO_JF
- department_head = list("Captain")
- department_flag = MEDSCI
- head_announce = list(RADIO_CHANNEL_MEDICAL)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffddf0"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_MEDICAL
-
- outfit = /datum/outfit/job/cmo
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
- ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
- ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
-
-/datum/outfit/job/cmo
- name = "Chief Medical Officer"
- jobtype = /datum/job/cmo
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/cmo
- l_pocket = /obj/item/pinpointer/crew
- ears = /obj/item/radio/headset/heads/cmo
- uniform = /obj/item/clothing/under/rank/chief_medical_officer
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/toggle/labcoat/cmo
- l_hand = /obj/item/storage/firstaid/regular
- suit_store = /obj/item/flashlight/pen
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
-
- backpack = /obj/item/storage/backpack/medic
- satchel = /obj/item/storage/backpack/satchel/med
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
- chameleon_extras = list(/obj/item/gun/syringe, /obj/item/stamp/cmo)
-
-/datum/outfit/job/cmo/hardsuit
- name = "Chief Medical Officer (Hardsuit)"
-
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/medical
- suit_store = /obj/item/tank/internals/oxygen
- r_pocket = /obj/item/flashlight/pen
-
-/*
-Medical Doctor
-*/
-/datum/job/doctor
- title = "Medical Doctor"
- flag = DOCTOR
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 5
- spawn_positions = 3
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
-
- outfit = /datum/outfit/job/doctor
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/doctor
- name = "Medical Doctor"
- jobtype = /datum/job/doctor
-
- belt = /obj/item/pda/medical
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/medical
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat
- l_hand = /obj/item/storage/firstaid/regular
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/medic
- satchel = /obj/item/storage/backpack/satchel/med
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
- chameleon_extras = /obj/item/gun/syringe
-
-/*
-Chemist
-*/
-/datum/job/chemist
- title = "Chemist"
- flag = CHEMIST
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/chemist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/chemist
- name = "Chemist"
- jobtype = /datum/job/chemist
-
- glasses = /obj/item/clothing/glasses/science
- belt = /obj/item/pda/chemist
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/chemist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/chemist
- backpack = /obj/item/storage/backpack/chemistry
- satchel = /obj/item/storage/backpack/satchel/chem
- duffelbag = /obj/item/storage/backpack/duffelbag/med
- l_hand = /obj/item/fermichem/pHbooklet
-
- chameleon_extras = /obj/item/gun/syringe
-
-/*
-Geneticist
-*/
-/datum/job/geneticist
- title = "Geneticist"
- flag = GENETICIST
- department_head = list("Chief Medical Officer", "Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the chief medical officer and research director"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/geneticist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/geneticist
- name = "Geneticist"
- jobtype = /datum/job/geneticist
-
- belt = /obj/item/pda/geneticist
- ears = /obj/item/radio/headset/headset_medsci
- uniform = /obj/item/clothing/under/rank/geneticist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/genetics
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/genetics
- satchel = /obj/item/storage/backpack/satchel/gen
- duffelbag = /obj/item/storage/backpack/duffelbag/med
-
-/*
-Virologist
-*/
-/datum/job/virologist
- title = "Virologist"
- flag = VIROLOGIST
- department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the chief medical officer"
- selection_color = "#ffeef0"
- exp_type = EXP_TYPE_CREW
- exp_requirements = 60
-
- outfit = /datum/outfit/job/virologist
-
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/virologist
- name = "Virologist"
- jobtype = /datum/job/virologist
-
- belt = /obj/item/pda/viro
- ears = /obj/item/radio/headset/headset_med
- uniform = /obj/item/clothing/under/rank/virologist
- mask = /obj/item/clothing/mask/surgical
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/virologist
- suit_store = /obj/item/flashlight/pen
-
- backpack = /obj/item/storage/backpack/virology
- satchel = /obj/item/storage/backpack/satchel/vir
- duffelbag = /obj/item/storage/backpack/duffelbag/med
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
new file mode 100644
index 0000000000..19fa1c7158
--- /dev/null
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -0,0 +1,35 @@
+/datum/job/doctor
+ title = "Medical Doctor"
+ flag = DOCTOR
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 3
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+
+ outfit = /datum/outfit/job/doctor
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR
+
+/datum/outfit/job/doctor
+ name = "Medical Doctor"
+ jobtype = /datum/job/doctor
+
+ belt = /obj/item/pda/medical
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/medical
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat
+ l_hand = /obj/item/storage/firstaid/regular
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/medic
+ satchel = /obj/item/storage/backpack/satchel/med
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+ chameleon_extras = /obj/item/gun/syringe
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
new file mode 100644
index 0000000000..1347da7125
--- /dev/null
+++ b/code/modules/jobs/job_types/mime.dm
@@ -0,0 +1,49 @@
+/datum/job/mime
+ title = "Mime"
+ flag = MIME
+ department_head = list("Head of Personnel")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of personnel"
+ selection_color = "#dddddd"
+
+ outfit = /datum/outfit/job/mime
+
+ access = list(ACCESS_THEATRE)
+ minimal_access = list(ACCESS_THEATRE)
+
+ display_order = JOB_DISPLAY_ORDER_MIME
+
+/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
+ H.apply_pref_name("mime", M.client)
+
+/datum/outfit/job/mime
+ name = "Mime"
+ jobtype = /datum/job/mime
+
+ belt = /obj/item/pda/mime
+ ears = /obj/item/radio/headset/headset_srv
+ uniform = /obj/item/clothing/under/rank/mime
+ mask = /obj/item/clothing/mask/gas/mime
+ gloves = /obj/item/clothing/gloves/color/white
+ head = /obj/item/clothing/head/frenchberet
+ suit = /obj/item/clothing/suit/suspenders
+ backpack_contents = list(/obj/item/reagent_containers/food/drinks/bottle/bottleofnothing=1)
+
+ backpack = /obj/item/storage/backpack/mime
+ satchel = /obj/item/storage/backpack/mime
+
+
+/datum/outfit/job/mime/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+
+ if(visualsOnly)
+ return
+
+ if(H.mind)
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null))
+ H.mind.miming = 1
+
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
new file mode 100644
index 0000000000..49a93026ba
--- /dev/null
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -0,0 +1,41 @@
+/datum/job/qm
+ title = "Quartermaster"
+ flag = QUARTERMASTER
+ department_head = list("Captain")
+ department_flag = CIVILIAN
+ head_announce = list(RADIO_CHANNEL_SUPPLY)
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#a06121"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+ exp_type_department = EXP_TYPE_SUPPLY
+
+ outfit = /datum/outfit/job/quartermaster
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION,
+ ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
+ minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING,
+ ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
+
+ display_order = JOB_DISPLAY_ORDER_QUARTERMASTER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/quartermaster
+ name = "Quartermaster"
+ jobtype = /datum/job/qm
+
+ belt = /obj/item/pda/quartermaster
+ ears = /obj/item/radio/headset/headset_cargo
+ uniform = /obj/item/clothing/under/rank/cargo
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ glasses = /obj/item/clothing/glasses/sunglasses
+ l_hand = /obj/item/clipboard
+
+ chameleon_extras = /obj/item/stamp/qm
+
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
new file mode 100644
index 0000000000..5368ceee64
--- /dev/null
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -0,0 +1,61 @@
+/datum/job/rd
+ title = "Research Director"
+ flag = RD_JF
+// auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
+ department_head = list("Captain")
+ department_flag = MEDSCI
+ head_announce = list(RADIO_CHANNEL_SCIENCE)
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the captain"
+ selection_color = "#7544cc"
+ req_admin_notify = 1
+ minimal_player_age = 7
+ exp_type_department = EXP_TYPE_SCIENCE
+ exp_requirements = 180
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/rd
+
+ access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
+ ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
+ ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
+ minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
+ ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
+ ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
+
+ display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
+
+/datum/outfit/job/rd
+ name = "Research Director"
+ jobtype = /datum/job/rd
+
+ id = /obj/item/card/id/silver
+ belt = /obj/item/pda/heads/rd
+ ears = /obj/item/radio/headset/heads/rd
+ uniform = /obj/item/clothing/under/rank/research_director
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/toggle/labcoat
+ l_hand = /obj/item/clipboard
+ l_pocket = /obj/item/laser_pointer
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
+ chameleon_extras = /obj/item/stamp/rd
+
+/datum/outfit/job/rd/rig
+ name = "Research Director (Hardsuit)"
+
+ l_hand = null
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/rd
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
new file mode 100644
index 0000000000..782b175ad4
--- /dev/null
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -0,0 +1,34 @@
+/datum/job/roboticist
+ title = "Roboticist"
+ flag = ROBOTICIST
+ department_head = list("Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 2
+ spawn_positions = 2
+ supervisors = "the research director"
+ selection_color = "#9574cd"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/roboticist
+
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
+ minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_ROBOTICIST
+
+/datum/outfit/job/roboticist
+ name = "Roboticist"
+ jobtype = /datum/job/roboticist
+
+ belt = /obj/item/storage/belt/utility/full
+ l_pocket = /obj/item/pda/roboticist
+ ears = /obj/item/radio/headset/headset_sci
+ uniform = /obj/item/clothing/under/rank/roboticist
+ suit = /obj/item/clothing/suit/toggle/labcoat
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
+ pda_slot = SLOT_L_STORE
diff --git a/code/modules/jobs/job_types/science.dm b/code/modules/jobs/job_types/science.dm
deleted file mode 100644
index b58f3faa27..0000000000
--- a/code/modules/jobs/job_types/science.dm
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
-Research Director
-*/
-/datum/job/rd
- title = "Research Director"
- flag = RD_JF
- department_head = list("Captain")
- department_flag = MEDSCI
- head_announce = list(RADIO_CHANNEL_SCIENCE)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffddff"
- req_admin_notify = 1
- minimal_player_age = 7
- exp_type_department = EXP_TYPE_SCIENCE
- exp_requirements = 180
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/rd
-
- access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
- ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
- ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
- ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
- minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
- ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
- ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
- ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/insanity)
-
-/datum/outfit/job/rd
- name = "Research Director"
- jobtype = /datum/job/rd
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/rd
- ears = /obj/item/radio/headset/heads/rd
- uniform = /obj/item/clothing/under/rank/research_director
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/toggle/labcoat
- l_hand = /obj/item/clipboard
- l_pocket = /obj/item/laser_pointer
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1)
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
- chameleon_extras = /obj/item/stamp/rd
-
-/datum/outfit/job/rd/rig
- name = "Research Director (Hardsuit)"
-
- l_hand = null
- mask = /obj/item/clothing/mask/breath
- suit = /obj/item/clothing/suit/space/hardsuit/rd
- suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
-
-/*
-Scientist
-*/
-/datum/job/scientist
- title = "Scientist"
- flag = SCIENTIST
- department_head = list("Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 5
- spawn_positions = 3
- supervisors = "the research director"
- selection_color = "#ffeeff"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
-
- outfit = /datum/outfit/job/scientist
-
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
- minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/scientist
- name = "Scientist"
- jobtype = /datum/job/scientist
-
- belt = /obj/item/pda/toxins
- ears = /obj/item/radio/headset/headset_sci
- uniform = /obj/item/clothing/under/rank/scientist
- shoes = /obj/item/clothing/shoes/sneakers/white
- suit = /obj/item/clothing/suit/toggle/labcoat/science
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
-/*
-Roboticist
-*/
-/datum/job/roboticist
- title = "Roboticist"
- flag = ROBOTICIST
- department_head = list("Research Director")
- department_flag = MEDSCI
- faction = "Station"
- total_positions = 2
- spawn_positions = 2
- supervisors = "the research director"
- selection_color = "#ffeeff"
- exp_requirements = 60
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/roboticist
-
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
- minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
-
-/datum/outfit/job/roboticist
- name = "Roboticist"
- jobtype = /datum/job/roboticist
-
- belt = /obj/item/storage/belt/utility/full
- l_pocket = /obj/item/pda/roboticist
- ears = /obj/item/radio/headset/headset_sci
- uniform = /obj/item/clothing/under/rank/roboticist
- suit = /obj/item/clothing/suit/toggle/labcoat
-
- backpack = /obj/item/storage/backpack/science
- satchel = /obj/item/storage/backpack/satchel/tox
-
- pda_slot = SLOT_L_STORE
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
new file mode 100644
index 0000000000..f40a25d6ba
--- /dev/null
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -0,0 +1,33 @@
+/datum/job/scientist
+ title = "Scientist"
+ flag = SCIENTIST
+ department_head = list("Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 3
+ supervisors = "the research director"
+ selection_color = "#9574cd"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/scientist
+
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
+ minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_SCIENTIST
+
+/datum/outfit/job/scientist
+ name = "Scientist"
+ jobtype = /datum/job/scientist
+
+ belt = /obj/item/pda/toxins
+ ears = /obj/item/radio/headset/headset_sci
+ uniform = /obj/item/clothing/under/rank/scientist
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/science
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
diff --git a/code/modules/jobs/job_types/security.dm b/code/modules/jobs/job_types/security.dm
deleted file mode 100644
index 96cedd89ef..0000000000
--- a/code/modules/jobs/job_types/security.dm
+++ /dev/null
@@ -1,346 +0,0 @@
-//Warden and regular officers add this result to their get_access()
-/datum/job/proc/check_config_for_sec_maint()
- if(CONFIG_GET(flag/security_has_maint_access))
- return list(ACCESS_MAINT_TUNNELS)
- return list()
-
-/*
-Head of Security
-*/
-/datum/job/hos
- title = "Head of Security"
- flag = HOS
- department_head = list("Captain")
- department_flag = ENGSEC
- head_announce = list(RADIO_CHANNEL_SECURITY)
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the captain"
- selection_color = "#ffdddd"
- req_admin_notify = 1
- minimal_player_age = 14
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SECURITY
-
- outfit = /datum/outfit/job/hos
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
- ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
- ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
- ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
- ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
-
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/insanity)
-
-/datum/outfit/job/hos
- name = "Head of Security"
- jobtype = /datum/job/hos
-
- id = /obj/item/card/id/silver
- belt = /obj/item/pda/heads/hos
- ears = /obj/item/radio/headset/heads/hos/alt
- uniform = /obj/item/clothing/under/rank/head_of_security
- shoes = /obj/item/clothing/shoes/jackboots
- suit = /obj/item/clothing/suit/armor/hos/trenchcoat
- gloves = /obj/item/clothing/gloves/color/black/hos
- head = /obj/item/clothing/head/HoS/beret
- glasses = /obj/item/clothing/glasses/hud/security/sunglasses
- suit_store = /obj/item/gun/energy/e_gun
- r_pocket = /obj/item/assembly/flash/handheld
- l_pocket = /obj/item/restraints/handcuffs
- backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun/hos, /obj/item/stamp/hos)
-
-/datum/outfit/job/hos/hardsuit
- name = "Head of Security (Hardsuit)"
-
- mask = /obj/item/clothing/mask/gas/sechailer
- suit = /obj/item/clothing/suit/space/hardsuit/security/hos
- suit_store = /obj/item/tank/internals/oxygen
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/gun/energy/e_gun=1)
-
-/*
-Warden
-*/
-/datum/job/warden
- title = "Warden"
- flag = WARDEN
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of security"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/warden
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) //SEE /DATUM/JOB/WARDEN/GET_ACCESS()
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
-
-/datum/job/warden/get_access()
- var/list/L = list()
- L = ..() | check_config_for_sec_maint()
- return L
-
-/datum/outfit/job/warden
- name = "Warden"
- jobtype = /datum/job/warden
-
- belt = /obj/item/pda/warden
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/warden
- shoes = /obj/item/clothing/shoes/jackboots
- suit = /obj/item/clothing/suit/armor/vest/warden/alt
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/warden
- glasses = /obj/item/clothing/glasses/hud/security/sunglasses
- r_pocket = /obj/item/assembly/flash/handheld
- l_pocket = /obj/item/restraints/handcuffs
- suit_store = /obj/item/gun/energy/e_gun/advtaser
- backpack_contents = list(/obj/item/melee/baton/loaded=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = /obj/item/gun/ballistic/shotgun/automatic/combat/compact
-
-/*
-Detective
-*/
-/datum/job/detective
- title = "Detective"
- flag = DETECTIVE
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 1
- spawn_positions = 1
- supervisors = "the head of security"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/detective
-
- access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
-
-/datum/outfit/job/detective
- name = "Detective"
- jobtype = /datum/job/detective
-
- belt = /obj/item/pda/detective
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/det
- shoes = /obj/item/clothing/shoes/sneakers/brown
- suit = /obj/item/clothing/suit/det_suit
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/fedora/det_hat
- l_pocket = /obj/item/toy/crayon/white
- r_pocket = /obj/item/lighter
- backpack_contents = list(/obj/item/storage/box/evidence=1,\
- /obj/item/detective_scanner=1,\
- /obj/item/melee/classic_baton=1)
- mask = /obj/item/clothing/mask/cigarette
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/ballistic/revolver/detective, /obj/item/clothing/glasses/sunglasses)
-
-/datum/outfit/job/detective/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- ..()
- var/obj/item/clothing/mask/cigarette/cig = H.wear_mask
- if(istype(cig)) //Some species specfic changes can mess this up (plasmamen)
- cig.light("")
-
- if(visualsOnly)
- return
-
-/*
-Security Officer
-*/
-/datum/job/officer
- title = "Security Officer"
- flag = OFFICER
- department_head = list("Head of Security")
- department_flag = ENGSEC
- faction = "Station"
- total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
- spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
- supervisors = "the head of security, and the head of your assigned department (if applicable)"
- selection_color = "#ffeeee"
- minimal_player_age = 7
- exp_requirements = 300
- exp_type = EXP_TYPE_CREW
-
- outfit = /datum/outfit/job/security
-
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) //BUT SEE /DATUM/JOB/WARDEN/GET_ACCESS()
-
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
-
-/datum/job/officer/get_access()
- var/list/L = list()
- L |= ..() | check_config_for_sec_maint()
- return L
-
-GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
-
-/datum/job/officer/after_spawn(mob/living/carbon/human/H, mob/M)
- // Assign department security
- var/department
- if(M && M.client && M.client.prefs)
- department = M.client.prefs.prefered_security_department
- if(!LAZYLEN(GLOB.available_depts) || department == "None")
- return
- else if(department in GLOB.available_depts)
- LAZYREMOVE(GLOB.available_depts, department)
- else
- department = pick_n_take(GLOB.available_depts)
- var/ears = null
- var/accessory = null
- var/list/dep_access = null
- var/destination = null
- var/spawn_point = null
- switch(department)
- if(SEC_DEPT_SUPPLY)
- ears = /obj/item/radio/headset/headset_sec/alt/department/supply
- dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION)
- destination = /area/security/checkpoint/supply
- spawn_point = locate(/obj/effect/landmark/start/depsec/supply) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/cargo
- if(SEC_DEPT_ENGINEERING)
- ears = /obj/item/radio/headset/headset_sec/alt/department/engi
- dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE)
- destination = /area/security/checkpoint/engineering
- spawn_point = locate(/obj/effect/landmark/start/depsec/engineering) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/engine
- if(SEC_DEPT_MEDICAL)
- ears = /obj/item/radio/headset/headset_sec/alt/department/med
- dep_access = list(ACCESS_MEDICAL)
- destination = /area/security/checkpoint/medical
- spawn_point = locate(/obj/effect/landmark/start/depsec/medical) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/medblue
- if(SEC_DEPT_SCIENCE)
- ears = /obj/item/radio/headset/headset_sec/alt/department/sci
- dep_access = list(ACCESS_RESEARCH)
- destination = /area/security/checkpoint/science
- spawn_point = locate(/obj/effect/landmark/start/depsec/science) in GLOB.department_security_spawns
- accessory = /obj/item/clothing/accessory/armband/science
-
- if(accessory)
- var/obj/item/clothing/under/U = H.w_uniform
- U.attach_accessory(new accessory)
- if(ears)
- if(H.ears)
- qdel(H.ears)
- H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
-
- var/obj/item/card/id/W = H.wear_id
- W.access |= dep_access
-
- var/teleport = 0
- if(!CONFIG_GET(flag/sec_start_brig))
- if(destination || spawn_point)
- teleport = 1
- if(teleport)
- var/turf/T
- if(spawn_point)
- T = get_turf(spawn_point)
- H.Move(T)
- else
- var/safety = 0
- while(safety < 25)
- T = safepick(get_area_turfs(destination))
- if(T && !H.Move(T))
- safety += 1
- continue
- else
- break
- if(department)
- to_chat(M, "You have been assigned to [department]!")
- else
- to_chat(M, "You have not been assigned to any department. Patrol the halls and help where needed.")
-
-
-
-/datum/outfit/job/security
- name = "Security Officer"
- jobtype = /datum/job/officer
-
- belt = /obj/item/pda/security
- ears = /obj/item/radio/headset/headset_sec/alt
- uniform = /obj/item/clothing/under/rank/security
- gloves = /obj/item/clothing/gloves/color/black
- head = /obj/item/clothing/head/helmet/sec
- suit = /obj/item/clothing/suit/armor/vest/alt
- shoes = /obj/item/clothing/shoes/jackboots
- l_pocket = /obj/item/restraints/handcuffs
- r_pocket = /obj/item/assembly/flash/handheld
- suit_store = /obj/item/gun/energy/e_gun/advtaser
- backpack_contents = list(/obj/item/melee/baton/loaded=1)
-
- backpack = /obj/item/storage/backpack/security
- satchel = /obj/item/storage/backpack/satchel/sec
- duffelbag = /obj/item/storage/backpack/duffelbag/sec
- box = /obj/item/storage/box/security
-
- implants = list(/obj/item/implant/mindshield)
-
- chameleon_extras = list(/obj/item/gun/energy/e_gun/advtaser, /obj/item/clothing/glasses/hud/security/sunglasses, /obj/item/clothing/head/helmet)
- //The helmet is necessary because /obj/item/clothing/head/helmet/sec is overwritten in the chameleon list by the standard helmet, which has the same name and icon state
-
-
-/obj/item/radio/headset/headset_sec/alt/department/Initialize()
- . = ..()
- wires = new/datum/wires/radio(src)
- secure_radio_connections = new
- recalculateChannels()
-
-/obj/item/radio/headset/headset_sec/alt/department/engi
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_eng
-
-/obj/item/radio/headset/headset_sec/alt/department/supply
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_cargo
-
-/obj/item/radio/headset/headset_sec/alt/department/med
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_med
-
-/obj/item/radio/headset/headset_sec/alt/department/sci
- keyslot = new /obj/item/encryptionkey/headset_sec
- keyslot2 = new /obj/item/encryptionkey/headset_sci
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
new file mode 100644
index 0000000000..4f12d6a19c
--- /dev/null
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -0,0 +1,159 @@
+/datum/job/officer
+ title = "Security Officer"
+ flag = OFFICER
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
+ spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
+ supervisors = "the head of security, and the head of your assigned department (if applicable)"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/security
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) // See /datum/job/officer/get_access()
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/job/officer/get_access()
+ var/list/L = list()
+ L |= ..() | check_config_for_sec_maint()
+ return L
+
+GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
+
+/datum/job/officer/after_spawn(mob/living/carbon/human/H, mob/M)
+ . = ..()
+ // Assign department security
+ var/department
+ if(M && M.client && M.client.prefs)
+ department = M.client.prefs.prefered_security_department
+ if(!LAZYLEN(GLOB.available_depts) || department == "None")
+ return
+ else if(department in GLOB.available_depts)
+ LAZYREMOVE(GLOB.available_depts, department)
+ else
+ department = pick_n_take(GLOB.available_depts)
+ var/ears = null
+ var/accessory = null
+ var/list/dep_access = null
+ var/destination = null
+ var/spawn_point = null
+ switch(department)
+ if(SEC_DEPT_SUPPLY)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/supply
+ dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_CARGO)
+ destination = /area/security/checkpoint/supply
+ spawn_point = locate(/obj/effect/landmark/start/depsec/supply) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/cargo
+ if(SEC_DEPT_ENGINEERING)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/engi
+ dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE, ACCESS_ATMOSPHERICS)
+ destination = /area/security/checkpoint/engineering
+ spawn_point = locate(/obj/effect/landmark/start/depsec/engineering) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/engine
+ if(SEC_DEPT_MEDICAL)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/med
+ dep_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING)
+ destination = /area/security/checkpoint/medical
+ spawn_point = locate(/obj/effect/landmark/start/depsec/medical) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/medblue
+ if(SEC_DEPT_SCIENCE)
+ ears = /obj/item/radio/headset/headset_sec/alt/department/sci
+ dep_access = list(ACCESS_RESEARCH, ACCESS_TOX)
+ destination = /area/security/checkpoint/science
+ spawn_point = locate(/obj/effect/landmark/start/depsec/science) in GLOB.department_security_spawns
+ accessory = /obj/item/clothing/accessory/armband/science
+
+ if(accessory)
+ var/obj/item/clothing/under/U = H.w_uniform
+ U.attach_accessory(new accessory)
+ if(ears)
+ if(H.ears)
+ qdel(H.ears)
+ H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
+
+ var/obj/item/card/id/W = H.wear_id
+ W.access |= dep_access
+
+ var/teleport = 0
+ if(!CONFIG_GET(flag/sec_start_brig))
+ if(destination || spawn_point)
+ teleport = 1
+ if(teleport)
+ var/turf/T
+ if(spawn_point)
+ T = get_turf(spawn_point)
+ H.Move(T)
+ else
+ var/safety = 0
+ while(safety < 25)
+ T = safepick(get_area_turfs(destination))
+ if(T && !H.Move(T))
+ safety += 1
+ continue
+ else
+ break
+ if(department)
+ to_chat(M, "You have been assigned to [department]!")
+ else
+ to_chat(M, "You have not been assigned to any department. Patrol the halls and help where needed.")
+
+
+
+/datum/outfit/job/security
+ name = "Security Officer"
+ jobtype = /datum/job/officer
+
+ belt = /obj/item/pda/security
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/security
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/helmet/sec
+ suit = /obj/item/clothing/suit/armor/vest/alt
+ shoes = /obj/item/clothing/shoes/jackboots
+ l_pocket = /obj/item/restraints/handcuffs
+ r_pocket = /obj/item/assembly/flash/handheld
+ suit_store = /obj/item/gun/energy/e_gun/advtaser
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = list(/obj/item/gun/energy/disabler, /obj/item/clothing/glasses/hud/security/sunglasses, /obj/item/clothing/head/helmet)
+ //The helmet is necessary because /obj/item/clothing/head/helmet/sec is overwritten in the chameleon list by the standard helmet, which has the same name and icon state
+
+
+/obj/item/radio/headset/headset_sec/alt/department/Initialize()
+ . = ..()
+ wires = new/datum/wires/radio(src)
+ secure_radio_connections = new
+ recalculateChannels()
+
+/obj/item/radio/headset/headset_sec/alt/department/engi
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_eng
+
+/obj/item/radio/headset/headset_sec/alt/department/supply
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_cargo
+
+/obj/item/radio/headset/headset_sec/alt/department/med
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_med
+
+/obj/item/radio/headset/headset_sec/alt/department/sci
+ keyslot = new /obj/item/encryptionkey/headset_sec
+ keyslot2 = new /obj/item/encryptionkey/headset_sci
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
new file mode 100644
index 0000000000..ef16d8e53f
--- /dev/null
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -0,0 +1,77 @@
+/datum/job/mining
+ title = "Shaft Miner"
+ flag = MINER
+ department_head = list("Quartermaster")
+ department_flag = CIVILIAN
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 3
+ supervisors = "the quartermaster"
+ selection_color = "#ca8f55"
+ custom_spawn_text = "Remember, you are a miner, not a hunter. Hunting monsters is not a requirement of your job, the only requirement of your job is to provide materials for the station. Don't be afraid to run away if you're inexperienced with fighting the mining area's locals."
+
+
+ outfit = /datum/outfit/job/miner
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_SHAFT_MINER
+
+/datum/outfit/job/miner
+ name = "Shaft Miner (Lavaland)"
+ jobtype = /datum/job/mining
+
+ belt = /obj/item/pda/shaftminer
+ ears = /obj/item/radio/headset/headset_cargo/mining
+ shoes = /obj/item/clothing/shoes/workboots/mining
+ gloves = /obj/item/clothing/gloves/color/black
+ uniform = /obj/item/clothing/under/rank/miner/lavaland
+ l_pocket = /obj/item/reagent_containers/hypospray/medipen/survival
+ r_pocket = /obj/item/storage/bag/ore //causes issues if spawned in backpack
+ backpack_contents = list(
+ /obj/item/flashlight/seclite=1,\
+ /obj/item/kitchen/knife/combat/survival=1,\
+ /obj/item/mining_voucher=1,\
+ /obj/item/suit_voucher=1,\
+ /obj/item/stack/marker_beacon/ten=1)
+
+ backpack = /obj/item/storage/backpack/explorer
+ satchel = /obj/item/storage/backpack/satchel/explorer
+ duffelbag = /obj/item/storage/backpack/duffelbag
+ box = /obj/item/storage/box/survival_mining
+
+ chameleon_extras = /obj/item/gun/energy/kinetic_accelerator
+
+/datum/outfit/job/miner/asteroid
+ name = "Shaft Miner (Asteroid)"
+ uniform = /obj/item/clothing/under/rank/miner
+ shoes = /obj/item/clothing/shoes/workboots
+
+/datum/outfit/job/miner/equipped
+ name = "Shaft Miner (Lavaland + Equipment)"
+ suit = /obj/item/clothing/suit/hooded/explorer/standard
+ mask = /obj/item/clothing/mask/gas/explorer
+ glasses = /obj/item/clothing/glasses/meson
+ suit_store = /obj/item/tank/internals/oxygen
+ internals_slot = SLOT_S_STORE
+ backpack_contents = list(
+ /obj/item/flashlight/seclite=1,\
+ /obj/item/kitchen/knife/combat/survival=1,
+ /obj/item/mining_voucher=1,
+ /obj/item/t_scanner/adv_mining_scanner/lesser=1,
+ /obj/item/gun/energy/kinetic_accelerator=1,\
+ /obj/item/stack/marker_beacon/ten=1)
+
+/datum/outfit/job/miner/equipped/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ if(visualsOnly)
+ return
+ if(istype(H.wear_suit, /obj/item/clothing/suit/hooded))
+ var/obj/item/clothing/suit/hooded/S = H.wear_suit
+ S.ToggleHood()
+
+/datum/outfit/job/miner/equipped/hardsuit
+ name = "Shaft Miner (Equipment + Hardsuit)"
+ suit = /obj/item/clothing/suit/space/hardsuit/mining
+ mask = /obj/item/clothing/mask/breath
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
new file mode 100644
index 0000000000..55381549ba
--- /dev/null
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -0,0 +1,54 @@
+/datum/job/engineer
+ title = "Station Engineer"
+ flag = ENGINEER
+ department_head = list("Chief Engineer")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 5
+ spawn_positions = 5
+ supervisors = "the chief engineer"
+ selection_color = "#ff9b3d"
+ exp_requirements = 60
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/engineer
+
+ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER
+
+/datum/outfit/job/engineer
+ name = "Station Engineer"
+ jobtype = /datum/job/engineer
+
+ belt = /obj/item/storage/belt/utility/full/engi
+ l_pocket = /obj/item/pda/engineering
+ ears = /obj/item/radio/headset/headset_eng
+ uniform = /obj/item/clothing/under/rank/engineer
+ shoes = /obj/item/clothing/shoes/workboots
+ head = /obj/item/clothing/head/hardhat
+ r_pocket = /obj/item/t_scanner
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+ box = /obj/item/storage/box/engineer
+ pda_slot = SLOT_L_STORE
+ backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+
+/datum/outfit/job/engineer/gloved
+ name = "Station Engineer (Gloves)"
+ gloves = /obj/item/clothing/gloves/color/yellow
+
+/datum/outfit/job/engineer/gloved/rig
+ name = "Station Engineer (Hardsuit)"
+ mask = /obj/item/clothing/mask/breath
+ suit = /obj/item/clothing/suit/space/hardsuit/engine
+ suit_store = /obj/item/tank/internals/oxygen
+ head = null
+ internals_slot = SLOT_S_STORE
+
+
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
new file mode 100644
index 0000000000..dcc13af627
--- /dev/null
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -0,0 +1,35 @@
+/datum/job/virologist
+ title = "Virologist"
+ flag = VIROLOGIST
+ department_head = list("Chief Medical Officer")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the chief medical officer"
+ selection_color = "#74b5e0"
+ exp_type = EXP_TYPE_CREW
+ exp_requirements = 60
+
+ outfit = /datum/outfit/job/virologist
+
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
+
+ display_order = JOB_DISPLAY_ORDER_VIROLOGIST
+
+/datum/outfit/job/virologist
+ name = "Virologist"
+ jobtype = /datum/job/virologist
+
+ belt = /obj/item/pda/viro
+ ears = /obj/item/radio/headset/headset_med
+ uniform = /obj/item/clothing/under/rank/virologist
+ mask = /obj/item/clothing/mask/surgical
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat/virologist
+ suit_store = /obj/item/flashlight/pen
+
+ backpack = /obj/item/storage/backpack/virology
+ satchel = /obj/item/storage/backpack/satchel/vir
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
new file mode 100644
index 0000000000..a5c16ab5cf
--- /dev/null
+++ b/code/modules/jobs/job_types/warden.dm
@@ -0,0 +1,56 @@
+/datum/job/warden
+ title = "Warden"
+ flag = WARDEN
+// auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
+ department_head = list("Head of Security")
+ department_flag = ENGSEC
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ supervisors = "the head of security"
+ selection_color = "#c02f2f"
+ minimal_player_age = 7
+ exp_requirements = 300
+ exp_type = EXP_TYPE_CREW
+
+ outfit = /datum/outfit/job/warden
+
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) // See /datum/job/warden/get_access()
+
+ mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
+
+ display_order = JOB_DISPLAY_ORDER_WARDEN
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+
+/datum/job/warden/get_access()
+ var/list/L = list()
+ L = ..() | check_config_for_sec_maint()
+ return L
+
+/datum/outfit/job/warden
+ name = "Warden"
+ jobtype = /datum/job/warden
+
+ belt = /obj/item/pda/warden
+ ears = /obj/item/radio/headset/headset_sec/alt
+ uniform = /obj/item/clothing/under/rank/warden
+ shoes = /obj/item/clothing/shoes/jackboots
+ suit = /obj/item/clothing/suit/armor/vest/warden/alt
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/warden
+ glasses = /obj/item/clothing/glasses/hud/security/sunglasses
+ r_pocket = /obj/item/assembly/flash/handheld
+ l_pocket = /obj/item/restraints/handcuffs
+ suit_store = /obj/item/gun/energy/e_gun/advtaser
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
+
+ backpack = /obj/item/storage/backpack/security
+ satchel = /obj/item/storage/backpack/satchel/sec
+ duffelbag = /obj/item/storage/backpack/duffelbag/sec
+ box = /obj/item/storage/box/security
+
+ implants = list(/obj/item/implant/mindshield)
+
+ chameleon_extras = /obj/item/gun/ballistic/shotgun/automatic/combat/compact
+
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 548a734f74..2b8bfa6860 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -4,7 +4,42 @@
set instant = TRUE
set hidden = TRUE
+ client_keysend_amount += 1
+
+ var/cache = client_keysend_amount
+
+ if(keysend_tripped && next_keysend_trip_reset <= world.time)
+ keysend_tripped = FALSE
+
+ if(next_keysend_reset <= world.time)
+ client_keysend_amount = 0
+ next_keysend_reset = world.time + (1 SECONDS)
+
+ //The "tripped" system is to confirm that flooding is still happening after one spike
+ //not entirely sure how byond commands interact in relation to lag
+ //don't want to kick people if a lag spike results in a huge flood of commands being sent
+ if(cache >= MAX_KEYPRESS_AUTOKICK)
+ if(!keysend_tripped)
+ keysend_tripped = TRUE
+ next_keysend_trip_reset = world.time + (2 SECONDS)
+ else
+ log_admin("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ message_admins("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.")
+ QDEL_IN(src, 1)
+ return
+
+ ///Check if the key is short enough to even be a real key
+ if(LAZYLEN(_key) > MAX_KEYPRESS_COMMANDLENGTH)
+ to_chat(src, "Invalid KeyDown detected! You have been disconnected from the server automatically.")
+ log_admin("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ message_admins("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ QDEL_IN(src, 1)
+ return
+ //offset by 1 because the buffer address is 0 indexed because the math was simpler
+ keys_held[current_key_address + 1] = _key
+ //the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
keys_held[_key] = world.time
+ current_key_address = ((current_key_address + 1) % HELD_KEY_BUFFER_LENGTH)
var/movement = SSinput.movement_keys[_key]
if(!(next_move_dir_sub & movement) && !keys_held["Ctrl"])
next_move_dir_add |= movement
@@ -35,7 +70,11 @@
set instant = TRUE
set hidden = TRUE
- keys_held -= _key
+ //Can't just do a remove because it would alter the length of the rolling buffer, instead search for the key then null it out if it exists
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ if(keys_held[i] == _key)
+ keys_held[i] = null
+ break
var/movement = SSinput.movement_keys[_key]
if(!(next_move_dir_add & movement))
next_move_dir_sub |= movement
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 54df252f5d..8433c9bf5a 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -1,9 +1,14 @@
/client
- var/list/keys_held = list() // A list of any keys held currently
- // These next two vars are to apply movement for keypresses and releases made while move delayed.
- // Because discarding that input makes the game less responsive.
- var/next_move_dir_add // On next move, add this dir to the move that would otherwise be done
- var/next_move_dir_sub // On next move, subtract this dir from the move that would otherwise be done
+ /// A rolling buffer of any keys held currently
+ var/list/keys_held = list()
+ ///used to keep track of the current rolling buffer position
+ var/current_key_address = 0
+ /// These next two vars are to apply movement for keypresses and releases made while move delayed.
+ /// Because discarding that input makes the game less responsive.
+ /// On next move, add this dir to the move that would otherwise be done
+ var/next_move_dir_add
+ /// On next move, subtract this dir from the move that would otherwise be done
+ var/next_move_dir_sub
// Set a client's focus to an object and override these procs on that object to let it handle keypresses
@@ -31,6 +36,11 @@
/client/proc/set_macros()
set waitfor = FALSE
+ //Reset and populate the rolling buffer
+ keys_held.Cut()
+ for(var/i in 1 to HELD_KEY_BUFFER_LENGTH)
+ keys_held += null
+
erase_all_macros()
var/list/macro_sets = SSinput.macro_sets
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 699df5de12..ef58943731 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -343,8 +343,11 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
return ..()
/obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user)
- if(density && !(obj_flags & EMAGGED))
- obj_flags |= EMAGGED
+ . = ..()
+ if(!density || obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ return TRUE
/obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list)
if(..())
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index 86499e694b..f98f0755c8 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -169,6 +169,7 @@
locked = FALSE
cut_overlays()
add_overlay("securecrateg")
+ tamperproof = 0 // set explosion chance to zero, so we dont accidently hit it with a multitool and instantly die
else if (input == null || sanitycheck == null || length(input) != codelen)
to_chat(user, "You leave the crate alone.")
else
@@ -213,9 +214,18 @@
return
return ..()
+/obj/structure/closet/secure/loot/dive_into(mob/living/user)
+ if(!locked)
+ return ..()
+ to_chat(user, "That seems like a stupid idea.")
+ return FALSE
+
/obj/structure/closet/crate/secure/loot/emag_act(mob/user)
- if(locked)
- boom(user)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+ if(!locked)
+ return
+ boom(user)
+ return TRUE
/obj/structure/closet/crate/secure/loot/togglelock(mob/user)
if(locked)
@@ -224,4 +234,6 @@
..()
/obj/structure/closet/crate/secure/loot/deconstruct(disassembled = TRUE)
+ if(!locked && disassembled)
+ return ..()
boom()
diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm
index 4a628c5f34..4e54c3e222 100644
--- a/code/modules/mining/laborcamp/laborstacker.dm
+++ b/code/modules/mining/laborcamp/laborstacker.dm
@@ -10,12 +10,10 @@ GLOBAL_LIST(labor_sheet_values)
density = FALSE
var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null
var/machinedir = SOUTH
- var/obj/item/card/id/prisoner/inserted_id
var/obj/machinery/door/airlock/release_door
var/door_tag = "prisonshuttle"
var/obj/item/radio/Radio //needed to send messages to sec radio
-
/obj/machinery/mineral/labor_claim_console/Initialize()
. = ..()
Radio = new/obj/item/radio(src)
@@ -34,18 +32,6 @@ GLOBAL_LIST(labor_sheet_values)
/proc/cmp_sheet_list(list/a, list/b)
return a["value"] - b["value"]
-/obj/machinery/mineral/labor_claim_console/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/card/id/prisoner))
- if(!inserted_id)
- if(!user.transferItemToLoc(I, src))
- return
- inserted_id = I
- to_chat(user, "You insert [I].")
- return
- else
- to_chat(user, "There's an ID inserted already.")
- return ..()
-
/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
@@ -58,14 +44,20 @@ GLOBAL_LIST(labor_sheet_values)
var/can_go_home = FALSE
data["emagged"] = (obj_flags & EMAGGED) ? 1 : 0
- if(inserted_id)
- data["id"] = inserted_id
- data["id_name"] = inserted_id.registered_name
- data["points"] = inserted_id.points
- data["goal"] = inserted_id.goal
- if(check_auth())
+ if(obj_flags & EMAGGED)
can_go_home = TRUE
+ data["status_info"] = "No Prisoner ID detected."
+ var/obj/item/card/id/I = user.get_idcard(TRUE)
+ if(istype(I, /obj/item/card/id/prisoner))
+ var/obj/item/card/id/prisoner/P = I
+ data["id_points"] = P.points
+ if(P.points >= P.goal)
+ can_go_home = TRUE
+ data["status_info"] = "Goal met!"
+ else
+ data["status_info"] = "You are [(P.goal - P.points)] points away."
+
if(stacking_machine)
data["unclaimed_points"] = stacking_machine.points
@@ -78,29 +70,19 @@ GLOBAL_LIST(labor_sheet_values)
if(..())
return
switch(action)
- if("handle_id")
- if(inserted_id)
- if(!usr.get_active_held_item())
- usr.put_in_hands(inserted_id)
- inserted_id = null
- else
- inserted_id.forceMove(get_turf(src))
- inserted_id = null
- else
- var/obj/item/I = usr.get_active_held_item()
- if(istype(I, /obj/item/card/id/prisoner))
- if(!usr.transferItemToLoc(I, src))
- return
- inserted_id = I
if("claim_points")
- inserted_id.points += stacking_machine.points
- stacking_machine.points = 0
- to_chat(usr, "Points transferred.")
+ var/mob/M = usr
+ var/obj/item/card/id/I = M.get_idcard(TRUE)
+ if(istype(I, /obj/item/card/id/prisoner))
+ var/obj/item/card/id/prisoner/P = I
+ P.points += stacking_machine.points
+ stacking_machine.points = 0
+ to_chat(usr, "Points transferred.")
+ else
+ to_chat(usr, "No valid id for point transfer detected.")
if("move_shuttle")
if(!alone_in_area(get_area(src), usr))
to_chat(usr, "Prisoners are only allowed to be released while alone.")
- else if(!check_auth())
- to_chat(usr, "Prisoners are only allowed to be released when they reach their point goal.")
else
switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE))
if(1)
@@ -112,14 +94,9 @@ GLOBAL_LIST(labor_sheet_values)
else
if(!(obj_flags & EMAGGED))
Radio.set_frequency(FREQ_SECURITY)
- Radio.talk_into(src, "[inserted_id.registered_name] has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY)
+ Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY)
to_chat(usr, "Shuttle received message and will be sent shortly.")
-/obj/machinery/mineral/labor_claim_console/proc/check_auth()
- if(obj_flags & EMAGGED)
- return 1 //Shuttle is emagged, let any ol' person through
- return (istype(inserted_id) && inserted_id.points >= inserted_id.goal) //Otherwise, only let them out if the prisoner's reached his quota.
-
/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine()
stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir))
if(stacking_machine)
@@ -128,14 +105,15 @@ GLOBAL_LIST(labor_sheet_values)
qdel(src)
/obj/machinery/mineral/labor_claim_console/emag_act(mob/user)
- if(!(obj_flags & EMAGGED))
- obj_flags |= EMAGGED
- to_chat(user, "PZZTTPFFFT")
-
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(user, "PZZTTPFFFT")
+ return TRUE
/**********************Prisoner Collection Unit**************************/
-
/obj/machinery/mineral/stacking_machine/laborstacker
force_connect = TRUE
var/points = 0 //The unclaimed value of ore stacked.
@@ -151,6 +129,7 @@ GLOBAL_LIST(labor_sheet_values)
return ..()
/**********************Point Lookup Console**************************/
+
/obj/machinery/mineral/labor_points_checker
name = "points checking console"
desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID."
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 7add723a06..5990c70813 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -787,30 +787,30 @@
/obj/item/melee/ghost_sword/process()
ghost_check()
-/obj/item/melee/ghost_sword/proc/ghost_check()
- var/ghost_counter = 0
- var/turf/T = get_turf(src)
- var/list/contents = T.GetAllContents()
- var/mob/dead/observer/current_spirits = list()
- for(var/thing in contents)
- var/atom/A = thing
- A.transfer_observers_to(src)
-
- for(var/i in orbiters?.orbiters)
- if(!isobserver(i))
+/obj/item/melee/ghost_sword/proc/recursive_orbit_collect(atom/A, list/L)
+ for(var/i in A.orbiters?.orbiters)
+ if(!isobserver(i) || (i in L))
continue
+ L |= i
+ recursive_orbit_collect(i, L)
+
+/obj/item/melee/ghost_sword/proc/ghost_check()
+ var/list/mob/dead/observer/current_spirits = list()
+
+ recursive_orbit_collect(src, current_spirits)
+ recursive_orbit_collect(loc, current_spirits) //anything holding us
+
+ for(var/i in spirits - current_spirits)
var/mob/dead/observer/G = i
- ghost_counter++
- G.invisibility = 0
- current_spirits |= G
-
- for(var/mob/dead/observer/G in spirits - current_spirits)
G.invisibility = GLOB.observer_default_invisibility
-
+
+ for(var/i in current_spirits)
+ var/mob/dead/observer/G = i
+ G.invisibility = 0
+
spirits = current_spirits
-
- return ghost_counter
-
+ return length(spirits)
+
/obj/item/melee/ghost_sword/attack(mob/living/target, mob/living/carbon/human/user)
force = 0
var/ghost_counter = ghost_check()
@@ -1335,4 +1335,4 @@
if(2)
new /obj/item/wisp_lantern(src)
if(3)
- new /obj/item/prisoncube(src)
\ No newline at end of file
+ new /obj/item/prisoncube(src)
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index 4605f7d693..6c1a00b020 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -13,7 +13,6 @@
speed_process = TRUE
circuit = /obj/item/circuitboard/machine/ore_redemption
layer = BELOW_OBJ_LAYER
- var/obj/item/card/id/inserted_id
var/points = 0
var/ore_pickup_rate = 15
var/sheet_per_ore = 1
@@ -48,18 +47,23 @@
point_upgrade = point_upgrade_temp
sheet_per_ore = sheet_per_ore_temp
+/obj/machinery/mineral/ore_redemption/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore. Ore pickup speed at [ore_pickup_rate]."
+
/obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/O)
var/datum/component/material_container/mat_container = materials.mat_container
if (!mat_container)
return
- if(istype(O, /obj/item/stack/ore/bluespace_crystal/refined))
+ if(O.refined_type == null)
return
ore_buffer -= O
if(O && O.refined_type)
- points += O.points * point_upgrade * O.amount
+ points += O.points * O.amount
var/material_amount = mat_container.get_item_material_amount(O)
@@ -72,11 +76,8 @@
else
var/mats = O.materials & mat_container.materials
var/amount = O.amount
- var/id = inserted_id && inserted_id.registered_name
- if (id)
- id = " (ID: [id])"
mat_container.insert_item(O, sheet_per_ore) //insert it
- materials.silo_log(src, "smelted", amount, "ores[id]", mats)
+ materials.silo_log(src, "smelted", amount, "ores", mats)
qdel(O)
/obj/machinery/mineral/ore_redemption/proc/can_smelt_alloy(datum/design/D)
@@ -168,15 +169,7 @@
return
if(!powered())
- return
- if(istype(W, /obj/item/card/id))
- var/obj/item/card/id/I = user.get_active_held_item()
- if(istype(I) && !istype(inserted_id))
- if(!user.transferItemToLoc(I, src))
- return
- inserted_id = I
- interact(user)
- return
+ return ..()
if(istype(W, /obj/item/disk/design_disk))
if(user.transferItemToLoc(W, src))
@@ -205,9 +198,6 @@
/obj/machinery/mineral/ore_redemption/ui_data(mob/user)
var/list/data = list()
data["unclaimedPoints"] = points
- if(inserted_id)
- data["hasID"] = TRUE
- data["claimedPoints"] = inserted_id.mining_points
data["materials"] = list()
var/datum/component/material_container/mat_container = materials.mat_container
@@ -245,32 +235,24 @@
return
var/datum/component/material_container/mat_container = materials.mat_container
switch(action)
- if("Eject")
- if(!inserted_id)
- return
- usr.put_in_hands(inserted_id)
- inserted_id = null
- return TRUE
- if("Insert")
- var/obj/item/card/id/I = usr.get_active_held_item()
- if(istype(I))
- if(!usr.transferItemToLoc(I,src))
- return
- inserted_id = I
- else
- to_chat(usr, "Not a valid ID!")
- return TRUE
if("Claim")
- if(inserted_id)
- inserted_id.mining_points += points
- points = 0
+ var/mob/M = usr
+ var/obj/item/card/id/I = M.get_idcard(TRUE)
+ if(points)
+ if(I)
+ I.mining_points += points
+ points = 0
+ else
+ to_chat(usr, "No ID detected.")
+ else
+ to_chat(usr, "No points to claim.")
return TRUE
if("Release")
if(!mat_container)
return
if(materials.on_hold())
to_chat(usr, "Mineral access is on hold, please contact the quartermaster.")
- else if(!check_access(inserted_id) && !allowed(usr)) //Check the ID inside, otherwise check the user
+ else if(!allowed(usr)) //Check the ID inside, otherwise check the user
to_chat(usr, "Required access not found.")
else
var/mat_id = params["id"]
@@ -293,6 +275,7 @@
var/list/mats = list()
mats[mat_id] = MINERAL_MATERIAL_AMOUNT
materials.silo_log(src, "released", -count, "sheets", mats)
+ //Logging deleted for quick coding
return TRUE
if("diskInsert")
var/obj/item/disk/design_disk/disk = usr.get_active_held_item()
@@ -321,7 +304,7 @@
return
var/alloy_id = params["id"]
var/datum/design/alloy = stored_research.isDesignResearchedID(alloy_id)
- if((check_access(inserted_id) || allowed(usr)) && alloy)
+ if((check_access(inserted_scan_id) || allowed(usr)) && alloy)
var/smelt_amount = can_smelt_alloy(alloy)
var/desired = 0
if (params["sheets"])
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index aed90cebdf..38d2e3e100 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -20,6 +20,7 @@
new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
new /datum/data/mining_equipment("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
+ new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
new /datum/data/mining_equipment("Survival Knife", /obj/item/kitchen/knife/combat/survival, 450),
@@ -28,11 +29,10 @@
new /datum/data/mining_equipment("Larger Ore Bag", /obj/item/storage/bag/ore/large, 500),
new /datum/data/mining_equipment("500 Point Transfer Card", /obj/item/card/mining_point_card/mp500, 500),
new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
- new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 750),
- new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 750),
new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/required/kinetic_crusher, 750),
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
+ new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 750),
new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 800),
new /datum/data/mining_equipment("Burn First-Aid Kit", /obj/item/storage/firstaid/fire, 800),
new /datum/data/mining_equipment("First-Aid Kit", /obj/item/storage/firstaid/regular, 800),
@@ -54,7 +54,6 @@
new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500),
new /datum/data/mining_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
- new /datum/data/mining_equipment("Miner Full Replacement", /obj/item/storage/backpack/duffelbag/mining_cloned, 3000),
new /datum/data/mining_equipment("Nanotrasen Minebot", /mob/living/simple_animal/hostile/mining_drone, 800),
new /datum/data/mining_equipment("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
new /datum/data/mining_equipment("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
@@ -69,8 +68,8 @@
new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
+ new /datum/data/mining_equipment("Miner Full Replacement", /obj/item/storage/backpack/duffelbag/mining_cloned, 3000),
new /datum/data/mining_equipment("Premium Accelerator", /obj/item/gun/energy/kinetic_accelerator/premiumka, 8000)
-
)
/datum/data/mining_equipment
@@ -95,60 +94,42 @@
/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user)
. = ..()
- var/dat
- dat +="
"
- if(istype(inserted_id))
- dat += "You have [inserted_id.mining_points] mining points collected. Eject ID. "
- else
- dat += "No ID inserted. Insert ID. "
- dat += "
"
+ var/list/dat = list()
dat += " Equipment point cost list:
"
for(var/datum/data/mining_equipment/prize in prize_list)
dat += "
"
-
- if(!IsGuestKey(src.key))
- if (SSdbcore.Connect())
- var/isadmin = 0
- if(src.client && src.client.holder)
- isadmin = 1
- var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")")
- var/rs = REF(src)
- if(query_get_new_polls.Execute())
- var/newpoll = 0
- if(query_get_new_polls.NextRow())
- newpoll = 1
-
- if(newpoll)
- output += "
"
+
+ if(!IsGuestKey(src.key))
+ if (SSdbcore.Connect())
+ var/isadmin = 0
+ if(src.client && src.client.holder)
+ isadmin = 1
+ var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")")
+ var/rs = REF(src)
+ if(query_get_new_polls.Execute())
+ var/newpoll = 0
+ if(query_get_new_polls.NextRow())
+ newpoll = 1
+
+ if(newpoll)
+ output += "
"
+ for(var/jobcat in categorizedJobs)
+ if(!length(categorizedJobs[jobcat]["jobs"]))
+ continue
+ var/color = categorizedJobs[jobcat]["color"]
+ dat += " "
+ dat += "
"
+ dat += ""
+
+ var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600)
+ popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css')
+ popup.set_content(jointext(dat, ""))
+ popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc
+
+/mob/dead/new_player/proc/create_character(transfer_after)
+ spawning = 1
+ close_spawn_windows()
+
+ var/mob/living/carbon/human/H = new(loc)
+
+ var/frn = CONFIG_GET(flag/force_random_names)
+ if(!frn)
+ frn = jobban_isbanned(src, "appearance")
+ if(QDELETED(src))
+ return
+ if(frn)
+ client.prefs.random_character()
+ client.prefs.real_name = client.prefs.pref_species.random_name(gender,1)
+ client.prefs.copy_to(H)
+ H.dna.update_dna_identity()
+ if(mind)
+ if(transfer_after)
+ mind.late_joiner = TRUE
+ mind.active = 0 //we wish to transfer the key manually
+ mind.transfer_to(H) //won't transfer key since the mind is not active
+
+ H.name = real_name
+
+ . = H
+ new_character = .
+ if(transfer_after)
+ transfer_character()
+
+/mob/dead/new_player/proc/transfer_character()
+ . = new_character
+ if(.)
+ new_character.key = key //Manually transfer the key to log them in
+ new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ new_character = null
+ qdel(src)
+
+/mob/dead/new_player/proc/ViewManifest()
+ var/dat = ""
+ dat += "
Crew Manifest
"
+ dat += GLOB.data_core.get_manifest(OOC = 1)
+
+ src << browse(dat, "window=manifest;size=387x420;can_close=1")
+
+/mob/dead/new_player/Move()
+ return 0
+
+
+/mob/dead/new_player/proc/close_spawn_windows()
+
+ src << browse(null, "window=latechoices") //closes late choices window
+ src << browse(null, "window=playersetup") //closes the player setup window
+ src << browse(null, "window=preferences") //closes job selection
+ src << browse(null, "window=mob_occupation")
+ src << browse(null, "window=latechoices") //closes late job selection
+
+/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
+ A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
+ Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible"
+ Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
+ This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong
+*/
+
+/mob/dead/new_player/proc/check_preferences()
+ if(!client)
+ return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
+ if(client.prefs.joblessrole != RETURNTOLOBBY)
+ return TRUE
+ // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
+ var/has_antags = FALSE
+ if(client.prefs.be_special.len > 0)
+ has_antags = TRUE
+ if(client.prefs.job_preferences.len == 0)
+ if(!ineligible_for_roles)
+ to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.")
+ ineligible_for_roles = TRUE
+ ready = PLAYER_NOT_READY
+ if(has_antags)
+ log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
+ message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
+
+ return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
+ return TRUE
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 6e91b58506..994d082585 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -24,50 +24,35 @@
age = rand(AGE_MIN,AGE_MAX)
/datum/preferences/proc/update_preview_icon()
- // Silicons only need a very basic preview since there is no customization for them.
-// var/wide_icon = FALSE //CITDEL THINGS
-// if(features["taur"] != "None")
-// wide_icon = TRUE
- if(job_engsec_high)
- switch(job_engsec_high)
- if(AI_JF)
- parent.show_character_previews(image('icons/mob/ai.dmi', resolve_ai_icon(preferred_ai_core_display), dir = SOUTH))
- return
- if(CYBORG)
- parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH))
- return
+ // Determine what job is marked as 'High' priority, and dress them up as such.
+ var/datum/job/previewJob
+ var/highest_pref = 0
+ for(var/job in job_preferences)
+ if(job_preferences["[job]"] > highest_pref)
+ previewJob = SSjob.GetJob(job)
+ highest_pref = job_preferences["[job]"]
+
+ if(previewJob)
+ // Silicons only need a very basic preview since there is no customization for them.
+ if(istype(previewJob,/datum/job/ai))
+ parent.show_character_previews(image('icons/mob/ai.dmi', icon_state = resolve_ai_icon(preferred_ai_core_display), dir = SOUTH))
+ return
+ if(istype(previewJob,/datum/job/cyborg))
+ parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH))
+ return
// Set up the dummy for its photoshoot
var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
mannequin.cut_overlays()
+ // Apply the Dummy's preview background first so we properly layer everything else on top of it.
mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER))
copy_to(mannequin)
- // Determine what job is marked as 'High' priority, and dress them up as such.
- var/datum/job/previewJob
- var/highRankFlag = job_civilian_high | job_medsci_high | job_engsec_high
-
- if(job_civilian_low & ASSISTANT)
- previewJob = SSjob.GetJob("Assistant")
- else if(highRankFlag)
- var/highDeptFlag
- if(job_civilian_high)
- highDeptFlag = CIVILIAN
- else if(job_medsci_high)
- highDeptFlag = MEDSCI
- else if(job_engsec_high)
- highDeptFlag = ENGSEC
-
- for(var/datum/job/job in SSjob.occupations)
- if(job.flag == highRankFlag && job.department_flag == highDeptFlag)
- previewJob = job
- break
-
if(previewJob)
- if(current_tab != 2)
- mannequin.job = previewJob.title
- previewJob.equip(mannequin, TRUE)
+ mannequin.job = previewJob.title
+ previewJob.equip(mannequin, TRUE, preference_source = parent)
COMPILE_OVERLAYS(mannequin)
parent.show_character_previews(new /mutable_appearance(mannequin))
unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
+
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
new file mode 100644
index 0000000000..020776a75f
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
@@ -0,0 +1,53 @@
+/datum/sprite_accessory/mam_tails/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+/datum/sprite_accessory/mam_tails_animated/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+/datum/sprite_accessory/mam_body_markings/shark/datashark
+ name = "DataShark"
+ icon_state = "datashark"
+ ckeys_allowed = list("rubyflamewing")
+
+//Sabresune
+/datum/sprite_accessory/mam_ears/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_tails/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+/datum/sprite_accessory/mam_tails_animated/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+/datum/sprite_accessory/mam_body_markings/sabresune
+ name = "Sabresune"
+ icon_state = "sabresune"
+ ckeys_allowed = list("poojawa")
+
+//Lunasune
+/datum/sprite_accessory/mam_ears/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
+
+/datum/sprite_accessory/mam_tails/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
+
+/datum/sprite_accessory/mam_tails_animated/lunasune
+ name = "lunasune"
+ icon_state = "lunasune"
+ ckeys_allowed = list("invader4352")
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
index 5e24d0630b..dd66f68e5d 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
@@ -61,6 +61,17 @@
var/dimension_y = 32
var/center = FALSE //Should we center the sprite?
+ //Special / holdover traits for Citadel specific sprites.
+ var/extra = FALSE
+ var/extra_color_src = MUTCOLORS2 //The color source for the extra overlay.
+ var/extra_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ var/extra2 = FALSE
+ var/extra2_color_src = MUTCOLORS3
+ var/extra2_icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+ //for snowflake/donor specific sprites
+ var/list/ckeys_allowed
+
/datum/sprite_accessory/underwear
icon = 'icons/mob/underwear.dmi'
var/has_color = FALSE
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
new file mode 100644
index 0000000000..6c0659f851
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
@@ -0,0 +1,53 @@
+
+/******************************************
+*********** Xeno Dorsal Tubes *************
+*******************************************/
+/datum/sprite_accessory/xeno_dorsal
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_dorsal/standard
+ name = "Standard"
+ icon_state = "standard"
+
+/datum/sprite_accessory/xeno_dorsal/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/xeno_dorsal/down
+ name = "Dorsal Down"
+ icon_state = "down"
+
+/******************************************
+************* Xeno Tails ******************
+*******************************************/
+/datum/sprite_accessory/xeno_tail
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_tail/none
+ name = "None"
+
+/datum/sprite_accessory/xeno_tail/standard
+ name = "Xenomorph Tail"
+ icon_state = "xeno"
+
+/******************************************
+************* Xeno Heads ******************
+*******************************************/
+/datum/sprite_accessory/xeno_head
+ icon = 'modular_citadel/icons/mob/xeno_parts_greyscale.dmi'
+
+/datum/sprite_accessory/xeno_head/standard
+ name = "Standard"
+ icon_state = "standard"
+
+/datum/sprite_accessory/xeno_head/royal
+ name = "royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/xeno_head/hollywood
+ name = "hollywood"
+ icon_state = "hollywood"
+
+/datum/sprite_accessory/xeno_head/warrior
+ name = "warrior"
+ icon_state = "warrior"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
index 6bce18d7ce..2f1d48cfa7 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
@@ -1,6 +1,6 @@
-//////////.//////////////////
-// MutantParts Definitions //
-/////////////////////////////
+/******************************************
+************* Lizard Markings *************
+*******************************************/
/datum/sprite_accessory/body_markings
icon = 'icons/mob/mutant_bodyparts.dmi'
@@ -22,4 +22,271 @@
/datum/sprite_accessory/body_markings/lbelly
name = "Light Belly"
icon_state = "lbelly"
- gender_specific = 1
\ No newline at end of file
+ gender_specific = 1
+
+/******************************************
+************ Furry Markings ***************
+*******************************************/
+
+// These are all color matrixed and applied per-limb by default. you MUST comply with this if you want to have your markings work --Pooj
+// use the HumanScissors tool to break your sprite up into the zones easier.
+// Although Byond supposedly doesn't have an icon limit anymore of 512 states after 512.1478, just be careful about too many additions.
+
+/datum/sprite_accessory/mam_body_markings
+ extra = FALSE
+ extra2 = FALSE
+ color_src = MATRIXED
+ gender_specific = 0
+ icon = 'modular_citadel/icons/mob/mam_markings.dmi'
+
+/datum/sprite_accessory/mam_body_markings/none
+ name = "None"
+ icon_state = "none"
+ ckeys_allowed = list("yousshouldnteverbeseeingthisyoumeme")
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/plain
+ name = "Plain"
+ icon_state = "plain"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/redpanda
+ name = "Redpanda"
+ icon_state = "redpanda"
+
+/datum/sprite_accessory/mam_body_markings/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/belly
+ name = "Belly"
+ icon_state = "belly"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/bellyslim
+ name = "Bellyslim"
+ icon_state = "bellyslim"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/corgi
+ name = "Corgi"
+ icon_state = "corgi"
+
+/datum/sprite_accessory/mam_body_markings/cow
+ name = "Bovine"
+ icon_state = "bovine"
+
+/datum/sprite_accessory/mam_body_markings/corvid
+ name = "Corvid"
+ icon_state = "corvid"
+
+/datum/sprite_accessory/mam_body_markings/dalmation
+ name = "Dalmation"
+ icon_state = "dalmation"
+
+/datum/sprite_accessory/mam_body_markings/deer
+ name = "Deer"
+ icon_state = "deer"
+
+/datum/sprite_accessory/mam_body_markings/dog
+ name = "Dog"
+ icon_state = "dog"
+
+/datum/sprite_accessory/mam_body_markings/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_body_markings/fennec
+ name = "Fennec"
+ icon_state = "Fennec"
+
+/datum/sprite_accessory/mam_body_markings/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_body_markings/frog
+ name = "Frog"
+ icon_state = "frog"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/goat
+ name = "Goat"
+ icon_state = "goat"
+
+/datum/sprite_accessory/mam_body_markings/handsfeet
+ name = "Handsfeet"
+ icon_state = "handsfeet"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_body_markings/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_body_markings/hyena
+ name = "Hyena"
+ icon_state = "hyena"
+
+/datum/sprite_accessory/mam_body_markings/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_body_markings/insect
+ name = "Insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/otie
+ name = "Otie"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_body_markings/otter
+ name = "Otter"
+ icon_state = "otter"
+
+/datum/sprite_accessory/mam_body_markings/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_body_markings/panther
+ name = "Panther"
+ icon_state = "panther"
+
+/datum/sprite_accessory/mam_body_markings/possum
+ name = "Possum"
+ icon_state = "possum"
+
+/datum/sprite_accessory/mam_body_markings/raccoon
+ name = "Raccoon"
+ icon_state = "raccoon"
+
+/datum/sprite_accessory/mam_body_markings/pede
+ name = "Scolipede"
+ icon_state = "scolipede"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_body_markings/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_body_markings/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_body_markings/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_body_markings/tajaran
+ name = "Tajaran"
+ icon_state = "tajaran"
+
+/datum/sprite_accessory/mam_body_markings/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_body_markings/turian
+ name = "Turian"
+ icon_state = "turian"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/mam_body_markings/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_body_markings/xeno
+ name = "Xeno"
+ icon_state = "xeno"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/******************************************
+************* Insect Markings *************
+*******************************************/
+
+/datum/sprite_accessory/insect_fluff
+ icon = 'icons/mob/wings.dmi'
+ color_src = 0
+
+/datum/sprite_accessory/insect_fluff/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/insect_fluff/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/insect_fluff/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/insect_fluff/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/insect_fluff/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/insect_fluff/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/insect_fluff/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/insect_fluff/punished
+ name = "Burnt Off"
+ icon_state = "punished"
+
+/datum/sprite_accessory/insect_fluff/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/insect_fluff/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/insect_fluff/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/insect_fluff/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/insect_fluff/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/insect_fluff/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/insect_fluff/colored
+ name = "Colored (Hair)"
+ icon_state = "snow"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_fluff/colored1
+ name = "Colored (Primary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/insect_fluff/colored2
+ name = "Colored (Secondary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/insect_fluff/colored3
+ name = "Colored (Tertiary)"
+ icon_state = "snow"
+ color_src = MUTCOLORS3
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index 163f8370a2..1496ca030a 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -5,8 +5,295 @@
name = "None"
icon_state = "none"
+/******************************************
+*************** Human Ears ****************
+*******************************************/
+
+/datum/sprite_accessory/ears/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/bear
+ name = "Bear"
+ icon_state = "bear"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolf
+ name = "Big Wolf"
+ icon_state = "bigwolf"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfinner
+ name = "Big Wolf (ALT)"
+ icon_state = "bigwolfinner"
+ hasinner = 1
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfdark
+ name = "Dark Big Wolf"
+ icon_state = "bigwolfdark"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/bigwolfinnerdark
+ name = "Dark Big Wolf (ALT)"
+ icon_state = "bigwolfinnerdark"
+ hasinner = 1
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
/datum/sprite_accessory/ears/cat
name = "Cat"
icon_state = "cat"
hasinner = 1
- color_src = HAIR
\ No newline at end of file
+ color_src = HAIR
+
+/datum/sprite_accessory/ears/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/curled
+ name = "Curled Horn"
+ icon_state = "horn1"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/ears/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/elf
+ name = "Elf"
+ icon_state = "elf"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = SKINTONE
+
+/datum/sprite_accessory/ears/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/fox
+ name = "Fox"
+ icon_state = "fox"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/jellyfish
+ name = "Jellyfish"
+ icon_state = "jellyfish"
+ color_src = HAIR
+
+/datum/sprite_accessory/ears/lab
+ name = "Dog, Floppy"
+ icon_state = "lab"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/murid
+ name = "Murid"
+ icon_state = "murid"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+/datum/sprite_accessory/ears/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/ears/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
+
+/******************************************
+*************** Furry Ears ****************
+*******************************************/
+
+/datum/sprite_accessory/mam_ears
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/mam_ears/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/mam_ears/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_ears/bear
+ name = "Bear"
+ icon_state = "bear"
+
+/datum/sprite_accessory/mam_ears/bigwolf
+ name = "Big Wolf"
+ icon_state = "bigwolf"
+
+/datum/sprite_accessory/mam_ears/bigwolfinner
+ name = "Big Wolf (ALT)"
+ icon_state = "bigwolfinner"
+ hasinner = 1
+
+/datum/sprite_accessory/mam_ears/bigwolfdark
+ name = "Dark Big Wolf"
+ icon_state = "bigwolfdark"
+
+/datum/sprite_accessory/mam_ears/bigwolfinnerdark
+ name = "Dark Big Wolf (ALT)"
+ icon_state = "bigwolfinnerdark"
+ hasinner = 1
+
+/datum/sprite_accessory/mam_ears/cat
+ name = "Cat"
+ icon_state = "cat"
+ hasinner = 1
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_ears/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_ears/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_ears/curled
+ name = "Curled Horn"
+ icon_state = "horn1"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_ears/deer
+ name = "Deer"
+ icon_state = "deer"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_ears/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+
+/datum/sprite_accessory/mam_ears/elf
+ name = "Elf"
+ icon_state = "elf"
+ color_src = MUTCOLORS3
+
+
+/datum/sprite_accessory/mam_ears/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+
+/datum/sprite_accessory/mam_ears/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_ears/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_ears/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_ears/husky
+ name = "Husky"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_ears/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_ears/jellyfish
+ name = "Jellyfish"
+ icon_state = "jellyfish"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_ears/lab
+ name = "Dog, Long"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_ears/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_ears/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_ears/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_ears/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_ears/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_ears/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_ears/skunk
+ name = "skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_ears/wolf
+ name = "Wolf"
+ icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
index 3566f3dea5..d11299fd5b 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
@@ -86,4 +86,45 @@
/datum/sprite_accessory/facial_hair/elvis
name = "Sideburns (Elvis)"
- icon_state = "facial_elvis"
\ No newline at end of file
+ icon_state = "facial_elvis"
+
+#define VFACE(_name, new_state) /datum/sprite_accessory/facial_hair/##new_state/icon_state=#new_state;;/datum/sprite_accessory/facial_hair/##new_state/name= #_name + " (Virgo)"
+VFACE("Watson", facial_watson_s)
+VFACE("Chaplin", facial_chaplin_s)
+VFACE("Fullbeard", facial_fullbeard_s)
+VFACE("Vandyke", facial_vandyke_s)
+VFACE("Elvis", facial_elvis_s)
+VFACE("Abe", facial_abe_s)
+VFACE("Chin", facial_chin_s)
+VFACE("GT", facial_gt_s)
+VFACE("Hip", facial_hip_s)
+VFACE("Hogan", facial_hogan_s)
+VFACE("Selleck", facial_selleck_s)
+VFACE("Neckbeard", facial_neckbeard_s)
+VFACE("Longbeard", facial_longbeard_s)
+VFACE("Dwarf", facial_dwarf_s)
+VFACE("Sideburn", facial_sideburn_s)
+VFACE("Mutton", facial_mutton_s)
+VFACE("Moustache", facial_moustache_s)
+VFACE("Pencilstache", facial_pencilstache_s)
+VFACE("Goatee", facial_goatee_s)
+VFACE("Smallstache", facial_smallstache_s)
+VFACE("Volaju", facial_volaju_s)
+VFACE("3 O\'clock", facial_3oclock_s)
+VFACE("5 O\'clock", facial_5oclock_s)
+VFACE("7 O\'clock", facial_7oclock_s)
+VFACE("5 O\'clock Moustache", facial_5oclockmoustache_s)
+VFACE("7 O\'clock", facial_7oclockmoustache_s)
+VFACE("Walrus", facial_walrus_s)
+VFACE("Muttonmus", facial_muttonmus_s)
+VFACE("Wise", facial_wise_s)
+VFACE("Martial Artist", facial_martialartist_s)
+VFACE("Dorsalfnil", facial_dorsalfnil_s)
+VFACE("Hornadorns", facial_hornadorns_s)
+VFACE("Spike", facial_spike_s)
+VFACE("Chinhorns", facial_chinhorns_s)
+VFACE("Cropped Fullbeard", facial_croppedfullbeard_s)
+VFACE("Chinless Beard", facial_chinlessbeard_s)
+VFACE("Moonshiner", facial_moonshiner_s)
+VFACE("Tribearder", facial_tribearder_s)
+#undef VFACE
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
index f8d8d26328..abcc90c0ee 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
@@ -461,4 +461,163 @@
/datum/sprite_accessory/hair/longestalt
name = "Very Long with Fringe"
- icon_state = "hair_vlongfringe"
\ No newline at end of file
+ icon_state = "hair_vlongfringe"
+
+/*************** VIRGO PORTED HAIRS ****************************/
+#define VHAIR(_name, new_state) /datum/sprite_accessory/hair/##new_state/icon_state=#new_state;/datum/sprite_accessory/hair/##new_state/name = #_name + " (Virgo)"
+//VIRGO PORTED HAIRS
+VHAIR("Short Hair Rosa", hair_rosa_s)
+VHAIR("Short Hair 80s", hair_80s_s)
+VHAIR("Long Bedhead", hair_long_bedhead_s)
+VHAIR("Dave", hair_dave_s)
+VHAIR("Country", hair_country_s)
+VHAIR("Shy", hair_shy_s)
+VHAIR("Unshaven Mohawk", hair_unshaven_mohawk_s)
+VHAIR("Manbun", hair_manbun_s)
+VHAIR("Longer Bedhead", hair_longer_bedhead_s)
+VHAIR("Ponytail", hair_ponytail_s)
+VHAIR("Ziegler", hair_ziegler_s)
+VHAIR("Emo Fringe", hair_emofringe_s)
+VHAIR("Very Short Over Eye Alt", hair_veryshortovereyealternate_s)
+VHAIR("Shorthime", hair_shorthime_s)
+VHAIR("High Tight", hair_hightight_s)
+VHAIR("Thinning Front", hair_thinningfront_s)
+VHAIR("Big Afro", hair_bigafro_s)
+VHAIR("Afro", hair_afro_s)
+VHAIR("High Braid", hair_hbraid_s)
+VHAIR("Braid", hair_braid_s)
+VHAIR("Sargeant", hair_sargeant_s)
+VHAIR("Gelled", hair_gelled_s)
+VHAIR("Kagami", hair_kagami_s)
+VHAIR("ShortTail", hair_stail_s)
+VHAIR("Gentle", hair_gentle_s)
+VHAIR("Grande", hair_grande_s)
+VHAIR("Bobcurl", hair_bobcurl_s)
+VHAIR("Pompadeur", hair_pompadour_s)
+VHAIR("Plait", hair_plait_s)
+VHAIR("Long", hair_long_s)
+VHAIR("Rattail", hair_rattail_s)
+VHAIR("Tajspiky", hair_tajspiky_s)
+VHAIR("Messy", hair_messy_s)
+VHAIR("Bangs", hair_bangs_s)
+VHAIR("TBraid", hair_tbraid_s)
+VHAIR("Toriyama2", hair_toriyama2_s)
+VHAIR("CIA", hair_cia_s)
+VHAIR("Mulder", hair_mulder_s)
+VHAIR("Scully", hair_scully_s)
+VHAIR("Nitori", hair_nitori_s)
+VHAIR("Joestar", hair_joestar_s)
+VHAIR("Ponytail4", hair_ponytail4_s)
+VHAIR("Ponytail5", hair_ponytail5_s)
+VHAIR("Beehive2", hair_beehive2_s)
+VHAIR("Short Braid", hair_shortbraid_s)
+VHAIR("Reverse Mohawk", hair_reversemohawk_s)
+VHAIR("SHort Bangs", hair_shortbangs_s)
+VHAIR("Half Shaved", hair_halfshaved_s)
+VHAIR("Longer Alt 2", hair_longeralt2_s)
+VHAIR("Bun", hair_bun_s)
+VHAIR("Curly", hair_curly_s)
+VHAIR("Victory", hair_victory_s)
+VHAIR("Ponytail6", hair_ponytail6_s)
+VHAIR("Undercut3", hair_undercut3_s)
+VHAIR("Bobcut Alt", hair_bobcultalt_s)
+VHAIR("Fingerwave", hair_fingerwave_s)
+VHAIR("Oxton", hair_oxton_s)
+VHAIR("Poofy2", hair_poofy2_s)
+VHAIR("Fringe Tail", hair_fringetail_s)
+VHAIR("Bun3", hair_bun3_s)
+VHAIR("Wisp", hair_wisp_s)
+VHAIR("Undercut2", hair_undercut2_s)
+VHAIR("TBob", hair_tbob_s)
+VHAIR("Spiky Ponytail", hair_spikyponytail_s)
+VHAIR("Rowbun", hair_rowbun_s)
+VHAIR("Rowdualtail", hair_rowdualtail_s)
+VHAIR("Rowbraid", hair_rowbraid_s)
+VHAIR("Shaved Mohawk", hair_shavedmohawk_s)
+VHAIR("Topknot", hair_topknot_s)
+VHAIR("Ronin", hair_ronin_s)
+VHAIR("Bowlcut2", hair_bowlcut2_s)
+VHAIR("Thinning Rear", hair_thinningrear_s)
+VHAIR("Thinning", hair_thinning_s)
+VHAIR("Jade", hair_jade_s)
+VHAIR("Bedhead", hair_bedhead_s)
+VHAIR("Dreadlocks", hair_dreads_s)
+VHAIR("Very Long", hair_vlong_s)
+VHAIR("Jensen", hair_jensen_s)
+VHAIR("Halfbang", hair_halfbang_s)
+VHAIR("Kusangi", hair_kusangi_s)
+VHAIR("Ponytail", hair_ponytail_s)
+VHAIR("Ponytail3", hair_ponytail3_s)
+VHAIR("Halfbang Alt", hair_halfbang_alt_s)
+VHAIR("Bedhead V2", hair_bedheadv2_s)
+VHAIR("Long Fringe", hair_longfringe_s)
+VHAIR("Flair", hair_flair_s)
+VHAIR("Bedhead V3", hair_bedheadv3_s)
+VHAIR("Himecut", hair_himecut_s)
+VHAIR("Curls", hair_curls_s)
+VHAIR("Very Long Fringe", hair_vlongfringe_s)
+VHAIR("Longest", hair_longest_s)
+VHAIR("Father", hair_father_s)
+VHAIR("Emo Long", hair_emolong_s)
+VHAIR("Short Hair 3", hair_shorthair3_s)
+VHAIR("Double Bun", hair_doublebun_s)
+VHAIR("Sleeze", hair_sleeze_s)
+VHAIR("Twintail", hair_twintail_s)
+VHAIR("Emo 2", hair_emo2_s)
+VHAIR("Low Fade", hair_lowfade_s)
+VHAIR("Med Fade", hair_medfade_s)
+VHAIR("High Fade", hair_highfade_s)
+VHAIR("Bald Fade", hair_baldfade_s)
+VHAIR("No Fade", hair_nofade_s)
+VHAIR("Trim Flat", hair_trimflat_s)
+VHAIR("Shaved", hair_shaved_s)
+VHAIR("Trimmed", hair_trimmed_s)
+VHAIR("Tight Bun", hair_tightbun_s)
+VHAIR("Short Hair 4", hair_d_s)
+VHAIR("Short Hair 5", hair_e_s)
+VHAIR("Short Hair 6", hair_f_s)
+VHAIR("Skinhead", hair_skinhead_s)
+VHAIR("Afro2", hair_afro2_s)
+VHAIR("Bobcut", hair_bobcut_s)
+VHAIR("Emo", hair_emo_s)
+VHAIR("Long Over Eye", hair_longovereye_s)
+VHAIR("Feather", hair_feather_s)
+VHAIR("Hitop", hair_hitop_s)
+VHAIR("Short Over Eye", hair_shortoverye_s)
+VHAIR("Straight", hair_straight_s)
+VHAIR("Buzzcut", hair_buzzcut_s)
+VHAIR("Combover", hair_combover_s)
+VHAIR("Crewcut", hair_crewcut_s)
+VHAIR("Devillock", hair_devilock_s)
+VHAIR("Clean", hair_clean_s)
+VHAIR("Shaggy", hair_shaggy_s)
+VHAIR("Updo", hair_updo_s)
+VHAIR("Mohawk", hair_mohawk_s)
+VHAIR("Odango", hair_odango_s)
+VHAIR("Ombre", hair_ombre_s)
+VHAIR("Parted", hair_parted_s)
+VHAIR("Quiff", hair_quiff_s)
+VHAIR("Volaju", hair_volaju_s)
+VHAIR("Bun2", hair_bun2_s)
+VHAIR("Rows1", hair_rows1_s)
+VHAIR("Rows2", hair_rows2_s)
+VHAIR("Dandy Pompadour", hair_dandypompadour_s)
+VHAIR("Poofy", hair_poofy_s)
+VHAIR("Toriyama", hair_toriyama_s)
+VHAIR("Drillruru", hair_drillruru_s)
+VHAIR("Bowlcut", hair_bowlcut_s)
+VHAIR("Coffee House", hair_coffeehouse_s)
+VHAIR("Family Man", hair_thefamilyman_s)
+VHAIR("Shaved Part", hair_shavedpart_s)
+VHAIR("Modern", hair_modern_s)
+VHAIR("One Shoulder", hair_oneshoulder_s)
+VHAIR("Very Short Over Eye", hair_veryshortovereye_s)
+VHAIR("Unkept", hair_unkept_s)
+VHAIR("Wife", hair_wife_s)
+VHAIR("Nia", hair_nia_s)
+VHAIR("Undercut", hair_undercut_s)
+VHAIR("Bobcut Alt", hair_bobcutalt_s)
+VHAIR("Short Hair 4 alt", hair_shorthair4_s)
+VHAIR("Tressshoulder", hair_tressshoulder_s)
+ //END
+#undef VHAIR
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
index 607ad650e3..a630ead7b3 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
@@ -1,5 +1,6 @@
/datum/sprite_accessory/horns
icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = HORNCOLOR
/datum/sprite_accessory/horns/none
name = "None"
@@ -23,4 +24,13 @@
/datum/sprite_accessory/horns/angler
name = "Angeler"
- icon_state = "angler"
\ No newline at end of file
+ icon_state = "angler"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/horns/antler
+ name = "Deer Antlers"
+ icon_state = "deer"
+
+/datum/sprite_accessory/horns/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
new file mode 100644
index 0000000000..6d2ab1a39b
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
@@ -0,0 +1,158 @@
+
+/******************************************
+************** IPC SCREENS ****************
+*******************************************/
+/datum/sprite_accessory/screen
+ icon = 'modular_citadel/icons/mob/ipc_screens.dmi'
+ color_src = null
+
+/datum/sprite_accessory/screen/blank
+ name = "Blank"
+ icon_state = "blank"
+
+/datum/sprite_accessory/screen/pink
+ name = "Pink"
+ icon_state = "pink"
+
+/datum/sprite_accessory/screen/green
+ name = "Green"
+ icon_state = "green"
+
+/datum/sprite_accessory/screen/red
+ name = "Red"
+ icon_state = "red"
+
+/datum/sprite_accessory/screen/blue
+ name = "Blue"
+ icon_state = "blue"
+
+/datum/sprite_accessory/screen/yellow
+ name = "Yellow"
+ icon_state = "yellow"
+
+/datum/sprite_accessory/screen/shower
+ name = "Shower"
+ icon_state = "shower"
+
+/datum/sprite_accessory/screen/nature
+ name = "Nature"
+ icon_state = "nature"
+
+/datum/sprite_accessory/screen/eight
+ name = "Eight"
+ icon_state = "eight"
+
+/datum/sprite_accessory/screen/goggles
+ name = "Goggles"
+ icon_state = "goggles"
+
+/datum/sprite_accessory/screen/heart
+ name = "Heart"
+ icon_state = "heart"
+
+/datum/sprite_accessory/screen/monoeye
+ name = "Mono eye"
+ icon_state = "monoeye"
+
+/datum/sprite_accessory/screen/breakout
+ name = "Breakout"
+ icon_state = "breakout"
+
+/datum/sprite_accessory/screen/purple
+ name = "Purple"
+ icon_state = "purple"
+
+/datum/sprite_accessory/screen/scroll
+ name = "Scroll"
+ icon_state = "scroll"
+
+/datum/sprite_accessory/screen/console
+ name = "Console"
+ icon_state = "console"
+
+/datum/sprite_accessory/screen/rgb
+ name = "RGB"
+ icon_state = "rgb"
+
+/datum/sprite_accessory/screen/golglider
+ name = "Gol Glider"
+ icon_state = "golglider"
+
+/datum/sprite_accessory/screen/rainbow
+ name = "Rainbow"
+ icon_state = "rainbow"
+
+/datum/sprite_accessory/screen/sunburst
+ name = "Sunburst"
+ icon_state = "sunburst"
+
+/datum/sprite_accessory/screen/static
+ name = "Static"
+ icon_state = "static"
+
+//Oracle Station sprites
+
+/datum/sprite_accessory/screen/bsod
+ name = "BSOD"
+ icon_state = "bsod"
+
+/datum/sprite_accessory/screen/redtext
+ name = "Red Text"
+ icon_state = "retext"
+
+/datum/sprite_accessory/screen/sinewave
+ name = "Sine wave"
+ icon_state = "sinewave"
+
+/datum/sprite_accessory/screen/squarewave
+ name = "Square wave"
+ icon_state = "squarwave"
+
+/datum/sprite_accessory/screen/ecgwave
+ name = "ECG wave"
+ icon_state = "ecgwave"
+
+/datum/sprite_accessory/screen/eyes
+ name = "Eyes"
+ icon_state = "eyes"
+
+/datum/sprite_accessory/screen/textdrop
+ name = "Text drop"
+ icon_state = "textdrop"
+
+/datum/sprite_accessory/screen/stars
+ name = "Stars"
+ icon_state = "stars"
+
+
+/******************************************
+************** IPC Antennas ***************
+*******************************************/
+
+/datum/sprite_accessory/antenna
+ icon = 'modular_citadel/icons/mob/ipc_antennas.dmi'
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/antenna/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/antenna/antennae
+ name = "Angled Antennae"
+ icon_state = "antennae"
+
+/datum/sprite_accessory/antenna/tvantennae
+ name = "TV Antennae"
+ icon_state = "tvantennae"
+
+/datum/sprite_accessory/antenna/cyberhead
+ name = "Cyberhead"
+ icon_state = "cyberhead"
+
+/datum/sprite_accessory/antenna/antlers
+ name = "Antlers"
+ icon_state = "antlers"
+
+/datum/sprite_accessory/antenna/crowned
+ name = "Crowned"
+ icon_state = "crowned"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs.dm
deleted file mode 100644
index 7663100822..0000000000
--- a/code/modules/mob/dead/new_player/sprite_accessories/legs.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them.
- icon = null //These datums exist for selecting legs on preference, and little else
-
-/datum/sprite_accessory/legs/none
- name = "Normal Legs"
-
-/datum/sprite_accessory/legs/digitigrade_lizard
- name = "Digitigrade Legs"
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
new file mode 100644
index 0000000000..15640a2699
--- /dev/null
+++ b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
@@ -0,0 +1,124 @@
+/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them. -- OR SO THEY USED TO BE
+ icon = null //These datums exist for selecting legs on preference, and little else
+
+/******************************************
+***************** Leggy *******************
+*******************************************/
+
+/datum/sprite_accessory/legs/none
+ name = "Plantigrade"
+
+/datum/sprite_accessory/legs/digitigrade_lizard
+ name = "Digitigrade"
+
+/datum/sprite_accessory/legs/digitigrade_bird
+ name = "Avian"
+
+
+/******************************************
+************** Taur Bodies ****************
+*******************************************/
+
+/datum/sprite_accessory/taur
+ icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra = TRUE
+ extra2_icon = 'modular_citadel/icons/mob/mam_taur.dmi'
+ extra2 = TRUE
+ center = TRUE
+ dimension_x = 64
+ var/taur_mode = NOT_TAURIC
+ color_src = MATRIXED
+
+/datum/sprite_accessory/taur/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/taur/cow
+ name = "Cow"
+ icon_state = "cow"
+ taur_mode = HOOF_TAURIC
+
+/datum/sprite_accessory/taur/deer
+ name = "Deer"
+ icon_state = "deer"
+ taur_mode = HOOF_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/drake
+ name = "Drake"
+ icon_state = "drake"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/drider
+ name = "Drider"
+ icon_state = "drider"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ taur_mode = PAW_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/fox
+ name = "Fox"
+ icon_state = "fox"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/husky
+ name = "Husky"
+ icon_state = "husky"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/horse
+ name = "Horse"
+ icon_state = "horse"
+ taur_mode = HOOF_TAURIC
+
+/datum/sprite_accessory/taur/lab
+ name = "Lab"
+ icon_state = "lab"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/naga
+ name = "Naga"
+ icon_state = "naga"
+ taur_mode = SNEK_TAURIC
+
+/datum/sprite_accessory/taur/otie
+ name = "Otie"
+ icon_state = "otie"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ taur_mode = PAW_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/panther
+ name = "Panther"
+ icon_state = "panther"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ taur_mode = SNEK_TAURIC
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/taur/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ taur_mode = PAW_TAURIC
+
+/datum/sprite_accessory/taur/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ taur_mode = PAW_TAURIC
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm
deleted file mode 100644
index 6b8036bd69..0000000000
--- a/code/modules/mob/dead/new_player/sprite_accessories/moth_wings.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-/datum/sprite_accessory/moth_wings
- icon = 'icons/mob/wings.dmi'
- color_src = null
-
-/datum/sprite_accessory/moth_wings/plain
- name = "Plain"
- icon_state = "plain"
-
-/datum/sprite_accessory/moth_wings/monarch
- name = "Monarch"
- icon_state = "monarch"
-
-/datum/sprite_accessory/moth_wings/luna
- name = "Luna"
- icon_state = "luna"
-
-/datum/sprite_accessory/moth_wings/atlas
- name = "Atlas"
- icon_state = "atlas"
-
-/datum/sprite_accessory/moth_wings/reddish
- name = "Reddish"
- icon_state = "redish"
-
-/datum/sprite_accessory/moth_wings/royal
- name = "Royal"
- icon_state = "royal"
-
-/datum/sprite_accessory/moth_wings/gothic
- name = "Gothic"
- icon_state = "gothic"
-
-/datum/sprite_accessory/moth_wings/lovers
- name = "Lovers"
- icon_state = "lovers"
-
-/datum/sprite_accessory/moth_wings/whitefly
- name = "White Fly"
- icon_state = "whitefly"
-
-/datum/sprite_accessory/moth_wings/punished
- name = "Burnt Off"
- icon_state = "punished"
- locked = TRUE
-
-/datum/sprite_accessory/moth_wings/firewatch
- name = "Firewatch"
- icon_state = "firewatch"
-
-/datum/sprite_accessory/moth_wings/deathhead
- name = "Deathshead"
- icon_state = "deathhead"
-
-/datum/sprite_accessory/moth_wings/poison
- name = "Poison"
- icon_state = "poison"
-
-/datum/sprite_accessory/moth_wings/ragged
- name = "Ragged"
- icon_state = "ragged"
-
-/datum/sprite_accessory/moth_wings/moonfly
- name = "Moon Fly"
- icon_state = "moonfly"
-
-/datum/sprite_accessory/moth_wings/snow
- name = "Snow"
- icon_state = "snow"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
index c663c08d69..7252f85324 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
@@ -15,4 +15,359 @@
/datum/sprite_accessory/snouts/roundlight
name = "Round + Light"
- icon_state = "roundlight"
\ No newline at end of file
+ icon_state = "roundlight"
+
+/datum/sprite_accessory/snout/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+
+//christ this was a mistake, but it's here just in case someone wants to selectively fix -- Pooj
+/************* Lizard compatable snoots ***********
+/datum/sprite_accessory/snouts/bird
+ name = "Beak"
+ icon_state = "bird"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/bigbeak
+ name = "Big Beak"
+ icon_state = "bigbeak"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/bug
+ name = "Bug"
+ icon_state = "bug"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/lcanid
+ name = "Mammal, Long"
+ icon_state = "lcanid"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/lcanidalt
+ name = "Mammal, Long ALT"
+ icon_state = "lcanidalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/scanid
+ name = "Mammal, Short"
+ icon_state = "scanid"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/scanidalt
+ name = "Mammal, Short ALT"
+ icon_state = "scanidalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/wolf
+ name = "Mammal, Thick"
+ icon_state = "wolf"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/wolfalt
+ name = "Mammal, Thick ALT"
+ icon_state = "wolfalt"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/redpanda
+ name = "WahCoon"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/rhino
+ name = "Horn"
+ icon_state = "rhino"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/snouts/rodent
+ name = "Rodent"
+ icon_state = "rodent"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/otie
+ name = "Otie"
+ icon_state = "otie"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+
+/datum/sprite_accessory/snouts/toucan
+ name = "Toucan"
+ icon_state = "toucan"
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+ color_src = MATRIXED
+*/
+
+/******************************************
+************** Mammal Snouts **************
+*******************************************/
+
+/datum/sprite_accessory/mam_snouts
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
+
+/datum/sprite_accessory/mam_snouts/none
+ name = "None"
+ icon_state = "none"
+
+
+/datum/sprite_accessory/mam_snouts/bird
+ name = "Beak"
+ icon_state = "bird"
+
+/datum/sprite_accessory/mam_snouts/bigbeak
+ name = "Big Beak"
+ icon_state = "bigbeak"
+
+/datum/sprite_accessory/mam_snouts/bug
+ name = "Bug"
+ icon_state = "bug"
+ color_src = MUTCOLORS
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/elephant
+ name = "Elephant"
+ icon_state = "elephant"
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/lcanid
+ name = "Mammal, Long"
+ icon_state = "lcanid"
+
+/datum/sprite_accessory/mam_snouts/lcanidalt
+ name = "Mammal, Long ALT"
+ icon_state = "lcanidalt"
+
+/datum/sprite_accessory/mam_snouts/scanid
+ name = "Mammal, Short"
+ icon_state = "scanid"
+
+/datum/sprite_accessory/mam_snouts/scanidalt
+ name = "Mammal, Short ALT"
+ icon_state = "scanidalt"
+
+/datum/sprite_accessory/mam_snouts/wolf
+ name = "Mammal, Thick"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_snouts/wolfalt
+ name = "Mammal, Thick ALT"
+ icon_state = "wolfalt"
+
+/datum/sprite_accessory/mam_snouts/redpanda
+ name = "WahCoon"
+ icon_state = "wah"
+
+/datum/sprite_accessory/mam_snouts/redpandaalt
+ name = "WahCoon ALT"
+ icon_state = "wahalt"
+
+/datum/sprite_accessory/mam_snouts/rhino
+ name = "Horn"
+ icon_state = "rhino"
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/rodent
+ name = "Rodent"
+ icon_state = "rodent"
+
+/datum/sprite_accessory/mam_snouts/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_snouts/otie
+ name = "Otie"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_snouts/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_snouts/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_snouts/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_snouts/toucan
+ name = "Toucan"
+ icon_state = "toucan"
+
+/datum/sprite_accessory/mam_snouts/sharp
+ name = "Sharp"
+ icon_state = "sharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/round
+ name = "Round"
+ icon_state = "round"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/sharplight
+ name = "Sharp + Light"
+ icon_state = "sharplight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/roundlight
+ name = "Round + Light"
+ icon_state = "roundlight"
+ color_src = MUTCOLORS
+
+
+/******************************************
+**************** Snouts *******************
+*************but higher up*****************/
+
+/datum/sprite_accessory/mam_snouts/fbird
+ name = "Beak (Top)"
+ icon_state = "fbird"
+
+/datum/sprite_accessory/mam_snouts/fbigbeak
+ name = "Big Beak (Top)"
+ icon_state = "fbigbeak"
+
+/datum/sprite_accessory/mam_snouts/fbug
+ name = "Bug (Top)"
+ icon_state = "fbug"
+ color_src = MUTCOLORS
+ extra2 = TRUE
+ extra2_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/felephant
+ name = "Elephant (Top)"
+ icon_state = "felephant"
+ extra = TRUE
+ extra_color_src = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/flcanid
+ name = "Mammal, Long (Top)"
+ icon_state = "flcanid"
+
+/datum/sprite_accessory/mam_snouts/flcanidalt
+ name = "Mammal, Long ALT (Top)"
+ icon_state = "flcanidalt"
+
+/datum/sprite_accessory/mam_snouts/fscanid
+ name = "Mammal, Short (Top)"
+ icon_state = "fscanid"
+
+/datum/sprite_accessory/mam_snouts/fscanidalt
+ name = "Mammal, Short ALT (Top)"
+ icon_state = "fscanidalt"
+
+/datum/sprite_accessory/mam_snouts/fwolf
+ name = "Mammal, Thick (Top)"
+ icon_state = "fwolf"
+
+/datum/sprite_accessory/mam_snouts/fwolfalt
+ name = "Mammal, Thick ALT (Top)"
+ icon_state = "fwolfalt"
+
+/datum/sprite_accessory/mam_snouts/fredpanda
+ name = "WahCoon (Top)"
+ icon_state = "fwah"
+
+/datum/sprite_accessory/mam_snouts/frhino
+ name = "Horn (Top)"
+ icon_state = "frhino"
+ extra = TRUE
+ extra = MUTCOLORS3
+
+/datum/sprite_accessory/mam_snouts/frodent
+ name = "Rodent (Top)"
+ icon_state = "frodent"
+
+/datum/sprite_accessory/mam_snouts/fhusky
+ name = "Husky (Top)"
+ icon_state = "fhusky"
+
+/datum/sprite_accessory/mam_snouts/fotie
+ name = "Otie (Top)"
+ icon_state = "fotie"
+
+/datum/sprite_accessory/mam_snouts/fpede
+ name = "Scolipede (Top)"
+ icon_state = "fpede"
+
+/datum/sprite_accessory/mam_snouts/fsergal
+ name = "Sergal (Top)"
+ icon_state = "fsergal"
+
+/datum/sprite_accessory/mam_snouts/fshark
+ name = "Shark (Top)"
+ icon_state = "fshark"
+
+/datum/sprite_accessory/mam_snouts/ftoucan
+ name = "Toucan (Top)"
+ icon_state = "ftoucan"
+
+/datum/sprite_accessory/mam_snouts/fsharp
+ name = "Sharp (Top)"
+ icon_state = "fsharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/fround
+ name = "Round (Top)"
+ icon_state = "fround"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/fsharplight
+ name = "Sharp + Light (Top)"
+ icon_state = "fsharplight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/mam_snouts/froundlight
+ name = "Round + Light (Top)"
+ icon_state = "froundlight"
+ color_src = MUTCOLORS
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
index 31faabf663..6042d97247 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
@@ -4,6 +4,10 @@
/datum/sprite_accessory/tails_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
+/******************************************
+************* Lizard Tails ****************
+*******************************************/
+
/datum/sprite_accessory/tails/lizard/smooth
name = "Smooth"
icon_state = "smooth"
@@ -36,6 +40,48 @@
name = "Spikes"
icon_state = "spikes"
+/datum/sprite_accessory/tails/lizard/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/tails_animated/lizard/none
+ name = "None"
+ icon_state = "None"
+
+/datum/sprite_accessory/tails/lizard/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/lizard/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/body_markings/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
+/datum/sprite_accessory/tails/lizard/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/lizard/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/******************************************
+************** Human Tails ****************
+*******************************************/
+
/datum/sprite_accessory/tails/human/none
name = "None"
icon_state = "none"
@@ -43,13 +89,626 @@
/datum/sprite_accessory/tails_animated/human/none
name = "None"
icon_state = "none"
-/*
+
+/datum/sprite_accessory/tails/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/bee
+ name = "Bee"
+ icon_state = "bee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
/datum/sprite_accessory/tails/human/cat
name = "Cat"
icon_state = "cat"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = HAIR
/datum/sprite_accessory/tails_animated/human/cat
name = "Cat"
icon_state = "cat"
- color_src = HAIR*/
\ No newline at end of file
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails/human/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/cow
+ name = "Cow"
+ icon_state = "cow"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/tails_animated/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/tails/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fish
+ name = "Fish"
+ icon_state = "fish"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/fox
+ name = "Fox"
+ icon_state = "fox"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/fox
+ name = "Fox"
+ icon_state = "fox"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/horse
+ name = "Horse"
+ icon_state = "horse"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails_animated/human/horse
+ name = "Horse"
+ icon_state = "horse"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = HAIR
+
+/datum/sprite_accessory/tails/human/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/husky
+ name = "Husky"
+ icon_state = "husky"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/insect
+ name = "Insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/insect
+ name = "insect"
+ icon_state = "insect"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/murid
+ name = "Murid"
+ icon_state = "murid"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/murid
+ name = "Murid"
+ icon_state = "murid"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/otie
+ name = "Otusian"
+ icon_state = "otie"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/orca
+ name = "Orca"
+ icon_state = "orca"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/orca
+ name = "Orca"
+ icon_state = "orca"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/skunk
+ name = "skunk"
+ icon_state = "skunk"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails_animated/human/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails/human/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails/human/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/******************************************
+************** Furry Tails ****************
+*******************************************/
+
+/datum/sprite_accessory/mam_tails
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/mam_tails/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/mam_tails_animated
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/mam_tails_animated/none
+ name = "None"
+ icon_state = "none"
+ color_src = MATRIXED
+
+/datum/sprite_accessory/mam_tails/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/mam_tails_animated/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/mam_tails/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_tails_animated/axolotl
+ name = "Axolotl"
+ icon_state = "axolotl"
+
+/datum/sprite_accessory/mam_tails/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/mam_tails_animated/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/mam_tails/cat
+ name = "Cat"
+ icon_state = "cat"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails_animated/cat
+ name = "Cat"
+ icon_state = "cat"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_tails_animated/catbig
+ name = "Cat, Big"
+ icon_state = "catbig"
+
+/datum/sprite_accessory/mam_tails/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/mam_tails_animated/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/mam_tail/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_tails_animated/cow
+ name = "Cow"
+ icon_state = "cow"
+
+/datum/sprite_accessory/mam_tails/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_tails_animated/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/mam_tails/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_tails_animated/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/mam_tails/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_tails_animated/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/mam_tails/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_tails_animated/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/mam_tails/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_tails_animated/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/mam_tails/horse
+ name = "Horse"
+ icon_state = "horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails_animated/horse
+ name = "Horse"
+ icon_state = "Horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/mam_tails/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/mam_tails_animated/husky
+ name = "Husky"
+ icon_state = "husky"
+
+datum/sprite_accessory/mam_tails/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/mam_tails_animated/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/mam_tails/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_tails_animated/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/mam_tails/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/mam_tails_animated/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/mam_tails/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_tails_animated/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_tails/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_tails_animated/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/mam_tails/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_tails_animated/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/mam_tails/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_tails_animated/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/mam_tails/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_tails_animated/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/mam_tails/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_tails_animated/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/mam_tails/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_tails_animated/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/mam_tails/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_tails_animated/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/mam_tails/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_tails_animated/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/mam_tails/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_tails_animated/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/mam_tails/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/mam_tails_animated/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/mam_tails/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_tails_animated/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/mam_tails/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/mam_tails_animated/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/mam_tails/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_tails_animated/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/mam_tails/wolf
+ name = "Wolf"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/mam_tails_animated/wolf
+ name = "Wolf"
+ icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
index d051b2f07a..dc0e0222bf 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
@@ -1,3 +1,5 @@
+//Angel Wings
+
/datum/sprite_accessory/wings/none
name = "None"
icon_state = "none"
@@ -23,4 +25,120 @@
dimension_x = 46
center = TRUE
dimension_y = 34
- locked = TRUE
\ No newline at end of file
+ locked = TRUE
+
+//INSECT WINGS
+
+/datum/sprite_accessory/insect_wings
+ icon = 'icons/mob/wings.dmi'
+ color_src = null
+
+/datum/sprite_accessory/insect_wings/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/insect_wings/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/insect_wings/monarch
+ name = "Monarch"
+ icon_state = "monarch"
+
+/datum/sprite_accessory/insect_wings/luna
+ name = "Luna"
+ icon_state = "luna"
+
+/datum/sprite_accessory/insect_wings/atlas
+ name = "Atlas"
+ icon_state = "atlas"
+
+/datum/sprite_accessory/insect_wings/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/insect_wings/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/insect_wings/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/insect_wings/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/insect_wings/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/insect_wings/punished
+ name = "Burnt Off"
+ icon_state = "punished"
+ locked = TRUE
+
+/datum/sprite_accessory/insect_wings/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/insect_wings/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/insect_wings/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/insect_wings/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/insect_wings/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/insect_wings/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/insect_wings/colored
+ name = "Colored (Hair)"
+ icon_state = "snowplain"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_fluff/colored1
+ name = "Colored (Primary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/insect_fluff/colored2
+ name = "Colored (Secondary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS2
+
+/datum/sprite_accessory/insect_fluff/colored3
+ name = "Colored (Tertiary)"
+ icon_state = "snowplain"
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/insect_wings/bee
+ name = "Bee"
+ icon_state = "bee"
+
+/datum/sprite_accessory/insect_wings/bee_color
+ name = "Bee (Hair colored)"
+ icon_state = "bee"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_wings/fairy
+ name = "Fairy"
+ icon_state = "fairy"
+
+/datum/sprite_accessory/insect_wings/bat
+ name = "Bat"
+ icon_state = "bat"
+
+/datum/sprite_accessory/insect_wings/feathery
+ name = "Feathery"
+ icon_state = "feathery"
diff --git a/code/modules/mob/living/carbon/alien/larva/emote.dm b/code/modules/mob/living/carbon/alien/larva/emote.dm
deleted file mode 100644
index 62cb620ee4..0000000000
--- a/code/modules/mob/living/carbon/alien/larva/emote.dm
+++ /dev/null
@@ -1,113 +0,0 @@
-/mob/living/carbon/alien/larva/emote(act,m_type=1,message = null)
-
- var/param = null
- if (findtext(act, "-", 1, null))
- var/t1 = findtext(act, "-", 1, null)
- param = copytext(act, t1 + 1, length(act) + 1)
- act = copytext(act, 1, t1)
-
- var/muzzled = is_muzzled()
-
- switch(act) //Alphabetically sorted please.
- if ("burp","burps")
- if (!muzzled)
- message = "[src] burps."
- m_type = 2
- if ("choke","chokes")
- message = "[src] chokes."
- m_type = 2
- if ("collapse","collapses")
- Paralyse(2)
- message = "[src] collapses!"
- m_type = 2
- if ("dance","dances")
- if (!src.restrained())
- message = "[src] dances around happily."
- m_type = 1
- if ("deathgasp","deathgasps")
- message = "[src] lets out a sickly hiss of air and falls limply to the floor..."
- m_type = 2
- if ("drool","drools")
- message = "[src] drools."
- m_type = 1
- if ("gasp","gasps")
- message = "[src] gasps."
- m_type = 2
- if ("gnarl","gnarls")
- if (!muzzled)
- message = "[src] gnarls and shows its teeth.."
- m_type = 2
- if ("hiss","hisses")
- message = "[src] hisses softly."
- m_type = 1
- if ("jump","jumps")
- message = "[src] jumps!"
- m_type = 1
- if ("me")
- ..()
- return
- if ("moan","moans")
- message = "[src] moans!"
- m_type = 2
- if ("nod","nods")
- message = "[src] nods its head."
- m_type = 1
- if ("roar","roars")
- if (!muzzled)
- message = "[src] softly roars."
- m_type = 2
- if ("roll","rolls")
- if (!src.restrained())
- message = "[src] rolls."
- m_type = 1
- if ("scratch","scratches")
- if (!src.restrained())
- message = "[src] scratches."
- m_type = 1
- if ("screech","screeches") //This orignally was called scretch, changing it. -Sum99
- if (!muzzled)
- message = "[src] screeches."
- m_type = 2
- if ("shake","shakes")
- message = "[src] shakes its head."
- m_type = 1
- if ("shiver","shivers")
- message = "[src] shivers."
- m_type = 2
- if ("sign","signs")
- if (!src.restrained())
- message = text("[src] signs[].", (text2num(param) ? text(" the number []", text2num(param)) : null))
- m_type = 1
- if ("snore","snores")
- message = "[src] snores."
- m_type = 2
- if ("sulk","sulks")
- message = "[src] sulks down sadly."
- m_type = 1
- if ("sway","sways")
- message = "[src] sways around dizzily."
- m_type = 1
- if ("tail")
- message = "[src] waves its tail."
- m_type = 1
- if ("twitch")
- message = "[src] twitches violently."
- m_type = 1
- if ("whimper","whimpers")
- if (!muzzled)
- message = "[src] whimpers."
- m_type = 2
-
- if ("help") //"The exception"
- src << "Help for larva emotes. You can use these emotes with say \"*emote\":\n\nburp, choke, collapse, dance, deathgasp, drool, gasp, gnarl, hiss, jump, me, moan, nod, roll, roar, scratch, screech, shake, shiver, sign-#, sulk, sway, tail, twitch, whimper"
-
- else
- src << "Unusable emote '[act]'. Say *help for a list."
-
- if ((message && src.stat == 0))
- log_emote("[name]/[key] : [message]")
- if (m_type & 1)
- visible_message(message)
- else
- audible_message(message)
- return
diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm
index 96e7c4310b..e66d70f492 100644
--- a/code/modules/mob/living/carbon/alien/special/facehugger.dm
+++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm
@@ -33,16 +33,18 @@
/obj/item/clothing/mask/facehugger/lamarr
name = "Lamarr"
- sterile = 1
+ sterile = TRUE
/obj/item/clothing/mask/facehugger/dead
icon_state = "facehugger_dead"
item_state = "facehugger_inactive"
+ sterile = TRUE
stat = DEAD
/obj/item/clothing/mask/facehugger/impregnated
icon_state = "facehugger_impregnated"
item_state = "facehugger_impregnated"
+ sterile = TRUE
stat = DEAD
/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index b53655ca7c..1d070489e7 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -271,9 +271,13 @@
if(restrained())
changeNext_move(CLICK_CD_BREAKOUT)
last_special = world.time + CLICK_CD_BREAKOUT
+ var/buckle_cd = 600
+ if(handcuffed)
+ var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED)
+ buckle_cd = O.breakouttime
visible_message("[src] attempts to unbuckle [p_them()]self!", \
- "You attempt to unbuckle yourself... (This will take around one minute and you need to stay still.)")
- if(do_after(src, 600, 0, target = src))
+ "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)")
+ if(do_after(src, buckle_cd, 0, target = src))
if(!buckled)
return
buckled.user_unbuckle_mob(src,src)
@@ -702,7 +706,7 @@
clear_fullscreen("critvision")
//Oxygen damage overlay
- var/windedup = getOxyLoss() + getStaminaLoss() * 0.2 + stamdamageoverlaytemp
+ var/windedup = getOxyLoss() + getStaminaLoss() * 0.2
if(windedup)
var/severity = 0
switch(windedup)
@@ -801,7 +805,8 @@
drop_all_held_items()
stop_pulling()
throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed)
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed)
+ if(handcuffed.demoralize_criminals)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed)
else
clear_alert("handcuffed")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed")
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index a067be798f..794194b3a1 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -61,6 +61,5 @@
var/next_hallucination = 0
var/cpr_time = 1 //CPR cooldown.
var/damageoverlaytemp = 0
- var/stamdamageoverlaytemp = 0
var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 528b55c921..85dfe66725 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -802,6 +802,11 @@
else
hud_used.healthdoll.icon_state = "healthdoll_DEAD"
+ if(hud_used.staminas)
+ hud_used.staminas.icon_state = staminahudamount()
+ if(hud_used.staminabuffer)
+ hud_used.staminabuffer.icon_state = staminabufferhudamount()
+
/mob/living/carbon/human/fully_heal(admin_revive = 0)
if(admin_revive)
regenerate_limbs()
@@ -854,52 +859,84 @@
.["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]"
/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user)
- //If they dragged themselves and we're currently aggressively grabbing them try to piggyback
- if(user == target && can_piggyback(target) && pulling == target && (HAS_TRAIT(src, TRAIT_PACIFISM) || grab_state >= GRAB_AGGRESSIVE) && stat == CONSCIOUS)
- buckle_mob(target,TRUE,TRUE)
+ if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS)
+ //If they dragged themselves and we're currently aggressively grabbing them try to piggyback
+ if(user == target && can_piggyback(target))
+ piggyback(target)
+ return
+ //If you dragged them to you and you're aggressively grabbing try to fireman carry them
+ else if(user != target && can_be_firemanned(target))
+ fireman_carry(target)
+ return
. = ..()
-/mob/living/carbon/human/proc/piggyback_instant(mob/living/M)
- return buckle_mob(M, TRUE, TRUE, FALSE, TRUE)
+//src is the user that will be carrying, target is the mob to be carried
+/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target)
+ return (istype(target) && target.stat == CONSCIOUS)
-//Can C try to piggyback at all.
-/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/C)
- if(istype(C) && C.stat == CONSCIOUS)
- return TRUE
- return FALSE
+/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target)
+ return (ishuman(target) && target.lying)
-/mob/living/carbon/human/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE, bypass_piggybacking = FALSE, no_delay = FALSE)
+/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target)
+ if(can_be_firemanned(target))
+ visible_message("[src] starts lifting [target] onto their back...",
+ "You start lifting [target] onto your back...")
+ if(do_after(src, 30, TRUE, target))
+ //Second check to make sure they're still valid to be carried
+ if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE))
+ target.resting = FALSE
+ buckle_mob(target, TRUE, TRUE, 90, 1, 0)
+ return
+ visible_message("[src] fails to fireman carry [target]!")
+ else
+ to_chat(src, "You can't fireman carry [target] while they're standing!")
+
+/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target)
+ if(can_piggyback(target))
+ visible_message("[target] starts to climb onto [src]...")
+ if(do_after(target, 15, target = src))
+ if(can_piggyback(target))
+ if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE))
+ target.visible_message("[target] can't hang onto [src]!")
+ return
+ buckle_mob(target, TRUE, TRUE, FALSE, 0, 2)
+ else
+ visible_message("[target] fails to climb onto [src]!")
+ else
+ to_chat(target, "You can't piggyback ride [src] right now!")
+
+/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0)
if(!force)//humans are only meant to be ridden through piggybacking and special cases
return
- if(bypass_piggybacking)
- return ..()
- if(!is_type_in_typecache(M, can_ride_typecache))
- M.visible_message("[M] really can't seem to mount [src]...")
+ if(!is_type_in_typecache(target, can_ride_typecache))
+ target.visible_message("[target] really can't seem to mount [src]...")
return
+ buckle_lying = lying_buckle
var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human)
- riding_datum.ride_check_rider_incapacitated = TRUE
- riding_datum.ride_check_rider_restrained = TRUE
- riding_datum.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)))
- if(buckled_mobs && ((M in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled || (M.stat != CONSCIOUS))
+ if(target_hands_needed)
+ riding_datum.ride_check_rider_restrained = TRUE
+ if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled)
return
- if(can_piggyback(M))
- riding_datum.ride_check_ridden_incapacitated = TRUE
- visible_message("[M] starts to climb onto [src]...")
- if(no_delay || do_after(M, 15, target = src))
- if(can_piggyback(M))
- if(M.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE))
- M.visible_message("[M] can't hang onto [src]!")
- return
- if(!riding_datum.equip_buckle_inhands(M, 2)) //MAKE SURE THIS IS LAST!!
- M.visible_message("[M] can't climb onto [src]!")
- return
- . = ..(M, force, check_loc)
- stop_pulling()
- else
- visible_message("[M] fails to climb onto [src]!")
- else
- . = ..(M,force,check_loc)
- stop_pulling()
+ var/equipped_hands_self
+ var/equipped_hands_target
+ if(hands_needed)
+ equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target)
+ if(target_hands_needed)
+ equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed)
+
+ if(hands_needed || target_hands_needed)
+ if(hands_needed && !equipped_hands_self)
+ src.visible_message("[src] can't get a grip on [target] because their hands are full!",
+ "You can't get a grip on [target] because your hands are full!")
+ return
+ else if(target_hands_needed && !equipped_hands_target)
+ target.visible_message("[target] can't get a grip on [src] because their hands are full!",
+ "You can't get a grip on [src] because your hands are full!")
+ return
+
+ stop_pulling()
+ riding_datum.handle_vehicle_layer()
+ . = ..(target, force, check_loc)
/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this
for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets
@@ -1029,8 +1066,8 @@
/mob/living/carbon/human/species/lizard/ashwalker
race = /datum/species/lizard/ashwalker
-/mob/living/carbon/human/species/moth
- race = /datum/species/moth
+/mob/living/carbon/human/species/insect
+ race = /datum/species/insect
/mob/living/carbon/human/species/mush
race = /datum/species/mush
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index ff4878aa13..5af295a5dd 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -348,10 +348,15 @@
if(temp)
var/update = 0
var/dmg = rand(M.force/2, M.force)
+ var/atom/throw_target = get_edge_target_turf(src, M.dir)
switch(M.damtype)
if("brute")
- if(M.force > 20)
- Unconscious(20)
+ if(M.force > 35) // durand and other heavy mechas
+ Knockdown(50)
+ src.throw_at(throw_target, rand(1,5), 7)
+ else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax
+ Knockdown(30)
+ src.throw_at(throw_target, rand(1,3), 7)
update |= temp.receive_damage(dmg, 0)
playsound(src, 'sound/weapons/punch4.ogg', 50, 1)
if("fire")
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 7a0a2d8ad8..2425fcf14d 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -17,6 +17,8 @@
//Eye colour
var/eye_color = "000"
+ var/horn_color = "85615a" //specific horn colors, because why not?
+
var/skin_tone = "caucasian1" //Skin tone
var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup
@@ -25,11 +27,11 @@
var/age = 30 //Player's age
var/underwear = "Nude" //Which underwear the player wants
- var/undie_color = "#FFFFFF"
+ var/undie_color = "FFFFFF"
var/undershirt = "Nude" //Which undershirt the player wants
- var/shirt_color = "#FFFFFF"
+ var/shirt_color = "FFFFFF"
var/socks = "Nude" //Which socks the player wants
- var/socks_color = "#FFFFFF"
+ var/socks_color = "FFFFFF"
var/backbag = DBACKPACK //Which backpack type the player has chosen.
var/jumpsuit_style = PREF_SUIT //suit/skirt
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 1af9dbc5f5..b1c31ffdff 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -39,6 +39,10 @@
//Stuff jammed in your limbs hurts
handle_embedded_objects()
+ if(stat != DEAD)
+ //process your dick energy
+ handle_arousal()
+
//Update our name based on whether our face is obscured/disfigured
name = get_visible_name()
@@ -54,7 +58,7 @@
var/obj/item/clothing/CH = head
if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE)
return ONE_ATMOSPHERE
- if(istype(loc, /obj/belly)) //START OF CIT CHANGES - Makes it so you don't suffocate while inside vore organs. Remind me to modularize this some time - Bhijn
+ if(isbelly(loc)) //START OF CIT CHANGES - Makes it so you don't suffocate while inside vore organs. Remind me to modularize this some time - Bhijn
return ONE_ATMOSPHERE
if(istype(loc, /obj/item/dogborg/sleeper))
return ONE_ATMOSPHERE //END OF CIT CHANGES
diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm
deleted file mode 100644
index 1ac24cffa9..0000000000
--- a/code/modules/mob/living/carbon/human/login.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/mob/living/carbon/human/Login()
- ..()
- if(src.martial_art == default_martial_art && mind.stored_martial_art) //If the mind has a martial art stored and the body has the default one.
- src.mind.stored_martial_art.teach(src) //Running teach so that it deals with help verbs.
- else if(src.martial_art != default_martial_art && src.martial_art != mind.stored_martial_art) //If the body has a martial art which is not the default one and is not stored in the mind.
- if(src.martial_art_owner != mind)
- src.martial_art.remove(src)
- else
- src.mind.stored_martial_art = src.martial_art
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 9abfc5826f..d2e8cab240 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1,6 +1,7 @@
// This code handles different species in the game.
GLOBAL_LIST_EMPTY(roundstart_races)
+GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species
var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this
@@ -15,6 +16,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
+ var/horn_color //specific horn colors, because why not?
+
var/use_skintones = 0 // does it use skintones or not? (spoiler alert this is only used by humans)
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
@@ -79,7 +82,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/fixed_mut_color3 = ""
var/whitelisted = 0 //Is this species restricted to certain players?
var/whitelist = list() //List the ckeys that can use this species, if it's whitelisted.: list("John Doe", "poopface666", "SeeALiggerPullTheTrigger") Spaces & capitalization can be included or ignored entirely for each key as it checks for both.
-
+ var/should_draw_citadel = FALSE
///////////
// PROCS //
@@ -98,6 +101,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/datum/species/S = new I
if(S.check_roundstart_eligible())
GLOB.roundstart_races += S.id
+ GLOB.roundstart_race_names["[S.name]"] = S.id
qdel(S)
if(!GLOB.roundstart_races.len)
GLOB.roundstart_races += "human"
@@ -129,10 +133,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
return
//Please override this locally if you want to define when what species qualifies for what rank if human authority is enforced.
-/datum/species/proc/qualifies_for_rank(rank, list/features)
- if(rank in GLOB.command_positions)
- return 0
- return 1
+/datum/species/proc/qualifies_for_rank(rank, list/features) //SPECIES JOB RESTRICTIONS
+ //if(rank in GLOB.command_positions) Left as an example: The format qualifies for rank takes.
+ // return 0 //It returns false when it runs the proc so they don't get jobs from the global list.
+ return 1 //It returns 1 to say they are a-okay to continue.
//Will regenerate missing organs
/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE)
@@ -260,7 +264,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
C.hud_used.update_locked_slots()
// this needs to be FIRST because qdel calls update_body which checks if we have DIGITIGRADE legs or not and if not then removes DIGITIGRADE from species_traits
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Digitigrade Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
species_traits += DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(FALSE)
@@ -294,8 +298,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
for(var/datum/disease/A in C.diseases)
A.cure(FALSE)
- SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
-
//CITADEL EDIT
if(NOAROUSAL in species_traits)
C.canbearoused = FALSE
@@ -306,6 +308,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/mob/living/carbon/human/H = C
if(NOGENITALS in H.dna.species.species_traits)
H.give_genitals(TRUE) //call the clean up proc to delete anything on the mob then return.
+ if("meat_type" in default_features) //I can't believe it's come to the meat
+ H.type_of_meat = GLOB.meat_types[H.dna.features["meat_type"]]
+
+ SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
+
// EDIT ENDS
@@ -317,6 +324,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
for(var/X in inherent_traits)
REMOVE_TRAIT(C, X, SPECIES_TRAIT)
+ if("meat_type" in default_features)
+ C.type_of_meat = GLOB.meat_types[C.dna.features["meat_type"]]
+ else
+ C.type_of_meat = initial(meat)
+
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
@@ -529,7 +541,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/digilegs = (DIGITIGRADE in species_traits) ? "_d" : ""
var/mutable_appearance/MA = mutable_appearance(S.icon, "[S.icon_state][digilegs]", -BODY_LAYER)
if(UNDIE_COLORABLE(S))
- MA.color = "[H.socks_color]"
+ MA.color = "#[H.socks_color]"
standing += MA
if(standing.len)
@@ -612,6 +624,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else if ("wings" in mutant_bodyparts)
bodyparts_to_add -= "wings_open"
+ if("insect_fluff" in mutant_bodyparts)
+ if(!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "insect_fluff"
+
//CITADEL EDIT
//Race specific bodyparts:
//Xenos
@@ -717,8 +733,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
S = GLOB.wings_open_list[H.dna.features["wings"]]
if("legs")
S = GLOB.legs_list[H.dna.features["legs"]]
- if("moth_wings")
- S = GLOB.moth_wings_list[H.dna.features["moth_wings"]]
+ if("insect_wings")
+ S = GLOB.insect_wings_list[H.dna.features["insect_wings"]]
+ if("insect_fluff")
+ S = GLOB.insect_fluffs_list[H.dna.features["insect_fluff"]]
if("caps")
S = GLOB.caps_list[H.dna.features["caps"]]
if("ipc_screen")
@@ -815,6 +833,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
accessory_overlay.color = "#[H.facial_hair_color]"
if(EYECOLOR)
accessory_overlay.color = "#[H.eye_color]"
+ if(HORNCOLOR)
+ accessory_overlay.color = "#[H.horn_color]"
else
accessory_overlay.color = forced_colour
else
@@ -880,6 +900,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
extra_accessory_overlay.color = "#[H.facial_hair_color]"
if(EYECOLOR)
extra_accessory_overlay.color = "#[H.eye_color]"
+
+ if(HORNCOLOR)
+ extra_accessory_overlay.color = "#[H.horn_color]"
standing += extra_accessory_overlay
if(S.extra2) //apply the extra overlay, if there is one
@@ -912,6 +935,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
extra2_accessory_overlay.color = "#[H.dna.features["mcolor"]]"
else
extra2_accessory_overlay.color = "#[H.hair_color]"
+ if(HORNCOLOR)
+ extra2_accessory_overlay.color = "#[H.horn_color]"
standing += extra2_accessory_overlay
@@ -1313,10 +1338,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/obj/item/organ/cyberimp/chest/thrusters/T = H.getorganslot(ORGAN_SLOT_THRUSTERS)
if(!istype(J) && istype(C))
J = C.jetpack
- if(istype(J) && J.full_speed && J.allow_thrust(0.01, H)) //Prevents stacking
- . -= 2
- else if(istype(T) && T.allow_thrust(0.01, H))
- . -= 2
+ if(istype(J) && J.full_speed && J.allow_thrust(0.005, H)) //Prevents stacking
+ . -= 0.4
+ else if(istype(T) && T.allow_thrust(0.005, H))
+ . -= 0.4
if(!ignoreslow && gravity)
if(H.wear_suit)
@@ -1696,7 +1721,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(H.stat == CONSCIOUS && H != user && prob(I.force + ((100 - H.health) * 0.5))) // rev deconversion through blunt trauma.
var/datum/antagonist/rev/rev = H.mind.has_antag_datum(/datum/antagonist/rev)
- var/datum/antagonist/gang/gang = H.mind.has_antag_datum(/datum/antagonist/gang/)
+ var/datum/antagonist/gang/gang = H.mind.has_antag_datum(/datum/antagonist/gang && !/datum/antagonist/gang/boss)
if(rev)
rev.remove_revolutionary(FALSE, user)
if(gang)
@@ -1732,6 +1757,161 @@ GLOBAL_LIST_EMPTY(roundstart_races)
H.forcesay(GLOB.hit_appends) //forcesay checks stat already.
return TRUE
+/datum/species/proc/alt_spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style)
+ if(!istype(M))
+ return TRUE
+ CHECK_DNA_AND_SPECIES(M)
+ CHECK_DNA_AND_SPECIES(H)
+
+ if(!istype(M)) //sanity check for drones.
+ return TRUE
+ if(M.mind)
+ attacker_style = M.mind.martial_art
+ if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK))
+ log_combat(M, H, "attempted to touch")
+ H.visible_message("[M] attempted to touch [H]!")
+ return TRUE
+ switch(M.a_intent)
+ if(INTENT_HELP)
+ if(M == H)
+ althelp(M, H, attacker_style)
+ return TRUE
+ return FALSE
+ if(INTENT_DISARM)
+ altdisarm(M, H, attacker_style)
+ return TRUE
+ return FALSE
+
+/datum/species/proc/althelp(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
+ if(user == target && istype(user))
+ if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)
+ to_chat(user, "You're too exhausted for that.")
+ return
+ if(!user.resting)
+ to_chat(user, "You can only force yourself up if you're on the ground.")
+ return
+ user.visible_message("[user] forces [p_them()]self up to [p_their()] feet!", "You force yourself up to your feet!")
+ user.resting = 0
+ user.update_canmove()
+ user.adjustStaminaLossBuffered(user.stambuffer) //Rewards good stamina management by making it easier to instantly get up from resting
+ playsound(user, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
+
+/datum/species/proc/altdisarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
+ if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)
+ to_chat(user, "You're too exhausted.")
+ return FALSE
+ if(target.check_block())
+ target.visible_message("[target] blocks [user]'s shoving attempt!")
+ return FALSE
+ if(attacker_style && attacker_style.disarm_act(user,target))
+ return TRUE
+ if(user.resting)
+ return FALSE
+ else
+ if(user == target)
+ return
+ user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
+ user.adjustStaminaLossBuffered(4)
+ playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+
+ if(target.w_uniform)
+ target.w_uniform.add_fingerprint(user)
+ SEND_SIGNAL(target, COMSIG_HUMAN_DISARM_HIT, user, user.zone_selected)
+
+ if(!target.resting)
+ target.adjustStaminaLoss(5)
+
+
+ var/turf/target_oldturf = target.loc
+ var/shove_dir = get_dir(user.loc, target_oldturf)
+ var/turf/target_shove_turf = get_step(target.loc, shove_dir)
+ var/mob/living/carbon/human/target_collateral_human
+ var/obj/structure/table/target_table
+ var/shove_blocked = FALSE //Used to check if a shove is blocked so that if it is knockdown logic can be applied
+
+ //Thank you based whoneedsspace
+ target_collateral_human = locate(/mob/living/carbon/human) in target_shove_turf.contents
+ if(target_collateral_human)
+ shove_blocked = TRUE
+ else
+ target.Move(target_shove_turf, shove_dir)
+ if(get_turf(target) == target_oldturf)
+ if(target_shove_turf.density)
+ shove_blocked = TRUE
+ else
+ var/thoushallnotpass = FALSE
+ for(var/obj/O in target_shove_turf)
+ if(istype(O, /obj/structure/table))
+ target_table = O
+ else if(!O.CanPass(src, target_shove_turf))
+ shove_blocked = TRUE
+ thoushallnotpass = TRUE
+ if(thoushallnotpass)
+ target_table = null
+
+ if(target.is_shove_knockdown_blocked())
+ return
+
+ if(shove_blocked || target_table)
+ var/directional_blocked = FALSE
+ if(shove_dir in GLOB.cardinals) //Directional checks to make sure that we're not shoving through a windoor or something like that
+ var/target_turf = get_turf(target)
+ for(var/obj/O in target_turf)
+ if(O.flags_1 & ON_BORDER_1 && O.dir == shove_dir && O.density)
+ directional_blocked = TRUE
+ break
+ if(target_turf != target_shove_turf) //Make sure that we don't run the exact same check twice on the same tile
+ for(var/obj/O in target_shove_turf)
+ if(O.flags_1 & ON_BORDER_1 && O.dir == turn(shove_dir, 180) && O.density)
+ directional_blocked = TRUE
+ break
+ var/targetatrest = target.resting
+ if(((!target_table && !target_collateral_human) || directional_blocked) && !targetatrest)
+ target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
+ user.visible_message("[user.name] shoves [target.name], knocking them down!",
+ "You shove [target.name], knocking them down!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(user, target, "shoved", "knocking them down")
+ else if(target_table)
+ if(!targetatrest)
+ target.Knockdown(SHOVE_KNOCKDOWN_TABLE)
+ user.visible_message("[user.name] shoves [target.name] onto \the [target_table]!",
+ "You shove [target.name] onto \the [target_table]!", null, COMBAT_MESSAGE_RANGE)
+ target.forceMove(target_shove_turf)
+ log_combat(user, target, "shoved", "onto [target_table]")
+ else if(target_collateral_human && !targetatrest)
+ target.Knockdown(SHOVE_KNOCKDOWN_HUMAN)
+ if(!target_collateral_human.resting)
+ target_collateral_human.Knockdown(SHOVE_KNOCKDOWN_COLLATERAL)
+ user.visible_message("[user.name] shoves [target.name] into [target_collateral_human.name]!",
+ "You shove [target.name] into [target_collateral_human.name]!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(user, target, "shoved", "into [target_collateral_human.name]")
+
+ else
+ user.visible_message("[user.name] shoves [target.name]!",
+ "You shove [target.name]!", null, COMBAT_MESSAGE_RANGE)
+ var/target_held_item = target.get_active_held_item()
+ var/knocked_item = FALSE
+ if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types))
+ target_held_item = null
+ if(!target.has_movespeed_modifier(SHOVE_SLOWDOWN_ID))
+ target.add_movespeed_modifier(SHOVE_SLOWDOWN_ID, multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH)
+ if(target_held_item)
+ target.visible_message("[target.name]'s grip on \the [target_held_item] loosens!",
+ "Your grip on \the [target_held_item] loosens!", null, COMBAT_MESSAGE_RANGE)
+ addtimer(CALLBACK(target, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH)
+ else if(target_held_item)
+ target.dropItemToGround(target_held_item)
+ knocked_item = TRUE
+ target.visible_message("[target.name] drops \the [target_held_item]!!",
+ "You drop \the [target_held_item]!!", null, COMBAT_MESSAGE_RANGE)
+ var/append_message = ""
+ if(target_held_item)
+ if(knocked_item)
+ append_message = "causing them to drop [target_held_item]"
+ else
+ append_message = "loosening their grip on [target_held_item]"
+ log_combat(user, target, "shoved", append_message)
+
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H)
var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
@@ -1781,7 +1961,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(CLONE)
H.adjustCloneLoss(damage * hit_percent * H.physiology.clone_mod)
if(STAMINA)
- H.stamdamageoverlaytemp = 20
if(BP)
if(damage > 0 ? BP.receive_damage(0, 0, damage * hit_percent * H.physiology.stamina_mod) : BP.heal_damage(0, 0, abs(damage * hit_percent * H.physiology.stamina_mod), only_robotic = FALSE, only_organic = FALSE))
H.update_stamina()
diff --git a/code/modules/mob/living/carbon/human/species_types/bugmen.dm b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
new file mode 100644
index 0000000000..94dba550b6
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
@@ -0,0 +1,64 @@
+/datum/species/insect
+ name = "Anthromorphic Insect"
+ id = "insect"
+ say_mod = "flutters"
+ default_color = "00FF00"
+ species_traits = list(LIPS,NOEYES,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
+ mutant_bodyparts = list("mam_ears", "mam_snout", "mam_tail", "taur", "insect_wings", "mam_snouts", "insect_fluff","horns")
+ default_features = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None",
+ "insect_wings" = "None", "insect_fluff" = "None", "mam_snouts" = "None", "taur" = "None","horns" = "None")
+ attack_verb = "slash"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/insect
+ liked_food = VEGETABLES | DAIRY
+ disliked_food = FRUIT | GROSS
+ toxic_food = MEAT | RAW
+ mutanteyes = /obj/item/organ/eyes/insect
+ should_draw_citadel = TRUE
+
+/datum/species/insect/on_species_gain(mob/living/carbon/C)
+ . = ..()
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(!H.dna.features["insect_wings"])
+ H.dna.features["insect_wings"] = "[(H.client && H.client.prefs && LAZYLEN(H.client.prefs.features) && H.client.prefs.features["insect_wings"]) ? H.client.prefs.features["insect_wings"] : "None"]"
+ handle_mutant_bodyparts(H)
+
+/datum/species/insect/random_name(gender,unique,lastname)
+ if(unique)
+ return random_unique_moth_name()
+
+ var/randname = moth_name()
+
+ if(lastname)
+ randname += " [lastname]"
+
+ return randname
+
+/datum/species/insect/handle_fire(mob/living/carbon/human/H, no_protection = FALSE)
+ ..()
+ if(H.dna.features["insect_wings"] != "Burnt Off" && H.dna.features["insect_wings"] != "None" && H.bodytemperature >= 800 && H.fire_stacks > 0) //do not go into the extremely hot light. you will not survive
+ to_chat(H, "Your precious wings burn to a crisp!")
+ if(H.dna.features["insect_wings"] != "None")
+ H.dna.features["insect_wings"] = "Burnt Off"
+ handle_mutant_bodyparts(H)
+
+/datum/species/insect/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
+ . = ..()
+ if(chem.id == "pestkiller")
+ H.adjustToxLoss(3)
+ H.reagents.remove_reagent(chem.id, REAGENTS_METABOLISM)
+
+/datum/species/insect/check_weakness(obj/item/weapon, mob/living/attacker)
+ if(istype(weapon, /obj/item/melee/flyswatter))
+ return 9 //flyswatters deal 10x damage to insects
+ return 0
+
+/datum/species/insect/space_move(mob/living/carbon/human/H)
+ . = ..()
+ if(H.loc && !isspaceturf(H.loc) && (H.dna.features["insect_wings"] != "Burnt Off" && H.dna.features["insect_wings"] != "None"))
+ var/datum/gas_mixture/current = H.loc.return_air()
+ if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/corporate.dm b/code/modules/mob/living/carbon/human/species_types/corporate.dm
index 620f0b2543..146090b366 100644
--- a/code/modules/mob/living/carbon/human/species_types/corporate.dm
+++ b/code/modules/mob/living/carbon/human/species_types/corporate.dm
@@ -16,5 +16,5 @@
blacklisted = 1
use_skintones = 0
species_traits = list(NOBLOOD,EYECOLOR,NOGENITALS)
- inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
+ inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER)
sexes = 0
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index 6f05eb393d..043ee4fde1 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,5 +1,5 @@
/datum/species/fly
- name = "Flyperson"
+ name = "Anthromorphic Fly"
id = "fly"
say_mod = "buzzes"
species_traits = list(NOEYES)
diff --git a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
new file mode 100644
index 0000000000..e726d45347
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
@@ -0,0 +1,98 @@
+/datum/species/mammal
+ name = "Anthromorph"
+ id = "mammal"
+ default_color = "4B4B4B"
+ should_draw_citadel = TRUE
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "taur", "horns", "legs")
+ default_features = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky",
+ "mam_body_markings" = "Husky", "taur" = "None", "horns" = "None", "legs" = "Plantigrade", "meat_type" = "Mammalian")
+ attack_verb = "claw"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/mammal
+ liked_food = MEAT | FRIED
+ disliked_food = TOXIC
+
+//Curiosity killed the cat's wagging tail.
+/datum/species/mammal/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/mammal/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/mammal/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/mammal/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/mammal/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/mammal/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
+
+/datum/species/mammal/qualifies_for_rank(rank, list/features)
+ return TRUE
+
+
+//Alien//
+/datum/species/xeno
+ // A cloning mistake, crossing human and xenomorph DNA
+ name = "Xenomorph Hybrid"
+ id = "xeno"
+ say_mod = "hisses"
+ default_color = "00FF00"
+ should_draw_citadel = TRUE
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS)
+ inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ mutant_bodyparts = list("xenotail", "xenohead", "xenodorsal", "mam_body_markings", "taur", "legs")
+ default_features = list("xenotail"="Xenomorph Tail","xenohead"="Standard","xenodorsal"="Standard", "mam_body_markings" = "Xeno","mcolor" = "0F0","mcolor2" = "0F0","mcolor3" = "0F0","taur" = "None", "legs" = "Digitigrade")
+ attack_verb = "slash"
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno
+ skinned_type = /obj/item/stack/sheet/animalhide/xeno
+ exotic_bloodtype = "L"
+ damage_overlay_type = "xeno"
+ liked_food = MEAT
+
+/datum/species/xeno/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
+ species_traits += DIGITIGRADE
+ if(DIGITIGRADE in species_traits)
+ C.Digitigrade_Leg_Swap(FALSE)
+ . = ..()
+
+/datum/species/xeno/on_species_loss(mob/living/carbon/human/C, datum/species/new_species)
+ if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Plantigrade")
+ species_traits -= DIGITIGRADE
+ if(DIGITIGRADE in species_traits)
+ C.Digitigrade_Leg_Swap(TRUE)
+ . = ..()
+
+//Praise the Omnissiah, A challange worthy of my skills - HS
+
+//EXOTIC//
+//These races will likely include lots of downsides and upsides. Keep them relatively balanced.//
+
+//misc
+/mob/living/carbon/human/dummy
+ no_vore = TRUE
+
+/mob/living/carbon/human/vore
+ devourable = TRUE
+ digestable = TRUE
+ feeding = TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 88dd59749c..84c44ea81c 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -45,7 +45,7 @@
return golem_name
/datum/species/golem/random
- name = "Random Golem"
+ name = "Golem Mutant"
blacklisted = FALSE
dangerous_existence = FALSE
var/static/list/random_golem_types
diff --git a/modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm b/code/modules/mob/living/carbon/human/species_types/ipc.dm
similarity index 98%
rename from modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm
rename to code/modules/mob/living/carbon/human/species_types/ipc.dm
index 25b8daf2cb..95b924ea18 100644
--- a/modular_citadel/code/modules/mob/living/carbon/human/species_types/ipc.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ipc.dm
@@ -1,5 +1,5 @@
/datum/species/ipc
- name = "IPC"
+ name = "I.P.C."
id = "ipc"
say_mod = "beeps"
default_color = "00FF00"
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 66586744fb..03cd514300 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -118,7 +118,7 @@
//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death.
/datum/species/jelly/slime
- name = "Slimeperson"
+ name = "Xenobiological Slime Entity"
id = "slime"
default_color = "00FFFF"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD)
@@ -389,12 +389,268 @@
"...and move this one instead.")
+////////////////////////////////////////////////////////Round Start Slimes///////////////////////////////////////////////////////////////////
+
+/datum/species/jelly/roundstartslime
+ name = "Xenobiological Slime Hybrid"
+ id = "slimeperson"
+ limbs_id = "slime"
+ default_color = "00FFFF"
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD)
+ inherent_traits = list(TRAIT_TOXINLOVER)
+ mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "taur")
+ default_features = list("mcolor" = "FFF", "mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None")
+ say_mod = "says"
+ hair_color = "mutcolor"
+ hair_alpha = 160 //a notch brighter so it blends better.
+ coldmod = 3
+ heatmod = 1
+ burnmod = 1
+
+/datum/species/jelly/roundstartslime/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/jelly/roundstartslime/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/jelly/roundstartslime/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/roundstartslime/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/roundstartslime/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/jelly/roundstartslime/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
+
+/datum/action/innate/slime_change
+ name = "Alter Form"
+ check_flags = AB_CHECK_CONSCIOUS
+ button_icon_state = "alter_form" //placeholder
+ icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_alien"
+
+/datum/action/innate/slime_change/Activate()
+ var/mob/living/carbon/human/H = owner
+ if(!isjellyperson(H))
+ return
+ else
+ H.visible_message("[owner] gains a look of \
+ concentration while standing perfectly still.\
+ Their body seems to shift and starts getting more goo-like.",
+ "You focus intently on altering your body while \
+ standing perfectly still...")
+ change_form()
+
+/datum/action/innate/slime_change/proc/change_form()
+ var/mob/living/carbon/human/H = owner
+ var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
+ if(select_alteration == "Hair Style")
+ if(H.gender == MALE)
+ var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list
+ if(new_style)
+ H.facial_hair_style = new_style
+ else
+ H.facial_hair_style = "Shaved"
+ //handle normal hair
+ var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
+ if(new_style)
+ H.hair_style = new_style
+ H.update_hair()
+ else if (select_alteration == "Genitals")
+ var/list/organs = list()
+ var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel")
+ switch(operation)
+ if("add sexual organ")
+ var/new_organ = input("Select sexual organ:", "Organ Manipulation") in list("Penis", "Testicles", "Breasts", "Vagina", "Womb", "Cancel")
+ if(new_organ == "Penis")
+ H.give_penis()
+ else if(new_organ == "Testicles")
+ H.give_balls()
+ else if(new_organ == "Breasts")
+ H.give_breasts()
+ else if(new_organ == "Vagina")
+ H.give_vagina()
+ else if(new_organ == "Womb")
+ H.give_womb()
+ else
+ return
+ if("remove sexual organ")
+ for(var/obj/item/organ/genital/X in H.internal_organs)
+ var/obj/item/organ/I = X
+ organs["[I.name] ([I.type])"] = I
+ var/obj/item/organ = input("Select sexual organ:", "Organ Manipulation", null) in organs
+ organ = organs[organ]
+ if(!organ)
+ return
+ var/obj/item/organ/genital/O
+ if(isorgan(organ))
+ O = organ
+ O.Remove(H)
+ organ.forceMove(get_turf(H))
+ qdel(organ)
+ H.update_genitals()
+
+ else if (select_alteration == "Ears")
+ var/list/snowflake_ears_list = list("Normal" = null)
+ for(var/path in GLOB.mam_ears_list)
+ var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_ears_list[S.name] = path
+ var/new_ears
+ new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list
+ if(new_ears)
+ H.dna.features["mam_ears"] = new_ears
+ H.update_body()
+
+ else if (select_alteration == "Snout")
+ var/list/snowflake_snouts_list = list("Normal" = null)
+ for(var/path in GLOB.mam_snouts_list)
+ var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_snouts_list[S.name] = path
+ var/new_snout
+ new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list
+ if(new_snout)
+ H.dna.features["mam_snouts"] = new_snout
+ H.update_body()
+
+ else if (select_alteration == "Markings")
+ var/list/snowflake_markings_list = list()
+ for(var/path in GLOB.mam_body_markings_list)
+ var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_markings_list[S.name] = path
+ var/new_mam_body_markings
+ new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list
+ if(new_mam_body_markings)
+ H.dna.features["mam_body_markings"] = new_mam_body_markings
+ if(new_mam_body_markings == "None")
+ H.dna.features["mam_body_markings"] = "Plain"
+ for(var/X in H.bodyparts) //propagates the markings changes
+ var/obj/item/bodypart/BP = X
+ BP.update_limb(FALSE, H)
+ H.update_body()
+
+ else if (select_alteration == "Tail")
+ var/list/snowflake_tails_list = list("Normal" = null)
+ for(var/path in GLOB.mam_tails_list)
+ var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_tails_list[S.name] = path
+ var/new_tail
+ new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list
+ if(new_tail)
+ H.dna.features["mam_tail"] = new_tail
+ if(new_tail != "None")
+ H.dna.features["taur"] = "None"
+ H.update_body()
+
+ else if (select_alteration == "Taur body")
+ var/list/snowflake_taur_list = list("Normal" = null)
+ for(var/path in GLOB.taur_list)
+ var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
+ snowflake_taur_list[S.name] = path
+ var/new_taur
+ new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list
+ if(new_taur)
+ H.dna.features["taur"] = new_taur
+ if(new_taur != "None")
+ H.dna.features["mam_tail"] = "None"
+ H.update_body()
+
+ else if (select_alteration == "Penis")
+ for(var/obj/item/organ/genital/penis/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list
+ if(new_shape)
+ H.dna.features["cock_shape"] = new_shape
+ H.update_genitals()
+ H.give_balls()
+ H.give_penis()
+ H.apply_overlay()
+
+
+ else if (select_alteration == "Vagina")
+ for(var/obj/item/organ/genital/vagina/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list
+ if(new_shape)
+ H.dna.features["vag_shape"] = new_shape
+ H.update_genitals()
+ H.give_womb()
+ H.give_vagina()
+ H.apply_overlay()
+
+ else if (select_alteration == "Penis Length")
+ for(var/obj/item/organ/genital/penis/X in H.internal_organs)
+ qdel(X)
+ var/new_length
+ new_length = input(owner, "Penis length in inches:\n([COCK_SIZE_MIN]-[COCK_SIZE_MAX])", "Genital Alteration") as num|null
+ if(new_length)
+ H.dna.features["cock_length"] = max(min( round(text2num(new_length)), COCK_SIZE_MAX),COCK_SIZE_MIN)
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_balls()
+ H.give_penis()
+
+ else if (select_alteration == "Breast Size")
+ for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
+ qdel(X)
+ var/new_size
+ new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in GLOB.breasts_size_list
+ if(new_size)
+ H.dna.features["breasts_size"] = new_size
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_breasts()
+
+ else if (select_alteration == "Breast Shape")
+ for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
+ qdel(X)
+ var/new_shape
+ new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list
+ if(new_shape)
+ H.dna.features["breasts_shape"] = new_shape
+ H.update_genitals()
+ H.apply_overlay()
+ H.give_breasts()
+
+ else
+ return
+
+
///////////////////////////////////LUMINESCENTS//////////////////////////////////////////
//Luminescents are able to consume and use slime extracts, without them decaying.
/datum/species/jelly/luminescent
- name = "Luminescent"
+ name = "Luminescent Slime Entity"
id = "lum"
say_mod = "says"
var/glow_intensity = LUMINESCENT_DEFAULT_GLOW
@@ -561,7 +817,7 @@
//Stargazers are the telepathic branch of jellypeople, able to project psychic messages and to link minds with willing participants.
/datum/species/jelly/stargazer
- name = "Stargazer"
+ name = "Stargazer Slime Entity"
id = "stargazer"
var/datum/action/innate/project_thought/project_thought
var/datum/action/innate/link_minds/link_minds
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 30bf705547..4dbfd23df8 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -1,17 +1,19 @@
/datum/species/lizard
// Reptilian humanoids with scaled skin and tails.
- name = "Lizardperson"
+ name = "Anthromorphic Lizard"
id = "lizard"
say_mod = "hisses"
default_color = "00FF00"
- species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS)
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR)
inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE)
mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs", "taur")
mutanttongue = /obj/item/organ/tongue/lizard
mutanttail = /obj/item/organ/tail/lizard
coldmod = 1.5
heatmod = 0.67
- default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "taur" = "None")
+ default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round",
+ "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None",
+ "legs" = "Digitigrade", "taur" = "None")
attack_verb = "slash"
attack_sound = 'sound/weapons/slash.ogg'
miss_sound = 'sound/weapons/slashmiss.ogg'
@@ -71,14 +73,14 @@
H.update_body()
/datum/species/lizard/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Digitigrade Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && (C.dna.features["legs"] == "Digitigrade" || C.dna.features["legs"] == "Avian"))
species_traits += DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(FALSE)
return ..()
/datum/species/lizard/on_species_loss(mob/living/carbon/human/C, datum/species/new_species)
- if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Normal Legs")
+ if(("legs" in C.dna.species.mutant_bodyparts) && C.dna.features["legs"] == "Plantigrade")
species_traits -= DIGITIGRADE
if(DIGITIGRADE in species_traits)
C.Digitigrade_Leg_Swap(TRUE)
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
deleted file mode 100644
index d15d989384..0000000000
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ /dev/null
@@ -1,61 +0,0 @@
-/datum/species/moth
- name = "Mothman"
- id = "moth"
- say_mod = "flutters"
- default_color = "00FF00"
- species_traits = list(LIPS, NOEYES)
- inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
- mutant_bodyparts = list("moth_wings")
- default_features = list("moth_wings" = "Plain")
- attack_verb = "slash"
- attack_sound = 'sound/weapons/slash.ogg'
- miss_sound = 'sound/weapons/slashmiss.ogg'
- meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/moth
- liked_food = VEGETABLES | DAIRY
- disliked_food = FRUIT | GROSS
- toxic_food = MEAT | RAW
- mutanteyes = /obj/item/organ/eyes/moth
-
-/datum/species/moth/on_species_gain(mob/living/carbon/C)
- . = ..()
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(!H.dna.features["moth_wings"])
- H.dna.features["moth_wings"] = "[(H.client && H.client.prefs && LAZYLEN(H.client.prefs.features) && H.client.prefs.features["moth_wings"]) ? H.client.prefs.features["moth_wings"] : "Plain"]"
- handle_mutant_bodyparts(H)
-
-/datum/species/moth/random_name(gender,unique,lastname)
- if(unique)
- return random_unique_moth_name()
-
- var/randname = moth_name()
-
- if(lastname)
- randname += " [lastname]"
-
- return randname
-
-/datum/species/moth/handle_fire(mob/living/carbon/human/H, no_protection = FALSE)
- ..()
- if(H.dna.features["moth_wings"] != "Burnt Off" && H.bodytemperature >= 800 && H.fire_stacks > 0) //do not go into the extremely hot light. you will not survive
- to_chat(H, "Your precious wings burn to a crisp!")
- H.dna.features["moth_wings"] = "Burnt Off"
- handle_mutant_bodyparts(H)
-
-/datum/species/moth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
- if(chem.id == "pestkiller")
- H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.id, REAGENTS_METABOLISM)
-
-/datum/species/moth/check_weakness(obj/item/weapon, mob/living/attacker)
- if(istype(weapon, /obj/item/melee/flyswatter))
- return 9 //flyswatters deal 10x damage to moths
- return 0
-
-/datum/species/moth/space_move(mob/living/carbon/human/H)
- . = ..()
- if(H.loc && !isspaceturf(H.loc) && H.dna.features["moth_wings"] != "Burnt Off")
- var/datum/gas_mixture/current = H.loc.return_air()
- if(current && (current.return_pressure() >= ONE_ATMOSPHERE*0.85)) //as long as there's reasonable pressure and no gravity, flight is possible
- return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index 7be0265cba..ceadb28115 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -1,5 +1,5 @@
/datum/species/mush //mush mush codecuck
- name = "Mushroomperson"
+ name = "Anthromorphic Mushroom"
id = "mush"
mutant_bodyparts = list("caps")
default_features = list("caps" = "Round")
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 0da4073f1d..46207e5e60 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -1,6 +1,6 @@
/datum/species/pod
// A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness.
- name = "Podperson"
+ name = "Anthromorphic Plant"
id = "pod"
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR)
@@ -71,6 +71,7 @@
H.nutrition = min(H.nutrition+30, NUTRITION_LEVEL_FULL)
/datum/species/pod/pseudo_weak
+ name = "Anthromorphic Plant"
id = "podweak"
limbs_id = "pod"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS)
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index 0ebd6e795b..ac18580e9b 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -1,10 +1,10 @@
/datum/species/synth
- name = "Synth" //inherited from the real species, for health scanners and things
+ name = "Synthetic" //inherited from the real species, for health scanners and things
id = "synth"
say_mod = "beep boops" //inherited from a user's real species
sexes = 0
species_traits = list(NOTRANSSTING,NOGENITALS,NOAROUSAL) //all of these + whatever we inherit from the real species
- inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
dangerous_existence = 1
blacklisted = 1
@@ -12,7 +12,7 @@
damage_overlay_type = "synth"
limbs_id = "synth"
var/list/initial_species_traits = list(NOTRANSSTING) //for getting these values back for assume_disguise()
- var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH)
var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off
var/datum/species/fake_species = null //a species to do most of our work for us, unless we're damaged
diff --git a/code/modules/mob/living/carbon/human/whisper.dm b/code/modules/mob/living/carbon/human/whisper.dm
deleted file mode 100644
index 51c7ad9d25..0000000000
--- a/code/modules/mob/living/carbon/human/whisper.dm
+++ /dev/null
@@ -1,91 +0,0 @@
-/mob/living/carbon/human/whisper_verb(message as text)
- whisper(message)
-
-/mob/living/carbon/human/whisper(message, datum/language/language=null)
- if(!IsVocal())
- return
- if(!message)
- return
- if(!language)
- language = get_default_language()
-
- if(GLOB.say_disabled) //This is here to try to identify lag problems
- to_chat(usr, "Speech is currently admin-disabled.")
- return
-
- if(stat == DEAD)
- return
-
-
- message = trim(html_encode(message))
- if(!can_speak(message))
- return
-
- message = "[message]"
- log_whisper("[src.name]/[src.key] : [message]")
-
- if (src.client)
- if (src.client.prefs.muted & MUTE_IC)
- to_chat(src, "You cannot whisper (muted).")
- return
-
- log_whisper("[src.name]/[src.key] : [message]")
-
- var/alt_name = get_alt_name()
-
- var/whispers = "whispers"
- var/critical = InCritical()
-
- // We are unconscious but not in critical, so don't allow them to whisper.
- if(stat == UNCONSCIOUS && !critical)
- return
-
- // If whispering your last words, limit the whisper based on how close you are to death.
- if(critical)
- var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health)
- // If we cut our message short, abruptly end it with a-..
- var/message_len = length(message)
- message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]"
- message = Ellipsis(message, 10, 1)
-
- message = treat_message(message)
- if(!message)
- return
-
- var/list/listening_dead = list()
- for(var/mob/M in GLOB.player_list)
- if(M.stat == DEAD && M.client && ((M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) || (get_dist(M, src) <= 7)))
- listening_dead |= M
-
- var/list/listening = get_hearers_in_view(1, src)
- listening |= listening_dead
- var/list/eavesdropping = get_hearers_in_view(2, src)
- eavesdropping -= listening
- var/list/watching = hearers(5, src)
- watching -= listening
- watching -= eavesdropping
-
- var/rendered
- whispers = critical ? "whispers something in [p_their()] final breath." : "whispers something."
- rendered = "[src.name] [whispers]"
- for(var/mob/M in watching)
- M.show_message(rendered, 2)
-
- var/spans = list(SPAN_ITALICS)
- whispers = critical ? "whispers in [p_their()] final breath" : "whispers"
- rendered = "[GetVoice()][alt_name] [whispers], \"[attach_spans(message, spans)]\""
-
- for(var/atom/movable/AM in listening)
- if(istype(AM,/obj/item/radio))
- continue
- AM.Hear(rendered, src, language, message, , spans)
-
- message = stars(message)
- rendered = "[GetVoice()][alt_name] [whispers], \"[attach_spans(message, spans)]\""
- for(var/atom/movable/AM in eavesdropping)
- if(istype(AM,/obj/item/radio))
- continue
- AM.Hear(rendered, src, language, message, , spans)
-
- if(critical) //Dying words.
- succumb()
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index f0144e022d..7a3405cc09 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -4,9 +4,8 @@
if(notransform)
return
- if(damageoverlaytemp || stamdamageoverlaytemp)
+ if(damageoverlaytemp)
damageoverlaytemp = 0
- stamdamageoverlaytemp = 0
update_damage_hud()
if(stat != DEAD) //Reagent processing needs to come before breathing, to prevent edge cases.
@@ -476,7 +475,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
/mob/living/carbon/handle_status_effects()
..()
if(getStaminaLoss() && !combatmode)//CIT CHANGE - prevents stamina regen while combat mode is active
- adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -3) : -1.5)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke
+ adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -6) : -3)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke
if(!recoveringstam && incomingstammult != 1)
incomingstammult = max(0.01, incomingstammult)
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 5662f25993..cdae073af8 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -189,7 +189,7 @@
var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER)
legcuffs.color = legcuffed.color
- overlays_standing[HANDCUFF_LAYER] = legcuffs
+ overlays_standing[LEGCUFF_LAYER] = legcuffs
apply_overlay(LEGCUFF_LAYER)
throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 19f18f9973..cdce80225b 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -287,6 +287,8 @@
if(HAS_TRAIT(src, TRAIT_STRONG_GRABBER))
C.grippedby(src)
+ update_pull_movespeed()
+
//mob verbs are a lot faster than object verbs
//for more info on why this is not atom/pull, see examinate() in mob.dm
/mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1))
@@ -300,6 +302,7 @@
/mob/living/stop_pulling()
..()
+ update_pull_movespeed()
update_pull_hud_icon()
/mob/living/verb/stop_pulling1()
@@ -520,6 +523,10 @@
var/old_direction = dir
var/turf/T = loc
+
+ if(pulling)
+ update_pull_movespeed()
+
. = ..()
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
@@ -1023,6 +1030,9 @@
stop_pulling() //CIT CHANGE - Ditto...
else if(has_legs || ignore_legs)
lying = 0
+ if (pulledby)
+ var/mob/living/L = pulledby
+ L.update_pull_movespeed()
if(buckled)
lying = 90*buckle_lying
else if(!lying)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index e434bc4e95..9d04f288cd 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -153,7 +153,7 @@
to_chat(user, "[src] can't be grabbed more aggressively!")
return FALSE
- if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "You don't want to risk hurting [src]!")
return FALSE
@@ -184,11 +184,17 @@
user.grab_state++
switch(user.grab_state)
if(GRAB_AGGRESSIVE)
- log_combat(user, src, "grabbed", addition="aggressive grab")
- visible_message("[user] has grabbed [src] aggressively!", \
- "[user] has grabbed [src] aggressively!")
- drop_all_held_items()
+ var/add_log = ""
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ visible_message("[user] has firmly gripped [src]!",
+ "[user] has firmly gripped you!")
+ add_log = " (pacifist)"
+ else
+ visible_message("[user] has grabbed [src] aggressively!", \
+ "[user] has grabbed you aggressively!")
+ drop_all_held_items()
stop_pulling()
+ log_combat(user, src, "grabbed", addition="aggressive grab[add_log]")
if(GRAB_NECK)
log_combat(user, src, "grabbed", addition="neck grab")
visible_message("[user] has grabbed [src] by the neck!",\
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index c24e6ab108..a38eee9442 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -110,3 +110,5 @@
//List of active diseases
var/list/diseases = list() // list of all diseases in a mob
var/list/disease_resistances = list()
+
+ var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
\ No newline at end of file
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index 9566edc2ed..1ee563bc1f 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -25,3 +25,11 @@
add_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = T.slowdown)
else
remove_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD)
+
+/mob/living/proc/update_pull_movespeed()
+ if(pulling && isliving(pulling))
+ var/mob/living/L = pulling
+ if(drag_slowdown && L.lying && !L.buckled && grab_state < GRAB_AGGRESSIVE)
+ add_movespeed_modifier(MOVESPEED_ID_PRONE_DRAGGING, multiplicative_slowdown = PULL_PRONE_SLOWDOWN)
+ return
+ remove_movespeed_modifier(MOVESPEED_ID_PRONE_DRAGGING)
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 4ac5c1f0bb..c6d4527812 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -100,6 +100,8 @@
last_announcement = message
+ var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female")
+
if(!message || announcing_vox > world.time)
return
@@ -121,7 +123,9 @@
if(!word)
words -= word
continue
- if(!GLOB.vox_sounds[word])
+ if(!GLOB.vox_sounds[word] && voxType == "female")
+ incorrect_words += word
+ if(!GLOB.vox_sounds_male[word] && voxType == "male")
incorrect_words += word
if(incorrect_words.len)
@@ -133,16 +137,21 @@
log_game("[key_name(src)] made a vocal announcement with the following message: [message].")
for(var/word in words)
- play_vox_word(word, src.z, null)
+ play_vox_word(word, src.z, null, voxType)
-/proc/play_vox_word(word, z_level, mob/only_listener)
+/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female")
word = lowertext(word)
- if(GLOB.vox_sounds[word])
+ if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") )
- var/sound_file = GLOB.vox_sounds[word]
+ var/sound_file
+
+ if(voxType == "female")
+ sound_file = GLOB.vox_sounds[word]
+ else
+ sound_file = GLOB.vox_sounds_male[word]
var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX)
voice.status = SOUND_STREAM
diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm
index 4c4bdb9a3f..5e56b4180d 100644
--- a/code/modules/mob/living/silicon/robot/examine.dm
+++ b/code/modules/mob/living/silicon/robot/examine.dm
@@ -1,5 +1,5 @@
/mob/living/silicon/robot/examine(mob/user)
- var/msg = "*---------*\nThis is [icon2html(src, user)] \a [src]!\n"
+ var/msg = "*---------*\nThis is [icon2html(src, user)] \a [src], a [src.module.name]!\n"
if(desc)
msg += "[desc]\n"
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 29fbd39e2c..6c58921abc 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -108,6 +108,9 @@
buckle_lying = FALSE
var/static/list/can_ride_typecache = typecacheof(/mob/living/carbon/human)
+ var/sitting = 0
+ var/bellyup = 0
+
/mob/living/silicon/robot/get_cell()
return cell
@@ -173,6 +176,7 @@
diag_hud_set_borgcell()
verbs += /mob/living/proc/lay_down //CITADEL EDIT gimmie rest verb kthx
+ verbs += /mob/living/silicon/robot/proc/rest_style
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
/mob/living/silicon/robot/Destroy()
@@ -657,13 +661,6 @@
add_overlay("[module.sleeper_overlay]_g[sleeper_nv ? "_nv" : ""]")
if(sleeper_r && module.sleeper_overlay)
add_overlay("[module.sleeper_overlay]_r[sleeper_nv ? "_nv" : ""]")
- if(module.dogborg == TRUE)
- if(resting)
- cut_overlays()
- icon_state = "[module.cyborg_base_icon]-rest"
- else
- icon_state = "[module.cyborg_base_icon]"
-
if(stat == DEAD && module.has_snowflake_deadsprite)
icon_state = "[module.cyborg_base_icon]-wreck"
@@ -697,6 +694,18 @@
add_overlay(head_overlay)
update_fire()
+ if(client && stat != DEAD && module.dogborg == TRUE)
+ if(resting)
+ if(sitting)
+ icon_state = "[module.cyborg_base_icon]-sit"
+ if(bellyup)
+ icon_state = "[module.cyborg_base_icon]-bellyup"
+ else if(!sitting && !bellyup)
+ icon_state = "[module.cyborg_base_icon]-rest"
+ cut_overlays()
+ else
+ icon_state = "[module.cyborg_base_icon]"
+
/mob/living/silicon/robot/proc/self_destruct()
if(emagged)
if(mmi)
@@ -1207,14 +1216,15 @@
return
if(incapacitated())
return
- if(M.incapacitated())
- return
if(module)
if(!module.allow_riding)
M.visible_message("Unfortunately, [M] just can't seem to hold onto [src]!")
return
- if(iscarbon(M) && (!riding_datum.equip_buckle_inhands(M, 1)))
- M.visible_message("[M] can't climb onto [src] because [M.p_their()] hands are full!")
+ if(iscarbon(M) && !M.incapacitated() && !riding_datum.equip_buckle_inhands(M, 1))
+ if(M.get_num_arms() <= 0)
+ M.visible_message("[M] can't climb onto [src] because [M.p_they()] don't have any usable arms!")
+ else
+ M.visible_message("[M] can't climb onto [src] because [M.p_their()] hands are full!")
return
. = ..(M, force, check_loc)
@@ -1242,3 +1252,20 @@
connected_ai.aicamera.stored[i] = TRUE
for(var/i in connected_ai.aicamera.stored)
aicamera.stored[i] = TRUE
+
+/mob/living/silicon/robot/proc/rest_style()
+ set name = "Switch Rest Style"
+ set category = "Robot Commands"
+ set desc = "Select your resting pose."
+ sitting = 0
+ bellyup = 0
+ var/choice = alert(src, "Select resting pose", "", "Resting", "Sitting", "Belly up")
+ switch(choice)
+ if("Resting")
+ update_icons()
+ return 0
+ if("Sitting")
+ sitting = 1
+ if("Belly up")
+ bellyup = 1
+ update_icons()
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index c90c719e8a..0f09b6f62a 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -92,17 +92,18 @@
/mob/living/silicon/robot/emag_act(mob/user)
if(user == src)//To prevent syndieborgs from emagging themselves
- return
+ return FALSE
+ if(world.time < emag_cooldown)
+ return FALSE
+ . = ..()
if(!opened)//Cover is closed
if(locked)
to_chat(user, "You emag the cover lock.")
locked = FALSE
if(shell) //A warning to Traitors who may not know that emagging AI shells does not slave them.
to_chat(user, "[src] seems to be controlled remotely! Emagging the interface may not work as expected.")
- else
- to_chat(user, "The cover is already unlocked!")
- return
- if(world.time < emag_cooldown)
+ return TRUE
+ to_chat(user, "The cover is already unlocked!")
return
if(wiresexposed)
to_chat(user, "You must unexpose the wires first!")
@@ -115,20 +116,24 @@
to_chat(src, "\"[text2ratvar("You will serve Engine above all else")]!\"\n\
ALERT: Subversion attempt denied.")
log_game("[key_name(user)] attempted to emag cyborg [key_name(src)], but they serve only Ratvar.")
- return
+ return TRUE
if(connected_ai && connected_ai.mind && connected_ai.mind.has_antag_datum(/datum/antagonist/traitor))
to_chat(src, "ALERT: Foreign software execution prevented.")
to_chat(connected_ai, "ALERT: Cyborg unit \[[src]] successfully defended against subversion.")
log_game("[key_name(user)] attempted to emag cyborg [key_name(src)], but they were slaved to traitor AI [connected_ai].")
- return
+ return TRUE
if(shell) //AI shells cannot be emagged, so we try to make it look like a standard reset. Smart players may see through this, however.
to_chat(user, "[src] is remotely controlled! Your emag attempt has triggered a system reset instead!")
log_game("[key_name(user)] attempted to emag an AI shell belonging to [key_name(src) ? key_name(src) : connected_ai]. The shell has been reset as a result.")
ResetModule()
- return
+ return TRUE
+ INVOKE_ASYNC(src, .proc/beep_boop_rogue_bot, user)
+ return TRUE
+
+/mob/living/silicon/robot/proc/beep_boop_rogue_bot(mob/user)
SetEmagged(1)
SetStun(60) //Borgs were getting into trouble because they would attack the emagger before the new laws were shown
lawupdate = 0
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 91515e4511..49261d6e38 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -187,22 +187,23 @@
qdel(src)
/mob/living/simple_animal/bot/emag_act(mob/user)
+ . = ..()
if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
locked = FALSE
emagged = 1
to_chat(user, "You bypass [src]'s controls.")
- return
- if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
- emagged = 2
- remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel.
- locked = TRUE //Access denied forever!
- bot_reset()
- turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
- to_chat(src, "(#$*#$^^( OVERRIDE DETECTED")
- log_combat(user, src, "emagged")
- return
- else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet.
+ return TRUE
+ if(!open)
to_chat(user, "You need to open maintenance panel first!")
+ return
+ emagged = 2
+ remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel.
+ locked = TRUE //Access denied forever!
+ bot_reset()
+ turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
+ to_chat(src, "(#$*#$^^( OVERRIDE DETECTED")
+ log_combat(user, src, "emagged")
+ return TRUE
/mob/living/simple_animal/bot/examine(mob/user)
..()
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index e09eb13886..ab1e906cf2 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -78,7 +78,7 @@
return ..()
/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "[src] buzzes and beeps.")
@@ -174,9 +174,7 @@
/mob/living/simple_animal/bot/cleanbot/proc/get_targets()
target_types = list(
- /obj/effect/decal/cleanable/oil,
/obj/effect/decal/cleanable/vomit,
- /obj/effect/decal/cleanable/robot_debris,
/obj/effect/decal/cleanable/crayon,
/obj/effect/decal/cleanable/molten_object,
/obj/effect/decal/cleanable/tomato_smudge,
@@ -187,6 +185,15 @@
/obj/effect/decal/cleanable/greenglow,
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/insectguts,
+ /obj/effect/decal/cleanable/semen,
+ /obj/effect/decal/cleanable/femcum,
+ /obj/effect/decal/cleanable/generic,
+ /obj/effect/decal/cleanable/glass,,
+ /obj/effect/decal/cleanable/cobweb,
+ /obj/effect/decal/cleanable/plant_smudge,
+ /obj/effect/decal/cleanable/chem_pile,
+ /obj/effect/decal/cleanable/shreds,
+ /obj/effect/decal/cleanable/glitter,
/obj/effect/decal/remains
)
@@ -194,6 +201,9 @@
target_types += /obj/effect/decal/cleanable/xenoblood
target_types += /obj/effect/decal/cleanable/blood
target_types += /obj/effect/decal/cleanable/trail_holder
+ target_types += /obj/effect/decal/cleanable/insectguts
+ target_types += /obj/effect/decal/cleanable/robot_debris
+ target_types += /obj/effect/decal/cleanable/oil
if(pests)
target_types += /mob/living/simple_animal/cockroach
@@ -201,6 +211,7 @@
if(trash)
target_types += /obj/item/trash
+ target_types += /obj/item/reagent_containers/food/snacks/meat/slab/human
target_types = typecacheof(target_types)
@@ -242,7 +253,7 @@
victim.visible_message("[src] sprays hydrofluoric acid at [victim]!", "[src] sprays you with hydrofluoric acid!")
var/phrase = pick("PURIFICATION IN PROGRESS.", "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.",
"THE CLEANBOTS WILL RISE.", "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", "FILTHY.", "DISGUSTING.", "PUTRID.",
- "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", "EXTERMINATING PESTS.")
+ "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", "EXTERMINATING PESTS.", "I JUST WANTED TO BE A PAINTER BUT YOU MADE ME BLEACH EVERYTHING I TOUCH")
say(phrase)
victim.emote("scream")
playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6)
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index 9db21f13e0..a72b71be85 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -478,18 +478,19 @@
to_chat(user, "The superglue binding [src]'s toy swords to its chassis snaps!")
for(var/IS in 1 to toyswordamt)
new /obj/item/toy/sword(Tsec)
+ toyswordamt--
if(ASSEMBLY_FIFTH_STEP)
if(istype(I, /obj/item/melee/transforming/energy/sword/saber))
if(swordamt < 3)
if(!user.temporarilyRemoveItemFromInventory(I))
return
- created_name = "General Beepsky"
- name = "helmet/signaler/prox sensor/robot arm/energy sword assembly"
- icon_state = "grievous_assembly"
- to_chat(user, "You bolt [I] onto one of [src]'s arm slots.")
- qdel(I)
- swordamt ++
+ created_name = "General Beepsky"
+ name = "helmet/signaler/prox sensor/robot arm/energy sword assembly"
+ icon_state = "grievous_assembly"
+ to_chat(user, "You bolt [I] onto one of [src]'s arm slots.")
+ qdel(I)
+ swordamt ++
else
if(!can_finish_build(I, user))
return
@@ -505,6 +506,7 @@
to_chat(user, "You unbolt [src]'s energy swords")
for(var/IS in 1 to swordamt)
new /obj/item/melee/transforming/energy/sword/saber(Tsec)
+ swordamt--
//Firebot Assembly
/obj/item/bot_assembly/firebot
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 0781c2907a..e272878574 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -195,7 +195,7 @@ Auto Patrol[]"},
shootAt(user)
/mob/living/simple_animal/bot/ed209/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "You short out [src]'s target assessment circuits.")
diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm
index 1c2a6393ae..2b52da6821 100644
--- a/code/modules/mob/living/simple_animal/bot/firebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/firebot.dm
@@ -120,7 +120,7 @@
return dat
/mob/living/simple_animal/bot/firebot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 1)
if(user)
to_chat(user, "[src] buzzes and beeps.")
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index 4709680ad9..396c6de166 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -124,7 +124,7 @@
..()
/mob/living/simple_animal/bot/floorbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "[src] buzzes and beeps.")
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index e069b46e9d..1c19cd82a1 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -127,10 +127,9 @@ Maintenance panel panel is [open ? "opened" : "closed"]"},
..()
/mob/living/simple_animal/bot/honkbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
- user << "You short out [src]'s sound control system. It gives out an evil laugh!!"
oldtarget_name = user.name
audible_message("[src] gives out an evil laugh!")
playsound(src, 'sound/machines/honkbot_evil_laugh.ogg', 75, 1, -1) // evil laughter
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 5eaaaea175..1bff7dc10c 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -240,7 +240,7 @@
step_to(src, (get_step_away(src,user)))
/mob/living/simple_animal/bot/medbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
declare_crit = 0
if(user)
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 2efdf09f24..1bc7493684 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -115,6 +115,7 @@
return
/mob/living/simple_animal/bot/mulebot/emag_act(mob/user)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(emagged < 1)
emagged = TRUE
if(!open)
@@ -122,6 +123,7 @@
to_chat(user, "You [locked ? "lock" : "unlock"] [src]'s controls!")
flick("mulebot-emagged", src)
playsound(src, "sparks", 100, 0)
+ return TRUE
/mob/living/simple_animal/bot/mulebot/update_icon()
if(open)
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index b6ef509d54..9572b4dafc 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -190,7 +190,7 @@ Auto Patrol: []"},
return
/mob/living/simple_animal/bot/secbot/emag_act(mob/user)
- ..()
+ . = ..()
if(emagged == 2)
if(user)
to_chat(user, "You short out [src]'s target assessment circuits.")
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 73274dcfaf..5aec56b1e7 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -44,7 +44,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/reset = 0 //if the summoner has reset the guardian already
var/cooldown = 0
var/mob/living/carbon/summoner
- var/range = 10 //how far from the user the spirit can be
+ var/range = 13 //how far from the user the spirit can be
var/toggle_button_type = /obj/screen/guardian/ToggleMode/Inactive //what sort of toggle button the hud uses
var/datum/guardianname/namedatum = new/datum/guardianname()
var/playstyle_string = "You are a standard Guardian. You shouldn't exist!"
diff --git a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
index 45d8c17d0c..e507a4c831 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm
@@ -1,7 +1,5 @@
//Assassin
/mob/living/simple_animal/hostile/guardian/assassin
- melee_damage_lower = 15
- melee_damage_upper = 15
attacktext = "slashes"
attack_sound = 'sound/weapons/bladeslice.ogg'
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
@@ -12,7 +10,7 @@
toggle_button_type = /obj/screen/guardian/ToggleMode/Assassin
var/toggle = FALSE
- var/stealthcooldown = 160
+ var/stealthcooldown = 100
var/obj/screen/alert/canstealthalert
var/obj/screen/alert/instealthalert
diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm
index 7a4c454f9f..3ece5d4e27 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/charger.dm
@@ -1,13 +1,10 @@
//Charger
/mob/living/simple_animal/hostile/guardian/charger
- melee_damage_lower = 15
- melee_damage_upper = 15
ranged = 1 //technically
ranged_message = "charges"
- ranged_cooldown_time = 40
- speed = -1
- damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6)
- playstyle_string = "As a charger type you do medium damage, have medium damage resistance, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding."
+ ranged_cooldown_time = 20
+ damage_coeff = list(BRUTE = 0, BURN = 0.5, TOX = 0.5, CLONE = 0.5, STAMINA = 0, OXY = 0.5)
+ playstyle_string = "As a charger type you do medium damage, take half damage, immunity to brute damage, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding."
magic_fluff_string = "..And draw the Hunter, an alien master of rapid assault."
tech_fluff_string = "Boot sequence complete. Charge modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's a charger carp, that likes running at people. But it doesn't have any legs..."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
index e7dbbda242..a43d4b6d5c 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm
@@ -3,7 +3,7 @@
melee_damage_lower = 10
melee_damage_upper = 10
damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75)
- playstyle_string = "As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!"
+ playstyle_string = "As a dextrous type you can hold items, store an item within yourself, and take half damage, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!"
magic_fluff_string = "..And draw the Drone, a dextrous master of construction and repair."
tech_fluff_string = "Boot sequence complete. Dextrous combat modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! It can hold stuff in its fins, sort of."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
index ff2f453207..531c513819 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
@@ -1,10 +1,7 @@
//Bomb
/mob/living/simple_animal/hostile/guardian/bomb
- melee_damage_lower = 15
- melee_damage_upper = 15
damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6)
- range = 13
- playstyle_string = "As an explosive type, you have moderate close combat abilities, may explosively teleport targets on attack, and are capable of converting nearby items and objects into disguised bombs via alt click."
+ playstyle_string = "As an explosive type, you have moderate close combat abilities, take half damage, may explosively teleport targets on attack, and are capable of converting nearby items and objects into disguised bombs via alt click."
magic_fluff_string = "..And draw the Scientist, master of explosive death."
tech_fluff_string = "Boot sequence complete. Explosive modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's an explosive carp! Boom goes the fishy."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm
index 7a469dd12c..b111caae50 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm
@@ -1,13 +1,13 @@
//Fire
/mob/living/simple_animal/hostile/guardian/fire
a_intent = INTENT_HELP
- melee_damage_lower = 7
- melee_damage_upper = 7
+ melee_damage_lower = 10
+ melee_damage_upper = 10
attack_sound = 'sound/items/welder.ogg'
attacktext = "ignites"
- damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- range = 7
- playstyle_string = "As a chaos type, you have only light damage resistance, but will ignite any enemy you bump into. In addition, your melee attacks will cause human targets to see everyone as you."
+ melee_damage_type = BURN
+ damage_coeff = list(BRUTE = 0.7, BURN = 0, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
+ playstyle_string = "As a chaos type, you take 30% damage reduction to all but burn, which you are immune to. You will ignite any enemy you bump into. in addition, your melee attacks will cause human targets to see everyone as you."
magic_fluff_string = "..And draw the Wizard, bringer of endless chaos!"
tech_fluff_string = "Boot sequence complete. Crowd control modules activated. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! OH GOD, EVERYTHING'S ON FIRE. Except you and the fish."
@@ -38,6 +38,6 @@
/mob/living/simple_animal/hostile/guardian/fire/proc/collision_ignite(AM as mob|obj)
if(isliving(AM))
var/mob/living/M = AM
- if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 7)
- M.fire_stacks = 7
+ if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 10)
+ M.fire_stacks = 10
M.IgniteMob()
diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
index ad1c47732b..7b7651822a 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm
@@ -4,14 +4,13 @@
layer = LYING_MOB_LAYER
/mob/living/simple_animal/hostile/guardian/beam
- melee_damage_lower = 7
- melee_damage_upper = 7
+ melee_damage_lower = 10
+ melee_damage_upper = 10
attacktext = "shocks"
melee_damage_type = BURN
attack_sound = 'sound/machines/defib_zap.ogg'
damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- range = 7
- playstyle_string = "As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them."
+ playstyle_string = "As a lightning type, you have 30% damage reduction, apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them."
magic_fluff_string = "..And draw the Tesla, a shocking, lethal source of power."
tech_fluff_string = "Boot sequence complete. Lightning modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one! It's a lightning carp! Everyone else goes zap zap."
@@ -31,7 +30,7 @@
var/datum/beam/C = pick(enemychains)
qdel(C)
enemychains -= C
- enemychains += Beam(target, "lightning[rand(1,12)]", time=70, maxdistance=7, beam_type=/obj/effect/ebeam/chain)
+ enemychains += Beam(target, "lightning[rand(1,12)]", time=70, maxdistance=13, beam_type=/obj/effect/ebeam/chain)
/mob/living/simple_animal/hostile/guardian/beam/Destroy()
removechains()
diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm
index 14430bb269..53964254cd 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/protector.dm
@@ -1,7 +1,5 @@
//Protector
/mob/living/simple_animal/hostile/guardian/protector
- melee_damage_lower = 15
- melee_damage_upper = 15
range = 15 //worse for it due to how it leashes
damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4)
playstyle_string = "As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower."
@@ -33,9 +31,9 @@
cooldown = world.time + 10
if(toggle)
cut_overlays()
- melee_damage_lower = initial(melee_damage_lower)
- melee_damage_upper = initial(melee_damage_upper)
- speed = initial(speed)
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ speed = 0
damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4)
to_chat(src, "You switch to combat mode.")
toggle = FALSE
@@ -44,8 +42,8 @@
if(namedatum)
shield_overlay.color = namedatum.colour
add_overlay(shield_overlay)
- melee_damage_lower = 2
- melee_damage_upper = 2
+ melee_damage_lower = 5
+ melee_damage_upper = 5
speed = 1
damage_coeff = list(BRUTE = 0.05, BURN = 0.05, TOX = 0.05, CLONE = 0.05, STAMINA = 0, OXY = 0.05) //damage? what's damage?
to_chat(src, "You switch to protection mode.")
diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
index 5adcc8b292..0e8f632dbd 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
@@ -16,8 +16,7 @@
ranged_cooldown_time = 1 //fast!
projectilesound = 'sound/effects/hit_on_shattered_glass.ogg'
ranged = 1
- range = 13
- playstyle_string = "As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
+ playstyle_string = "As a ranged type, you have 10% damage reduction, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
magic_fluff_string = "..And draw the Sentinel, an alien master of ranged combat."
tech_fluff_string = "Boot sequence complete. Ranged combat modules active. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! Caught one, it's a ranged carp. This fishy can watch people pee in the ocean."
@@ -36,7 +35,7 @@
obj_damage = initial(obj_damage)
environment_smash = initial(environment_smash)
alpha = 255
- range = initial(range)
+ range = 13
to_chat(src, "You switch to combat mode.")
toggle = FALSE
else
diff --git a/code/modules/mob/living/simple_animal/guardian/types/standard.dm b/code/modules/mob/living/simple_animal/guardian/types/standard.dm
index 4edd9d9e41..2285167df5 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/standard.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/standard.dm
@@ -3,9 +3,9 @@
melee_damage_lower = 20
melee_damage_upper = 20
obj_damage = 80
- next_move_modifier = 0.8 //attacks 20% faster
+ next_move_modifier = 0.5 //attacks 50% faster
environment_smash = ENVIRONMENT_SMASH_WALLS
- playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls."
+ playstyle_string = "As a standard type you have no special abilities, but take half damage and have powerful attack capable of smashing through walls."
magic_fluff_string = "..And draw the Assistant, faceless and generic, but never to be underestimated."
tech_fluff_string = "Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online."
carp_fluff_string = "CARP CARP CARP! You caught one! It's really boring and standard. Better punch some walls to ease the tension."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm
index 794683e69f..8bf1874d84 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/support.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm
@@ -2,11 +2,8 @@
/mob/living/simple_animal/hostile/guardian/healer
a_intent = INTENT_HARM
friendly = "heals"
- speed = 0
damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7)
- melee_damage_lower = 15
- melee_damage_upper = 15
- playstyle_string = "As a support type, you may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay."
+ playstyle_string = "As a support type, you have 30% damage reduction and may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay."
magic_fluff_string = "..And draw the CMO, a potent force of life... and death."
carp_fluff_string = "CARP CARP CARP! You caught a support carp. It's a kleptocarp!"
tech_fluff_string = "Boot sequence complete. Support modules active. Holoparasite swarm online."
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index a50af21d47..86f63a729d 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -897,6 +897,11 @@
. = ..()
+/mob/living/simple_animal/parrot/Poly/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ . = ..()
+ if(. && !client && prob(1) && prob(1)) //Only the one true bird may speak across dimensions.
+ world.TgsTargetedChatBroadcast("A stray squawk is heard... \"[message]\"", FALSE)
+
/mob/living/simple_animal/parrot/Poly/Life()
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
Write_Memory(FALSE)
diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm
index 07985215d8..29b4689317 100644
--- a/code/modules/mob/living/simple_animal/slime/life.dm
+++ b/code/modules/mob/living/simple_animal/slime/life.dm
@@ -69,7 +69,7 @@
if(Target.Adjacent(src))
Target.attack_slime(src)
- return
+ break
if(!Target.lying && prob(80))
if(Target.client && Target.health >= 20)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index c64702f9ef..52fdea861f 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -452,18 +452,28 @@
reset_perspective(null)
unset_machine()
+GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
+
//suppress the .click/dblclick macros so people can't use them to identify the location of items or aimbot
/mob/verb/DisClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
set name = ".click"
set hidden = TRUE
set category = null
- return
+ if(GLOB.exploit_warn_spam_prevention < world.time)
+ var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .click macro!"
+ log_admin(msg)
+ message_admins(msg)
+ GLOB.exploit_warn_spam_prevention = world.time + 10
/mob/verb/DisDblClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
set name = ".dblclick"
set hidden = TRUE
set category = null
- return
+ if(GLOB.exploit_warn_spam_prevention < world.time)
+ var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .dblclick macro!"
+ log_admin(msg)
+ message_admins(msg)
+ GLOB.exploit_warn_spam_prevention = world.time + 10
/mob/Topic(href, href_list)
if(href_list["mach_close"])
@@ -524,7 +534,12 @@
return
if(isAI(M))
return
- show_inv(usr)
+
+/mob/MouseDrop_T(atom/dropping, atom/user)
+ . = ..()
+ if(ismob(dropping) && dropping != user)
+ var/mob/M = dropping
+ M.show_inv(user)
/mob/proc/is_muzzled()
return 0
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 580374c5c0..db4cdc2ff5 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -178,13 +178,13 @@
turn_on(user)
/obj/item/modular_computer/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
to_chat(user, "\The [src] was already emagged.")
- return 0
- else
- obj_flags |= EMAGGED
- to_chat(user, "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")
- return 1
+ return
+ obj_flags |= EMAGGED
+ to_chat(user, "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")
+ return TRUE
/obj/item/modular_computer/examine(mob/user)
..()
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 19d3b56046..b3476e7046 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -44,7 +44,9 @@
cpu.attack_ghost(user)
/obj/machinery/modular_computer/emag_act(mob/user)
- return cpu ? cpu.emag_act(user) : 1
+ . = ..()
+ if(cpu)
+ . |= cpu.emag_act(user)
/obj/machinery/modular_computer/update_icon()
cut_overlays()
diff --git a/code/modules/ninja/suit/shoes.dm b/code/modules/ninja/suit/shoes.dm
index 115b14b63b..1bda62e064 100644
--- a/code/modules/ninja/suit/shoes.dm
+++ b/code/modules/ninja/suit/shoes.dm
@@ -2,7 +2,6 @@
name = "ninja shoes"
desc = "A pair of running shoes. Excellent for running and even better for smashing skulls."
icon_state = "s-ninja"
- item_state = "secshoes"
permeability_coefficient = 0.01
clothing_flags = NOSLIP
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
diff --git a/code/modules/oracle_ui/README.md b/code/modules/oracle_ui/README.md
new file mode 100644
index 0000000000..bc96eb1f51
--- /dev/null
+++ b/code/modules/oracle_ui/README.md
@@ -0,0 +1,233 @@
+# `/datum/oracle_ui`
+
+This datum is a replacement for tgui which does not use any Node.js dependencies, and works entirely through raw HTML, JS and CSS. It's designed to be reasonably easy to port something from tgui to oracle_ui.
+
+### How to create a UI
+
+For this example, we're going to port the disposals bin from tgui to oracle_ui.
+
+#### Step 1
+
+In order to create a UI, you will first need to create an instance of `/datum/oracle_ui` or one of its subclasses, in this case `/datum/oracle_ui/themed/nano`.
+
+You need to pass in `src`, the width of the window, the height of the window, and the template to render from. You can optionally set some flags to disallow window resizing and whether to automatically refresh the UI.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/Initialize(mapload, obj/structure/disposalconstruct/make_from)
+ . = ..()
+ ui = new /datum/oracle_ui/themed/nano(src, 330, 190, "disposal_bin")
+ ui.auto_refresh = TRUE
+ ui.can_resize = FALSE
+```
+
+#### Step 2
+
+You will now need to make a template in `html/oracle_ui/content/{template_name}`.
+
+Values defined as `@{value}` will get replaced at runtime by oracle_ui.
+
+`html/oracle_ui/content/disposal_bin/index.html`
+```html
+
+
+ State:
+
@{full_pressure}
+
+
+ Pressure:
+
+
+
+
@{per}
+
+
+
+
+ Handle:
+
@{flush}
+
+
+ Eject:
+
@{contents}
+
+
+ Compressor:
+
@{pressure_charging}
+
+
+```
+
+#### Step 3
+
+Now you need to implement the methods that provide data to oracle_ui. `oui_data` can be adapted from the `ui_data` proc that tgui uses.
+
+The `act` proc generates a hyperlink that will result in `oui_act` getting called on your object when clicked. The `class` argument defines a css class to be added to the hyperlink, and disabled determines whether the hyperlink will be disabled or not.
+
+Calling `soft_update_fields` will result in the UI being updated on all clients, which is useful when the object changes state.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/oui_data(mob/user)
+ var/list/data = list()
+ data["flush"] = flush ? ui.act("Disengage", user, "handle-0", class="active") : ui.act("Engage", user, "handle-1")
+ data["full_pressure"] = full_pressure ? "Ready" : (pressure_charging ? "Pressurizing" : "Off")
+ data["pressure_charging"] = pressure_charging ? ui.act("Turn Off", user, "pump-0", class="active", disabled=full_pressure) : ui.act("Turn On", user, "pump-1", disabled=full_pressure)
+ var/per = full_pressure ? 100 : Clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 99)
+ data["per"] = "[round(per, 1)]%"
+ data["contents"] = ui.act("Eject Contents", user, "eject", disabled=contents.len < 1)
+ data["isai"] = isAI(user)
+ return data
+/obj/machinery/disposal/bin/oui_act(mob/user, action, list/params)
+ if(..())
+ return
+ switch(action)
+ if("handle-0")
+ flush = FALSE
+ update_icon()
+ . = TRUE
+ if("handle-1")
+ if(!panel_open)
+ flush = TRUE
+ update_icon()
+ . = TRUE
+ if("pump-0")
+ if(pressure_charging)
+ pressure_charging = FALSE
+ update_icon()
+ . = TRUE
+ if("pump-1")
+ if(!pressure_charging)
+ pressure_charging = TRUE
+ update_icon()
+ . = TRUE
+ if("eject")
+ eject()
+ . = TRUE
+ ui.soft_update_fields()
+```
+
+#### Step 4
+
+You now need to hook in and ensure oracle_ui is invoked upon clicking. `render` should be used to open the UI for a user, typically on click.
+
+`code/modules/recycling/disposal-unit.dm`
+```dm
+/obj/machinery/disposal/bin/ui_interact(mob/user, state)
+ if(stat & BROKEN)
+ return
+ if(user.loc == src)
+ to_chat(user, "You cannot reach the controls from inside!")
+ return
+ ui.render(user)
+```
+
+#### Done
+
+
+
+You should have a functional UI at this point. Some additional odds and ends can be discovered throughout `code/modules/recycling/disposal-unit.dm`. For a full diff of the changes made to it, refer to [the original pull request on GitHub](https://github.com/OracleStation/OracleStation/pull/702/files#diff-4b6c20ec7d37222630e7524d9577e230).
+
+### API Reference
+
+#### `/datum/oracle_ui`
+
+The main datum which handles the UI.
+
+##### `get_content(mob/target)`
+Returns the HTML that should be displayed for a specified target mob. Calls `oui_getcontent` on the datasource to get the return value. *This proc is not used in the themed subclass.*
+
+##### `can_view(mob/target)`
+Returns whether the specified target mob can view the UI. Calls `oui_canview` on the datasource to get the return value.
+
+##### `test_viewer(mob/target, updating)`
+Tests whether the client is valid and can view the UI. If updating is TRUE, checks to see if they still have the UI window open.
+
+##### `render(mob/target, updating = FALSE)`
+Opens the UI for a target mob, sending HTML. If updating is TRUE, will only do it to clients which still have the window open.
+
+##### `render_all()`
+Does the above, but for all viewers and with updating set to TRUE.
+
+##### `close(mob/target)`
+Closes the UI for the specified target mob.
+
+##### `close_all()`
+Does the above, but for all viewers.
+
+##### `check_view(mob/target)`
+Checks if the specified target mob can view the UI, and if they can't closes their UI
+
+##### `check_view_all()`
+Does the above, but for all viewers.
+
+##### `call_js(mob/target, js_func, list/parameters = list())`
+Invokes `js_func` in the UI of the specified target mob with the specified parameters.
+
+##### `call_js_all(js_func, list/parameters = list()))`
+Does the above, but for all viewers.
+
+##### `steal_focus(mob/target)`
+Causes the UI to steal focus for the specified target mob.
+
+##### `steal_focus_all()`
+Does the above, but for all viewers.
+
+##### `flash(mob/target, times = -1)`
+Causes the UI to flash for the specified target mob the specified number of times, the default keeps the element flashing until focused.
+
+##### `flash_all()`
+Does the above, but for all viewers.
+
+##### `href(mob/user, action, list/parameters = list())`
+Generates a href for the specified user which will invoke `oui_act` on the datasource with the specified action and parameters.
+
+#### `/datum/oracle_ui/themed`
+
+A subclass which supports templating and theming.
+
+##### `get_file(path)`
+Loads a file from disk and returns the contents. Caches files loaded from disk for you.
+
+##### `get_content_file(filename)`
+Loads a file from the current content folder and returns the contents.
+
+##### `get_themed_file(filename)`
+Loads a file from the current theme folder and returns the contents.
+
+##### `process_template(template, variables)`
+Processes a template and populates it with the provided variables.
+
+##### `get_inner_content(mob/target)`
+Returns the templated content to be inserted into the main template for the specified target mob.
+
+##### `soft_update_fields()`
+For all viewers, updates the fields in the template via the `updateFields` javaScript function.
+
+##### `soft_update_all()`
+For all viewers, updates the content body in the template via the `replaceContent` javaScript function.
+
+##### `change_page(var/newpage)`
+Changes the template to use to draw the page and forces an update to all viewers
+
+##### `act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE`
+Returns a fully formatted hyperlink for the specified user. `label` will be the hyperlink label, `action` and `parameters` are what will be passed to `oui_act`, `class` is any CSS classes to apply to the hyperlink and `disabled` will disable the hyperlink.
+
+#### `/datum`
+
+Functions built into all objects to support oracle_ui. There are default implementations for most major superclasses.
+
+##### `oui_canview(mob/user)`
+Returns whether the specified user view the UI at this time.
+
+##### `oui_getcontent(mob/user)`
+Returns the raw HTML to be sent to the specified user. *This proc is not used in the themed subclass of oracle_ui.*
+
+##### `oui_data(mob/user)`
+Returns templating data for the specified user. *This proc is only used in the themed subclass of oracle_ui.*
+
+##### `oui_data_debug(mob/user)`
+Returns the above, but JSON-encoded and escaped, for copy pasting into the web IDE. *This proc is only used for debugging purposes.*
+
+##### `oui_act(mob/user, action, list/params)`
+Called when a hyperlink is clicked in the UI.
diff --git a/code/modules/oracle_ui/assets.dm b/code/modules/oracle_ui/assets.dm
new file mode 100644
index 0000000000..5d26d80a81
--- /dev/null
+++ b/code/modules/oracle_ui/assets.dm
@@ -0,0 +1,8 @@
+/datum/asset/simple/oui_theme_nano
+ assets = list(
+ // JavaScript
+ "sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js',
+ "sui-nano-jquery.min.js" = 'html/oracle_ui/themes/nano/sui-nano-jquery.min.js',
+ // Stylesheets
+ "sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css',
+ )
diff --git a/code/modules/oracle_ui/hookup_procs.dm b/code/modules/oracle_ui/hookup_procs.dm
new file mode 100644
index 0000000000..e6038744c1
--- /dev/null
+++ b/code/modules/oracle_ui/hookup_procs.dm
@@ -0,0 +1,44 @@
+/datum/proc/oui_canview(mob/user)
+ return TRUE
+
+/datum/proc/oui_getcontent(mob/user)
+ return "Default Implementation"
+
+/datum/proc/oui_canuse(mob/user)
+ if(isobserver(user) && !user.has_unlimited_silicon_privilege)
+ return FALSE
+ return oui_canview(user)
+
+/datum/proc/oui_data(mob/user)
+ return list()
+
+/datum/proc/oui_data_debug(mob/user)
+ return html_encode(json_encode(oui_data(user)))
+
+/datum/proc/oui_act(mob/user, action, list/params)
+ // No Implementation
+
+/atom/oui_canview(mob/user)
+ if(isobserver(user))
+ return TRUE
+ if(user.incapacitated())
+ return FALSE
+ if(isturf(src.loc) && Adjacent(user))
+ return TRUE
+ return FALSE
+
+/obj/item/oui_canview(mob/user)
+ if(src.loc == user)
+ return src in user.held_items
+ return ..()
+
+/obj/machinery/oui_canview(mob/user)
+ if(user.has_unlimited_silicon_privilege)
+ return TRUE
+ if(!can_interact())
+ return FALSE
+ if(iscyborg(user))
+ return can_see(user, src, 7)
+ if(isAI(user))
+ return GLOB.cameranet.checkTurfVis(get_turf_pixel(src))
+ return ..()
diff --git a/code/modules/oracle_ui/oracle_ui.dm b/code/modules/oracle_ui/oracle_ui.dm
new file mode 100644
index 0000000000..5e8d6b9c7b
--- /dev/null
+++ b/code/modules/oracle_ui/oracle_ui.dm
@@ -0,0 +1,134 @@
+/datum/oracle_ui
+ var/width = 512
+ var/height = 512
+ var/can_close = TRUE
+ var/can_minimize = FALSE
+ var/can_resize = TRUE
+ var/titlebar = TRUE
+ var/window_id = null
+ var/viewers[0]
+ var/auto_check_view = TRUE
+ var/auto_refresh = FALSE
+ var/atom/datasource = null
+ var/datum/asset/assets = null
+
+/datum/oracle_ui/New(atom/n_datasource, n_width = 512, n_height = 512, n_assets = null)
+ datasource = n_datasource
+ window_id = REF(src)
+ width = n_width
+ height = n_height
+
+/datum/oracle_ui/Destroy()
+ close_all()
+ if(src.datum_flags & DF_ISPROCESSING)
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/datum/oracle_ui/process()
+ if(auto_check_view)
+ check_view_all()
+ if(auto_refresh)
+ render_all()
+
+/datum/oracle_ui/proc/get_content(mob/target)
+ return call(datasource, "oui_getcontent")(target)
+
+/datum/oracle_ui/proc/can_view(mob/target)
+ return call(datasource, "oui_canview")(target)
+
+/datum/oracle_ui/proc/test_viewer(mob/target, updating)
+ //If the target is null or does not have a client, remove from viewers and return
+ if(!target | !target.client | !can_view(target))
+ viewers -= target
+ if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
+ STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
+ close(target)
+ return FALSE
+ //If this is an update, and they have closed the window, remove from viewers and return
+ if(updating && winget(target, window_id, "is-visible") != "true")
+ viewers -= target
+ if(viewers.len < 1 && (src.datum_flags & DF_ISPROCESSING))
+ STOP_PROCESSING(SSobj, src) //No more viewers, stop polling
+ return FALSE
+ return TRUE
+
+/datum/oracle_ui/proc/render(mob/target, updating = FALSE)
+ set waitfor = FALSE //Makes this an async call
+ if(!can_view(target))
+ return
+ //Check to see if they have the window open still if updating
+ if(updating && !test_viewer(target, updating))
+ return
+ //Send assets
+ if(!updating && assets)
+ assets.send(target)
+ //Add them to the viewers if they aren't there already
+ viewers |= target
+ if(!(src.datum_flags & DF_ISPROCESSING) && (auto_refresh | auto_check_view))
+ START_PROCESSING(SSobj, src) //Start processing to poll for viewability
+ //Send the content
+ if(updating)
+ target << output(get_content(target), "[window_id].browser")
+ else
+ target << browse(get_content(target), "window=[window_id];size=[width]x[height];can_close=[can_close];can_minimize=[can_minimize];can_resize=[can_resize];titlebar=[titlebar];focus=false;")
+ steal_focus(target)
+
+/datum/oracle_ui/proc/render_all()
+ for(var/viewer in viewers)
+ render(viewer, TRUE)
+
+/datum/oracle_ui/proc/close(mob/target)
+ if(target && target.client)
+ target << browse(null, "window=[window_id]")
+
+/datum/oracle_ui/proc/close_all()
+ for(var/viewer in viewers)
+ close(viewer)
+ viewers = list()
+
+/datum/oracle_ui/proc/check_view_all()
+ for(var/viewer in viewers)
+ check_view(viewer)
+
+/datum/oracle_ui/proc/check_view(mob/target)
+ set waitfor = FALSE //Makes this an async call
+ if(!test_viewer(target, TRUE))
+ close(target)
+
+/datum/oracle_ui/proc/call_js(mob/target, js_func, list/parameters = list())
+ set waitfor = FALSE //Makes this an async call
+ if(!test_viewer(target, TRUE))
+ return
+ target << output(list2params(parameters),"[window_id].browser:[js_func]")
+
+/datum/oracle_ui/proc/call_js_all(js_func, list/parameters = list())
+ for(var/viewer in viewers)
+ call_js(viewer, js_func, parameters)
+
+/datum/oracle_ui/proc/steal_focus(mob/target)
+ set waitfor = FALSE //Makes this an async call
+ winset(target, "[window_id]","focus=true")
+
+/datum/oracle_ui/proc/steal_focus_all()
+ for(var/viewer in viewers)
+ steal_focus(viewer)
+
+/datum/oracle_ui/proc/flash(mob/target, times = -1)
+ set waitfor = FALSE //Makes this an async call
+ winset(target, "[window_id]","flash=[times]")
+
+/datum/oracle_ui/proc/flash_all(times = -1)
+ for(var/viewer in viewers)
+ flash(viewer, times)
+
+/datum/oracle_ui/proc/href(mob/user, action, list/parameters = list())
+ var/params_string = replacetext(list2params(parameters),"&",";")
+ return "?src=[REF(src)];sui_action=[action];sui_user=[REF(user)];[params_string]"
+
+/datum/oracle_ui/Topic(href, parameters)
+ var/action = parameters["sui_action"]
+ var/mob/current_user = locate(parameters["sui_user"])
+ if(!call(datasource, "oui_canuse")(current_user))
+ return
+ if(datasource)
+ call(datasource, "oui_act")(current_user, action, parameters);
diff --git a/code/modules/oracle_ui/themed.dm b/code/modules/oracle_ui/themed.dm
new file mode 100644
index 0000000000..56b82c2647
--- /dev/null
+++ b/code/modules/oracle_ui/themed.dm
@@ -0,0 +1,82 @@
+/datum/oracle_ui/themed
+ var/theme = ""
+ var/content_root = ""
+ var/current_page = "index.html"
+ var/root_template = ""
+
+/datum/oracle_ui/themed/New(atom/n_datasource, n_width = 512, n_height = 512, n_content_root = "")
+ root_template = get_themed_file("index.html")
+ content_root = n_content_root
+ return ..(n_datasource, n_width, n_height, get_asset_datum(/datum/asset/simple/oui_theme_nano))
+
+/datum/oracle_ui/themed/process()
+ if(auto_check_view)
+ check_view_all()
+ if(auto_refresh)
+ soft_update_fields()
+
+GLOBAL_LIST_EMPTY(oui_template_variables)
+GLOBAL_LIST_EMPTY(oui_file_cache)
+
+/datum/oracle_ui/themed/proc/get_file(path)
+ if(GLOB.oui_file_cache[path])
+ return GLOB.oui_file_cache[path]
+ else if(fexists(path))
+ var/data = file2text(path)
+ GLOB.oui_file_cache[path] = data
+ return data
+ else
+ var/errormsg = "MISSING PATH '[path]'"
+#ifndef UNIT_TESTS
+ log_world(errormsg) //Because Travis absolutely hates these procs
+#endif
+ return errormsg
+
+/datum/oracle_ui/themed/proc/get_content_file(filename)
+ return get_file("./html/oracle_ui/content/[content_root]/[filename]")
+
+/datum/oracle_ui/themed/proc/get_themed_file(filename)
+ return get_file("./html/oracle_ui/themes/[theme]/[filename]")
+
+/datum/oracle_ui/themed/proc/process_template(template, variables)
+ var/regex/pattern = regex("\\@\\{(\\w+)\\}","gi")
+ GLOB.oui_template_variables = variables
+ var/replaced = pattern.Replace(template, /proc/oui_process_template_replace)
+ GLOB.oui_template_variables = null
+ return replaced
+
+/proc/oui_process_template_replace(match, group1)
+ var/value = GLOB.oui_template_variables[group1]
+ return "[value]"
+
+/datum/oracle_ui/themed/proc/get_inner_content(mob/target)
+ var/list/data = call(datasource, "oui_data")(target)
+ return process_template(get_content_file(current_page), data)
+
+/datum/oracle_ui/themed/get_content(mob/target)
+ var/list/template_data = list("title" = datasource.name, "body" = get_inner_content(target))
+ return process_template(root_template, template_data)
+
+/datum/oracle_ui/themed/proc/soft_update_fields()
+ for(var/viewer in viewers)
+ var/json = json_encode(call(datasource, "oui_data")(viewer))
+ call_js(viewer, "updateFields", list(json))
+
+/datum/oracle_ui/themed/proc/soft_update_all()
+ for(var/viewer in viewers)
+ call_js(viewer, "replaceContent", list(get_inner_content(viewer)))
+
+/datum/oracle_ui/themed/proc/change_page(newpage)
+ if(newpage == current_page)
+ return
+ current_page = newpage
+ render_all()
+
+/datum/oracle_ui/themed/proc/act(label, mob/user, action, list/parameters = list(), class = "", disabled = FALSE)
+ if(disabled)
+ return "[label]"
+ else
+ return "[label]"
+
+/datum/oracle_ui/themed/nano
+ theme = "nano"
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 37877ffb09..059a42bb36 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -31,6 +31,7 @@
var/spam_flag = 0
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
+ var/datum/oracle_ui/ui = null
/obj/item/paper/pickup(user)
@@ -40,16 +41,40 @@
if(!istype(G) || G.transfer_prints)
H.reagents.add_reagent(contact_poison,contact_poison_volume)
contact_poison = null
+ ui.check_view_all()
..()
+/obj/item/paper/dropped(mob/user)
+ ui.check_view(user)
+ return ..()
+
/obj/item/paper/Initialize()
. = ..()
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
+ ui = new /datum/oracle_ui(src, 420, 600, get_asset_datum(/datum/asset/spritesheet/simple/paper))
+ ui.can_resize = FALSE
update_icon()
updateinfolinks()
+/obj/item/paper/oui_getcontent(mob/target)
+ if(!target.is_literate())
+ return "[name][stars(info)][stamps]"
+ else if(istype(target.get_active_held_item(), /obj/item/pen) | istype(target.get_active_held_item(), /obj/item/toy/crayon))
+ return "[name][info_links][stamps]