diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index d000508fdeb..933cf12dcfc 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -242,6 +242,9 @@
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
+// Locations
+#define is_ventcrawling(A) (istype(A.loc, /obj/machinery/atmospherics))
+
// Hearing protection
#define HEARING_PROTECTION_NONE 0
#define HEARING_PROTECTION_MINOR 1
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 50c5314d0b4..03d34395479 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -9,7 +9,7 @@
Note that AI have no need for the adjacency proc, and so this proc is a lot cleaner.
*/
-/mob/living/silicon/ai/DblClickOn(var/atom/A, params)
+/mob/living/silicon/ai/DblClickOn(atom/A, params)
if(client.click_intercept)
// Not doing a click intercept here, because otherwise we double-tap with the `ClickOn` proc.
// But we return here since we don't want to do regular dblclick handling
@@ -23,7 +23,7 @@
A.move_camera_by_click()
-/mob/living/silicon/ai/ClickOn(var/atom/A, params)
+/mob/living/silicon/ai/ClickOn(atom/A, params)
if(client.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
@@ -123,7 +123,7 @@
/mob/living/silicon/ai/RangedAttack(atom/A, params)
A.attack_ai(src)
-/atom/proc/attack_ai(mob/user as mob)
+/atom/proc/attack_ai(mob/user)
return
/*
@@ -132,17 +132,17 @@
for AI shift, ctrl, and alt clicking.
*/
-/mob/living/silicon/ai/CtrlShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/CtrlShiftClickOn(atom/A)
A.AICtrlShiftClick(src)
-/mob/living/silicon/ai/AltShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/AltShiftClickOn(atom/A)
A.AIAltShiftClick(src)
-/mob/living/silicon/ai/ShiftClickOn(var/atom/A)
+/mob/living/silicon/ai/ShiftClickOn(atom/A)
A.AIShiftClick(src)
-/mob/living/silicon/ai/CtrlClickOn(var/atom/A)
+/mob/living/silicon/ai/CtrlClickOn(atom/A)
A.AICtrlClick(src)
-/mob/living/silicon/ai/AltClickOn(var/atom/A)
+/mob/living/silicon/ai/AltClickOn(atom/A)
A.AIAltClick(src)
-/mob/living/silicon/ai/MiddleClickOn(var/atom/A)
+/mob/living/silicon/ai/MiddleClickOn(atom/A)
A.AIMiddleClick(src)
@@ -224,14 +224,6 @@
return
toggle_light(user)
-// FIRE ALARMS
-
-/obj/machinery/firealarm/AICtrlClick()
- if(enabled)
- reset()
- else
- alarm()
-
// AI-CONTROLLED SLIP GENERATOR IN AI CORE
/obj/machinery/ai_slipper/AICtrlClick(mob/living/silicon/ai/user) //Turns liquid dispenser on or off
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index f35b2cece0b..fa29e857ab7 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -6,7 +6,7 @@
adjacency code.
*/
-/mob/living/silicon/robot/ClickOn(var/atom/A, var/params)
+/mob/living/silicon/robot/ClickOn(atom/A, params)
if(client.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
@@ -15,6 +15,8 @@
return
changeNext_click(1)
+ if(is_ventcrawling(src)) // To stop drones interacting with anything while ventcrawling
+ return
var/list/modifiers = params2list(params)
if(modifiers["shift"] && modifiers["ctrl"])
@@ -98,12 +100,12 @@
return
//Ctrl+Middle click cycles through modules
-/mob/living/silicon/robot/proc/CtrlMiddleClickOn(var/atom/A)
+/mob/living/silicon/robot/proc/CtrlMiddleClickOn(atom/A)
cycle_modules()
return
//Middle click points
-/mob/living/silicon/robot/MiddleClickOn(var/atom/A)
+/mob/living/silicon/robot/MiddleClickOn(atom/A)
if(istype(src, /mob/living/silicon/robot/drone))
// Drones cannot point.
return
@@ -112,18 +114,31 @@
//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks
// for non-doors/apcs
-/mob/living/silicon/robot/CtrlShiftClickOn(var/atom/A)
- A.BorgCtrlShiftClick(src)
-/mob/living/silicon/robot/AltShiftClickOn(var/atom/A)
- A.BorgAltShiftClick(src)
-/mob/living/silicon/robot/ShiftClickOn(var/atom/A)
+/mob/living/silicon/robot/ShiftClickOn(atom/A)
A.BorgShiftClick(src)
-/mob/living/silicon/robot/CtrlClickOn(var/atom/A)
+/mob/living/silicon/robot/CtrlClickOn(atom/A)
A.BorgCtrlClick(src)
-/mob/living/silicon/robot/AltClickOn(var/atom/A)
+/mob/living/silicon/robot/AltClickOn(atom/A)
A.BorgAltClick(src)
+/mob/living/silicon/robot/CtrlShiftClickOn(atom/A)
+ A.BorgCtrlShiftClick(src)
+/mob/living/silicon/robot/AltShiftClickOn(atom/A)
+ A.BorgAltShiftClick(src)
-/atom/proc/BorgCtrlShiftClick(var/mob/user) // Examines
+
+/atom/proc/BorgShiftClick(mob/user)
+ if(user.client && user.client.eye == user)
+ user.examinate(src)
+ return
+
+/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overriden
+ CtrlClick(user)
+
+/atom/proc/BorgAltClick(mob/living/silicon/robot/user)
+ AltClick(user)
+ return
+
+/atom/proc/BorgCtrlShiftClick(mob/user) // Examines
if(user.client && user.client.eye == user)
user.examinate(src)
return
@@ -131,38 +146,29 @@
/atom/proc/BorgAltShiftClick()
return
-/obj/machinery/door/airlock/BorgAltShiftClick(mob/living/silicon/robot/user) // Enables emergency override on doors! Forwards to AI code.
- AIAltShiftClick(user)
-/atom/proc/BorgShiftClick(var/mob/user)
- if(user.client && user.client.eye == user)
- user.examinate(src)
- return
+// AIRLOCKS
/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code.
AIShiftClick(user)
-/atom/proc/BorgCtrlClick(var/mob/living/silicon/robot/user) //forward to human click if not overriden
- CtrlClick(user)
-
/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code.
AICtrlClick(user)
-/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code.
- AICtrlClick(user)
-
-/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code.
- AICtrlClick(user)
-
-/atom/proc/BorgAltClick(var/mob/living/silicon/robot/user)
- AltClick(user)
- return
-
/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code.
AIAltClick(user)
-/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code.
- AIAltClick(user)
+/obj/machinery/door/airlock/BorgAltShiftClick(mob/living/silicon/robot/user) // Enables emergency override on doors! Forwards to AI code.
+ AIAltShiftClick(user)
+
+
+// APC
+
+/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code.
+ AICtrlClick(user)
+
+
+// AI SLIPPER
/obj/machinery/ai_slipper/BorgCtrlClick(mob/living/silicon/robot/user) //Turns liquid dispenser on or off
ToggleOn()
@@ -170,9 +176,15 @@
/obj/machinery/ai_slipper/BorgAltClick(mob/living/silicon/robot/user) //Dispenses liquid if on
Activate()
-/obj/machinery/firealarm/BorgCtrlClick(mob/living/silicon/robot/user)
+
+// TURRETCONTROL
+
+/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code.
AICtrlClick(user)
+/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code.
+ AIAltClick(user)
+
/*
As with AI, these are not used in click code,
because the code for robots is specific, not generic.
@@ -187,6 +199,6 @@
/mob/living/silicon/robot/RangedAttack(atom/A, params)
A.attack_robot(src)
-/atom/proc/attack_robot(mob/user as mob)
+/atom/proc/attack_robot(mob/user)
attack_ai(user)
return
diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm
index d063b742047..e099ff0b384 100644
--- a/code/game/gamemodes/miniantags/guardian/guardian.dm
+++ b/code/game/gamemodes/miniantags/guardian/guardian.dm
@@ -41,9 +41,16 @@
var/tech_fluff_string = "BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!"
var/bio_fluff_string = "Your scarabs fail to mutate. This shouldn't happen! Submit a bug report!"
var/admin_fluff_string = "URK URF!"//the wheels on the bus...
- var/adminseal = FALSE
var/name_color = "white"//only used with protector shields for the time being
+/mob/living/simple_animal/hostile/guardian/Initialize(mapload, mob/living/host)
+ . = ..()
+ if(!host)
+ return
+ summoner = host
+ host.grant_guardian_actions(src)
+ RegisterSignal(host, COMSIG_MOB_DEATH, .proc/on_host_death)
+
/mob/living/simple_animal/hostile/guardian/med_hud_set_health()
if(summoner)
var/image/holder = hud_list[HEALTH_HUD]
@@ -59,19 +66,26 @@
else
holder.icon_state = "hudhealthy"
-/mob/living/simple_animal/hostile/guardian/Life(seconds, times_fired) //Dies if the summoner dies
+/**
+ * Proc which is called when the guardian's host dies.
+ *
+ * This will only fire if the guardian was created through a holoparsite injector, or the equivalent.
+ */
+/mob/living/simple_animal/hostile/guardian/proc/on_host_death()
+ if(summoner.stat == DEAD || (!summoner.check_death_method() && summoner.health <= HEALTH_THRESHOLD_DEAD))
+ summoner.remove_guardian_actions() // Remove our summoner's action buttons.
+ to_chat(src, "Your summoner has died!")
+ visible_message("[src] dies along with its user!")
+ ghostize()
+ qdel(src)
+
+/mob/living/simple_animal/hostile/guardian/Life(seconds, times_fired)
..()
- if(summoner)
- if(summoner.stat == DEAD || (!summoner.check_death_method() && summoner.health <= HEALTH_THRESHOLD_DEAD))
- to_chat(src, "Your summoner has died!")
- visible_message("[src] dies along with its user!")
- ghostize()
- qdel(src)
snapback()
- if(summoned && !summoner && !adminseal)
+ if(summoned && !summoner && !admin_spawned)
to_chat(src, "You somehow lack a summoner! As a result, you dispel!")
ghostize()
- qdel()
+ qdel(src)
/mob/living/simple_animal/hostile/guardian/proc/snapback()
// If the summoner dies instantly, the summoner's ghost may be drawn into null space as the protector is deleted. This check should prevent that.
@@ -197,7 +211,7 @@
//override set to true if message should be passed through instead of going to host communication
/mob/living/simple_animal/hostile/guardian/say(message, override = FALSE)
- if(adminseal || override)//if it's an admin-spawned guardian without a host it can still talk normally
+ if(admin_spawned || override)//if it's an admin-spawned guardian without a host it can still talk normally
return ..(message)
Communicate(message)
@@ -206,61 +220,6 @@
to_chat(src, "You dont have another mode!")
-/mob/living/proc/guardian_comm()
- set name = "Communicate"
- set category = "Guardian"
- set desc = "Communicate telepathically with your guardian."
- var/input = stripped_input(src, "Please enter a message to tell your guardian.", "Message", "")
- if(!input)
- return
-
- // Find the guardian in our host's contents.
- var/mob/living/simple_animal/hostile/guardian/G = locate() in contents
- if(!G)
- return
-
- // Show the message to our guardian and to host.
- to_chat(G, "[src]: [input]")
- to_chat(src, "[src]: [input]")
- log_say("(GUARDIAN to [key_name(G)]) [input]", src)
- create_log(SAY_LOG, "HOST to GUARDIAN: [input]", G)
-
- // Show the message to any ghosts/dead players.
- for(var/mob/M in GLOB.dead_mob_list)
- if(M && M.client && M.stat == DEAD && !isnewplayer(M))
- to_chat(M, "Guardian Communication from [src] ([ghost_follow_link(src, ghost=M)]): [input]")
-
-/mob/living/proc/guardian_recall()
- set name = "Recall Guardian"
- set category = "Guardian"
- set desc = "Forcibly recall your guardian."
- for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.mob_list)
- if(G.summoner == src)
- G.Recall()
-
-/mob/living/proc/guardian_reset()
- set name = "Reset Guardian Player (One Use)"
- set category = "Guardian"
- set desc = "Re-rolls which ghost will control your Guardian. One use."
-
- src.verbs -= /mob/living/proc/guardian_reset
- for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.mob_list)
- if(G.summoner == src)
- var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [G.real_name]?", ROLE_GUARDIAN, FALSE, 10 SECONDS, source = G)
- var/mob/dead/observer/new_stand = null
- if(candidates.len)
- new_stand = pick(candidates)
- to_chat(G, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
- to_chat(src, "Your guardian has been successfully reset.")
- message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(G)])")
- G.ghostize()
- G.key = new_stand.key
- else
- to_chat(src, "There were no ghosts willing to take control. Looks like you're stuck with your Guardian for now.")
- spawn(3000)
- verbs += /mob/living/proc/guardian_reset
-
-
/mob/living/simple_animal/hostile/guardian/proc/ToggleLight()
if(!light_on)
set_light(luminosity_on)
@@ -364,7 +323,6 @@
pickedtype = /mob/living/simple_animal/hostile/guardian/protector
var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user, user)
- G.summoner = user
G.summoned = TRUE
G.key = key
to_chat(G, "You are a [mob_name] bound to serve [user.real_name].")
@@ -372,9 +330,6 @@
to_chat(G, "While personally invincible, you will die if [user.real_name] does, and any damage dealt to you will have a portion passed on to them as you feed upon them to sustain yourself.")
to_chat(G, "[G.playstyle_string]")
G.faction = user.faction
- user.verbs += /mob/living/proc/guardian_comm
- user.verbs += /mob/living/proc/guardian_recall
- user.verbs += /mob/living/proc/guardian_reset
var/color = pick(color_list)
G.name_color = color_list[color]
diff --git a/code/game/gamemodes/miniantags/guardian/host_actions.dm b/code/game/gamemodes/miniantags/guardian/host_actions.dm
new file mode 100644
index 00000000000..c6e48221705
--- /dev/null
+++ b/code/game/gamemodes/miniantags/guardian/host_actions.dm
@@ -0,0 +1,130 @@
+/**
+ * # Base guardian host action
+ *
+ * These are used by guardian hosts to interact with their guardians. These are not buttons that guardians themselves use.
+ */
+/datum/action/guardian
+ name = "Generic guardian host action"
+ icon_icon = 'icons/mob/guardian.dmi'
+ button_icon_state = "base"
+ var/mob/living/simple_animal/hostile/guardian/guardian
+
+/datum/action/guardian/Grant(mob/M, mob/living/simple_animal/hostile/guardian/G)
+ if(!G || !istype(G))
+ stack_trace("/datum/action/guardian created with no guardian to link to.")
+ qdel(src)
+ guardian = G
+ return ..()
+
+/**
+ * # Communicate action
+ *
+ * Allows the guardian host to communicate with their guardian.
+ */
+/datum/action/guardian/communicate
+ name = "Communicate"
+ desc = "Communicate telepathically with your guardian."
+ button_icon_state = "communicate"
+
+/datum/action/guardian/communicate/Trigger()
+ var/input = stripped_input(owner, "Enter a message to tell your guardian:", "Message", "")
+ if(!input)
+ return
+
+ // Show the message to our guardian and to host.
+ to_chat(guardian, "[owner]: [input]")
+ to_chat(owner, "[owner]: [input]")
+ log_say("(GUARDIAN to [key_name(guardian)]) [input]", owner)
+ owner.create_log(SAY_LOG, "HOST to GUARDIAN: [input]", guardian)
+
+ // Show the message to any ghosts/dead players.
+ for(var/mob/M in GLOB.dead_mob_list)
+ if(M && M.client && M.stat == DEAD && !isnewplayer(M))
+ to_chat(M, "Guardian Communication from [owner] ([ghost_follow_link(owner, ghost=M)]): [input]")
+
+/**
+ * # Recall guardian action
+ *
+ * Allows the guardian host to recall their guardian.
+ */
+/datum/action/guardian/recall
+ name = "Recall Guardian"
+ desc = "Forcibly recall your guardian."
+ button_icon_state = "recall"
+
+/datum/action/guardian/recall/Trigger()
+ guardian.Recall()
+
+/**
+ * # Reset guardian action
+ *
+ * Allows the guardian host to exchange their guardian's player for another.
+ */
+/datum/action/guardian/reset_guardian
+ name = "Replace Guardian Player"
+ desc = "Replace your guardian's player with a ghost. This can only be done once."
+ button_icon_state = "reset"
+ var/cooldown_timer
+
+/datum/action/guardian/reset_guardian/IsAvailable()
+ if(cooldown_timer)
+ return FALSE
+ return TRUE
+
+/datum/action/guardian/reset_guardian/Trigger()
+ if(cooldown_timer)
+ to_chat(owner, "This ability is still recharging.")
+ return
+
+ var/confirm = alert("Are you sure you want replace your guardian's player?", "Confirm", "Yes", "No")
+ if(confirm == "No")
+ return
+
+ // Do this immediately, so the user can't spam a bunch of polls.
+ cooldown_timer = addtimer(CALLBACK(src, .proc/reset_cooldown), 5 MINUTES)
+ UpdateButtonIcon()
+
+ to_chat(owner, "Searching for a replacement ghost...")
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [guardian.real_name]?", ROLE_GUARDIAN, FALSE, 15 SECONDS, source = guardian)
+
+ if(!length(candidates))
+ to_chat(owner, "There were no ghosts willing to take control of your guardian. You can try again in 5 minutes.")
+ return
+
+ var/mob/dead/observer/new_stand = pick(candidates)
+ to_chat(guardian, "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")
+ to_chat(owner, "Your guardian has been successfully reset.")
+ message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(guardian)])")
+ guardian.ghostize()
+ guardian.key = new_stand.key
+ qdel(src)
+
+/**
+ * Takes the action button off cooldown and makes it available again.
+ */
+/datum/action/guardian/reset_guardian/proc/reset_cooldown()
+ cooldown_timer = null
+ UpdateButtonIcon()
+
+/**
+ * Grants all existing `/datum/action/guardian` type actions to the src mob.
+ *
+ * Called whenever the host gains their gauardian.
+ */
+/mob/living/proc/grant_guardian_actions(mob/living/simple_animal/hostile/guardian/G)
+ if(!G || !istype(G))
+ return
+ for(var/action in subtypesof(/datum/action/guardian))
+ var/datum/action/guardian/A = new action
+ A.Grant(src, G)
+
+/**
+ * Removes all `/datum/action/guardian` type actions from the src mob.
+ *
+ * Called whenever the host loses their guardian.
+ */
+/mob/living/proc/remove_guardian_actions()
+ for(var/action in actions)
+ var/datum/action/A = action
+ if(istype(A, /datum/action/guardian))
+ A.Remove(src)
diff --git a/code/game/gamemodes/miniantags/guardian/types/healer.dm b/code/game/gamemodes/miniantags/guardian/types/healer.dm
index c3f8046f0a9..72d1a03301e 100644
--- a/code/game/gamemodes/miniantags/guardian/types/healer.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/healer.dm
@@ -26,7 +26,7 @@
melee_damage_lower = 0
melee_damage_upper = 0
melee_damage_type = STAMINA
- adminseal = TRUE
+ admin_spawned = TRUE
/mob/living/simple_animal/hostile/guardian/healer/New()
..()
@@ -68,7 +68,7 @@
hud_used.action_intent.icon_state = a_intent
speed = 0
damage_transfer = 0.7
- if(adminseal)
+ if(admin_spawned)
damage_transfer = 0
melee_damage_lower = 15
melee_damage_upper = 15
@@ -79,7 +79,7 @@
hud_used.action_intent.icon_state = a_intent
speed = 1
damage_transfer = 1
- if(adminseal)
+ if(admin_spawned)
damage_transfer = 0
melee_damage_lower = 0
melee_damage_upper = 0
diff --git a/code/game/gamemodes/miniantags/guardian/types/lightning.dm b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
index 2c716d5d4a4..e2ab6360ae5 100644
--- a/code/game/gamemodes/miniantags/guardian/types/lightning.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
@@ -18,13 +18,12 @@
var/list/enemychains = list()
var/successfulshocks = 0
-/mob/living/simple_animal/hostile/guardian/beam/New(loc, mob/living/user)
- . = ..()
- if(!user)
- return
- summoner = user
- if(!(NO_SHOCK in summoner.mutations))
- summoner.mutations.Add(NO_SHOCK)
+/mob/living/simple_animal/hostile/guardian/beam/Initialize(mapload, mob/living/host)
+ . = ..()
+ if(!summoner)
+ return
+ if(!(NO_SHOCK in summoner.mutations))
+ summoner.mutations.Add(NO_SHOCK)
/mob/living/simple_animal/hostile/guardian/beam/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE)
return FALSE //You are lightning, you should not be hurt by such things.
diff --git a/code/game/gamemodes/miniantags/guardian/types/standard.dm b/code/game/gamemodes/miniantags/guardian/types/standard.dm
index e6d95ffb92c..d458ce2f49d 100644
--- a/code/game/gamemodes/miniantags/guardian/types/standard.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/standard.dm
@@ -45,4 +45,4 @@
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."
environment_smash = 2
battlecry = "URK"
- adminseal = TRUE
+ admin_spawned = TRUE
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm
index ea273850d90..b950f3d51a3 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/game/machinery/atmoalter/canister.dm
@@ -367,10 +367,8 @@ update_flag
if("eject")
if(holding)
if(valve_open)
- if(air_contents && (air_contents.toxins > 0 || air_contents.sleeping_agent > 0))
- message_admins("[ADMIN_LOOKUPFLW(usr)] removed [holding] from [src] with valve still open at [ADMIN_VERBOSEJMP(src)] releasing contents into the air.")
- release_log += "[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air
"
- investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air.", "atmos")
+ valve_open = FALSE
+ release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
"
replace_tank(usr, FALSE)
if("recolor")
if(can_label)
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 6144d1c3883..137dcb50110 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -32,7 +32,6 @@ FIRE ALARM
var/wiresexposed = 0
var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
- var/enabled = FALSE
var/report_fire_alarms = TRUE // Should triggered fire alarms also trigger an actual alarm?
var/show_alert_level = TRUE // Should fire alarms display the current alert level?
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 067411ed647..fe15d146aa4 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -183,6 +183,82 @@
return TRUE
+/obj/item/borg/upgrade/abductor_engi
+ name = "engineering cyborg abductor upgrade"
+ desc = "An experimental upgrade that replaces an engineering cyborgs tools with the abductor version."
+ icon_state = "abductor_mod"
+ origin_tech = "engineering=6;materials=6;abductor=3"
+ require_module = TRUE
+ module_type = /obj/item/robot_module/engineering
+
+/obj/item/borg/upgrade/abductor_engi/action(mob/living/silicon/robot/R)
+ if(..())
+ return
+
+ for(var/obj/item/weldingtool/largetank/cyborg/W in R.module.modules)
+ qdel(W)
+ for(var/obj/item/screwdriver/cyborg/S in R.module.modules)
+ qdel(S)
+ for(var/obj/item/wrench/cyborg/E in R.module.modules)
+ qdel(E)
+ for(var/obj/item/crowbar/cyborg/C in R.module.modules)
+ qdel(C)
+ for(var/obj/item/wirecutters/cyborg/I in R.module.modules)
+ qdel(I)
+ for(var/obj/item/multitool/cyborg/M in R.module.modules)
+ qdel(M)
+
+ R.module.modules += new /obj/item/weldingtool/abductor(R.module)
+ R.module.modules += new /obj/item/wrench/abductor(R.module)
+ R.module.modules += new /obj/item/screwdriver/abductor(R.module)
+ R.module.modules += new /obj/item/crowbar/abductor(R.module)
+ R.module.modules += new /obj/item/wirecutters/abductor(R.module)
+ R.module.modules += new /obj/item/multitool/abductor(R.module)
+ R.module.rebuild()
+
+ return TRUE
+
+/obj/item/borg/upgrade/abductor_medi
+ name = "medical cyborg abductor upgrade"
+ desc = "An experimental upgrade that replaces a medical cyborgs tools with the abductor version."
+ icon_state = "abductor_mod"
+ origin_tech = "biotech=6;materials=6;abductor=3"
+ require_module = TRUE
+ module_type = /obj/item/robot_module/medical
+
+/obj/item/borg/upgrade/abductor_medi/action(mob/living/silicon/robot/R)
+ if(..())
+ return
+
+ for(var/obj/item/scalpel/laser/laser1/L in R.module.modules)
+ qdel(L)
+ for(var/obj/item/hemostat/H in R.module.modules)
+ qdel(H)
+ for(var/obj/item/retractor/E in R.module.modules)
+ qdel(E)
+ for(var/obj/item/bonegel/B in R.module.modules)
+ qdel(B)
+ for(var/obj/item/FixOVein/F in R.module.modules)
+ qdel(F)
+ for(var/obj/item/bonesetter/S in R.module.modules)
+ qdel(S)
+ for(var/obj/item/circular_saw/C in R.module.modules)
+ qdel(C)
+ for(var/obj/item/surgicaldrill/D in R.module.modules)
+ qdel(D)
+
+ R.module.modules += new /obj/item/scalpel/laser/laser3(R.module) //no abductor laser scalpel, so next best thing.
+ R.module.modules += new /obj/item/hemostat/alien(R.module)
+ R.module.modules += new /obj/item/retractor/alien(R.module)
+ R.module.modules += new /obj/item/bonegel/alien(R.module)
+ R.module.modules += new /obj/item/FixOVein/alien(R.module)
+ R.module.modules += new /obj/item/bonesetter/alien(R.module)
+ R.module.modules += new /obj/item/circular_saw/alien(R.module)
+ R.module.modules += new /obj/item/surgicaldrill/alien(R.module)
+ R.module.rebuild()
+
+ return TRUE
+
/obj/item/borg/upgrade/syndicate
name = "safety override module"
desc = "Unlocks the hidden, deadlier functions of a cyborg."
diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index 121782b2e8e..5370004a0a7 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -31,7 +31,6 @@
desc = "It's the heavy-duty black polymer kind. Time to take out the trash!"
icon = 'icons/obj/janitor.dmi'
icon_state = "trashbag"
- item_state = "trashbag"
w_class = WEIGHT_CLASS_BULKY
max_w_class = WEIGHT_CLASS_SMALL
@@ -48,14 +47,19 @@
/obj/item/storage/bag/trash/update_icon()
switch(contents.len)
- if(20 to INFINITY)
+ if(21 to INFINITY)
icon_state = "[initial(icon_state)]3"
if(11 to 20)
icon_state = "[initial(icon_state)]2"
- if(1 to 11)
+ if(1 to 10)
icon_state = "[initial(icon_state)]1"
else
icon_state = "[initial(icon_state)]"
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ H.update_inv_l_hand()
+ H.update_inv_r_hand()
+ ..()
/obj/item/storage/bag/trash/cyborg
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 138c77f92eb..d1d26b7a2bb 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -234,7 +234,7 @@
add_fingerprint(user)
/obj/structure/closet/attack_ai(mob/user)
- if(isrobot(user) && Adjacent(user) && !istype(user.loc, /obj/machinery/atmospherics)) //Robots can open/close it, but not the AI
+ if(isrobot(user) && Adjacent(user)) //Robots can open/close it, but not the AI
attack_hand(user)
/obj/structure/closet/relaymove(mob/user)
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 63f5cf8dca3..f90b1703d34 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -312,6 +312,22 @@
flags = BLOCKHAIR
flags_inv = HIDEEARS
+/obj/item/clothing/suit/hooded/salmon_costume
+ name = "salmon suit"
+ desc = "A costume made from authentic salmon scales, it reeks!"
+ icon_state = "salmon"
+ body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS
+ allowed = list(/obj/item/fish/salmon, /obj/item/fish_eggs/salmon)
+ hoodtype = /obj/item/clothing/head/hooded/salmon_hood
+
+/obj/item/clothing/head/hooded/salmon_hood
+ name = "salmon hood"
+ desc = "A hood attached to a salmon suit."
+ icon_state = "salmon"
+ body_parts_covered = HEAD
+ flags = BLOCKHAIR
+ flags_inv = HIDEEARS
+
/obj/item/clothing/suit/hooded/bee_costume // It's Hip!
name = "bee costume"
desc = "Bee the true Queen!"
diff --git a/code/modules/crafting/tailoring.dm b/code/modules/crafting/tailoring.dm
index 38419f718c4..68fa7fca3f3 100644
--- a/code/modules/crafting/tailoring.dm
+++ b/code/modules/crafting/tailoring.dm
@@ -152,3 +152,13 @@
/obj/item/clothing/shoes/sandal = 1)
tools = list(TOOL_WIRECUTTER)
category = CAT_CLOTHING
+
+/datum/crafting_recipe/salmonsuit
+ name = "Salmon Suit"
+ result = /obj/item/clothing/suit/hooded/salmon_costume
+ time = 60
+ reqs = list(/obj/item/fish/salmon = 20,
+ /obj/item/stack/tape_roll = 5)
+ tools = list(TOOL_WIRECUTTER)
+ pathtools = list(/obj/item/kitchen/knife)
+ category = CAT_CLOTHING
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index ece9c743854..508dd271849 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -9,6 +9,7 @@
return FALSE
/mob/proc/death(gibbed)
+ SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
return FALSE
/mob/proc/dust_animation()
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index b6e93dc78ff..a496526008f 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -514,10 +514,10 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
/mob/living/update_pipe_vision()
if(pipes_shown.len)
- if(!istype(loc, /obj/machinery/atmospherics))
+ if(!is_ventcrawling(src))
remove_ventcrawl()
else
- if(istype(loc, /obj/machinery/atmospherics))
+ if(is_ventcrawling(src))
add_ventcrawl(loc)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index 6af90792355..09cc812eae3 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -51,6 +51,7 @@
// Whew! Good thing I'm indestructible! (or already dead)
return FALSE
+ ..()
stat = DEAD
SetDizzy(0)
SetJitter(0)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index ff967354131..25d9a0decb8 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -74,7 +74,7 @@
var/list/temp_list = modules
modules = list()
for(var/obj/O in temp_list)
- if(O)
+ if(!QDELETED(O)) //so items getting deleted don't stay in module list and haunt you
modules += O
/obj/item/robot_module/proc/add_languages(mob/living/silicon/robot/R)
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index d3f5f76303c..6146cd3ac2c 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -311,15 +311,6 @@
/obj/machinery/power/supermatter_shard/attack_robot(mob/user as mob)
if(Adjacent(user))
return attack_hand(user)
- else
- ui_interact(user)
- return
-
-/obj/machinery/power/supermatter_shard/attack_ai(mob/user as mob)
- ui_interact(user)
-
-/obj/machinery/power/supermatter_shard/attack_ghost(mob/user as mob)
- ui_interact(user)
/obj/machinery/power/supermatter_shard/attack_hand(mob/user as mob)
user.visible_message("\The [user] reaches out and touches \the [src], inducing a resonance... [user.p_their(TRUE)] body starts to glow and bursts into flames before flashing into ash.",\
@@ -336,35 +327,6 @@
integrity = integrity < 0 ? 0 : integrity
return integrity
-// This is purely informational UI that may be accessed by AIs or robots
-/obj/machinery/power/supermatter_shard/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "supermatter_crystal.tmpl", "Supermatter Crystal", 500, 300)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/power/supermatter_shard/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
-
- data["integrity_percentage"] = round(get_integrity())
- var/datum/gas_mixture/env = null
- if(!istype(src.loc, /turf/space))
- env = src.loc.return_air()
-
- if(!env)
- data["ambient_temp"] = 0
- data["ambient_pressure"] = 0
- else
- data["ambient_temp"] = round(env.temperature)
- data["ambient_pressure"] = round(env.return_pressure())
- if(damage > explosion_point)
- data["detonating"] = 1
- else
- data["detonating"] = 0
-
- return data
-
/obj/machinery/power/supermatter_shard/proc/transfer_energy()
for(var/obj/machinery/power/rad_collector/R in GLOB.rad_collectors)
if(get_dist(R, src) <= 15) // Better than using orange() every process
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index d9b20c9d075..36faa49b533 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -185,7 +185,8 @@
/obj/structure/sign,
/obj/machinery/gateway,
/obj/structure/grille,
- /obj/machinery/the_singularitygen/tesla))
+ /obj/machinery/the_singularitygen/tesla,
+ /mob/living/simple_animal/slime))
for(var/A in typecache_filter_multi_list_exclusion(oview(source, zap_range+2), things_to_shock, blacklisted_tesla_types))
diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm
index 914fabbc752..7156eff6482 100644
--- a/code/modules/reagents/chemistry/reagents/toxins.dm
+++ b/code/modules/reagents/chemistry/reagents/toxins.dm
@@ -917,6 +917,7 @@
reagent_state = LIQUID
color = "#191919"
metabolization_rate = 0.1
+ can_synth = FALSE
penetrates_skin = TRUE
taste_mult = 0
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 7bd5b3e9c93..22a5c7494bb 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -1081,6 +1081,26 @@
construction_time = 120
category = list("Cyborg Upgrade Modules")
+/datum/design/borg_upgrade_abductor_engi
+ name = "Cyborg Upgrade (Abdcutor Engineering Equipment)"
+ id = "borg_upgade_abductor_engi"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/abductor_engi
+ req_tech = list("engineering" = 7, "materials" = 7, "abductor" = 4)
+ materials = list(MAT_METAL = 25000, MAT_SILVER = 12500, MAT_PLASMA = 5000, MAT_TITANIUM = 10000, MAT_DIAMOND = 10000) //Base abductor engineering tools * 4
+ construction_time = 120
+ category = list("Cyborg Upgrade Modules")
+
+/datum/design/borg_upgrade_abductor_medi
+ name = "Cyborg Upgrade (Abdcutor Medical Equipment)"
+ id = "borg_upgade_abductor_medi"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/abductor_medi
+ req_tech = list("biotech" = 7, "materials" = 7, "abductor" = 4)
+ materials = list(MAT_METAL = 18000, MAT_GLASS = 1500, MAT_SILVER = 13000, MAT_GOLD = 1000, MAT_PLASMA = 4000, MAT_TITANIUM = 12000, MAT_DIAMOND = 1000) //Base abductor engineering tools *8 + IMS cost
+ construction_time = 120
+ category = list("Cyborg Upgrade Modules")
+
/datum/design/borg_upgrade_lavaproof
name = "Cyborg Upgrade (Lavaproof Chassis)"
id = "borg_upgrade_lavaproof"
diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm
index 9b7ba72209b..0d9acc07207 100644
--- a/code/modules/telesci/gps.dm
+++ b/code/modules/telesci/gps.dm
@@ -16,12 +16,14 @@ GLOBAL_LIST_EMPTY(GPS_list)
/obj/item/gps/New()
..()
GLOB.GPS_list.Add(src)
+ GLOB.poi_list.Add(src)
if(name == "default gps") //use default naming scheme
name = "global positioning system ([gpstag])"
overlays += "working"
/obj/item/gps/Destroy()
GLOB.GPS_list.Remove(src)
+ GLOB.poi_list.Remove(src)
return ..()
/obj/item/gps/emp_act(severity)
diff --git a/icons/mob/guardian.dmi b/icons/mob/guardian.dmi
index a10e1e0b0ec..e10a5c618ea 100644
Binary files a/icons/mob/guardian.dmi and b/icons/mob/guardian.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index ddc5f3cf05c..5221aec971f 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index ddc012075f6..61224892056 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index b13e2c3307d..34a1864257b 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi
index 12a0a371a94..c7df6f275aa 100644
Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 30f34f01b4f..2418b096381 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 62fc0a6120c..771b784f6cd 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 730431590ae..ebc1cca56da 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/nano/templates/supermatter_crystal.tmpl b/nano/templates/supermatter_crystal.tmpl
deleted file mode 100644
index 40bc6796cd7..00000000000
--- a/nano/templates/supermatter_crystal.tmpl
+++ /dev/null
@@ -1,25 +0,0 @@
-{{if data.detonating}}
-
-
CRYSTAL DELAMINATING
-
Evacuate area immediately
-
-
-{{else}}
- Crystal Integrity
- {{:helper.displayBar(data.integrity_percentage, 0, 100, (data.integrity_percentage >= 90) ? 'good' : (data.integrity_percentage >= 25) ? 'average' : 'bad')}}
- {{:data.integrity_percentage}} %
- Environment
-
- Temperature:
-
-
- {{:helper.displayBar(data.ambient_temp, 0, 10000, (data.ambient_temp >= 5000) ? 'bad' : (data.ambient_temp >= 4000) ? 'average' : 'good')}}
- {{:data.ambient_temp}} K
-
-
- Pressure:
-
-
- {{:helper.smoothRound(data.ambient_pressure)}} kPa
-
-{{/if}}
\ No newline at end of file
diff --git a/paradise.dme b/paradise.dme
index efddfaea144..23e566ede1a 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -575,6 +575,7 @@
#include "code\game\gamemodes\miniantags\bot_swarm\swarmer.dm"
#include "code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm"
#include "code\game\gamemodes\miniantags\guardian\guardian.dm"
+#include "code\game\gamemodes\miniantags\guardian\host_actions.dm"
#include "code\game\gamemodes\miniantags\guardian\types\assassin.dm"
#include "code\game\gamemodes\miniantags\guardian\types\bomb.dm"
#include "code\game\gamemodes\miniantags\guardian\types\charger.dm"
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 67e74085c7e..595a6978f91 100644
--- a/tgui/.eslintrc.yml
+++ b/tgui/.eslintrc.yml
@@ -366,13 +366,13 @@ rules:
## Enforce a maximum depth that blocks can be nested
# max-depth: error
## Enforce a maximum line length
- max-len: [error, {
- code: 80,
+ #max-len: [error, {
+ # code: 80,
## Ignore imports
- ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
- ignoreUrls: true,
- ignoreRegExpLiterals: true,
- }]
+ #ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
+ #ignoreUrls: true,
+ #ignoreRegExpLiterals: true,
+ #}]
## Enforce a maximum number of lines per file
# max-lines: error
## Enforce a maximum number of line of code in a function
diff --git a/tgui/packages/tgui/interfaces/Canister.js b/tgui/packages/tgui/interfaces/Canister.js
index 8c184f71e68..621637b4cc2 100644
--- a/tgui/packages/tgui/interfaces/Canister.js
+++ b/tgui/packages/tgui/interfaces/Canister.js
@@ -221,7 +221,6 @@ export const Canister = (props, context) => {
buttons={!!hasHoldingTank && (