diff --git a/_maps/map_files/RandomRuins/SpaceRuins/rocky_motel.dmm b/_maps/map_files/RandomRuins/SpaceRuins/rocky_motel.dmm
index 425cdcd768f4..08a28a4cfd0d 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/rocky_motel.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/rocky_motel.dmm
@@ -126,7 +126,6 @@
/area/ruin/space/powered)
"lq" = (
/obj/structure/closet/cabinet,
-/obj/item/clothing/suit/jacket/fluff,
/obj/item/clothing/under/color/random,
/obj/item/clothing/under/suit/navy,
/obj/item/clothing/under/pants/blackjeans,
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/submaps/rocky_motel_submap.dmm b/_maps/map_files/RandomRuins/SpaceRuins/submaps/rocky_motel_submap.dmm
index 0dcd8d091aa0..8328a2a74ad7 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/submaps/rocky_motel_submap.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/submaps/rocky_motel_submap.dmm
@@ -143,7 +143,6 @@
/area/ruin/space/powered)
"nz" = (
/obj/structure/closet/cabinet,
-/obj/item/clothing/suit/jacket/fluff,
/obj/item/clothing/under/color/random,
/obj/item/clothing/under/suit/navy,
/obj/item/clothing/under/pants/blackjeans,
diff --git a/code/__DEFINES/dcs/datum_signals.dm b/code/__DEFINES/dcs/datum_signals.dm
index 1851311a8abf..fa475cfd2a2a 100644
--- a/code/__DEFINES/dcs/datum_signals.dm
+++ b/code/__DEFINES/dcs/datum_signals.dm
@@ -68,6 +68,8 @@
#define COMPONENT_TWOHANDED_BLOCK_WIELD (1<<0)
///from base of datum/component/two_handed/proc/unwield(mob/living/carbon/user): (/mob/user)
#define COMSIG_TWOHANDED_UNWIELD "twohanded_unwield"
+///from base of /datum/component/forces_doors_open/proc/force_open_door(obj/item): (datum/source, mob/user, atom/target)
+#define COMSIG_TWOHANDED_WIELDED_TRY_WIELD_INTERACT "twohanded_wielded_try_wield_interact"
// /datum/action
@@ -122,3 +124,13 @@
/// Sent when bodies transfer between shades/shards and constructs
/// from base of /datum/component/construct_held_body/proc/transfer_held_body()
#define COMSIG_SHADE_TO_CONSTRUCT_TRANSFER "shade_to_construct_transfer"
+
+
+/// /datum/component/label
+/// Called when a handlabeler is used on an item when off
+#define COMSIG_LABEL_REMOVE "label_remove"
+
+// /datum/ruleset
+
+/// from base of /datum/ruleset/proc/can_apply()
+#define COMSIG_RULESET_FAILED_SPECIES "failed_species"
diff --git a/code/__DEFINES/directions.dm b/code/__DEFINES/directions.dm
index b78423b7bcc2..718ebeedf14f 100644
--- a/code/__DEFINES/directions.dm
+++ b/code/__DEFINES/directions.dm
@@ -35,3 +35,5 @@
#define DIR_JUST_HORIZONTAL(dir) ((dir == EAST) || (dir == WEST))
/// returns TRUE if the direction is NORTH or SOUTH
#define DIR_JUST_VERTICAL(dir) ((dir == NORTH) || (dir == SOUTH))
+
+#define EXCLUSIVE_OR(thing_one, thing_two) ((thing_one)^(thing_two))
diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index 2c9ef8563d63..76332f7fb738 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -71,3 +71,14 @@
#define NUKE_SITE_OFF_STATION_ZLEVEL 2
/// The bomb's location cannot be found.
#define NUKE_SITE_INVALID 3
+
+/**
+ * Dynamic Gamemode Defines
+ */
+#define DYNAMIC_RULESET_NORMAL "Normal"
+#define DYNAMIC_RULESET_FORCED "Forced"
+#define DYNAMIC_RULESET_BANNED "Banned"
+
+#define RULESET_FAILURE_BUDGET "Not enough budget"
+#define RULESET_FAILURE_NO_PLAYERS "No drafted players"
+#define RULESET_FAILURE_CHANGELING_SECONDARY_RULESET "Needs a secondary ruleset in rotation"
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index 1328cbef4371..227cae67ed84 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -732,3 +732,7 @@ do { \
#define LAVALAND_TENDRIL_COLLAPSE_RANGE 2 //! The radius of the chasm created by killed tendrils.
#define ALPHA_VISIBLE 255 // the max alpha
+
+/// Economy account defines
+#define BANK_PIN_MIN 10000
+#define BANK_PIN_MAX 99999
diff --git a/code/__DEFINES/mob_defines.dm b/code/__DEFINES/mob_defines.dm
index bccec9548a2f..d3eb93078ea4 100644
--- a/code/__DEFINES/mob_defines.dm
+++ b/code/__DEFINES/mob_defines.dm
@@ -259,7 +259,7 @@
#define ismorph(A) (istype((A), /mob/living/simple_animal/hostile/morph))
#define issilicon(A) (istype((A), /mob/living/silicon))
-#define isAI(A) (istype((A), /mob/living/silicon/ai))
+#define is_ai(A) (istype((A), /mob/living/silicon/ai))
#define isrobot(A) (istype((A), /mob/living/silicon/robot))
#define isdrone(A) (istype((A), /mob/living/silicon/robot/drone))
#define ispAI(A) (istype((A), /mob/living/silicon/pai))
@@ -273,7 +273,7 @@
#define ispathanimal(A) (ispath(A, /mob/living/simple_animal))
#define iscameramob(A) (istype((A), /mob/camera))
-#define isAIEye(A) (istype((A), /mob/camera/ai_eye))
+#define is_ai_eye(A) (istype((A), /mob/camera/eye))
#define isovermind(A) (istype((A), /mob/camera/blob))
#define isSpirit(A) (istype((A), /mob/spirit))
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index 4b23a6c59af1..5243e3473fda 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -280,8 +280,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_WIELDED "wielded"
/// Wires on this will have their titles randomized for those with SHOW_WIRES
#define TRAIT_OBSCURED_WIRES "obscured_wires"
-/// Forces open doors after a delay specific to the item
-#define TRAIT_FORCES_OPEN_DOORS_ITEM "forces_open_doors_item_varient"
/// Makes the item no longer spit out a visible message when thrown
#define TRAIT_NO_THROWN_MESSAGE "no_message_when_thrown"
/// Makes the item not display a message on storage insertion
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 82ead623fa87..d9b0875dd4d2 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -879,7 +879,7 @@ Returns 1 if the chain up to the area contains the given typepath
continue
O.loc.Exited(O)
- O.setLoc(X)
+ O.set_loc(X)
O.loc.Entered(O)
for(var/mob/M in T)
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index afd12fa96120..7e4d94bf4d13 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -133,7 +133,6 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SUPERMATTER_IMMUNE" = TRAIT_SUPERMATTER_IMMUNE,
"TRAIT_BUTCHER_HUMANS" = TRAIT_BUTCHERS_HUMANS,
"TRAIT_CMAGGED" = TRAIT_CMAGGED,
- "TRAIT_FORCES_OPEN_DOORS" = TRAIT_FORCES_OPEN_DOORS_ITEM,
"TRAIT_OBSCURED_WIRES" = TRAIT_OBSCURED_WIRES,
"TRAIT_XENO_INTERACTABLE" = TRAIT_XENO_INTERACTABLE,
"TRAIT_NO_THROWN_MESSAGE" = TRAIT_NO_THROWN_MESSAGE,
diff --git a/code/_onclick/ai_onclick.dm b/code/_onclick/ai_onclick.dm
index 3801ae81dd78..a9d63f51c6bb 100644
--- a/code/_onclick/ai_onclick.dm
+++ b/code/_onclick/ai_onclick.dm
@@ -102,20 +102,20 @@
add_attack_logs(src, src, "[key_name_admin(src)] might be running a modified client! (failed can_see on AI click of [A]([ADMIN_COORDJMP(pixel_turf)]))", ATKLOG_ALL)
var/message = "[key_name(src)] might be running a modified client! (failed can_see on AI click of [A]([COORD(pixel_turf)]))"
log_admin(message)
- GLOB.discord_manager.send2discord_simple_noadmins("**\[Warning]** [key_name(src)] might be running a modified client! (failed checkTurfVis on AI click of [A]([COORD(pixel_turf)]))")
+ GLOB.discord_manager.send2discord_simple_noadmins("**\[Warning]** [key_name(src)] might be running a modified client! (failed check_turf_vis on AI click of [A]([COORD(pixel_turf)]))")
return FALSE
var/turf_visible
if(pixel_turf)
- turf_visible = GLOB.cameranet.checkTurfVis(pixel_turf)
+ turf_visible = GLOB.cameranet.check_turf_vis(pixel_turf)
if(!turf_visible)
if((istype(loc, /obj/item/aicard) || ismecha(loc)) && (pixel_turf in range(client.view, loc)))
turf_visible = TRUE
else
if(pixel_turf.obscured)
- log_admin("[key_name_admin(src)] might be running a modified client! (failed checkTurfVis on AI click of [A]([COORD(pixel_turf)])")
- add_attack_logs(src, src, "[key_name_admin(src)] might be running a modified client! (failed checkTurfVis on AI click of [A]([ADMIN_COORDJMP(pixel_turf)]))", ATKLOG_ALL)
- GLOB.discord_manager.send2discord_simple_noadmins("**\[Warning]** [key_name(src)] might be running a modified client! (failed checkTurfVis on AI click of [A]([COORD(pixel_turf)]))")
+ log_admin("[key_name_admin(src)] might be running a modified client! (failed check_turf_vis on AI click of [A]([COORD(pixel_turf)])")
+ add_attack_logs(src, src, "[key_name_admin(src)] might be running a modified client! (failed check_turf_vis on AI click of [A]([ADMIN_COORDJMP(pixel_turf)]))", ATKLOG_ALL)
+ GLOB.discord_manager.send2discord_simple_noadmins("**\[Warning]** [key_name(src)] might be running a modified client! (failed check_turf_vis on AI click of [A]([COORD(pixel_turf)]))")
return FALSE
return TRUE
@@ -184,7 +184,7 @@
return
/mob/living/silicon/ai/TurfAdjacent(turf/T)
- return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
+ return (GLOB.cameranet && GLOB.cameranet.check_turf_vis(T))
// APC
diff --git a/code/_onclick/hud/ai_hud.dm b/code/_onclick/hud/ai_hud.dm
index 9d2fba720293..f25d9356c0a8 100644
--- a/code/_onclick/hud/ai_hud.dm
+++ b/code/_onclick/hud/ai_hud.dm
@@ -105,7 +105,7 @@
/atom/movable/screen/ai/state_laws/Click()
if(..())
return
- if(isAI(usr))
+ if(is_ai(usr))
var/mob/living/silicon/ai/AI = usr
AI.subsystem_law_manager()
@@ -156,7 +156,7 @@
/atom/movable/screen/ai/sensors/Click()
if(..())
return
- if(isAI(usr))
+ if(is_ai(usr))
var/mob/living/silicon/ai/AI = usr
AI.sensor_mode()
else if(isrobot(usr))
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 5fbf8c8ef26e..f1823e4b4b85 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -514,7 +514,7 @@ so as to remain in compliance with the most up-to-date laws."
var/mob/living/silicon/ai/AI = usr
var/turf/T = get_turf(target)
if(T)
- AI.eyeobj.setLoc(T)
+ AI.eyeobj.set_loc(T)
//MECHS
/atom/movable/screen/alert/low_mech_integrity
diff --git a/code/controllers/subsystem/SSair.dm b/code/controllers/subsystem/SSair.dm
index 5553f6d78d06..2701124ec466 100644
--- a/code/controllers/subsystem/SSair.dm
+++ b/code/controllers/subsystem/SSair.dm
@@ -12,12 +12,20 @@ SUBSYSTEM_DEF(air)
name = "Atmospherics"
init_order = INIT_ORDER_AIR
priority = FIRE_PRIORITY_AIR
- wait = 2
- flags = SS_BACKGROUND
+ // The MC really doesn't like it if we sleep (even though it's supposed to), and ends up running us continuously. Instead, we ask it to run us every tick, and "sleep" by skipping the current tick.
+ wait = 1
+ flags = SS_BACKGROUND | SS_TICKER
+ /// How long we actually wait between ticks. Will round up to the next server tick.
+ var/self_wait = 0.15 SECONDS
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
offline_implications = "Turfs will no longer process atmos, and all atmospheric machines (including cryotubes) will no longer function. Shuttle call recommended."
cpu_display = SS_CPUDISPLAY_HIGH
+ /// When did we last finish running a complete tick?
+ var/last_complete_tick = 0
+ /// When did we last start a tick?
+ var/last_tick_start = 0
+
/// How long we took for a full pass through the subsystem. Custom-tracked version of `cost`.
var/datum/resumable_cost_counter/cost_full = new()
/// How long we spent sleeping while waiting for MILLA to finish the last tick, shown in SS Info's C block as ZZZ.
@@ -178,26 +186,21 @@ SUBSYSTEM_DEF(air)
currentpart = SSair.currentpart
milla_idle = SSair.milla_idle
-#define SLEEPABLE_TIMER (world.time + world.tick_usage * world.tick_lag / 100)
/datum/controller/subsystem/air/fire(resumed = 0)
// All atmos stuff assumes MILLA is synchronous. Ensure it actually is.
- if(!milla_idle || length(sleepers) > 0)
- var/timer = SLEEPABLE_TIMER
-
- while(!milla_idle || length(sleepers) > 0)
- // Sleep for 1ms.
- sleep(0.01)
- var/new_timer = SLEEPABLE_TIMER
- time_slept.record_progress((new_timer - timer) * 100, FALSE)
- timer = new_timer
+ var/now = world.timeofday + (world.tick_lag * world.tick_usage) / 100
+ var/elapsed = now - last_complete_tick
+ if(!milla_idle || (elapsed >= 0 && elapsed < self_wait))
+ return
- time_slept.record_progress((SLEEPABLE_TIMER - timer) * 100, TRUE)
+ if(last_tick_start <= last_complete_tick)
+ last_tick_start = now
+ time_slept.record_progress(max(0, elapsed) * 100, TRUE)
// Run the sleepless callbacks again in case more showed up since on_milla_tick_finished()
run_sleepless_callbacks()
fire_sleepless(resumed)
-#undef SLEEPABLE_TIMER
/datum/controller/subsystem/air/proc/fire_sleepless(resumed)
// Any proc that wants MILLA to be synchronous should not sleep.
@@ -317,13 +320,15 @@ SUBSYSTEM_DEF(air)
milla_idle = FALSE
cost_milla_tick = MC_AVERAGE(cost_milla_tick, get_milla_tick_time())
- cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), state != SS_PAUSED && state != SS_PAUSING)
+ cost_full.record_progress(TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer), FALSE)
if(state == SS_PAUSED || state == SS_PAUSING)
in_milla_safe_code = FALSE
return
resumed = 0
currentpart = SSAIR_DEFERREDPIPENETS
+ last_complete_tick = world.timeofday + (world.tick_lag * world.tick_usage) / 100
+ cost_full.record_progress(0, TRUE)
in_milla_safe_code = FALSE
/datum/controller/subsystem/air/proc/build_pipenets(resumed = 0)
@@ -624,6 +629,9 @@ SUBSYSTEM_DEF(air)
for(var/turf/T as anything in block(low_corner, high_corner))
T.Initialize_Atmos(times_fired)
milla_load_turfs(low_corner, high_corner)
+ for(var/turf/T as anything in block(low_corner, high_corner))
+ T.milla_data.len = 0
+ T.milla_data = null
/datum/controller/subsystem/air/proc/setup_write_to_milla()
var/watch = start_watch()
diff --git a/code/controllers/subsystem/SSshuttles.dm b/code/controllers/subsystem/SSshuttles.dm
index 1ccc4e5be710..2065c39026f6 100644
--- a/code/controllers/subsystem/SSshuttles.dm
+++ b/code/controllers/subsystem/SSshuttles.dm
@@ -173,7 +173,7 @@ SUBSYSTEM_DEF(shuttle)
var/callShuttle = 1
for(var/thing in GLOB.shuttle_caller_list)
- if(isAI(thing))
+ if(is_ai(thing))
var/mob/living/silicon/ai/AI = thing
if(AI.stat || !AI.client)
continue
diff --git a/code/controllers/subsystem/tickets/SStickets.dm b/code/controllers/subsystem/tickets/SStickets.dm
index 60a085664367..705dc5259694 100644
--- a/code/controllers/subsystem/tickets/SStickets.dm
+++ b/code/controllers/subsystem/tickets/SStickets.dm
@@ -161,7 +161,7 @@ SUBSYSTEM_DEF(tickets)
if(M)
L += "([ADMIN_QUE(M,"?")]) ([ADMIN_PP(M,"PP")]) ([ADMIN_VV(M,"VV")]) ([ADMIN_TP(M,"TP")]) ([ADMIN_SM(M,"SM")]) ([admin_jump_link(M)])"
L += "(TICKET) "
- L += "[isAI(M) ? "(CL)" : ""] (TAKE) "
+ L += "[is_ai(M) ? "(CL)" : ""] (TAKE) "
L += "(RESOLVE) (AUTO) "
// SS220 ADDTITION START
if(GLOB.configuration.gpt.gpt_enabled)
diff --git a/code/datums/components/forces_doors_open.dm b/code/datums/components/forces_doors_open.dm
new file mode 100644
index 000000000000..c099a927d925
--- /dev/null
+++ b/code/datums/components/forces_doors_open.dm
@@ -0,0 +1,117 @@
+/**
+ * /datum/component/forces_doors_open
+ *
+ * This component allows item to pry open doors.
+ *
+ */
+
+/datum/component/forces_doors_open
+ /// The time it takes to open the airlock when forced
+ var/time_to_open
+ /// Whether the airlock can be forced open while powered.
+ var/can_force_open_while_powered
+ /// Whether the airlock can be forced open while unpowered.
+ var/can_force_open_while_unpowered
+ /// Whether the firedoor can be opened.
+ var/can_open_firedoors
+ /// The sound played when the airlock is forced open.
+ var/open_sound
+ /// Indicates whether no sound should be played when opening.
+ var/no_sound
+
+/datum/component/forces_doors_open/Initialize(
+ time_to_open = 5 SECONDS,
+ can_force_open_while_powered = TRUE,
+ can_force_open_while_unpowered = TRUE,
+ can_open_firedoors = TRUE,
+ open_sound = 'sound/machines/airlock_alien_prying.ogg',
+ no_sound = FALSE)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.time_to_open = time_to_open
+ src.can_force_open_while_powered = can_force_open_while_powered
+ src.can_force_open_while_unpowered = can_force_open_while_unpowered
+ src.can_open_firedoors = can_open_firedoors
+ src.open_sound = open_sound
+ src.no_sound = no_sound
+
+ RegisterSignal(parent, COMSIG_INTERACTING, PROC_REF(on_interact))
+
+/datum/component/forces_doors_open/proc/on_interact(datum/source, mob/user, atom/target)
+ SIGNAL_HANDLER // COMSIG_INTERACTING
+
+ if(check_intent(user))
+ return
+
+ if(try_to_open_firedoor(target))
+ return ITEM_INTERACT_COMPLETE
+
+ if(try_to_force_open_airlock(user, target))
+ return ITEM_INTERACT_COMPLETE
+
+/// check is user in harm intent
+/datum/component/forces_doors_open/proc/check_intent(mob/user)
+ if(user.a_intent == INTENT_HARM)
+ return TRUE
+
+/// try to open firedoor
+/datum/component/forces_doors_open/proc/try_to_open_firedoor(atom/target)
+ if(can_open_firedoors && istype(target, /obj/machinery/door/firedoor)) // open firedoor and dont open blastdoors and windowdoors
+ INVOKE_ASYNC(src, PROC_REF(open_unpowered_door), target)
+ return TRUE
+
+/// try to force open airlock
+/datum/component/forces_doors_open/proc/try_to_force_open_airlock(mob/user, obj/machinery/door/airlock/airlock)
+ if(!istype(airlock, /obj/machinery/door/airlock)) // only airlocks have arePowerSystemsOn()
+ return
+
+ if(SEND_SIGNAL(parent, COMSIG_TWOHANDED_WIELDED_TRY_WIELD_INTERACT) && airlock.arePowerSystemsOn())
+ to_chat(user, "You need to be wielding [parent] to do that!")
+ return TRUE
+
+ if(!airlock.density)
+ return TRUE
+
+ // open unpowered
+ if(can_force_open_while_unpowered && !airlock.arePowerSystemsOn())
+ INVOKE_ASYNC(src, PROC_REF(open_unpowered_door), airlock)
+ return TRUE
+
+ // open powered
+ if(can_force_open_while_powered)
+ INVOKE_ASYNC(src, PROC_REF(open_powered_airlock), airlock, user)
+ return TRUE
+
+/// open airlock with delay
+/datum/component/forces_doors_open/proc/open_powered_airlock(obj/machinery/door/airlock/airlock, mob/user)
+ if(!no_sound)
+ playsound(parent, open_sound, 100, 1)
+
+ if(do_after_once(user, time_to_open, target = airlock, attempt_cancel_message = "You decide to stop prying [airlock] with [parent]."))
+ if(airlock.open(TRUE))
+ return // successfully opened
+
+ // opening failed
+ if(airlock.density)
+ to_chat(user, "Despite your attempts, [airlock] refuses to open.")
+
+/// open door without checks
+/datum/component/forces_doors_open/proc/open_unpowered_door(obj/machinery/door/door)
+ door.open(TRUE)
+
+/// subtype for mantis blades
+/datum/component/forces_doors_open/mantis/on_interact(datum/source, mob/user, atom/target)
+ if(check_intent(user))
+ return
+
+ if(try_to_open_firedoor(target))
+ return ITEM_INTERACT_COMPLETE
+
+ var/obj/item/melee/mantis_blade/secondblade = user.get_inactive_hand()
+ if(!istype(secondblade, /obj/item/melee/mantis_blade))
+ to_chat(user, "You need a second [parent] to pry open doors!")
+ return ITEM_INTERACT_COMPLETE
+
+ if(try_to_force_open_airlock(user, target))
+ return ITEM_INTERACT_COMPLETE
diff --git a/code/datums/components/label.dm b/code/datums/components/label.dm
index dcc7a3fc73bb..876a5bb98740 100644
--- a/code/datums/components/label.dm
+++ b/code/datums/components/label.dm
@@ -22,12 +22,12 @@
apply_label()
/datum/component/label/RegisterWithParent()
- RegisterSignal(parent, COMSIG_ATTACK_BY, PROC_REF(on_attack_by))
+ RegisterSignal(parent, COMSIG_LABEL_REMOVE, PROC_REF(on_remove))
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_NAME, PROC_REF(on_update_name))
/datum/component/label/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_ATTACK_BY, COMSIG_PARENT_EXAMINE, COMSIG_ATOM_UPDATE_NAME))
+ UnregisterSignal(parent, list(COMSIG_LABEL_REMOVE, COMSIG_PARENT_EXAMINE, COMSIG_ATOM_UPDATE_NAME))
/**
This proc will fire after the parent is hit by a hand labeler which is trying to apply another label.
@@ -41,29 +41,10 @@
label_name = _label_name
apply_label()
-/**
- This proc will trigger when any object is used to attack the parent.
-
- If the attacking object is not a hand labeler, it will return.
- If the attacking object is a hand labeler it will restore the name of the parent to what it was before this component was added to it, and the component will be deleted.
-
- Arguments:
- * source: The parent.
- * attacker: The object that is hitting the parent.
- * user: The mob who is wielding the attacking object.
-*/
-/datum/component/label/proc/on_attack_by(datum/source, obj/item/attacker, mob/user)
- SIGNAL_HANDLER // COMSIG_ATTACK_BY
- // If the attacking object is not a hand labeler or it's not off (has a label ready to apply), return.
- // The hand labeler should be off in order to remove a label.
- var/obj/item/hand_labeler/labeler = attacker
- if(!istype(labeler) || labeler.mode)
- return
-
+/datum/component/label/proc/on_remove(datum/source)
remove_label()
- playsound(parent, 'sound/items/poster_ripped.ogg', 20, TRUE)
- to_chat(user, "You remove the label from [parent].")
qdel(src) // Remove the component from the object.
+ return TRUE
/**
This proc will trigger when someone examines the parent.
diff --git a/code/datums/components/two_handed.dm b/code/datums/components/two_handed.dm
index df36672ac546..23e1856b65d5 100644
--- a/code/datums/components/two_handed.dm
+++ b/code/datums/components/two_handed.dm
@@ -111,6 +111,7 @@
RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen))
RegisterSignal(parent, COMSIG_CARBON_UPDATE_HANDCUFFED, PROC_REF(on_handcuff_user))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
+ RegisterSignal(parent, COMSIG_TWOHANDED_WIELDED_TRY_WIELD_INTERACT, PROC_REF(check_unwielded))
// Remove all siginals registered to the parent item
/datum/component/two_handed/UnregisterFromParent()
@@ -307,6 +308,15 @@
// Clear any old refrence to an item that should be gone now
offhand_item = null
+/**
+ * Check if item is unwielded
+ *
+ * returns TRUE if unwielded
+ */
+/datum/component/two_handed/proc/check_unwielded()
+ SIGNAL_HANDLER // COMSIG_TWOHANDED_WIELDED_TRY_WIELD_INTERACT
+ return wielded ? FALSE : TRUE;
+
/**
* on_attack triggers on attack with the parent item
*/
diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm
index 10095d0ad13e..ddb6782f6dbc 100644
--- a/code/datums/diseases/magnitis.dm
+++ b/code/datums/diseases/magnitis.dm
@@ -22,7 +22,7 @@
if(!M.anchored && (M.flags & CONDUCT))
step_towards(M,affected_mob)
for(var/mob/living/silicon/S in orange(2,affected_mob))
- if(isAI(S)) continue
+ if(is_ai(S)) continue
step_towards(S,affected_mob)
if(3)
if(prob(2))
@@ -37,7 +37,7 @@
for(i=0,iYou haven't set location [location_number] yet!")
return
- AI.eyeobj.setLoc(AI.stored_locations[location_number])
+ AI.eyeobj.set_loc(AI.stored_locations[location_number])
/datum/keybinding/ai/to_location/one
name = "Jump to Location One"
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 0309d55ad7e0..2225053d77f8 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -1517,7 +1517,7 @@
message_admins("[key_name_admin(usr)] has un-emagged [key_name_admin(current)]")
if("unemagcyborgs")
- if(!isAI(current))
+ if(!is_ai(current))
return
var/mob/living/silicon/ai/ai = current
for(var/mob/living/silicon/robot/R in ai.connected_robots)
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
index 037bc69cca02..c55de30be0b6 100644
--- a/code/datums/station_traits/negative_traits.dm
+++ b/code/datums/station_traits/negative_traits.dm
@@ -1,7 +1,7 @@
/datum/station_trait/carp_infestation
name = "Carp infestation"
trait_type = STATION_TRAIT_NEGATIVE
- weight = 5
+ weight = 3
show_in_report = TRUE
report_message = "Dangerous fauna is present in the area of this station."
diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm
index a545c1504e53..b4f5420d6ec6 100644
--- a/code/datums/station_traits/positive_traits.dm
+++ b/code/datums/station_traits/positive_traits.dm
@@ -213,7 +213,7 @@
var/cybernetic_type = job_to_cybernetic[job.type]
if(!cybernetic_type)
- if(isAI(spawned))
+ if(is_ai(spawned))
var/mob/living/silicon/ai/ai = spawned
ai.eyeobj.relay_speech = TRUE //surveillance upgrade. the ai gets cybernetics too.
return
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 54e94de1030b..cfc7910f17e1 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -342,11 +342,13 @@
ADD_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath")
var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
H.add_hud_to(owner)
+ owner.permanent_huds |= H
return ..()
/datum/status_effect/hippocratic_oath/on_remove()
REMOVE_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath")
var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
+ owner.permanent_huds ^= H
H.remove_hud_from(owner)
/datum/status_effect/hippocratic_oath/tick()
diff --git a/code/datums/uplink_items/uplink_general.dm b/code/datums/uplink_items/uplink_general.dm
index ecbe37308bc5..43de5d7988b7 100644
--- a/code/datums/uplink_items/uplink_general.dm
+++ b/code/datums/uplink_items/uplink_general.dm
@@ -949,6 +949,15 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/autosurgeon/organ/syndicate/oneuse/scope_eyes
cost = 10
+/datum/uplink_item/cyber_implants/mantis_kit
+ name = "'Naginata' Mantis Blades Kit"
+ desc = "A pair of devastating 'Naginata' concealable mantis blades, which retract into the arms of the user. \
+ Their monomolecular edges will easily tear through flesh and armor alike, and can even pry open airlocks when used together. \
+ When both blades are equipped, they enable the user to perform double attacks. \
+ Can be used to parry incoming melee attacks."
+ reference = "MBK"
+ item = /obj/item/storage/box/syndie_kit/syndie_mantis
+ cost = 60
////////////////////////////////////////
// MARK: POINTLESS BADASSERY
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 37f2b52023c9..de66376bff58 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -61,8 +61,8 @@
var/list/firealarms
var/firedoors_last_closed_on = 0
- /// The air alarm to use for atmos_alert consoles
- var/obj/machinery/alarm/master_air_alarm
+ /// The air alarms present in this area.
+ var/list/air_alarms = list()
/// The list of vents in our area.
var/list/obj/machinery/atmospherics/unary/vent_pump/vents = list()
/// The list of scrubbers in our area.
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 13e3133e7d9d..2c102af007a7 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -198,9 +198,9 @@
return FALSE
return TRUE
-// Used in shuttle movement and AI eye stuff.
-// Primarily used to notify objects being moved by a shuttle/bluespace fuckup.
-/atom/movable/proc/setLoc(T, teleported=0)
+/// Used in shuttle movement and camera eye stuff.
+/// Primarily used to notify objects being moved by a shuttle/bluespace fuckup.
+/atom/movable/proc/set_loc(T, teleported=0)
var/old_loc = loc
loc = T
Moved(old_loc, get_dir(old_loc, loc))
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index da9bc720a4f4..83704a835859 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -534,7 +534,7 @@
else if(isrobot(commenter))
var/mob/living/silicon/robot/U = commenter
commenter_display = "[U.name] ([U.modtype] [U.braintype])"
- else if(isAI(commenter))
+ else if(is_ai(commenter))
var/mob/living/silicon/ai/U = commenter
commenter_display = "[U.name] (artificial intelligence)"
comment_text = "Made by [commenter_display] on [GLOB.current_date_string] [station_time_timestamp()]:
[comment_text]"
diff --git a/code/game/gamemodes/autotraitor/autotraitor.dm b/code/game/gamemodes/autotraitor/autotraitor.dm
index 9c510fc5a744..cb4e4048bca2 100644
--- a/code/game/gamemodes/autotraitor/autotraitor.dm
+++ b/code/game/gamemodes/autotraitor/autotraitor.dm
@@ -72,7 +72,7 @@
if(player.mind.has_antag_datum(/datum/antagonist/traitor))
traitor_count += 1
continue
- if(ishuman(player) || isAI(player))
+ if(ishuman(player) || is_ai(player))
if((ROLE_TRAITOR in player.client.prefs.be_special) && !player.client.skip_antag && !jobban_isbanned(player, ROLE_TRAITOR) && !jobban_isbanned(player, ROLE_SYNDICATE))
possible_traitors += player.mind
for(var/datum/mind/player in possible_traitors)
@@ -144,3 +144,6 @@
message_admins("New traitor roll passed. Making a new Traitor.")
log_game("New traitor roll passed. Making a new Traitor.")
character.mind.make_Traitor()
+
+/datum/game_mode/traitor/autotraitor/on_mob_cryo(mob/sleepy_mob, obj/machinery/cryopod/cryopod)
+ possible_traitors.Remove(sleepy_mob)
diff --git a/code/game/gamemodes/dynamic/antag_rulesets.dm b/code/game/gamemodes/dynamic/antag_rulesets.dm
new file mode 100644
index 000000000000..3e56784bdbb2
--- /dev/null
+++ b/code/game/gamemodes/dynamic/antag_rulesets.dm
@@ -0,0 +1,192 @@
+/**
+ * These are gamemode rulesets for the dynamic gamemode type. They determine what antagonists spawn during a round.
+ */
+/datum/ruleset
+ /// What this ruleset is called
+ var/name = "BASE RULESET"
+ /// The cost to roll this ruleset
+ var/ruleset_cost = 1
+ /// The weight to roll this ruleset
+ var/ruleset_weight = 1
+ /// The cost to roll an antagonist of this ruleset
+ var/antag_cost = 1
+ /// The weight to roll an antagonist of this ruleset
+ var/antag_weight = 1
+ /// Antagonist datum to apply to users
+ var/datum/antagonist/antagonist_type
+ /// A ruleset to be added when this ruleset is selected by the gamemode
+ var/datum/ruleset/implied/implied_ruleset_type
+
+ /// These roles 100% cannot be this antagonist
+ var/list/banned_jobs = list("Cyborg")
+ /// These roles can't be antagonists because mindshielding (this can be disabled via config)
+ var/list/protected_jobs = list(
+ "Security Officer",
+ "Warden",
+ "Detective",
+ "Head of Security",
+ "Captain",
+ "Blueshield",
+ "Nanotrasen Representative",
+ "Magistrate",
+ "Internal Affairs Agent",
+ "Nanotrasen Career Trainer",
+ "Nanotrasen Navy Officer",
+ "Special Operations Officer",
+ "Trans-Solar Federation General"
+ )
+ /// Applies the mind roll to assigned_role, preventing them from rolling a normal job. Good for wizards and nuclear operatives.
+ var/assign_job_role = FALSE
+ /// A blacklist of species names that cannot play this antagonist
+ var/list/banned_species = list()
+ /// If true, the species blacklist is now a species whitelist
+ var/banned_species_only = FALSE
+
+ // var/list/banned_mutual_rulesets = list() // UNIMPLEMENTED: could be used to prevent nukies rolling while theres cultists, or wizards, etc
+
+ /* This stuff changes, all stuff above is static */
+ /// How many antagonists to spawn
+ var/antag_amount = 0
+ /// All of the minds that we will make into our antagonist type
+ var/list/datum/mind/pre_antags = list()
+
+/datum/ruleset/Destroy(force, ...)
+ stack_trace("[src] ([type]) was destroyed.")
+ return ..()
+
+/datum/ruleset/proc/ruleset_possible(ruleset_budget, rulesets)
+ if(ruleset_budget < ruleset_cost)
+ return RULESET_FAILURE_BUDGET
+ if(!length(SSticker.mode.get_players_for_role(antagonist_type::job_rank))) // this specifically needs to be job_rank not special_rank
+ return RULESET_FAILURE_NO_PLAYERS
+
+/datum/ruleset/proc/antagonist_possible(budget)
+ return budget >= antag_cost
+
+/datum/ruleset/proc/pre_setup()
+ if(antag_amount == 0)
+ return
+ if(antag_amount < 0)
+ stack_trace("/datum/ruleset/proc/pre_setup() for [type] somehow had a negative antagonist amount")
+ return
+ var/list/datum/mind/possible_antags = SSticker.mode.get_players_for_role(antagonist_type::job_rank) // this specifically needs to be job_rank not special_rank
+ if(!length(possible_antags))
+ refund("No possible players for [src] ruleset.") // we allocate antag budget before we allocate players, and previous rulesets can steal our players
+ return antag_cost * antag_amount // shitty refund for now
+
+ if(GLOB.configuration.gamemode.prevent_mindshield_antags)
+ banned_jobs += protected_jobs
+
+ shuffle_inplace(possible_antags)
+ for(var/datum/mind/antag as anything in possible_antags)
+ if(antag_amount <= 0)
+ break
+ if(!can_apply(antag))
+ continue
+ pre_antags += antag
+ if(assign_job_role)
+ antag.assigned_role = antagonist_type::special_role
+ antag.special_role = antagonist_type::special_role
+ antag.restricted_roles = banned_jobs
+ antag_amount -= 1
+
+ if(antag_amount > 0)
+ refund("Missing [antag_amount] antagonists for [src] ruleset.")
+ return antag_cost * antag_amount // shitty refund for now
+
+/datum/ruleset/proc/can_apply(datum/mind/antag)
+ if(EXCLUSIVE_OR(antag.current.client.prefs.active_character.species in banned_species, banned_species_only))
+ SEND_SIGNAL(src, COMSIG_RULESET_FAILED_SPECIES)
+ return FALSE
+ if(antag.special_role) // You can only have 1 antag roll at a time, sorry
+ return FALSE
+ return TRUE
+
+/datum/ruleset/proc/post_setup(datum/game_mode/dynamic)
+ for(var/datum/mind/antag as anything in pre_antags)
+ antag.add_antag_datum(antagonist_type)
+
+/datum/ruleset/proc/refund(info)
+ // not enough antagonists signed up!!! idk what to do. The only real solution is to procedurally allocate budget, which will result in 1000x more get_players_for_role() calls. Which is not cheap.
+ // OR we cache get_players_for_role() and then just check if they have a special_role. May be unreliable.
+ // log_dynamic("[info] Refunding [antag_cost * antag_amount] budget.")
+ // Currently unimplemented. Will be useful for a possible future PR where latejoin antagonists are factored in.
+ return
+
+/datum/ruleset/traitor
+ name = "Traitor"
+ ruleset_weight = 11
+ antag_cost = 5
+ antag_weight = 2
+ antagonist_type = /datum/antagonist/traitor
+
+/datum/ruleset/traitor/post_setup(datum/game_mode/dynamic)
+ var/random_time = rand(5 MINUTES, 15 MINUTES)
+ for(var/datum/mind/antag as anything in pre_antags)
+ var/datum/antagonist/traitor/traitor_datum = new antagonist_type()
+ if(ishuman(antag.current))
+ traitor_datum.delayed_objectives = TRUE
+ traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), random_time, TIMER_DELETE_ME)
+ antag.add_antag_datum(traitor_datum)
+ addtimer(CALLBACK(dynamic, TYPE_PROC_REF(/datum/game_mode, fill_antag_slots)), random_time)
+
+/datum/ruleset/vampire
+ name = "Vampire"
+ ruleset_weight = 12
+ antag_cost = 10
+ antagonist_type = /datum/antagonist/vampire
+
+ banned_jobs = list("Cyborg", "AI", "Chaplain")
+ banned_species = list("Machine")
+ implied_ruleset_type = /datum/ruleset/implied/mindflayer
+
+/datum/ruleset/changeling
+ name = "Changeling"
+ ruleset_weight = 9
+ antag_cost = 10
+ antagonist_type = /datum/antagonist/changeling
+
+ banned_jobs = list("Cyborg", "AI")
+ banned_species = list("Machine")
+ implied_ruleset_type = /datum/ruleset/implied/mindflayer
+
+/datum/ruleset/changeling/ruleset_possible(ruleset_budget, rulesets)
+ // Theres already a ruleset, we're good to go
+ if(length(rulesets))
+ return ..()
+ // We're the first ruleset, but we can afford another ruleset
+ if((ruleset_budget >= /datum/ruleset/traitor::ruleset_cost) || (ruleset_budget >= /datum/ruleset/vampire::ruleset_cost))
+ return ..()
+ return RULESET_FAILURE_CHANGELING_SECONDARY_RULESET
+
+// This is the fucking worst, but its required to not change functionality with mindflayers. Cannot be rolled normally, this is applied by other methods.
+/datum/ruleset/implied
+ // These 3 variables should never change
+ ruleset_cost = 0
+ ruleset_weight = 0
+ antag_weight = 0
+ // antag_cost is allowed to be edited to help with refunding antagonists
+ antag_cost = 0
+ /// This signal is registered on whatever (multiple) rulesets implied us. This will call on_implied.
+ var/target_signal
+ /// Set this to true if this implied ruleset was activated
+ var/was_triggered = FALSE
+
+/datum/ruleset/implied/proc/on_implied(datum/antagonist/implier)
+ stack_trace("[type]/on_implied() not implemented!")
+
+/datum/ruleset/implied/mindflayer
+ name = "Mindflayer"
+ antagonist_type = /datum/antagonist/mindflayer
+ antag_cost = 10
+ target_signal = COMSIG_RULESET_FAILED_SPECIES
+
+ banned_jobs = list("Cyborg", "AI")
+ banned_species = list("Machine")
+ banned_species_only = TRUE
+
+/datum/ruleset/implied/mindflayer/on_implied(datum/ruleset/implier)
+ // log_dynamic("Rolled implied [name]: +1 [name], -1 [implier.name].")
+ implier.antag_amount -= 1
+ antag_amount += 1
+ was_triggered = TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
new file mode 100644
index 000000000000..1e0e1ec4ad2e
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -0,0 +1,173 @@
+GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
+
+/datum/game_mode/dynamic
+ name = "Dynamic"
+ config_tag = "dynamic"
+ secondary_restricted_jobs = list("AI")
+ required_players = 10
+ /// Non-implied rulesets in play
+ var/list/datum/ruleset/rulesets = list()
+ /// Implied rulesets that are in play
+ var/list/datum/ruleset/implied_rulesets = list()
+
+ /// How much budget is left after roundstart antagonists roll
+ var/budget_overflow = 0
+
+/datum/game_mode/dynamic/announce()
+ to_chat(world, "The current game mode is - Dynamic")
+ var/list/possible_rulesets = list()
+ for(var/datum/ruleset/ruleset as anything in subtypesof(/datum/ruleset))
+ if(ruleset.ruleset_weight <= 0)
+ continue
+ possible_rulesets |= ruleset.name
+ if(ruleset.implied_ruleset_type)
+ possible_rulesets |= ruleset.implied_ruleset_type.name
+ to_chat(world, "Possible Rulesets: [english_list(possible_rulesets)]")
+
+/datum/game_mode/dynamic/proc/allocate_ruleset_budget()
+ var/ruleset_budget = text2num(GLOB.dynamic_forced_rulesets["budget"] || pickweight(list("0" = 3, "1" = 5, "2" = 12, "3" = 3))) // more likely to or 2
+ // log_dynamic("Allocated gamemode budget: [ruleset_budget]")
+ var/list/possible_rulesets = list()
+ for(var/datum/ruleset/ruleset as anything in subtypesof(/datum/ruleset))
+ if(ruleset.ruleset_weight <= 0)
+ continue
+ if(GLOB.dynamic_forced_rulesets[ruleset] == DYNAMIC_RULESET_BANNED)
+ continue
+ var/datum/ruleset/new_ruleset = new ruleset()
+ possible_rulesets[new_ruleset] = new_ruleset.ruleset_weight
+
+ // log_dynamic("Available rulesets: [english_list(possible_rulesets)]")
+
+ for(var/datum/ruleset/ruleset as anything in GLOB.dynamic_forced_rulesets)
+ if(ruleset == "budget")
+ continue
+ if(GLOB.dynamic_forced_rulesets[ruleset] != DYNAMIC_RULESET_FORCED)
+ continue
+ if(!ispath(ruleset, /datum/ruleset))
+ stack_trace("Non-ruleset in GLOB.dynamic_forced_rulesets: \"[ruleset]\" ([ruleset?.type])")
+ continue
+ // log_dynamic("Forcing ruleset: [ruleset.name]")
+ ruleset_budget -= pick_ruleset(new ruleset, ruleset_budget, force = TRUE)
+ for(var/datum/ruleset/old_ruleset in possible_rulesets)
+ if(old_ruleset.type == ruleset)
+ possible_rulesets -= old_ruleset
+ qdel(old_ruleset)
+
+ while(ruleset_budget >= 0)
+ var/datum/ruleset/ruleset = pickweight(possible_rulesets)
+ if(!ruleset)
+ // log_dynamic("No more available rulesets")
+ return
+ ruleset_budget -= pick_ruleset(ruleset, ruleset_budget)
+ possible_rulesets -= ruleset
+ // log_dynamic("No more ruleset budget")
+
+/datum/game_mode/dynamic/proc/pick_ruleset(datum/ruleset/ruleset, ruleset_budget, force)
+ if(!ruleset)
+ return
+ if(!force)
+ var/failure_reason = ruleset.ruleset_possible(ruleset_budget, rulesets)
+ if(failure_reason)
+ // log_dynamic("Failed [ruleset.name] ruleset: [failure_reason]")
+ return
+ // log_dynamic("Rolled ruleset: [ruleset.name]")
+ rulesets[ruleset] = ruleset.antag_weight
+ . = ruleset.ruleset_cost // return the ruleset cost to be subtracted from the gamemode budget
+ if(!ruleset.implied_ruleset_type)
+ return
+
+ var/datum/ruleset/implied/implied = locate(ruleset.implied_ruleset_type) in implied_rulesets
+ if(!implied)
+ // log_dynamic("Adding implied ruleset: [ruleset.implied_ruleset_type.name]")
+ implied = new ruleset.implied_ruleset_type
+ implied_rulesets += implied
+ implied.RegisterSignal(ruleset, implied.target_signal, TYPE_PROC_REF(/datum/ruleset/implied, on_implied))
+
+/datum/game_mode/dynamic/proc/allocate_antagonist_budget()
+ if(!length(rulesets))
+ // log_dynamic("No rulesets in play.")
+ return
+ var/budget = num_players()
+ // log_dynamic("Allocated antagonist budget: [budget].")
+
+ for(var/datum/ruleset/ruleset in rulesets)
+ ruleset.antag_amount = 1
+ budget -= ruleset.antag_cost
+ // log_dynamic("Automatic deduction: +1 [ruleset.name]. Remaining budget: [budget].")
+
+ // log_dynamic("Rulesets in play: [english_list((rulesets + implied_rulesets))]")
+
+ apply_antag_budget(budget)
+
+/datum/game_mode/dynamic/proc/apply_antag_budget(budget) // todo, can be called later in the game to apply more budget. That also means there has to be shit done for latejoins.
+ var/list/temp_rulesets = rulesets.Copy()
+ while(budget >= 0)
+ var/datum/ruleset/ruleset = pickweight(temp_rulesets)
+ if(!ruleset)
+ // log_dynamic("No rulesets remaining. Remaining budget: [budget].")
+ budget_overflow = budget
+ return
+ if(!ruleset.antagonist_possible(budget))
+ // log_dynamic("Rolled [ruleset.name]: failed, removing [ruleset.name] ruleset.")
+ temp_rulesets -= ruleset
+ continue
+ ruleset.antag_amount++
+ budget -= ruleset.antag_cost
+ // log_dynamic("Rolled [ruleset.name]: success, +1 [ruleset.name]. Remaining budget: [budget].")
+ // log_dynamic("No more antagonist budget remaining.")
+
+/datum/game_mode/dynamic/pre_setup()
+ // var/watch = start_watch()
+ // log_dynamic("Starting dynamic setup.")
+ allocate_ruleset_budget()
+ // log_dynamic("-=-=-=-=-=-=-=-=-=-=-=-=-")
+ allocate_antagonist_budget()
+ // log_dynamic("=-=-=-=-=-=-=-=-=-=-=-=-=")
+
+ for(var/datum/ruleset/ruleset in (rulesets + implied_rulesets)) // rulesets first, then implied rulesets
+ // log_dynamic("Applying [ruleset.antag_amount] [ruleset.name]\s.")
+ budget_overflow += ruleset.pre_setup()
+
+ // log_dynamic("Budget overflow: [budget_overflow].")
+ // for the future, maybe try readding antagonists with apply_antag_budget(budget_overflow)
+ // log_dynamic("Finished dynamic setup in [stop_watch(watch)]s")
+ return TRUE
+
+/datum/game_mode/dynamic/post_setup()
+ for(var/datum/ruleset/ruleset in (rulesets + implied_rulesets))
+ // if(length(ruleset.pre_antags))
+ // log_dynamic("Making antag datums for [ruleset.name] ruleset.")
+ ruleset.post_setup(src)
+ ..()
+
+/datum/game_mode/dynamic/traitors_to_add()
+ . = floor(budget_overflow / /datum/ruleset/traitor::antag_cost)
+ budget_overflow -= (. * /datum/ruleset/traitor::antag_cost)
+
+/datum/game_mode/dynamic/latespawn(mob)
+ . = ..()
+ budget_overflow++
+
+/datum/game_mode/dynamic/on_mob_cryo(mob/sleepy_mob, obj/machinery/cryopod/cryopod)
+ var/turf/T = get_turf(cryopod)
+ if(!T || is_admin_level(T.z))
+ return
+ budget_overflow--
+ if(!sleepy_mob.mind || !length(sleepy_mob.mind.antag_datums))
+ return
+ for(var/datum/antagonist/antag in sleepy_mob.mind.antag_datums)
+ for(var/datum/ruleset/possible_ruleset as anything in subtypesof(/datum/ruleset))
+ if(istype(antag, possible_ruleset.antagonist_type))
+ budget_overflow += possible_ruleset.antag_cost
+
+/datum/game_mode/dynamic/get_webhook_name()
+ var/list/implied_and_used = list()
+ for(var/datum/ruleset/implied/implied as anything in implied_rulesets)
+ if(implied.was_triggered)
+ implied_and_used += implied
+ return "[name] ([english_list(rulesets + implied_and_used, nothing_text = "Extended")])"
+
+// /proc/log_dynamic(text)
+// for(var/client/C in GLOB.admins)
+// if(check_rights(R_DEBUG, FALSE, C.mob) && (C.prefs.toggles & PREFTOGGLE_CHAT_DEBUGLOGS))
+// to_chat(C, "DYNAMIC: [text]", MESSAGE_TYPE_DEBUG, confidential = TRUE)
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index ba25db4acb0e..50960ce80b97 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -249,7 +249,7 @@
for(var/tech_id in SSeconomy.tech_levels)
SSblackbox.record_feedback("tally", "cargo max tech level sold", SSeconomy.tech_levels[tech_id], tech_id)
- GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_PRIMARY, "A round of [name] has ended - [surviving_total] survivors, [ghosts] ghosts. <@&[GLOB.configuration.discord.new_round_waiting_role]>") // SS220 Addition
+ GLOB.discord_manager.send2discord_simple(DISCORD_WEBHOOK_PRIMARY, "A round of [get_webhook_name()] has ended - [surviving_total] survivors, [ghosts] ghosts. <@&[GLOB.configuration.discord.new_round_waiting_role]>") // SS220 Addition
if(SSredis.connected)
// Send our presence to required channels
var/list/presence_data = list()
@@ -653,7 +653,7 @@
if(length(traitors) < traitors_to_add())
traitors_to_add += (traitors_to_add() - length(traitors))
- if(!traitors_to_add)
+ if(traitors_to_add <= 0)
return
var/list/potential_recruits = get_alive_players_for_role(ROLE_TRAITOR)
@@ -671,3 +671,9 @@
traitor.special_role = SPECIAL_ROLE_TRAITOR
traitor.restricted_roles = restricted_jobs
traitor.add_antag_datum(/datum/antagonist/traitor) // They immediately get a new objective
+
+/datum/game_mode/proc/get_webhook_name()
+ return name
+
+/datum/game_mode/proc/on_mob_cryo(mob/sleepy_mob, obj/machinery/cryopod/cryopod)
+ return
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 90ba39865b7a..5aff5df2a955 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -126,7 +126,7 @@
/datum/module_picker/Topic(href, href_list)
..()
- if(!isAI(usr))
+ if(!is_ai(usr))
return
var/mob/living/silicon/ai/A = usr
@@ -573,8 +573,8 @@
var/turf/T = turfs[n]
if(!isfloorturf(T))
success = FALSE
- var/datum/camerachunk/C = GLOB.cameranet.getCameraChunk(T.x, T.y, T.z)
- if(!C.visibleTurfs[T])
+ var/datum/camerachunk/C = GLOB.cameranet.get_camera_chunk(T.x, T.y, T.z)
+ if(!C.visible_turfs[T])
alert_msg = "You don't have camera vision of this location!"
success = FALSE
for(var/atom/movable/AM in T.contents)
@@ -673,12 +673,12 @@
I.loc = deploylocation
client.images += I
I.icon_state = "redOverlay"
- var/datum/camerachunk/C = GLOB.cameranet.getCameraChunk(deploylocation.x, deploylocation.y, deploylocation.z)
+ var/datum/camerachunk/C = GLOB.cameranet.get_camera_chunk(deploylocation.x, deploylocation.y, deploylocation.z)
if(!istype(deploylocation))
to_chat(src, "There isn't enough room! Make sure you are placing the machine in a clear area and on a floor.")
return FALSE
- if(!C.visibleTurfs[deploylocation])
+ if(!C.visible_turfs[deploylocation])
to_chat(src, "You don't have camera vision of this location!")
addtimer(CALLBACK(src, PROC_REF(remove_transformer_image), client, I, deploylocation), 3 SECONDS)
return FALSE
diff --git a/code/game/gamemodes/miniantags/abduction/machinery/abductor_camera.dm b/code/game/gamemodes/miniantags/abduction/machinery/abductor_camera.dm
index df5767cf2a95..b8d28d1161a6 100644
--- a/code/game/gamemodes/miniantags/abduction/machinery/abductor_camera.dm
+++ b/code/game/gamemodes/miniantags/abduction/machinery/abductor_camera.dm
@@ -23,7 +23,8 @@
return ..()
/obj/machinery/computer/camera_advanced/abductor/CreateEye()
- ..()
+ eyeobj = new /mob/camera/eye/abductor(loc, name, src, current_user)
+ give_eye_control(current_user)
eyeobj.visible_icon = 1
eyeobj.icon = 'icons/obj/abductor.dmi'
eyeobj.icon_state = "camera_target"
@@ -74,10 +75,10 @@
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/abductor/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
P.PadToLoc(remote_eye.loc)
/datum/action/innate/teleport_out
@@ -101,10 +102,10 @@
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/abductor/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
P.MobToLoc(remote_eye.loc,C)
/datum/action/innate/vest_mode_swap
@@ -137,7 +138,7 @@
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/abductor/remote_eye = C.remote_control
var/obj/machinery/abductor/console/console = target
console.SetDroppoint(remote_eye.loc,owner)
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 323943bd78dd..99be8d840c2e 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -501,7 +501,7 @@
for(var/mob in GLOB.mob_living_list)
var/mob/living/C = mob
- if(ishuman(C) || isAI(C) || isrobot(C))
+ if(ishuman(C) || is_ai(C) || isrobot(C))
if(C.stat == DEAD)
continue
if(!C.client)
diff --git a/code/game/gamemodes/scoreboard.dm b/code/game/gamemodes/scoreboard.dm
index ad5d6da9479c..a38ba8eeffca 100644
--- a/code/game/gamemodes/scoreboard.dm
+++ b/code/game/gamemodes/scoreboard.dm
@@ -135,7 +135,7 @@ GLOBAL_VAR(scoreboard) // Variable to save the scoreboard string once it's been
/datum/scoreboard/proc/check_station_player(mob/M)
if(!is_station_level(M.z) || M.stat != DEAD)
return
- if(isAI(M))
+ if(is_ai(M))
dead_ai = TRUE
score_dead_crew++
else if(ishuman(M))
diff --git a/code/game/machinery/ai_display.dm b/code/game/machinery/ai_display.dm
index fda00c73582b..155d67d72383 100644
--- a/code/game/machinery/ai_display.dm
+++ b/code/game/machinery/ai_display.dm
@@ -23,7 +23,7 @@ GLOBAL_LIST_EMPTY(ai_displays)
return ..()
/obj/machinery/ai_status_display/attack_ai(mob/living/silicon/ai/user)
- if(isAI(user))
+ if(is_ai(user))
user.ai_statuschange()
/obj/machinery/ai_status_display/emp_act(severity)
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index ca131022a999..bc793fadcc35 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -55,7 +55,7 @@
GLOB.cameranet.cameras += src
part_of_camera_network = should_add_to_cameranet
if(part_of_camera_network)
- GLOB.cameranet.addCamera(src)
+ GLOB.cameranet.add_camera(src)
if(isturf(loc))
var/area/our_area = get_area(src)
LAZYADD(our_area.cameras, UID())
@@ -81,7 +81,7 @@
kick_out_watchers()
QDEL_NULL(assembly)
QDEL_NULL(wires)
- GLOB.cameranet.removeCamera(src)
+ GLOB.cameranet.remove_camera(src)
GLOB.cameranet.cameras -= src
var/area/our_area = get_area(src)
if(our_area) // We should probably send out the warning alarms if this doesn't exist, because this should always have an area!
@@ -134,7 +134,7 @@
/obj/machinery/camera/proc/setViewRange(num = CAMERA_VIEW_DISTANCE)
view_range = num
- GLOB.cameranet.updateVisibility(src, 0)
+ GLOB.cameranet.update_visibility(src, 0)
/obj/machinery/camera/singularity_pull(S, current_size)
if(status && current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects and the camera is still active, turn off the camera as it gets ripped off the wall.
@@ -192,7 +192,7 @@
to_chat(U, "You hold \the [itemname] up to the camera ...")
U.changeNext_move(CLICK_CD_MELEE)
for(var/mob/O in GLOB.player_list)
- if(isAI(O))
+ if(is_ai(O))
var/mob/living/silicon/ai/AI = O
if(AI.control_disabled || (AI.stat == DEAD))
return
@@ -415,7 +415,7 @@
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/stretch/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
- if(isXRay() && isAI(user))
+ if(isXRay() && is_ai(user))
user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
user.see_in_dark = max(user.see_in_dark, 8)
user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
@@ -446,5 +446,5 @@
return
SEND_SIGNAL(src, COMSIG_CAMERA_MOVED, prev_turf)
- GLOB.cameranet.updatePortableCamera(src, prev_turf)
+ GLOB.cameranet.update_portable_camera(src, prev_turf)
prev_turf = get_turf(src)
diff --git a/code/game/machinery/camera/camera_presets.dm b/code/game/machinery/camera/camera_presets.dm
index 36a39afc6c2e..34f90ce2f40b 100644
--- a/code/game/machinery/camera/camera_presets.dm
+++ b/code/game/machinery/camera/camera_presets.dm
@@ -146,7 +146,7 @@
assembly.upgrades.Add(new /obj/item/analyzer(assembly))
setPowerUsage()
//Update what it can see.
- GLOB.cameranet.updateVisibility(src, 0)
+ GLOB.cameranet.update_visibility(src, 0)
// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list.
/obj/machinery/camera/proc/upgradeMotion()
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 5f2e83b11a26..e3b570143305 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -22,7 +22,7 @@
return localMotionTargets
/obj/machinery/camera/proc/newTarget(mob/target)
- if(isAI(target))
+ if(is_ai(target))
return FALSE
if(isbot(target)) //No armsky, you don't get to set off the motion alarm constantly
return FALSE
diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm
index 92f39c2a0d3a..9c1a491b6fc8 100644
--- a/code/game/machinery/camera/tracking.dm
+++ b/code/game/machinery/camera/tracking.dm
@@ -41,7 +41,7 @@
return 0
var/obj/machinery/camera/C = track.cameras[camera]
- src.eyeobj.setLoc(C)
+ src.eyeobj.set_loc(C)
return
@@ -104,18 +104,18 @@
ai_actual_track(target)
/mob/living/silicon/ai/proc/ai_cancel_tracking(forced = 0)
- if(!cameraFollow)
+ if(!camera_follow)
return
to_chat(src, "Follow camera mode [forced ? "terminated" : "ended"].")
- cameraFollow = null
+ camera_follow = null
/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target, doubleclick = FALSE)
if(!istype(target))
return
var/mob/living/silicon/ai/U = usr
- U.cameraFollow = target
+ U.camera_follow = target
U.tracking = TRUE
to_chat(U, "Attempting to track [target.get_visible_name()]...")
@@ -126,20 +126,20 @@
if(target.is_jammed())
to_chat(U, "Unable to track [target.get_visible_name()]...")
- U.cameraFollow = null
+ U.camera_follow = null
return
if(!target || !target.can_track(usr))
to_chat(U, "Target is not near any active cameras.")
- U.cameraFollow = null
+ U.camera_follow = null
return
to_chat(U, "Now tracking [target.get_visible_name()] on camera.")
var/cameraticks = 0
spawn(0)
- while(U.cameraFollow == target)
- if(U.cameraFollow == null)
+ while(U.camera_follow == target)
+ if(U.camera_follow == null)
return
if(!target.can_track(usr))
@@ -148,7 +148,7 @@
to_chat(U, "Target is not near any active cameras. Attempting to reacquire...")
cameraticks++
if(cameraticks > 9)
- U.cameraFollow = null
+ U.camera_follow = null
to_chat(U, "Unable to reacquire, cancelling track...")
U.tracking = FALSE
return
@@ -161,11 +161,11 @@
U.tracking = FALSE
if(U.eyeobj)
- U.eyeobj.setLoc(get_turf(target))
+ U.eyeobj.set_loc(get_turf(target))
else
view_core()
- U.cameraFollow = null
+ U.camera_follow = null
return
sleep(10)
@@ -175,9 +175,9 @@
return 0
if(isrobot(M))
var/mob/living/silicon/robot/R = M
- if(!(R.camera && R.camera.can_use()) && !GLOB.cameranet.checkCameraVis(M))
+ if(!(R.camera && R.camera.can_use()) && !GLOB.cameranet.check_camera_vis(M))
return 0
- else if(!GLOB.cameranet.checkCameraVis(M))
+ else if(!GLOB.cameranet.check_camera_vis(M))
return 0
return 1
@@ -186,7 +186,7 @@
return
if(!src.can_use())
return
- user.eyeobj.setLoc(get_turf(src))
+ user.eyeobj.set_loc(get_turf(src))
/mob/living/silicon/ai/attack_ai(mob/user)
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 9b3fee6c25ca..3d2fc2646d09 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -19,26 +19,29 @@
parent_area_type = machine_area.get_top_parent_type()
/obj/machinery/computer/atmos_alert/process()
- // This is relatively cheap because the areas list is pretty small
- for(var/obj/machinery/alarm/air_alarm as anything in GLOB.air_alarms)
- if(!((get_area(air_alarm)).type in typesof(parent_area_type)) || air_alarm.z != z)
- continue // Not an area we monitor, or outside our z-level
- if(!air_alarm.report_danger_level)
+ alarm_cache = list()
+ alarm_cache["priority"] = list()
+ alarm_cache["minor"] = list()
+ alarm_cache["mode"] = list()
+ for(var/area/A in GLOB.all_areas)
+ if(!istype(A, parent_area_type))
continue
- switch(air_alarm.alarm_area.atmosalm)
- if(ATMOS_ALARM_DANGER)
- alarm_cache["priority"] |= air_alarm.alarm_area.name
- alarm_cache["minor"] -= air_alarm.alarm_area.name
- if(ATMOS_ALARM_WARNING)
- alarm_cache["priority"] -= air_alarm.alarm_area.name
- alarm_cache["minor"] |= air_alarm.alarm_area.name
- else
- alarm_cache["priority"] -= air_alarm.alarm_area.name
- alarm_cache["minor"] -= air_alarm.alarm_area.name
- if(air_alarm.mode == AALARM_MODE_FILTERING)
- alarm_cache["mode"] -= air_alarm.alarm_area.name
- else
- alarm_cache["mode"][air_alarm.alarm_area.name] = GLOB.aalarm_modes["[air_alarm.mode]"]
+ var/alarm_level = null
+ for(var/obj/machinery/alarm/air_alarm in A.air_alarms)
+ if(!istype(air_alarm))
+ continue
+ if(!air_alarm.report_danger_level)
+ continue
+ switch(air_alarm.alarm_area.atmosalm)
+ if(ATMOS_ALARM_DANGER)
+ alarm_level = "priority"
+ if(ATMOS_ALARM_WARNING)
+ if(isnull(alarm_level))
+ alarm_level = "minor"
+ if(!isnull(alarm_level))
+ alarm_cache[alarm_level] += A.name
+ if(air_alarm.mode != AALARM_MODE_FILTERING)
+ alarm_cache["mode"][A.name] = GLOB.aalarm_modes["[air_alarm.mode]"]
update_icon()
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 8c09419e916e..a920236b48d5 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -3,7 +3,7 @@
desc = "Used to access the various cameras on the station."
icon_screen = "cameras"
icon_keyboard = "security_key"
- var/mob/camera/ai_eye/remote/eyeobj
+ var/mob/camera/eye/eyeobj
var/mob/living/carbon/human/current_user = null
var/list/networks = list("SS13")
var/datum/action/innate/camera_off/off_action = new
@@ -11,8 +11,8 @@
var/list/actions = list()
/obj/machinery/computer/camera_advanced/proc/CreateEye()
- eyeobj = new()
- eyeobj.origin = src
+ eyeobj = new /mob/camera/eye/syndicate(loc, name, src, current_user)
+ give_eye_control(current_user)
/obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/user)
if(off_action)
@@ -25,19 +25,17 @@
jump_action.Grant(user)
actions += jump_action
-/obj/machinery/computer/camera_advanced/proc/remove_eye_control(mob/living/user)
- if(!user)
+/obj/machinery/computer/camera_advanced/proc/RemoveActions()
+ if(!istype(current_user))
return
for(var/V in actions)
var/datum/action/A = V
- A.Remove(user)
+ A.Remove(current_user)
actions.Cut()
- if(user.client)
- user.reset_perspective(null)
- eyeobj.RemoveImages()
- eyeobj.eye_user = null
- user.remote_control = null
+/obj/machinery/computer/camera_advanced/proc/remove_eye_control(mob/living/user)
+ RemoveActions()
+ eyeobj.release_control()
current_user = null
remove_eye(user)
playsound(src, 'sound/machines/terminal_off.ogg', 25, 0)
@@ -57,7 +55,7 @@
return ..()
/obj/machinery/computer/camera_advanced/proc/remove_eye(mob/M)
- if(M == current_user)
+ if(istype(M) && M == current_user)
remove_eye_control(M)
/obj/machinery/computer/camera_advanced/attack_hand(mob/user)
@@ -68,102 +66,17 @@
return
if(..())
return
+ current_user = user
if(!eyeobj)
CreateEye()
-
- if(!eyeobj.eye_initialized)
- var/camera_location
- for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if(!C.can_use())
- continue
- if(length(C.network & networks))
- camera_location = get_turf(C)
- break
- if(camera_location)
- eyeobj.eye_initialized = 1
- give_eye_control(user)
- eyeobj.setLoc(camera_location)
- else
- // An abberant case - silent failure is obnoxious
- to_chat(user, "ERROR: No linked and active camera network found.")
- remove_eye(user)
else
give_eye_control(user)
- eyeobj.setLoc(eyeobj.loc)
-
+ eyeobj.set_loc(eyeobj.loc)
/obj/machinery/computer/camera_advanced/proc/give_eye_control(mob/user)
+ eyeobj.give_control(user)
GrantActions(user)
- current_user = user
- eyeobj.eye_user = user
- eyeobj.name = "Camera Eye ([user.name])"
- user.remote_control = eyeobj
- user.reset_perspective(eyeobj)
-
-/mob/camera/ai_eye/remote
- name = "Inactive Camera Eye"
- // Abductors dont trigger the Ai Detector
- ai_detector_visible = FALSE
- var/sprint = 10
- var/cooldown = 0
- var/acceleration = 1
- var/mob/living/carbon/human/eye_user = null
- var/obj/machinery/computer/camera_advanced/origin
- var/eye_initialized = 0
- var/visible_icon = 0
- var/image/user_image = null
-
-/mob/camera/ai_eye/remote/Destroy()
- eye_user = null
- origin = null
- return ..()
-
-/mob/camera/ai_eye/remote/RemoveImages()
- ..()
- if(visible_icon)
- var/client/C = GetViewerClient()
- if(C)
- C.images -= user_image
-
-/mob/camera/ai_eye/remote/GetViewerClient()
- if(eye_user)
- return eye_user.client
- return null
-
-/mob/camera/ai_eye/remote/setLoc(T)
- if(eye_user)
- if(!isturf(eye_user.loc))
- return
- T = get_turf(T)
- var/old_loc = loc
- loc = T
- Moved(old_loc, get_dir(old_loc, loc))
- if(use_static)
- GLOB.cameranet.visibility(src, GetViewerClient())
- if(visible_icon)
- if(eye_user.client)
- eye_user.client.images -= user_image
- user_image = image(icon,loc,icon_state,FLY_LAYER)
- eye_user.client.images += user_image
-
-/mob/camera/ai_eye/remote/relaymove(mob/user,direct)
- var/initial = initial(sprint)
- var/max_sprint = 50
-
- if(cooldown && cooldown < world.timeofday) // 3 seconds
- sprint = initial
-
- for(var/i = 0; i < max(sprint, initial); i += 20)
- var/turf/step = get_turf(get_step(src, direct))
- if(step)
- src.setLoc(step)
-
- cooldown = world.timeofday + 5
- if(acceleration)
- sprint = min(sprint + 0.5, max_sprint)
- else
- sprint = initial
/datum/action/innate/camera_off
name = "End Camera View"
@@ -173,7 +86,7 @@
if(!target || !iscarbon(target))
return
var/mob/living/carbon/C = target
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/console = remote_eye.origin
console.remove_eye_control(target)
@@ -185,7 +98,7 @@
if(!target || !iscarbon(target))
return
var/mob/living/carbon/C = target
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin
var/list/L = list()
@@ -209,7 +122,7 @@
playsound(origin, "terminal_type", 25, 0)
if(final)
playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, FALSE)
- remote_eye.setLoc(get_turf(final))
+ remote_eye.set_loc(get_turf(final))
C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/stretch/flash/noise)
C.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
else
diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm
index dfb4e1f08ea4..1bcf98280039 100644
--- a/code/game/machinery/computer/camera_console.dm
+++ b/code/game/machinery/computer/camera_console.dm
@@ -173,7 +173,7 @@
ui_interact(user)
/obj/machinery/computer/security/attack_ai(mob/user)
- if(isAI(user))
+ if(is_ai(user))
to_chat(user, "You realise it's kind of stupid to access a camera console when you have the entire camera network at your metaphorical fingertips.")
return
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 2870683a5c3f..83ce0497dcc7 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -134,7 +134,7 @@
setMenuState(ui.user, COMM_SCREEN_MAIN)
if("newalertlevel")
- if(isAI(ui.user) || isrobot(ui.user))
+ if(is_ai(ui.user) || isrobot(ui.user))
to_chat(ui.user, "Firewalls prevent you from changing the alert level.")
return
else if(ADMIN_CHECK(ui.user))
@@ -184,7 +184,7 @@
setMenuState(ui.user, COMM_SCREEN_MAIN)
if("cancelshuttle")
- if(isAI(ui.user) || isrobot(ui.user))
+ if(is_ai(ui.user) || isrobot(ui.user))
to_chat(ui.user, "Firewalls prevent you from recalling the shuttle.")
return
var/response = tgui_alert(usr, "Are you sure you wish to recall the shuttle?", "Confirm", list("Yes", "No"))
@@ -418,7 +418,7 @@
/obj/machinery/computer/communications/ui_data(mob/user)
var/list/data = list()
- data["is_ai"] = isAI(user) || isrobot(user)
+ data["is_ai"] = is_ai(user) || isrobot(user)
data["noauthbutton"] = !ishuman(user)
data["menu_state"] = data["is_ai"] ? ai_menu_state : menu_state
data["emagged"] = emagged
@@ -515,19 +515,19 @@
return data
/obj/machinery/computer/communications/proc/setCurrentMessage(mob/user, value)
- if(isAI(user) || isrobot(user))
+ if(is_ai(user) || isrobot(user))
aicurrmsg = value
else
currmsg = value
/obj/machinery/computer/communications/proc/getCurrentMessage(mob/user)
- if(isAI(user) || isrobot(user))
+ if(is_ai(user) || isrobot(user))
return aicurrmsg
else
return currmsg
/obj/machinery/computer/communications/proc/setMenuState(mob/user, value)
- if(isAI(user) || isrobot(user))
+ if(is_ai(user) || isrobot(user))
ai_menu_state=value
else
menu_state=value
diff --git a/code/game/machinery/computer/message_monitor.dm b/code/game/machinery/computer/message_monitor.dm
index fd66ec23513b..d89bd4eba629 100644
--- a/code/game/machinery/computer/message_monitor.dm
+++ b/code/game/machinery/computer/message_monitor.dm
@@ -129,7 +129,7 @@
else
for(var/n = ++i; n <= optioncount; n++)
dat += " [n]. ---------------
"
- if((isAI(user) || isrobot(user)) && (user.mind.special_role && user.mind.is_original_mob(user)))
+ if((is_ai(user) || isrobot(user)) && (user.mind.special_role && user.mind.is_original_mob(user)))
//Malf/Traitor AIs can bruteforce into the system to gain the Key.
dat += "*&@#. Bruteforce Key
"
else
@@ -159,7 +159,7 @@
dat += ""
//Hacking screen.
if(2)
- if(isAI(user) || isrobot(user))
+ if(is_ai(user) || isrobot(user))
dat += "Brute-forcing for server key.
It will take 20 seconds for every character that the password has."
dat += "In the meantime, this console can reveal your true intentions if you let someone access it. Make sure no humans enter the room during that time."
else
@@ -349,7 +349,7 @@
//Hack the Console to get the password
if(href_list["hack"])
- if((isAI(usr) || isrobot(usr)) && (usr.mind.special_role && usr.mind.is_original_mob(usr)))
+ if((is_ai(usr) || isrobot(usr)) && (usr.mind.special_role && usr.mind.is_original_mob(usr)))
src.hacking = 1
src.screen = 2
src.icon_screen = hack_icon
diff --git a/code/game/machinery/computer/robot_control.dm b/code/game/machinery/computer/robot_control.dm
index f15a9014be02..d9f96e0a9915 100644
--- a/code/game/machinery/computer/robot_control.dm
+++ b/code/game/machinery/computer/robot_control.dm
@@ -66,7 +66,7 @@
return FALSE
if(!console_shows(R))
return FALSE
- if(isAI(user))
+ if(is_ai(user))
if(R.connected_ai != user)
if(telluserwhy)
to_chat(user, "AIs can only control cyborgs which are linked to them.")
@@ -107,7 +107,7 @@
return FALSE
if(user.can_admin_interact())
return TRUE
- if(!isAI(user))
+ if(!is_ai(user))
return FALSE
return (user.mind.special_role && user.mind.is_original_mob(user))
diff --git a/code/game/machinery/computer/sm_monitor.dm b/code/game/machinery/computer/sm_monitor.dm
index 051ab8e47d91..535b66018271 100644
--- a/code/game/machinery/computer/sm_monitor.dm
+++ b/code/game/machinery/computer/sm_monitor.dm
@@ -105,7 +105,7 @@
return
for(var/obj/machinery/atmospherics/supermatter_crystal/S in SSair.atmos_machinery)
// Delaminating, not within coverage, not on a tile.
- if(!(is_station_level(S.z) || is_mining_level(S.z) || atoms_share_level(S, T) || !issimulatedturf(S.loc)))
+ if(!atoms_share_level(S, T) || !issimulatedturf(S.loc))
continue
supermatters.Add(S)
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 327d6885e6a7..fdcfc2518104 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -339,7 +339,7 @@
//Delete all items not on the preservation list.
var/list/items = contents
- items -= occupant // Don't delete the occupant
+ items -= occupant // Don't delete the occupant // this fucking nullspaces the occupant btw, i fuckin hate old code
items -= announce // or the autosay radio.
ADD_TRAIT(occupant, TRAIT_CRYO_DESPAWNING, TRAIT_GENERIC)
@@ -367,6 +367,8 @@
if(IS_SACRIFICE_TARGET(occupant.mind))
SSticker.mode.cult_team.find_new_sacrifice_target()
+ SSticker.mode.on_mob_cryo(occupant, src)
+
//Update any existing objectives involving this mob.
if(occupant.mind)
if(occupant.mind.initial_account)
@@ -385,10 +387,6 @@
if(occupant.mind.objective_holder.clear())
occupant.mind.special_role = null
- else
- if(SSticker.mode.name == "AutoTraitor")
- var/datum/game_mode/traitor/autotraitor/current_mode = SSticker.mode
- current_mode.possible_traitors.Remove(occupant)
// Delete them from datacore.
diff --git a/code/game/machinery/dance_machine.dm b/code/game/machinery/dance_machine.dm
index f5a4cf907523..c2389895e0cb 100644
--- a/code/game/machinery/dance_machine.dm
+++ b/code/game/machinery/dance_machine.dm
@@ -95,7 +95,7 @@
if(!anchored)
to_chat(user,"This device must be anchored by a wrench!")
return
- if(!Adjacent(user) && !isAI(user))
+ if(!Adjacent(user) && !is_ai(user))
return
user.set_machine(src)
var/list/dat = list()
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 493306bd6c6e..e2d0c901c88c 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1200,9 +1200,6 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
/obj/machinery/door/airlock/try_to_crowbar(mob/living/user, obj/item/I)
if(operating)
return
- if(HAS_TRAIT(I, TRAIT_FORCES_OPEN_DOORS_ITEM))
- force_open_with_item(user, I)
- return
var/beingcrowbarred = FALSE
if(I.tool_behaviour == TOOL_CROWBAR && I.tool_use_check(user, 0))
beingcrowbarred = TRUE
@@ -1247,31 +1244,6 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
if(density && !open(1))
to_chat(user, "Despite your attempts, [src] refuses to open.")
-/obj/machinery/door/airlock/proc/force_open_with_item(mob/living/user, obj/item/I)
- /// Time it takes to open an airlock with an item with the TRAIT_FORCES_OPEN_DOORS_ITEM trait, 5 seconds for wielded items, 10 seconds for nonwielded items
- var/time_to_open_airlock = 10 SECONDS
- /// Can we open the airlock while unpowered? Wielded item's can't, but unwielded items can
- var/can_force_open_while_unpowered = TRUE
- if(I.GetComponent(/datum/component/two_handed))
- can_force_open_while_unpowered = FALSE
- time_to_open_airlock = 5 SECONDS
- if(!HAS_TRAIT(I, TRAIT_WIELDED))
- to_chat(user, "You need to be wielding [I] to do that!")
- return
- if(!density || prying_so_hard)
- return
- playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, 1) //is it aliens or just the CE being a dick?
- if(!arePowerSystemsOn() && can_force_open_while_unpowered)
- open(TRUE)
- return
- prying_so_hard = TRUE //so you dont pry the door when you are already trying to pry it
- var/result = do_after(user, time_to_open_airlock, target = src)
- prying_so_hard = FALSE
- if(result)
- open(TRUE)
- if(density && !open(TRUE))
- to_chat(user, "Despite your attempts, [src] refuses to open.")
-
/obj/machinery/door/airlock/open(forced=0)
if(operating || welded || locked || emagged)
return 0
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index fd9fd32a64cc..c89fff93af6a 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -260,9 +260,6 @@
if(HAS_TRAIT(src, TRAIT_CMAGGED) && I.can_clean()) //If the cmagged door is being hit with cleaning supplies, don't open it, it's being cleaned!
return
- if(user.a_intent != INTENT_HARM && HAS_TRAIT(I, TRAIT_FORCES_OPEN_DOORS_ITEM))
- try_to_crowbar(user, I)
- return TRUE
else if(!(I.flags & NOBLUDGEON) && user.a_intent != INTENT_HARM)
try_to_activate_door(user)
return TRUE
@@ -469,7 +466,7 @@
/obj/machinery/door/proc/update_freelook_sight()
if(!glass && GLOB.cameranet)
- GLOB.cameranet.updateVisibility(src, 0)
+ GLOB.cameranet.update_visibility(src, 0)
/obj/machinery/door/proc/check_unres() //unrestricted sides. This overlay indicates which directions the player can access even without an ID
if(hasPower() && unres_sides)
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index e2e4465b9f3e..118340d2df83 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -92,6 +92,7 @@ GLOBAL_LIST_EMPTY(holopads)
var/ringing = FALSE
/// Whether or not the user is currently selecting where to send their call.
var/dialling_input = FALSE
+ var/mob/camera/eye/hologram/eye
/obj/machinery/hologram/holopad/Initialize(mapload)
. = ..()
@@ -176,6 +177,9 @@ GLOBAL_LIST_EMPTY(holopads)
if(outgoing_call)
return
+ if(user.a_intent != INTENT_HELP)
+ return
+
user.set_machine(src)
interact(user)
@@ -187,7 +191,7 @@ GLOBAL_LIST_EMPTY(holopads)
/obj/machinery/hologram/holopad/AltClick(mob/living/carbon/human/user)
if(..())
return
- if(isAI(user))
+ if(is_ai(user))
hangup_all_calls()
return
@@ -237,7 +241,7 @@ GLOBAL_LIST_EMPTY(holopads)
popup.open()
/obj/machinery/hologram/holopad/Topic(href, href_list)
- if(..() || isAI(usr))
+ if(..() || is_ai(usr))
return
add_fingerprint(usr)
if(stat & NOPOWER)
@@ -316,7 +320,7 @@ GLOBAL_LIST_EMPTY(holopads)
I don't need to check for client since they're clicking on an object.
This may change in the future but for now will suffice.*/
else if(ai.eyeobj.loc != loc)//Set client eye on the object if it's not already.
- ai.eyeobj.setLoc(get_turf(src))
+ ai.eyeobj.set_loc(get_turf(src))
else if(!LAZYLEN(masters) || !masters[ai])//If there is no hologram, possibly make one.
activate_holo(ai, 1)
else//If there is a hologram, remove it.
@@ -355,7 +359,7 @@ GLOBAL_LIST_EMPTY(holopads)
//Try to transfer hologram to another pad that can project on T
/obj/machinery/hologram/holopad/proc/transfer_to_nearby_pad(turf/T, mob/holo_owner)
- if(!isAI(holo_owner))
+ if(!is_ai(holo_owner))
return
for(var/pad in GLOB.holopads)
var/obj/machinery/hologram/holopad/another = pad
@@ -372,7 +376,7 @@ GLOBAL_LIST_EMPTY(holopads)
if(QDELETED(user) || user.incapacitated() || !user.client)
return FALSE
- if(isAI(user))
+ if(is_ai(user))
var/mob/living/silicon/ai/AI = user
if(!AI.current)
return FALSE
@@ -380,8 +384,8 @@ GLOBAL_LIST_EMPTY(holopads)
//Can we display holos there
//Area check instead of line of sight check because this is a called a lot if AI wants to move around.
-/obj/machinery/hologram/holopad/proc/validate_location(turf/T,check_los = FALSE)
- if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src))
+/obj/machinery/hologram/holopad/proc/validate_location(turf/T)
+ if(T.z == z && (get_dist(T, src) <= holo_range) && T.loc == get_area(src))
return TRUE
return FALSE
@@ -415,7 +419,7 @@ GLOBAL_LIST_EMPTY(holopads)
return
var/obj/effect/overlay/holo_pad_hologram/hologram = new(loc)//Spawn a blank effect at the location.
- if(isAI(user))
+ if(is_ai(user))
hologram.icon = AI.holo_icon
else //make it like real life
if(isrobot(user))
@@ -435,6 +439,7 @@ GLOBAL_LIST_EMPTY(holopads)
hologram.set_light(2) //hologram lighting
move_hologram()
+ eye = new /mob/camera/eye/hologram(src, user.name, src, user)
set_holo(user, hologram)
if(!masters[user])//If there is not already a hologram.
@@ -487,6 +492,8 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
/obj/machinery/hologram/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h)
+ eye = user.remote_control
+ eye.holopad = src
masters[user] = h
holorays[user] = new /obj/effect/overlay/holoray(loc)
var/mob/living/silicon/ai/AI = user
@@ -498,10 +505,13 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
/obj/machinery/hologram/holopad/proc/clear_holo(mob/living/user)
qdel(masters[user]) // Get rid of user's hologram
+ if(!QDELETED(eye))
+ QDEL_NULL(eye)
unset_holo(user)
return TRUE
/obj/machinery/hologram/holopad/proc/unset_holo(mob/living/user)
+ eye = null
var/mob/living/silicon/ai/AI = user
if(istype(AI) && AI.current == src)
AI.current = null
diff --git a/code/game/machinery/magnetic_module.dm b/code/game/machinery/magnetic_module.dm
index 5f50939ffd11..7e0e012b6481 100644
--- a/code/game/machinery/magnetic_module.dm
+++ b/code/game/machinery/magnetic_module.dm
@@ -165,7 +165,7 @@
step_towards(M, center)
for(var/mob/living/silicon/S in orange(magnetic_field, center))
- if(isAI(S)) continue
+ if(is_ai(S)) continue
step_towards(S, center)
use_power(electricity_level * 5)
diff --git a/code/game/machinery/poolcontroller.dm b/code/game/machinery/poolcontroller.dm
index 46df33dfc237..71cc230dba36 100644
--- a/code/game/machinery/poolcontroller.dm
+++ b/code/game/machinery/poolcontroller.dm
@@ -102,7 +102,7 @@
QDEL_IN(decal, 25)
/obj/machinery/poolcontroller/proc/handleTemp(mob/M)
- if(!M || isAIEye(M) || issilicon(M) || isobserver(M) || M.stat == DEAD)
+ if(!M || is_ai_eye(M) || issilicon(M) || isobserver(M) || M.stat == DEAD)
return
M.water_act(100, temperature, src)//leave temp at 0, we handle it in the switch. oh wait
switch(temperature) //Apply different effects based on what the temperature is set to.
diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm
index f97faf4f0ad6..b01568472112 100644
--- a/code/game/machinery/portable_turret.dm
+++ b/code/game/machinery/portable_turret.dm
@@ -189,7 +189,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
/obj/machinery/porta_turret/proc/isLocked(mob/user)
if(HasController())
return TRUE
- if(isrobot(user) || isAI(user))
+ if(isrobot(user) || is_ai(user))
if(ailock)
to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
return TRUE
diff --git a/code/game/machinery/quantum_pad.dm b/code/game/machinery/quantum_pad.dm
index f5f31efc6fb8..7d85686548ac 100644
--- a/code/game/machinery/quantum_pad.dm
+++ b/code/game/machinery/quantum_pad.dm
@@ -147,7 +147,7 @@
return TRUE
/obj/machinery/quantumpad/attack_hand(mob/user)
- if(isAI(user))
+ if(is_ai(user))
return
if(!check_usable(user))
return
@@ -161,13 +161,13 @@
if(!istype(AI))
return
if(AI.eyeobj.loc != loc)
- AI.eyeobj.setLoc(get_turf(loc))
+ AI.eyeobj.set_loc(get_turf(loc))
return
if(!check_usable(user))
return
var/turf/T = get_turf(linked_pad)
- if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
- AI.eyeobj.setLoc(T)
+ if(GLOB.cameranet && GLOB.cameranet.check_turf_vis(T))
+ AI.eyeobj.set_loc(T)
else
to_chat(user, "Linked pad is not on or near any active cameras on the station.")
diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm
index da4fafec5c35..d8b133a3eb2c 100644
--- a/code/game/machinery/teleporter.dm
+++ b/code/game/machinery/teleporter.dm
@@ -334,8 +334,8 @@
Prevents AI from using the teleporter, prints out failure messages for clarity
*/
/obj/machinery/teleport/proc/blockAI(atom/A)
- if(isAI(A) || istype(A, /obj/structure/ai_core))
- if(isAI(A))
+ if(is_ai(A) || istype(A, /obj/structure/ai_core))
+ if(is_ai(A))
var/mob/living/silicon/ai/T = A
if(T.allow_teleporter)
return FALSE
diff --git a/code/game/machinery/turret_control.dm b/code/game/machinery/turret_control.dm
index ea9bf1271819..bdc87cd7d9c5 100644
--- a/code/game/machinery/turret_control.dm
+++ b/code/game/machinery/turret_control.dm
@@ -88,7 +88,7 @@
update_icon(UPDATE_ICON_STATE)
/obj/machinery/turretid/proc/isLocked(mob/user)
- if(isrobot(user) || isAI(user))
+ if(isrobot(user) || is_ai(user))
if(ailock)
to_chat(user, "There seems to be a firewall preventing you from accessing this device.")
return TRUE
diff --git a/code/game/machinery/vendors/departmental_vendors.dm b/code/game/machinery/vendors/departmental_vendors.dm
index 039d460e9287..cf2885665bfe 100644
--- a/code/game/machinery/vendors/departmental_vendors.dm
+++ b/code/game/machinery/vendors/departmental_vendors.dm
@@ -108,6 +108,7 @@
/obj/item/reagent_containers/condiment/saltshaker =5,
/obj/item/reagent_containers/condiment/peppermill =5,
/obj/item/whetstone = 2,
+ /obj/item/storage/box/papersack = 20,
/obj/item/mixing_bowl = 10,
/obj/item/kitchen/mould/bear = 1,
/obj/item/kitchen/mould/worm = 1,
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index a0719e38671e..df4cad6e0014 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -660,7 +660,7 @@
occupant.SetSleeping(destruction_sleep_duration)
go_out()
for(var/mob/M in src) //Let's just be ultra sure
- if(isAI(M))
+ if(is_ai(M))
var/mob/living/silicon/ai/AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck.
AI.gib() //No wreck, no AI to recover
else
@@ -949,7 +949,7 @@
/////////////////////////////////////
/obj/mecha/attack_ai(mob/living/silicon/ai/user)
- if(!isAI(user))
+ if(!is_ai(user))
return
//Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot.
if(user.can_dominate_mechs)
@@ -987,7 +987,7 @@
to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.")
return
AI = occupant
- if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot
+ if(!AI || !is_ai(occupant)) //Mech does not have an AI for a pilot
to_chat(user, "No AI detected in the [name] onboard computer.")
return
if(AI.mind.special_role) //Malf AIs cannot leave mechs. Except through death.
@@ -1042,6 +1042,7 @@
AI.cancel_camera()
AI.controlled_mech = src
AI.remote_control = src
+ AI.reset_perspective(src)
AI.can_shunt = FALSE //ONE AI ENTERS. NO AI LEAVES.
to_chat(AI, "[AI.can_dominate_mechs ? "Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!" \
: "You have been uploaded to a mech's onboard computer."]")
@@ -1273,7 +1274,7 @@
/obj/mecha/Exited(atom/movable/M, direction)
var/new_loc = get_step(M, direction)
if(occupant && occupant == M) // The occupant exited the mech without calling go_out()
- if(!isAI(occupant)) //This causes carded AIS to gib, so we do not want this to be called during carding.
+ if(!is_ai(occupant)) //This causes carded AIS to gib, so we do not want this to be called during carding.
go_out(1, new_loc)
/obj/mecha/proc/go_out(forced, atom/newloc = loc)
@@ -1292,7 +1293,7 @@
var/mob/living/brain/brain = occupant
RemoveActions(brain)
mob_container = brain.container
- else if(isAI(occupant))
+ else if(is_ai(occupant))
var/mob/living/silicon/ai/AI = occupant
if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf.
RemoveActions(occupant)
@@ -1306,7 +1307,11 @@
return
to_chat(AI, "Returning to core...")
AI.controlled_mech = null
- AI.remote_control = null
+ if(istype(AI.eyeobj))
+ AI.remote_control = AI.eyeobj
+ AI.reset_perspective(AI.eyeobj)
+ else
+ AI.eyeobj = new /mob/camera/eye/ai(loc, AI.name, AI, AI)
RemoveActions(occupant, 1)
mob_container = AI
newloc = get_turf(AI.linked_core)
@@ -1581,7 +1586,7 @@
/obj/mecha/obj_destruction()
if(wreckage)
var/mob/living/silicon/ai/AI
- if(isAI(occupant))
+ if(is_ai(occupant))
AI = occupant
occupant = null
var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI)
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index 1d213eb825e2..47df4e682660 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -418,7 +418,7 @@
shootAt(H)
if(prob(10))
- var/obj/effect/nanofrost_container/A = new /obj/effect/nanofrost_container(get_turf(src))
+ var/obj/effect/nanofrost_container/A = new /obj/effect/nanofrost_container/anomaly(get_turf(src))
for(var/i in 1 to 5)
step_towards(A, pick(turf_targets))
sleep(2)
diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm
index 246fd64e9638..1a6ee994609a 100644
--- a/code/game/objects/effects/effect_system/effect_system.dm
+++ b/code/game/objects/effects/effect_system/effect_system.dm
@@ -13,11 +13,11 @@ would spawn and follow the beaker, even if it is carried or thrown.
/obj/effect/particle_effect/New()
..()
if(SSticker)
- GLOB.cameranet.updateVisibility(src)
+ GLOB.cameranet.update_visibility(src)
/obj/effect/particle_effect/Destroy()
if(SSticker)
- GLOB.cameranet.updateVisibility(src)
+ GLOB.cameranet.update_visibility(src)
return ..()
/datum/effect_system
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index 0f0da313d3d2..b446bd2b7121 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -71,7 +71,7 @@
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
return ..()
- else if(isanimal(loc) || isAI(loc))
+ else if(isanimal(loc) || is_ai(loc))
return ..()
return FALSE
@@ -519,8 +519,3 @@
keyslot1 = new /obj/item/encryptionkey/syndicate
syndiekey = keyslot1
recalculateChannels()
-
-/obj/item/radio/headset/proc/make_epsilon() // Turns AI's and cyborgs radio to Epsilon radio!
- qdel(keyslot1)
- keyslot1 = new /obj/item/encryptionkey/centcom
- recalculateChannels()
diff --git a/code/game/objects/items/devices/radio/radio_objects.dm b/code/game/objects/items/devices/radio/radio_objects.dm
index d505e3aa64d0..98a12cb91452 100644
--- a/code/game/objects/items/devices/radio/radio_objects.dm
+++ b/code/game/objects/items/devices/radio/radio_objects.dm
@@ -411,7 +411,7 @@ GLOBAL_LIST_EMPTY(deadsay_radio_systems)
jobname = "No id"
// --- AI ---
- else if(isAI(M))
+ else if(is_ai(M))
jobname = "AI"
// --- Cyborg ---
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index b45a8cf50222..395cca3a9642 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -497,7 +497,7 @@ SLIME SCANNER
scan_type = "robot"
else if(ishuman(M))
scan_type = "prosthetics"
- else if(isAI(M))
+ else if(is_ai(M))
scan_type = "ai"
else
to_chat(user, "You can't analyze non-robotic things!")
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 538183524332..f334ed4641d3 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -11,7 +11,7 @@ Keeping it in for adminabuse but the malf one is /obj/item/melee/baton/borg_stun
*/
/obj/item/borg/stun
name = "electrically-charged arm"
- icon_state = "elecarm"
+ icon_state = "elecarm_active"
var/charge_cost = 30
/obj/item/borg/stun/attack__legacy__attackchain(mob/living/M, mob/living/silicon/robot/user)
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index d3b9c46cfd3e..00b4a870bfed 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -84,6 +84,7 @@ GLOBAL_LIST_INIT(metal_recipes, list(
new /datum/stack_recipe("meatspike frame", /obj/structure/kitchenspike_frame, 5, time = 5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("reflector frame", /obj/structure/reflector, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("storage shelf", /obj/structure/shelf, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
+ new /datum/stack_recipe("metal bookcase", /obj/structure/bookcase/metal, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("gun rack", /obj/structure/gunrack, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
null,
new /datum/stack_recipe_list("airlock assemblies", list(
@@ -186,6 +187,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list(
new /datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 3, time = 5 SECONDS),
new /datum/stack_recipe("Surgery Table", /obj/machinery/optable, 5, time = 5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("Metal crate", /obj/structure/closet/crate, 10, time = 5 SECONDS, one_per_turf = TRUE),
+ new /datum/stack_recipe("military bookcase", /obj/structure/bookcase/military, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("Mass Driver frame", /obj/machinery/mass_driver_frame, 3, time = 5 SECONDS, one_per_turf = TRUE),
new /datum/stack_recipe("hardened wheelchair", /obj/structure/chair/wheelchair/plasteel, 15, time = 6 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
null,
diff --git a/code/game/objects/items/tools/multitool.dm b/code/game/objects/items/tools/multitool.dm
index 5f7187c9a0ca..42d477cf7f04 100644
--- a/code/game/objects/items/tools/multitool.dm
+++ b/code/game/objects/items/tools/multitool.dm
@@ -94,15 +94,15 @@
/obj/item/multitool/ai_detect/proc/multitool_detect()
var/turf/our_turf = get_turf(src)
for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
- if(AI.cameraFollow == src)
+ if(AI.camera_follow == src)
detect_state = PROXIMITY_ON_SCREEN
break
- if(!detect_state && GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z))
- var/datum/camerachunk/chunk = GLOB.cameranet.getCameraChunk(our_turf.x, our_turf.y, our_turf.z)
+ if(!detect_state && GLOB.cameranet.chunk_generated(our_turf.x, our_turf.y, our_turf.z))
+ var/datum/camerachunk/chunk = GLOB.cameranet.get_camera_chunk(our_turf.x, our_turf.y, our_turf.z)
if(chunk)
if(length(chunk.seenby))
- for(var/mob/camera/ai_eye/A in chunk.seenby)
+ for(var/mob/camera/eye/ai/A in chunk.seenby)
//Checks if the A is to be detected or not
if(!A.ai_detector_visible)
continue
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index 86ad64db8266..b21530f93181 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -23,9 +23,12 @@
tool_behaviour = TOOL_SCREWDRIVER
var/random_color = TRUE //if the screwdriver uses random coloring
+ new_attack_chain = TRUE
+
/obj/item/screwdriver/Initialize(mapload)
. = ..()
AddComponent(/datum/component/surgery_initiator/robo)
+ RegisterSignal(src, COMSIG_ATTACK, PROC_REF(on_attack))
/obj/item/screwdriver/nuke
name = "screwdriver"
@@ -50,14 +53,15 @@
if(prob(75))
src.pixel_y = rand(0, 16)
-/obj/item/screwdriver/attack__legacy__attackchain(mob/living/carbon/M, mob/living/carbon/user)
- if(!istype(M) || user.a_intent == INTENT_HELP)
- return ..()
+/obj/item/screwdriver/proc/on_attack(datum/source, mob/living/carbon/target, mob/living/user)
+ if(!istype(target) || user.a_intent == INTENT_HELP)
+ return
if(user.zone_selected != "eyes" && user.zone_selected != "head")
- return ..()
+ return
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
- M = user
- return eyestab(M,user)
+ target = user
+ eyestab(target, user)
+ return COMPONENT_SKIP_ATTACK
/obj/item/screwdriver/brass
name = "brass screwdriver"
@@ -118,7 +122,10 @@
user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!")
return BRUTELOSS
-/obj/item/screwdriver/power/attack_self__legacy__attackchain(mob/user)
+/obj/item/screwdriver/power/activate_self(mob/user)
+ if(..())
+ return
+
playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, 1)
var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power
to_chat(user, "You attach the bolt driver bit to [src].")
diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm
index 3d6b38d653ef..5f24feb644ac 100644
--- a/code/game/objects/items/tools/wirecutters.dm
+++ b/code/game/objects/items/tools/wirecutters.dm
@@ -23,6 +23,8 @@
tool_behaviour = TOOL_WIRECUTTER
var/random_color = TRUE
+ new_attack_chain = TRUE
+
/obj/item/wirecutters/New(loc, param_color = null)
..()
if(random_color)
@@ -31,16 +33,15 @@
belt_icon = "wirecutters_[param_color]"
icon_state = "cutters_[param_color]"
-/obj/item/wirecutters/attack__legacy__attackchain(mob/living/carbon/C, mob/user)
- if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable))
- user.visible_message("[user] cuts [C]'s restraints with [src]!")
- QDEL_NULL(C.handcuffed)
- if(C.buckled && C.buckled.buckle_requires_restraints)
- C.unbuckle()
- C.update_handcuffed()
- return
- else
- return ..()
+/obj/item/wirecutters/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ var/mob/living/carbon/mob = target
+ if(istype(mob) && mob.handcuffed && istype(mob.handcuffed, /obj/item/restraints/handcuffs/cable))
+ user.visible_message("[user] cuts [mob]'s restraints with [src]!")
+ QDEL_NULL(mob.handcuffed)
+ if(mob.buckled && mob.buckled.buckle_requires_restraints)
+ mob.unbuckle()
+ mob.update_handcuffed()
+ return ITEM_INTERACT_COMPLETE
/obj/item/wirecutters/suicide_act(mob/user)
user.visible_message("[user] is cutting at [user.p_their()] [is_robotic_suicide(user) ? "wiring" : "arteries"] with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -146,7 +147,10 @@
return OXYLOSS
-/obj/item/wirecutters/power/attack_self__legacy__attackchain(mob/user)
+/obj/item/wirecutters/power/activate_self(mob/user)
+ if(..())
+ return
+
playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power
to_chat(user, "You attach the pry jaws to [src].")
diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm
index 040c0e1d3b1c..e741c4540fa5 100644
--- a/code/game/objects/items/tools/wrench.dm
+++ b/code/game/objects/items/tools/wrench.dm
@@ -19,6 +19,8 @@
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, RAD = 0, FIRE = 50, ACID = 30)
tool_behaviour = TOOL_WRENCH
+ new_attack_chain = TRUE
+
/obj/item/wrench/suicide_act(mob/user)
user.visible_message("[user] is unsecuring [user.p_their()] head with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -70,7 +72,10 @@
toolspeed = 0.25
w_class = WEIGHT_CLASS_NORMAL
-/obj/item/wrench/power/attack_self__legacy__attackchain(mob/user)
+/obj/item/wrench/power/activate_self(mob/user)
+ if(..())
+ return
+
playsound(get_turf(user),'sound/items/change_drill.ogg', 50, 1)
var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power
to_chat(user, "You attach the screwdriver bit to [src].")
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 0b4d7a1b1b16..f9b0a46abc22 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -515,7 +515,7 @@
..()
playsound(src, 'sound/effects/meteorimpact.ogg', 40, 1)
for(var/mob/M in range(10, src))
- if(!M.stat && !isAI(M))\
+ if(!M.stat && !is_ai(M))\
shake_camera(M, 3, 1)
qdel(src)
@@ -1378,7 +1378,7 @@
playsound(src, 'sound/effects/explosionfar.ogg', 50, FALSE, 0)
flick("bigred_press", src)
for(var/mob/M in range(10, src)) // Checks range
- if(!M.stat && !isAI(M)) // Checks to make sure whoever's getting shaken is alive/not the AI
+ if(!M.stat && !is_ai(M)) // Checks to make sure whoever's getting shaken is alive/not the AI
sleep(8) // Short delay to match up with the explosion sound
shake_camera(M, 2, 1) // Shakes player camera 2 squares for 1 second.
diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm
index 427e2587f601..a3c57ce9dccf 100644
--- a/code/game/objects/items/weapons/storage/belt.dm
+++ b/code/game/objects/items/weapons/storage/belt.dm
@@ -785,15 +785,16 @@
/obj/item/storage/belt/sheath/remove_from_storage(obj/item/W, atom/new_location)
if(!..())
return
- playsound(src, 'sound/weapons/blade_unsheath.ogg', 20)
+ if(!length(contents)) // telekinesis grab spawns inside of the sheath and leaves it immediately...
+ playsound(src, 'sound/weapons/blade_unsheath.ogg', 20)
/obj/item/storage/belt/sheath/update_icon_state()
if(length(contents))
- icon_state = "[icon_state]-sword"
- item_state = "[item_state]-sword"
+ icon_state = "[base_icon_state]-sword"
+ item_state = "[base_icon_state]-sword"
else
- icon_state = initial(icon_state)
- item_state = initial(item_state)
+ icon_state = base_icon_state
+ item_state = base_icon_state
if(isliving(loc))
var/mob/living/L = loc
L.update_inv_belt()
@@ -801,8 +802,7 @@
/obj/item/storage/belt/sheath/saber
name = "saber sheath"
desc = "Can hold sabers."
- icon_state = "sheath"
- item_state = "sheath"
+ base_icon_state = "sheath"
can_hold = list(/obj/item/melee/saber)
/obj/item/storage/belt/sheath/saber/populate_contents()
@@ -812,8 +812,7 @@
/obj/item/storage/belt/sheath/snakesfang
name = "snakesfang scabbard"
desc = "Can hold scimitars."
- icon_state = "snakesfangsheath"
- item_state = "snakesfangsheath"
+ base_icon_state = "snakesfangsheath"
can_hold = list(/obj/item/melee/snakesfang)
/obj/item/storage/belt/sheath/snakesfang/populate_contents()
@@ -823,8 +822,7 @@
/obj/item/storage/belt/sheath/breach_cleaver
name = "breach cleaver scabbard"
desc = "Can hold massive cleavers."
- icon_state = "breachcleaversheath"
- item_state = "breachcleaversheath"
+ base_icon_state = "breachcleaversheath"
can_hold = list(/obj/item/melee/breach_cleaver)
/obj/item/storage/belt/sheath/breach_cleaver/populate_contents()
diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm
index 71150a1daffa..fb1dcaca5733 100644
--- a/code/game/objects/items/weapons/storage/uplink_kits.dm
+++ b/code/game/objects/items/weapons/storage/uplink_kits.dm
@@ -579,3 +579,12 @@
/obj/item/storage/box/syndie_kit/forgers_kit/populate_contents()
new /obj/item/stamp/chameleon(src)
new /obj/item/pen/chameleon(src)
+
+/obj/item/storage/box/syndie_kit/syndie_mantis
+ name = "\improper Mantis Blades kit"
+ desc = "A sleek box marked with a Cybersun logo. The label says it contains a pair of CX-12 'Naginata' mantis blades and accompanying autosurgeons."
+
+/obj/item/storage/box/syndie_kit/syndie_mantis/populate_contents()
+ new /obj/item/autosurgeon/organ/syndicate/oneuse/syndie_mantis(src)
+ new /obj/item/autosurgeon/organ/syndicate/oneuse/syndie_mantis/l(src)
+
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index 33733f7e25d2..e877bb946940 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -332,6 +332,9 @@
/obj/item/melee/baton/loaded/borg_stun_arm
name = "electrically-charged arm"
desc = "A piece of scrap metal wired directly to your power cell."
+ icon = 'icons/mob/robot_items.dmi'
+ base_icon = "elecarm"
+ icon_state = "elecarm"
hitcost = 100
/obj/item/melee/baton/loaded/borg_stun_arm/screwdriver_act(mob/living/user, obj/item/I)
diff --git a/code/game/objects/items/weapons/tanks/watertank.dm b/code/game/objects/items/weapons/tanks/watertank.dm
index ac334cfbaccf..4722417ad467 100644
--- a/code/game/objects/items/weapons/tanks/watertank.dm
+++ b/code/game/objects/items/weapons/tanks/watertank.dm
@@ -299,9 +299,8 @@
var/obj/effect/nanofrost_container/A = new /obj/effect/nanofrost_container(get_turf(src))
log_game("[key_name(user)] used Nanofrost at [get_area(user)] ([user.x], [user.y], [user.z]).")
playsound(src,'sound/items/syringeproj.ogg', 40, TRUE)
- for(var/a in 1 to 6)
- step_towards(A, target)
- sleep(2)
+ A.throw_at(target, 6, 2, user)
+ sleep(2)
A.Smoke()
addtimer(VARSET_CALLBACK(src, nanofrost_cooldown, FALSE))
if(METAL_FOAM)
@@ -334,6 +333,11 @@
playsound(src, 'sound/effects/bamf.ogg', 100, TRUE)
qdel(src)
+/obj/effect/nanofrost_container/anomaly
+ name = "nanofrost anomaly"
+ desc = "A frozen shell of ice containing nanofrost that freezes the surrounding area."
+ icon_state = "frozen_smoke_anomaly"
+
#undef EXTINGUISHER
#undef NANOFROST
#undef METAL_FOAM
diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm
index 31f6bd289c61..72a5e86ec3e3 100644
--- a/code/game/objects/items/weapons/twohanded.dm
+++ b/code/game/objects/items/weapons/twohanded.dm
@@ -28,7 +28,7 @@
/obj/item/fireaxe/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_FORCES_OPEN_DOORS_ITEM, ROUNDSTART_TRAIT)
+ AddComponent(/datum/component/forces_doors_open)
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.7, _parryable_attack_types = MELEE_ATTACK, _parry_cooldown = (10 / 3) SECONDS, _requires_two_hands = TRUE) // 2.3333 seconds of cooldown for 30% uptime
AddComponent(/datum/component/two_handed, force_unwielded = force_unwielded, force_wielded = force_wielded, icon_wielded = "[base_icon_state]1")
@@ -1070,8 +1070,8 @@
/obj/item/supermatter_halberd/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_FORCES_OPEN_DOORS_ITEM, ROUNDSTART_TRAIT)
ADD_TRAIT(src, TRAIT_SUPERMATTER_IMMUNE, ROUNDSTART_TRAIT) //so it can't be dusted by the SM
+ AddComponent(/datum/component/forces_doors_open)
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.25, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (4 / 3) SECONDS, _requires_two_hands = TRUE) // 0.3333 seconds of cooldown for 75% uptime
AddComponent(/datum/component/two_handed, force_wielded = 40, force_unwielded = force, icon_wielded = "[base_icon_state]1")
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 635984ca9c51..66661d0d0d42 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -113,7 +113,7 @@
if(M.client && M.machine == src)
is_in_use = TRUE
src.attack_hand(M)
- if(isAI(usr) || isrobot(usr))
+ if(is_ai(usr) || isrobot(usr))
if(!(usr in nearby))
if(usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
is_in_use = TRUE
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 3b6a8c876337..35f8be4f3017 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -22,7 +22,7 @@
if(smoothing_flags & SMOOTH_CORNERS)
icon_state = ""
if(SSticker)
- GLOB.cameranet.updateVisibility(src)
+ GLOB.cameranet.update_visibility(src)
/obj/structure/Initialize(mapload)
if(!armor)
@@ -34,7 +34,7 @@
/obj/structure/Destroy()
climbers = null
if(SSticker)
- GLOB.cameranet.updateVisibility(src)
+ GLOB.cameranet.update_visibility(src)
if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
var/turf/T = get_turf(src)
QUEUE_SMOOTH_NEIGHBORS(T)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 81a88adc6d22..a1e0b163ebf3 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -247,7 +247,7 @@
continue
if(M.buckled || M.anchored || M.has_buckled_mobs())
continue
- if(isAI(M))
+ if(is_ai(M))
continue
M.forceMove(src)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 40e729f30e3b..128209bca78a 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -383,6 +383,13 @@
var/target_temp = T0C - 40
var/cooling_power = 40
+/obj/structure/closet/crate/freezer/deluxe
+ name = "Deluxe Freezer"
+ desc = "A fancy looking freezer emblazoned with the Nanotrasen logo."
+ icon_state = "freezerdeluxe"
+ icon_opened = "freezerdeluxe_open"
+ icon_closed = "freezerdeluxe"
+
/obj/structure/closet/crate/freezer/return_obj_air()
RETURN_TYPE(/datum/gas_mixture)
var/datum/gas_mixture/gas = ..()
diff --git a/code/game/objects/structures/inflatable.dm b/code/game/objects/structures/inflatable.dm
index 1db795432b71..d8e4e9754530 100644
--- a/code/game/objects/structures/inflatable.dm
+++ b/code/game/objects/structures/inflatable.dm
@@ -107,7 +107,7 @@
var/is_operating = FALSE
/obj/structure/inflatable/door/attack_ai(mob/user as mob) //those aren't machinery, they're just big fucking slabs of a mineral
- if(isAI(user)) //so the AI can't open it
+ if(is_ai(user)) //so the AI can't open it
return
else if(isrobot(user)) //but cyborgs can
if(get_dist(user,src) <= 1) //not remotely though
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 4ab47a636f32..26d4692461b2 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -87,7 +87,7 @@
deconstruct(TRUE)
/obj/structure/kitchenspike/MouseDrop_T(mob/living/victim, mob/living/user)
- if(!user.Adjacent(src) || !user.Adjacent(victim) || isAI(user) || !ismob(victim))
+ if(!user.Adjacent(src) || !user.Adjacent(victim) || is_ai(user) || !ismob(victim))
return
if(isanimal(user) && victim != user)
return // animals cannot put mobs other than themselves onto spikes
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index 144bf5f747df..e12f73b712df 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -47,7 +47,7 @@
return try_to_operate(user)
/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral
- if(isAI(user)) //so the AI can't open it
+ if(is_ai(user)) //so the AI can't open it
return
else if(isrobot(user) && Adjacent(user)) //but cyborgs can, but not remotely
return try_to_operate(user)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 09341eaea354..765a74fed915 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -85,13 +85,7 @@
/// The effect used to render a pressure overlay from this tile.
var/obj/effect/pressure_overlay/pressure_overlay
- var/list/milla_atmos_airtight = list(FALSE, FALSE, FALSE, FALSE)
- var/list/milla_superconductivity = list(
- OPEN_HEAT_TRANSFER_COEFFICIENT,
- OPEN_HEAT_TRANSFER_COEFFICIENT,
- OPEN_HEAT_TRANSFER_COEFFICIENT,
- OPEN_HEAT_TRANSFER_COEFFICIENT)
- var/list/milla_data = list()
+ var/list/milla_data = null
new_attack_chain = TRUE
@@ -118,7 +112,7 @@
SET_BITFLAG_LIST(canSmoothWith)
if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
QUEUE_SMOOTH(src)
- visibilityChanged()
+ visibility_changed()
for(var/atom/movable/AM in src)
Entered(AM)
@@ -150,7 +144,7 @@
qdel(A)
return
REMOVE_FROM_SMOOTH_QUEUE(src)
- visibilityChanged()
+ visibility_changed()
QDEL_LIST_CONTENTS(blueprint_data)
initialized = FALSE
bound_air = null
@@ -480,9 +474,9 @@
ChangeTurf(baseturf)
return 2
-/turf/proc/visibilityChanged()
+/turf/proc/visibility_changed()
if(SSticker)
- GLOB.cameranet.updateVisibility(src)
+ GLOB.cameranet.update_visibility(src)
/turf/attack_by(obj/item/attacking, mob/user, params)
if(..())
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index d5e6faed788e..10414b200a48 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -183,10 +183,10 @@ GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00")
if(target.mob in heard)
send = 1
- if(isAI(target.mob))
+ if(is_ai(target.mob))
prefix = " (Core)"
- else if(isAI(target.mob)) // Special case
+ else if(is_ai(target.mob)) // Special case
var/mob/living/silicon/ai/A = target.mob
if(A.eyeobj in hearers(7, source))
send = 1
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 85a09203eced..8d1896340311 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -387,9 +387,9 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
if(ishuman(mob))
var/mob/living/carbon/human/H = mob
H.regenerate_icons() // workaround for #13269
- if(isAI(mob)) // client.mob, built in byond client var
+ if(is_ai(mob)) // client.mob, built in byond client var
var/mob/living/silicon/ai/ai = mob
- ai.eyeobj.setLoc(old_turf)
+ ai.eyeobj.set_loc(old_turf)
else if(isnewplayer(mob))
to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or observe first.")
else
diff --git a/code/modules/admin/misc_admin_procs.dm b/code/modules/admin/misc_admin_procs.dm
index aee3772ced09..bf1c706cac86 100644
--- a/code/modules/admin/misc_admin_procs.dm
+++ b/code/modules/admin/misc_admin_procs.dm
@@ -157,7 +157,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
"}
var/jumptoeye = ""
- if(isAI(M))
+ if(is_ai(M))
var/mob/living/silicon/ai/A = M
if(A.client && A.eyeobj) // No point following clientless AI eyes
jumptoeye = " (Eye)"
@@ -199,7 +199,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
body += "Corgize | "
//AI / Cyborg
- if(isAI(M))
+ if(is_ai(M))
body += "Is an AI "
else if(ishuman(M))
body += {"Make AI |
@@ -327,6 +327,8 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
dat += "Change Game Mode
"
if(GLOB.master_mode == "secret")
dat += "(Force Secret Mode)
"
+ if(GLOB.master_mode == "dynamic" || (GLOB.master_mode == "secret" && GLOB.secret_force_mode == "dynamic"))
+ dat += "(Force Dynamic Rulesets)
"
dat += "
"
dat += "Create Object
"
dat += "Quick Create Object
"
@@ -800,7 +802,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
if(istype(S, /mob/living/silicon/decoy) && !S.client)
continue
ai_number++
- if(isAI(S))
+ if(is_ai(S))
messages += "AI [key_name(S, TRUE)]'s laws:"
else if(isrobot(S))
var/mob/living/silicon/robot/R = S
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 8240bbeae686..231bb1fbd628 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -255,7 +255,7 @@
M_job = "Carbon-based"
else if(issilicon(M)) //silicon
- if(isAI(M))
+ if(is_ai(M))
M_job = "AI"
else if(ispAI(M))
M_job = "pAI"
@@ -298,7 +298,7 @@
M_key = replacetext(M_key, "\\", "")
var/M_eyeUID = ""
- if(isAI(M))
+ if(is_ai(M))
var/mob/living/silicon/ai/A = M
if(A.client && A.eyeobj) // No point following clientless AI eyes
M_eyeUID = "[A.eyeobj.UID()]"
@@ -355,6 +355,9 @@
if(SSticker && SSticker.current_state >= GAME_STATE_PLAYING)
var/dat = "Round StatusRound Status
"
dat += "Current Game Mode: [SSticker.mode.name]
"
+ if(istype(SSticker.mode, /datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/dynamic = SSticker.mode
+ dat += "Rulesets: [english_list(dynamic.rulesets + dynamic.implied_rulesets)]
"
dat += "Round Duration: [round(ROUND_TIME / 36000)]:[add_zero(num2text(ROUND_TIME / 600 % 60), 2)]:[add_zero(num2text(ROUND_TIME / 10 % 60), 2)]
"
dat += "Emergency shuttle
"
if(SSshuttle.emergency.mode < SHUTTLE_CALL)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 602e710d128a..0ba880790919 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1041,6 +1041,20 @@
dat += {"Now: [GLOB.secret_force_mode]"}
usr << browse(dat, "window=f_secret")
+ else if(href_list["f_dynamic"])
+ 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" && !(GLOB.master_mode == "secret" && GLOB.secret_force_mode == "dynamic"))
+ return alert(usr, "The game mode has to be dynamic!", null, null, null, null)
+ var/dat = {"Possible Rulesets:
"}
+ var/list/rulesets = subtypesof(/datum/ruleset) - typesof(/datum/ruleset/implied)
+ dat += {"Budget: [isnull(GLOB.dynamic_forced_rulesets["budget"]) ? "Random" : GLOB.dynamic_forced_rulesets["budget"]]
"}
+ for(var/datum/ruleset/ruleset as anything in rulesets)
+ dat += {"[ruleset.name]: [GLOB.dynamic_forced_rulesets[ruleset] || DYNAMIC_RULESET_NORMAL]
"}
+ usr << browse(dat, "window=f_dynamic")
+
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER)) return
@@ -1067,6 +1081,44 @@
Game() // updates the main game menu
.(href, list("f_secret"=1))
+ else if(href_list["f_dynamic2"])
+ if(!check_rights(R_ADMIN|R_SERVER)) return
+
+ if(SSticker && SSticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(GLOB.master_mode != "dynamic" && !(GLOB.master_mode == "secret" && GLOB.secret_force_mode == "dynamic"))
+ return alert(usr, "The game mode has to be dynamic!", null, null, null, null)
+ if(href_list["f_dynamic2"] == "budget")
+ var/budget = input(usr, "Pick a budget for the dynamic gamemode (-1 to randomize)", "Gamemode Budget") as num|null
+ if(isnull(budget))
+ return
+ if(budget < 0)
+ GLOB.dynamic_forced_rulesets -= "budget"
+ log_admin("[key_name(usr)] set the ruleset budget to random.")
+ message_admins("[key_name_admin(usr)] set the ruleset budget to random.")
+ else
+ GLOB.dynamic_forced_rulesets["budget"] = budget
+ log_admin("[key_name(usr)] set the ruleset budget to [budget]")
+ message_admins("[key_name_admin(usr)] set the ruleset budget to [budget].")
+ .(href, list("f_dynamic"=1))
+ return
+
+ var/datum/ruleset/ruleset = text2path(href_list["f_dynamic2"])
+ switch(GLOB.dynamic_forced_rulesets[ruleset])
+ if(DYNAMIC_RULESET_FORCED)
+ log_admin("[key_name(usr)] banned the [ruleset] ruleset.")
+ message_admins("[key_name_admin(usr)] banned the [ruleset.name] ([ruleset.type]) ruleset.")
+ GLOB.dynamic_forced_rulesets[ruleset] = DYNAMIC_RULESET_BANNED
+ if(DYNAMIC_RULESET_BANNED)
+ log_admin("[key_name(usr)] set the [ruleset] ruleset to normal.")
+ message_admins("[key_name_admin(usr)] set the [ruleset.name] ([ruleset.type]) ruleset to normal.")
+ GLOB.dynamic_forced_rulesets[ruleset] = DYNAMIC_RULESET_NORMAL
+ else // not set, and already at normal
+ log_admin("[key_name(usr)] forced the [ruleset] ruleset.")
+ message_admins("[key_name_admin(usr)] forced the [ruleset.name] ([ruleset.type]) ruleset.")
+ GLOB.dynamic_forced_rulesets[ruleset] = DYNAMIC_RULESET_FORCED
+ .(href, list("f_dynamic"=1))
+
else if(href_list["monkeyone"])
if(!check_rights(R_SPAWN)) return
@@ -1146,7 +1198,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -1297,7 +1349,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -1329,7 +1381,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -1361,7 +1413,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -1385,7 +1437,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -1492,7 +1544,7 @@
if(!ismob(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- if(isAI(M))
+ if(is_ai(M))
to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
return
@@ -2255,7 +2307,7 @@
if(!href_list["cryoafk"] && !isLivingSSD(M))
to_chat(usr, "This can only be used on living, SSD players.")
return
- if(isAI(M))
+ if(is_ai(M))
var/mob/living/silicon/ai/A = M
A.cryo_AI()
if(istype(M.loc, /obj/machinery/cryopod))
@@ -3615,7 +3667,7 @@
target = C.mob
. = ADMIN_FLW(target, "FLW")
- if(isAI(target)) // AI core/eye follow links
+ if(is_ai(target)) // AI core/eye follow links
var/mob/living/silicon/ai/A = target
if(A.client && A.eyeobj) // No point following clientless AI eyes
. += "|[ADMIN_FLW(A.eyeobj,"EYE")]"
diff --git a/code/modules/admin/verbs/deathsquad.dm b/code/modules/admin/verbs/deathsquad.dm
index b65274f5f3b0..dd526796c258 100644
--- a/code/modules/admin/verbs/deathsquad.dm
+++ b/code/modules/admin/verbs/deathsquad.dm
@@ -40,15 +40,17 @@ GLOBAL_VAR_INIT(deathsquad_sent, FALSE)
SEND_SOUND(AI, notice_sound)
AI.show_laws()
var/obj/item/radio/headset/heads/ai_integrated/ai_radio = AI.get_radio()
- ai_radio.make_epsilon()
+ ai_radio.channels |= list("Response Team" = 1, "Special Ops" = 1)
+ ai_radio.config(ai_radio.channels)
for(var/mob/living/silicon/robot/R in AI.connected_robots)
R.sync()
to_chat(R, "Central command has uploaded a new set of laws you must follow. Make sure you follow them.")
SEND_SOUND(R, notice_sound)
R.show_laws()
- var/obj/item/radio/headset/heads/ai_integrated/cyberg_radio = R.get_radio()
- cyberg_radio.make_epsilon()
+ var/obj/item/radio/borg/cyborg_radio = R.get_radio()
+ cyborg_radio.channels |= list("Response Team" = 1, "Special Ops" = 1)
+ cyborg_radio.config(cyborg_radio.channels)
// Locates commandos spawns
var/list/commando_spawn_locations = list()
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index b4953dd31076..0a09fa34d0d1 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -23,7 +23,7 @@
return
if(ismob(M))
- if(isAI(M))
+ if(is_ai(M))
alert("The AI can't be sent to prison you jerk!", null, null, null, null, null)
return
//strip their stuff before they teleport into a cell :downs:
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index c6fd006e4728..b485eca0da49 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -149,7 +149,7 @@
/obj/item/melee/arm_blade/Initialize(mapload, silent, new_parent_action)
. = ..()
parent_action = new_parent_action
- ADD_TRAIT(src, TRAIT_FORCES_OPEN_DOORS_ITEM, ROUNDSTART_TRAIT)
+ AddComponent(/datum/component/forces_doors_open, time_to_open = 10 SECONDS)
/obj/item/melee/arm_blade/Destroy()
if(parent_action)
diff --git a/code/modules/antagonists/mind_flayer/flayer_datum.dm b/code/modules/antagonists/mind_flayer/flayer_datum.dm
index 637bcd1d7884..d8ec8a391496 100644
--- a/code/modules/antagonists/mind_flayer/flayer_datum.dm
+++ b/code/modules/antagonists/mind_flayer/flayer_datum.dm
@@ -1,8 +1,9 @@
/datum/antagonist/mindflayer
name = "Mindflayer"
+ job_rank = ROLE_MIND_FLAYER
+ special_role = SPECIAL_ROLE_MIND_FLAYER
antag_hud_type = ANTAG_HUD_MIND_FLAYER
antag_hud_name = "hudflayer"
- special_role = SPECIAL_ROLE_MIND_FLAYER
wiki_page_name = "Mindflayer"
/// The current amount of swarms the mind flayer has access to purchase with
var/usable_swarms = 0
diff --git a/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm b/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm
index a50582678eac..c19b625635a2 100644
--- a/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm
+++ b/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm
@@ -58,7 +58,8 @@
new /obj/item/card/id/syndicate(src)
new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src)
new /obj/item/lighter/zippo(src)
-
+ new /obj/item/handheld_mirror(src)
+
/obj/item/paper/contractor_guide
name = "contractor guide"
diff --git a/code/modules/antagonists/traitor/datum_mindslave.dm b/code/modules/antagonists/traitor/datum_mindslave.dm
index 09beb78be3ca..0e9f58ed6c3b 100644
--- a/code/modules/antagonists/traitor/datum_mindslave.dm
+++ b/code/modules/antagonists/traitor/datum_mindslave.dm
@@ -4,7 +4,7 @@ RESTRICT_TYPE(/datum/antagonist/mindslave)
/datum/antagonist/mindslave
name = "Mindslave"
roundend_category = "mindslaves"
- job_rank = SPECIAL_ROLE_TRAITOR
+ job_rank = ROLE_TRAITOR
special_role = SPECIAL_ROLE_TRAITOR
antag_hud_type = ANTAG_HUD_TRAITOR
antag_hud_name = "mindslave" // This isn't named "hudmindslave" because `add_serve_hud()` adds "hud" to the beginning.
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 3cf88d939d26..cc709d5fe20f 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -44,7 +44,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
/datum/antagonist/traitor/Destroy(force, ...)
// Remove all associated malf AI abilities.
- if(isAI(owner.current))
+ if(is_ai(owner.current))
var/mob/living/silicon/ai/A = owner.current
A.clear_zeroth_law()
var/obj/item/radio/headset/heads/ai_integrated/radio = A.get_radio()
@@ -84,7 +84,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
return ..()
/datum/antagonist/traitor/select_organization()
- if(isAI(owner.current))
+ if(is_ai(owner.current))
return
var/chaos = pickweight(list(ORG_CHAOS_HUNTER = ORG_PROB_HUNTER, ORG_CHAOS_MILD = ORG_PROB_MILD, ORG_CHAOS_AVERAGE = ORG_PROB_AVERAGE, ORG_CHAOS_HIJACK = ORG_PROB_HIJACK))
for(var/org_type in shuffle(subtypesof(/datum/antag_org/syndicate)))
@@ -108,7 +108,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
return ..()
/datum/antagonist/traitor/give_objectives()
- if(isAI(owner.current))
+ if(is_ai(owner.current))
forge_ai_objectives()
else
forge_human_objectives()
@@ -163,7 +163,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
antag_memory += "Organization: [organization.name]
"
if(give_codewords)
messages.Add(give_codewords())
- if(isAI(owner.current))
+ if(is_ai(owner.current))
add_law_zero()
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
var/mob/living/silicon/ai/A = owner.current
@@ -211,7 +211,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
* Gives a traitor human their uplink, and uplink code.
*/
/datum/antagonist/traitor/proc/give_uplink()
- if(isAI(owner.current))
+ if(is_ai(owner.current))
return FALSE
var/mob/living/carbon/human/traitor_mob = owner.current
diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm
index b6e97c0c6717..3a83ef87e088 100644
--- a/code/modules/antagonists/vampire/vamp_datum.dm
+++ b/code/modules/antagonists/vampire/vamp_datum.dm
@@ -2,9 +2,10 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
/datum/antagonist/vampire
name = "Vampire"
+ job_rank = ROLE_VAMPIRE
+ special_role = SPECIAL_ROLE_VAMPIRE
antag_hud_type = ANTAG_HUD_VAMPIRE
antag_hud_name = "hudvampire"
- special_role = SPECIAL_ROLE_VAMPIRE
wiki_page_name = "Vampire"
var/bloodtotal = 0
var/bloodusable = 0
diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
index 8a845f5994ad..882772719295 100644
--- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
+++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
@@ -173,9 +173,9 @@
/turf/proc/Initialize_Atmos(times_fired)
// This is one of two places expected to call this otherwise-unsafe method.
- private_unsafe_recalculate_atmos_connectivity()
+ var/list/connectivity = private_unsafe_recalculate_atmos_connectivity()
var/list/air = list(oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature)
- milla_data = milla_atmos_airtight + list(atmos_mode, SSmapping.environments[atmos_environment]) + air + milla_superconductivity
+ milla_data = connectivity[1] + list(atmos_mode, SSmapping.environments[atmos_environment]) + air + connectivity[2]
/turf/proc/recalculate_atmos_connectivity()
var/datum/milla_safe/recalculate_atmos_connectivity/milla = new()
@@ -188,26 +188,26 @@
return
// This is one of two places expected to call this otherwise-unsafe method.
- T.private_unsafe_recalculate_atmos_connectivity()
+ var/list/connectivity = T.private_unsafe_recalculate_atmos_connectivity()
- set_tile_airtight(T, T.milla_atmos_airtight)
+ set_tile_airtight(T, connectivity[1])
reset_superconductivity(T)
- reduce_superconductivity(T, T.milla_superconductivity)
+ reduce_superconductivity(T, connectivity[2])
/// This method is unsafe to use because it only updates milla_* properties, but does not write them to MILLA. Use recalculate_atmos_connectivity() instead.
/turf/proc/private_unsafe_recalculate_atmos_connectivity()
if(blocks_air)
- milla_atmos_airtight = list(TRUE, TRUE, TRUE, TRUE)
- milla_superconductivity = list(0, 0, 0, 0)
- return
+ var/milla_atmos_airtight = list(TRUE, TRUE, TRUE, TRUE)
+ var/milla_superconductivity = list(0, 0, 0, 0)
+ return list(milla_atmos_airtight, milla_superconductivity)
- milla_atmos_airtight = list(
+ var/milla_atmos_airtight = list(
!CanAtmosPass(NORTH, FALSE),
!CanAtmosPass(EAST, FALSE),
!CanAtmosPass(SOUTH, FALSE),
!CanAtmosPass(WEST, FALSE))
- milla_superconductivity = list(
+ var/milla_superconductivity = list(
OPEN_HEAT_TRANSFER_COEFFICIENT,
OPEN_HEAT_TRANSFER_COEFFICIENT,
OPEN_HEAT_TRANSFER_COEFFICIENT,
@@ -230,6 +230,8 @@
milla_superconductivity[INDEX_SOUTH] = min(milla_superconductivity[INDEX_SOUTH], O.get_superconductivity(SOUTH))
milla_superconductivity[INDEX_WEST] = min(milla_superconductivity[INDEX_WEST], O.get_superconductivity(WEST))
+ return list(milla_atmos_airtight, milla_superconductivity)
+
/obj/effect/wind
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index 7bfd1fa004ec..e14706d7b269 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -114,12 +114,10 @@ What are the archived variables for?
/// Calculate moles
/datum/gas_mixture/proc/total_moles()
- var/moles = private_oxygen + private_carbon_dioxide + private_nitrogen + private_toxins + private_sleeping_agent + private_agent_b
- return moles
+ return private_oxygen + private_carbon_dioxide + private_nitrogen + private_toxins + private_sleeping_agent + private_agent_b
/datum/gas_mixture/proc/total_trace_moles()
- var/moles = private_agent_b
- return moles
+ return private_agent_b
/// Calculate pressure in kilopascals
/datum/gas_mixture/proc/return_pressure()
@@ -662,23 +660,26 @@ What are the archived variables for?
/proc/share_many_airs(list/mixtures)
var/total_volume = 0
- var/total_thermal_energy = 0
- var/total_heat_capacity = 0
var/total_oxygen = 0
var/total_nitrogen = 0
var/total_toxins = 0
var/total_carbon_dioxide = 0
var/total_sleeping_agent = 0
var/total_agent_b = 0
+ var/must_share = FALSE
+ // Collect all the cheap data and check if there's a significant temperature difference.
+ var/temperature = null
for(var/datum/gas_mixture/G as anything in mixtures)
if(!istype(G))
stack_trace("share_many_airs had [G] in mixtures ([json_encode(mixtures)])")
continue
total_volume += G.volume
- var/heat_capacity = G.heat_capacity()
- total_heat_capacity += heat_capacity
- total_thermal_energy += G.private_temperature * heat_capacity
+
+ if(isnull(temperature))
+ temperature = G.private_temperature
+ else if(abs(temperature - G.private_temperature) >= 1)
+ must_share = TRUE
total_oxygen += G.private_oxygen
total_nitrogen += G.private_nitrogen
@@ -687,26 +688,65 @@ What are the archived variables for?
total_sleeping_agent += G.private_sleeping_agent
total_agent_b += G.private_agent_b
- if(total_volume > 0)
- //Calculate temperature
- var/temperature = 0
-
- if(total_heat_capacity > 0)
- temperature = total_thermal_energy/total_heat_capacity
+ if(total_volume <= 0)
+ return
- //Update individual gas_mixtures by volume ratio
+ // If we don't have a significant temperature difference, check for a significant gas amount difference.
+ if(!must_share)
for(var/datum/gas_mixture/G as anything in mixtures)
if(!istype(G))
continue
- G.private_oxygen = total_oxygen * G.volume / total_volume
- G.private_nitrogen = total_nitrogen * G.volume / total_volume
- G.private_toxins = total_toxins * G.volume / total_volume
- G.private_carbon_dioxide = total_carbon_dioxide * G.volume / total_volume
- G.private_sleeping_agent = total_sleeping_agent * G.volume / total_volume
- G.private_agent_b = total_agent_b * G.volume / total_volume
-
- G.private_temperature = temperature
- G.set_dirty()
+ if(abs(G.private_oxygen - total_oxygen * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+ if(abs(G.private_nitrogen - total_nitrogen * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+ if(abs(G.private_toxins - total_toxins * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+ if(abs(G.private_carbon_dioxide - total_carbon_dioxide * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+ if(abs(G.private_sleeping_agent - total_sleeping_agent * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+ if(abs(G.private_agent_b - total_agent_b * G.volume / total_volume) > 0.1)
+ must_share = TRUE
+ break
+
+ if(!must_share)
+ // Nothing significant, don't do any more work.
+ return
+
+ // Collect the more expensive data.
+ var/total_thermal_energy = 0
+ var/total_heat_capacity = 0
+ for(var/datum/gas_mixture/G as anything in mixtures)
+ if(!istype(G))
+ continue
+ var/heat_capacity = G.heat_capacity()
+ total_heat_capacity += heat_capacity
+ total_thermal_energy += G.private_temperature * heat_capacity
+
+ // Calculate shared temperature.
+ temperature = TCMB
+ if(total_heat_capacity > 0)
+ temperature = total_thermal_energy/total_heat_capacity
+
+ // Update individual gas_mixtures by volume ratio.
+ for(var/datum/gas_mixture/G as anything in mixtures)
+ if(!istype(G))
+ continue
+ G.private_oxygen = total_oxygen * G.volume / total_volume
+ G.private_nitrogen = total_nitrogen * G.volume / total_volume
+ G.private_toxins = total_toxins * G.volume / total_volume
+ G.private_carbon_dioxide = total_carbon_dioxide * G.volume / total_volume
+ G.private_sleeping_agent = total_sleeping_agent * G.volume / total_volume
+ G.private_agent_b = total_agent_b * G.volume / total_volume
+
+ G.private_temperature = temperature
+ // In theory, we should G.set_dirty() here, but that's only useful for bound mixtures, and these can't be.
/datum/gas_mixture/proc/hotspot_expose(temperature, volume)
return
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 87fd428c61dc..795772f5d1a6 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -222,6 +222,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
set_pixel_offsets_from_dir(24, -24, 24, -24)
GLOB.air_alarms += src
+ alarm_area.air_alarms += src
if(!mapload)
GLOB.air_alarms = sortAtom(GLOB.air_alarms)
@@ -231,17 +232,12 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
if(!building)
first_run()
- if(!master_is_operating())
- elect_master()
-
/obj/machinery/alarm/Destroy()
SStgui.close_uis(wires)
GLOB.air_alarms -= src
+ alarm_area.air_alarms -= src
GLOB.air_alarm_repository.update_cache(src)
QDEL_NULL(wires)
- if(alarm_area && alarm_area.master_air_alarm == src)
- alarm_area.master_air_alarm = null
- elect_master(exclude_self = 1)
alarm_area = null
return ..()
@@ -249,24 +245,6 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
apply_preset(AALARM_PRESET_HUMAN) // Don't cycle.
GLOB.air_alarm_repository.update_cache(src)
-/obj/machinery/alarm/proc/master_is_operating()
- if(!alarm_area)
- alarm_area = get_area(src)
- if(!alarm_area)
- . = FALSE
- CRASH("Air alarm /obj/machinery/alarm lacks alarm_area vars during proc/master_is_operating()")
- return alarm_area.master_air_alarm && !(alarm_area.master_air_alarm.stat & (NOPOWER|BROKEN))
-
-
-/obj/machinery/alarm/proc/elect_master(exclude_self = 0) //Why is this an alarm and not area proc?
- for(var/obj/machinery/alarm/AA in alarm_area)
- if(exclude_self && AA == src)
- continue
- if(!(AA.stat & (NOPOWER|BROKEN)))
- alarm_area.master_air_alarm = AA
- return 1
- return 0
-
/obj/machinery/alarm/process()
if((stat & (NOPOWER|BROKEN)) || shorted || buildstage != 2)
return
@@ -275,8 +253,9 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
if(!istype(location))
return 0
- var/datum/milla_safe/airalarm_heat_cool/milla = new()
- milla.invoke_async(src)
+ if(thermostat_state)
+ var/datum/milla_safe/airalarm_heat_cool/milla = new()
+ milla.invoke_async(src)
var/datum/gas_mixture/environment = location.get_readonly_air()
var/GET_PP = R_IDEAL_GAS_EQUATION * environment.temperature() / environment.volume
@@ -342,8 +321,6 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
var/turf/location = get_turf(alarm)
var/datum/gas_mixture/environment = get_turf_air(location)
- if(!alarm.thermostat_state)
- return
var/datum/tlv/cur_tlv = alarm.TLV["temperature"]
//Handle temperature adjustment here.
if(environment.temperature() < alarm.target_temperature - 2 || environment.temperature() > alarm.target_temperature + 2 || alarm.regulating_temperature)
@@ -674,7 +651,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return data
/obj/machinery/alarm/proc/has_rcon_access(mob/user)
- return user && (isAI(user) || allowed(user) || emagged || rcon_setting == RCON_YES)
+ return user && (is_ai(user) || allowed(user) || emagged || rcon_setting == RCON_YES)
// Intentional nulls here
/obj/machinery/alarm/ui_data(mob/user)
@@ -796,7 +773,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return thresholds
/obj/machinery/alarm/ui_state(mob/user)
- if(isAI(user))
+ if(is_ai(user))
var/mob/living/silicon/ai/AI = user
if(!AI.lacks_power() || AI.apc_override)
return GLOB.always_state
@@ -821,7 +798,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return TRUE
if(user.can_admin_interact())
return TRUE
- else if(isAI(user) || isrobot(user) || emagged || user.has_unlimited_silicon_privilege)
+ else if(is_ai(user) || isrobot(user) || emagged || user.has_unlimited_silicon_privilege)
return TRUE
else
return !locked
@@ -830,7 +807,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
if(buildstage != 2)
return UI_CLOSE
- if(aidisabled && (isAI(user) || isrobot(user)))
+ if(aidisabled && (is_ai(user) || isrobot(user)))
to_chat(user, "AI control for \the [src] interface has been disabled.")
return UI_CLOSE
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/unary_base.dm b/code/modules/atmospherics/machinery/components/unary_devices/unary_base.dm
index a831588f4014..dbd529be80db 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/unary_base.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/unary_base.dm
@@ -94,21 +94,14 @@
if(Old == parent)
parent = New
-/obj/machinery/atmospherics/unary/unsafe_pressure_release(mob/user, pressures)
+/obj/machinery/atmospherics/unary/unsafe_pressure_release(mob/user, pressure)
..()
var/turf/T = get_turf(src)
- if(T)
- var/datum/milla_safe/unary_unsafe_pressure_release/milla = new()
- milla.invoke_async(src, pressures)
-
-/datum/milla_safe/unary_unsafe_pressure_release
+ if(!T)
+ return
-/datum/milla_safe/unary_unsafe_pressure_release/on_run(obj/machinery/atmospherics/unary/device, pressures)
- //Remove the gas from air_contents and assume it
- var/turf/T = get_turf(device)
- var/datum/gas_mixture/environment = get_turf_air(T)
- var/lost = pressures * environment.volume / (device.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
+ var/lost = pressure * CELL_VOLUME / (air_contents.temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/to_release = device.air_contents.remove(lost)
- environment.merge(to_release)
+ var/datum/gas_mixture/to_release = air_contents.remove(lost)
+ T.blind_release_air(to_release)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index 2743bfdd7f92..412bf1b2e24a 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -108,69 +108,71 @@
update_underlays()
/obj/machinery/atmospherics/unary/vent_pump/process_atmos()
- var/datum/milla_safe/vent_pump_process/milla = new()
- milla.invoke_async(src)
-
-/datum/milla_safe/vent_pump_process
-
-/datum/milla_safe/vent_pump_process/on_run(obj/machinery/atmospherics/unary/vent_pump/vent_pump)
- if(vent_pump.stat & (NOPOWER|BROKEN))
+ if(stat & (NOPOWER|BROKEN))
return FALSE
- if(QDELETED(vent_pump.parent))
+ if(QDELETED(parent))
// We're orphaned!
return FALSE
- var/turf/T = get_turf(vent_pump)
+ var/turf/T = get_turf(src)
if(T.density) //No, you should not be able to get free air from walls
return
- if(!vent_pump.node)
- vent_pump.on = FALSE
- if(!vent_pump.on)
+ if(!node)
+ on = FALSE
+ if(!on)
return FALSE
- if(vent_pump.welded)
- if(vent_pump.air_contents.return_pressure() >= vent_pump.weld_burst_pressure && prob(5)) //the weld is on but the cover is welded shut, can it withstand the internal pressure?
- vent_pump.visible_message("The welded cover of [vent_pump] bursts open!")
+ if(welded)
+ if(air_contents.return_pressure() >= weld_burst_pressure && prob(5)) //the weld is on but the cover is welded shut, can it withstand the internal pressure?
+ visible_message("The welded cover of [src] bursts open!")
for(var/mob/living/M in range(1))
- vent_pump.unsafe_pressure_release(M, vent_pump.air_contents.return_pressure()) //let's send everyone flying
- vent_pump.welded = FALSE
- vent_pump.update_icon()
+ unsafe_pressure_release(M, air_contents.return_pressure()) //let's send everyone flying
+ welded = FALSE
+ update_icon()
return FALSE
- var/datum/gas_mixture/environment = get_turf_air(T)
- var/environment_pressure = environment.return_pressure()
- if(vent_pump.releasing) //internal -> external
+ var/datum/gas_mixture/environment = T.get_readonly_air()
+ if(releasing) //internal -> external
var/pressure_delta = 10000
- if(vent_pump.pressure_checks == ONLY_CHECK_EXT_PRESSURE)
+ if(pressure_checks == ONLY_CHECK_EXT_PRESSURE)
// Only checks difference between set pressure and environment pressure
- pressure_delta = min(pressure_delta, (vent_pump.external_pressure_bound - environment_pressure))
- if(vent_pump.pressure_checks == ONLY_CHECK_INT_PRESSURE)
- pressure_delta = min(pressure_delta, (vent_pump.air_contents.return_pressure() - vent_pump.internal_pressure_bound))
+ pressure_delta = min(pressure_delta, (external_pressure_bound - environment.return_pressure()))
+ if(pressure_checks == ONLY_CHECK_INT_PRESSURE)
+ pressure_delta = min(pressure_delta, (air_contents.return_pressure() - internal_pressure_bound))
- if(pressure_delta > 0.5 && vent_pump.air_contents.temperature() > 0)
+ if(pressure_delta > 0.5 && air_contents.temperature() > 0)
// 1kPa * 1L = 1J
var/wanted_joules = pressure_delta * environment.volume
- var/transfer_moles = min(vent_pump.max_transfer_joules, wanted_joules) / (vent_pump.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = vent_pump.air_contents.remove(transfer_moles)
- environment.merge(removed)
- vent_pump.parent.update = TRUE
+ var/transfer_moles = min(max_transfer_joules, wanted_joules) / (air_contents.temperature() * R_IDEAL_GAS_EQUATION)
+ var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
+ // This isn't exactly "blind", but using the data from last tick is good enough for a vent.
+ T.blind_release_air(removed)
+ parent.update = TRUE
else //external -> internal
- var/pressure_delta = 10000
- if(vent_pump.pressure_checks == ONLY_CHECK_EXT_PRESSURE)
- pressure_delta = min(pressure_delta, (environment_pressure - vent_pump.external_pressure_bound))
- if(vent_pump.pressure_checks == ONLY_CHECK_INT_PRESSURE)
- pressure_delta = min(pressure_delta, (vent_pump.internal_pressure_bound - vent_pump.air_contents.return_pressure()))
-
- if(pressure_delta > 0.5 && environment.temperature() > 0)
- // 1kPa * 1L = 1J
- var/wanted_joules = pressure_delta * environment.volume
- var/transfer_moles = min(vent_pump.max_transfer_joules, wanted_joules) / (environment.temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = environment.remove(transfer_moles)
- vent_pump.air_contents.merge(removed)
- vent_pump.parent.update = TRUE
+ var/datum/milla_safe/vent_pump_siphon/milla = new()
+ milla.invoke_async(src)
return TRUE
+/datum/milla_safe/vent_pump_siphon
+
+/datum/milla_safe/vent_pump_siphon/on_run(obj/machinery/atmospherics/unary/vent_pump/vent_pump)
+ var/turf/T = get_turf(vent_pump)
+ var/datum/gas_mixture/environment = get_turf_air(T)
+ var/pressure_delta = 10000
+ if(vent_pump.pressure_checks == ONLY_CHECK_EXT_PRESSURE)
+ pressure_delta = min(pressure_delta, (environment.return_pressure() - vent_pump.external_pressure_bound))
+ if(vent_pump.pressure_checks == ONLY_CHECK_INT_PRESSURE)
+ pressure_delta = min(pressure_delta, (vent_pump.internal_pressure_bound - vent_pump.air_contents.return_pressure()))
+
+ if(pressure_delta > 0.5 && environment.temperature() > 0)
+ // 1kPa * 1L = 1J
+ var/wanted_joules = pressure_delta * environment.volume
+ var/transfer_moles = min(vent_pump.max_transfer_joules, wanted_joules) / (environment.temperature() * R_IDEAL_GAS_EQUATION)
+ var/datum/gas_mixture/removed = environment.remove(transfer_moles)
+ vent_pump.air_contents.merge(removed)
+ vent_pump.parent.update = TRUE
+
/obj/machinery/atmospherics/unary/vent_pump/can_crawl_through()
return !welded
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
index 3066b5308b01..70b00ea21618 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
@@ -140,9 +140,31 @@
adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir=1)
/obj/machinery/atmospherics/unary/vent_scrubber/proc/scrub(turf/simulated/tile)
+ if(!tile || !istype(tile))
+ return 0
+
+ if(scrubbing && !should_scrub(tile.get_readonly_air()))
+ return 0
+
var/datum/milla_safe/vent_scrubber_process/milla = new()
milla.invoke_async(src, tile)
+/obj/machinery/atmospherics/unary/vent_scrubber/proc/should_scrub(datum/gas_mixture/environment)
+ if(scrub_O2 && environment.oxygen() > 0.001)
+ return TRUE
+ if(scrub_N2 && environment.nitrogen() > 0.001)
+ return TRUE
+ if(scrub_CO2 && environment.carbon_dioxide() > 0.001)
+ return TRUE
+ if(scrub_Toxins && environment.toxins() > 0.001)
+ return TRUE
+ if(environment.sleeping_agent() > 0.001)
+ return TRUE
+ if(environment.agent_b() > 0.001)
+ return TRUE
+
+ return FALSE
+
/datum/milla_safe/vent_scrubber_process
/datum/milla_safe/vent_scrubber_process/on_run(obj/machinery/atmospherics/unary/vent_scrubber/scrubber, turf/simulated/tile)
@@ -152,7 +174,7 @@
var/datum/gas_mixture/environment = get_turf_air(tile)
if(scrubber.scrubbing)
- if((scrubber.scrub_O2 && environment.oxygen() > 0.001) || (scrubber.scrub_N2 && environment.nitrogen() > 0.001) || (scrubber.scrub_CO2 && environment.carbon_dioxide() > 0.001) || (scrubber.scrub_Toxins && environment.toxins() > 0.001) || (environment.sleeping_agent()) || (environment.agent_b()))
+ if(scrubber.should_scrub(environment))
var/transfer_moles = min(1, scrubber.volume_rate / environment.volume) * environment.total_moles()
//Take a gas sample
diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm
index 0f6deb15046b..c485f4afda28 100644
--- a/code/modules/atmospherics/machinery/other/meter.dm
+++ b/code/modules/atmospherics/machinery/other/meter.dm
@@ -61,7 +61,7 @@ GLOBAL_LIST_EMPTY(gas_meters)
/obj/machinery/atmospherics/meter/examine(mob/user)
. = ..()
. += "Measures the volume and temperature of the pipe under the meter."
- if(get_dist(user, src) > 3 && !(isAI(user) || istype(user, /mob/dead)))
+ if(get_dist(user, src) > 3 && !(is_ai(user) || istype(user, /mob/dead)))
. += "You are too far away to read it."
else if(stat & (NOPOWER|BROKEN))
@@ -77,7 +77,7 @@ GLOBAL_LIST_EMPTY(gas_meters)
. += "The connect error light is blinking."
/obj/machinery/atmospherics/meter/Click()
- if(isAI(usr)) // ghosts can call ..() for examine
+ if(is_ai(usr)) // ghosts can call ..() for examine
usr.examinate(src)
return TRUE
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index 0d31cf0572f0..e8023b2a3ba7 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -210,41 +210,41 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
/obj/machinery/atmospherics/portable/canister/process_atmos()
..()
sync_pressure_appearance()
- var/datum/milla_safe/canister_process/milla = new()
- milla.invoke_async(src)
+ if(stat & BROKEN)
+ return
-/datum/milla_safe/canister_process
+ if(valve_open)
+ var/datum/milla_safe/canister_release/milla = new()
+ milla.invoke_async(src)
-/datum/milla_safe/canister_process/on_run(obj/machinery/atmospherics/portable/canister/canister)
- if(canister.stat & BROKEN)
- return
+ if(air_contents.return_pressure() < 1)
+ can_label = TRUE
+ else
+ can_label = FALSE
- if(canister.valve_open)
- var/datum/gas_mixture/environment
- if(canister.holding_tank)
- environment = canister.holding_tank.air_contents
- else
- var/turf/T = get_turf(canister)
- environment = get_turf_air(T)
+/datum/milla_safe/canister_release
- var/env_pressure = environment.return_pressure()
- var/pressure_delta = min(canister.release_pressure - env_pressure, (canister.air_contents.return_pressure() - env_pressure) / 2)
- //Can not have a pressure delta that would cause environment pressure > tank pressure
+/datum/milla_safe/canister_release/on_run(obj/machinery/atmospherics/portable/canister/canister)
+ var/datum/gas_mixture/environment
+ if(canister.holding_tank)
+ environment = canister.holding_tank.air_contents
+ else
+ var/turf/T = get_turf(canister)
+ environment = get_turf_air(T)
- var/transfer_moles = 0
- if((canister.air_contents.temperature() > 0) && (pressure_delta > 0))
- transfer_moles = pressure_delta * environment.volume / (canister.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
+ var/env_pressure = environment.return_pressure()
+ var/pressure_delta = min(canister.release_pressure - env_pressure, (canister.air_contents.return_pressure() - env_pressure) / 2)
+ //Can not have a pressure delta that would cause environment pressure > tank pressure
- //Actually transfer the gas
- var/datum/gas_mixture/removed = canister.air_contents.remove(transfer_moles)
+ var/transfer_moles = 0
+ if((canister.air_contents.temperature() > 0) && (pressure_delta > 0))
+ transfer_moles = pressure_delta * environment.volume / (canister.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
- environment.merge(removed)
- canister.sync_pressure_appearance()
+ //Actually transfer the gas
+ var/datum/gas_mixture/removed = canister.air_contents.remove(transfer_moles)
- if(canister.air_contents.return_pressure() < 1)
- canister.can_label = TRUE
- else
- canister.can_label = FALSE
+ environment.merge(removed)
+ canister.sync_pressure_appearance()
/obj/machinery/atmospherics/portable/canister/return_obj_air()
RETURN_TYPE(/datum/gas_mixture)
diff --git a/code/modules/atmospherics/machinery/portable/portable_pump.dm b/code/modules/atmospherics/machinery/portable/portable_pump.dm
index 8821a9331033..9adab28c93cc 100644
--- a/code/modules/atmospherics/machinery/portable/portable_pump.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_pump.dm
@@ -61,44 +61,44 @@
/obj/machinery/atmospherics/portable/pump/process_atmos()
..()
- var/datum/milla_safe/portable_pump_process/milla = new()
- milla.invoke_async(src)
+ if(on)
+ var/datum/milla_safe/portable_pump_process/milla = new()
+ milla.invoke_async(src)
/datum/milla_safe/portable_pump_process
/datum/milla_safe/portable_pump_process/on_run(obj/machinery/atmospherics/portable/pump/pump)
- if(pump.on)
- var/datum/gas_mixture/environment
- if(pump.holding_tank)
- environment = pump.holding_tank.air_contents
- else
- var/turf/T = get_turf(pump)
- environment = get_turf_air(T)
- if(pump.direction == DIRECTION_OUT)
- var/pressure_delta = pump.target_pressure - environment.return_pressure()
- //Can not have a pressure delta that would cause environment pressure > tank pressure
-
- var/transfer_moles = 0
- if(pump.air_contents.temperature() > 0)
- transfer_moles = pressure_delta*environment.volume/(pump.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
-
- //Actually transfer the gas
- var/datum/gas_mixture/removed = pump.air_contents.remove(transfer_moles)
-
- environment.merge(removed)
- else
- var/pressure_delta = pump.target_pressure - pump.air_contents.return_pressure()
- //Can not have a pressure delta that would cause environment pressure > tank pressure
-
- var/transfer_moles = 0
- if(environment.temperature() > 0)
- transfer_moles = pressure_delta*pump.air_contents.volume/(environment.temperature() * R_IDEAL_GAS_EQUATION)
-
- //Actually transfer the gas
- var/datum/gas_mixture/removed
- removed = environment.remove(transfer_moles)
-
- pump.air_contents.merge(removed)
+ var/datum/gas_mixture/environment
+ if(pump.holding_tank)
+ environment = pump.holding_tank.air_contents
+ else
+ var/turf/T = get_turf(pump)
+ environment = get_turf_air(T)
+ if(pump.direction == DIRECTION_OUT)
+ var/pressure_delta = pump.target_pressure - environment.return_pressure()
+ //Can not have a pressure delta that would cause environment pressure > tank pressure
+
+ var/transfer_moles = 0
+ if(pump.air_contents.temperature() > 0)
+ transfer_moles = pressure_delta*environment.volume/(pump.air_contents.temperature() * R_IDEAL_GAS_EQUATION)
+
+ //Actually transfer the gas
+ var/datum/gas_mixture/removed = pump.air_contents.remove(transfer_moles)
+
+ environment.merge(removed)
+ else
+ var/pressure_delta = pump.target_pressure - pump.air_contents.return_pressure()
+ //Can not have a pressure delta that would cause environment pressure > tank pressure
+
+ var/transfer_moles = 0
+ if(environment.temperature() > 0)
+ transfer_moles = pressure_delta*pump.air_contents.volume/(environment.temperature() * R_IDEAL_GAS_EQUATION)
+
+ //Actually transfer the gas
+ var/datum/gas_mixture/removed
+ removed = environment.remove(transfer_moles)
+
+ pump.air_contents.merge(removed)
/obj/machinery/atmospherics/portable/pump/return_obj_air()
RETURN_TYPE(/datum/gas_mixture)
diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm
index df888f2dcceb..71e1af7f9b76 100644
--- a/code/modules/atmospherics/machinery/portable/scrubber.dm
+++ b/code/modules/atmospherics/machinery/portable/scrubber.dm
@@ -46,18 +46,18 @@
/obj/machinery/atmospherics/portable/scrubber/process_atmos()
..()
- var/datum/milla_safe/portable_scrubber_process/milla = new()
- milla.invoke_async(src)
-
-/datum/milla_safe/portable_scrubber_process
-
-/datum/milla_safe/portable_scrubber_process/on_run(obj/machinery/atmospherics/portable/scrubber/scrubber)
- if(!scrubber.on)
+ if(!on)
return
- if(scrubber.holding_tank)
- scrubber.scrub(scrubber.holding_tank.air_contents)
+ if(holding_tank)
+ scrub(holding_tank.air_contents)
return
+ var/datum/milla_safe/portable_scrubber_scrub/milla = new()
+ milla.invoke_async(src)
+
+/datum/milla_safe/portable_scrubber_scrub
+
+/datum/milla_safe/portable_scrubber_scrub/on_run(obj/machinery/atmospherics/portable/scrubber/scrubber)
var/turf/T = get_turf(scrubber)
scrubber.scrub(get_turf_air(T))
if(scrubber.widenet)
diff --git a/code/modules/client/preference/loadout/loadout_glasses.dm b/code/modules/client/preference/loadout/loadout_glasses.dm
index af40da04e6ed..b2535f98dbec 100644
--- a/code/modules/client/preference/loadout/loadout_glasses.dm
+++ b/code/modules/client/preference/loadout/loadout_glasses.dm
@@ -39,7 +39,7 @@
/datum/gear/glasses/sechud
display_name = "Classic security HUD"
path = /obj/item/clothing/glasses/hud/security
- allowed_roles = list("Head of Security", "Warden", "Security Officer", "Internal Affairs Agent","Magistrate")
+ allowed_roles = list("Head of Security", "Warden", "Security Officer", "Detective", "Internal Affairs Agent","Magistrate")
/datum/gear/glasses/goggles
display_name = "Goggles"
@@ -52,7 +52,7 @@
/datum/gear/glasses/goggles_job/sechudgoggles
display_name = "Security HUD goggles"
path = /obj/item/clothing/glasses/hud/security/goggles
- allowed_roles = list("Head of Security", "Warden", "Security Officer", "Internal Affairs Agent", "Magistrate")
+ allowed_roles = list("Head of Security", "Warden", "Security Officer", "Detective", "Internal Affairs Agent", "Magistrate")
/datum/gear/glasses/goggles_job/medhudgoggles
display_name = "Health HUD goggles"
diff --git a/code/modules/client/preference/loadout/loadout_hat.dm b/code/modules/client/preference/loadout/loadout_hat.dm
index 6d3b7e84575f..b40f4c916ff4 100644
--- a/code/modules/client/preference/loadout/loadout_hat.dm
+++ b/code/modules/client/preference/loadout/loadout_hat.dm
@@ -70,12 +70,12 @@
/datum/gear/hat/capcsec
display_name = "Security cap, corporate"
path = /obj/item/clothing/head/soft/sec/corp
- allowed_roles = list("Head of Security", "Warden", "Security Officer")
+ allowed_roles = list("Head of Security", "Warden", "Security Officer", "Detective")
/datum/gear/hat/capsec
display_name = "Security cap"
path = /obj/item/clothing/head/soft/sec
- allowed_roles = list("Head of Security", "Warden", "Security Officer")
+ allowed_roles = list("Head of Security", "Warden", "Security Officer", "Detective")
/datum/gear/hat/capjanigrey
display_name = "Cap, janitor grey"
@@ -146,7 +146,7 @@
/datum/gear/hat/cowboyhat/sec
display_name = "Cowboy hat, security"
path = /obj/item/clothing/head/cowboyhat/sec
- allowed_roles = list("Head of Security", "Warden", "Security Officer")
+ allowed_roles = list("Head of Security", "Warden", "Security Officer", "Detective")
/datum/gear/hat/beret_purple
display_name = "Beret, purple"
diff --git a/code/modules/client/preference/loadout/loadout_uniform.dm b/code/modules/client/preference/loadout/loadout_uniform.dm
index c5dc635a57ce..3821188bf446 100644
--- a/code/modules/client/preference/loadout/loadout_uniform.dm
+++ b/code/modules/client/preference/loadout/loadout_uniform.dm
@@ -390,12 +390,12 @@
/datum/gear/uniform/sec/secorporate
display_name = "Security uniform, corporate"
path = /obj/item/clothing/under/rank/security/officer/corporate
- allowed_roles = list("Head of Security", "Warden", "Security Officer")
+ allowed_roles = list("Head of Security", "Warden", "Detective", "Security Officer")
/datum/gear/uniform/sec/dispatch
display_name = "Security uniform, dispatch"
path = /obj/item/clothing/under/rank/security/officer/dispatch
- allowed_roles = list("Head of Security", "Warden", "Security Officer")
+ allowed_roles = list("Head of Security", "Warden", "Detective", "Security Officer")
/datum/gear/uniform/sec/casual
display_name = "Security uniform, casual"
diff --git a/code/modules/clothing/suits/misc_suits.dm b/code/modules/clothing/suits/misc_suits.dm
index d1f6402e34d6..c08071b58812 100644
--- a/code/modules/clothing/suits/misc_suits.dm
+++ b/code/modules/clothing/suits/misc_suits.dm
@@ -204,7 +204,7 @@
if(!M.anchored && (M.flags & CONDUCT))
step_towards(M,src)
for(var/mob/living/silicon/S in orange(2,src))
- if(isAI(S)) continue
+ if(is_ai(S)) continue
step_towards(S,src)
for(var/mob/living/carbon/human/machine/M in orange(2,src))
step_towards(M,src)
diff --git a/code/modules/economy/economy_machinery/economy_machinery.dm b/code/modules/economy/economy_machinery/economy_machinery.dm
index 31a7a1fb79d9..ac7b999a8ed7 100644
--- a/code/modules/economy/economy_machinery/economy_machinery.dm
+++ b/code/modules/economy/economy_machinery/economy_machinery.dm
@@ -33,10 +33,10 @@
/obj/machinery/economy/proc/attempt_account_authentification(datum/money_account/customer_account, attempted_pin, mob/user)
var/attempt_pin = attempted_pin
- if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempted_pin)
- //if pin is not given, we'll prompt them here
- attempt_pin = input("Enter pin code", "Vendor transaction") as num
- if(!Adjacent(user))
+ if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempt_pin)
+ // if pin is not given, we'll prompt them here
+ attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction", max_value = BANK_PIN_MAX, min_value = BANK_PIN_MIN)
+ if(!Adjacent(user) || !attempt_pin)
return FALSE
var/is_admin = is_admin(user)
if(!account_database.try_authenticate_login(customer_account, attempt_pin, restricted_bypass, FALSE, is_admin))
diff --git a/code/modules/economy/economy_machinery/eftpos.dm b/code/modules/economy/economy_machinery/eftpos.dm
index c28cda4a1e97..ef7dd5c83007 100644
--- a/code/modules/economy/economy_machinery/eftpos.dm
+++ b/code/modules/economy/economy_machinery/eftpos.dm
@@ -167,7 +167,7 @@
//if security level high enough, prompt for pin
var/attempt_pin
if(D.security_level != ACCOUNT_SECURITY_ID)
- attempt_pin = tgui_input_number(user, "Enter pin code", "EFTPOS transaction", max_value = 9999, min_value = 1000)
+ attempt_pin = tgui_input_number(user, "Enter pin code", "EFTPOS transaction", max_value = BANK_PIN_MAX, min_value = BANK_PIN_MIN)
if(!attempt_pin || !Adjacent(user))
return
//given the credentials, can the associated account be accessed right now?
diff --git a/code/modules/economy/money_account.dm b/code/modules/economy/money_account.dm
index 2202838ac06c..8a8faf13f442 100644
--- a/code/modules/economy/money_account.dm
+++ b/code/modules/economy/money_account.dm
@@ -47,7 +47,7 @@
security_level = _security_level
account_number = SSeconomy.generate_account_number()
account_type = _account_type
- account_pin = rand(10000, 99999)
+ account_pin = rand(BANK_PIN_MIN, BANK_PIN_MAX) // defines are currently housed in misc_defines.dm
//update SSeconomy stats
SSeconomy.total_space_credits += starting_balance
diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm
index de6518137768..f4f83edc12f5 100644
--- a/code/modules/events/dust.dm
+++ b/code/modules/events/dust.dm
@@ -73,7 +73,7 @@
return
if(prob(shake_chance))
for(var/mob/M in range(10, src))
- if(!M.stat && !isAI(M))
+ if(!M.stat && !is_ai(M))
shake_camera(M, 3, 1)
playsound(loc, 'sound/effects/meteorimpact.ogg', 40, 1)
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 63e2a1f78567..9bc6779e62dd 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -32,6 +32,10 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
var/move_delay = 1
var/atom/start
var/atom/end
+ /// The minimum amount of damage dealt to walls, relative to their max HP.
+ var/wall_damage_min_fraction = 0.9
+ /// The maximum amount of damage dealt to walls, relative to their max HP. Values over 1 are useful for adjusting the probability of destroying the wall.
+ var/wall_damage_max_fraction = 1.4
/obj/effect/immovablerod/New(atom/_start, atom/_end, delay)
. = ..()
@@ -84,8 +88,20 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
audible_message("CLANG")
clong_turf(newloc)
- for(var/atom/victim as anything in newloc)
- clong_thing(victim)
+ if(isnull(newloc))
+ // The turf is dead, long live the turf!
+ newloc = loc
+
+ while(TRUE)
+ var/hit_something_dense = FALSE
+ for(var/atom/victim as anything in newloc)
+ clong_thing(victim)
+ if(victim.density)
+ hit_something_dense = TRUE
+
+ // Keep hitting stuff until there's nothing dense or we randomly go through it.
+ if(!hit_something_dense || prob(25))
+ break
/obj/effect/immovablerod/proc/clong_turf(turf/victim)
if(!victim.density)
@@ -93,7 +109,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
if(iswallturf(victim))
var/turf/simulated/wall/W = victim
- W.take_damage(rand(W.damage_cap / 3, W.damage_cap * 4 / 3))
+ W.take_damage(rand(W.damage_cap * wall_damage_min_fraction, W.damage_cap * wall_damage_max_fraction))
else
victim.ex_act(EXPLODE_LIGHT)
@@ -111,9 +127,13 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
victim.ex_act(EXPLODE_HEAVY)
/obj/effect/immovablerod/event
+ wall_damage_min_fraction = 0.33
+ wall_damage_max_fraction = 1.33
// The base chance to "damage" the floor when passing. This is not guaranteed to cause a full on hull breach.
- // Chance to expose the tile to space is like 15% of this value.
+ // Chance to expose the tile to space is like 60% of this value.
var/floor_rip_chance = 40
+ // Chance to damage the floor if we didn't rip it.
+ var/floor_graze_chance = 50
/obj/effect/immovablerod/event/Move()
. = ..()
@@ -124,13 +144,10 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
if(!isfloorturf(victim))
return ..()
- if(!prob(floor_rip_chance))
- return
-
var/turf/simulated/floor/T = victim
- if(prob(25))
+ if(prob(floor_rip_chance))
T.ex_act(EXPLODE_HEAVY)
- else
+ else if(prob(floor_graze_chance))
T.ex_act(EXPLODE_LIGHT)
/obj/effect/immovablerod/deadchat_plays(mode = DEADCHAT_DEMOCRACY_MODE, cooldown = 6 SECONDS)
diff --git a/code/modules/fish/fishtank.dm b/code/modules/fish/fishtank.dm
index a5fb9ffed0d9..eaa21b7dc702 100644
--- a/code/modules/fish/fishtank.dm
+++ b/code/modules/fish/fishtank.dm
@@ -567,7 +567,7 @@
return ..()
/obj/machinery/fishtank/attack_hand(mob/user)
- if(isAI(user))
+ if(is_ai(user))
return
user.changeNext_move(CLICK_CD_MELEE)
playsound(get_turf(src), 'sound/effects/glassknock.ogg', 80, TRUE)
diff --git a/code/modules/library/library_equipment.dm b/code/modules/library/library_equipment.dm
index 38b9038220e4..33f2180b308e 100644
--- a/code/modules/library/library_equipment.dm
+++ b/code/modules/library/library_equipment.dm
@@ -10,7 +10,7 @@
/obj/structure/bookcase
name = "bookcase"
icon = 'icons/obj/library.dmi'
- icon_state = "bookshelf-0"
+ icon_state = "bookshelf"
anchored = TRUE
density = TRUE
opacity = TRUE
@@ -18,6 +18,7 @@
max_integrity = 200
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, RAD = 0, FIRE = 50, ACID = 0)
var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/bible, /obj/item/tome) //Things allowed in the bookcase
+ var/material_type = /obj/item/stack/sheet/wood
/obj/structure/bookcase/Initialize(mapload)
. = ..()
@@ -29,14 +30,14 @@
for(var/obj/item/I in get_turf(src))
if(is_type_in_list(I, allowed_books))
I.forceMove(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/attackby__legacy__attackchain(obj/item/O, mob/user)
if(is_type_in_list(O, allowed_books))
if(!user.drop_item())
return
O.forceMove(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
return TRUE
if(istype(O, /obj/item/storage/bag/books))
var/obj/item/storage/bag/books/B = O
@@ -44,7 +45,7 @@
if(is_type_in_list(T, allowed_books))
B.remove_from_storage(T, src)
to_chat(user, "You empty [O] into [src].")
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
return TRUE
if(is_pen(O))
rename_interactive(user, O)
@@ -65,17 +66,19 @@
user.put_in_hands(choice)
else
choice.forceMove(get_turf(src))
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/wood(loc, 5)
+ new material_type(get_turf(src), 5)
for(var/obj/item/I in contents)
if(is_type_in_list(I, allowed_books))
I.forceMove(get_turf(src))
..()
-/obj/structure/bookcase/update_icon_state()
- icon_state = "bookshelf-[min(length(contents), 5)]"
+/obj/structure/bookcase/update_overlays()
+ . = ..()
+ if(length(contents))
+ . += "[icon_state]-[min(length(contents), 5)]"
/obj/structure/bookcase/screwdriver_act(mob/user, obj/item/I)
@@ -101,7 +104,7 @@
/obj/structure/bookcase/manuals/medical/Initialize(mapload)
. = ..()
new /obj/item/book/manual/medical_cloning(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/manuals/engineering
@@ -115,7 +118,7 @@
new /obj/item/book/manual/wiki/engineering_guide(src)
new /obj/item/book/manual/engineering_singularity_safety(src)
new /obj/item/book/manual/wiki/robotics_cyborgs(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/manuals/research_and_development
name = "R&D Manuals bookcase"
@@ -123,7 +126,7 @@
/obj/structure/bookcase/manuals/research_and_development/Initialize(mapload)
. = ..()
new /obj/item/book/manual/research_and_development(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/sop
name = "bookcase (Standard Operating Procedures)"
@@ -139,23 +142,36 @@
new /obj/item/book/manual/wiki/sop_security(src)
new /obj/item/book/manual/wiki/sop_service(src)
new /obj/item/book/manual/wiki/sop_supply(src)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
/obj/structure/bookcase/random
var/category = null
var/book_count = 5
- icon_state = "random_bookcase"
+ icon_state = "random_bookshelf"
anchored = TRUE
/obj/structure/bookcase/random/Initialize(mapload)
. = ..()
addtimer(CALLBACK(src, PROC_REF(load_books)), 0)
+ icon_state = "bookshelf" // to keep random_bookshelf icon for mappers
/obj/structure/bookcase/random/proc/load_books()
var/list/books = GLOB.library_catalog.get_random_book(book_count)
for(var/datum/cachedbook/book as anything in books)
new /obj/item/book(src, book, TRUE, FALSE)
- update_icon(UPDATE_ICON_STATE)
+ update_icon(UPDATE_OVERLAYS)
+
+/obj/structure/bookcase/metal
+ icon_state = "bookshelf_metal"
+ material_type = /obj/item/stack/sheet/metal
+
+/obj/structure/bookcase/nt
+ icon_state = "bookshelf_nt"
+ material_type = /obj/item/stack/sheet/metal
+
+/obj/structure/bookcase/military
+ icon_state = "bookshelf_military"
+ material_type = /obj/item/stack/sheet/plasteel
/*
* Book binder
diff --git a/code/modules/mob/living/silicon/ai/freelook/README.md b/code/modules/mob/camera/README.md
similarity index 100%
rename from code/modules/mob/living/silicon/ai/freelook/README.md
rename to code/modules/mob/camera/README.md
diff --git a/code/modules/mob/camera/cameranet.dm b/code/modules/mob/camera/cameranet.dm
new file mode 100644
index 000000000000..8e6277f40452
--- /dev/null
+++ b/code/modules/mob/camera/cameranet.dm
@@ -0,0 +1,160 @@
+// CAMERA NET
+//
+// The datum containing all the chunks.
+GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
+
+/datum/cameranet
+ var/name = "Camera Net"
+
+ /// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Destroy().
+ var/list/cameras = list()
+ /// The chunks of the map, mapping the areas that the cameras can see.
+ var/list/chunks = list()
+ var/ready = FALSE
+
+/// Returns the chunk at (x, y, z) if it exists, otherwise returns null.
+/datum/cameranet/proc/chunk_generated(x, y, z)
+ x &= ~(CAMERA_CHUNK_SIZE - 1)
+ y &= ~(CAMERA_CHUNK_SIZE - 1)
+ var/key = "[x],[y],[z]"
+ return (chunks[key])
+
+/// Returns the chunk at (x, y, z) if it exists, otherwise returns a new chunk at that location.
+/datum/cameranet/proc/get_camera_chunk(x, y, z)
+ x &= ~(CAMERA_CHUNK_SIZE - 1)
+ y &= ~(CAMERA_CHUNK_SIZE - 1)
+ var/key = "[x],[y],[z]"
+ if(!chunks[key])
+ chunks[key] = new /datum/camerachunk(null, x, y, z)
+
+ return chunks[key]
+
+/// Updates what the camera network can see.
+/datum/cameranet/proc/visibility(list/moved_eyes, client/C)
+ if(!islist(moved_eyes))
+ moved_eyes = moved_eyes ? list(moved_eyes) : list()
+
+ var/list/chunks_pre_seen = list()
+ var/list/chunks_post_seen = list()
+
+ for(var/V in moved_eyes)
+ var/mob/camera/eye/eye = V
+ if(C)
+ chunks_pre_seen |= eye.visible_camera_chunks
+ // 0xf = 15
+ var/static_range = eye.static_visibility_range
+ var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
+
+ var/list/visible_chunks = list()
+
+ for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
+ for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
+ visible_chunks |= get_camera_chunk(x, y, eye.z)
+
+ var/list/remove = eye.visible_camera_chunks - visible_chunks
+ var/list/add = visible_chunks - eye.visible_camera_chunks
+
+ for(var/chunk in remove)
+ var/datum/camerachunk/c = chunk
+ c.remove(eye, FALSE)
+
+ for(var/chunk in add)
+ var/datum/camerachunk/c = chunk
+ c.add(eye, FALSE)
+
+ if(C)
+ chunks_post_seen |= eye.visible_camera_chunks
+
+ if(C)
+ var/list/remove = chunks_pre_seen - chunks_post_seen
+ var/list/add = chunks_post_seen - chunks_pre_seen
+
+ for(var/chunk in remove)
+ var/datum/camerachunk/c = chunk
+ C.images -= c.obscured
+
+ for(var/chunk in add)
+ var/datum/camerachunk/c = chunk
+ C.images += c.obscured
+
+
+/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
+/datum/cameranet/proc/update_visibility(atom/A, opacity_check = 1)
+ if(!SSticker || (opacity_check && !A.opacity))
+ return
+ major_chunk_change(update = A)
+
+/// Removes a camera from a chunk.
+/datum/cameranet/proc/remove_camera(obj/machinery/camera/c)
+ major_chunk_change(remove = c)
+
+/// Add a camera to a chunk.
+/datum/cameranet/proc/add_camera(obj/machinery/camera/c)
+ major_chunk_change(add = c)
+
+/// Refreshes the chunk location of a portable camera. Used by Cyborgs.
+/datum/cameranet/proc/update_portable_camera(obj/machinery/camera/c, turf/old_loc)
+ major_chunk_change(add = c, old_loc = old_loc)
+
+/// Private method for updating the chunk an atom is in, and all surrounding chunks.
+/// `add` will be added as a camera to the chunk of its current location and all surrounding chunks.
+/// `remove` will be removed as a camera from the chunk of its current location and all surrounding chunks.
+/// `update` will not be added or removed as a camera, but its surrounding chunks will be updated.
+/// These parameters are mutually exclusive.
+/datum/cameranet/proc/major_chunk_change(atom/add = null, atom/remove = null, atom/update = null, turf/old_loc = null)
+ // 0xf = 15
+ if(!add && !remove && !update)
+ return
+ if(add && remove)
+ CRASH("Adding and removing a camera to the cameranet simultaneously is not implemented")
+
+ var/atom/c = remove
+ if(!c)
+ c = add
+ if(!c)
+ c = update
+
+ var/turf/T = get_turf(c)
+ if(!T)
+ return
+
+ // Check if the turf to add falls in the same chunk as the old_loc. If so, do nothing
+ if(old_loc)
+ if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1))
+ return
+
+ // Use camera view distance here to actually know how far a camera can max watch
+ var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+ var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
+
+ for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
+ for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
+ if(chunk_generated(x, y, T.z))
+ var/datum/camerachunk/chunk = get_camera_chunk(x, y, T.z)
+ if(remove)
+ // Remove the camera.
+ chunk.remove_camera(c)
+ else if(add)
+ // You can't have the same camera in the list twice.
+ chunk.add_camera(c)
+ chunk.has_changed()
+
+/// Returns 1 if a mob is on a viewable turf, otherwise returns 0.
+/datum/cameranet/proc/check_camera_vis(mob/living/target as mob)
+ // 0xf = 15
+ var/turf/position = get_turf(target)
+ return check_turf_vis(position)
+
+/datum/cameranet/proc/check_turf_vis(turf/position)
+ var/datum/camerachunk/chunk = get_camera_chunk(position.x, position.y, position.z)
+ if(chunk)
+ if(chunk.changed)
+ chunk.has_changed(1) // Update now, no matter if it's visible or not.
+ if(chunk.visible_turfs[position])
+ return 1
+ return 0
diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/camera/chunk.dm
similarity index 63%
rename from code/modules/mob/living/silicon/ai/freelook/chunk.dm
rename to code/modules/mob/camera/chunk.dm
index 7deb20f2ef3e..6a0e7ff9c26f 100644
--- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm
+++ b/code/modules/mob/camera/chunk.dm
@@ -1,11 +1,8 @@
-// CAMERA CHUNK
-//
-// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
-// Allows the AI Eye to stream these chunks and know what it can and cannot see.
-
+/// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
+/// Allows camera eyes to stream these chunks and know what they can and cannot see.
/datum/camerachunk
- var/list/obscuredTurfs = list()
- var/list/visibleTurfs = list()
+ var/list/obscured_turfs = list()
+ var/list/visible_turfs = list()
var/list/obscured = list()
var/list/active_cameras = list()
var/list/inactive_cameras = list()
@@ -17,6 +14,7 @@
var/y = 0
var/z = 0
+/// Adds a camera to the chunk if it has not already been added.
/datum/camerachunk/proc/add_camera(obj/machinery/camera/cam)
if(active_cameras[cam] || inactive_cameras[cam])
return
@@ -32,6 +30,7 @@
else
inactive_cameras[cam] = cam
+/// Handles each movement by a camera that has been added to the chunk.
/datum/camerachunk/proc/camera_moved(obj/machinery/camera/cam, atom/old_loc)
var/turf/T = get_turf(cam)
@@ -39,6 +38,7 @@
if(T.x + CAMERA_VIEW_DISTANCE < x || T.x - CAMERA_VIEW_DISTANCE >= x + CAMERA_CHUNK_SIZE || T.y + CAMERA_VIEW_DISTANCE < y || T.y - CAMERA_VIEW_DISTANCE >= y + CAMERA_CHUNK_SIZE || T.z != z)
remove_camera(cam)
+/// Removes a camera from the chunk.
/datum/camerachunk/proc/remove_camera(obj/machinery/camera/cam)
UnregisterSignal(cam, list(COMSIG_CAMERA_OFF, COMSIG_CAMERA_ON, COMSIG_PARENT_QDELETING, COMSIG_CAMERA_MOVED))
active_cameras -= cam
@@ -55,42 +55,40 @@
active_cameras -= cam
SScamera.queue(src)
-// Add an AI eye to the chunk, then update if changed.
-/datum/camerachunk/proc/add(mob/camera/ai_eye/eye, add_images = TRUE)
+/// Add a camera eye to the chunk, then update if changed.
+/datum/camerachunk/proc/add(mob/camera/eye/eye, add_images = TRUE)
if(add_images)
- var/client/client = eye.GetViewerClient()
+ var/client/client = eye.get_viewer_client()
if(client)
client.images += obscured
- eye.visibleCameraChunks += src
+ eye.visible_camera_chunks += src
seenby += eye
- RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(aiEye_destroyed))
+ RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(eye_destroyed))
if(changed)
SScamera.queue(src)
-/datum/camerachunk/proc/aiEye_destroyed(mob/camera/ai_eye/eye)
+/datum/camerachunk/proc/eye_destroyed(mob/camera/eye/eye)
remove(eye, FALSE)
-// Remove an AI eye from the chunk, then update if changed.
-/datum/camerachunk/proc/remove(mob/camera/ai_eye/eye, remove_images = TRUE)
+/// Remove a camera eye from the chunk, then update if changed.
+/datum/camerachunk/proc/remove(mob/camera/eye/eye, remove_images = TRUE)
if(remove_images)
- var/client/client = eye.GetViewerClient()
+ var/client/client = eye.get_viewer_client()
if(client)
client.images -= obscured
- eye.visibleCameraChunks -= src
+ eye.visible_camera_chunks -= src
seenby -= eye
UnregisterSignal(eye, COMSIG_PARENT_QDELETING)
-// Called when a chunk has changed. I.E: A wall was deleted.
-
-/datum/camerachunk/proc/visibilityChanged(turf/loc)
- if(!visibleTurfs[loc])
+/// Called when a chunk has changed. I.E: A wall was deleted.
+/datum/camerachunk/proc/visibility_changed(turf/loc)
+ if(!visible_turfs[loc])
return
- hasChanged()
+ has_changed()
-// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
-// instead be flagged to update the next time an AI Eye moves near it.
-
-/datum/camerachunk/proc/hasChanged(update_now = 0)
+/// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
+/// instead be flagged to update the next time a camera eye moves near it.
+/datum/camerachunk/proc/has_changed(update_now = 0)
if(update_now)
update()
SScamera.remove_from_queue(src)
@@ -100,48 +98,48 @@
else
changed = TRUE
-// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.
-
+/// Gathers the visible turfs from cameras and puts them into the appropiate lists.
/datum/camerachunk/proc/update()
- var/list/newVisibleTurfs = list()
+ var/list/new_visible_turfs = list()
for(var/obj/machinery/camera/c as anything in active_cameras)
var/turf/point = locate(src.x + (CAMERA_CHUNK_SIZE / 2), src.y + (CAMERA_CHUNK_SIZE / 2), src.z)
var/turf/T = get_turf(c)
if(get_dist(point, T) > CAMERA_VIEW_DISTANCE + (CAMERA_CHUNK_SIZE / 2))
- continue // Still needed for Ais who get created on Z level 1 on the spot of the new player
+ // Still needed for Ais who get created on Z level 1 on the spot of the new player
+ continue
for(var/turf/t in c.can_see())
if(turfs[t])
- newVisibleTurfs[t] = t
+ new_visible_turfs[t] = t
- var/list/visAdded = newVisibleTurfs - visibleTurfs
- var/list/visRemoved = visibleTurfs - newVisibleTurfs
+ var/list/vis_added = new_visible_turfs - visible_turfs
+ var/list/vis_removed = visible_turfs - new_visible_turfs
- visibleTurfs = newVisibleTurfs
- obscuredTurfs = turfs - newVisibleTurfs
+ visible_turfs = new_visible_turfs
+ obscured_turfs = turfs - new_visible_turfs
var/list/images_to_remove = list()
var/list/images_to_add = list()
- for(var/turf/t as anything in visAdded)
+ for(var/turf/t as anything in vis_added)
if(t.obscured)
obscured -= t.obscured
images_to_remove += t.obscured
- for(var/turf/t as anything in visRemoved)
+ for(var/turf/t as anything in vis_removed)
if(!t.obscured)
t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1)
t.obscured.plane = BYOND_LIGHTING_PLANE + 1
obscured += t.obscured
images_to_add += t.obscured
- for(var/mob/camera/ai_eye/eye as anything in seenby)
- var/client/client = eye.GetViewerClient()
+ for(var/mob/camera/eye/eye as anything in seenby)
+ var/client/client = eye.get_viewer_client()
if(client)
client.images -= images_to_remove
client.images += images_to_add
changed = FALSE
-// Create a new camera chunk, since the chunks are made as they are needed.
+/// Create a new camera chunk, since the chunks are made as they are needed.
/datum/camerachunk/New(loc, x, y, z)
// 0xf = 15
@@ -162,11 +160,11 @@
for(var/obj/machinery/camera/c as anything in active_cameras)
for(var/turf/t in c.can_see())
if(turfs[t])
- visibleTurfs[t] = t
+ visible_turfs[t] = t
- obscuredTurfs = turfs - visibleTurfs
+ obscured_turfs = turfs - visible_turfs
- for(var/turf/t as anything in obscuredTurfs)
+ for(var/turf/t as anything in obscured_turfs)
if(!t.obscured)
t.obscured = image('icons/effects/cameravis.dmi', t, "black", BYOND_LIGHTING_LAYER + 0.1)
t.obscured.plane = BYOND_LIGHTING_PLANE + 1
diff --git a/code/modules/mob/camera/eye.dm b/code/modules/mob/camera/eye.dm
new file mode 100644
index 000000000000..2034a6c7c303
--- /dev/null
+++ b/code/modules/mob/camera/eye.dm
@@ -0,0 +1,191 @@
+/// Camera eyes are remote-control mobs that can move and see throughout the global cameranet.
+/// They're used in AI eyes, holograms, advanced camera consoles, abductor consoles, shuttle consoles,
+/// and xenobiology consoles. When created, the user with which they are initialized will be granted control,
+/// and their movements will be relayed to the camera eye instead. When destroyed, the user's control of the
+/// camera eye will be released; if they were previously remote controlling another object (such as another
+/// camera eye) then they will be put back in control of that object; otherwise they will return to their body.
+/mob/camera/eye
+ name = "Inactive Camera Eye"
+ icon = 'icons/obj/abductor.dmi'
+ icon_state = "camera_target"
+ alpha = 127
+ invisibility = SEE_INVISIBLE_OBSERVER
+
+ /// The list of camera chunks currently visible to the camera eye.
+ var/list/visible_camera_chunks = list()
+ /// The user controlling the eye.
+ var/mob/living/user
+ /// The thing that the user was previously remote controlling before this eye.
+ var/user_previous_remote_control
+ /// The object that created the eye.
+ var/origin
+ /// If true, speech near the camera eye will be relayed to its controller.
+ var/relay_speech = FALSE
+ /// Sets the camera eye visibility range; does not expand viewport, only affects cameranet obscuring
+ var/static_visibility_range = 16
+ /// Toggles whether this eye is detectable by AI Detectors.
+ var/ai_detector_visible = TRUE
+ /// Toggles whether the eye's icon should be visible to its user.
+ var/visible_icon = FALSE
+ /// The list of cameranets that this camera eye can see and access.
+ var/list/networks = list("SS13")
+ /// The in-memory image of the camera eye's icon.
+ var/image/user_image
+
+ // Camera acceleration settings
+ // Initially, the camera moves one turf per move. If there is no movement for
+ // cooldown_rate in deciseconds, the camera will reset to this movement rate.
+ // Every move otherwise increases sprint by acceleration_rate, until sprint
+ // exceeds sprint_threshold, and the movement rate increases by one per move.
+ // The movement rate is 1 + round(sprint / sprint_threshold).
+
+ /// The maximum sprint value - this caps acceleration
+ var/max_sprint = 50
+ /// The minimum sprint needed to increase base velocity
+ var/sprint_threshold = 20
+ /// The amount that sprint is increased per move
+ var/acceleration_rate = 0.5
+ /// Keeps track of acceleration - movement rate is 1 + round(sprint / sprint_threshold)
+ var/sprint = 10
+ /// The absolute time that sprint will reset to its initial value
+ var/cooldown = 0
+ /// The time after which sprint should be reset to its initial state, if no movements are made
+ var/cooldown_rate = 5
+ /// Toggles camera acceleration on or off.
+ var/acceleration = 1
+
+/mob/camera/eye/Initialize(mapload, owner_name, camera_origin, mob/living/user)
+ . = ..()
+ name = "Camera Eye ([owner_name])"
+ origin = camera_origin
+ give_control(user)
+ update_visibility()
+ refresh_visible_icon()
+ if(!validate_active_cameranet())
+ return INITIALIZE_HINT_QDEL
+
+/// Validates that there is an active cameranet. If strict is 0, does nothing.
+/// Returns 1 if there is an active cameranet. Warns the user and returns 0 if there is not.
+/mob/camera/eye/proc/validate_active_cameranet(strict = 0)
+ var/camera = first_active_camera()
+ if(strict && !camera)
+ to_chat(user, "ERROR: No linked and active camera network found.")
+ return FALSE
+ return TRUE
+
+/// Returns the turf of the first active camera in the global cameranet.
+/mob/camera/eye/proc/first_active_camera()
+ for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
+ if(!C.can_use())
+ continue
+ if(length(C.network & networks))
+ return get_turf(C)
+
+/// Updates what the global cameranet can see with respect to this eye and its user's client.
+/mob/camera/eye/proc/update_visibility()
+ GLOB.cameranet.visibility(src, user.client)
+
+/// Refreshes user_image in the user's client.images.
+/mob/camera/eye/proc/refresh_visible_icon()
+ if(visible_icon && user.client)
+ user.client.images -= user_image
+ user_image = image(icon,loc,icon_state,FLY_LAYER)
+ user.client.images += user_image
+
+/// Sets the camera eye's location to T, updates global cameranet visibility, and refreshes user_images.
+/mob/camera/eye/set_loc(T)
+ if(user)
+ T = get_turf(T)
+ ..(T)
+ update_visibility()
+ refresh_visible_icon()
+
+/// Disables independent movement by camera eyes; camera eyes must be controlled by relaymove.
+/mob/camera/eye/Move()
+ return FALSE
+
+/// If `usr` is an AI, set the camera eye's location to the location of the atom clicked.
+/atom/proc/move_camera_by_click()
+ if(is_ai(usr))
+ var/mob/living/silicon/ai/AI = usr
+ if(AI.eyeobj && (AI.client.eye == AI.eyeobj) && (AI.eyeobj.z == z))
+ AI.camera_follow = null
+ if(isturf(loc) || isturf(src))
+ AI.eyeobj.set_loc(src)
+
+/// Returns the user's client, if it exists; otherwise returns null.
+/mob/camera/eye/proc/get_viewer_client()
+ return user?.client
+
+/// Removes obscured chunk images and user_images from the user's client.images.
+/mob/camera/eye/proc/remove_images()
+ var/client/C = get_viewer_client()
+ if(!C)
+ return
+ for(var/datum/camerachunk/chunk as anything in visible_camera_chunks)
+ C.images -= chunk.obscured
+ if(visible_icon)
+ C.images -= user_image
+
+/// Calls `remove_images`, changes the user's remote control from this camera eye to `user_previous_remote_control`.
+/mob/camera/eye/proc/release_control()
+ if(!istype(user))
+ return
+ if(user.client)
+ user.reset_perspective(user.client.mob)
+ remove_images()
+ user.remote_control = null
+ if(user_previous_remote_control)
+ user.reset_perspective(user_previous_remote_control)
+ user.remote_control = user_previous_remote_control
+ user_previous_remote_control = null
+ user = null
+
+/// Forces this eye's current user to release control, renames this eye, and grants `new_user` control of this eye.
+/mob/camera/eye/proc/give_control(mob/new_user)
+ if(!istype(new_user))
+ return
+ release_control()
+ user = new_user
+ rename_camera(user.name)
+ if(istype(user.remote_control))
+ user_previous_remote_control = user.remote_control
+ user.remote_control = src
+ user.reset_perspective(src)
+
+/// Renames the camera eye (only visible in observer Orbit menu)
+/mob/camera/eye/proc/rename_camera(new_name)
+ name = "Camera Eye ([new_name])"
+
+/// Remove this eye from all chunks containing it.
+/mob/camera/eye/proc/release_chunks()
+ for(var/datum/camerachunk/chunk as anything in visible_camera_chunks)
+ chunk.remove(src)
+
+/mob/camera/eye/Destroy()
+ release_control()
+ release_chunks()
+ return ..()
+
+/// Called when the user controlling this eye attempts to move; uses camera acceleration settings.
+/mob/camera/eye/relaymove(mob/user,direct)
+ var/initial = initial(sprint)
+
+ if(cooldown && cooldown < world.timeofday)
+ sprint = initial
+
+ for(var/i in 0 to sprint step sprint_threshold)
+ var/turf/next_step= get_turf(get_step(src, direct))
+ if(next_step)
+ set_loc(next_step)
+
+ cooldown = world.timeofday + cooldown_rate
+ if(acceleration)
+ sprint = min(sprint + acceleration_rate, max_sprint)
+ else
+ sprint = initial
+
+/// If `relay_speech` is truthy, allows the camera eye's user to hear speech spoken at the eye's location.
+/mob/camera/eye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+ if(relay_speech)
+ user.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency)
diff --git a/code/modules/mob/camera/eye/abductor.dm b/code/modules/mob/camera/eye/abductor.dm
new file mode 100644
index 000000000000..3b8c531099aa
--- /dev/null
+++ b/code/modules/mob/camera/eye/abductor.dm
@@ -0,0 +1,14 @@
+/mob/camera/eye/abductor
+ name = "Abductor Camera Eye"
+ ai_detector_visible = FALSE
+
+/mob/camera/eye/abductor/Initialize(mapload, owner_name, camera_origin, user)
+ ..()
+ set_loc(first_active_camera())
+
+/mob/camera/eye/abductor/rename_camera(new_name)
+ name = "Abductor Camera Eye ([new_name])"
+
+/// Requires the cameranet to be validated.
+/mob/camera/eye/abductor/validate_active_cameranet()
+ ..(TRUE)
diff --git a/code/modules/mob/camera/eye/ai_eye.dm b/code/modules/mob/camera/eye/ai_eye.dm
new file mode 100644
index 000000000000..d915f95f2fe7
--- /dev/null
+++ b/code/modules/mob/camera/eye/ai_eye.dm
@@ -0,0 +1,34 @@
+/mob/camera/eye/ai
+ name = "Inactive AI Eye"
+ icon = 'icons/mob/ai.dmi'
+ icon_state = "eye"
+ var/mob/living/silicon/ai/ai = null
+
+/mob/camera/eye/ai/Initialize(mapload, owner_name, camera_origin, mob/living/user)
+ . = ..()
+ ai = user
+ if(is_ai_eye(ai.eyeobj))
+ stack_trace("Tried to create an AI Eye for [user], but there is one already assigned")
+ return INITIALIZE_HINT_QDEL
+ name = "[owner_name] (AI Eye)"
+
+/// Ensures that the user's perspective is on this eye (instead of, for example, a mech or hologram eye) and
+/// updates parallax
+/mob/camera/eye/ai/set_loc(T)
+ ..()
+ user.reset_perspective(src)
+ update_parallax_contents()
+
+/// Cancels the camera's follow target if tracking has stopped, and lights up nearby lights if the AI has
+/// "Toggle Camera Lights" enabled
+/mob/camera/eye/ai/relaymove(mob/user, direct)
+ ..()
+ if(!ai.tracking)
+ ai.camera_follow = null
+ if(ai.camera_light_on)
+ ai.light_cameras()
+
+/// Relays speech near the AI eye to the AI if the AI has surveillance, either from a malf module or a station trait
+/mob/camera/eye/ai/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+ if(relay_speech)
+ ai.relay_speech(speaker, message_pieces, verb)
diff --git a/code/modules/mob/camera/eye/hologram_eye.dm b/code/modules/mob/camera/eye/hologram_eye.dm
new file mode 100644
index 000000000000..a30c63827a9d
--- /dev/null
+++ b/code/modules/mob/camera/eye/hologram_eye.dm
@@ -0,0 +1,53 @@
+/mob/camera/eye/hologram
+ name = "Inactive Hologram Eye"
+ ai_detector_visible = FALSE
+ acceleration = FALSE
+ relay_speech = TRUE
+ var/obj/machinery/hologram/holopad/holopad
+
+/mob/camera/eye/hologram/Initialize(mapload, owner_name, camera_origin, mob/living/user)
+ ..()
+ holopad = camera_origin
+ set_loc(holopad)
+
+/mob/camera/eye/hologram/rename_camera(new_name)
+ name = "Hologram ([new_name])"
+
+/// Hologram movement copies the delays and diagonal delays of regular mob movement
+/// for crew, and for AI unless fast holograms are enabled
+/mob/camera/eye/hologram/relaymove(mob/user, direct)
+ var/mob/living/silicon/ai/ai = user
+ if(istype(ai) && ai.fast_holograms)
+ return ..()
+ var/turf/new_location = get_turf(get_step(src, direct))
+ if(new_location.z != z || get_dist(new_location, src) > 1)
+ return
+ var/base_delay = GLOB.configuration.movement.base_run_speed
+ var/diag_delay = base_delay * SQRT_2
+ var/delay = (direct & (direct - 1)) ? diag_delay : base_delay
+ user.client.move_delay = debounced_move(normalized_delay_speed(delay))
+ user.last_movement = world.time
+ set_loc(new_location)
+
+/mob/camera/eye/hologram/proc/debounced_move(delay_speed)
+ var/old_move_delay = user.client.move_delay
+ if(old_move_delay + world.tick_lag > world.time)
+ return old_move_delay + delay_speed
+ else
+ return world.time + delay_speed
+
+/mob/camera/eye/hologram/proc/normalized_delay_speed(delay_speed)
+ return TICKS2DS(-round(-(DS2TICKS(delay_speed))))
+
+/// Requires the cameranet to be validated.
+/mob/camera/eye/hologram/validate_active_cameranet()
+ ..(TRUE)
+
+/// Moves the associated hologram, and the previously controlled object (probably an AI eye), to the new location.
+/mob/camera/eye/hologram/set_loc(T)
+ . = ..()
+ var/turf/new_location = get_turf(T)
+ holopad.move_hologram(user, new_location)
+ var/mob/camera/eye/previous_eye = user_previous_remote_control
+ if(istype(previous_eye))
+ previous_eye.set_loc(new_location)
diff --git a/code/modules/mob/camera/eye/shuttle_navigator.dm b/code/modules/mob/camera/eye/shuttle_navigator.dm
new file mode 100644
index 000000000000..6e80fbc2ee49
--- /dev/null
+++ b/code/modules/mob/camera/eye/shuttle_navigator.dm
@@ -0,0 +1,30 @@
+/mob/camera/eye/shuttle_docker
+ name = "Shuttle Docker Camera Eye"
+ ai_detector_visible = FALSE
+ simulated = FALSE
+ var/list/placement_images = list()
+ var/list/placed_images = list()
+
+/mob/camera/eye/shuttle_docker/Initialize(mapload, owner_name, camera_origin, mob/living/user)
+ ..()
+ set_loc(first_active_camera())
+
+/mob/camera/eye/shuttle_docker/rename_camera(new_name)
+ name = "Shuttle Docker Camera Eye ([new_name])"
+
+/// Prevents the shuttle docker eye from updating global cameranet chunks. Shuttle dockers don't use station cameras.
+/mob/camera/eye/shuttle_docker/update_visibility()
+ return FALSE
+
+/// Prevents moving the camera eye into the station, and updates the current location's landing spot validity.
+/mob/camera/eye/shuttle_docker/set_loc(T)
+ if(isspaceturf(get_turf(T)) || istype(get_area(T), /area/space) || istype(get_area(T), /area/shuttle))
+ ..()
+ var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
+ console.check_landing_spot()
+
+/mob/camera/eye/shuttle_docker/update_remote_sight(mob/living/user)
+ user.sight = SEE_TURFS
+
+ ..()
+ return TRUE
diff --git a/code/modules/mob/camera/eye/syndicate.dm b/code/modules/mob/camera/eye/syndicate.dm
new file mode 100644
index 000000000000..47fa732f81ff
--- /dev/null
+++ b/code/modules/mob/camera/eye/syndicate.dm
@@ -0,0 +1,10 @@
+/mob/camera/eye/syndicate
+ name = "Syndicate Researcher Camera Eye"
+ ai_detector_visible = FALSE
+
+/mob/camera/eye/syndicate/Initialize(mapload, owner_name, camera_origin, user)
+ ..()
+ set_loc(first_active_camera())
+
+/mob/camera/eye/syndicate/rename_camera(new_name)
+ name = "Syndicate Researcher Camera Eye ([new_name])"
diff --git a/code/modules/mob/camera/eye/xenobio_eye.dm b/code/modules/mob/camera/eye/xenobio_eye.dm
new file mode 100644
index 000000000000..2e2bef7e710b
--- /dev/null
+++ b/code/modules/mob/camera/eye/xenobio_eye.dm
@@ -0,0 +1,22 @@
+/mob/camera/eye/xenobio
+ name = "Xenobiology Console Camera Eye"
+ visible_icon = TRUE
+ ai_detector_visible = FALSE
+ var/allowed_area
+
+/mob/camera/eye/xenobio/Initialize(mapload, owner_name, camera_origin, mob/living/user)
+ . = ..()
+ var/area/A = get_area(camera_origin)
+ allowed_area = A.name
+
+/mob/camera/eye/xenobio/rename_camera(new_name)
+ name = "Xenobiology Console ([new_name])"
+
+/// Prevents the camera eye from going outside of the xenobiology area
+/mob/camera/eye/xenobio/set_loc(T)
+ var/area/new_area = get_area(T)
+ if(!new_area)
+ return
+ if(new_area.name != allowed_area && !new_area.xenobiology_compatible)
+ return
+ return ..()
diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm
index 59159c638f89..d7d10ee753df 100644
--- a/code/modules/mob/dead/observer/observer_base.dm
+++ b/code/modules/mob/dead/observer/observer_base.dm
@@ -255,7 +255,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
warningmsg = "You have committed suicide too early in the round"
else if(stat != DEAD)
warningmsg = "You are alive"
- if(isAI(src))
+ if(is_ai(src))
warningmsg = "You are a living AI! You should probably use OOC -> Wipe Core instead."
else if(GLOB.non_respawnable_keys[ckey])
warningmsg = "You have lost your right to respawn"
@@ -656,7 +656,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/proc/ghost_follow_link(atom/target, atom/ghost)
if((!target) || (!ghost)) return
- if(isAI(target)) // AI core/eye follow links
+ if(is_ai(target)) // AI core/eye follow links
var/mob/living/silicon/ai/A = target
. = "core"
if(A.client && A.eyeobj) // No point following clientless AI eyes
diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm
index 0f86ba90898b..79e01aeae491 100644
--- a/code/modules/mob/dead/observer/observer_say.dm
+++ b/code/modules/mob/dead/observer/observer_say.dm
@@ -17,7 +17,7 @@
if(!speaker || !ismob(speaker))
return speaker_name
var/mob/speaker_mob = speaker
- if(isAI(speaker_mob))
+ if(is_ai(speaker_mob))
//AI's can't pretend to be other mobs.
return speaker_name
if(!check_name_against || check_name_against == speaker_mob.real_name)
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index f466389d8f09..4ea474bdd741 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -629,7 +629,7 @@
continue
else if(drone_only && !isdrone(S))
continue
- else if(isAI(S))
+ else if(is_ai(S))
message_start = list("[name], [speaker.name]")
else if(isrobot(S))
var/mob/living/silicon/robot/borg = S
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 0dd5b93626a1..3ac09b57112f 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -364,7 +364,7 @@
return (hudtype in have_hudtypes)
- else if(isrobot(M) || isAI(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system
+ else if(isrobot(M) || is_ai(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system
return (hudtype in list(EXAMINE_HUD_SECURITY_READ, EXAMINE_HUD_SECURITY_WRITE, EXAMINE_HUD_MEDICAL_READ, EXAMINE_HUD_MEDICAL_WRITE))
else if(isobserver(M))
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index aa3a3aff2eb7..3fddf689100e 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -723,7 +723,7 @@
else if(isrobot(user))
var/mob/living/silicon/robot/U = user
rank = "[U.modtype] [U.braintype]"
- else if(isAI(user))
+ else if(is_ai(user))
rank = "AI"
set_criminal_status(user, found_record, new_status, reason, rank)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index db2fa4914bd2..e85da8a348a0 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -30,7 +30,7 @@
var/now_pushing = null
- var/atom/movable/cameraFollow = null
+ var/atom/movable/camera_follow = null
var/on_fire = 0 //The "Are we on fire?" var
var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20
diff --git a/code/modules/mob/living/silicon/ai/ai_death.dm b/code/modules/mob/living/silicon/ai/ai_death.dm
index 6f170c292f12..b2dbf6e8bda0 100644
--- a/code/modules/mob/living/silicon/ai/ai_death.dm
+++ b/code/modules/mob/living/silicon/ai/ai_death.dm
@@ -10,7 +10,7 @@
else
icon_state = "ai_dead"
if(eyeobj)
- eyeobj.setLoc(get_turf(src))
+ eyeobj.set_loc(get_turf(src))
GLOB.shuttle_caller_list -= src
SSshuttle.autoEvac()
diff --git a/code/modules/mob/living/silicon/ai/ai_life.dm b/code/modules/mob/living/silicon/ai/ai_life.dm
index abf0b4e94526..3ba153e83e9c 100644
--- a/code/modules/mob/living/silicon/ai/ai_life.dm
+++ b/code/modules/mob/living/silicon/ai/ai_life.dm
@@ -10,7 +10,7 @@
var/turf/T = get_turf(src)
if(stat != CONSCIOUS) //ai's fucked
- cameraFollow = null
+ camera_follow = null
reset_perspective(null)
unset_machine()
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index fc2981d927ff..cbadc6fa3d66 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -14,6 +14,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/pick_icon,
/mob/living/silicon/ai/proc/sensor_mode,
/mob/living/silicon/ai/proc/show_laws_verb,
+ /mob/living/silicon/ai/proc/toggle_fast_holograms,
/mob/living/silicon/ai/proc/toggle_acceleration,
/mob/living/silicon/ai/proc/toggle_camera_light,
/mob/living/silicon/ai/proc/botcall,
@@ -104,10 +105,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/obj/machinery/doomsday_device/doomsday_device
var/obj/machinery/hologram/holopad/holo = null
- var/mob/camera/ai_eye/eyeobj
- var/sprint = 10
- var/cooldown = 0
- var/acceleration = 1
+ var/mob/camera/eye/ai/eyeobj
+ var/fast_holograms = TRUE
var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed.
/// If true, this AI core can use the teleporter.
@@ -121,7 +120,6 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/announce_arrivals = TRUE
var/arrivalmsg = "$name, $rank, has arrived on the station."
- var/list/all_eyes = list()
var/next_text_announcement
//Used with the hotkeys on 2-5 to store locations.
@@ -228,8 +226,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
spawn(5)
new /obj/machinery/ai_powersupply(src)
-
- create_eye()
+
+ eyeobj = new /mob/camera/eye/ai(loc, name, src, src)
builtInCamera = new /obj/machinery/camera/portable(src)
builtInCamera.c_tag = name
@@ -769,7 +767,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(controlled_mech)
to_chat(src, "You are already loaded into an onboard computer!")
return
- if(!GLOB.cameranet.checkCameraVis(M))
+ if(!GLOB.cameranet.check_camera_vis(M))
to_chat(src, "Exosuit is no longer near active cameras.")
return
if(lacks_power())
@@ -826,7 +824,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
//The target must be in view of a camera or near the core.
if(turf_check in range(get_turf(src)))
call_bot(turf_check)
- else if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(turf_check))
+ else if(GLOB.cameranet && GLOB.cameranet.check_turf_vis(turf_check))
call_bot(turf_check)
else
to_chat(src, "Selected location is not visible.")
@@ -882,7 +880,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C)
if(!tracking)
- cameraFollow = null
+ camera_follow = null
if(QDELETED(C) || stat == DEAD) //C.can_use())
return FALSE
@@ -891,7 +889,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
view_core()
return
// ok, we're alive, camera is good and in our network...
- eyeobj.setLoc(get_turf(C))
+ eyeobj.set_loc(get_turf(C))
//machine = src
return TRUE
@@ -939,7 +937,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(!C.can_use())
continue
if(network in C.network)
- U.eyeobj.setLoc(get_turf(C))
+ U.eyeobj.set_loc(get_turf(C))
break
to_chat(src, "Switched to [network] camera network.")
//End of code by Mord_Sith
@@ -1238,7 +1236,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
- for(var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
+ for(var/datum/camerachunk/CC in eyeobj.visible_camera_chunks)
for(var/obj/machinery/camera/C in CC.active_cameras)
if(!C.can_use() || get_dist(C, eyeobj) > 7)
continue
@@ -1340,7 +1338,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
//apc_override is needed here because AIs use their own APC when depowered
var/turf/T = isturf(A) ? A : get_turf_pixel(A)
- return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T)) || apc_override
+ return (GLOB.cameranet && GLOB.cameranet.check_turf_vis(T)) || apc_override
//AI is carded/shunted
//view(src) returns nothing for carded/shunted AIs and they have x-ray vision so just use get_dist
var/list/viewscale = getviewsize(client.view)
@@ -1433,8 +1431,45 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
else
to_chat(src, "Target is not on or near any active cameras on the station.")
-/mob/living/silicon/ai/proc/camera_visibility(mob/camera/ai_eye/moved_eye)
- GLOB.cameranet.visibility(moved_eye, client, all_eyes)
+// Return to the Core.
+/mob/living/silicon/ai/proc/core()
+ set category = "AI Commands"
+ set name = "AI Core"
+
+ view_core()
+
+/mob/living/silicon/ai/proc/view_core()
+
+ current = null
+ camera_follow = null
+ unset_machine()
+
+ if(eyeobj && loc)
+ eyeobj.loc = loc
+ else
+ to_chat(src, "ERROR: Eyeobj not found. Creating new eye...")
+ eyeobj = new /mob/camera/eye/ai(loc, name, src, src)
+
+ eyeobj.set_loc(loc)
+
+/mob/living/silicon/ai/proc/toggle_fast_holograms()
+ set category = "AI Commands"
+ set name = "Toggle Fast Holograms"
+
+ if(usr.stat == DEAD || !is_ai_eye(eyeobj))
+ return
+ fast_holograms = !fast_holograms
+ to_chat(usr, "Fast holograms have been toggled [fast_holograms ? "on" : "off"].")
+
+/mob/living/silicon/ai/proc/toggle_acceleration()
+ set category = "AI Commands"
+ set name = "Toggle Camera Acceleration"
+
+ if(usr.stat == DEAD)
+ return //won't work if dead
+ if(is_ai_eye(eyeobj))
+ eyeobj.acceleration = !eyeobj.acceleration
+ to_chat(usr, "Camera acceleration has been toggled [eyeobj.acceleration ? "on" : "off"].")
/mob/living/silicon/ai/handle_fire()
return
diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm
index 1e676eb05a32..ebcec382ef43 100644
--- a/code/modules/mob/living/silicon/ai/ai_say.dm
+++ b/code/modules/mob/living/silicon/ai/ai_say.dm
@@ -28,7 +28,7 @@
else if(iscarbon(speaker)) // Nonhuman carbon mob
jobname = "No ID"
- else if(isAI(speaker))
+ else if(is_ai(speaker))
jobname = "AI"
else if(isrobot(speaker))
jobname = "Cyborg"
diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
deleted file mode 100644
index c98203ef2bde..000000000000
--- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
+++ /dev/null
@@ -1,177 +0,0 @@
-// CAMERA NET
-//
-// The datum containing all the chunks.
-GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
-
-/datum/cameranet
- var/name = "Camera Net" // Name to show for VV and stat()
-
- // The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Destroy().
- var/list/cameras = list()
- // The chunks of the map, mapping the areas that the cameras can see.
- var/list/chunks = list()
- var/ready = FALSE
-
-// Checks if a chunk has been Generated in x, y, z.
-/datum/cameranet/proc/chunkGenerated(x, y, z)
- x &= ~(CAMERA_CHUNK_SIZE - 1)
- y &= ~(CAMERA_CHUNK_SIZE - 1)
- var/key = "[x],[y],[z]"
- return (chunks[key])
-
-// Returns the chunk in the x, y, z.
-// If there is no chunk, it creates a new chunk and returns that.
-/datum/cameranet/proc/getCameraChunk(x, y, z)
- x &= ~(CAMERA_CHUNK_SIZE - 1)
- y &= ~(CAMERA_CHUNK_SIZE - 1)
- var/key = "[x],[y],[z]"
- if(!chunks[key])
- chunks[key] = new /datum/camerachunk(null, x, y, z)
-
- return chunks[key]
-
-// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.
-
-/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes)
- if(!islist(moved_eyes))
- moved_eyes = moved_eyes ? list(moved_eyes) : list()
- if(islist(other_eyes))
- other_eyes = (other_eyes - moved_eyes)
- else
- other_eyes = list()
-
- var/list/chunks_pre_seen = list()
- var/list/chunks_post_seen = list()
-
- for(var/V in moved_eyes)
- var/mob/camera/ai_eye/eye = V
- if(C)
- chunks_pre_seen |= eye.visibleCameraChunks
- // 0xf = 15
- var/static_range = eye.static_visibility_range
- var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
- var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
- var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
- var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
-
- var/list/visibleChunks = list()
-
- for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
- for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
- visibleChunks |= getCameraChunk(x, y, eye.z)
-
- var/list/remove = eye.visibleCameraChunks - visibleChunks
- var/list/add = visibleChunks - eye.visibleCameraChunks
-
- for(var/chunk in remove)
- var/datum/camerachunk/c = chunk
- c.remove(eye, FALSE)
-
- for(var/chunk in add)
- var/datum/camerachunk/c = chunk
- c.add(eye, FALSE)
-
- if(C)
- chunks_post_seen |= eye.visibleCameraChunks
-
- if(C)
- for(var/V in other_eyes)
- var/mob/camera/ai_eye/eye = V
- chunks_post_seen |= eye.visibleCameraChunks
-
- var/list/remove = chunks_pre_seen - chunks_post_seen
- var/list/add = chunks_post_seen - chunks_pre_seen
-
- for(var/chunk in remove)
- var/datum/camerachunk/c = chunk
- C.images -= c.obscured
-
- for(var/chunk in add)
- var/datum/camerachunk/c = chunk
- C.images += c.obscured
-
-
-// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
-
-/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1)
-
- if(!SSticker || (opacity_check && !A.opacity))
- return
- majorChunkChange(A, 2)
-
-/datum/cameranet/proc/updateChunk(x, y, z)
- // 0xf = 15
- if(!chunkGenerated(x, y, z))
- return
- var/datum/camerachunk/chunk = getCameraChunk(x, y, z)
- chunk.hasChanged()
-
-// Removes a camera from a chunk.
-
-/datum/cameranet/proc/removeCamera(obj/machinery/camera/c)
- majorChunkChange(c, 0)
-
-// Add a camera to a chunk.
-
-/datum/cameranet/proc/addCamera(obj/machinery/camera/c)
- majorChunkChange(c, 1)
-
-// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.
-
-/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c, turf/old_loc)
- majorChunkChange(c, 1, old_loc)
-
-// Never access this proc directly!!!!
-// This will update the chunk and all the surrounding chunks.
-// It will also add the atom to the cameras list if you set the choice to 1.
-// Setting the choice to 0 will remove the camera from the chunks.
-// If you want to update the chunks around an object, without adding/removing a camera, use choice 2.
-
-/datum/cameranet/proc/majorChunkChange(atom/c, choice, turf/old_loc = null)
- // 0xf = 15
- if(!c)
- return
-
- var/turf/T = get_turf(c)
- if(!T)
- return
-
- if(old_loc)
- // Check if the current turf falls in the same chunka as the old_loc. If so, don't do anything
- if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1))
- return
-
- // Use camera view distance here to actually know how far a camera can max watch
- var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
- var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
- var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
- var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
-
- for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
- for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
- if(chunkGenerated(x, y, T.z))
- var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z)
- if(choice == 0)
- // Remove the camera.
- chunk.remove_camera(c)
- else if(choice == 1)
- // You can't have the same camera in the list twice.
- chunk.add_camera(c)
- chunk.hasChanged()
-
-// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
-
-/datum/cameranet/proc/checkCameraVis(mob/living/target as mob)
-
- // 0xf = 15
- var/turf/position = get_turf(target)
- return checkTurfVis(position)
-
-/datum/cameranet/proc/checkTurfVis(turf/position)
- var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
- if(chunk)
- if(chunk.changed)
- chunk.hasChanged(1) // Update now, no matter if it's visible or not.
- if(chunk.visibleTurfs[position])
- return 1
- return 0
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
deleted file mode 100644
index e4219d090d11..000000000000
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ /dev/null
@@ -1,155 +0,0 @@
-// AI EYE
-//
-// An invisible (no icon) mob that the AI controls to look around the station with.
-// It streams chunks as it moves around, which will show it what the AI can and cannot see.
-
-/mob/camera/ai_eye
- name = "Inactive AI Eye"
-
- icon = 'icons/mob/ai.dmi' //Allows ghosts to see what the AI is looking at.
- icon_state = "eye"
- alpha = 127
- invisibility = SEE_INVISIBLE_OBSERVER
-
- var/list/visibleCameraChunks = list()
- var/mob/living/silicon/ai/ai = null
- var/relay_speech = FALSE
- var/use_static = TRUE
- var/static_visibility_range = 16
- // Decides if it is shown by AI Detector or not
- var/ai_detector_visible = TRUE
-
-
-// Use this when setting the aiEye's location.
-// It will also stream the chunk that the new loc is in.
-
-/mob/camera/ai_eye/setLoc(T)
- if(ai)
- if(!isturf(ai.loc))
- return
- T = get_turf(T)
- ..(T)
- if(use_static)
- ai.camera_visibility(src)
- if(ai.client)
- ai.client.eye = src
- update_parallax_contents()
- //Holopad
- if(istype(ai.current, /obj/machinery/hologram/holopad))
- var/obj/machinery/hologram/holopad/H = ai.current
- H.move_hologram(ai, T)
-
-/mob/camera/ai_eye/Move()
- return 0
-
-/mob/camera/ai_eye/Process_Spacemove(movement_dir)
- // Nothing in space can stop us from moving.
- return 1
-
-/mob/camera/ai_eye/proc/GetViewerClient()
- if(ai)
- return ai.client
- return null
-
-/mob/camera/ai_eye/proc/RemoveImages()
- var/client/C = GetViewerClient()
- if(C && use_static)
- for(var/V in visibleCameraChunks)
- var/datum/camerachunk/chunk = V
- C.images -= chunk.obscured
-
-/mob/camera/ai_eye/Destroy()
- if(ai)
- ai.all_eyes -= src
- ai = null
- for(var/V in visibleCameraChunks)
- var/datum/camerachunk/chunk = V
- chunk.remove(src)
- return ..()
-
-/atom/proc/move_camera_by_click()
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- if(AI.eyeobj && (AI.client.eye == AI.eyeobj) && (AI.eyeobj.z == z))
- AI.cameraFollow = null
- if(isturf(loc) || isturf(src))
- AI.eyeobj.setLoc(src)
-
-// AI MOVEMENT
-
-// This will move the AIEye. It will also cause lights near the eye to light up, if toggled.
-// This is handled in the proc below this one.
-
-/client/proc/AIMove(n, direct, mob/living/silicon/ai/user)
-
- var/initial = initial(user.sprint)
- var/max_sprint = 50
-
- if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds
- user.sprint = initial
-
- for(var/i = 0; i < max(user.sprint, initial); i += 20)
- var/turf/step = get_turf(get_step(user.eyeobj, direct))
- if(step)
- user.eyeobj.setLoc(step)
-
- user.cooldown = world.timeofday + 5
- if(user.acceleration)
- user.sprint = min(user.sprint + 0.5, max_sprint)
- else
- user.sprint = initial
-
- if(!user.tracking)
- user.cameraFollow = null
-
- //user.unset_machine() //Uncomment this if it causes problems.
- //user.lightNearbyCamera()
- if(user.camera_light_on)
- user.light_cameras()
-
-// Return to the Core.
-/mob/living/silicon/ai/proc/core()
- set category = "AI Commands"
- set name = "AI Core"
-
- view_core()
-
-/mob/living/silicon/ai/proc/view_core()
-
- current = null
- cameraFollow = null
- unset_machine()
-
- if(src.eyeobj && src.loc)
- src.eyeobj.loc = src.loc
- else
- to_chat(src, "ERROR: Eyeobj not found. Creating new eye...")
- create_eye()
-
- eyeobj.setLoc(loc)
-
-/mob/living/silicon/ai/proc/create_eye()
- if(eyeobj)
- return
- eyeobj = new /mob/camera/ai_eye()
- all_eyes += eyeobj
- eyeobj.ai = src
- eyeobj.setLoc(loc)
- eyeobj.name = "[name] (AI Eye)"
-
-/mob/living/silicon/ai/proc/toggle_acceleration()
- set category = "AI Commands"
- set name = "Toggle Camera Acceleration"
-
- if(usr.stat == DEAD)
- return //won't work if dead
- acceleration = !acceleration
- to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].")
-
-/mob/camera/ai_eye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
- if(relay_speech)
- if(istype(ai))
- ai.relay_speech(speaker, message_pieces, verb)
- else
- var/mob/M = ai
- M.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency)
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index b85a67cd8696..ddb084a680c1 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -1174,8 +1174,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
to_chat(user, "You must close the panel first")
return
else
- sleep(6)
SetEmagged(TRUE)
+ sleep(6)
SetLockdown(1) //Borgs were getting into trouble because they would attack the emagger before the new laws were shown
if(hud_used)
hud_used.update_robot_modules_display() //Shows/hides the emag item if the inventory screen is already open.
@@ -1424,7 +1424,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
. = ..()
if(camera && last_camera_update + CAMERA_UPDATE_COOLDOWN < world.time)
last_camera_update = world.time
- GLOB.cameranet.updatePortableCamera(camera, OldLoc)
+ GLOB.cameranet.update_portable_camera(camera, OldLoc)
SEND_SIGNAL(camera, COMSIG_CAMERA_MOVED, OldLoc)
#undef CAMERA_UPDATE_COOLDOWN
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 1a841a9a9d81..72be8b5800bb 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -649,9 +649,11 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
if(LAZYACCESS(client.recent_examines, ref_to_atom))
result = A.examine_more(src)
if(!length(result))
- result += "You examine [A] closer, but find nothing of interest..."
+ result = A.examine(src)
else
result = A.examine(src)
+ if(length(A.examine_more()))
+ result += "You can examine [A.p_them()] again to take a closer look..."
client.recent_examines[ref_to_atom] = world.time + EXAMINE_MORE_WINDOW // set to when we should not examine something
to_chat(src, chat_box_examine(result.Join("\n")), MESSAGE_TYPE_INFO, confidential = TRUE)
@@ -805,8 +807,8 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
reset_perspective(null)
unset_machine()
if(isliving(src))
- if(src:cameraFollow)
- src:cameraFollow = null
+ if(src:camera_follow)
+ src:camera_follow = null
/mob/Topic(href, href_list)
if(href_list["flavor_more"])
diff --git a/code/modules/mob/mob_misc_procs.dm b/code/modules/mob/mob_misc_procs.dm
index 1edfb3f8c4fb..dfcd46f47bcf 100644
--- a/code/modules/mob/mob_misc_procs.dm
+++ b/code/modules/mob/mob_misc_procs.dm
@@ -471,7 +471,7 @@
if(hud_used && hud_used.action_intent)
hud_used.action_intent.icon_state = "[a_intent]"
- else if(isrobot(src) || islarva(src) || isanimal(src) || isAI(src))
+ else if(isrobot(src) || islarva(src) || isanimal(src) || is_ai(src))
switch(input)
if(INTENT_HELP)
a_intent = INTENT_HELP
@@ -521,7 +521,7 @@
var/obj/item/multitool/P
if(isrobot(user) || ishuman(user))
P = user.get_active_hand()
- else if(isAI(user))
+ else if(is_ai(user))
var/mob/living/silicon/ai/AI=user
P = AI.aiMulti
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 6f1cd194d7f6..80254530dca2 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -91,12 +91,17 @@
if(mob.remote_control) //we're controlling something, our movement is relayed to it
return mob.remote_control.relaymove(mob, direct)
- if(isAI(mob))
- if(istype(mob.loc, /obj/item/aicard))
- var/obj/O = mob.loc
- return O.relaymove(mob, direct) // aicards have special relaymove stuff
- return AIMove(n, direct, mob)
-
+ if(is_ai(mob))
+ var/mob/living/silicon/ai/ai = mob
+ var/mob/camera/eye/ai/eye = ai.eyeobj
+ if(istype(eye) && !istype(ai.remote_control))
+ ai.remote_control = eye
+ return eye.relaymove(mob, direct)
+ if(!istype(eye) && !istype(mob.loc, /obj/item/aicard))
+ eye = new /mob/camera/eye/ai(mob.loc, ai.name, ai, ai)
+ if(istype(eye))
+ return eye.relaymove(mob, direct)
+ return FALSE // If the AI is outside of its eye or a mech (e.g. carded), it can't move
if(Process_Grab())
return
diff --git a/code/modules/mob/mob_say_base.dm b/code/modules/mob/mob_say_base.dm
index ddb279e0ac99..c3bc723c4d3a 100644
--- a/code/modules/mob/mob_say_base.dm
+++ b/code/modules/mob/mob_say_base.dm
@@ -89,7 +89,7 @@
var/mob/other_mob = other
if(other_mob.universal_speak)
return TRUE
- if(isAI(src) && ispAI(other_mob))
+ if(is_ai(src) && ispAI(other_mob))
return TRUE
if(istype(other_mob, src.type) || istype(src, other_mob.type))
return TRUE
diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm
index 230f9d163411..2efd024af583 100644
--- a/code/modules/mod/modules/modules_engineering.dm
+++ b/code/modules/mod/modules/modules_engineering.dm
@@ -239,10 +239,9 @@
reagents.remove_any(100)
var/obj/effect/nanofrost_container/A = new /obj/effect/nanofrost_container(get_turf(src))
log_game("[key_name(user)] used Nanofrost at [get_area(user)] ([user.x], [user.y], [user.z]).")
- playsound(src, 'sound/items/syringeproj.ogg', 40, 1)
- for(var/counter in 1 to 5)
- step_towards(A, target)
- sleep(2)
+ playsound(src, 'sound/items/syringeproj.ogg', 40, TRUE)
+ A.throw_at(target, 6, 2, user)
+ sleep(2)
A.Smoke()
if(METAL_FOAM)
diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm
index 6e6a50806b3d..81ea787089ed 100644
--- a/code/modules/paperwork/handlabeler.dm
+++ b/code/modules/paperwork/handlabeler.dm
@@ -11,44 +11,62 @@
var/label = null
var/labels_left = 30
var/mode = LABEL_MODE_OFF
+ new_attack_chain = TRUE
-/obj/item/hand_labeler/afterattack__legacy__attackchain(atom/A, mob/user, proximity)
- if(!proximity)
- return
- if(mode == LABEL_MODE_OFF) //if it's off, give up.
- return
+/obj/item/hand_labeler/examine(mob/user)
+ . = ..()
+ . += "There [labels_left == 1 ? "is" : "are"] [labels_left] label\s remaining."
+ if(label)
+ . += "The label is currently set to \"[label]\"."
+
+/obj/item/hand_labeler/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ if(!mode == LABEL_MODE_OFF)
+ apply_label(target, user)
+ return ITEM_INTERACT_COMPLETE
+ if(remove_label(target, user))
+ return ITEM_INTERACT_COMPLETE
+/obj/item/hand_labeler/proc/remove_label(atom/target, mob/living/user)
+ if(SEND_SIGNAL(target, COMSIG_LABEL_REMOVE)) // sends a signal to label.dm
+ playsound(target, 'sound/items/poster_ripped.ogg', 20, TRUE)
+ to_chat(user, "You remove the label from [target].")
+ return TRUE
+
+/obj/item/hand_labeler/proc/apply_label(atom/target, mob/living/user)
if(!labels_left)
to_chat(user, "No labels left!")
return
if(!label || !length(label))
to_chat(user, "No text set!")
return
- if(ismob(A))
+ if(ismob(target))
to_chat(user, "You can't label creatures!") // use a collar
return
if(mode == LABEL_MODE_GOAL)
- if(istype(A, /obj/item))
+ if(istype(target, /obj/item))
to_chat(user, "Put it in a personal crate instead!")
return
- user.visible_message("[user] labels [A] as part of a secondary goal for [label].", \
- "You label [A] as part of a secondary goal for [label].")
- A.AddComponent(/datum/component/label/goal, label)
+ user.visible_message("[user] labels [target] as part of a secondary goal for [label].", \
+ "You label [target] as part of a secondary goal for [label].")
+ target.AddComponent(/datum/component/label/goal, label)
return
- if(length(A.name) + length(label) > 64)
+ if(length(target.name) + length(label) > 64)
to_chat(user, "Label too big!")
return
- user.visible_message("[user] labels [A] as [label].", \
- "You label [A] as [label].")
- investigate_log("[key_name(user)] ([ADMIN_FLW(user,"FLW")]) labelled \"[A]\" ([ADMIN_VV(A, "VV")]) with \"[label]\" at [COORD(A.loc)] [ADMIN_JMP(A)].", INVESTIGATE_RENAME) // Investigate goes BEFORE rename so the original name is preserved in the log
- A.AddComponent(/datum/component/label, label)
- playsound(A, 'sound/items/handling/component_pickup.ogg', 20, TRUE)
+ user.visible_message("[user] labels [target] as [label].", \
+ "You label [target] as [label].")
+ investigate_log("[key_name(user)] ([ADMIN_FLW(user,"FLW")]) labelled \"[target]\" ([ADMIN_VV(target, "VV")]) with \"[label]\" at [COORD(target.loc)] [ADMIN_JMP(target)].", INVESTIGATE_RENAME) // Investigate goes BEFORE rename so the original name is preserved in the log
+ target.AddComponent(/datum/component/label, label)
+ playsound(target, 'sound/items/handling/component_pickup.ogg', 20, TRUE)
labels_left--
-/obj/item/hand_labeler/attack_self__legacy__attackchain(mob/user as mob)
+/obj/item/hand_labeler/activate_self(mob/user)
+ if(..())
+ return
+
// off -> normal
// normal or goal -> off
mode = !mode
@@ -65,22 +83,27 @@
else
to_chat(user, "You turn off \the [src].")
-/obj/item/hand_labeler/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/hand_labeler_refill))
- to_chat(user, "You insert [I] into [src].")
+/obj/item/hand_labeler/item_interaction(mob/living/user, obj/item/used, list/modifiers)
+ if(..())
+ return ITEM_INTERACT_COMPLETE
+
+ if(istype(used, /obj/item/hand_labeler_refill))
+ to_chat(user, "You insert [used] into [src].")
user.drop_item()
- qdel(I)
+ qdel(used)
labels_left = initial(labels_left) //Yes, it's capped at its initial value
- else if(istype(I, /obj/item/card/id))
- var/obj/item/card/id/id = I
+ return ITEM_INTERACT_COMPLETE
+ else if(istype(used, /obj/item/card/id))
+ var/obj/item/card/id/id = used
if(istype(id, /obj/item/card/id/guest) || !id.registered_name)
to_chat(user, "Invalid ID card.")
- return
- label = id.registered_name
- mode = LABEL_MODE_GOAL
- to_chat(user, "You configure the hand labeler with [I].")
- icon_state = "labeler1"
-
+ return ITEM_INTERACT_COMPLETE
+ else
+ label = id.registered_name
+ mode = LABEL_MODE_GOAL
+ to_chat(user, "You configure the hand labeler with [used].")
+ icon_state = "labeler1"
+ return ITEM_INTERACT_COMPLETE
/obj/item/hand_labeler_refill
name = "hand labeler paper roll"
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index 59e45f6f463e..04d168a11581 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -525,7 +525,7 @@
toner = 0
/obj/machinery/photocopier/MouseDrop_T(mob/target, mob/living/user)
- if(!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || isAI(user) || target.move_resist > user.pull_force)
+ if(!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || is_ai(user) || target.move_resist > user.pull_force)
return
if(check_mob()) //is target mob or another mob on this photocopier already?
return
diff --git a/code/modules/paperwork/silicon_photography.dm b/code/modules/paperwork/silicon_photography.dm
index dedde0f789f2..f8180ef4a5ab 100644
--- a/code/modules/paperwork/silicon_photography.dm
+++ b/code/modules/paperwork/silicon_photography.dm
@@ -165,7 +165,7 @@
deletepicture(src)
/obj/item/camera/siliconcam/proc/getsource()
- if(isAI(loc))
+ if(is_ai(loc))
return src
var/mob/living/silicon/robot/C = loc
diff --git a/code/modules/pda/nanobank.dm b/code/modules/pda/nanobank.dm
index 2d992b968516..ab3f53561529 100644
--- a/code/modules/pda/nanobank.dm
+++ b/code/modules/pda/nanobank.dm
@@ -331,7 +331,7 @@
error_message(user, "Incorrect Credentials")
/datum/data/pda/app/nanobank/proc/input_account_pin(mob/user)
- var/attempt_pin = tgui_input_number(user, "Enter pin code", "NanoBank Account Auth", max_value = 99999)
+ var/attempt_pin = tgui_input_number(user, "Enter pin code", "NanoBank Account Auth", max_value = BANK_PIN_MAX, min_value = BANK_PIN_MIN)
if(!user_account || isnull(attempt_pin))
return
return attempt_pin
@@ -467,7 +467,7 @@
var/attempt_pin = pin
if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempt_pin)
//if pin is not given, we'll prompt them here
- attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction")
+ attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction", max_value = BANK_PIN_MAX, min_value = BANK_PIN_MIN)
if(!attempt_pin)
return FALSE
var/is_admin = is_admin(user)
diff --git a/code/modules/power/apc/apc.dm b/code/modules/power/apc/apc.dm
index 6552d232ce6b..ffb063c52d4e 100644
--- a/code/modules/power/apc/apc.dm
+++ b/code/modules/power/apc/apc.dm
@@ -518,7 +518,7 @@
/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob)
if(user.can_admin_interact())
return TRUE
- if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
+ if(is_ai(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
return TRUE
else
return !locked
@@ -526,7 +526,7 @@
/obj/machinery/power/apc/proc/is_locked(mob/user as mob)
if(user.can_admin_interact())
return FALSE
- if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
+ if(is_ai(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
return FALSE
else
return locked
diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm
index f6a3a4ea5590..211be3cdf4d2 100644
--- a/code/modules/power/engines/supermatter/supermatter.dm
+++ b/code/modules/power/engines/supermatter/supermatter.dm
@@ -143,7 +143,7 @@
/// Refered to as eer on the moniter. This value effects gas output, heat, damage, and radiation.
var/power = 0
/// A bonus to rad production equal to EER multiplied by the bonus given by each gas. The bonus gets higher the more gas there is in the chamber.
- var/gas_coefficient
+ var/gas_coefficient = 0
///Determines the rate of positve change in gas comp values
var/gas_change_rate = 0.05
diff --git a/code/modules/power/generators/portable generators/pacman.dm b/code/modules/power/generators/portable generators/pacman.dm
index f350c7bf8a11..12705003760b 100644
--- a/code/modules/power/generators/portable generators/pacman.dm
+++ b/code/modules/power/generators/portable generators/pacman.dm
@@ -263,7 +263,7 @@
var/list/data = list()
data["active"] = active
- if(isAI(user))
+ if(is_ai(user))
data["is_ai"] = TRUE
else if(isrobot(user) && !Adjacent(user))
data["is_ai"] = TRUE
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index 86ff7958f7a5..59e51c611ac4 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -52,7 +52,7 @@
impact_light_color_override = LIGHT_COLOR_DARKRED
/obj/item/projectile/beam/laser/ai_turret/prehit(atom/target)
- if(isAI(target))
+ if(is_ai(target))
damage = 0 //cheater cheater I don't want AI to die to stupid placement eater
nodamage = 1
if(isliving(target))
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index 0ea0f0e2f01e..33a01ea0c848 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -273,7 +273,7 @@
// mouse drop another mob or self
//
/obj/machinery/disposal/MouseDrop_T(mob/living/target, mob/living/user)
- if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || isAI(user))
+ if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || is_ai(user))
return
// Animals cannot put mobs other than themselves into disposals.
@@ -389,7 +389,7 @@
/obj/machinery/disposal/ui_data(mob/user)
var/list/data = list()
- data["isAI"] = isAI(user)
+ data["is_ai"] = is_ai(user)
data["flushing"] = flush
data["mode"] = mode
data["pressure"] = round(clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100),1)
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index ddc87780cbc3..1a391158a0c2 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -308,7 +308,7 @@
. = ..()
/obj/machinery/disposal/delivery_chute/Bumped(atom/movable/AM) //Go straight into the chute
- if(isprojectile(AM) || isAI(AM) || QDELETED(AM))
+ if(isprojectile(AM) || is_ai(AM) || QDELETED(AM))
return
// We may already contain the object because thrown objects
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index a5e0ff230ee8..24b5de832aa1 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -399,3 +399,15 @@
reagents_list = list("fuel" = 30)
build_path = /obj/item/chemical_canister/pyrotechnics
category = list("Weapons")
+
+/datum/design/nt_mantis
+ name = "'Scylla' mantis blade implant"
+ desc = "A reverse-engineered mantis blade implant. While the monomolecular edge was lost, they remain deadly weapons."
+ id = "mantis_blade_nt"
+ req_tech = list("materials" = 7, "engineering" = 6, "combat" = 7, "syndicate" = 6)
+ build_type = PROTOLATHE | MECHFAB
+ construction_time = 6 SECONDS
+ materials = list(MAT_METAL = 10000, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_TITANIUM = 3000, MAT_DIAMOND = 4000)
+ build_path = /obj/item/organ/internal/cyberimp/arm/nt_mantis
+ category = list("Medical", "Weapons")
+
diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
index fe330b6a36ea..84a60c49cf08 100644
--- a/code/modules/research/xenobiology/xenobio_camera.dm
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -1,26 +1,3 @@
-//Xenobio control console
-/mob/camera/ai_eye/remote/xenobio
- visible_icon = 1
- icon = 'icons/obj/abductor.dmi'
- icon_state = "camera_target"
- // The Xenobio Console does not trigger the AI Detector
- ai_detector_visible = FALSE
- /// Area that the xenobio camera eye is allowed to travel
- var/allowed_area = null
-
-/mob/camera/ai_eye/remote/xenobio/Initialize(mapload)
- . = ..()
- var/area/A = get_area(loc)
- allowed_area = A.name
-
-/mob/camera/ai_eye/remote/xenobio/setLoc(t)
- var/area/new_area = get_area(t)
- if(!new_area)
- return
- if(new_area.name != allowed_area && !new_area.xenobiology_compatible)
- return
- return ..()
-
/*
* # Slime Management Console
*
@@ -54,6 +31,10 @@
if(!connected_recycler)
locate_recycler()
+/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
+ eyeobj = new /mob/camera/eye/xenobio(loc, name, src, current_user)
+ give_eye_control(current_user)
+
/obj/machinery/computer/camera_advanced/xenobio/proc/locate_recycler()
for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers)
if(get_area(recycler) == get_area(loc))
@@ -79,14 +60,6 @@
stored_slimes -= A
return ..()
-/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
- eyeobj = new /mob/camera/ai_eye/remote/xenobio(get_turf(src))
- eyeobj.origin = src
- eyeobj.visible_icon = TRUE
- eyeobj.acceleration = FALSE
- eyeobj.icon = 'icons/obj/abductor.dmi'
- eyeobj.icon_state = "camera_target"
-
/obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/carbon/user)
..()
@@ -223,13 +196,13 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(iswallturf(remote_eye.loc))
to_chat(owner, "You can't place slime here.")
return
- else if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ else if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in X.stored_slimes)
X.release_slime(S, remote_eye.loc)
else
@@ -243,10 +216,10 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
if(length(X.stored_slimes) >= X.max_slimes)
break
@@ -265,11 +238,11 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
if(LAZYLEN(SSmobs.cubemonkeys) >= GLOB.configuration.general.monkey_cube_cap)
to_chat(owner, "Bluespace harmonics prevent the spawning of more than [GLOB.configuration.general.monkey_cube_cap] monkeys on the station at one time!")
return
@@ -309,14 +282,14 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
if(!recycler)
to_chat(owner, "There is no connected monkey recycler. Use a multitool to link one.")
return
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/carbon/human/M in remote_eye.loc)
if(issmall(M) && M.stat)
M.visible_message("[M] vanishes as [M.p_theyre()] reclaimed for recycling!")
@@ -334,9 +307,9 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
slime_scan(S, C)
else
@@ -351,14 +324,14 @@
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(QDELETED(X.current_potion))
to_chat(owner, "No potion loaded.")
return
- if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
X.current_potion.attack__legacy__attackchain(S, C)
break
@@ -416,22 +389,22 @@
// Scans slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/S)
- if(!GLOB.cameranet.checkTurfVis(S.loc))
+ if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
slime_scan(S, C)
//Feeds a potion to slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/S)
- if(!GLOB.cameranet.checkTurfVis(S.loc))
+ if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(!X.current_potion)
@@ -442,11 +415,11 @@
//Picks up slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/S)
- if(!GLOB.cameranet.checkTurfVis(S.loc))
+ if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
@@ -463,11 +436,11 @@
//Place slimes
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/T)
- if(!GLOB.cameranet.checkTurfVis(T))
+ if(!GLOB.cameranet.check_turf_vis(T))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
if(iswallturf(T))
@@ -479,11 +452,11 @@
//Place monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/T)
- if(!GLOB.cameranet.checkTurfVis(T))
+ if(!GLOB.cameranet.check_turf_vis(T))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
var/area/turfarea = get_area(T)
@@ -513,11 +486,11 @@
//Pick up monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/human/M)
- if(!GLOB.cameranet.checkTurfVis(M.loc))
+ if(!GLOB.cameranet.check_turf_vis(M.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
+ var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(M.loc)
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index f70f0fb3f5f3..9c3c2b4ac48c 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -182,7 +182,7 @@
attempt_hijack_stage(user)
/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
- var/is_ai = isAI(user)
+ var/is_ai = is_ai(user)
if(!Adjacent(user) && !is_ai)
return
if(!ishuman(user) && !is_ai) //No, xenomorphs, constructs and traitors in cyborgs can not hack it.
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
index ac9c5a4add94..9cdaca55d547 100644
--- a/code/modules/shuttle/navigation_computer.dm
+++ b/code/modules/shuttle/navigation_computer.dm
@@ -55,8 +55,8 @@
shuttle_port = null
return
- eyeobj = new /mob/camera/ai_eye/remote/shuttle_docker(get_turf(locate("landmark*Observer-Start")), src) // There should always be an observer start landmark
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ eyeobj = new /mob/camera/eye/shuttle_docker(get_turf(locate("landmark*Observer-Start")), name, src, current_user) // There should always be an observer start landmark
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
the_eye.setDir(shuttle_port.dir)
var/turf/origin = locate(shuttle_port.x + x_offset, shuttle_port.y + y_offset, shuttle_port.z)
for(var/V in shuttle_port.shuttle_areas)
@@ -72,11 +72,12 @@
I.plane = 0
I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
the_eye.placement_images[I] = list(x_off, y_off)
+ give_eye_control(current_user)
/obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user)
..()
if(!QDELETED(user) && user.client)
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/to_add = list()
to_add += the_eye.placement_images
to_add += the_eye.placed_images
@@ -89,7 +90,7 @@
/obj/machinery/computer/camera_advanced/shuttle_docker/remove_eye_control(mob/living/user)
..()
if(!QDELETED(user) && user.client)
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/to_remove = list()
to_remove += the_eye.placement_images
to_remove += the_eye.placed_images
@@ -103,8 +104,8 @@
if(designating_target_loc || !current_user)
return
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
- var/landing_clear = checkLandingSpot()
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
+ var/landing_clear = check_landing_spot()
if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED))
to_chat(current_user, "Targeting transit location, please wait [DisplayTimeText(designate_time)]...")
designating_target_loc = the_eye.loc
@@ -115,7 +116,7 @@
if(!wait_completed)
to_chat(current_user, "Operation aborted.")
return
- landing_clear = checkLandingSpot()
+ landing_clear = check_landing_spot()
if(landing_clear != SHUTTLE_DOCKER_LANDING_CLEAR)
switch(landing_clear)
@@ -162,7 +163,7 @@
return TRUE
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/image_cache = the_eye.placement_images
the_eye.setDir(turn(the_eye.dir, -90))
for(var/i in 1 to length(image_cache))
@@ -175,10 +176,10 @@
var/Tmp = x_offset
x_offset = y_offset
y_offset = -Tmp
- checkLandingSpot()
+ check_landing_spot()
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingSpot()
- var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/check_landing_spot()
+ var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/turf/eyeturf = get_turf(the_eye)
if(!eyeturf)
return SHUTTLE_DOCKER_BLOCKED
@@ -253,34 +254,6 @@
if(dock)
jumpto_ports[dock.id] = TRUE
-/mob/camera/ai_eye/remote/shuttle_docker
- visible_icon = FALSE
- use_static = FALSE
- simulated = FALSE
- // The Shuttle Docker does not trigger the AI Detector
- ai_detector_visible = FALSE
- var/list/placement_images = list()
- var/list/placed_images = list()
-
-/mob/camera/ai_eye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin)
- src.origin = origin
- return ..()
-
-/mob/camera/ai_eye/remote/shuttle_docker/setLoc(T)
- if(isspaceturf(get_turf(T)) || isspacearea(get_area(T)) || istype(get_area(T), /area/shuttle))
- ..()
- var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
- console.checkLandingSpot()
- return
- else
- return
-
-/mob/camera/ai_eye/remote/shuttle_docker/update_remote_sight(mob/living/user)
- user.sight = SEE_TURFS
-
- ..()
- return TRUE
-
/datum/action/innate/shuttledocker_rotate
name = "Rotate"
button_overlay_icon = 'icons/mob/actions/actions_mecha.dmi'
@@ -290,7 +263,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
origin.rotateLandingSpot()
@@ -303,7 +276,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
origin.placeLandingSpot(target)
@@ -315,7 +288,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
- var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/console = remote_eye.origin
playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
@@ -337,7 +310,7 @@
var/turf/T = get_turf(L[selected])
if(T)
playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
- remote_eye.setLoc(T)
+ remote_eye.set_loc(T)
to_chat(target, "Jumped to [selected]")
else
playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/modules/supply/supply_console.dm b/code/modules/supply/supply_console.dm
index eb371ac7cc25..dbb5db070066 100644
--- a/code/modules/supply/supply_console.dm
+++ b/code/modules/supply/supply_console.dm
@@ -462,7 +462,7 @@
var/attempt_pin = pin
if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempt_pin)
//if pin is not given, we'll prompt them here
- attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction", max_value = 99999)
+ attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction", max_value = BANK_PIN_MAX, min_value = BANK_PIN_MIN)
if(!Adjacent(user) || !attempt_pin)
return FALSE
var/is_admin = is_admin(user)
diff --git a/code/modules/supply/supply_packs/pack_organic.dm b/code/modules/supply/supply_packs/pack_organic.dm
index 92a6af557e4f..ca1fb83e2889 100644
--- a/code/modules/supply/supply_packs/pack_organic.dm
+++ b/code/modules/supply/supply_packs/pack_organic.dm
@@ -37,6 +37,22 @@
cost = 500
containername = "Pizza crate"
+/datum/supply_packs/organic/fancyparty
+ name = "Executive Party Crate"
+ contains = list(/obj/item/food/sliceable/cheesewheel/edam,
+ /obj/item/food/sliceable/cheesewheel/blue,
+ /obj/item/food/sliceable/cheesewheel/camembert,
+ /obj/item/food/sliceable/cheesewheel/camembert,
+ /obj/item/food/sliceable/cheesewheel/smoked,
+ /obj/item/reagent_containers/drinks/bottle/wine,
+ /obj/item/food/caviar,
+ /obj/item/food/caviar,
+ /obj/item/reagent_containers/drinks/drinkingglass,
+ /obj/item/reagent_containers/drinks/drinkingglass)
+ cost = 1000
+ containername = "Executive Party crate"
+ containertype = /obj/structure/closet/crate/freezer/deluxe
+
/// its a bit hacky...
/datum/supply_packs/misc/randomised/ingredients
num_contained = 25
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index 03738d2aa37b..d3a4834906dd 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -836,3 +836,99 @@
if(emp_proof)
return
muscle_implant.emp_act(severity, owner)
+
+// Mantis blades
+
+/obj/item/melee/mantis_blade
+ name = "mantis blade"
+ desc = "A blade designed to be hidden just beneath the skin. The brain is directly linked to this bad boy, allowing it to spring into action. \
+ When both blades are equipped, they enable the user to perform double attacks."
+ icon = 'icons/obj/weapons/melee.dmi'
+ lefthand_file = 'icons/mob/inhands/implants_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/implants_righthand.dmi'
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ new_attack_chain = TRUE
+ var/double_attack = TRUE
+ var/double_attack_cd = 1.5 // seconds, so every second attack
+ sharp = TRUE
+ w_class = WEIGHT_CLASS_BULKY
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "lacerated", "ripped", "diced", "cut")
+
+/obj/item/melee/mantis_blade/equipped(mob/user, slot, initial)
+ . = ..()
+ if(slot == ITEM_SLOT_LEFT_HAND)
+ transform = matrix(-1, 0, 0, 0, 1, 0)
+ else
+ transform = null
+
+// make double attack if blades in both hands and not on CD
+/obj/item/melee/mantis_blade/attack(mob/living/target, mob/living/user, params)
+ var/obj/item/melee/mantis_blade/secondblade = user.get_inactive_hand()
+ if(!istype(secondblade, /obj/item/melee/mantis_blade) || !double_attack)
+ return ..()
+
+ double_attack(target, user, params, secondblade)
+ return FINISH_ATTACK
+
+/obj/item/melee/mantis_blade/proc/double_attack(mob/living/target, mob/living/user, params, obj/item/melee/mantis_blade/secondblade)
+ // first attack
+ single_attack(target, user, params)
+ user.changeNext_move(CLICK_CD_MELEE)
+ // second attack
+ addtimer(CALLBACK(secondblade, PROC_REF(single_attack), target, user, params), 0.2 SECONDS) // not instant second attack
+
+/obj/item/melee/mantis_blade/proc/single_attack(mob/living/target, mob/living/user, params)
+ if(QDELETED(src))
+ return
+ double_attack = FALSE
+ attack(target, user, params)
+ addtimer(VARSET_CALLBACK(src, double_attack, TRUE), double_attack_cd SECONDS)
+
+/obj/item/melee/mantis_blade/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/forces_doors_open/mantis)
+
+/obj/item/melee/mantis_blade/syndicate
+ name = "'Naginata' mantis blade"
+ icon_state = "syndie_mantis"
+ item_state = "syndie_mantis"
+ force = 20
+ armour_penetration_percentage = 30
+
+/obj/item/melee/mantis_blade/syndicate/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.35, _parryable_attack_types = NON_PROJECTILE_ATTACKS, _parry_cooldown = (4 / 3) SECONDS) // 0.3333 seconds of cooldown for 75% uptime, non projectile
+
+/obj/item/melee/mantis_blade/nt
+ name = "'Scylla' mantis blade"
+ icon_state = "mantis"
+ item_state = "mantis"
+ force = 18
+
+/obj/item/melee/mantis_blade/nt/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.35, _parryable_attack_types = NON_PROJECTILE_ATTACKS, _parry_cooldown = (5 / 3) SECONDS) // 0.666667 seconds for 60% uptime, non projectile
+
+// Mantis blades implants
+/obj/item/organ/internal/cyberimp/arm/syndie_mantis
+ name = "'Naginata' mantis blade implants"
+ desc = "A powerful and concealable mantis blade with a monomolecular edge, produced by Cybersun Industries. Cuts through flesh and armor alike with ease."
+ origin_tech = "materials=5;combat=5;biotech=5;syndicate=4"
+ contents = newlist(/obj/item/melee/mantis_blade/syndicate)
+ icon_state = "syndie_mantis"
+ icon = 'icons/obj/weapons/melee.dmi'
+
+/obj/item/organ/internal/cyberimp/arm/syndie_mantis/l
+ parent_organ = "l_arm"
+
+/obj/item/organ/internal/cyberimp/arm/nt_mantis
+ name = "'Scylla' mantis blade implant"
+ desc = "A reverse-engineered mantis blade design produced by Nanotrasen. While still quite deadly, the loss of the monomolecular blade has drastically reduced its armor penetration capability."
+ origin_tech = "materials=5;combat=5;biotech=5;syndicate=4"
+ contents = newlist(/obj/item/melee/mantis_blade/nt)
+ icon_state = "mantis"
+ icon = 'icons/obj/weapons/melee.dmi'
+
+/obj/item/organ/internal/cyberimp/arm/nt_mantis/l
+ parent_organ = "l_arm"
+
diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm
index 92ee6f5ebfc1..3c64e68538e7 100644
--- a/code/modules/surgery/organs/autosurgeon.dm
+++ b/code/modules/surgery/organs/autosurgeon.dm
@@ -152,4 +152,10 @@
/obj/item/autosurgeon/organ/syndicate/oneuse/hardened_heart
starting_organ = /obj/item/organ/internal/heart/cybernetic/upgraded/hardened
+/obj/item/autosurgeon/organ/syndicate/oneuse/syndie_mantis
+ starting_organ = /obj/item/organ/internal/cyberimp/arm/syndie_mantis
+
+/obj/item/autosurgeon/organ/syndicate/oneuse/syndie_mantis/l
+ starting_organ = /obj/item/organ/internal/cyberimp/arm/syndie_mantis/l
+
#undef INFINITE
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index a4e35b9bec99..289beee4223b 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -166,8 +166,8 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
var/mob/living/carbon/human/H = L
if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
continue
- if(istype(L, /mob/camera/ai_eye))
- var/mob/camera/ai_eye/ai_eye = L
+ if(istype(L, /mob/camera/eye/ai))
+ var/mob/camera/eye/ai/ai_eye = L
if(ai_eye.relay_speech && ai_eye.ai)
listeners += ai_eye.ai
else
diff --git a/code/modules/tgui/modules/crew_monitor.dm b/code/modules/tgui/modules/crew_monitor.dm
index 7b8df4bae23e..f10b8ac3b59b 100644
--- a/code/modules/tgui/modules/crew_monitor.dm
+++ b/code/modules/tgui/modules/crew_monitor.dm
@@ -37,7 +37,7 @@
switch(action)
if("track")
var/mob/living/carbon/human/H = locate(params["track"]) in GLOB.human_list
- if(isAI(usr))
+ if(is_ai(usr))
var/mob/living/silicon/ai/AI = usr
if(hassensorlevel(H, SUIT_SENSOR_TRACKING))
AI.ai_actual_track(H)
@@ -105,7 +105,7 @@
data["offsetX"] = offset_x
data["offsetY"] = offset_y
- data["isAI"] = isAI(user)
+ data["isAI"] = is_ai(user)
data["isObserver"] = isobserver(user)
data["ignoreSensors"] = ignore_sensors
data["crewmembers"] = GLOB.crew_repository.health_data(viewing_current_z_level, ignore_sensors)
diff --git a/code/modules/tgui/modules/law_manager.dm b/code/modules/tgui/modules/law_manager.dm
index 001a8bdc31c4..4e8cf27f7919 100644
--- a/code/modules/tgui/modules/law_manager.dm
+++ b/code/modules/tgui/modules/law_manager.dm
@@ -140,7 +140,7 @@
if("notify_laws")
to_chat(owner, "Law Notice")
owner.laws.show_laws(owner)
- if(isAI(owner))
+ if(is_ai(owner))
var/mob/living/silicon/ai/AI = owner
for(var/mob/living/silicon/robot/R in AI.connected_robots)
to_chat(R, "Law Notice")
@@ -178,7 +178,7 @@
package_laws(data, "inherent_laws", owner.laws.inherent_laws)
package_laws(data, "supplied_laws", owner.laws.supplied_laws)
- data["isAI"] = isAI(owner)
+ data["isAI"] = is_ai(owner)
data["isMalf"] = is_malf(user)
data["isSlaved"] = owner.is_slaved()
data["isAdmin"] = is_admin(user)
diff --git a/code/modules/tgui/plugins/tgui_login.dm b/code/modules/tgui/plugins/tgui_login.dm
index 0f80f0a13fe4..ea1beabaf6e0 100644
--- a/code/modules/tgui/plugins/tgui_login.dm
+++ b/code/modules/tgui/plugins/tgui_login.dm
@@ -56,7 +56,7 @@ GLOBAL_LIST(ui_logins)
"rank" = state.rank,
"logged_in" = state.logged_in,
)
- data["isAI"] = isAI(user)
+ data["isAI"] = is_ai(user)
data["isRobot"] = isrobot(user)
data["isAdmin"] = user.can_admin_interact()
@@ -133,7 +133,7 @@ GLOBAL_LIST(ui_logins)
else
to_chat(usr, "Access Denied")
return
- else if(login_type == LOGIN_TYPE_AI && isAI(usr))
+ else if(login_type == LOGIN_TYPE_AI && is_ai(usr))
state.name = usr.name
state.rank = "AI"
else if(login_type == LOGIN_TYPE_ROBOT && isrobot(usr))
diff --git a/config/example/config.toml b/config/example/config.toml
index 730df5df72de..014e63b393d5 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -269,17 +269,18 @@ gamemode_probabilities = [
{ gamemode = "changeling", probability = 0 },
{ gamemode = "cult", probability = 3 },
{ gamemode = "extend-a-traitormongous", probability = 2 }, # Autotraitor
- { gamemode = "extended", probability = 3 },
+ { gamemode = "extended", probability = 0 },
{ gamemode = "nuclear", probability = 2 },
{ gamemode = "raginmages", probability = 0 },
{ gamemode = "revolution", probability = 0 },
- { gamemode = "traitor", probability = 2 },
- { gamemode = "traitorchan", probability = 3 },
- { gamemode = "traitorvamp", probability = 3 },
- { gamemode = "vampchan", probability = 3 },
- { gamemode = "vampire", probability = 3 },
+ { gamemode = "traitor", probability = 0 },
+ { gamemode = "traitorchan", probability = 0 },
+ { gamemode = "traitorvamp", probability = 0 },
+ { gamemode = "vampchan", probability = 0 },
+ { gamemode = "vampire", probability = 0 },
{ gamemode = "wizard", probability = 2 },
- { gamemode = "trifecta", probability = 3 },
+ { gamemode = "trifecta", probability = 0 },
+ { gamemode = "dynamic", probability = 20 },
{ gamemode = "antag_mix", probability = 3 },
]
# Do we want the amount of traitors to scale with population?
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 6bb68da79e71..0439814e3101 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/mob/inhands/implants_lefthand.dmi b/icons/mob/inhands/implants_lefthand.dmi
index 74340cf1dc6b..5206dc0cb98a 100644
Binary files a/icons/mob/inhands/implants_lefthand.dmi and b/icons/mob/inhands/implants_lefthand.dmi differ
diff --git a/icons/mob/inhands/implants_righthand.dmi b/icons/mob/inhands/implants_righthand.dmi
index 36900ec8a30e..4d4d29ea56d7 100644
Binary files a/icons/mob/inhands/implants_righthand.dmi and b/icons/mob/inhands/implants_righthand.dmi differ
diff --git a/icons/mob/robot_items.dmi b/icons/mob/robot_items.dmi
index 7e9553d5fc6d..95e835789f55 100644
Binary files a/icons/mob/robot_items.dmi and b/icons/mob/robot_items.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index 30cfc1ceb279..add3bd451903 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/obj/closet.dmi b/icons/obj/closet.dmi
index ec827afe1c6f..40239587a352 100644
Binary files a/icons/obj/closet.dmi and b/icons/obj/closet.dmi differ
diff --git a/icons/obj/crates.dmi b/icons/obj/crates.dmi
index deedf5f28566..ce6214e90112 100644
Binary files a/icons/obj/crates.dmi and b/icons/obj/crates.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index c72150b5abc7..25884cea23a9 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index 7b8e4a277aa0..9fdfed83f5f3 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/obj/weapons/melee.dmi b/icons/obj/weapons/melee.dmi
index 8ee6b5cd9ec1..87dfa467f41d 100644
Binary files a/icons/obj/weapons/melee.dmi and b/icons/obj/weapons/melee.dmi differ
diff --git a/modular_ss220/aesthetics/library/code/library.dm b/modular_ss220/aesthetics/library/code/library.dm
index 46a6045bc6a4..502c788ca5b3 100644
--- a/modular_ss220/aesthetics/library/code/library.dm
+++ b/modular_ss220/aesthetics/library/code/library.dm
@@ -1,5 +1,2 @@
-/obj/structure/bookcase
- icon = 'modular_ss220/aesthetics/library/icons/library.dmi'
-
/obj/machinery/bookbinder
icon = 'modular_ss220/aesthetics/library/icons/library.dmi'
diff --git a/modular_ss220/pixel_shift/code/pixel_shift_component.dm b/modular_ss220/pixel_shift/code/pixel_shift_component.dm
index e463072b7b28..b6da4c78a331 100644
--- a/modular_ss220/pixel_shift/code/pixel_shift_component.dm
+++ b/modular_ss220/pixel_shift/code/pixel_shift_component.dm
@@ -12,7 +12,7 @@
/datum/component/pixel_shift/Initialize(...)
. = ..()
- if(!isliving(parent) || isAI(parent))
+ if(!isliving(parent) || is_ai(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/pixel_shift/RegisterWithParent()
diff --git a/modular_ss220/sm_space_drop/code/sm_drop_button.dm b/modular_ss220/sm_space_drop/code/sm_drop_button.dm
index c3376a007942..46cf218ccc76 100644
--- a/modular_ss220/sm_space_drop/code/sm_drop_button.dm
+++ b/modular_ss220/sm_space_drop/code/sm_drop_button.dm
@@ -50,7 +50,7 @@
if(active)
return
- if(isAI(user))
+ if(is_ai(user))
return
add_fingerprint(user)
diff --git a/modular_ss220/species/serpentids/code/organs/internal/implants/serpentid_mantis_blades.dm b/modular_ss220/species/serpentids/code/organs/internal/implants/serpentid_mantis_blades.dm
index 53d87cb04f5d..0eb45db8c0dd 100644
--- a/modular_ss220/species/serpentids/code/organs/internal/implants/serpentid_mantis_blades.dm
+++ b/modular_ss220/species/serpentids/code/organs/internal/implants/serpentid_mantis_blades.dm
@@ -15,6 +15,6 @@
/obj/item/kitchen/knife/combat/serpentblade/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_ADVANCED_SURGICAL, ROUNDSTART_TRAIT)
- ADD_TRAIT(src, TRAIT_FORCES_OPEN_DOORS_ITEM, ROUNDSTART_TRAIT)
+ AddComponent(/datum/component/forces_doors_open)
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS)
AddComponent(/datum/component/double_attack)
diff --git a/paradise.dme b/paradise.dme
index 498bfe367fe9..e98f464ac85f 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -449,6 +449,7 @@
#include "code\datums\components\edit_complainer.dm"
#include "code\datums\components\emissive_blocker.dm"
#include "code\datums\components\footstep.dm"
+#include "code\datums\components\forces_doors_open.dm"
#include "code\datums\components\fullauto.dm"
#include "code\datums\components\ghost_direct_control.dm"
#include "code\datums\components\label.dm"
@@ -762,6 +763,8 @@
#include "code\game\gamemodes\cult\cult_structures.dm"
#include "code\game\gamemodes\cult\ritual.dm"
#include "code\game\gamemodes\cult\runes.dm"
+#include "code\game\gamemodes\dynamic\antag_rulesets.dm"
+#include "code\game\gamemodes\dynamic\dynamic.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\malfunction\Malf_Modules.dm"
#include "code\game\gamemodes\miniantags\abduction\abductee_objectives.dm"
@@ -2215,6 +2218,15 @@
#include "code\modules\mob\transform_procs.dm"
#include "code\modules\mob\typing_indicator.dm"
#include "code\modules\mob\camera\camera_mob.dm"
+#include "code\modules\mob\camera\cameranet.dm"
+#include "code\modules\mob\camera\chunk.dm"
+#include "code\modules\mob\camera\eye.dm"
+#include "code\modules\mob\camera\eye\abductor.dm"
+#include "code\modules\mob\camera\eye\ai_eye.dm"
+#include "code\modules\mob\camera\eye\hologram_eye.dm"
+#include "code\modules\mob\camera\eye\shuttle_navigator.dm"
+#include "code\modules\mob\camera\eye\syndicate.dm"
+#include "code\modules\mob\camera\eye\xenobio_eye.dm"
#include "code\modules\mob\dead\dead.dm"
#include "code\modules\mob\dead\observer\observer_base.dm"
#include "code\modules\mob\dead\observer\observer_login.dm"
@@ -2350,9 +2362,6 @@
#include "code\modules\mob\living\silicon\ai\ai_update_status.dm"
#include "code\modules\mob\living\silicon\ai\latejoin.dm"
#include "code\modules\mob\living\silicon\ai\ai_verbs\botcall.dm"
-#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm"
-#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
-#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
#include "code\modules\mob\living\silicon\decoy\decoy.dm"
#include "code\modules\mob\living\silicon\decoy\decoy_death.dm"
#include "code\modules\mob\living\silicon\decoy\decoy_life.dm"
diff --git a/rustlibs.dll b/rustlibs.dll
index 2a3c4e0617fe..195a8ab83301 100644
Binary files a/rustlibs.dll and b/rustlibs.dll differ
diff --git a/rustlibs_prod.dll b/rustlibs_prod.dll
index e1f1f49841b2..018a6878f0f7 100644
Binary files a/rustlibs_prod.dll and b/rustlibs_prod.dll differ