diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index 31103fee93ee..3137088d1c90 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -17,12 +17,10 @@
#define NOTE_ADMIN 1
///This note is used by staff for positive record keeping.
#define NOTE_MERIT 2
-///These notes are used by respective whitelist councils for record keeping.
-#define NOTE_COMMANDER 3
-#define NOTE_SYNTHETIC 4
-#define NOTE_YAUTJA 5
+///These notes are automatically applied by the Whitelist Panel.
+#define NOTE_WHITELIST 3
///Note categories in text form, in order of their numerical #defines.
-GLOBAL_LIST_INIT(note_categories, list("Admin", "Merit", "Commanding Officer", "Synthetic", "Yautja"))
+GLOBAL_LIST_INIT(note_categories, list("Admin", "Merit", "Whitelist"))
#define ADMIN_FLW(user) "(FLW)"
#define ADMIN_PP(user) "(PP)"
diff --git a/code/__DEFINES/dcs/signals/atom/signals_item.dm b/code/__DEFINES/dcs/signals/atom/signals_item.dm
index f038d4c5dbce..5ba79960657b 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_item.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_item.dm
@@ -29,8 +29,10 @@
#define COMSIG_ITEM_PICKUP "item_pickup"
-///from /obj/item/device/camera/broadcasting/attack_self
+///from /obj/item/device/camera/broadcasting
#define COMSIG_BROADCAST_GO_LIVE "broadcast_live"
+#define COMSIG_BROADCAST_HEAR_TALK "broadcast_hear_talk"
+#define COMSIG_BROADCAST_SEE_EMOTE "broadcast_see_emote"
/// from /obj/item/proc/mob_can_equip
#define COMSIG_ITEM_ATTEMPTING_EQUIP "item_attempting_equip"
diff --git a/code/__DEFINES/dcs/signals/atom/signals_obj.dm b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
index ed27c26b2115..c850b2a52e03 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_obj.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
@@ -30,8 +30,16 @@
#define COMSIG_TENT_COLLAPSING "tent_collapsing"
/// from /obj/proc/afterbuckle()
-#define COSMIG_OBJ_AFTER_BUCKLE "signal_obj_after_buckle"
+#define COMSIG_OBJ_AFTER_BUCKLE "signal_obj_after_buckle"
+/// from /datum/cm_objective/retrieve_data/disk/process()
+#define COMSIG_INTEL_DISK_LOST_POWER "intel_disk_lost_power"
+
+/// from /datum/cm_objective/retrieve_data/disk/complete()
+#define COMSIG_INTEL_DISK_COMPLETED "intel_disk_completed"
+
+/// from /obj/vehicle/multitile/arc/toggle_antenna()
+#define COMSIG_ARC_ANTENNA_TOGGLED "arc_antenna_toggled"
/// from /obj/structure/machinery/cryopod/go_out()
#define COMSIG_CRYOPOD_GO_OUT "cryopod_go_out"
diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm
index f975a67824ef..306f37deb8cb 100644
--- a/code/__DEFINES/dcs/signals/signals_global.dm
+++ b/code/__DEFINES/dcs/signals/signals_global.dm
@@ -69,6 +69,9 @@
/// From /proc/biohazard_lockdown()
#define COMSIG_GLOB_RESEARCH_LOCKDOWN "!research_lockdown_closed"
#define COMSIG_GLOB_RESEARCH_LIFT "!research_lockdown_opened"
+/// From /proc/aicore_lockdown()
+#define COMSIG_GLOB_AICORE_LOCKDOWN "!aicore_lockdown_closed"
+#define COMSIG_GLOB_AICORE_LIFT "!aicore_lockdown_opened"
/// From /obj/structure/machinery/power/reactor/proc/set_overloading() : (set_overloading)
#define COMSIG_GLOB_GENERATOR_SET_OVERLOADING "!generator_set_overloading"
diff --git a/code/__DEFINES/equipment.dm b/code/__DEFINES/equipment.dm
index 4368bf304a48..b37ae0d59d64 100644
--- a/code/__DEFINES/equipment.dm
+++ b/code/__DEFINES/equipment.dm
@@ -44,6 +44,8 @@
#define USES_HEARING (1<<17)
/// Should we use the initial icon for display? Mostly used by overlay only objects
#define HTML_USE_INITAL_ICON (1<<18)
+// Whether or not the object sees emotes
+#define USES_SEEING (1<<19)
//==========================================================================================
diff --git a/code/__DEFINES/job.dm b/code/__DEFINES/job.dm
index 1b2907cf57ce..f869357efd1b 100644
--- a/code/__DEFINES/job.dm
+++ b/code/__DEFINES/job.dm
@@ -206,6 +206,7 @@ GLOBAL_LIST_INIT(job_command_roles, JOB_COMMAND_ROLES_LIST)
//-------- WY Goons --------//
#define JOB_WY_GOON "WY Corporate Security"
+#define JOB_WY_GOON_TECH "WY Corporate Security Technician"
#define JOB_WY_GOON_LEAD "WY Corporate Security Lead"
#define JOB_WY_GOON_RESEARCHER "WY Research Consultant"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index e2bd868f9a80..dbd8dbe7ce41 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -120,6 +120,7 @@
#define CANDAZE (1<<18)
#define CANSLOW (1<<19)
#define NO_PERMANENT_DAMAGE (1<<20)
+#define CORRUPTED_ALLY (1<<21)
// =============================
// hive types
diff --git a/code/__DEFINES/vehicle.dm b/code/__DEFINES/vehicle.dm
index 8a1617229926..5eb6a824d8ac 100644
--- a/code/__DEFINES/vehicle.dm
+++ b/code/__DEFINES/vehicle.dm
@@ -7,7 +7,7 @@
#define HDPT_TURRET "turret"
#define HDPT_SPECIAL "special" //special pre-installed hardpoints with unique behaviour
-#define HDPT_LAYER_WHEELS 1
+#define HDPT_LAYER_WHEELS 0.01 // so it appears below xenomorphs and other mobs
#define HDPT_LAYER_SUPPORT 2
#define HDPT_LAYER_ARMOR 3
#define HDPT_LAYER_TURRET 4
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 8d77920a59cc..71fc9162dcaa 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -16,7 +16,8 @@
return FALSE
/atom/movable/attackby(obj/item/W, mob/living/user)
- if(W)
+ . = ..()
+ if(W && !.)
if(!(W.flags_item & NOBLUDGEON))
visible_message(SPAN_DANGER("[src] has been hit by [user] with [W]."), null, null, 5, CHAT_TYPE_MELEE_HIT)
user.animation_attack_on(src)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index f265315460e3..40400f2a151c 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -50,7 +50,7 @@ SUBSYSTEM_DEF(ticker)
var/totalPlayers = 0 //used for pregame stats on statpanel
var/totalPlayersReady = 0 //used for pregame stats on statpanel
- var/tutorial_disabled = FALSE //zonenote
+ var/tutorial_disabled = FALSE
/datum/controller/subsystem/ticker/Initialize(timeofday)
load_mode()
diff --git a/code/datums/ammo/bullet/arc.dm b/code/datums/ammo/bullet/arc.dm
new file mode 100644
index 000000000000..5e74508e04b2
--- /dev/null
+++ b/code/datums/ammo/bullet/arc.dm
@@ -0,0 +1,14 @@
+/datum/ammo/bullet/re700
+ name = "rotary cannon bullet"
+ icon_state = "autocannon"
+ damage_falloff = 0
+ flags_ammo_behavior = AMMO_BALLISTIC
+
+ accuracy = HIT_ACCURACY_TIER_7
+ scatter = 0
+ damage = 30
+ damage_var_high = PROJECTILE_VARIANCE_TIER_8
+ penetration = ARMOR_PENETRATION_TIER_2
+ accurate_range = 10
+ max_range = 12
+ shell_speed = AMMO_SPEED_TIER_6
diff --git a/code/datums/components/disk_reader.dm b/code/datums/components/disk_reader.dm
new file mode 100644
index 000000000000..6292519893e9
--- /dev/null
+++ b/code/datums/components/disk_reader.dm
@@ -0,0 +1,87 @@
+/datum/component/disk_reader
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Ref to the inserted disk
+ var/obj/item/disk/objective/disk
+
+/datum/component/disk_reader/Initialize()
+ . = ..()
+ if(!istype(parent, /obj/structure/machinery))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/disk_reader/Destroy(force, silent)
+ handle_qdel()
+ return ..()
+
+/datum/component/disk_reader/RegisterWithParent()
+ ..()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_disk_insert))
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdel))
+ RegisterSignal(parent, COMSIG_INTEL_DISK_COMPLETED, PROC_REF(on_disk_complete))
+ RegisterSignal(parent, COMSIG_INTEL_DISK_LOST_POWER, PROC_REF(on_power_lost))
+
+/datum/component/disk_reader/UnregisterFromParent()
+ ..()
+ handle_qdel()
+
+/datum/component/disk_reader/proc/handle_qdel()
+ SIGNAL_HANDLER
+ QDEL_NULL(disk)
+
+/datum/component/disk_reader/proc/on_disk_insert(datum/source, obj/item/disk/objective/potential_disk, mob/living/inserter, params)
+ SIGNAL_HANDLER
+
+ if(!istype(potential_disk) || !potential_disk.objective)
+ return
+
+ if(disk)
+ to_chat(inserter, SPAN_WARNING("There's already a disk inside [parent], wait for it to finish first!"))
+ return COMPONENT_NO_AFTERATTACK
+
+ if(potential_disk.objective.state == OBJECTIVE_COMPLETE)
+ to_chat(inserter, SPAN_WARNING("The reader displays a message stating this disk has already been read and refuses to accept it."))
+ return COMPONENT_NO_AFTERATTACK
+
+ INVOKE_ASYNC(src, PROC_REF(handle_disk_insert), potential_disk, inserter)
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/component/disk_reader/proc/handle_disk_insert(obj/item/disk/objective/potential_disk, mob/living/inserter)
+ if(tgui_input_text(inserter, "Enter the encryption key", "Decrypting [potential_disk]", "") != potential_disk.objective.decryption_password)
+ to_chat(inserter, SPAN_WARNING("The reader buzzes, ejecting the disk."))
+ return
+
+ if(disk)
+ to_chat(inserter, SPAN_WARNING("There's already a disk inside [parent], wait for it to finish first!"))
+ return
+
+ if(!(potential_disk in inserter.contents))
+ return
+
+ potential_disk.objective.activate()
+
+ inserter.drop_inv_item_to_loc(potential_disk, parent)
+ disk = potential_disk
+ to_chat(inserter, SPAN_NOTICE("You insert [potential_disk] and enter the decryption key."))
+ inserter.count_niche_stat(STATISTICS_NICHE_DISK)
+
+/datum/component/disk_reader/proc/on_disk_complete(datum/source)
+ SIGNAL_HANDLER
+ var/atom/atom_parent = parent
+
+ atom_parent.visible_message("[atom_parent] pings softly as the upload finishes and ejects [disk].")
+ playsound(atom_parent, 'sound/machines/screen_output1.ogg', 25, 1)
+ disk.forceMove(get_turf(atom_parent))
+ disk.name = "[disk.name] (complete)"
+ disk.objective.award_points()
+ disk.retrieve_objective.state = OBJECTIVE_ACTIVE
+ disk.retrieve_objective.activate()
+ disk = null
+
+/datum/component/disk_reader/proc/on_power_lost(datum/source)
+ SIGNAL_HANDLER
+ var/atom/atom_parent = parent
+
+ atom_parent.visible_message(SPAN_WARNING("[atom_parent] powers down mid-operation as the area loses power."))
+ playsound(atom_parent, 'sound/machines/terminal_shutdown.ogg', 25, 1)
+ SSobjectives.stop_processing_objective(src)
+ disk.forceMove(get_turf(atom_parent))
+ disk = null
diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm
index 8737fba08db9..ce6fe35e4a28 100644
--- a/code/datums/components/weed_food.dm
+++ b/code/datums/components/weed_food.dm
@@ -87,7 +87,7 @@
parent_buckle = null
/datum/component/weed_food/RegisterWithParent()
- RegisterSignal(parent_mob, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ RegisterSignal(parent_mob, COMSIG_MOVABLE_TURF_ENTERED, PROC_REF(on_move))
RegisterSignal(parent_mob, list(COMSIG_LIVING_REJUVENATED, COMSIG_HUMAN_REVIVED), PROC_REF(on_rejuv))
RegisterSignal(parent_mob, COMSIG_HUMAN_SET_UNDEFIBBABLE, PROC_REF(on_update))
RegisterSignal(parent_mob, COMSIG_LIVING_PREIGNITION, PROC_REF(on_preignition))
@@ -98,7 +98,7 @@
/datum/component/weed_food/UnregisterFromParent()
if(parent_mob)
UnregisterSignal(parent_mob, list(
- COMSIG_MOVABLE_MOVED,
+ COMSIG_MOVABLE_TURF_ENTERED,
COMSIG_LIVING_REJUVENATED,
COMSIG_HUMAN_REVIVED,
COMSIG_HUMAN_SET_UNDEFIBBABLE,
@@ -109,12 +109,12 @@
if(parent_turf)
UnregisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH)
if(parent_buckle)
- UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE)
if(parent_nest)
UnregisterSignal(parent_nest, COMSIG_PARENT_QDELETING)
UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
-/// SIGNAL_HANDLER for COMSIG_MOVABLE_MOVED
+/// SIGNAL_HANDLER for COMSIG_MOVABLE_TURF_ENTERED
/datum/component/weed_food/proc/on_move()
SIGNAL_HANDLER
@@ -148,7 +148,7 @@
qdel(src)
-/// SIGNAL_HANDLER for COSMIG_OBJ_AFTER_BUCKLE
+/// SIGNAL_HANDLER for COMSIG_OBJ_AFTER_BUCKLE
/datum/component/weed_food/proc/on_after_buckle(obj/source, mob/buckled)
SIGNAL_HANDLER
@@ -220,12 +220,12 @@
return FALSE // Still buckled to the same thing
if(!istype(parent_mob.buckled, /obj/structure/bed/nest))
if(parent_buckle) // Still have a lingering reference somehow?
- UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE)
parent_buckle = parent_mob.buckled
- RegisterSignal(parent_mob.buckled, COSMIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
+ RegisterSignal(parent_mob.buckled, COMSIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
return FALSE
if(parent_buckle)
- UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE)
parent_buckle = null
if(parent_mob.is_xeno_grabbable())
@@ -282,16 +282,16 @@
return FALSE // Still buckled to the same thing somehow?
if(!istype(parent_mob.buckled, /obj/structure/bed/nest))
if(parent_buckle) // Still have a lingering reference somehow?
- UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE)
parent_buckle = parent_mob.buckled
- RegisterSignal(parent_mob.buckled, COSMIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
+ RegisterSignal(parent_mob.buckled, COMSIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
return FALSE
else
parent_nest = parent_mob.buckled
RegisterSignal(parent_nest, COMSIG_PARENT_QDELETING, PROC_REF(on_nest_deletion))
if(parent_buckle)
- UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE)
parent_buckle = null
if(SEND_SIGNAL(parent_mob, COMSIG_ATTEMPT_MOB_PULL) & COMPONENT_CANCEL_MOB_PULL)
diff --git a/code/datums/effects/neurotoxin.dm b/code/datums/effects/neurotoxin.dm
index 490ed213292b..b7402ca370fd 100644
--- a/code/datums/effects/neurotoxin.dm
+++ b/code/datums/effects/neurotoxin.dm
@@ -37,6 +37,10 @@
return FALSE
if(affected_mob.stat == DEAD)
return
+
+ if(issynth(affected_atom))
+ return
+
// General effects
affected_mob.last_damage_data = cause_data
affected_mob.apply_stamina_damage(stam_dam)
diff --git a/code/datums/elements/bullet_trait/iff.dm b/code/datums/elements/bullet_trait/iff.dm
index ab48b29f4812..cee36acbed80 100644
--- a/code/datums/elements/bullet_trait/iff.dm
+++ b/code/datums/elements/bullet_trait/iff.dm
@@ -46,7 +46,7 @@
// The cache is reset when the user drops their ID
/datum/element/bullet_trait_iff/proc/get_user_iff_group(mob/living/carbon/human/user)
if(!ishuman(user))
- return user.faction_group
+ return user?.faction_group
var/iff_group = LAZYACCESS(iff_group_cache, user)
if(isnull(iff_group))
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index b691d87a2169..6e84052720d4 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -112,6 +112,7 @@
var/paygrade = user.get_paygrade()
var/formatted_message = "[paygrade][user] [msg]"
var/user_turf = get_turf(user)
+ var/list/seeing_obj = list()
if (user.client)
for(var/mob/ghost as anything in GLOB.dead_mob_list)
if(!ghost.client || isnewplayer(ghost))
@@ -132,12 +133,18 @@
if(emote_type & EMOTE_VISIBLE)
var/list/viewers = get_mobs_in_view(7, user)
for(var/mob/current_mob in viewers)
+ for(var/obj/object in current_mob.contents)
+ if((object.flags_atom & USES_SEEING))
+ seeing_obj |= object
if(!(current_mob.client?.prefs.toggles_langchat & LANGCHAT_SEE_EMOTES))
viewers -= current_mob
run_langchat(user, viewers)
else if(emote_type & EMOTE_AUDIBLE)
var/list/heard = get_mobs_in_view(7, user)
for(var/mob/current_mob in heard)
+ for(var/obj/object in current_mob.contents)
+ if((object.flags_atom & USES_HEARING))
+ seeing_obj |= object
if(current_mob.ear_deaf)
heard -= current_mob
continue
@@ -145,6 +152,9 @@
heard -= current_mob
run_langchat(user, heard)
+ for(var/obj/object as anything in seeing_obj)
+ object.see_emote(user, msg, (emote_type & EMOTE_AUDIBLE))
+
SEND_SIGNAL(user, COMSIG_MOB_EMOTED(key))
diff --git a/code/datums/entities/player.dm b/code/datums/entities/player.dm
index aefa81672b54..a62e663ba21c 100644
--- a/code/datums/entities/player.dm
+++ b/code/datums/entities/player.dm
@@ -90,11 +90,12 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
/datum/entity/player/proc/add_note(note_text, is_confidential, note_category = NOTE_ADMIN, is_ban = FALSE, duration = null)
var/client/admin = usr.client
// do all checks here, especially for sensitive stuff like this
- if(!admin || !admin.player_data)
- return FALSE
- if(note_category == NOTE_ADMIN || is_confidential)
- if (!AHOLD_IS_MOD(admin.admin_holder))
+ if(!(note_category == NOTE_WHITELIST))
+ if(!admin || !admin.player_data)
return FALSE
+ if(note_category == NOTE_ADMIN || is_confidential)
+ if (!AHOLD_IS_MOD(admin.admin_holder))
+ return FALSE
// this is here for a short transition period when we still are testing DB notes and constantly deleting the file
if(CONFIG_GET(flag/duplicate_notes_to_file))
@@ -119,7 +120,7 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
note.note_category = note_category
note.is_ban = is_ban
note.ban_time = duration
- note.admin_rank = admin.admin_holder.rank
+ note.admin_rank = admin.admin_holder ? admin.admin_holder.rank : "Non-Staff"
// since admin is in game, their player_data has to be populated. This is also checked above
note.admin_id = admin.player_data.id
note.admin = admin.player_data
@@ -134,13 +135,17 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
notes.Add(note)
return TRUE
-/datum/entity/player/proc/remove_note(note_id)
+/datum/entity/player/proc/remove_note(note_id, whitelist = FALSE)
+ if(IsAdminAdvancedProcCall())
+ return PROC_BLOCKED
var/client/admin = usr.client
// do all checks here, especially for sensitive stuff like this
if(!admin || !admin.player_data)
return FALSE
- if (!AHOLD_IS_MOD(admin.admin_holder))
+ if((!AHOLD_IS_MOD(admin.admin_holder)) && !whitelist)
+ return FALSE
+ if(whitelist && !(isSenator(admin) || CLIENT_HAS_RIGHTS(admin, R_PERMISSIONS)))
return FALSE
// this is here for a short transition period when we still are testing DB notes and constantly deleting the file
diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm
index 5e57b8f5616c..88785474bc23 100644
--- a/code/datums/mob_hud.dm
+++ b/code/datums/mob_hud.dm
@@ -422,6 +422,10 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list(
if(hive && hive.color)
holder3.color = hive.color
+ if(status_flags & CORRUPTED_ALLY)
+ holder4.color = "#80ff80"
+ holder4.icon_state = "hudalien_ally"
+
if(stat == DEAD || status_flags & FAKEDEATH)
if(revive_enabled)
if(!client)
diff --git a/code/datums/skills/uscm.dm b/code/datums/skills/uscm.dm
index 1e0dedf5dd05..9c56e7f62a88 100644
--- a/code/datums/skills/uscm.dm
+++ b/code/datums/skills/uscm.dm
@@ -239,7 +239,8 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_SPEC_WEAPONS = SKILL_SPEC_ALL,
SKILL_EXECUTION = SKILL_EXECUTION_TRAINED, //can BE people
- SKILL_INTEL = SKILL_INTEL_EXPERT
+ SKILL_INTEL = SKILL_INTEL_EXPERT,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/commander
@@ -261,7 +262,8 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_EXECUTION = SKILL_EXECUTION_TRAINED, //can BE people
SKILL_INTEL = SKILL_INTEL_EXPERT,
- SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED //can change ship alt
+ SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED, //can change ship alt
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/XO
@@ -282,6 +284,7 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_INTEL = SKILL_INTEL_EXPERT,
SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/SO
@@ -299,6 +302,7 @@ COMMAND STAFF
SKILL_POWERLOADER = SKILL_POWERLOADER_TRAINED,
SKILL_JTAC = SKILL_JTAC_EXPERT,
SKILL_INTEL = SKILL_INTEL_TRAINED,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/SEA
diff --git a/code/datums/skills/wygoons.dm b/code/datums/skills/wygoons.dm
new file mode 100644
index 000000000000..2d2c247bd1ea
--- /dev/null
+++ b/code/datums/skills/wygoons.dm
@@ -0,0 +1,36 @@
+/datum/skills/wy_goon
+ name = "Corporate Security"
+ skills = list(
+ SKILL_CQC = SKILL_CQC_SKILLED,
+ SKILL_POLICE = SKILL_POLICE_SKILLED,
+ SKILL_FIREMAN = SKILL_FIREMAN_SKILLED,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ SKILL_MELEE_WEAPONS = SKILL_MELEE_TRAINED,
+ )
+
+/datum/skills/wy_goon_tech
+ name = "Corporate Security Support Technician"
+ skills = list(
+ SKILL_CQC = SKILL_CQC_SKILLED,
+ SKILL_POLICE = SKILL_POLICE_SKILLED,
+ SKILL_FIREMAN = SKILL_FIREMAN_TRAINED,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ SKILL_MELEE_WEAPONS = SKILL_MELEE_TRAINED,
+ SKILL_MEDICAL = SKILL_MEDICAL_TRAINED,
+ SKILL_CONSTRUCTION = SKILL_CONSTRUCTION_ENGI,
+ SKILL_ENGINEER = SKILL_ENGINEER_ENGI,
+ )
+
+/datum/skills/wy_goon_lead
+ name = "Corporate Security Leader"
+ skills = list(
+ SKILL_CQC = SKILL_CQC_SKILLED,
+ SKILL_POLICE = SKILL_POLICE_SKILLED,
+ SKILL_FIREMAN = SKILL_FIREMAN_SKILLED,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ SKILL_MELEE_WEAPONS = SKILL_MELEE_TRAINED,
+ SKILL_MEDICAL = SKILL_MEDICAL_TRAINED,
+ SKILL_CONSTRUCTION = SKILL_CONSTRUCTION_ENGI,
+ SKILL_ENGINEER = SKILL_ENGINEER_ENGI,
+ SKILL_LEADERSHIP = SKILL_LEAD_TRAINED,
+ )
diff --git a/code/datums/supply_packs/ammo.dm b/code/datums/supply_packs/ammo.dm
index 0929f24f7c95..2e81d8fed164 100644
--- a/code/datums/supply_packs/ammo.dm
+++ b/code/datums/supply_packs/ammo.dm
@@ -135,7 +135,7 @@
group = "Ammo"
/datum/supply_packs/ammo_m4ra_mag_box_ap
- name = "Magazine box (MRRA, 16x AP mags)"
+ name = "Magazine box (M4RA, 16x AP mags)"
contains = list(
/obj/item/ammo_box/magazine/m4ra/ap,
)
diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm
index 5dad91d27ed4..43ce36ec2b64 100644
--- a/code/datums/supply_packs/vehicle_ammo.dm
+++ b/code/datums/supply_packs/vehicle_ammo.dm
@@ -148,3 +148,15 @@
containertype = /obj/structure/closet/crate/ammo
containername = "M-87F Flare Launcher ammo crate"
group = "Vehicle Ammo"
+
+/datum/supply_packs/ammo_arcsentry
+ name = "RE700 Rotary Cannon magazines (x3)"
+ contains = list(
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ )
+ cost = 20
+ containertype = /obj/structure/closet/crate/ammo
+ containername = "RE700 Rotary Cannon ammo crate"
+ group = "Vehicle Ammo"
diff --git a/code/datums/supply_packs/vehicle_equipment.dm b/code/datums/supply_packs/vehicle_equipment.dm
new file mode 100644
index 000000000000..df106761d467
--- /dev/null
+++ b/code/datums/supply_packs/vehicle_equipment.dm
@@ -0,0 +1,9 @@
+/datum/supply_packs/arcsentry_replacement
+ name = "Replacement RE700 Rotary Cannon (x1)"
+ contains = list(
+ /obj/item/hardpoint/primary/arc_sentry,
+ )
+ cost = 25
+ containertype = /obj/structure/closet/crate/weapon
+ containername = "RE700 Rotary Cannon crate"
+ group = "Vehicle Equipment"
diff --git a/code/datums/tutorial/xenomorph/xenomorph_basic.dm b/code/datums/tutorial/xenomorph/xenomorph_basic.dm
index 965f7b55d3c0..276d2ac824f0 100644
--- a/code/datums/tutorial/xenomorph/xenomorph_basic.dm
+++ b/code/datums/tutorial/xenomorph/xenomorph_basic.dm
@@ -160,7 +160,16 @@
add_highlight(hugger, COLOR_YELLOW)
message_to_player("This is a facehugger, highlighted in yellow. Pick up the facehugger by clicking it.")
message_to_player("Stand next to the downed human and click them to apply the facehugger. Or drop the facehugger near them to see it leap onto their face automatically.")
- RegisterSignal(human_dummy, COMSIG_HUMAN_IMPREGNATE, PROC_REF(nest_cap_phase))
+ RegisterSignal(hugger, COMSIG_PARENT_QDELETING, PROC_REF(on_hugger_deletion))
+ RegisterSignal(human_dummy, COMSIG_HUMAN_IMPREGNATE, PROC_REF(nest_cap_phase), override = TRUE)
+
+/datum/tutorial/xenomorph/basic/proc/on_hugger_deletion(hugger)
+ SIGNAL_HANDLER
+ TUTORIAL_ATOM_FROM_TRACKING(/obj/effect/alien/resin/special/eggmorph, morpher)
+ morpher.stored_huggers = 1
+ add_highlight(morpher, COLOR_YELLOW)
+ message_to_player("Click the egg morpher to take a facehugger.")
+ RegisterSignal(xeno, COMSIG_XENO_TAKE_HUGGER_FROM_MORPHER, PROC_REF(take_facehugger_phase))
/datum/tutorial/xenomorph/basic/proc/nest_cap_phase()
SIGNAL_HANDLER
@@ -168,6 +177,7 @@
TUTORIAL_ATOM_FROM_TRACKING(/obj/item/clothing/mask/facehugger, hugger)
UnregisterSignal(human_dummy, COMSIG_MOB_TAKE_DAMAGE)
UnregisterSignal(human_dummy, COMSIG_HUMAN_IMPREGNATE)
+ UnregisterSignal(hugger, COMSIG_PARENT_QDELETING)
remove_highlight(hugger)
message_to_player("We should nest the infected human to make sure they don't get away.")
diff --git a/code/datums/vehicles.dm b/code/datums/vehicles.dm
index 36ac96938c6b..67070dd04c0b 100644
--- a/code/datums/vehicles.dm
+++ b/code/datums/vehicles.dm
@@ -37,3 +37,7 @@
/datum/map_template/interior/van
name = "Van"
interior_id = "van"
+
+/datum/map_template/interior/arc
+ name = "ARC"
+ interior_id = "arc"
diff --git a/code/defines/procs/announcement.dm b/code/defines/procs/announcement.dm
index 2ebba6a774e8..bad07104db22 100644
--- a/code/defines/procs/announcement.dm
+++ b/code/defines/procs/announcement.dm
@@ -42,14 +42,19 @@
continue
if(is_mainship_level(H.z)) // People on ship see everything
continue
+
+ // If they have iff AND a marine headset they will recieve announcements
+ if ((FACTION_MARINE in H.wear_id?.faction_group) && (istype(H.wear_l_ear, /obj/item/device/radio/headset/almayer) || istype(H.wear_r_ear, /obj/item/device/radio/headset/almayer)))
+ continue
+
if((H.faction != faction_to_display && !add_PMCs) || (H.faction != faction_to_display && add_PMCs && !(H.faction in FACTION_LIST_WY)) && !(faction_to_display in H.faction_group)) //faction checks
targets.Remove(H)
switch(logging)
if(ARES_LOG_MAIN)
- log_ares_announcement(title, message)
+ log_ares_announcement(title, message, signature)
if(ARES_LOG_SECURITY)
- log_ares_security(title, message)
+ log_ares_security(title, message, signature)
else if(faction_to_display == "Everyone (-Yautja)")
for(var/mob/M in targets)
@@ -98,9 +103,9 @@
switch(logging)
if(ARES_LOG_MAIN)
- log_ares_announcement("[MAIN_AI_SYSTEM] Comms Update", message)
+ log_ares_announcement("Comms Update", message, MAIN_AI_SYSTEM)
if(ARES_LOG_SECURITY)
- log_ares_security("[MAIN_AI_SYSTEM] Security Update", message)
+ log_ares_security("Security Update", message, MAIN_AI_SYSTEM)
/proc/ai_silent_announcement(message, channel_prefix, bypass_cooldown = FALSE)
if(!message)
@@ -133,9 +138,9 @@
message += "
Signed by, [signature]"
switch(ares_logging)
if(ARES_LOG_MAIN)
- log_ares_announcement(title, message)
+ log_ares_announcement(title, message, signature)
if(ARES_LOG_SECURITY)
- log_ares_security(title, message)
+ log_ares_security(title, message, signature)
announcement_helper(message, title, targets, sound_to_play)
@@ -148,7 +153,7 @@
if(!ishuman(T) || isyautja(T) || !is_mainship_level((get_turf(T))?.z))
targets.Remove(T)
- log_ares_announcement("[title] Shipwide Update", message)
+ log_ares_announcement("Shipwide Update", message, title)
announcement_helper(message, title, targets, sound_to_play)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 21f7b6b0a9be..52a35b715b1a 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -45,7 +45,10 @@
if(orbiting)
orbiting.end_orbit(src)
orbiting = null
- vis_contents.Cut()
+
+ vis_locs = null //clears this atom out of all viscontents
+ if(length(vis_contents))
+ vis_contents.Cut()
. = ..()
moveToNullspace() //so we move into null space. Must be after ..() b/c atom's Dispose handles deleting our lighting stuff
diff --git a/code/game/cas_manager/datums/cas_fire_envelope.dm b/code/game/cas_manager/datums/cas_fire_envelope.dm
index cc38b034c764..864d7f23a3e8 100644
--- a/code/game/cas_manager/datums/cas_fire_envelope.dm
+++ b/code/game/cas_manager/datums/cas_fire_envelope.dm
@@ -3,7 +3,10 @@
var/list/datum/cas_fire_mission/missions
var/fire_length
var/grace_period //how much time you have after initiating fire mission and before you can't change firemissions
- var/flyto_period //how much time it takes from sound alarm start to first hit. CAS is vulnerable here
+ var/first_warning
+ var/second_warning
+ var/third_warning
+ var/execution_start
var/flyoff_period //how much time it takes after shots fired to get off the map. CAS is vulnerable here
var/cooldown_period //how much time you have to wait before new Fire Mission run
var/soundeffect //what sound effect to play
@@ -36,10 +39,6 @@
for(var/datum/cas_fire_mission/mission in missions)
.["missions"] += list(mission.ui_data(user))
-
-/datum/cas_fire_envelope/proc/get_total_duration()
- return grace_period+flyto_period+flyoff_period
-
/datum/cas_fire_envelope/proc/update_weapons(list/obj/structure/dropship_equipment/weapon/weapons)
for(var/datum/cas_fire_mission/mission in missions)
mission.update_weapons(weapons, fire_length)
@@ -242,14 +241,60 @@
/datum/cas_fire_envelope/proc/check_firemission_loc(datum/cas_signal/target_turf)
return TRUE //redefined in child class
-/**
- * Execute firemission.
- */
+/// Step 1: Sets the stat to FIRE_MISSION_STATE_ON_TARGET and starts the sound effect for the fire mission.
+/datum/cas_fire_envelope/proc/play_sound(atom/target_turf)
+ stat = FIRE_MISSION_STATE_ON_TARGET
+ change_current_loc(target_turf)
+ playsound(target_turf, soundeffect, vol = 70, vary = TRUE, sound_range = 50, falloff = 8)
+
+/// Step 2, 3, 4: Warns nearby mobs of the incoming fire mission. Warning as 1 is non-precise, whereas 2 and 3 are precise.
+/datum/cas_fire_envelope/proc/chat_warning(atom/target_turf, range = 10, warning_number = 1)
+ var/ds_identifier = "LARGE BIRD"
+ var/fm_identifier = "SPIT FIRE"
+ var/relative_dir
+ for(var/mob/mob in range(15, target_turf))
+ if (mob.mob_flags & KNOWS_TECHNOLOGY)
+ ds_identifier = "DROPSHIP"
+ fm_identifier = "FIRE"
+ if(get_turf(mob) == target_turf)
+ relative_dir = 0
+ else
+ relative_dir = Get_Compass_Dir(mob, target_turf)
+ switch(warning_number)
+ if(1)
+ mob.show_message( \
+ SPAN_HIGHDANGER("YOU HEAR THE [ds_identifier] ROAR AS IT PREPARES TO [fm_identifier] NEAR YOU!"),SHOW_MESSAGE_VISIBLE, \
+ SPAN_HIGHDANGER("YOU HEAR SOMETHING FLYING CLOSER TO YOU!") , SHOW_MESSAGE_AUDIBLE \
+ )
+ if(2)
+ mob.show_message( \
+ SPAN_HIGHDANGER("A [ds_identifier] FLIES [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_VISIBLE, \
+ SPAN_HIGHDANGER("YOU HEAR SOMETHING GO [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_AUDIBLE \
+ )
+ if(3)
+ mob.show_message( \
+ SPAN_HIGHDANGER("A [ds_identifier] FLIES [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_VISIBLE, \
+ SPAN_HIGHDANGER("YOU HEAR SOMETHING GO [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_AUDIBLE \
+ )
+
+/// Step 5: Actually executes the fire mission updating stat to FIRE_MISSION_STATE_FIRING and then FIRE_MISSION_STATE_OFF_TARGET
+/datum/cas_fire_envelope/proc/open_fire(atom/target_turf,datum/cas_fire_mission/mission,dir)
+ stat = FIRE_MISSION_STATE_FIRING
+ mission.execute_firemission(linked_console, target_turf, dir, fire_length, step_delay, src)
+ stat = FIRE_MISSION_STATE_OFF_TARGET
+
+/// Step 6: Sets the fire mission stat to FIRE_MISSION_STATE_COOLDOWN
+/datum/cas_fire_envelope/proc/flyoff()
+ stat = FIRE_MISSION_STATE_COOLDOWN
+
+/// Step 7: Sets the fire mission stat to FIRE_MISSION_STATE_IDLE
+/datum/cas_fire_envelope/proc/end_cooldown()
+ stat = FIRE_MISSION_STATE_IDLE
+
+
/datum/cas_fire_envelope/proc/execute_firemission_unsafe(datum/cas_signal/signal, turf/target_turf, dir, datum/cas_fire_mission/mission)
stat = FIRE_MISSION_STATE_IN_TRANSIT
to_chat(usr, SPAN_ALERT("Firemission underway!"))
- sleep(grace_period)
- stat = FIRE_MISSION_STATE_ON_TARGET
if(!target_turf)
stat = FIRE_MISSION_STATE_IDLE
mission_error = "Target Lost."
@@ -258,29 +303,27 @@
stat = FIRE_MISSION_STATE_IDLE
mission_error = "Target is off bounds or obstructed."
return
- change_current_loc(target_turf)
- playsound(source = target_turf, soundin = soundeffect, vol = 70, vary = TRUE, sound_range = 50, falloff = 8)
- for(var/mob/mob in range(15, target_turf))
- var/ds_identifier = "LARGE BIRD"
- var/fm_identifier = "SPIT FIRE"
- if (mob.mob_flags & KNOWS_TECHNOLOGY)
- ds_identifier = "DROPSHIP"
- fm_identifier = "FIRE"
+ var/obj/effect/firemission_effect = new(target_turf)
- mob.show_message( \
- SPAN_HIGHDANGER("YOU HEAR THE [ds_identifier] ROAR AS IT PREPARES TO [fm_identifier] NEAR YOU!"),SHOW_MESSAGE_VISIBLE, \
- SPAN_HIGHDANGER("YOU HEAR SOMETHING FLYING CLOSER TO YOU!") , SHOW_MESSAGE_AUDIBLE \
- )
+ firemission_effect.icon = 'icons/obj/items/weapons/projectiles.dmi'
+ firemission_effect.icon_state = "laser_target2"
+ firemission_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ firemission_effect.invisibility = INVISIBILITY_MAXIMUM
+ QDEL_IN(firemission_effect, 12 SECONDS)
- sleep(flyto_period)
- stat = FIRE_MISSION_STATE_FIRING
- mission.execute_firemission(linked_console, target_turf, dir, fire_length, step_delay, src)
- stat = FIRE_MISSION_STATE_OFF_TARGET
- sleep(flyoff_period)
- stat = FIRE_MISSION_STATE_COOLDOWN
- sleep(cooldown_period)
- stat = FIRE_MISSION_STATE_IDLE
+
+ notify_ghosts(header = "CAS Fire Mission", message = "[usr ? usr : "Someone"] is launching Fire Mission '[mission.name]' at [get_area(target_turf)].", source = firemission_effect)
+ msg_admin_niche("[usr ? key_name(usr) : "Someone"] is launching Fire Mission '[mission.name]' at ([target_turf.x],[target_turf.y],[target_turf.z]) [ADMIN_JMP(target_turf)]")
+
+
+ addtimer(CALLBACK(src, PROC_REF(play_sound), target_turf), grace_period)
+ addtimer(CALLBACK(src, PROC_REF(chat_warning), target_turf, 15, 1), first_warning)
+ addtimer(CALLBACK(src, PROC_REF(chat_warning), target_turf, 15, 2), second_warning)
+ addtimer(CALLBACK(src, PROC_REF(chat_warning), target_turf, 10, 3), third_warning)
+ addtimer(CALLBACK(src, PROC_REF(open_fire), target_turf, mission,dir), execution_start)
+ addtimer(CALLBACK(src, PROC_REF(flyoff)), flyoff_period)
+ addtimer(CALLBACK(src, PROC_REF(end_cooldown)), cooldown_period)
/**
* Change attack vector for firemission
@@ -324,10 +367,13 @@
/datum/cas_fire_envelope/uscm_dropship
fire_length = 12
- grace_period = 5 SECONDS
- flyto_period = 4 SECONDS //sleep in the FM itself has been increased by one more second
- flyoff_period = 5 SECONDS
- cooldown_period = 10 SECONDS
+ grace_period = 5 SECONDS
+ first_warning = 6 SECONDS
+ second_warning = 8 SECONDS
+ third_warning = 9 SECONDS
+ execution_start = 10 SECONDS
+ flyoff_period = 15 SECONDS
+ cooldown_period = 25 SECONDS
soundeffect = 'sound/weapons/dropship_sonic_boom.ogg' //BOOM~WOOOOOSH~HSOOOOOW~BOOM
step_delay = 3
max_offset = 12
diff --git a/code/game/cas_manager/datums/cas_fire_mission.dm b/code/game/cas_manager/datums/cas_fire_mission.dm
index 927dded210f0..dc55e057edcd 100644
--- a/code/game/cas_manager/datums/cas_fire_mission.dm
+++ b/code/game/cas_manager/datums/cas_fire_mission.dm
@@ -164,51 +164,6 @@
if(initial_turf == null || check(linked_console) != FIRE_MISSION_ALL_GOOD)
return FIRE_MISSION_NOT_EXECUTABLE
- var/obj/effect/firemission_effect = new(initial_turf)
-
- firemission_effect.icon = 'icons/obj/items/weapons/projectiles.dmi'
- firemission_effect.icon_state = "laser_target2"
- firemission_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- firemission_effect.invisibility = INVISIBILITY_MAXIMUM
- QDEL_IN(firemission_effect, 5 SECONDS)
-
- notify_ghosts(header = "CAS Fire Mission", message = "[usr ? usr : "Someone"] is launching Fire Mission '[name]' at [get_area(initial_turf)].", source = firemission_effect)
- msg_admin_niche("[usr ? key_name(usr) : "Someone"] is launching Fire Mission '[name]' at ([initial_turf.x],[initial_turf.y],[initial_turf.z]) [ADMIN_JMP(initial_turf)]")
-
- var/relative_dir
- for(var/mob/mob in range(15, initial_turf))
- if(get_turf(mob) == initial_turf)
- relative_dir = 0
- else
- relative_dir = Get_Compass_Dir(mob, initial_turf)
-
- var/ds_identifier = "LARGE BIRD"
- if (mob.mob_flags & KNOWS_TECHNOLOGY)
- ds_identifier = "DROPSHIP"
-
- mob.show_message( \
- SPAN_HIGHDANGER("A [ds_identifier] FLIES [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_VISIBLE, \
- SPAN_HIGHDANGER("YOU HEAR SOMETHING GO [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), SHOW_MESSAGE_AUDIBLE \
- )
-
- // Xenos have time to react to the first message
- sleep(1.5 SECONDS)
-
- for(var/mob/mob in range(10, initial_turf))
- if(get_turf(mob) == initial_turf)
- relative_dir = 0
- else
- relative_dir = Get_Compass_Dir(mob, initial_turf)
-
- var/ds_identifier = "LARGE BIRD"
- if (mob.mob_flags & KNOWS_TECHNOLOGY)
- ds_identifier = "DROPSHIP"
-
- mob.show_message( \
- SPAN_HIGHDANGER("A [ds_identifier] FIRES [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), 1, \
- SPAN_HIGHDANGER("YOU HEAR SOMETHING FIRE [SPAN_UNDERLINE(relative_dir ? uppertext(("TO YOUR " + dir2text(relative_dir))) : uppertext("right above you"))]!"), 2 \
- )
-
var/turf/current_turf = initial_turf
var/tally_step = steps / mission_length //how much shots we need before moving to next turf
var/next_step = tally_step //when we move to next turf
diff --git a/code/game/jobs/whitelist.dm b/code/game/jobs/whitelist.dm
index 09db84fec2c2..8cd91a494c83 100644
--- a/code/game/jobs/whitelist.dm
+++ b/code/game/jobs/whitelist.dm
@@ -24,6 +24,8 @@
if(isSenator(src))
add_verb(src, /client/proc/whitelist_panel)
+ if(isCouncil(src))
+ add_verb(src, /client/proc/other_records)
/client
var/datum/whitelist_panel/wl_panel
@@ -144,8 +146,10 @@ GLOBAL_LIST_INIT(misc_flags, list(
/datum/whitelist_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
- return
+ return FALSE
var/mob/user = ui.user
+ if(!isSenator(user.client) && !CLIENT_HAS_RIGHTS(user.client, R_PERMISSIONS))
+ return FALSE
switch(action)
if("go_back")
go_back()
@@ -165,6 +169,7 @@ GLOBAL_LIST_INIT(misc_flags, list(
return
var/datum/entity/player/player = get_player_from_key(player_key)
player.set_whitelist_status(new_rights)
+ player.add_note("Whitelists updated by [user.key]. Reason: '[reason]'.", FALSE, NOTE_WHITELIST)
to_chat(user, SPAN_HELPFUL("Whitelists for [player_key] updated."))
message_admins("Whitelists for [player_key] updated by [key_name(user)]. Reason: '[reason]'.")
log_admin("WHITELISTS: Flags for [player_key] changed from [target_rights] to [new_rights]. Reason: '[reason]'.")
diff --git a/code/game/machinery/ARES/ARES_interface.dm b/code/game/machinery/ARES/ARES_interface.dm
index 6cc7c220fd04..04547d079664 100644
--- a/code/game/machinery/ARES/ARES_interface.dm
+++ b/code/game/machinery/ARES/ARES_interface.dm
@@ -368,6 +368,10 @@
new_title = "[record.title] at [record.time]"
new_details = record.details
datacore.records_tech -= record
+ if(ARES_RECORD_FLIGHT)
+ new_title = "[record.title] at [record.time]"
+ new_details = record.details
+ datacore.records_flight -= record
new_delete.details = new_details
new_delete.user = last_login
@@ -416,7 +420,7 @@
shipwide_ai_announcement("ATTENTION! GENERAL QUARTERS. ALL HANDS, MAN YOUR BATTLESTATIONS.", MAIN_AI_SYSTEM, 'sound/effects/GQfullcall.ogg')
log_game("[key_name(user)] has called for general quarters via ARES.")
message_admins("[key_name_admin(user)] has called for general quarters via ARES.")
- log_ares_security("General Quarters", "[last_login] has called for general quarters via ARES.")
+ log_ares_security("General Quarters", "Called for general quarters via ARES.", last_login)
COOLDOWN_START(datacore, ares_quarters_cooldown, 10 MINUTES)
. = TRUE
@@ -438,7 +442,7 @@
log_game("[key_name(user)] has called for an emergency evacuation via ARES.")
message_admins("[key_name_admin(user)] has called for an emergency evacuation via ARES.")
- log_ares_security("Initiate Evacuation", "[last_login] has called for an emergency evacuation via ARES.")
+ log_ares_security("Initiate Evacuation", "Called for an emergency evacuation via ARES.", last_login)
. = TRUE
if("distress")
@@ -492,7 +496,7 @@
playsound_client(admin,'sound/effects/sos-morse-code.ogg',10)
message_admins("[key_name(user)] has requested use of Nuclear Ordnance (via ARES)! Reason: [reason] [CC_MARK(user)] (APPROVE) (DENY) [ADMIN_JMP_USER(user)] [CC_REPLY(user)]")
to_chat(user, SPAN_NOTICE("A nuclear ordnance request has been sent to USCM High Command for the following reason: [reason]"))
- log_ares_security("Nuclear Ordnance Request", "[last_login] has sent a request for nuclear ordnance for the following reason: [reason]")
+ log_ares_security("Nuclear Ordnance Request", "Sent a request for nuclear ordnance for the following reason: [reason]", last_login)
if(ares_can_interface())
ai_silent_announcement("[last_login] has sent a request for nuclear ordnance to USCM High Command.", ".V")
ai_silent_announcement("Reason given: [reason].", ".V")
@@ -514,9 +518,16 @@
playsound(src, 'sound/machines/chime.ogg', 15, 1)
COOLDOWN_START(sec_vent, vent_trigger_cooldown, COOLDOWN_ARES_VENT)
ares_apollo_talk("Nerve Gas release imminent from [sec_vent.vent_tag].")
- log_ares_security("Nerve Gas Release", "[last_login] released Nerve Gas from Vent '[sec_vent.vent_tag]'.")
+ log_ares_security("Nerve Gas Release", "Released Nerve Gas from Vent '[sec_vent.vent_tag]'.", last_login)
sec_vent.create_gas(VENT_GAS_CN20_XENO, 6, 5 SECONDS)
log_admin("[key_name(user)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
+ if("security_lockdown")
+ if(!COOLDOWN_FINISHED(datacore, aicore_lockdown))
+ to_chat(user, SPAN_BOLDWARNING("AI Core Lockdown procedures are on cooldown! They will be ready in [COOLDOWN_SECONDSLEFT(datacore, aicore_lockdown)] seconds!"))
+ return FALSE
+ aicore_lockdown(user)
+ return TRUE
+
if(playsound)
playsound(src, "keyboard_alt", 15, 1)
diff --git a/code/game/machinery/ARES/ARES_interface_admin.dm b/code/game/machinery/ARES/ARES_interface_admin.dm
index 586b01a51af9..758ba9fbb5e7 100644
--- a/code/game/machinery/ARES/ARES_interface_admin.dm
+++ b/code/game/machinery/ARES/ARES_interface_admin.dm
@@ -511,6 +511,6 @@
playsound(src, 'sound/machines/chime.ogg', 15, 1)
COOLDOWN_START(sec_vent, vent_trigger_cooldown, COOLDOWN_ARES_VENT)
ares_apollo_talk("Nerve Gas release imminent from [sec_vent.vent_tag].")
- log_ares_security("Nerve Gas Release", "[MAIN_AI_SYSTEM] released Nerve Gas from Vent '[sec_vent.vent_tag]'.")
+ log_ares_security("Nerve Gas Release", "Released Nerve Gas from Vent '[sec_vent.vent_tag]'.", MAIN_AI_SYSTEM)
sec_vent.create_gas(VENT_GAS_CN20_XENO, 6, 5 SECONDS)
log_admin("[key_name(user)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
diff --git a/code/game/machinery/ARES/ARES_interface_apollo.dm b/code/game/machinery/ARES/ARES_interface_apollo.dm
index 48fcad588574..243ecdf1355f 100644
--- a/code/game/machinery/ARES/ARES_interface_apollo.dm
+++ b/code/game/machinery/ARES/ARES_interface_apollo.dm
@@ -158,29 +158,29 @@
return
var/playsound = TRUE
- var/mob/living/carbon/human/operator = ui.user
+ var/mob/living/carbon/human/user = ui.user
switch (action)
if("go_back")
if(!last_menu)
- return to_chat(operator, SPAN_WARNING("Error, no previous page detected."))
+ return to_chat(user, SPAN_WARNING("Error, no previous page detected."))
var/temp_holder = current_menu
current_menu = last_menu
last_menu = temp_holder
if("login")
- var/obj/item/card/id/idcard = operator.get_active_hand()
+ var/obj/item/card/id/idcard = user.get_active_hand()
if(istype(idcard))
authentication = get_ares_access(idcard)
last_login = idcard.registered_name
- else if(operator.wear_id)
- idcard = operator.wear_id
+ else if(user.wear_id)
+ idcard = user.wear_id
if(istype(idcard))
authentication = get_ares_access(idcard)
last_login = idcard.registered_name
else
- to_chat(operator, SPAN_WARNING("You require an ID card to access this terminal!"))
+ to_chat(user, SPAN_WARNING("You require an ID card to access this terminal!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(authentication)
@@ -222,23 +222,23 @@
if("new_report")
var/priority_report = FALSE
- var/maint_type = tgui_input_list(operator, "What is the type of maintenance item you wish to report?", "Report Category", GLOB.maintenance_categories, 30 SECONDS)
+ var/maint_type = tgui_input_list(user, "What is the type of maintenance item you wish to report?", "Report Category", GLOB.maintenance_categories, 30 SECONDS)
switch(maint_type)
if("Major Structural Damage", "Fire", "Communications Failure", "Power Generation Failure")
priority_report = TRUE
if(!maint_type)
return FALSE
- var/details = tgui_input_text(operator, "What are the details for this report?", "Ticket Details", encode = FALSE)
+ var/details = tgui_input_text(user, "What are the details for this report?", "Ticket Details", encode = FALSE)
if(!details)
return FALSE
if((authentication >= APOLLO_ACCESS_REPORTER) && !priority_report)
- var/is_priority = tgui_alert(operator, "Is this a priority report?", "Priority designation", list("Yes", "No"))
+ var/is_priority = tgui_alert(user, "Is this a priority report?", "Priority designation", list("Yes", "No"))
if(is_priority == "Yes")
priority_report = TRUE
- var/confirm = alert(operator, "Please confirm the submission of your maintenance report. \n\n Priority: [priority_report ? "Yes" : "No"]\n Category: '[maint_type]'\n Details: '[details]'\n\n Is this correct?", "Confirmation", "Yes", "No")
+ var/confirm = alert(user, "Please confirm the submission of your maintenance report. \n\n Priority: [priority_report ? "Yes" : "No"]\n Category: '[maint_type]'\n Details: '[details]'\n\n Is this correct?", "Confirmation", "Yes", "No")
if(confirm == "Yes")
if(link)
var/datum/ares_ticket/maintenance/maint_ticket = new(last_login, maint_type, details, priority_report)
@@ -247,7 +247,7 @@
ares_apollo_talk("Priority Maintenance Report: [maint_type] - ID [maint_ticket.ticket_id]. Seek and resolve.")
else
send_notifcation()
- log_game("ARES: Maintenance Ticket '\ref[maint_ticket]' created by [key_name(operator)] as [last_login] with Category '[maint_type]' and Details of '[details]'.")
+ log_game("ARES: Maintenance Ticket '\ref[maint_ticket]' created by [key_name(user)] as [last_login] with Category '[maint_type]' and Details of '[details]'.")
return TRUE
return FALSE
@@ -259,14 +259,14 @@
var/assigned = ticket.ticket_assignee
if(assigned)
if(assigned == last_login)
- var/prompt = tgui_alert(operator, "You already claimed this ticket! Do you wish to drop your claim?", "Unclaim ticket", list("Yes", "No"))
+ var/prompt = tgui_alert(user, "You already claimed this ticket! Do you wish to drop your claim?", "Unclaim ticket", list("Yes", "No"))
if(prompt != "Yes")
return FALSE
/// set ticket back to pending
ticket.ticket_assignee = null
ticket.ticket_status = TICKET_PENDING
return claim
- var/choice = tgui_alert(operator, "This ticket has already been claimed by [assigned]! Do you wish to override their claim?", "Claim Override", list("Yes", "No"))
+ var/choice = tgui_alert(user, "This ticket has already been claimed by [assigned]! Do you wish to override their claim?", "Claim Override", list("Yes", "No"))
if(choice != "Yes")
claim = FALSE
if(claim)
@@ -279,9 +279,9 @@
if(!istype(ticket))
return FALSE
if(ticket.ticket_submitter != last_login)
- to_chat(operator, SPAN_WARNING("You cannot cancel a ticket that does not belong to you!"))
+ to_chat(user, SPAN_WARNING("You cannot cancel a ticket that does not belong to you!"))
return FALSE
- to_chat(operator, SPAN_WARNING("[ticket.ticket_type] [ticket.ticket_id] has been cancelled."))
+ to_chat(user, SPAN_WARNING("[ticket.ticket_type] [ticket.ticket_id] has been cancelled."))
ticket.ticket_status = TICKET_CANCELLED
if(ticket.ticket_priority)
ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been cancelled.")
@@ -294,9 +294,9 @@
if(!istype(ticket))
return FALSE
if(ticket.ticket_assignee != last_login && ticket.ticket_assignee) //must be claimed by you or unclaimed.)
- to_chat(operator, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
+ to_chat(user, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
return FALSE
- var/choice = tgui_alert(operator, "What do you wish to mark the ticket as?", "Mark", list(TICKET_COMPLETED, TICKET_REJECTED), 20 SECONDS)
+ var/choice = tgui_alert(user, "What do you wish to mark the ticket as?", "Mark", list(TICKET_COMPLETED, TICKET_REJECTED), 20 SECONDS)
switch(choice)
if(TICKET_COMPLETED)
ticket.ticket_status = TICKET_COMPLETED
@@ -308,39 +308,39 @@
ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been [choice] by [last_login].")
else
send_notifcation()
- to_chat(operator, SPAN_NOTICE("[ticket.ticket_type] [ticket.ticket_id] marked as [choice]."))
+ to_chat(user, SPAN_NOTICE("[ticket.ticket_type] [ticket.ticket_id] marked as [choice]."))
return TRUE
if("new_access")
- var/obj/item/card/id/idcard = operator.get_active_hand()
+ var/obj/item/card/id/idcard = user.get_active_hand()
var/has_id = FALSE
if(istype(idcard))
has_id = TRUE
- else if(operator.wear_id)
- idcard = operator.wear_id
+ else if(user.wear_id)
+ idcard = user.wear_id
if(istype(idcard))
has_id = TRUE
if(!has_id)
- to_chat(operator, SPAN_WARNING("You require an ID card to request an access ticket!"))
+ to_chat(user, SPAN_WARNING("You require an ID card to request an access ticket!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(idcard.registered_name != last_login)
- to_chat(operator, SPAN_WARNING("This ID card does not match the active login!"))
+ to_chat(user, SPAN_WARNING("This ID card does not match the active login!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
- var/details = tgui_input_text(operator, "What is the purpose of this access ticket?", "Ticket Details", encode = FALSE)
+ var/details = tgui_input_text(user, "What is the purpose of this access ticket?", "Ticket Details", encode = FALSE)
if(!details)
return FALSE
- var/confirm = alert(operator, "Please confirm the submission of your access ticket request.\n\nHolder: '[last_login]'\nDetails: '[details]'\n\nIs this correct?", "Confirmation", "Yes", "No")
+ var/confirm = alert(user, "Please confirm the submission of your access ticket request.\n\nHolder: '[last_login]'\nDetails: '[details]'\n\nIs this correct?", "Confirmation", "Yes", "No")
if(confirm != "Yes" || !link)
return FALSE
var/datum/ares_ticket/access/access_ticket = new(last_login, details, FALSE, idcard.registered_gid)
link.waiting_ids += idcard
link.tickets_access += access_ticket
- log_game("ARES: Access Ticket '\ref[access_ticket]' created by [key_name(operator)] as [last_login] with Details of '[details]'.")
- message_admins(SPAN_STAFF_IC("[key_name_admin(operator)] created a new ARES Access Ticket."), 1)
+ log_game("ARES: Access Ticket '\ref[access_ticket]' created by [key_name(user)] as [last_login] with Details of '[details]'.")
+ message_admins(SPAN_STAFF_IC("[key_name_admin(user)] created a new ARES Access Ticket."), 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] requesting access for '[details].")
return TRUE
@@ -361,9 +361,9 @@
access_ticket.ticket_status = TICKET_RETURNED
identification.access -= ACCESS_MARINE_AI_TEMP
- identification.modification_log += "Temporary AI Access self-returned by [key_name(operator)]."
+ identification.modification_log += "Temporary AI Access self-returned by [key_name(user)]."
- to_chat(operator, SPAN_NOTICE("Temporary Access Ticket surrendered."))
+ to_chat(user, SPAN_NOTICE("Temporary Access Ticket surrendered."))
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] surrendered their access.")
@@ -372,7 +372,7 @@
datacore.apollo_login_list += "[last_login] at [worldtime2text()], Surrendered Temporary Access Ticket."
return TRUE
- to_chat(operator, SPAN_WARNING("This ID card does not have an access ticket!"))
+ to_chat(user, SPAN_WARNING("This ID card does not have an access ticket!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
@@ -384,7 +384,7 @@
for(var/obj/item/card/id/identification in link.waiting_ids)
if(identification.registered_gid != access_ticket.user_id_num)
continue
- identification.handle_ares_access(last_login, operator)
+ identification.handle_ares_access(last_login, user)
access_ticket.ticket_status = TICKET_GRANTED
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] was granted access by [last_login].")
@@ -392,7 +392,7 @@
for(var/obj/item/card/id/identification in link.active_ids)
if(identification.registered_gid != access_ticket.user_id_num)
continue
- identification.handle_ares_access(last_login, operator)
+ identification.handle_ares_access(last_login, user)
access_ticket.ticket_status = TICKET_REVOKED
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] had access revoked by [last_login].")
@@ -404,10 +404,10 @@
if(!istype(access_ticket))
return FALSE
if(access_ticket.ticket_assignee != last_login && access_ticket.ticket_assignee) //must be claimed by you or unclaimed.)
- to_chat(operator, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
+ to_chat(user, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
return FALSE
access_ticket.ticket_status = TICKET_REJECTED
- to_chat(operator, SPAN_NOTICE("[access_ticket.ticket_type] [access_ticket.ticket_id] marked as rejected."))
+ to_chat(user, SPAN_NOTICE("[access_ticket.ticket_type] [access_ticket.ticket_id] marked as rejected."))
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] was rejected access by [last_login].")
for(var/obj/item/card/id/identification in link.waiting_ids)
if(identification.registered_gid != access_ticket.user_id_num)
@@ -422,36 +422,41 @@
playsound = FALSE
var/obj/structure/pipes/vents/pump/no_boom/gas/sec_vent = locate(params["vent"])
if(!istype(sec_vent) || sec_vent.welded)
- to_chat(operator, SPAN_WARNING("ERROR: Gas release failure."))
+ to_chat(user, SPAN_WARNING("ERROR: Gas release failure."))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(!COOLDOWN_FINISHED(sec_vent, vent_trigger_cooldown))
- to_chat(operator, SPAN_WARNING("ERROR: Insufficient gas reserve for this vent."))
+ to_chat(user, SPAN_WARNING("ERROR: Insufficient gas reserve for this vent."))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
- to_chat(operator, SPAN_WARNING("Initiating gas release from [sec_vent.vent_tag]."))
+ to_chat(user, SPAN_WARNING("Initiating gas release from [sec_vent.vent_tag]."))
playsound(src, 'sound/machines/chime.ogg', 15, 1)
COOLDOWN_START(sec_vent, vent_trigger_cooldown, COOLDOWN_ARES_VENT)
ares_apollo_talk("Nerve Gas release imminent from [sec_vent.vent_tag].")
- log_ares_security("Nerve Gas Release", "[last_login] released Nerve Gas from Vent '[sec_vent.vent_tag]'.")
+ log_ares_security("Nerve Gas Release", "Released Nerve Gas from Vent '[sec_vent.vent_tag]'.", last_login)
sec_vent.create_gas(VENT_GAS_CN20_XENO, 6, 5 SECONDS)
- log_admin("[key_name(operator)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
+ log_admin("[key_name(user)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
+
+ if("security_lockdown")
+ if(!COOLDOWN_FINISHED(datacore, aicore_lockdown))
+ to_chat(user, SPAN_BOLDWARNING("AI Core Lockdown procedures are on cooldown! They will be ready in [COOLDOWN_SECONDSLEFT(datacore, aicore_lockdown)] seconds!"))
+ return FALSE
+ aicore_lockdown(user)
+ return TRUE
if(playsound)
playsound(src, "keyboard_alt", 15, 1)
/obj/item/card/id/proc/handle_ares_access(logged_in = MAIN_AI_SYSTEM, mob/user)
- var/operator = key_name(user)
+ var/changer = logged_in
+ if(user)
+ changer = key_name(user)
var/datum/ares_link/link = GLOB.ares_link
- if(logged_in == MAIN_AI_SYSTEM)
- if(!user)
- operator = "[MAIN_AI_SYSTEM] (Automated)"
- else
- operator = "[user.ckey]/([MAIN_AI_SYSTEM])"
+
if(ACCESS_MARINE_AI_TEMP in access)
access -= ACCESS_MARINE_AI_TEMP
link.active_ids -= src
- modification_log += "Temporary AI access revoked by [operator]"
+ log_idmod(src, "Temporary AI access revoked by [logged_in]", changer)
to_chat(user, SPAN_NOTICE("Access revoked from [registered_name]."))
var/mob/living/carbon/human/id_owner = registered_ref?.resolve()
if(id_owner)
@@ -459,7 +464,7 @@
playsound_client(id_owner?.client, 'sound/machines/pda_ping.ogg', src, 25, 0)
else
access += ACCESS_MARINE_AI_TEMP
- modification_log += "Temporary AI access granted by [operator]"
+ log_idmod(src, "Temporary AI access granted by [logged_in]", changer)
to_chat(user, SPAN_NOTICE("Access granted to [registered_name]."))
link.waiting_ids -= src
link.active_ids += src
diff --git a/code/game/machinery/ARES/ARES_procs.dm b/code/game/machinery/ARES/ARES_procs.dm
index 05f110ec1a0c..ef1b836a3d4b 100644
--- a/code/game/machinery/ARES/ARES_procs.dm
+++ b/code/game/machinery/ARES/ARES_procs.dm
@@ -106,9 +106,13 @@ GLOBAL_LIST_INIT(maintenance_categories, list(
/// Is nuke request usable or not?
var/nuke_available = TRUE
+ /// Status of the AI Core Lockdown
+ var/ai_lockdown_active = FALSE
+
COOLDOWN_DECLARE(ares_distress_cooldown)
COOLDOWN_DECLARE(ares_nuclear_cooldown)
COOLDOWN_DECLARE(ares_quarters_cooldown)
+ COOLDOWN_DECLARE(aicore_lockdown)
// ------ ARES Logging Procs ------ //
/proc/ares_is_active()
@@ -171,11 +175,14 @@ GLOBAL_LIST_INIT(maintenance_categories, list(
var/datum/ares_datacore/datacore = GLOB.ares_datacore
datacore.records_bombardment.Add(new /datum/ares_record/bombardment(ob_name, message, user_name))
-/proc/log_ares_announcement(title, message)
+/proc/log_ares_announcement(title, message, signature)
if(!ares_can_log())
return FALSE
+ var/final_msg = message
+ if(signature)
+ final_msg = "[signature]: - [final_msg]"
var/datum/ares_datacore/datacore = GLOB.ares_datacore
- datacore.records_announcement.Add(new /datum/ares_record/announcement(title, message))
+ datacore.records_announcement.Add(new /datum/ares_record/announcement(title, final_msg))
/proc/log_ares_requisition(source, details, user_name)
if(!ares_can_log())
@@ -183,11 +190,14 @@ GLOBAL_LIST_INIT(maintenance_categories, list(
var/datum/ares_datacore/datacore = GLOB.ares_datacore
datacore.records_asrs.Add(new /datum/ares_record/requisition_log(source, details, user_name))
-/proc/log_ares_security(title, details)
+/proc/log_ares_security(title, details, signature)
if(!ares_can_log())
return FALSE
+ var/final_msg = details
+ if(signature)
+ final_msg = "[signature]: - [final_msg]"
var/datum/ares_datacore/datacore = GLOB.ares_datacore
- datacore.records_security.Add(new /datum/ares_record/security(title, details))
+ datacore.records_security.Add(new /datum/ares_record/security(title, final_msg))
/proc/log_ares_antiair(details)
if(!ares_can_log())
diff --git a/code/game/machinery/ARES/apollo_pda.dm b/code/game/machinery/ARES/apollo_pda.dm
index e447bb6f7ee7..787b194ffb44 100644
--- a/code/game/machinery/ARES/apollo_pda.dm
+++ b/code/game/machinery/ARES/apollo_pda.dm
@@ -181,29 +181,29 @@
return
var/playsound = TRUE
- var/mob/living/carbon/human/operator = ui.user
+ var/mob/living/carbon/human/user = ui.user
switch (action)
if("go_back")
if(!last_menu)
- return to_chat(operator, SPAN_WARNING("Error, no previous page detected."))
+ return to_chat(user, SPAN_WARNING("Error, no previous page detected."))
var/temp_holder = current_menu
current_menu = last_menu
last_menu = temp_holder
if("login")
- var/obj/item/card/id/idcard = operator.get_active_hand()
+ var/obj/item/card/id/idcard = user.get_active_hand()
if(istype(idcard))
authentication = get_ares_access(idcard)
last_login = idcard.registered_name
- else if(operator.wear_id)
- idcard = operator.wear_id
+ else if(user.wear_id)
+ idcard = user.wear_id
if(istype(idcard))
authentication = get_ares_access(idcard)
last_login = idcard.registered_name
else
- to_chat(operator, SPAN_WARNING("You require an ID card to access this terminal!"))
+ to_chat(user, SPAN_WARNING("You require an ID card to access this terminal!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(authentication)
@@ -248,23 +248,23 @@
if("new_report")
var/priority_report = FALSE
- var/maint_type = tgui_input_list(operator, "What is the type of maintenance item you wish to report?", "Report Category", GLOB.maintenance_categories, 30 SECONDS)
+ var/maint_type = tgui_input_list(user, "What is the type of maintenance item you wish to report?", "Report Category", GLOB.maintenance_categories, 30 SECONDS)
switch(maint_type)
if("Major Structural Damage", "Fire", "Communications Failure", "Power Generation Failure")
priority_report = TRUE
if(!maint_type)
return FALSE
- var/details = tgui_input_text(operator, "What are the details for this report?", "Ticket Details", encode = FALSE)
+ var/details = tgui_input_text(user, "What are the details for this report?", "Ticket Details", encode = FALSE)
if(!details)
return FALSE
if((authentication >= APOLLO_ACCESS_REPORTER) && !priority_report)
- var/is_priority = tgui_alert(operator, "Is this a priority report?", "Priority designation", list("Yes", "No"))
+ var/is_priority = tgui_alert(user, "Is this a priority report?", "Priority designation", list("Yes", "No"))
if(is_priority == "Yes")
priority_report = TRUE
- var/confirm = alert(operator, "Please confirm the submission of your maintenance report. \n\n Priority: [priority_report ? "Yes" : "No"]\n Category: '[maint_type]'\n Details: '[details]'\n\n Is this correct?", "Confirmation", "Yes", "No")
+ var/confirm = alert(user, "Please confirm the submission of your maintenance report. \n\n Priority: [priority_report ? "Yes" : "No"]\n Category: '[maint_type]'\n Details: '[details]'\n\n Is this correct?", "Confirmation", "Yes", "No")
if(confirm == "Yes")
if(link)
var/datum/ares_ticket/maintenance/maint_ticket = new(last_login, maint_type, details, priority_report)
@@ -273,7 +273,7 @@
ares_apollo_talk("Priority Maintenance Report: [maint_type] - ID [maint_ticket.ticket_id]. Seek and resolve.")
else
send_notifcation()
- log_game("ARES: Maintenance Ticket '\ref[maint_ticket]' created by [key_name(operator)] as [last_login] with Category '[maint_type]' and Details of '[details]'.")
+ log_game("ARES: Maintenance Ticket '\ref[maint_ticket]' created by [key_name(user)] as [last_login] with Category '[maint_type]' and Details of '[details]'.")
return TRUE
return FALSE
@@ -285,14 +285,14 @@
var/assigned = ticket.ticket_assignee
if(assigned)
if(assigned == last_login)
- var/prompt = tgui_alert(operator, "You already claimed this ticket! Do you wish to drop your claim?", "Unclaim ticket", list("Yes", "No"))
+ var/prompt = tgui_alert(user, "You already claimed this ticket! Do you wish to drop your claim?", "Unclaim ticket", list("Yes", "No"))
if(prompt != "Yes")
return FALSE
/// set ticket back to pending
ticket.ticket_assignee = null
ticket.ticket_status = TICKET_PENDING
return claim
- var/choice = tgui_alert(operator, "This ticket has already been claimed by [assigned]! Do you wish to override their claim?", "Claim Override", list("Yes", "No"))
+ var/choice = tgui_alert(user, "This ticket has already been claimed by [assigned]! Do you wish to override their claim?", "Claim Override", list("Yes", "No"))
if(choice != "Yes")
claim = FALSE
if(claim)
@@ -305,9 +305,9 @@
if(!istype(ticket))
return FALSE
if(ticket.ticket_submitter != last_login)
- to_chat(operator, SPAN_WARNING("You cannot cancel a ticket that does not belong to you!"))
+ to_chat(user, SPAN_WARNING("You cannot cancel a ticket that does not belong to you!"))
return FALSE
- to_chat(operator, SPAN_WARNING("[ticket.ticket_type] [ticket.ticket_id] has been cancelled."))
+ to_chat(user, SPAN_WARNING("[ticket.ticket_type] [ticket.ticket_id] has been cancelled."))
ticket.ticket_status = TICKET_CANCELLED
if(ticket.ticket_priority)
ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been cancelled.")
@@ -320,9 +320,9 @@
if(!istype(ticket))
return FALSE
if(ticket.ticket_assignee != last_login && ticket.ticket_assignee) //must be claimed by you or unclaimed.)
- to_chat(operator, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
+ to_chat(user, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
return FALSE
- var/choice = tgui_alert(operator, "What do you wish to mark the ticket as?", "Mark", list(TICKET_COMPLETED, TICKET_REJECTED), 20 SECONDS)
+ var/choice = tgui_alert(user, "What do you wish to mark the ticket as?", "Mark", list(TICKET_COMPLETED, TICKET_REJECTED), 20 SECONDS)
switch(choice)
if(TICKET_COMPLETED)
ticket.ticket_status = TICKET_COMPLETED
@@ -334,39 +334,39 @@
ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been [choice] by [last_login].")
else
send_notifcation()
- to_chat(operator, SPAN_NOTICE("[ticket.ticket_type] [ticket.ticket_id] marked as [choice]."))
+ to_chat(user, SPAN_NOTICE("[ticket.ticket_type] [ticket.ticket_id] marked as [choice]."))
return TRUE
if("new_access")
- var/obj/item/card/id/idcard = operator.get_active_hand()
+ var/obj/item/card/id/idcard = user.get_active_hand()
var/has_id = FALSE
if(istype(idcard))
has_id = TRUE
- else if(operator.wear_id)
- idcard = operator.wear_id
+ else if(user.wear_id)
+ idcard = user.wear_id
if(istype(idcard))
has_id = TRUE
if(!has_id)
- to_chat(operator, SPAN_WARNING("You require an ID card to request an access ticket!"))
+ to_chat(user, SPAN_WARNING("You require an ID card to request an access ticket!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(idcard.registered_name != last_login)
- to_chat(operator, SPAN_WARNING("This ID card does not match the active login!"))
+ to_chat(user, SPAN_WARNING("This ID card does not match the active login!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
- var/details = tgui_input_text(operator, "What is the purpose of this access ticket?", "Ticket Details", encode = FALSE)
+ var/details = tgui_input_text(user, "What is the purpose of this access ticket?", "Ticket Details", encode = FALSE)
if(!details)
return FALSE
- var/confirm = alert(operator, "Please confirm the submission of your access ticket request.\n\nHolder: '[last_login]'\nDetails: '[details]'\n\nIs this correct?", "Confirmation", "Yes", "No")
+ var/confirm = alert(user, "Please confirm the submission of your access ticket request.\n\nHolder: '[last_login]'\nDetails: '[details]'\n\nIs this correct?", "Confirmation", "Yes", "No")
if(confirm != "Yes" || !link)
return FALSE
var/datum/ares_ticket/access/access_ticket = new(last_login, details, FALSE, idcard.registered_gid)
link.waiting_ids += idcard
link.tickets_access += access_ticket
- log_game("ARES: Access Ticket '\ref[access_ticket]' created by [key_name(operator)] as [last_login] with Details of '[details]'.")
- message_admins(SPAN_STAFF_IC("[key_name_admin(operator)] created a new ARES Access Ticket."), 1)
+ log_game("ARES: Access Ticket '\ref[access_ticket]' created by [key_name(user)] as [last_login] with Details of '[details]'.")
+ message_admins(SPAN_STAFF_IC("[key_name_admin(user)] created a new ARES Access Ticket."), 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] requesting access for '[details].")
return TRUE
@@ -387,9 +387,9 @@
access_ticket.ticket_status = TICKET_RETURNED
identification.access -= ACCESS_MARINE_AI_TEMP
- identification.modification_log += "Temporary AI Access self-returned by [key_name(operator)]."
+ identification.modification_log += "Temporary AI Access self-returned by [key_name(user)]."
- to_chat(operator, SPAN_NOTICE("Temporary Access Ticket surrendered."))
+ to_chat(user, SPAN_NOTICE("Temporary Access Ticket surrendered."))
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] surrendered their access.")
@@ -398,7 +398,7 @@
datacore.apollo_login_list += "[last_login] at [worldtime2text()], Surrendered Temporary Access Ticket."
return TRUE
- to_chat(operator, SPAN_WARNING("This ID card does not have an access ticket!"))
+ to_chat(user, SPAN_WARNING("This ID card does not have an access ticket!"))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
@@ -410,7 +410,7 @@
for(var/obj/item/card/id/identification in link.waiting_ids)
if(identification.registered_gid != access_ticket.user_id_num)
continue
- identification.handle_ares_access(last_login, operator)
+ identification.handle_ares_access(last_login, user)
access_ticket.ticket_status = TICKET_GRANTED
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] was granted access by [last_login].")
@@ -418,7 +418,7 @@
for(var/obj/item/card/id/identification in link.active_ids)
if(identification.registered_gid != access_ticket.user_id_num)
continue
- identification.handle_ares_access(last_login, operator)
+ identification.handle_ares_access(last_login, user)
access_ticket.ticket_status = TICKET_REVOKED
playsound(src, 'sound/machines/chime.ogg', 15, 1)
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] had access revoked by [last_login].")
@@ -430,10 +430,10 @@
if(!istype(access_ticket))
return FALSE
if(access_ticket.ticket_assignee != last_login && access_ticket.ticket_assignee) //must be claimed by you or unclaimed.)
- to_chat(operator, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
+ to_chat(user, SPAN_WARNING("You cannot update a ticket that is not assigned to you!"))
return FALSE
access_ticket.ticket_status = TICKET_REJECTED
- to_chat(operator, SPAN_NOTICE("[access_ticket.ticket_type] [access_ticket.ticket_id] marked as rejected."))
+ to_chat(user, SPAN_NOTICE("[access_ticket.ticket_type] [access_ticket.ticket_id] marked as rejected."))
ares_apollo_talk("Access Ticket [access_ticket.ticket_id]: [access_ticket.ticket_submitter] was rejected access by [last_login].")
for(var/obj/item/card/id/identification in link.waiting_ids)
if(identification.registered_gid != access_ticket.user_id_num)
@@ -448,20 +448,27 @@
playsound = FALSE
var/obj/structure/pipes/vents/pump/no_boom/gas/sec_vent = locate(params["vent"])
if(!istype(sec_vent) || sec_vent.welded)
- to_chat(operator, SPAN_WARNING("ERROR: Gas release failure."))
+ to_chat(user, SPAN_WARNING("ERROR: Gas release failure."))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
if(!COOLDOWN_FINISHED(sec_vent, vent_trigger_cooldown))
- to_chat(operator, SPAN_WARNING("ERROR: Insufficient gas reserve for this vent."))
+ to_chat(user, SPAN_WARNING("ERROR: Insufficient gas reserve for this vent."))
playsound(src, 'sound/machines/buzz-two.ogg', 15, 1)
return FALSE
- to_chat(operator, SPAN_WARNING("Initiating gas release from [sec_vent.vent_tag]."))
+ to_chat(user, SPAN_WARNING("Initiating gas release from [sec_vent.vent_tag]."))
playsound(src, 'sound/machines/chime.ogg', 15, 1)
COOLDOWN_START(sec_vent, vent_trigger_cooldown, COOLDOWN_ARES_VENT)
ares_apollo_talk("Nerve Gas release imminent from [sec_vent.vent_tag].")
- log_ares_security("Nerve Gas Release", "[last_login] released Nerve Gas from Vent '[sec_vent.vent_tag]'.")
+ log_ares_security("Nerve Gas Release", "Released Nerve Gas from Vent '[sec_vent.vent_tag]'.", last_login)
sec_vent.create_gas(VENT_GAS_CN20_XENO, 6, 5 SECONDS)
- log_admin("[key_name(operator)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
+ log_admin("[key_name(user)] released nerve gas from Vent '[sec_vent.vent_tag]' via ARES.")
+
+ if("security_lockdown")
+ if(!COOLDOWN_FINISHED(datacore, aicore_lockdown))
+ to_chat(user, SPAN_BOLDWARNING("AI Core Lockdown procedures are on cooldown! They will be ready in [COOLDOWN_SECONDSLEFT(datacore, aicore_lockdown)] seconds!"))
+ return FALSE
+ aicore_lockdown(user)
+ return TRUE
if(playsound)
var/sound = pick('sound/machines/pda_button1.ogg', 'sound/machines/pda_button2.ogg')
diff --git a/code/game/machinery/aicore_lockdown.dm b/code/game/machinery/aicore_lockdown.dm
new file mode 100644
index 000000000000..8120e98977dc
--- /dev/null
+++ b/code/game/machinery/aicore_lockdown.dm
@@ -0,0 +1,119 @@
+/obj/structure/machinery/aicore_lockdown
+ name = "AI Core Lockdown"
+ icon_state = "big_red_button_tablev"
+ unslashable = TRUE
+ unacidable = TRUE
+
+/obj/structure/machinery/aicore_lockdown/ex_act(severity)
+ return FALSE
+
+/obj/structure/machinery/aicore_lockdown/attack_remote(mob/user as mob)
+ return FALSE
+
+/obj/structure/machinery/aicore_lockdown/attack_alien(mob/user as mob)
+ return FALSE
+
+/obj/structure/machinery/aicore_lockdown/attackby(obj/item/attacking_item, mob/user)
+ return attack_hand(user)
+
+/obj/structure/machinery/aicore_lockdown/attack_hand(mob/living/user)
+ if(isxeno(user))
+ return FALSE
+ if(!allowed(user))
+ to_chat(user, SPAN_DANGER("Access Denied"))
+ flick(initial(icon_state) + "-denied", src)
+ return FALSE
+
+ if(!COOLDOWN_FINISHED(GLOB.ares_datacore, aicore_lockdown))
+ to_chat(user, SPAN_BOLDWARNING("AI Core Lockdown procedures are on cooldown! They will be ready in [COOLDOWN_SECONDSLEFT(GLOB.ares_datacore, aicore_lockdown)] seconds!"))
+ return FALSE
+
+ add_fingerprint(user)
+ aicore_lockdown(user)
+
+/obj/structure/machinery/door/poddoor/almayer/blended/ai_lockdown
+ name = "ARES Emergency Lockdown Shutter"
+ density = FALSE
+ open_layer = 1.9
+ plane = FLOOR_PLANE
+
+/obj/structure/machinery/door/poddoor/almayer/blended/ai_lockdown/aicore
+ icon_state = "aidoor1"
+ base_icon_state = "aidoor"
+
+/obj/structure/machinery/door/poddoor/almayer/blended/ai_lockdown/aicore/white
+ icon_state = "w_aidoor1"
+ base_icon_state = "w_aidoor"
+
+/obj/structure/machinery/door/poddoor/almayer/blended/ai_lockdown/white
+ icon_state = "w_almayer_pdoor1"
+ base_icon_state = "w_almayer_pdoor"
+
+/obj/structure/machinery/door/poddoor/almayer/blended/ai_lockdown/Initialize()
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_GLOB_AICORE_LOCKDOWN, PROC_REF(close))
+ RegisterSignal(SSdcs, COMSIG_GLOB_AICORE_LIFT, PROC_REF(open))
+
+
+/client/proc/admin_aicore_alert()
+ set name = "AI Core Lockdown"
+ set category = "Admin.Ship"
+
+ if(!admin_holder ||!check_rights(R_EVENT))
+ return FALSE
+
+ var/prompt = "Are you sure you want to trigger an AI Core lockdown? This will raise to red alert, and lockdown the AI Core."
+
+ if(GLOB.ares_datacore.ai_lockdown_active == TRUE)
+ prompt = "Are you sure you want to lift the AI Core lockdown? This will lower to blue alert."
+
+ var/choice = tgui_alert(src, prompt, "Choose.", list("Yes", "No"), 20 SECONDS)
+ if(choice != "Yes")
+ return FALSE
+
+ choice = tgui_alert(src, "Do you want to use a custom announcement?", "Choose.", list("Yes", "No"), 20 SECONDS)
+ if(choice == "Yes")
+ var/message = tgui_input_text(src, "Please enter announcement text.", "what?")
+ aicore_lockdown(usr, message, admin = TRUE)
+ else
+ aicore_lockdown(usr, admin = TRUE)
+ return TRUE
+
+/proc/aicore_lockdown(mob/user, message, admin = FALSE)
+ if(IsAdminAdvancedProcCall())
+ return PROC_BLOCKED
+
+ var/log = "[key_name(user)] triggered AI core lockdown!"
+ var/ares_log = "Triggered triggered AI Core Emergency Lockdown."
+ var/person = user.name
+ if(message)
+ log = "[key_name(user)] triggered AI core emergency lockdown! (Using a custom announcement)."
+ if(admin)
+ log += " (Admin Triggered)."
+ person = MAIN_AI_SYSTEM
+
+ if(GLOB.ares_datacore.ai_lockdown_active)
+ GLOB.ares_datacore.ai_lockdown_active = FALSE
+ if(!message)
+ message = "ATTENTION! \n\nAI CORE EMERGENCY LOCKDOWN LIFTED."
+ log = "[key_name(user)] lifted AI core lockdown!"
+ ares_log = "Lifted AI Core Emergency Lockdown."
+ if(admin)
+ log += " (Admin Triggered)."
+ person = MAIN_AI_SYSTEM
+
+ if(GLOB.security_level > SEC_LEVEL_GREEN)
+ set_security_level(SEC_LEVEL_BLUE, TRUE, FALSE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_AICORE_LIFT)
+ else
+ GLOB.ares_datacore.ai_lockdown_active = TRUE
+ if(!message)
+ message = "ATTENTION! \n\nCORE SECURITY ALERT. \n\nAI CORE UNDER LOCKDOWN."
+ if(GLOB.security_level < SEC_LEVEL_RED)
+ set_security_level(SEC_LEVEL_RED, TRUE, FALSE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_AICORE_LOCKDOWN)
+
+ COOLDOWN_START(GLOB.ares_datacore, aicore_lockdown, 2 MINUTES)
+ shipwide_ai_announcement(message, MAIN_AI_SYSTEM, 'sound/effects/biohazard.ogg')
+ message_admins(log)
+ log_ares_security("AI Core Lockdown", ares_log, person)
diff --git a/code/game/machinery/biohazard_lockdown.dm b/code/game/machinery/biohazard_lockdown.dm
index 2e3cbf6de234..bb2674ccca6f 100644
--- a/code/game/machinery/biohazard_lockdown.dm
+++ b/code/game/machinery/biohazard_lockdown.dm
@@ -1,6 +1,6 @@
#define LOCKDOWN_READY 0
#define LOCKDOWN_ACTIVE 1
-GLOBAL_VAR_INIT(lockdown_state, LOCKDOWN_READY)
+GLOBAL_VAR_INIT(med_lockdown_state, LOCKDOWN_READY)
/obj/structure/machinery/biohazard_lockdown
name = "Emergency Containment Breach"
@@ -51,7 +51,7 @@ GLOBAL_VAR_INIT(lockdown_state, LOCKDOWN_READY)
base_icon_state = "w_almayer_pdoor"
/client/proc/admin_biohazard_alert()
- set name = "Containment Breach Alert"
+ set name = "Research Containment Lockdown"
set category = "Admin.Ship"
if(!admin_holder ||!check_rights(R_EVENT))
@@ -63,8 +63,8 @@ GLOBAL_VAR_INIT(lockdown_state, LOCKDOWN_READY)
prompt = tgui_alert(src, "Do you want to use a custom announcement?", "Choose.", list("Yes", "No"), 20 SECONDS)
if(prompt == "Yes")
- var/whattoannounce = tgui_input_text(src, "Please enter announcement text.", "what?")
- biohazard_lockdown(usr, whattoannounce, TRUE)
+ var/message = tgui_input_text(src, "Please enter announcement text.", "what?")
+ biohazard_lockdown(usr, message, admin = TRUE)
else
biohazard_lockdown(usr, admin = TRUE)
return TRUE
@@ -74,35 +74,38 @@ GLOBAL_VAR_INIT(lockdown_state, LOCKDOWN_READY)
return PROC_BLOCKED
var/log = "[key_name(user)] triggered research bio lockdown!"
- var/ares_log = "[user.name] triggered Medical Research Biohazard Containment Lockdown."
+ var/ares_log = "Triggered Medical Research Biohazard Containment Lockdown."
+ var/person = user.name
if(!message)
message = "ATTENTION! \n\nBIOHAZARD CONTAINMENT BREACH. \n\nRESEARCH DEPARTMENT UNDER LOCKDOWN."
else
log = "[key_name(user)] triggered research bio lockdown! (Using a custom announcement)."
if(admin)
log += " (Admin Triggered)."
- ares_log = "[MAIN_AI_SYSTEM] triggered Medical Research Biohazard Containment Lockdown."
+ person = MAIN_AI_SYSTEM
- switch(GLOB.lockdown_state)
+ switch(GLOB.med_lockdown_state)
if(LOCKDOWN_READY)
- GLOB.lockdown_state = LOCKDOWN_ACTIVE
- set_security_level(SEC_LEVEL_RED, TRUE, FALSE)
+ GLOB.med_lockdown_state = LOCKDOWN_ACTIVE
+ if(GLOB.security_level < SEC_LEVEL_RED)
+ set_security_level(SEC_LEVEL_RED, TRUE, FALSE)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_RESEARCH_LOCKDOWN)
if(LOCKDOWN_ACTIVE)
- GLOB.lockdown_state = LOCKDOWN_READY
+ GLOB.med_lockdown_state = LOCKDOWN_READY
message = "ATTENTION! \n\nBIOHAZARD CONTAINMENT LOCKDOWN LIFTED."
log = "[key_name(user)] lifted research bio lockdown!"
- ares_log = "[user.name] lifted Medical Research Biohazard Containment Lockdown."
+ ares_log = "Lifted Medical Research Biohazard Containment Lockdown."
if(admin)
log += " (Admin Triggered)."
- ares_log = "[MAIN_AI_SYSTEM] lifted Medical Research Biohazard Containment Lockdown."
+ person = MAIN_AI_SYSTEM
- set_security_level(SEC_LEVEL_BLUE, TRUE, FALSE)
+ if(GLOB.security_level > SEC_LEVEL_GREEN)
+ set_security_level(SEC_LEVEL_BLUE, TRUE, FALSE)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_RESEARCH_LIFT)
shipwide_ai_announcement(message, MAIN_AI_SYSTEM, 'sound/effects/biohazard.ogg')
message_admins(log)
- log_ares_security("Containment Lockdown", ares_log)
+ log_ares_security("Containment Lockdown", ares_log, person)
#undef LOCKDOWN_READY
#undef LOCKDOWN_ACTIVE
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 948d83e76148..6943544e30d4 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -312,8 +312,8 @@ GLOBAL_LIST_EMPTY_TYPED(all_cameras, /obj/structure/machinery/camera)
. = ..()
if(!camera_item)
return INITIALIZE_HINT_QDEL
- c_tag = camera_item.get_broadcast_name()
linked_broadcasting = camera_item
+ c_tag = linked_broadcasting.get_broadcast_name()
/obj/structure/machinery/camera/mortar
alpha = 0
diff --git a/code/game/machinery/computer/almayer_control.dm b/code/game/machinery/computer/almayer_control.dm
index 1f3338e15bf7..0090a6673961 100644
--- a/code/game/machinery/computer/almayer_control.dm
+++ b/code/game/machinery/computer/almayer_control.dm
@@ -108,33 +108,34 @@
. = ..()
if(.)
return
+ var/mob/user = ui.user
switch(action)
if("award")
- open_medal_panel(usr, src)
+ open_medal_panel(user, src)
. = TRUE
// evac stuff start \\
if("evacuation_start")
if(GLOB.security_level < SEC_LEVEL_RED)
- to_chat(usr, SPAN_WARNING("The ship must be under red alert in order to enact evacuation procedures."))
+ to_chat(user, SPAN_WARNING("The ship must be under red alert in order to enact evacuation procedures."))
return FALSE
if(SShijack.evac_admin_denied)
- to_chat(usr, SPAN_WARNING("The USCM has placed a lock on deploying the evacuation pods."))
+ to_chat(user, SPAN_WARNING("The USCM has placed a lock on deploying the evacuation pods."))
return FALSE
if(!SShijack.initiate_evacuation())
- to_chat(usr, SPAN_WARNING("You are unable to initiate an evacuation procedure right now!"))
+ to_chat(user, SPAN_WARNING("You are unable to initiate an evacuation procedure right now!"))
return FALSE
- log_game("[key_name(usr)] has called for an emergency evacuation.")
- message_admins("[key_name_admin(usr)] has called for an emergency evacuation.")
- log_ares_security("Initiate Evacuation", "[usr] has called for an emergency evacuation.")
+ log_game("[key_name(user)] has called for an emergency evacuation.")
+ message_admins("[key_name_admin(user)] has called for an emergency evacuation.")
+ log_ares_security("Initiate Evacuation", "Called for an emergency evacuation.", user)
. = TRUE
if("evacuation_cancel")
- var/mob/living/carbon/human/human_user = usr
+ var/mob/living/carbon/human/human_user = user
var/obj/item/card/id/idcard = human_user.get_active_hand()
var/bio_fail = FALSE
if(!istype(idcard))
@@ -148,12 +149,12 @@
return FALSE
if(!SShijack.cancel_evacuation())
- to_chat(usr, SPAN_WARNING("You are unable to cancel the evacuation right now!"))
+ to_chat(user, SPAN_WARNING("You are unable to cancel the evacuation right now!"))
return FALSE
- log_game("[key_name(usr)] has canceled the emergency evacuation.")
- message_admins("[key_name_admin(usr)] has canceled the emergency evacuation.")
- log_ares_security("Cancel Evacuation", "[usr] has cancelled the emergency evacuation.")
+ log_game("[key_name(user)] has canceled the emergency evacuation.")
+ message_admins("[key_name_admin(user)] has canceled the emergency evacuation.")
+ log_ares_security("Cancel Evacuation", "Cancelled the emergency evacuation.", user)
. = TRUE
// evac stuff end \\
@@ -168,32 +169,32 @@
if(SEC_LEVEL_DELTA)
return
- var/level_selected = tgui_input_list(usr, "What alert would you like to set it as?", "Alert Level", alert_list)
+ var/level_selected = tgui_input_list(user, "What alert would you like to set it as?", "Alert Level", alert_list)
if(!level_selected)
return
set_security_level(seclevel2num(level_selected), log = ARES_LOG_NONE)
- log_game("[key_name(usr)] has changed the security level to [get_security_level()].")
- message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].")
- log_ares_security("Manual Security Update", "[usr] has changed the security level to [get_security_level()].")
+ log_game("[key_name(user)] has changed the security level to [get_security_level()].")
+ message_admins("[key_name_admin(user)] has changed the security level to [get_security_level()].")
+ log_ares_security("Manual Security Update", "Changed the security level to [get_security_level()].", user)
. = TRUE
if("messageUSCM")
if(!COOLDOWN_FINISHED(src, cooldown_central))
- to_chat(usr, SPAN_WARNING("Arrays are re-cycling. Please stand by."))
+ to_chat(user, SPAN_WARNING("Arrays are re-cycling. Please stand by."))
return FALSE
- var/input = stripped_input(usr, "Please choose a message to transmit to USCM. Please be aware that this process is very expensive, and abuse will lead to termination. Transmission does not guarantee a response. There is a small delay before you may send another message. Be clear and concise.", "To abort, send an empty message.", "")
- if(!input || !(usr in view(1,src)) || !COOLDOWN_FINISHED(src, cooldown_central))
+ var/input = stripped_input(user, "Please choose a message to transmit to USCM. Please be aware that this process is very expensive, and abuse will lead to termination. Transmission does not guarantee a response. There is a small delay before you may send another message. Be clear and concise.", "To abort, send an empty message.", "")
+ if(!input || !(user in view(1,src)) || !COOLDOWN_FINISHED(src, cooldown_central))
return FALSE
- high_command_announce(input, usr)
- to_chat(usr, SPAN_NOTICE("Message transmitted."))
- log_announcement("[key_name(usr)] has made an USCM announcement: [input]")
+ high_command_announce(input, user)
+ to_chat(user, SPAN_NOTICE("Message transmitted."))
+ log_announcement("[key_name(user)] has made an USCM announcement: [input]")
COOLDOWN_START(src, cooldown_central, COOLDOWN_COMM_CENTRAL)
. = TRUE
if("ship_announce")
- var/mob/living/carbon/human/human_user = usr
+ var/mob/living/carbon/human/human_user = user
var/obj/item/card/id/idcard = human_user.get_active_hand()
var/bio_fail = FALSE
if(!istype(idcard))
@@ -207,10 +208,10 @@
return FALSE
if(!COOLDOWN_FINISHED(src, cooldown_message))
- to_chat(usr, SPAN_WARNING("Please allow at least [COOLDOWN_TIMELEFT(src, cooldown_message)/10] second\s to pass between announcements."))
+ to_chat(user, SPAN_WARNING("Please allow at least [COOLDOWN_TIMELEFT(src, cooldown_message)/10] second\s to pass between announcements."))
return FALSE
- var/input = stripped_multiline_input(usr, "Please write a message to announce to the station crew.", "Priority Announcement", "")
- if(!input || !COOLDOWN_FINISHED(src, cooldown_message) || !(usr in view(1,src)))
+ var/input = stripped_multiline_input(user, "Please write a message to announce to the station crew.", "Priority Announcement", "")
+ if(!input || !COOLDOWN_FINISHED(src, cooldown_message) || !(user in view(1,src)))
return FALSE
var/signed = null
@@ -219,35 +220,35 @@
COOLDOWN_START(src, cooldown_message, COOLDOWN_COMM_MESSAGE)
shipwide_ai_announcement(input, COMMAND_SHIP_ANNOUNCE, signature = signed)
- message_admins("[key_name(usr)] has made a shipwide annoucement.")
- log_announcement("[key_name(usr)] has announced the following to the ship: [input]")
+ message_admins("[key_name(user)] has made a shipwide annoucement.")
+ log_announcement("[key_name(user)] has announced the following to the ship: [input]")
. = TRUE
if("distress")
if(world.time < DISTRESS_TIME_LOCK)
- to_chat(usr, SPAN_WARNING("The distress beacon cannot be launched this early in the operation. Please wait another [time_left_until(DISTRESS_TIME_LOCK, world.time, 1 MINUTES)] minutes before trying again."))
+ to_chat(user, SPAN_WARNING("The distress beacon cannot be launched this early in the operation. Please wait another [time_left_until(DISTRESS_TIME_LOCK, world.time, 1 MINUTES)] minutes before trying again."))
return FALSE
if(!SSticker.mode)
return FALSE //Not a game mode?
if(SSticker.mode.force_end_at == 0)
- to_chat(usr, SPAN_WARNING("ARES has denied your request for operational security reasons."))
+ to_chat(user, SPAN_WARNING("ARES has denied your request for operational security reasons."))
return FALSE
if(!COOLDOWN_FINISHED(src, cooldown_request))
- to_chat(usr, SPAN_WARNING("The distress beacon has recently broadcast a message. Please wait."))
+ to_chat(user, SPAN_WARNING("The distress beacon has recently broadcast a message. Please wait."))
return FALSE
if(GLOB.security_level == SEC_LEVEL_DELTA)
- to_chat(usr, SPAN_WARNING("The ship is already undergoing self-destruct procedures!"))
+ to_chat(user, SPAN_WARNING("The ship is already undergoing self-destruct procedures!"))
return FALSE
for(var/client/admin_client as anything in GLOB.admins)
if((R_ADMIN|R_MOD) & admin_client.admin_holder.rights)
admin_client << 'sound/effects/sos-morse-code.ogg'
- SSticker.mode.request_ert(usr)
- to_chat(usr, SPAN_NOTICE("A distress beacon request has been sent to USCM Central Command."))
+ SSticker.mode.request_ert(user)
+ to_chat(user, SPAN_NOTICE("A distress beacon request has been sent to USCM Central Command."))
COOLDOWN_START(src, cooldown_request, COOLDOWN_COMM_REQUEST)
. = TRUE
@@ -256,29 +257,29 @@
if("destroy")
if(world.time < DISTRESS_TIME_LOCK)
- to_chat(usr, SPAN_WARNING("The self-destruct cannot be activated this early in the operation. Please wait another [time_left_until(DISTRESS_TIME_LOCK, world.time, 1 MINUTES)] minutes before trying again."))
+ to_chat(user, SPAN_WARNING("The self-destruct cannot be activated this early in the operation. Please wait another [time_left_until(DISTRESS_TIME_LOCK, world.time, 1 MINUTES)] minutes before trying again."))
return FALSE
if(!SSticker.mode)
return FALSE //Not a game mode?
if(SSticker.mode.force_end_at == 0)
- to_chat(usr, SPAN_WARNING("ARES has denied your request for operational security reasons."))
+ to_chat(user, SPAN_WARNING("ARES has denied your request for operational security reasons."))
return FALSE
if(!COOLDOWN_FINISHED(src, cooldown_destruct))
- to_chat(usr, SPAN_WARNING("A self-destruct request has already been sent to high command. Please wait."))
+ to_chat(user, SPAN_WARNING("A self-destruct request has already been sent to high command. Please wait."))
return FALSE
if(get_security_level() == "delta")
- to_chat(usr, SPAN_WARNING("The [MAIN_SHIP_NAME]'s self-destruct is already activated."))
+ to_chat(user, SPAN_WARNING("The [MAIN_SHIP_NAME]'s self-destruct is already activated."))
return FALSE
for(var/client/admin_client as anything in GLOB.admins)
if((R_ADMIN|R_MOD) & admin_client.admin_holder.rights)
admin_client << 'sound/effects/sos-morse-code.ogg'
- message_admins("[key_name(usr)] has requested Self-Destruct! [CC_MARK(usr)] (GRANT) (DENY) [ADMIN_JMP_USER(usr)] [CC_REPLY(usr)]")
- to_chat(usr, SPAN_NOTICE("A self-destruct request has been sent to USCM Central Command."))
+ message_admins("[key_name(user)] has requested Self-Destruct! [CC_MARK(user)] (GRANT) (DENY) [ADMIN_JMP_USER(user)] [CC_REPLY(user)]")
+ to_chat(user, SPAN_NOTICE("A self-destruct request has been sent to USCM Central Command."))
COOLDOWN_START(src, cooldown_destruct, COOLDOWN_COMM_DESTRUCT)
. = TRUE
diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm
index cd0ee780f478..1e2cb427cab4 100644
--- a/code/game/machinery/computer/camera_console.dm
+++ b/code/game/machinery/computer/camera_console.dm
@@ -17,6 +17,7 @@
var/colony_camera_mapload = TRUE
var/admin_console = FALSE
+ var/stay_connected = FALSE
/obj/structure/machinery/computer/cameras/Initialize(mapload)
. = ..()
@@ -33,7 +34,7 @@
/obj/structure/machinery/computer/cameras/Destroy()
SStgui.close_uis(src)
- QDEL_NULL(current)
+ current = null
UnregisterSignal(src, COMSIG_CAMERA_MAPNAME_ASSIGNED)
last_camera_turf = null
concurrent_users = null
@@ -147,7 +148,7 @@
// Unregister map objects
SEND_SIGNAL(src, COMSIG_CAMERA_UNREGISTER_UI, user)
// Turn off the console
- if(length(concurrent_users) == 0 && is_living)
+ if(length(concurrent_users) == 0 && is_living && !stay_connected)
current = null
SEND_SIGNAL(src, COMSIG_CAMERA_CLEAR)
last_camera_turf = null
@@ -206,6 +207,8 @@
name = "Television Set"
desc = "An old TV hooked up to a video cassette recorder, you can even use it to time shift WOW."
network = list(CAMERA_NET_CORRESPONDENT)
+ stay_connected = TRUE
+ circuit = /obj/item/circuitboard/computer/cameras/tv
var/obj/item/device/camera/broadcasting/broadcastingcamera = null
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/Destroy()
@@ -213,38 +216,76 @@
return ..()
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_state(mob/user)
- return GLOB.default_state
+ return GLOB.in_view
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_act(action, params)
. = ..()
if(action != "switch_camera")
return
- broadcastingcamera = null
- if (!istype(current, /obj/structure/machinery/camera/correspondent))
+ if(broadcastingcamera)
+ clear_camera()
+ if(!istype(current, /obj/structure/machinery/camera/correspondent))
return
var/obj/structure/machinery/camera/correspondent/corr_cam = current
- if (!corr_cam.linked_broadcasting)
+ if(!corr_cam.linked_broadcasting)
return
broadcastingcamera = corr_cam.linked_broadcasting
RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_GO_LIVE, PROC_REF(go_back_live))
+ RegisterSignal(broadcastingcamera, COMSIG_COMPONENT_ADDED, PROC_REF(handle_rename))
RegisterSignal(broadcastingcamera, COMSIG_PARENT_QDELETING, PROC_REF(clear_camera))
+ RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_HEAR_TALK, PROC_REF(transfer_talk))
+ RegisterSignal(broadcastingcamera, COMSIG_BROADCAST_SEE_EMOTE, PROC_REF(transfer_emote))
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/ui_close(mob/user)
. = ..()
- if (!current && broadcastingcamera)
+ if(!broadcastingcamera)
+ return
+ if(!current)
clear_camera()
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/clear_camera()
SIGNAL_HANDLER
- UnregisterSignal(broadcastingcamera, list(COMSIG_BROADCAST_GO_LIVE, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(broadcastingcamera, list(COMSIG_BROADCAST_GO_LIVE, COMSIG_PARENT_QDELETING, COMSIG_COMPONENT_ADDED, COMSIG_BROADCAST_HEAR_TALK, COMSIG_BROADCAST_SEE_EMOTE))
broadcastingcamera = null
/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/go_back_live(obj/item/device/camera/broadcasting/broadcastingcamera)
SIGNAL_HANDLER
- if (current.c_tag == broadcastingcamera.get_broadcast_name())
+ if(current.c_tag == broadcastingcamera.get_broadcast_name())
current = broadcastingcamera.linked_cam
SEND_SIGNAL(src, COMSIG_CAMERA_SET_TARGET, broadcastingcamera.linked_cam, broadcastingcamera.linked_cam.view_range, broadcastingcamera.linked_cam.view_range)
+/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/transfer_talk(obj/item/camera, mob/living/sourcemob, message, verb = "says", datum/language/language, italics = FALSE, show_message_above_tv = FALSE)
+ SIGNAL_HANDLER
+ if(inoperable())
+ return
+ if(show_message_above_tv)
+ langchat_speech(message, get_mobs_in_view(7, src), language, sourcemob.langchat_color, FALSE, LANGCHAT_FAST_POP, list(sourcemob.langchat_styles))
+ for(var/datum/weakref/user_ref in concurrent_users)
+ var/mob/user = user_ref.resolve()
+ if(user?.client?.prefs && !user.client.prefs.lang_chat_disabled && !user.ear_deaf && user.say_understands(sourcemob, language))
+ sourcemob.langchat_display_image(user)
+
+/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/transfer_emote(obj/item/camera, mob/living/sourcemob, emote, audible = FALSE, show_message_above_tv = FALSE)
+ SIGNAL_HANDLER
+ if(inoperable())
+ return
+ if(show_message_above_tv)
+ langchat_speech(emote, get_mobs_in_view(7, src), null, null, TRUE, LANGCHAT_FAST_POP, list("emote"))
+ for(var/datum/weakref/user_ref in concurrent_users)
+ var/mob/user = user_ref.resolve()
+ if(user?.client?.prefs && (user.client.prefs.toggles_langchat & LANGCHAT_SEE_EMOTES) && (!audible || !user.ear_deaf))
+ sourcemob.langchat_display_image(user)
+
+/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/examine(mob/user)
+ . = ..()
+ attack_hand(user) //watch tv on examine
+
+/obj/structure/machinery/computer/cameras/wooden_tv/broadcast/proc/handle_rename(obj/item/camera, datum/component/label)
+ SIGNAL_HANDLER
+ if(!istype(label, /datum/component/label))
+ return
+ current.c_tag = broadcastingcamera.get_broadcast_name()
+
/obj/structure/machinery/computer/cameras/wooden_tv/ot
name = "Mortar Monitoring Set"
desc = "A Console linked to Mortar launched cameras."
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index beed3610b53f..6ed2a8c7be64 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -161,7 +161,7 @@
log_game("[key_name(usr)] has called for an emergency evacuation.")
message_admins("[key_name_admin(usr)] has called for an emergency evacuation.")
- log_ares_security("Initiate Evacuation", "[usr] has called for an emergency evacuation.")
+ log_ares_security("Initiate Evacuation", "Called for an emergency evacuation.", usr)
return TRUE
state = STATE_EVACUATION
@@ -187,7 +187,7 @@
log_game("[key_name(usr)] has canceled the emergency evacuation.")
message_admins("[key_name_admin(usr)] has canceled the emergency evacuation.")
- log_ares_security("Cancel Evacuation", "[usr] has cancelled the emergency evacuation.")
+ log_ares_security("Cancel Evacuation", "Cancelled the emergency evacuation.", usr)
return TRUE
state = STATE_EVACUATION_CANCEL
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index c33517796271..f6efe6edb5e2 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -126,7 +126,7 @@
src.attack_alien(user)
return
src.attack_hand(user)
- return
+ return ..()
/obj/structure/machinery/computer/attack_hand()
. = ..()
diff --git a/code/game/machinery/computer/dropship_weapons.dm b/code/game/machinery/computer/dropship_weapons.dm
index dce026f4ce33..017a5f0736ca 100644
--- a/code/game/machinery/computer/dropship_weapons.dm
+++ b/code/game/machinery/computer/dropship_weapons.dm
@@ -754,7 +754,7 @@
if (!dropship.in_flyby || dropship.mode != SHUTTLE_CALL)
to_chat(user, SPAN_WARNING("Has to be in Fly By mode"))
return FALSE
- if (dropship.timer && dropship.timeLeft(1) < firemission_envelope.get_total_duration())
+ if (dropship.timer && dropship.timeLeft(1) < firemission_envelope.flyoff_period)
to_chat(user, SPAN_WARNING("Not enough time to complete the Fire Mission"))
return FALSE
var/datum/cas_signal/recorded_loc = firemission_envelope.recorded_loc
diff --git a/code/game/machinery/kitchen/microwave.dm b/code/game/machinery/kitchen/microwave.dm
index 220772e98b17..5a7e690fc3ca 100644
--- a/code/game/machinery/kitchen/microwave.dm
+++ b/code/game/machinery/kitchen/microwave.dm
@@ -229,8 +229,9 @@
//************************************/
/obj/structure/machinery/microwave/proc/cook(time_multiplier = 1)
- if(inoperable())
+ if(inoperable() || operating)
return
+
start()
if (reagents.total_volume==0 && !(locate(/obj) in contents)) //dry run
if (!wzhzhzh(10 * time_multiplier))
diff --git a/code/game/machinery/vending/vendor_types/crew/sea.dm b/code/game/machinery/vending/vendor_types/crew/sea.dm
index 44f530271037..37cacfd14a6f 100644
--- a/code/game/machinery/vending/vendor_types/crew/sea.dm
+++ b/code/game/machinery/vending/vendor_types/crew/sea.dm
@@ -29,17 +29,20 @@ GLOBAL_LIST_INIT(cm_vending_gear_sea, list(
GLOBAL_LIST_INIT(cm_vending_clothing_sea, list(
list("STANDARD EQUIPMENT (TAKE ALL)", 0, null, null, null),
list("Officer Uniform", 0, /obj/item/clothing/under/marine/dress, MARINE_CAN_BUY_UNIFORM, VENDOR_ITEM_MANDATORY),
- list("Marine Combat Gloves", 0, /obj/item/clothing/gloves/marine, MARINE_CAN_BUY_GLOVES, VENDOR_ITEM_MANDATORY),
list("Headset", 0, /obj/item/device/radio/headset/almayer/mcom/cdrcom, MARINE_CAN_BUY_EAR, VENDOR_ITEM_MANDATORY),
list("Satchel", 0, /obj/item/storage/backpack/satchel/lockable, MARINE_CAN_BUY_BACKPACK, VENDOR_ITEM_MANDATORY),
list("MRE", 0, /obj/item/storage/box/MRE, MARINE_CAN_BUY_MRE, VENDOR_ITEM_MANDATORY),
list("Marine Combat Boots", 0, /obj/item/clothing/shoes/marine/knife, MARINE_CAN_BUY_SHOES, VENDOR_ITEM_MANDATORY),
+ list("GLOVES (CHOOSE 1)", 0, null, null, null),
+ list("Insulated Gloves", 0, /obj/item/clothing/gloves/yellow, MARINE_CAN_BUY_GLOVES, VENDOR_ITEM_REGULAR),
+ list("Marine Combat Gloves", 0, /obj/item/clothing/gloves/marine, MARINE_CAN_BUY_GLOVES, VENDOR_ITEM_REGULAR),
+
list("BELT (CHOOSE 1)", 0, null, null, null),
list("G8-A General Utility Pouch", 0, /obj/item/storage/backpack/general_belt, MARINE_CAN_BUY_BELT, VENDOR_ITEM_REGULAR),
list("M276 Lifesaver Bag (Full)", 0, /obj/item/storage/belt/medical/lifesaver/full, MARINE_CAN_BUY_BELT, VENDOR_ITEM_RECOMMENDED),
list("M276 Toolbelt Rig (Full)", 0, /obj/item/storage/belt/utility/full, MARINE_CAN_BUY_BELT, VENDOR_ITEM_REGULAR),
- list("M276 Combat Toolbelt Rig (Full)", 0, /obj/item/storage/belt/gun/utility, MARINE_CAN_BUY_BELT, VENDOR_ITEM_REGULAR),
+ list("M276 Combat Toolbelt Rig (Full)", 0, /obj/item/storage/belt/gun/utility/full, MARINE_CAN_BUY_BELT, VENDOR_ITEM_REGULAR),
list("POUCHES (CHOOSE 2)", 0, null, null, null),
list("Autoinjector Pouch", 0, /obj/item/storage/pouch/autoinjector/full, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_REGULAR),
@@ -58,6 +61,12 @@ GLOBAL_LIST_INIT(cm_vending_clothing_sea, list(
list("Bulletproof Vest", 0, /obj/item/clothing/suit/armor/bulletproof, MARINE_CAN_BUY_ARMOR, VENDOR_ITEM_REGULAR),
list("USCM Service Jacket", 0, /obj/item/clothing/suit/storage/jacket/marine/service, MARINE_CAN_BUY_ARMOR, VENDOR_ITEM_REGULAR),
+ list("EYEWEAR (CHOOSE 1)", 0, null, null, null),
+ list("Welding Goggles", 0, /obj/item/clothing/glasses/welding, MARINE_CAN_BUY_GLASSES, VENDOR_ITEM_REGULAR),
+ list("Medical HUD Glasses", 0, /obj/item/clothing/glasses/hud/health, MARINE_CAN_BUY_GLASSES, VENDOR_ITEM_REGULAR),
+ list("Prescription Medical HUD Glasses", 0, /obj/item/clothing/glasses/hud/health/prescription, MARINE_CAN_BUY_GLASSES, VENDOR_ITEM_REGULAR),
+ list("Sunglasses", 0, /obj/item/clothing/glasses/sunglasses, MARINE_CAN_BUY_GLASSES, VENDOR_ITEM_REGULAR),
+
list("ACCESSORIES (CHOOSE 1)", 0, null, null, null),
list("Brown Webbing Vest", 0, /obj/item/clothing/accessory/storage/black_vest/brown_vest, MARINE_CAN_BUY_ACCESSORY, VENDOR_ITEM_REGULAR),
list("Black Webbing Vest", 0, /obj/item/clothing/accessory/storage/black_vest, MARINE_CAN_BUY_ACCESSORY, VENDOR_ITEM_REGULAR),
@@ -68,9 +77,6 @@ GLOBAL_LIST_INIT(cm_vending_clothing_sea, list(
list("HEADWEAR (CHOOSE 1)", 0, null, null, null),
list("Drill Hat", 0, /obj/item/clothing/head/drillhat, MARINE_CAN_BUY_MASK, VENDOR_ITEM_RECOMMENDED),
list("M10 Helmet", 0, /obj/item/clothing/head/helmet/marine, MARINE_CAN_BUY_HELMET, VENDOR_ITEM_REGULAR),
-
- list("TOOLS OF THE TRADE", 0, null, null, null),
- list("CPR Dummy", 5, /obj/item/cpr_dummy, null, VENDOR_ITEM_REGULAR)
))
/obj/structure/machinery/cm_vending/clothing/sea
diff --git a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
index 6877c2b4b5b3..0586f4b72fa5 100644
--- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
+++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
@@ -76,6 +76,9 @@
else
display_list = GLOB.cm_vending_vehicle_crew_tank_spare
+ else if(selected_vehicle == "ARC")
+ display_list = GLOB.cm_vending_vehicle_crew_arc
+
else if(selected_vehicle == "APC")
if(available_categories)
display_list = GLOB.cm_vending_vehicle_crew_apc
@@ -245,6 +248,11 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_apc_spare, list(
list("WHEELS", 0, null, null, null),
list("APC Wheels", 200, /obj/item/hardpoint/locomotion/apc_wheels, null, VENDOR_ITEM_REGULAR)))
+GLOBAL_LIST_INIT(cm_vending_vehicle_crew_arc, list(
+ list("STARTING KIT SELECTION:", 0, null, null, null),
+
+ list("WHEELS", 0, null, null, null),
+ list("Replacement ARC Wheels", 0, /obj/item/hardpoint/locomotion/arc_wheels, VEHICLE_TREADS_AVAILABLE, VENDOR_ITEM_MANDATORY)))
//------------WEAPONS RACK---------------
diff --git a/code/game/objects/items/devices/binoculars.dm b/code/game/objects/items/devices/binoculars.dm
index 84da7d9acff4..b39526e231b5 100644
--- a/code/game/objects/items/devices/binoculars.dm
+++ b/code/game/objects/items/devices/binoculars.dm
@@ -354,13 +354,16 @@
/obj/item/device/binoculars/range/designator/scout
name = "scout laser designator"
desc = "An improved laser designator, issued to USCM scouts, with two modes: target marking for CAS with IR laser and rangefinding. Ctrl + Click turf to target something. Ctrl + Click designator to stop lasing. Alt + Click designator to switch modes."
+ unacidable = TRUE
+ indestructible = TRUE
cooldown_duration = 80
target_acquisition_delay = 30
/obj/item/device/binoculars/range/designator/spotter
name = "spotter's laser designator"
desc = "A specially-designed laser designator, issued to USCM spotters, with two modes: target marking for CAS with IR laser and rangefinding. Ctrl + Click turf to target something. Ctrl + Click designator to stop lasing. Alt + Click designator to switch modes. Additionally, a trained spotter can laze targets for a USCM marksman, increasing the speed of target acquisition. A targeting beam will connect the binoculars to the target, but it may inherit the user's cloak, if possible."
-
+ unacidable = TRUE
+ indestructible = TRUE
var/is_spotting = FALSE
var/spotting_time = 10 SECONDS
var/spotting_cooldown_delay = 5 SECONDS
diff --git a/code/game/objects/items/devices/cictablet.dm b/code/game/objects/items/devices/cictablet.dm
index 664054fb59e2..de03f1779f2b 100644
--- a/code/game/objects/items/devices/cictablet.dm
+++ b/code/game/objects/items/devices/cictablet.dm
@@ -91,43 +91,43 @@
. = ..()
if(.)
return
-
+ var/mob/user = ui.user
switch(action)
if("announce")
- if(usr.client.prefs.muted & MUTE_IC)
- to_chat(usr, SPAN_DANGER("You cannot send Announcements (muted)."))
+ if(user.client.prefs.muted & MUTE_IC)
+ to_chat(user, SPAN_DANGER("You cannot send Announcements (muted)."))
return
if(!COOLDOWN_FINISHED(src, announcement_cooldown))
- to_chat(usr, SPAN_WARNING("Please wait [COOLDOWN_TIMELEFT(src, announcement_cooldown)/10] second\s before making your next announcement."))
+ to_chat(user, SPAN_WARNING("Please wait [COOLDOWN_TIMELEFT(src, announcement_cooldown)/10] second\s before making your next announcement."))
return FALSE
- var/input = stripped_multiline_input(usr, "Please write a message to announce to the [MAIN_SHIP_NAME]'s crew and all groundside personnel.", "Priority Announcement", "")
- if(!input || !COOLDOWN_FINISHED(src, announcement_cooldown) || !(usr in view(1, src)))
+ var/input = stripped_multiline_input(user, "Please write a message to announce to the [MAIN_SHIP_NAME]'s crew and all groundside personnel.", "Priority Announcement", "")
+ if(!input || !COOLDOWN_FINISHED(src, announcement_cooldown) || !(user in view(1, src)))
return FALSE
var/signed = null
- if(ishuman(usr))
- var/mob/living/carbon/human/H = usr
- var/obj/item/card/id/id = H.wear_id
+ if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ var/obj/item/card/id/id = human_user.wear_id
if(istype(id))
- var/paygrade = get_paygrades(id.paygrade, FALSE, H.gender)
+ var/paygrade = get_paygrades(id.paygrade, FALSE, human_user.gender)
signed = "[paygrade] [id.registered_name]"
marine_announcement(input, announcement_title, faction_to_display = announcement_faction, add_PMCs = add_pmcs, signature = signed)
- message_admins("[key_name(usr)] has made a command announcement.")
- log_announcement("[key_name(usr)] has announced the following: [input]")
+ message_admins("[key_name(user)] has made a command announcement.")
+ log_announcement("[key_name(user)] has announced the following: [input]")
COOLDOWN_START(src, announcement_cooldown, cooldown_between_messages)
. = TRUE
if("award")
if(announcement_faction != FACTION_MARINE)
return
- open_medal_panel(usr, src)
+ open_medal_panel(user, src)
. = TRUE
if("mapview")
- tacmap.tgui_interact(usr)
+ tacmap.tgui_interact(user)
. = TRUE
if("evacuation_start")
@@ -135,20 +135,20 @@
return
if(GLOB.security_level < SEC_LEVEL_RED)
- to_chat(usr, SPAN_WARNING("The ship must be under red alert in order to enact evacuation procedures."))
+ to_chat(user, SPAN_WARNING("The ship must be under red alert in order to enact evacuation procedures."))
return FALSE
if(SShijack.evac_admin_denied)
- to_chat(usr, SPAN_WARNING("The USCM has placed a lock on deploying the evacuation pods."))
+ to_chat(user, SPAN_WARNING("The USCM has placed a lock on deploying the evacuation pods."))
return FALSE
if(!SShijack.initiate_evacuation())
- to_chat(usr, SPAN_WARNING("You are unable to initiate an evacuation procedure right now!"))
+ to_chat(user, SPAN_WARNING("You are unable to initiate an evacuation procedure right now!"))
return FALSE
- log_game("[key_name(usr)] has called for an emergency evacuation.")
- message_admins("[key_name_admin(usr)] has called for an emergency evacuation.")
- log_ares_security("Initiate Evacuation", "[usr] has called for an emergency evacuation.")
+ log_game("[key_name(user)] has called for an emergency evacuation.")
+ message_admins("[key_name_admin(user)] has called for an emergency evacuation.")
+ log_ares_security("Initiate Evacuation", "Called for an emergency evacuation.", user)
. = TRUE
if("distress")
@@ -156,14 +156,14 @@
return FALSE //Not a game mode?
if(GLOB.security_level == SEC_LEVEL_DELTA)
- to_chat(usr, SPAN_WARNING("The ship is already undergoing self destruct procedures!"))
+ to_chat(user, SPAN_WARNING("The ship is already undergoing self destruct procedures!"))
return FALSE
for(var/client/C in GLOB.admins)
if((R_ADMIN|R_MOD) & C.admin_holder.rights)
playsound_client(C,'sound/effects/sos-morse-code.ogg',10)
- SSticker.mode.request_ert(usr)
- to_chat(usr, SPAN_NOTICE("A distress beacon request has been sent to USCM Central Command."))
+ SSticker.mode.request_ert(user)
+ to_chat(user, SPAN_NOTICE("A distress beacon request has been sent to USCM Central Command."))
COOLDOWN_START(src, distress_cooldown, COOLDOWN_COMM_REQUEST)
return TRUE
diff --git a/code/game/objects/items/frames/table_rack.dm b/code/game/objects/items/frames/table_rack.dm
index c7aa53a2c4c1..eda9b9c5749b 100644
--- a/code/game/objects/items/frames/table_rack.dm
+++ b/code/game/objects/items/frames/table_rack.dm
@@ -100,6 +100,7 @@
desc = "A kit for a table, including a large, flat wooden surface and four legs. Some assembly required."
icon_state = "wood_tableparts"
flags_atom = FPRINT
+ matter = null
table_type = /obj/structure/surface/table/woodentable
/obj/item/frame/table/wood/attackby(obj/item/W, mob/user)
@@ -140,6 +141,7 @@
desc = "A kit for a table, including a large, flat wooden and carpet surface and four legs. Some assembly required."
icon_state = "gamble_tableparts"
flags_atom = null
+ matter = null
table_type = /obj/structure/surface/table/gamblingtable
/obj/item/frame/table/gambling/attackby(obj/item/W as obj, mob/user as mob)
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 5e4bc8c726bd..4882db3b83ea 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -729,6 +729,8 @@ GLOBAL_LIST_EMPTY_TYPED(radio_packs, /obj/item/storage/backpack/marine/satchel/r
name = "\improper M68 Thermal Cloak"
desc = "The lightweight thermal dampeners and optical camouflage provided by this cloak are weaker than those found in standard USCM ghillie suits. In exchange, the cloak can be worn over combat armor and offers the wearer high maneuverability and adaptability to many environments."
icon_state = "scout_cloak"
+ unacidable = TRUE
+ indestructible = TRUE
uniform_restricted = list(/obj/item/clothing/suit/storage/marine/M3S) //Need to wear Scout armor and helmet to equip this.
has_gamemode_skin = FALSE //same sprite for all gamemode.
var/camo_active = FALSE
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index 3e7c00f3d0ff..201e34654624 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -510,6 +510,34 @@
/obj/item/storage/pill_bottle/proc/error_idlock(mob/user)
to_chat(user, SPAN_WARNING("It must have some kind of ID lock..."))
+/obj/item/storage/pill_bottle/proc/choose_color(mob/user)
+ if(!user)
+ user = usr
+ var/static/list/possible_colors = list(
+ "Orange" = "",
+ "Blue" = "1",
+ "Yellow" = "2",
+ "Light Purple" = "3",
+ "Light Grey" = "4",
+ "White" = "5",
+ "Light Green" = "6",
+ "Cyan" = "7",
+ "Bordeaux" = "8",
+ "Aquamarine" = "9",
+ "Grey" = "10",
+ "Red" = "11",
+ "Black" = "12",
+ )
+ var/selected_color = tgui_input_list(user, "Select a color.", "Color choice", possible_colors)
+ if(!selected_color)
+ return
+
+ selected_color = possible_colors[selected_color]
+
+ icon_state = "pill_canister" + selected_color
+ to_chat(user, SPAN_NOTICE("You color [src]."))
+ update_icon()
+
/obj/item/storage/pill_bottle/verb/set_maptext()
set category = "Object"
set name = "Set short label (on-sprite)"
diff --git a/code/game/objects/items/tools/misc_tools.dm b/code/game/objects/items/tools/misc_tools.dm
index 06f42aacd56c..b016f0e67b33 100644
--- a/code/game/objects/items/tools/misc_tools.dm
+++ b/code/game/objects/items/tools/misc_tools.dm
@@ -58,6 +58,10 @@
if(isturf(A))
to_chat(user, SPAN_WARNING("The label won't stick to that."))
return
+ if(istype(A, /obj/item/storage/pill_bottle))
+ var/obj/item/storage/pill_bottle/target_pill_bottle = A
+ target_pill_bottle.choose_color(user)
+
if(!label || !length(label))
remove_label(A, user)
return
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index a3febb11dddb..70dc5ff1786d 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -208,6 +208,9 @@
/obj/proc/hear_talk(mob/living/M as mob, msg, verb="says", datum/language/speaking, italics = 0)
return
+/obj/proc/see_emote(mob/living/M as mob, emote, audible = FALSE)
+ return
+
/obj/attack_hand(mob/user)
if(can_buckle) manual_unbuckle(user)
else . = ..()
@@ -231,7 +234,7 @@
/obj/proc/afterbuckle(mob/M as mob) // Called after somebody buckled / unbuckled
handle_rotation() // To be removed when we have full dir support in set_buckled
- SEND_SIGNAL(src, COSMIG_OBJ_AFTER_BUCKLE, buckled_mob)
+ SEND_SIGNAL(src, COMSIG_OBJ_AFTER_BUCKLE, buckled_mob)
if(!buckled_mob)
UnregisterSignal(M, COMSIG_PARENT_QDELETING)
else
diff --git a/code/game/objects/structures/bookcase.dm b/code/game/objects/structures/bookcase.dm
index b310bd00aa07..a6fc95fa0d73 100644
--- a/code/game/objects/structures/bookcase.dm
+++ b/code/game/objects/structures/bookcase.dm
@@ -7,6 +7,24 @@
density = TRUE
opacity = TRUE
+/obj/structure/bookcase/deconstruct(disassembled)
+ new /obj/item/stack/sheet/metal(loc)
+ return ..()
+
+/obj/structure/bookcase/attack_alien(mob/living/carbon/xenomorph/xeno)
+ if(xeno.a_intent == INTENT_HARM)
+ if(unslashable)
+ return
+ xeno.animation_attack_on(src)
+ playsound(loc, 'sound/effects/metalhit.ogg', 25, 1)
+ xeno.visible_message(SPAN_DANGER("[xeno] slices [src] apart!"),
+ SPAN_DANGER("We slice [src] apart!"), null, 5, CHAT_TYPE_XENO_COMBAT)
+ deconstruct(FALSE)
+ return XENO_ATTACK_ACTION
+ else
+ attack_hand(xeno)
+ return XENO_NONCOMBAT_ACTION
+
/obj/structure/bookcase/Initialize()
. = ..()
for(var/obj/item/I in loc)
@@ -20,12 +38,18 @@
O.forceMove(src)
update_icon()
else if(HAS_TRAIT(O, TRAIT_TOOL_PEN))
- var/newname = stripped_input(usr, "What would you like to title this bookshelf?")
+ var/newname = stripped_input(user, "What would you like to title this bookshelf?")
if(!newname)
return
else
name = ("bookcase ([strip_html(newname)])")
playsound(src, "paper_writing", 15, TRUE)
+ else if(HAS_TRAIT(O, TRAIT_TOOL_WRENCH))
+ playsound(loc, 'sound/items/Ratchet.ogg', 25, 1)
+ if(do_after(user, 1 SECONDS, INTERRUPT_MOVED, BUSY_ICON_FRIENDLY, src))
+ user.visible_message("[user] deconstructs [src].", \
+ "You deconstruct [src].", "You hear a noise.")
+ deconstruct(FALSE)
else
..()
@@ -33,7 +57,7 @@
if(contents.len)
var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents
if(choice)
- if(usr.is_mob_incapacitated() || !in_range(loc, usr))
+ if(user.is_mob_incapacitated() || !in_range(loc, user))
return
if(ishuman(user))
if(!user.get_active_hand())
@@ -67,7 +91,7 @@
/obj/structure/bookcase/manuals/medical
- name = "Medical Manuals bookcase"
+ name = "medical manuals bookcase"
/obj/structure/bookcase/manuals/medical/Initialize()
. = ..()
@@ -78,7 +102,7 @@
/obj/structure/bookcase/manuals/engineering
- name = "Engineering Manuals bookcase"
+ name = "engineering manuals bookcase"
/obj/structure/bookcase/manuals/engineering/Initialize()
. = ..()
@@ -90,7 +114,7 @@
update_icon()
/obj/structure/bookcase/manuals/research_and_development
- name = "R&D Manuals bookcase"
+ name = "\improper R&D manuals bookcase"
/obj/structure/bookcase/manuals/research_and_development/Initialize()
. = ..()
diff --git a/code/game/objects/structures/platforms.dm b/code/game/objects/structures/platforms.dm
index cfffbc90fb7c..5510d319ee1e 100644
--- a/code/game/objects/structures/platforms.dm
+++ b/code/game/objects/structures/platforms.dm
@@ -142,7 +142,6 @@
icon_state = "kutjevo_platform"
name = "raised metal edge"
desc = "A raised level of metal, often used to elevate areas above others, or construct bridges. You could probably climb it."
- climb_delay = 10
/obj/structure/platform_decoration/kutjevo
name = "raised metal corner"
diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm
index 482139c03707..03c554af3426 100644
--- a/code/game/supplyshuttle.dm
+++ b/code/game/supplyshuttle.dm
@@ -412,6 +412,7 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
"Operations",
"Weapons",
"Vehicle Ammo",
+ "Vehicle Equipment",
"Attachments",
"Ammo",
"Weapons Specialist Ammo",
@@ -1367,6 +1368,13 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
name = "Barebones M577 Armored Personal Carrier"
ordered_vehicle = /obj/effect/vehicle_spawner/apc/unarmed/broken
+/datum/vehicle_order/arc
+ name = "M540-B Armored Recon Carrier"
+ ordered_vehicle = /obj/effect/vehicle_spawner/arc
+
+/datum/vehicle_order/arc/has_vehicle_lock()
+ return
+
/obj/structure/machinery/computer/supplycomp/vehicle/Initialize()
. = ..()
diff --git a/code/game/turfs/floor_types.dm b/code/game/turfs/floor_types.dm
index 8a8698d0c047..0a1842134480 100644
--- a/code/game/turfs/floor_types.dm
+++ b/code/game/turfs/floor_types.dm
@@ -325,12 +325,21 @@
/turf/open/floor/almayer/aicore/glowing
icon_state = "ai_floor2"
light_color = "#d69c46"
- light_range = 2
+ light_range = 3
/turf/open/floor/almayer/aicore/glowing/Initialize(mapload, ...)
. = ..()
set_light_on(TRUE)
+ RegisterSignal(SSdcs, COMSIG_GLOB_AICORE_LOCKDOWN, PROC_REF(start_emergency_light_on))
+ RegisterSignal(SSdcs, COMSIG_GLOB_AICORE_LIFT, PROC_REF(start_emergency_light_off))
+
+/turf/open/floor/almayer/aicore/glowing/proc/start_emergency_light_on()
+ set_light(l_color = "#c70f0f")
+
+/turf/open/floor/almayer/aicore/glowing/proc/start_emergency_light_off()
+ set_light(l_color = "#d69c46")
+
/turf/open/floor/almayer/aicore/no_build
allow_construction = FALSE
hull_floor = TRUE
diff --git a/code/game/turfs/walls/walls.dm b/code/game/turfs/walls/walls.dm
index 251b23ad9c57..1781739176b7 100644
--- a/code/game/turfs/walls/walls.dm
+++ b/code/game/turfs/walls/walls.dm
@@ -9,7 +9,7 @@
var/hull = 0
var/walltype = WALL_METAL
/// when walls smooth with one another, the type of junction each wall is.
- var/junctiontype
+ var/junctiontype
var/thermite = 0
var/melting = FALSE
var/claws_minimum = CLAW_TYPE_SHARP
@@ -24,7 +24,7 @@
var/damage = 0
/// Wall will break down to girders if damage reaches this point
- var/damage_cap = HEALTH_WALL
+ var/damage_cap = HEALTH_WALL
var/damage_overlay
var/global/damage_overlays[8]
@@ -38,7 +38,7 @@
var/d_state = 0 //Normal walls are now as difficult to remove as reinforced walls
/// the acid hole inside the wall
- var/obj/effect/acid_hole/acided_hole
+ var/obj/effect/acid_hole/acided_hole
var/acided_hole_dir = SOUTH
var/special_icon = 0
@@ -178,7 +178,7 @@
switch(d_state)
if(WALL_STATE_WELD)
- . += SPAN_INFO("The outer plating is intact. A blowtorch should slice it open.")
+ . += SPAN_INFO("The outer plating is intact. If you are not on help intent, a blowtorch should slice it open.")
if(WALL_STATE_SCREW)
. += SPAN_INFO("The outer plating has been sliced open. A screwdriver should remove the support lines.")
if(WALL_STATE_WIRECUTTER)
@@ -482,6 +482,8 @@
/turf/closed/wall/proc/try_weldingtool_usage(obj/item/W, mob/user)
if(!damage || !iswelder(W))
return FALSE
+ if(user.a_intent != INTENT_HELP)
+ return FALSE
var/obj/item/tool/weldingtool/WT = W
if(WT.remove_fuel(0, user))
@@ -504,6 +506,8 @@
if(!(WT.remove_fuel(0, user)))
to_chat(user, SPAN_WARNING("You need more welding fuel!"))
return
+ if(user.a_intent == INTENT_HELP)
+ return
playsound(src, 'sound/items/Welder.ogg', 25, 1)
user.visible_message(SPAN_NOTICE("[user] begins slicing through the outer plating."),
diff --git a/code/game/verbs/records.dm b/code/game/verbs/records.dm
index 05506804790a..3810bf7e99cb 100644
--- a/code/game/verbs/records.dm
+++ b/code/game/verbs/records.dm
@@ -1,10 +1,8 @@
-//CO Whitelist is '1', Synthetic Whitelist is '2', Yautja Whitelist is '3'.
-
/client/verb/own_records()
set name = "View Own Records"
set category = "OOC.Records"
- var/list/options = list("Admin", "Merit", "Commanding Officer", "Synthetic", "Yautja")
+ var/list/options = list("Admin", "Merit", "Whitelist")
var/choice = tgui_input_list(usr, "What record do you wish to view?", "Record Choice", options)
switch(choice)
@@ -12,12 +10,8 @@
show_own_notes(NOTE_ADMIN, choice)
if("Merit")
show_own_notes(NOTE_MERIT, choice)
- if("Commanding Officer")
- show_own_notes(NOTE_COMMANDER, choice)
- if("Synthetic")
- show_own_notes(NOTE_SYNTHETIC, choice)
- if("Yautja")
- show_own_notes(NOTE_YAUTJA, choice)
+ if("Whitelist")
+ show_own_notes(NOTE_WHITELIST, choice)
else
return
to_chat(usr, SPAN_NOTICE("Displaying your [choice] Record."))
@@ -46,12 +40,8 @@
switch(note_category)
if(NOTE_MERIT)
color = "#9e3dff"
- if(NOTE_COMMANDER)
+ if(NOTE_WHITELIST)
color = "#324da5"
- if(NOTE_SYNTHETIC)
- color = "#39e7a4"
- if(NOTE_YAUTJA)
- color = "#114e11"
dat += "[N.text] by [admin_ckey] ([N.admin_rank]) on [N.date] [NOTE_ROUND_ID(N)] "
dat += "
"
@@ -69,16 +59,15 @@
//Contributions and suggestions are welcome.
//Kindly, forest2001
-/client/verb/other_records()
+/client/proc/other_records()
set name = "View Target Records"
set category = "OOC.Records"
///Management Access
- var/MA
+ var/manager = FALSE
///Edit Access
- var/edit_C = FALSE
- var/edit_S = FALSE
- var/edit_Y = FALSE
+ var/add_wl = FALSE
+ var/del_wl = FALSE
///Note category options
var/list/options = list()
@@ -86,7 +75,7 @@
if(CLIENT_IS_STAFF(src))
options = GLOB.note_categories.Copy()
if(admin_holder.rights & R_PERMISSIONS)
- MA = TRUE
+ manager = TRUE
else if(!isCouncil(src))
to_chat(usr, SPAN_WARNING("Error: you are not authorised to view the records of another player!"))
return
@@ -97,15 +86,11 @@
return
target = ckey(target)
- if(check_whitelist_status(WHITELIST_COMMANDER_COUNCIL))
- options |= "Commanding Officer"
- edit_C = TRUE
- if(check_whitelist_status(WHITELIST_SYNTHETIC_COUNCIL))
- options |= "Synthetic"
- edit_S = TRUE
- if(check_whitelist_status(WHITELIST_YAUTJA_COUNCIL))
- options |= "Yautja"
- edit_Y = TRUE
+ if(manager || isCouncil(src))
+ options |= "Whitelist"
+ add_wl = TRUE
+ if(manager || isSenator(src))
+ del_wl = TRUE
var/choice = tgui_input_list(usr, "What record do you wish to view?", "Record Choice", options)
if(!choice)
@@ -115,21 +100,8 @@
show_other_record(NOTE_ADMIN, choice, target, TRUE)
if("Merit")
show_other_record(NOTE_MERIT, choice, target, TRUE)
- if("Commanding Officer")
- if(MA || check_whitelist_status(WHITELIST_COMMANDER_LEADER))
- show_other_record(NOTE_COMMANDER, choice, target, TRUE, TRUE)
- else
- show_other_record(NOTE_COMMANDER, choice, target, edit_C)
- if("Synthetic")
- if(MA || check_whitelist_status(WHITELIST_SYNTHETIC_LEADER))
- show_other_record(NOTE_SYNTHETIC, choice, target, TRUE, TRUE)
- else
- show_other_record(NOTE_SYNTHETIC, choice, target, edit_S)
- if("Yautja")
- if(MA || check_whitelist_status(WHITELIST_YAUTJA_LEADER))
- show_other_record(NOTE_YAUTJA, choice, target, TRUE, TRUE)
- else
- show_other_record(NOTE_YAUTJA, choice, target, edit_Y)
+ if("Whitelist")
+ show_other_record(NOTE_WHITELIST, choice, target, add_wl, del_wl)
to_chat(usr, SPAN_NOTICE("Displaying [target]'s [choice] notes."))
@@ -148,15 +120,9 @@
if(NOTE_MERIT)
color = "#9e3dff"
add_dat = "Add Merit Note "
- if(NOTE_COMMANDER)
+ if(NOTE_WHITELIST)
color = "#324da5"
- add_dat = "Add Commander Note "
- if(NOTE_SYNTHETIC)
- color = "#39e7a4"
- add_dat = "Add Synthetic Note "
- if(NOTE_YAUTJA)
- color = "#114e11"
- add_dat = "Add Yautja Note "
+ add_dat = "Add Whitelist Note "
var/list/datum/view_record/note_view/NL = DB_VIEW(/datum/view_record/note_view, DB_COMP("player_ckey", DB_EQUALS, target))
for(var/datum/view_record/note_view/N as anything in NL)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 4194627262a4..979217019f0c 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -95,12 +95,8 @@
color = "#AA0055"
else if(N.note_category == NOTE_MERIT)
color = "#9e3dff"
- else if(N.note_category == NOTE_COMMANDER)
+ else if(N.note_category == NOTE_WHITELIST)
color = "#324da5"
- else if(N.note_category == NOTE_SYNTHETIC)
- color = "#39e7a4"
- else if(N.note_category == NOTE_YAUTJA)
- color = "#114e11"
dat += "[N.text] by [admin_ckey] ([N.admin_rank])[confidential_text] on [N.date] [NOTE_ROUND_ID(N)] "
if(admin_ckey == usr.ckey || admin_ckey == "Adminbot" || check_for_rights(R_PERMISSIONS))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 626758fc2a5a..aa87f157173c 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -72,6 +72,7 @@ GLOBAL_LIST_INIT(admin_verbs_default, list(
/client/proc/cmd_admin_say, /*staff-only ooc chat*/
/client/proc/cmd_mod_say, /* alternate way of typing asay, no different than cmd_admin_say */
/client/proc/cmd_admin_tacmaps_panel,
+ /client/proc/other_records,
))
GLOBAL_LIST_INIT(admin_verbs_admin, list(
@@ -138,6 +139,7 @@ GLOBAL_LIST_INIT(admin_verbs_minor_event, list(
/client/proc/adminpanelweapons,
/client/proc/admin_general_quarters,
/client/proc/admin_biohazard_alert,
+ /client/proc/admin_aicore_alert,
/client/proc/toggle_hardcore_perma,
/client/proc/toggle_bypass_joe_restriction,
/client/proc/toggle_joe_respawns,
diff --git a/code/modules/almayer/machinery.dm b/code/modules/almayer/machinery.dm
index 74ce9a81eb88..9411c229d2f3 100644
--- a/code/modules/almayer/machinery.dm
+++ b/code/modules/almayer/machinery.dm
@@ -73,7 +73,7 @@
/obj/structure/machinery/prop/almayer/CICmap
name = "map table"
- desc = "A table that displays a map of the current target location"
+ desc = "A table that displays a map of the current operation location."
icon = 'icons/obj/structures/machinery/computer.dmi'
icon_state = "maptable"
anchored = TRUE
@@ -103,6 +103,11 @@
map.tgui_interact(user)
+/obj/structure/machinery/prop/almayer/CICmap/computer
+ name = "map terminal"
+ desc = "A terminal that displays a map of the current operation location."
+ icon_state = "security"
+
/obj/structure/machinery/prop/almayer/CICmap/upp
minimap_type = MINIMAP_FLAG_UPP
faction = FACTION_UPP
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 213c54f0e201..6afbfa695db2 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -187,39 +187,21 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list(
var/datum/entity/player/P = get_player_from_key(key)
P.add_note(add, FALSE, NOTE_MERIT)
- if(href_list["add_wl_info_1"])
- var/key = href_list["add_wl_info_1"]
- var/add = input("Add Commander Note") as null|message
+ if(href_list["add_wl_info"])
+ var/key = href_list["add_wl_info"]
+ var/add = input("Add Whitelist Note") as null|message
if(!add)
return
var/datum/entity/player/P = get_player_from_key(key)
- P.add_note(add, FALSE, NOTE_COMMANDER)
-
- if(href_list["add_wl_info_2"])
- var/key = href_list["add_wl_info_2"]
- var/add = input("Add Synthetic Note") as null|message
- if(!add)
- return
-
- var/datum/entity/player/P = get_player_from_key(key)
- P.add_note(add, FALSE, NOTE_SYNTHETIC)
-
- if(href_list["add_wl_info_3"])
- var/key = href_list["add_wl_info_3"]
- var/add = input("Add Yautja Note") as null|message
- if(!add)
- return
-
- var/datum/entity/player/P = get_player_from_key(key)
- P.add_note(add, FALSE, NOTE_YAUTJA)
+ P.add_note(add, FALSE, NOTE_WHITELIST)
if(href_list["remove_wl_info"])
var/key = href_list["remove_wl_info"]
var/index = text2num(href_list["remove_index"])
var/datum/entity/player/P = get_player_from_key(key)
- P.remove_note(index)
+ P.remove_note(index, whitelist = TRUE)
switch(href_list["_src_"])
if("admin_holder")
diff --git a/code/modules/cm_marines/dropship_equipment.dm b/code/modules/cm_marines/dropship_equipment.dm
index bd40076ea500..af06f468adcd 100644
--- a/code/modules/cm_marines/dropship_equipment.dm
+++ b/code/modules/cm_marines/dropship_equipment.dm
@@ -524,12 +524,6 @@
if(light_on)
set_light(0)
-/obj/structure/dropship_equipment/electronics/spotlights/on_launch()
- set_light(0)
-
-/obj/structure/dropship_equipment/electronics/spotlights/on_arrival()
- set_light(brightness)
-
/obj/structure/dropship_equipment/electronics/spotlights/ui_data(mob/user)
. = list()
var/is_deployed = light_on
diff --git a/code/modules/cm_marines/overwatch.dm b/code/modules/cm_marines/overwatch.dm
index 70b2b82d8c86..500d575c053f 100644
--- a/code/modules/cm_marines/overwatch.dm
+++ b/code/modules/cm_marines/overwatch.dm
@@ -867,6 +867,9 @@
/obj/structure/machinery/computer/overwatch/almayer/broken
name = "Broken Overwatch Console"
+/obj/structure/machinery/computer/overwatch/almayer/small
+ icon_state = "engineering_terminal"
+
/obj/structure/machinery/computer/overwatch/clf
faction = FACTION_CLF
/obj/structure/machinery/computer/overwatch/upp
diff --git a/code/modules/cm_tech/techs/marine/tier1/arc.dm b/code/modules/cm_tech/techs/marine/tier1/arc.dm
new file mode 100644
index 000000000000..dc02762cc5f0
--- /dev/null
+++ b/code/modules/cm_tech/techs/marine/tier1/arc.dm
@@ -0,0 +1,40 @@
+/datum/tech/arc
+ name = "M540-B Armored Recon Carrier"
+ desc = "Purchase an M540-B Armored Recon Carrier, specialized in assisting groundside command. Able to be driven by Staff Officers, Executive Officers, and Commanding Officers."
+ icon_state = "upgrade"
+
+ required_points = 5
+
+ tier = /datum/tier/one
+
+ announce_name = "M540-B ARC ACQUIRED"
+ announce_message = "An M540-B Armored Recon Carrier has been authorized and will be delivered in the vehicle bay."
+
+ flags = TREE_FLAG_MARINE
+
+/datum/tech/arc/on_unlock()
+ . = ..()
+
+ var/obj/structure/machinery/computer/supplycomp/vehicle/comp = GLOB.VehicleElevatorConsole
+ var/obj/structure/machinery/cm_vending/gear/vehicle_crew/gearcomp = GLOB.VehicleGearConsole
+
+ if(!comp || !gearcomp)
+ return FALSE
+
+ comp.spent = FALSE
+ QDEL_NULL_LIST(comp.vehicles)
+ comp.vehicles = list(
+ new /datum/vehicle_order/arc()
+ )
+ comp.allowed_roles = list(JOB_SYNTH, JOB_SEA, JOB_SO, JOB_XO, JOB_CO, JOB_GENERAL)
+ comp.req_access = list(ACCESS_MARINE_COMMAND)
+ comp.req_one_access = list()
+ comp.spent = FALSE
+
+ gearcomp.req_access = list(ACCESS_MARINE_COMMAND)
+ gearcomp.req_one_access = list()
+ gearcomp.vendor_role = list()
+ gearcomp.selected_vehicle = "ARC"
+ gearcomp.available_categories = VEHICLE_ALL_AVAILABLE
+
+ return TRUE
diff --git a/code/modules/defenses/sentry.dm b/code/modules/defenses/sentry.dm
index a02e4e7808c9..954e6adca7ec 100644
--- a/code/modules/defenses/sentry.dm
+++ b/code/modules/defenses/sentry.dm
@@ -365,7 +365,7 @@
targets.Remove(A)
continue
- if(M.get_target_lock(faction_group) || M.invisibility || HAS_TRAIT(M, TRAIT_ABILITY_BURROWED))
+ if(M.get_target_lock(faction_group) || M.invisibility || HAS_TRAIT(M, TRAIT_ABILITY_BURROWED) || M.is_ventcrawling)
if(M == target)
target = null
targets.Remove(M)
diff --git a/code/modules/gear_presets/clf.dm b/code/modules/gear_presets/clf.dm
index 143a2271f00e..bb168f22457b 100644
--- a/code/modules/gear_presets/clf.dm
+++ b/code/modules/gear_presets/clf.dm
@@ -71,6 +71,7 @@
new_human.equip_to_slot_or_del(new /obj/item/storage/belt/shotgun/full/random(new_human), WEAR_WAIST)
new_human.equip_to_slot_or_del(new /obj/item/weapon/gun/shotgun/pump/dual_tube/cmb(new_human), WEAR_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/attachable/bayonet/upp(new_human), WEAR_FACE)
if(prob(50))
spawn_rebel_smg(new_human)
else
@@ -176,6 +177,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/welding, WEAR_HEAD)
new_human.equip_to_slot_or_del(new /obj/item/storage/belt/utility/full(new_human), WEAR_WAIST)
new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CLF/cct, WEAR_L_EAR)
+ new_human.equip_to_slot_or_del(new /obj/item/attachable/bayonet/upp(new_human), WEAR_FACE)
new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/marine/engineerpack/ert, WEAR_BACK)
new_human.equip_to_slot_or_del(new /obj/item/explosive/plastic/breaching_charge, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/explosive/plastic/breaching_charge, WEAR_IN_BACK)
@@ -300,6 +302,7 @@
new_human.equip_to_slot_or_del(new /obj/item/roller, WEAR_IN_BELT)
new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CLF/medic(new_human), WEAR_L_EAR)
+ new_human.equip_to_slot_or_del(new /obj/item/attachable/bayonet/upp(new_human), WEAR_FACE)
new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/lightpack(new_human), WEAR_BACK)
new_human.equip_to_slot_or_del(new /obj/item/explosive/grenade/custom/ied(new_human), WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/storage/firstaid/adv(new_human), WEAR_IN_BACK)
@@ -455,6 +458,7 @@
//clothing
new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/militia(new_human), WEAR_JACKET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/swat(new_human), WEAR_HEAD)
+ new_human.equip_to_slot_or_del(new /obj/item/attachable/bayonet/upp(new_human), WEAR_FACE)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(new_human), WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(new_human), WEAR_HANDS)
new_human.equip_to_slot_or_del(new /obj/item/storage/belt/gun/m4a3/mod88(new_human), WEAR_WAIST)
@@ -574,6 +578,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/colonist/clf(new_human), WEAR_BODY)
new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/militia(new_human), WEAR_JACKET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/beret/sec/hos(new_human), WEAR_HEAD)
+ new_human.equip_to_slot_or_del(new /obj/item/attachable/bayonet/upp(new_human), WEAR_FACE)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(new_human), WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/gloves/black(new_human), WEAR_HANDS)
new_human.equip_to_slot_or_del(new /obj/item/storage/belt/marine(new_human), WEAR_WAIST)
diff --git a/code/modules/gear_presets/wy_goons.dm b/code/modules/gear_presets/wy_goons.dm
index a8f3a443311e..7867016491dc 100644
--- a/code/modules/gear_presets/wy_goons.dm
+++ b/code/modules/gear_presets/wy_goons.dm
@@ -62,7 +62,7 @@
assignment = JOB_WY_GOON
rank = JOB_WY_GOON
paygrade = PAY_SHORT_CPO
- skills = /datum/skills/MP
+ skills = /datum/skills/wy_goon
/datum/equipment_preset/goon/standard/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/WY, WEAR_L_EAR)
@@ -88,6 +88,40 @@
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/ap, WEAR_IN_BACK)
+/datum/equipment_preset/goon/engineer
+ name = "Weyland-Yutani Corporate Security Technician (Goon Engineer)"
+ flags = EQUIPMENT_PRESET_EXTRA
+
+ assignment = JOB_WY_GOON_TECH
+ rank = JOB_WY_GOON_TECH
+ paygrade = PAY_SHORT_CPO
+ skills = /datum/skills/wy_goon_tech
+
+/datum/equipment_preset/goon/engineer/load_gear(mob/living/carbon/human/new_human)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/WY, WEAR_L_EAR)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/under/marine/veteran/pmc/corporate, WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/marine/veteran/pmc/light/corporate, WEAR_JACKET)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/gloves/marine/veteran, WEAR_HANDS)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/marine/veteran/pmc/corporate, WEAR_HEAD)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/welding, WEAR_EYES)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/corporate, WEAR_FEET)
+
+ new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/marine/engineerpack/ert, WEAR_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/weapon/baton, WEAR_IN_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/handcuffs/zip, WEAR_IN_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle, WEAR_IN_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle, WEAR_IN_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle, WEAR_IN_BACK)
+
+ new_human.equip_to_slot_or_del(new /obj/item/storage/belt/utility/full, WEAR_WAIST)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_R_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/construction/full, WEAR_L_STORE)
+
+ new_human.equip_to_slot_or_del(new /obj/item/weapon/gun/rifle/m41a/corporate, WEAR_J_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/ap, WEAR_IN_JACKET)
+ new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/ap, WEAR_IN_JACKET)
+
+
/datum/equipment_preset/goon/lead
name = "Weyland-Yutani Corporate Security Lead (Goon Lead)"
flags = EQUIPMENT_PRESET_EXTRA
@@ -95,7 +129,7 @@
assignment = JOB_WY_GOON_LEAD
rank = JOB_WY_GOON_LEAD
paygrade = PAY_SHORT_CSPO
- skills = /datum/skills/MP
+ skills = /datum/skills/wy_goon_lead
/datum/equipment_preset/goon/lead/New()
. = ..()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 4e71b178824d..bc7d11c25e1f 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1687,3 +1687,11 @@
INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living/carbon/human, regenerate_icons))
INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living/carbon/human, update_body), 1, 0)
INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living/carbon/human, update_hair))
+
+/mob/living/carbon/human/point_to_atom(atom/A, turf/T)
+ if(isitem(A))
+ var/obj/item/item = A
+ if(item == get_active_hand() || item == get_inactive_hand())
+ item.showoff(src)
+ return TRUE
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/human_abilities.dm b/code/modules/mob/living/carbon/human/human_abilities.dm
index a568e93df5c0..2d7f472952cc 100644
--- a/code/modules/mob/living/carbon/human/human_abilities.dm
+++ b/code/modules/mob/living/carbon/human/human_abilities.dm
@@ -605,3 +605,26 @@ CULT
var/mob/living/carbon/human/human_user = owner
SEND_SIGNAL(human_user, COMSIG_MOB_MG_EXIT)
+
+/datum/action/human_action/toggle_arc_antenna
+ name = "Toggle Sensor Antenna"
+ action_icon_state = "recoil_compensation"
+
+/datum/action/human_action/toggle_arc_antenna/give_to(mob/user)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOB_RESET_VIEW, PROC_REF(remove_from))
+
+/datum/action/human_action/toggle_arc_antenna/remove_from(mob/user)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOB_RESET_VIEW)
+
+/datum/action/human_action/toggle_arc_antenna/action_activate()
+ if(!can_use_action())
+ return
+
+ var/mob/living/carbon/human/human_user = owner
+ if(istype(human_user.buckled, /obj/structure/bed/chair/comfy/vehicle))
+ var/obj/structure/bed/chair/comfy/vehicle/vehicle_chair = human_user.buckled
+ if(istype(vehicle_chair.vehicle, /obj/vehicle/multitile/arc))
+ var/obj/vehicle/multitile/arc/vehicle = vehicle_chair.vehicle
+ vehicle.toggle_antenna(human_user)
diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
index 8aa3000092f5..9a87f10d74a3 100644
--- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
@@ -281,7 +281,7 @@
if(isturf(human.loc))
forceMove(human.loc)//Just checkin
- if(!human.handle_hugger_attachment(src))
+ if(!human.handle_hugger_attachment(src, hugger))
return FALSE
attached = TRUE
@@ -413,9 +413,6 @@
M.stored_huggers++
qdel(src)
return
- // Tutorial facehuggers never time out
- if(hivenumber == XENO_HIVE_TUTORIAL)
- return
die()
/obj/item/clothing/mask/facehugger/proc/die()
@@ -441,12 +438,15 @@
playsound(src.loc, 'sound/voice/alien_facehugger_dies.ogg', 25, 1)
if(ismob(loc)) //Make it fall off the person so we can update their icons. Won't update if they're in containers thou
- var/mob/M = loc
- M.drop_inv_item_on_ground(src)
+ var/mob/holder_mob = loc
+ holder_mob.drop_inv_item_on_ground(src)
layer = TURF_LAYER //so dead hugger appears below live hugger if stacked on same tile. (and below nested hosts)
- addtimer(CALLBACK(src, PROC_REF(decay)), 3 MINUTES)
+ if(hivenumber == XENO_HIVE_TUTORIAL)
+ addtimer(CALLBACK(src, PROC_REF(decay)), 5 SECONDS)
+ else
+ addtimer(CALLBACK(src, PROC_REF(decay)), 3 MINUTES)
/obj/item/clothing/mask/facehugger/proc/decay()
visible_message("[icon2html(src, viewers(src))] \The [src] decays into a mass of acid and chitin.")
@@ -491,15 +491,14 @@
/**
* Human hugger handling
*/
-
-/mob/living/carbon/human/proc/handle_hugger_attachment(obj/item/clothing/mask/facehugger/hugger)
+/mob/living/carbon/human/proc/handle_hugger_attachment(obj/item/clothing/mask/facehugger/hugger, mob/living/carbon/xenomorph/facehugger/mob_hugger)
var/can_infect = TRUE
if(!has_limb("head"))
hugger.visible_message(SPAN_WARNING("[hugger] looks for a face to hug on [src], but finds none!"))
hugger.go_idle()
return FALSE
- if(species && !species.handle_hugger_attachment(src, hugger))
+ if(species && !species.handle_hugger_attachment(src, hugger, mob_hugger))
return FALSE
if(head && !(head.flags_item & NODROP))
@@ -546,10 +545,10 @@
return can_infect
-/datum/species/proc/handle_hugger_attachment(mob/living/carbon/human/target, obj/item/clothing/mask/facehugger/hugger)
+/datum/species/proc/handle_hugger_attachment(mob/living/carbon/human/target, obj/item/clothing/mask/facehugger/hugger, mob/living/carbon/xenomorph/facehugger/mob_hugger)
return TRUE
-/datum/species/yautja/handle_hugger_attachment(mob/living/carbon/human/target, obj/item/clothing/mask/facehugger/hugger)
+/datum/species/yautja/handle_hugger_attachment(mob/living/carbon/human/target, obj/item/clothing/mask/facehugger/hugger, mob/living/carbon/xenomorph/facehugger/mob_hugger)
var/catch_chance = 50
if(target.dir == GLOB.reverse_dir[hugger.dir])
catch_chance += 20
@@ -563,7 +562,10 @@
if(!target.stat && target.dir != hugger.dir && prob(catch_chance)) //Not facing away
target.visible_message(SPAN_NOTICE("[target] snatches [hugger] out of the air and squashes it!"))
- hugger.die()
+ if(mob_hugger)
+ mob_hugger.death(create_cause_data("squished"))
+ else
+ hugger.die()
return FALSE
return TRUE
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoOverwatch.dm b/code/modules/mob/living/carbon/xenomorph/XenoOverwatch.dm
index 0b0efbc0f34f..0da2b61142e8 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoOverwatch.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoOverwatch.dm
@@ -93,6 +93,10 @@
to_chat(src, SPAN_XENOWARNING("Our sister's psychic connection is cut off!"))
return
+ if(HAS_TRAIT(src, TRAIT_ABILITY_BURROWED))
+ to_chat(src, SPAN_XENOWARNING("We cannot do this in our current state!"))
+ return
+
if(observed_xeno && targetXeno && observed_xeno == targetXeno)
if(istype(targetXeno, /obj/effect/alien/resin/marker))
to_chat(src, SPAN_XENOWARNING("We are already watching that mark!"))
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm b/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm
index 9bfc98a7091d..0990df678f61 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm
@@ -195,6 +195,10 @@
client.pixel_x = -viewoffset
client.pixel_y = 0
+ for (var/datum/action/xeno_action/onclick/toggle_long_range/action in actions)
+ action.on_zoom_in()
+ return
+
/mob/living/carbon/xenomorph/proc/zoom_out()
if(!client)
return
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
index f854272365d0..ee084e77a5a0 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
@@ -126,7 +126,7 @@
return
var/area/A = get_area(T)
- if(A.flags_area & AREA_NOTUNNEL)
+ if(A.flags_area & AREA_NOTUNNEL || get_dist(src, T) > 15)
to_chat(src, SPAN_XENOWARNING("There's no way to tunnel over there."))
return
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm
index 98c0c19f68f7..d7a4f987623a 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm
@@ -17,7 +17,6 @@
if(xeno.crest_defense)
to_chat(xeno, SPAN_XENOWARNING("We lower our crest."))
- xeno.balloon_alert(xeno, "crest lowered")
xeno.ability_speed_modifier += speed_debuff
xeno.armor_deflection_buff += armor_buff
@@ -26,7 +25,6 @@
xeno.update_icons()
else
to_chat(xeno, SPAN_XENOWARNING("We raise our crest."))
- xeno.balloon_alert(xeno, "crest raised")
xeno.ability_speed_modifier -= speed_debuff
xeno.armor_deflection_buff -= armor_buff
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
index ee1fed3094a7..d60d292cba25 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
@@ -17,3 +17,8 @@
freeze_time = 5
freeze_play_sound = FALSE
can_be_shield_blocked = TRUE
+
+/datum/action/xeno_action/onclick/toggle_long_range/facehugger
+ handles_movement = FALSE
+ should_delay = FALSE
+ ability_primacy = XENO_PRIMARY_ACTION_3
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm
index 054762b3c7d4..6eef21c6d5af 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm
@@ -25,4 +25,22 @@
if(current_airlock.density) //if its CLOSED YOU'RE SCUTTLING AND CANNOT POUNCE!!!
to_chat(owner, SPAN_WARNING("We cannot do that while squeezing and scuttling!"))
return FALSE
+
+ if(HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
+ to_chat(owner, SPAN_WARNING("We cannot do that while immobilized!"))
+ return FALSE
+
return ..()
+
+/datum/action/xeno_action/onclick/toggle_long_range/facehugger/on_zoom_out()
+ . = ..()
+
+ var/mob/living/carbon/xenomorph/facehugger/facehugger = owner
+ REMOVE_TRAIT(facehugger, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Long-Range Sight"))
+
+/datum/action/xeno_action/onclick/toggle_long_range/facehugger/on_zoom_in()
+ . = ..()
+
+ var/mob/living/carbon/xenomorph/facehugger/facehugger = owner
+ ADD_TRAIT(facehugger, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Long-Range Sight"))
+
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
index f36e23394eef..39b05b964648 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
@@ -329,6 +329,9 @@
xeno.recalculate_speed()
button.icon_state = "template"
+/datum/action/xeno_action/onclick/toggle_long_range/proc/on_zoom_in()
+ return
+
/datum/action/xeno_action/onclick/toggle_long_range/proc/handle_mob_move_or_look(mob/living/carbon/xenomorph/xeno, actually_moving, direction, specific_direction)
SIGNAL_HANDLER
movement_buffer--
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
index 8a829d8d6bc0..17677c8427af 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
@@ -18,8 +18,6 @@
macro_path = /datum/action/xeno_action/verb/verb_lurker_invisibility
ability_primacy = XENO_PRIMARY_ACTION_2
action_type = XENO_ACTION_CLICK
- xeno_cooldown = 1 // This ability never goes off cooldown 'naturally'. Cooldown is applied manually as a super-large value in the use_ability proc
- // and reset by the behavior_delegate whenever the ability ends (because it can be ended by things like slashes, that we can't easily track here)
plasma_cost = 20
var/duration = 30 SECONDS // 30 seconds base
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
index 0d1bcc99281a..51f23f22a09f 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
@@ -1,8 +1,10 @@
-/datum/action/xeno_action/activable/pounce/lurker/additional_effects_always()
+/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob)
var/mob/living/carbon/xenomorph/xeno = owner
if(!istype(xeno))
return
+ RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing
+
var/found = FALSE
for(var/mob/living/carbon/human/human in get_turf(xeno))
if(human.stat == DEAD)
@@ -12,14 +14,8 @@
if(found)
var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis = get_xeno_action_by_type(xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
- lurker_invis.invisibility_off()
-
-/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob)
- var/mob/living/carbon/xenomorph/xeno = owner
- if(!istype(xeno))
- return
-
- RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing
+ if(lurker_invis)
+ lurker_invis.invisibility_off() // Full cooldown
/datum/action/xeno_action/activable/pounce/lurker/proc/remove_freeze(mob/living/carbon/xenomorph/xeno)
SIGNAL_HANDLER
@@ -29,21 +25,33 @@
UnregisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF)
end_pounce_freeze()
+/datum/action/xeno_action/onclick/lurker_invisibility/can_use_action()
+ if(!..())
+ return FALSE
+ var/mob/living/carbon/xenomorph/xeno = owner
+ return xeno.deselect_timer < world.time // We clicked the same ability in a very short time
+
/datum/action/xeno_action/onclick/lurker_invisibility/use_ability(atom/targeted_atom)
var/mob/living/carbon/xenomorph/xeno = owner
- if (!istype(xeno))
+ if(!istype(xeno))
return
-
- if (!action_cooldown_check())
+ if(!action_cooldown_check())
return
-
- if (!check_and_use_plasma_owner())
+ if(!check_and_use_plasma_owner())
return
- animate(xeno, alpha = alpha_amount, time = 0.1 SECONDS, easing = QUAD_EASING)
+ xeno.deselect_timer = world.time + 5 // Half a second to prevent double clicks
+
+ if(xeno.stealth)
+ invisibility_off(0.9) // Near full refund of remaining time
+ return ..()
+
+ button.icon_state = "template_active"
xeno.update_icons() // callback to make the icon_state indicate invisibility is in lurker/update_icon
+ animate(xeno, alpha = alpha_amount, time = 0.1 SECONDS, easing = QUAD_EASING)
+
xeno.speed_modifier -= speed_buff
xeno.recalculate_speed()
@@ -53,31 +61,44 @@
// if we go off early, this also works fine.
invis_timer_id = addtimer(CALLBACK(src, PROC_REF(invisibility_off)), duration, TIMER_STOPPABLE)
- // Only resets when invisibility ends
- apply_cooldown_override(1000000000)
return ..()
-/datum/action/xeno_action/onclick/lurker_invisibility/proc/invisibility_off()
- if(!owner || owner.alpha == initial(owner.alpha))
+/// Implementation for disabling invisibility.
+/// (refund_multiplier) indicates how much cooldown to refund based on time remaining
+/// 0 indicates full cooldown; 0.5 indicates 50% of remaining time is refunded
+/datum/action/xeno_action/onclick/lurker_invisibility/proc/invisibility_off(refund_multiplier = 0.0)
+ var/mob/living/carbon/xenomorph/xeno = owner
+
+ if(!istype(xeno))
+ return
+ if(owner.alpha == initial(owner.alpha) && !xeno.stealth)
return
- if (invis_timer_id != TIMER_ID_NULL)
+ if(invis_timer_id != TIMER_ID_NULL)
deltimer(invis_timer_id)
invis_timer_id = TIMER_ID_NULL
- var/mob/living/carbon/xenomorph/xeno = owner
- if (istype(xeno))
- animate(xeno, alpha = initial(xeno.alpha), time = 0.1 SECONDS, easing = QUAD_EASING)
- to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our invisibility end!"))
+ animate(xeno, alpha = initial(xeno.alpha), time = 0.1 SECONDS, easing = QUAD_EASING)
+ to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our invisibility end!"))
- xeno.update_icons()
+ button.icon_state = "template"
+ xeno.update_icons()
+
+ xeno.speed_modifier += speed_buff
+ xeno.recalculate_speed()
+
+ var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate
+ if(!istype(behavior))
+ CRASH("lurker_base behavior_delegate missing/invalid for [xeno]!")
- xeno.speed_modifier += speed_buff
- xeno.recalculate_speed()
+ var/recharge_time = behavior.invis_recharge_time
+ if(behavior.invis_start_time > 0) // Sanity
+ refund_multiplier = clamp(refund_multiplier, 0, 1)
+ var/remaining = 1 - (world.time - behavior.invis_start_time) / behavior.invis_duration
+ recharge_time = behavior.invis_recharge_time - remaining * refund_multiplier * behavior.invis_recharge_time
+ apply_cooldown_override(recharge_time)
- var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate
- if (istype(behavior))
- behavior.on_invisibility_off()
+ behavior.on_invisibility_off()
/datum/action/xeno_action/onclick/lurker_invisibility/ability_cooldown_over()
to_chat(owner, SPAN_XENOHIGHDANGER("We are ready to use our invisibility again!"))
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm
index 1f37651b2c8e..38e643e0d25c 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm
@@ -371,10 +371,17 @@
xeno.use_plasma(plasma_cost_jelly)
return
/datum/action/xeno_action/onclick/manage_hive/use_ability(atom/Atom)
- var/mob/living/carbon/xenomorph/queen/queenbanish = owner
+ var/mob/living/carbon/xenomorph/queen/queen_manager = owner
plasma_cost = 0
-
- var/choice = tgui_input_list(queenbanish, "Manage The Hive", "Hive Management", list("Banish (500)", "Re-Admit (100)", "De-evolve (500)", "Reward Jelly (500)", "Exchange larva for evolution (100)",), theme="hive_status")
+ var/list/options = list("Banish (500)", "Re-Admit (100)", "De-evolve (500)", "Reward Jelly (500)", "Exchange larva for evolution (100)",)
+ if(queen_manager.hive.hivenumber == XENO_HIVE_CORRUPTED)
+ var/datum/hive_status/corrupted/hive = queen_manager.hive
+ options += "Add Personal Ally"
+ if(length(hive.personal_allies))
+ options += "Remove Personal Ally"
+ options += "Clear Personal Allies"
+
+ var/choice = tgui_input_list(queen_manager, "Manage The Hive", "Hive Management", options, theme="hive_status")
switch(choice)
if("Banish (500)")
banish()
@@ -383,9 +390,118 @@
if("De-evolve (500)")
de_evolve_other()
if("Reward Jelly (500)")
- give_jelly_reward(queenbanish.hive)
+ give_jelly_reward(queen_manager.hive)
if("Exchange larva for evolution (100)")
give_evo_points()
+ if("Add Personal Ally")
+ add_personal_ally()
+ if("Remove Personal Ally")
+ remove_personal_ally()
+ if("Clear Personal Allies")
+ clear_personal_allies()
+
+/datum/action/xeno_action/onclick/manage_hive/proc/add_personal_ally()
+ var/mob/living/carbon/xenomorph/queen/user_xeno = owner
+ if(user_xeno.hive.hivenumber != XENO_HIVE_CORRUPTED)
+ return
+
+ if(!user_xeno.check_state())
+ return
+
+ var/datum/hive_status/corrupted/hive = user_xeno.hive
+ var/list/target_list = list()
+ if(!user_xeno.client)
+ return
+ for(var/mob/living/carbon/human/possible_target in range(7, user_xeno.client.eye))
+ if(possible_target.stat == DEAD)
+ continue
+ if(possible_target.status_flags & CORRUPTED_ALLY)
+ continue
+ if(possible_target.hivenumber)
+ continue
+ target_list += possible_target
+
+ if(!length(target_list))
+ to_chat(user_xeno, SPAN_WARNING("No talls in view."))
+ return
+ var/mob/living/target_mob = tgui_input_list(usr, "Target", "Set Up a Personal Alliance With...", target_list, theme="hive_status")
+
+ if(!user_xeno.check_state(TRUE))
+ return
+
+ if(!target_mob)
+ return
+
+ if(target_mob.hivenumber)
+ to_chat(user_xeno, SPAN_WARNING("We cannot set up a personal alliance with a hive cultist."))
+ return
+
+ hive.add_personal_ally(target_mob)
+
+/datum/action/xeno_action/onclick/manage_hive/proc/remove_personal_ally()
+ var/mob/living/carbon/xenomorph/queen/user_xeno = owner
+ if(user_xeno.hive.hivenumber != XENO_HIVE_CORRUPTED)
+ return
+
+ if(!user_xeno.check_state())
+ return
+
+ var/datum/hive_status/corrupted/hive = user_xeno.hive
+
+ if(!length(hive.personal_allies))
+ to_chat(user_xeno, SPAN_WARNING("We don't have personal allies."))
+ return
+
+ var/list/mob/living/allies = list()
+ var/list/datum/weakref/dead_refs = list()
+ for(var/datum/weakref/ally_ref as anything in hive.personal_allies)
+ var/mob/living/ally = ally_ref.resolve()
+ if(ally)
+ allies += ally
+ continue
+ dead_refs += ally_ref
+
+ hive.personal_allies -= dead_refs
+
+ if(!length(allies))
+ to_chat(user_xeno, SPAN_WARNING("We don't have personal allies."))
+ return
+
+ var/mob/living/target_mob = tgui_input_list(usr, "Target", "Break the Personal Alliance With...", allies, theme="hive_status")
+
+ if(!target_mob)
+ return
+
+ var/target_mob_ref = WEAKREF(target_mob)
+
+ if(!(target_mob_ref in hive.personal_allies))
+ return
+
+ if(!user_xeno.check_state(TRUE))
+ return
+
+ hive.remove_personal_ally(target_mob_ref)
+
+/datum/action/xeno_action/onclick/manage_hive/proc/clear_personal_allies()
+ var/mob/living/carbon/xenomorph/queen/user_xeno = owner
+ if(user_xeno.hive.hivenumber != XENO_HIVE_CORRUPTED)
+ return
+
+ if(!user_xeno.check_state())
+ return
+
+ var/datum/hive_status/corrupted/hive = user_xeno.hive
+ if(!length(hive.personal_allies))
+ to_chat(user_xeno, SPAN_WARNING("We don't have personal allies."))
+ return
+
+ if(tgui_alert(user_xeno, "Are you sure you want to clear personal allies?", "Clear Personal Allies", list("No", "Yes"), 10 SECONDS) != "Yes")
+ return
+
+ if(!length(hive.personal_allies))
+ return
+
+ hive.clear_personal_allies()
/datum/action/xeno_action/onclick/manage_hive/proc/banish()
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
index b9bde4c78992..5196be26f3d7 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
@@ -67,15 +67,14 @@
name = "Base Lurker Behavior Delegate"
// Config
- var/invis_recharge_time = 150 // 15 seconds to recharge invisibility.
+ var/invis_recharge_time = 20 SECONDS
var/invis_start_time = -1 // Special value for when we're not invisible
- var/invis_duration = 300 // so we can display how long the lurker is invisible to it
+ var/invis_duration = 30 SECONDS // so we can display how long the lurker is invisible to it
var/buffed_slash_damage_ratio = 1.2
var/slash_slow_duration = 35
// State
var/next_slash_buffed = FALSE
- var/can_go_invisible = TRUE
/datum/behavior_delegate/lurker_base/melee_attack_modify_damage(original_damage, mob/living/carbon/target_carbon)
if (!isxeno_human(target_carbon))
@@ -116,55 +115,57 @@
var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
if (lurker_invis_action)
- lurker_invis_action.invisibility_off()
+ lurker_invis_action.invisibility_off() // Full cooldown
/datum/behavior_delegate/lurker_base/proc/decloak_handler(mob/source)
SIGNAL_HANDLER
var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
if(istype(lurker_invis_action))
- lurker_invis_action.invisibility_off()
+ lurker_invis_action.invisibility_off(0.5) // Partial refund of remaining time
-// What to do when we go invisible
+/// Implementation for enabling invisibility.
/datum/behavior_delegate/lurker_base/proc/on_invisibility()
var/datum/action/xeno_action/activable/pounce/lurker/lurker_pounce_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/activable/pounce/lurker)
- if (lurker_pounce_action)
+ if(lurker_pounce_action)
lurker_pounce_action.knockdown = TRUE // pounce knocks down
lurker_pounce_action.freeze_self = TRUE
ADD_TRAIT(bound_xeno, TRAIT_CLOAKED, TRAIT_SOURCE_ABILITY("cloak"))
RegisterSignal(bound_xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL, PROC_REF(decloak_handler))
bound_xeno.stealth = TRUE
- can_go_invisible = FALSE
invis_start_time = world.time
+/// Implementation for disabling invisibility.
/datum/behavior_delegate/lurker_base/proc/on_invisibility_off()
var/datum/action/xeno_action/activable/pounce/lurker/lurker_pounce_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/activable/pounce/lurker)
- if (lurker_pounce_action)
+ if(lurker_pounce_action)
lurker_pounce_action.knockdown = FALSE // pounce no longer knocks down
lurker_pounce_action.freeze_self = FALSE
bound_xeno.stealth = FALSE
REMOVE_TRAIT(bound_xeno, TRAIT_CLOAKED, TRAIT_SOURCE_ABILITY("cloak"))
UnregisterSignal(bound_xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL)
+ invis_start_time = -1
- // SLIGHTLY hacky because we need to maintain lots of other state on the lurker
- // whenever invisibility is on/off CD and when it's active.
- addtimer(CALLBACK(src, PROC_REF(regen_invisibility)), invis_recharge_time)
+/datum/behavior_delegate/lurker_base/append_to_stat()
+ . = list()
- invis_start_time = -1
+ // Invisible
+ if(invis_start_time != -1)
+ var/time_left = (invis_duration-(world.time - invis_start_time)) / 10
+ . += "Invisibility Remaining: [time_left] second\s."
+ return
-/datum/behavior_delegate/lurker_base/proc/regen_invisibility()
- if (can_go_invisible)
+ var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invisibility_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
+ if(!lurker_invisibility_action)
return
- can_go_invisible = TRUE
- if(bound_xeno)
- var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invisibility_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
- if(lurker_invisibility_action)
- lurker_invisibility_action.end_cooldown()
+ // Recharged
+ if(lurker_invisibility_action.cooldown_timer_id == TIMER_ID_NULL)
+ . += "Invisibility Recharge: Ready."
+ return
-/datum/behavior_delegate/lurker_base/append_to_stat()
- . = list()
- var/invis_message = (invis_start_time == -1) ? "N/A" : "[(invis_duration-(world.time - invis_start_time))/10] seconds."
- . += "Invisibility Time Left: [invis_message]"
+ // Recharging
+ var/time_left = timeleft(lurker_invisibility_action.cooldown_timer_id) / 10
+ . += "Invisibility Recharge: [time_left] second\s."
/datum/behavior_delegate/lurker_base/on_collide(atom/movable/movable_atom)
. = ..()
@@ -184,4 +185,4 @@
return
to_chat(bound_xeno, SPAN_XENOHIGHDANGER("We bumped into someone and lost our invisibility!"))
- lurker_invisibility_action.invisibility_off()
+ lurker_invisibility_action.invisibility_off(0.5) // partial refund of remaining time
diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
index 22b061715892..baa736382733 100644
--- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm
+++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
@@ -979,6 +979,7 @@
need_round_end_check = TRUE
var/list/defectors = list()
+ var/list/datum/weakref/personal_allies = list()
/datum/hive_status/corrupted/add_xeno(mob/living/carbon/xenomorph/xeno)
. = ..()
@@ -1253,9 +1254,9 @@
if(living_xeno_queen)
if(allies[faction])
- xeno_message(SPAN_XENOANNOUNCE("Your Queen set up an alliance with [faction]!"), 3, hivenumber)
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen set up an alliance with [faction]!"), 3, hivenumber)
else
- xeno_message(SPAN_XENOANNOUNCE("Your Queen broke the alliance with [faction]!"), 3, hivenumber)
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen broke the alliance with [faction]!"), 3, hivenumber)
for(var/number in GLOB.hive_datum)
var/datum/hive_status/target_hive = GLOB.hive_datum[number]
@@ -1291,14 +1292,14 @@
addtimer(CALLBACK(src, PROC_REF(handle_defectors), faction), 11 SECONDS)
/datum/hive_status/corrupted/proc/give_defection_choice(mob/living/carbon/xenomorph/xeno, faction)
- if(tgui_alert(xeno, "Your Queen has broken the alliance with the [faction]. The device inside your carapace begins to suppress your connection with the Hive. Do you remove it and stay loyal to her?", "Alliance broken!", list("Stay loyal", "Obey the talls"), 10 SECONDS) == "Obey the talls")
+ if(tgui_alert(xeno, "Our Queen has broken the alliance with the [faction]. The device inside our carapace begins to suppress our connection with the Hive. Do we remove it and stay loyal to her?", "Alliance broken!", list("Stay loyal", "Obey the talls"), 10 SECONDS) == "Obey the talls")
if(!xeno.iff_tag)
to_chat(xeno, SPAN_XENOWARNING("It's too late now. The device is gone and our service to the Queen continues."))
return
defectors += xeno
xeno.set_hive_and_update(XENO_HIVE_RENEGADE)
to_chat(xeno, SPAN_XENOANNOUNCE("You lost the connection with your Hive. Now you have no Queen, only your masters."))
- to_chat(xeno, SPAN_NOTICE("Our instincts have changed, we seem compelled to protect [english_list(xeno.iff_tag.faction_groups, "no one")]."))
+ to_chat(xeno, SPAN_NOTICE("Your instincts have changed, you seem compelled to protect [english_list(xeno.iff_tag.faction_groups, "no one")]."))
return
xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("We rip out [xeno.iff_tag]! For the Hive!"))
xeno.adjustBruteLoss(50)
@@ -1313,20 +1314,58 @@
continue
if(!(faction in xeno.iff_tag.faction_groups))
continue
- xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("You rip out [xeno.iff_tag]! For the hive!"))
+ xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("We rip out [xeno.iff_tag]! For the hive!"))
xeno.adjustBruteLoss(50)
xeno.iff_tag.forceMove(get_turf(xeno))
xeno.iff_tag = null
if(!length(defectors))
return
- xeno_message(SPAN_XENOANNOUNCE("You sense that [english_list(defectors)] turned their backs against their sisters and the Queen in favor of their slavemasters!"), 3, hivenumber)
+ xeno_message(SPAN_XENOANNOUNCE("We sense that [english_list(defectors)] turned their backs against their sisters and the Queen in favor of their slavemasters!"), 3, hivenumber)
defectors.Cut()
+/datum/hive_status/corrupted/proc/add_personal_ally(mob/living/ally)
+ personal_allies += WEAKREF(ally)
+ ally.status_flags |= CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen proclaimed [ally] our ally! We must not harm them."), 3, hivenumber)
+
+/datum/hive_status/corrupted/proc/remove_personal_ally(datum/weakref/ally_ref)
+ personal_allies -= ally_ref
+ var/mob/living/ally = ally_ref.resolve()
+ if(ally)
+ ally.status_flags &= ~CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen has declared that [ally] is no longer our ally!"), 3, hivenumber)
+
+/datum/hive_status/corrupted/proc/clear_personal_allies(death = FALSE)
+ for(var/datum/weakref/ally_ref in personal_allies)
+ var/mob/living/ally = ally_ref.resolve()
+ if(!ally)
+ continue
+ ally.status_flags &= ~CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ personal_allies.Cut()
+ if(!death)
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen has broken all personal alliances with the talls! Favoritism is no more."), 3, hivenumber)
+ return
+ xeno_message(SPAN_XENOWARNING("With the death of the Queen, her friends no longer matter to us."), 3, hivenumber)
+
+/datum/hive_status/corrupted/is_ally(mob/living/living_mob)
+ if(living_mob.status_flags & CORRUPTED_ALLY)
+ return TRUE
+ return ..()
+
/datum/hive_status/proc/override_evilution(evil, override)
if(SSxevolution)
SSxevolution.override_power(hivenumber, evil, override)
+/datum/hive_status/corrupted/on_queen_death()
+ ..()
+ if(!length(personal_allies))
+ return
+ clear_personal_allies(TRUE)
+
//Xeno Resin Mark Shit, the very best place for it too :0)
//Defines at the bottom of this list here will show up at the top in the mark menu
/datum/xeno_mark_define
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm
index e6c8061bd0fe..f64bfd6b500f 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/trapper
name = BOILER_TRAPPER
- description = "You trade your ability to bombard, lance, and dump your acid in order to gain some speed and the ability to create acid explosions and restrain talls within them. With your longer-range vision, set up traps that immobilize your opponents and place acid mines which deal damage to talls and barricades and reduce the cooldown of your trap deployment for every tall hit. Finally, hit talls with your Acid Shotgun ability which adds a stack of insight to empower the next trap you place once you reach a maximum of ten insight. A point-blank shot or a shot on a stunned target will instantly apply ten stacks."
- flavor_description = "Hsss, I love the smell of burnin' tallhost flesh in the mornin'."
+ description = "You trade your ability to bombard, lance, and dump your acid in order to gain some speed and the ability to create acid explosions and restrain enemies within them. With your longer-range vision, set up traps that immobilize your opponents and place acid mines which deal damage to enemies and barricades and reduce the cooldown of your trap deployment for every enemy hit. Finally, hit enemies with your Acid Shotgun ability which adds a stack of insight to empower the next trap you place once you reach a maximum of ten insight. A point-blank shot or a shot on a stunned target will instantly apply ten stacks."
+ flavor_description = "The battlefield is my canvas, this one, my painter. Melt them where they stand."
actions_to_remove = list(
/datum/action/xeno_action/activable/xeno_spit/bombard,
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm
index 84877b43571e..55e459cc6dde 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm
@@ -14,8 +14,8 @@
/datum/xeno_strain/charger
name = CRUSHER_CHARGER
- description = "In exchange for your shield, a little bit of your armor and damage, your slowdown resist from autospitters, your influence under frenzy pheromones, your stomp no longer knocking down talls, and your ability to lock your direction, you gain a considerable amount of health, some speed, your stomp does extra damage when stomping over a grounded tall, and your charge is now manually-controlled and momentum-based; the further you go, the more damage and speed you will gain until you achieve maximum momentum, indicated by your roar. In addition, your armor is now directional, being the toughest on the front, weaker on the sides, and weakest from the back. In return, you gain an ability to tumble to pass through talls and avoid enemy fire, and an ability to forcefully move enemies via ramming into them."
- flavor_description = "We're just getting started. Nothing stops this train. Nothing."
+ description = "In exchange for your shield, a little bit of your armor and damage, your slowdown resist from turrets, your influence under frenzy pheromones, your stomp no longer knocking down talls, and your ability to lock your direction, you gain a considerable amount of health, some speed, your stomp does extra damage when stomping over a grounded tall, and your charge is now manually-controlled and momentum-based; the further you go, the more damage and speed you will gain until you achieve maximum momentum, indicated by your roar. In addition, your armor is now directional, being the toughest on the front, weaker on the sides, and weakest from the back. In return, you gain an ability to tumble to pass through enemies and avoid enemy fire, and an ability to forcefully move enemies via ramming into them."
+ flavor_description = "Nothing stops this hive. This one will become both the immovable object and the unstoppable force."
actions_to_remove = list(
/datum/action/xeno_action/activable/pounce/crusher_charge,
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm
index f84566e9c841..cfbf85de299d 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/defender/steel_crest.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/steel_crest
name = DEFENDER_STEELCREST
description = "You trade your tail sweep and a small amount of your slash damage for slightly increased headbutt knockback and damage and the ability to slowly move and headbutt while fortified. Along with this, you gain a unique ability to accumulate damage, and use it to recover a slight amount of health and refresh your tail slam."
- flavor_description = "To handle yourself, use your head. To handle others, use your head."
+ flavor_description = "This one, like my will, is indomitable. It will become my steel crest against all that defy me."
icon_state_prefix = "Steelcrest"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm
index 0fcbb2ecf09a..5ebafc88eaef 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/healer.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/healer
name = DRONE_HEALER
- description = "You lose your choice of resin secretions, a chunk of your slash damage, and you will experience a slighty-increased difficulty in tackling tallhosts in exchange for strong pheromones, the ability to use a bit of your health to plant a maximum of three lesser resin fruits, and the ability to heal your sisters' wounds by secreting a regenerative resin salve by using your vital fluids and a fifth of your plasma. Be wary, this is a dangerous process; overexert yourself and you may exhaust yourself to unconsciousness, or die..."
- flavor_description = "To the very last drop, your blood belongs to The Hive; share it with your sisters to keep them fighting."
+ description = "You lose your choice of resin secretions, a chunk of your slash damage, and you will experience a slighty-increased difficulty in tackling hosts in exchange for strong pheromones, the ability to use a bit of your health to plant a maximum of three lesser resin fruits, and the ability to heal your sisters' wounds by secreting a regenerative resin salve by using your vital fluids and a fifth of your plasma. Be wary, this is a dangerous process; overexert yourself and you may exhaust yourself to unconsciousness, or die..."
+ flavor_description = "Divided we fall, united we win. We live for the hive, we die for the hive."
icon_state_prefix = "Healer"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm
index 7fba30b6f352..c5e1cff29c63 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm
@@ -1,13 +1,13 @@
/datum/xeno_strain/watcher
name = FACEHUGGER_WATCHER
- description = "You lose your ability to hide in exchange to see further and the ability to no longer take damage outside of weeds. This enables you to stalk your host from a distance and wait for the perfect oppertunity to strike."
+ description = "You lose your ability to hide in exchange to see further. This enables you to stalk your host from a distance and wait for the perfect oppertunity to strike."
flavor_description = "No need to hide when you can see the danger."
actions_to_remove = list(
/datum/action/xeno_action/onclick/xenohide,
)
actions_to_add = list(
- /datum/action/xeno_action/onclick/toggle_long_range/runner,
+ /datum/action/xeno_action/onclick/toggle_long_range/facehugger,
)
behavior_delegate_type = /datum/behavior_delegate/facehugger_watcher
@@ -19,3 +19,13 @@
// This has no special effects, it's just here to skip `/datum/behavior_delegate/facehugger_base/on_life()`.
/datum/behavior_delegate/facehugger_watcher
name = "Watcher Facehugger Behavior Delegate"
+
+/datum/behavior_delegate/facehugger_watcher/on_life()
+ // Sap health if we're standing, not on weeds, and not zoomed out
+ if(bound_xeno.body_position != STANDING_UP)
+ return
+ if(bound_xeno.is_zoomed)
+ return
+ if(locate(/obj/effect/alien/weeds) in get_turf(bound_xeno))
+ return
+ bound_xeno.adjustBruteLoss(1)
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm
index cf1cafde9267..538aacc63722 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/hivelord/resin_whisperer.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/resin_whisperer
name = HIVELORD_RESIN_WHISPERER
description = "You lose your corrosive acid, your ability to secrete thick resin, your ability to reinforce resin secretions, sacrifice your ability to plant weed nodes outside of weeds, and you sacrifice a fifth of your plasma reserves to enhance your vision and gain a stronger connection to the resin. You can now remotely place resin secretions including weed nodes up to a distance of twelve paces!"
- flavor_description = "Let the resin guide you. It whispers, so listen closely."
+ flavor_description = "We let the resin guide us. It whispers, so listen closely."
icon_state_prefix = "Resin Whisperer"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm
index f9a5dbedb614..7de4b93aad19 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/dancer.dm
@@ -1,8 +1,8 @@
/datum/xeno_strain/dancer
// My name is Cuban Pete, I'm the King of the Rumba Beat
name = PRAETORIAN_DANCER
- description = "You lose all of your acid-based abilities and a small amount of your armor in exchange for increased movement speed, evasion, and unparalleled agility that gives you an ability to move even more quickly, dodge bullets, and phase through tallhosts. By slashing tallhosts, you temporarily increase your movement speed and you also you apply a tag that changes how your two new tail abilities function. By tagging hosts, you will make Impale hit twice instead of once and make Tail Trip knock hosts down instead of stunning them."
- flavor_description = "Demonstrate to the talls what 'there is beauty in death' truly symbolizes, then dance upon their graves!"
+ description = "You lose all of your acid-based abilities and a small amount of your armor in exchange for increased movement speed, evasion, and unparalleled agility that gives you an ability to move even more quickly, dodge bullets, and phase through enemies and allies alike. By slashing enemies, you temporarily increase your movement speed and you also you apply a tag that changes how your two new tail abilities function. By tagging enemies, you will make Impale hit twice instead of once and make Tail Trip knock enemies down instead of stunning them."
+ flavor_description = "A performance fit for a Queen, this one will become my instrument of death."
icon_state_prefix = "Dancer"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm
index 91ea59661462..b9541a13ca80 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/oppressor.dm
@@ -1,8 +1,8 @@
/datum/xeno_strain/oppressor
// Dread it, run from it, destiny still arrives... or should I say, I do
name = PRAETORIAN_OPPRESSOR
- description = "You abandon all of your acid-based abilities, your dash, some speed, and a bit of your slash damage for some resistance against small explosives, slashes that deal extra damage to prone targets, and a powerful hook ability that pulls up to three talls towards you, slows them, and has varying effects depending on how many talls you pull. You also gain a powerful punch that reduces your other abilities' cooldowns, pierces through armor, and does double damage in addition to rooting slowed targets. You can also knock talls back and slow them with your new Tail Lash and quickly grab a tall, slow it, and pull it towards you with your unique Tail Stab."
- flavor_description = "Dread it. Run from it. The Hive arrives all the same, or, more accurately, you do."
+ description = "You abandon all of your acid-based abilities, your dash, some speed, and a bit of your slash damage for some resistance against small explosives, slashes that deal extra damage to prone targets, and a powerful hook ability that pulls up to three enemies towards you, slows them, and has varying effects depending on how many enemies you pull. You also gain a powerful punch that reduces your other abilities' cooldowns, pierces through armor, and does double damage in addition to rooting slowed targets. You can also knock enemies back and slow them with your new Tail Lash and quickly grab a tall, slow it, and pull it towards you with your unique Tail Stab."
+ flavor_description = "My reach is endless, this one will pull down the heavens."
icon_state_prefix = "Oppressor"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm
index 2a344523e974..310db35ab370 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/vanguard.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/vanguard
name = PRAETORIAN_VANGUARD
- description = "You forfeit all of your acid-based abilities and some health for some extra speed and a rechargable shield that can block one attack. Use your Pierce from up to three paces away to stab through talls, while stabbing through several will completely recharge your shield. Use your charge to plow through enemies and use it again to unleash a powerful AoE slash that reaches up to three paces. You also have a Cleave ability, amplified by your shield, which you can toggle to either immobilize or fling a target away."
- flavor_description = "They are my bulwark against the tallhosts. They are my Vanguard and they shall know no fear."
+ description = "You forfeit all of your acid-based abilities and some health for some extra speed and a rechargable shield that can block one attack. Use your Pierce from up to three paces away to stab through talls, while stabbing through two or more will completely recharge your shield. Use your charge to plow through enemies and use it again to unleash a powerful AoE slash that reaches up to three paces. You also have a Cleave ability, amplified by your shield, which you can toggle to either immobilize or fling a target away."
+ flavor_description = "Fearless you are born, fearless you serve, fearless you die. This one will become my Vanguard"
icon_state_prefix = "Vanguard"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm
index 313778baf038..9dc9404ee498 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/praetorian/warden.dm
@@ -1,8 +1,8 @@
/datum/xeno_strain/warden
// i mean so basically im braum
name = PRAETORIAN_WARDEN
- description = "You trade your acid ball, acid spray, dash, and a small bit of your slash damage and speed to become an effective medic. You gain the ability to emit strong pheromones, an ability that retrieves endangered, knocked-down or sitting allies and pulls them to your location, and you gain an internal hitpoint pool that fills with every slash against your enemies, which can be spent to aid your allies and yourself by healing them or curing their ailments."
- flavor_description = "Only in death does your sisters' service to the Queen end. They will be untouched by plague or disease; no sickness will blight them."
+ description = "You trade your acid ball, acid spray, dash, and a small bit of your slash damage and speed to become an effective medic. You gain the ability to emit strong pheromones, an ability that retrieves endangered, knocked-down or resting allies and pulls them to your location, and you gain an internal hitpoint pool that fills with every slash against your enemies, which can be spent to aid your allies and yourself by healing them or curing their ailments."
+ flavor_description = "This one will deny her sisters' deaths until they earn it. Fight or be forgotten."
icon_state_prefix = "Warden"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
index 365304259ac8..5b8981157bda 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/berserker
name = RAVAGER_BERSERKER
- description = "You lose your empower, charge, and scissor cut, decrease your health, and sacrifice a bit of your influence under frenzy pheromones to increase your movement speed, slightly increase your armor, and gain a new set of abilities that make you a terrifying melee monster. By slashing, you heal yourself and gain a stack of rage that increases your armor, movement speed, attack speed, and your heals per slash, to a maximum of six rage. Use your new Appehend ability to increase your movement speed and apply a slow on the next target you slash and use your Clothesline ability to fling your target to heal yourself, even more-so if you have a rage stack that will be used up. Finally, use your Eviscerate to unleash a devastating windmill attack that heals you for every host you hit after an immobilizing wind-up."
- flavor_description = "They shall be my finest warriors. They will rend and tear, crush and butcher, and maim and rage until every tallhost falls."
+ description = "You lose your empower, charge, and scissor cut, decrease your health, and sacrifice a bit of your influence under frenzy pheromones to increase your movement speed, slightly increase your armor, and gain a new set of abilities that make you a terrifying melee monster. By slashing, you heal yourself and gain a stack of rage that increases your armor, movement speed, attack speed, and your heals per slash, to a maximum of six rage. Use your new Appehend ability to increase your movement speed and apply a slow on the next target you slash and use your Clothesline ability to fling your target to heal yourself, even more-so if you have a rage stack that will be used up. Finally, use your Eviscerate to unleash a devastating windmill attack that heals you for every enemy you hit after an immobilizing wind-up."
+ flavor_description = "Unbridled fury fills this one. You will become an extension of my rage."
icon_state_prefix = "Berserker"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
index 5cb756d8889d..b88df4068cec 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/hedgehog
name = RAVAGER_HEDGEHOG
description = "You lose your empower, charge, scissor cut, and some slash damage in exchange for more explosive resistance. Your resistance scales with your shard count and at 50% grants you immunity to some explosive stuns. You accumulate shards over time and when taking damage. You can use these shards to power three new abilities: Spike Shield which gives you a temporary shield that spits bone shards around you when damaged; Fire Spikes which launches spikes at your target to slow them and deal damage when they move; and Spike Shed which launches all your spikes, grants a temporary speed boost, and disables shard generation for thirty seconds."
- flavor_description = "They will be of iron will and steely muscle. In great armor shall they be clad, and with the mightiest spikes will they be armed."
+ flavor_description = "You will pierce them a million times, show them what it feels like. This one will become my shield."
icon_state_prefix = "Hedgehog"
actions_to_remove = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm
index 7b9bafadeb7b..f98263871707 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm
@@ -1,7 +1,7 @@
/datum/xeno_strain/acider
name = RUNNER_ACIDER
description = "At the cost of a little bit of your speed and all of your current abilities, you gain a considerable amount of health, some armor, and a new organ that fills with volatile acid over time. Your Tail Stab and slashes apply acid to living lifeforms that slowly burns them, and slashes against targets with acid stacks fill your acid glands. You also gain Corrosive Acid equivalent to that of a boiler that you can deploy more quickly than any other caste, at the cost of a chunk of your acid reserves with each use. Finally, after a twenty second windup, you can force your body to explode, covering everything near you with acid. The more acid you have stored, the more devastating the explosion will be, but during those twenty seconds before detonation you are slowed and give off several warning signals which give talls an opportunity to end you before you can detonate. If you successfully explode, you will reincarnate as a larva again!"
- flavor_description = "Burn their walls, maim their faces! Your life, for The Hive!"
+ flavor_description = "This one will be the last thing they hear. A martyr."
icon_state_prefix = "Acider"
actions_to_remove = list(
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index bd7370f4806f..b6f2871e0370 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -132,6 +132,7 @@
if(user.action_busy)
to_chat(xeno, SPAN_WARNING("We are already busy with something."))
return
+ SEND_SIGNAL(xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL)
xeno.visible_message(SPAN_DANGER("[xeno] starts to devour [pulled]!"), \
SPAN_DANGER("We start to devour [pulled]!"), null, 5)
if(HAS_TRAIT(xeno, TRAIT_CLOAKED)) //cloaked don't show the visible message, so we gotta work around
diff --git a/code/modules/objectives/data_retrieval.dm b/code/modules/objectives/data_retrieval.dm
index f66c578f48fb..ee5053f26c4b 100644
--- a/code/modules/objectives/data_retrieval.dm
+++ b/code/modules/objectives/data_retrieval.dm
@@ -117,29 +117,14 @@
/datum/cm_objective/retrieve_data/disk/process()
var/obj/structure/machinery/computer/disk_reader/reader = disk.loc
if(!reader.powered())
- reader.visible_message(SPAN_WARNING("\The [reader] powers down mid-operation as the area looses power."))
- playsound(reader, 'sound/machines/terminal_shutdown.ogg', 25, 1)
- SSobjectives.stop_processing_objective(src)
- disk.forceMove(reader.loc)
- reader.disk = null
+ SEND_SIGNAL(reader, COMSIG_INTEL_DISK_LOST_POWER)
return
..()
/datum/cm_objective/retrieve_data/disk/complete()
state = OBJECTIVE_COMPLETE
- var/obj/structure/machinery/computer/disk_reader/reader = disk.loc
- reader.visible_message("\The [reader] pings softly as the upload finishes and ejects the disk.")
- playsound(reader, 'sound/machines/screen_output1.ogg', 25, 1)
- disk.forceMove(reader.loc)
- disk.name = "[disk.name] (complete)"
- reader.disk = null
- award_points()
-
- // Now enable the objective to store this disk in the lab.
- disk.retrieve_objective.state = OBJECTIVE_ACTIVE
- disk.retrieve_objective.activate()
-
+ SEND_SIGNAL(disk.loc, COMSIG_INTEL_DISK_COMPLETED)
..()
/datum/cm_objective/retrieve_data/disk/get_tgui_data()
@@ -295,34 +280,6 @@
unslashable = TRUE
unacidable = TRUE
-/obj/structure/machinery/computer/disk_reader/attack_hand(mob/living/user)
- if(isxeno(user))
- return
- if(disk)
- to_chat(user, SPAN_NOTICE("[disk] is currently being uploaded to ARES."))
-
-/obj/structure/machinery/computer/disk_reader/attackby(obj/item/W, mob/living/user)
- if(istype(W, /obj/item/disk/objective))
- if(istype(disk))
- to_chat(user, SPAN_WARNING("There is a disk in the drive being uploaded already!"))
- return FALSE
- var/obj/item/disk/objective/newdisk = W
- if(newdisk.objective.state == OBJECTIVE_COMPLETE)
- to_chat(user, SPAN_WARNING("The reader displays a message stating this disk has already been read and refuses to accept it."))
- return FALSE
- if(input(user,"Enter the encryption key","Decrypting [newdisk]","") != newdisk.objective.decryption_password)
- to_chat(user, SPAN_WARNING("The reader asks for the encryption key for this disk, not having the correct key you eject the disk."))
- return FALSE
- if(istype(disk))
- to_chat(user, SPAN_WARNING("There is a disk in the drive being uploaded already!"))
- return FALSE
-
- if(!(newdisk in user.contents))
- return FALSE
-
- newdisk.objective.activate()
-
- user.drop_inv_item_to_loc(W, src)
- disk = W
- to_chat(user, SPAN_NOTICE("You insert \the [W] and enter the decryption key."))
- user.count_niche_stat(STATISTICS_NICHE_DISK)
+/obj/structure/machinery/computer/disk_reader/Initialize()
+ . = ..()
+ AddComponent(/datum/component/disk_reader)
diff --git a/code/modules/objectives/objective_memory_storage.dm b/code/modules/objectives/objective_memory_storage.dm
index 161c78d4d1ba..de2ab30691cc 100644
--- a/code/modules/objectives/objective_memory_storage.dm
+++ b/code/modules/objectives/objective_memory_storage.dm
@@ -218,6 +218,15 @@ GLOBAL_DATUM_INIT(intel_system, /datum/intel_system, new())
GLOB.intel_system.store_single_objective(O)
return 1
+/obj/structure/machinery/computer/intel/disk_reader // ARC computer to save on tile space
+ name = "\improper SIGINT terminal"
+ desc = "An USCM computer capable of uploading data to the intelligence database. It has a disk reader slot built into the bottom, as well."
+ icon_state = "terminal"
+
+/obj/structure/machinery/computer/intel/disk_reader/Initialize()
+ . = ..()
+ AddComponent(/datum/component/disk_reader)
+
// --------------------------------------------
// *** View objectives with the computer ***
// --------------------------------------------
diff --git a/code/modules/organs/limbs.dm b/code/modules/organs/limbs.dm
index 4c3954575245..3cf8dae2a2c6 100644
--- a/code/modules/organs/limbs.dm
+++ b/code/modules/organs/limbs.dm
@@ -1513,7 +1513,7 @@ treat_grafted var tells it to apply to grafted but unsalved wounds, for burn kit
/obj/limb/head/limb_delimb(damage_source)
var/obj/item/clothing/head/helmet/owner_helmet = owner.head
- if(!istype(owner_helmet) || !owner.allow_gun_usage)
+ if(!istype(owner_helmet) || (issynth(owner) && !owner.allow_gun_usage))
droplimb(0, 0, damage_source)
return
diff --git a/code/modules/paperwork/desk_bell.dm b/code/modules/paperwork/desk_bell.dm
index 6e7de1101ae7..ddd4a9dc5d58 100644
--- a/code/modules/paperwork/desk_bell.dm
+++ b/code/modules/paperwork/desk_bell.dm
@@ -93,3 +93,12 @@
flick("desk_bell_activate", src)
times_rang++
return TRUE
+
+/obj/item/desk_bell/ares
+ name = "AI core reception bell"
+
+/obj/item/desk_bell/ares/ring_bell(mob/living/user)
+ if(broken_ringer)
+ return FALSE
+ ares_apollo_talk("Attendence requested at AI Core Reception.")
+ return ..()
diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm
index 5614a4ffe52b..df39248e343a 100644
--- a/code/modules/paperwork/photography.dm
+++ b/code/modules/paperwork/photography.dm
@@ -359,6 +359,12 @@
flags_equip_slot = NO_FLAGS //cannot be equiped
var/obj/structure/machinery/camera/correspondent/linked_cam
+/obj/item/device/camera/broadcasting/Initialize(mapload, ...)
+ . = ..()
+ linked_cam = new(loc, src)
+ linked_cam.status = FALSE
+ RegisterSignal(src, COMSIG_COMPONENT_ADDED, PROC_REF(handle_rename))
+
/obj/item/device/camera/broadcasting/Destroy()
clear_broadcast()
return ..()
@@ -367,13 +373,25 @@
. = ..()
if(!.)
return
- linked_cam = new(loc, src)
+ flags_atom |= (USES_HEARING|USES_SEEING)
+ if(!linked_cam || QDELETED(linked_cam))
+ linked_cam = new(loc, src)
+ else
+ linked_cam.status = TRUE
+ linked_cam.forceMove(loc)
SEND_SIGNAL(src, COMSIG_BROADCAST_GO_LIVE)
to_chat(user, SPAN_NOTICE("[src] begins to buzz softly as you go live."))
/obj/item/device/camera/broadcasting/unwield(mob/user)
. = ..()
- clear_broadcast()
+ flags_atom &= ~(USES_HEARING|USES_SEEING)
+ linked_cam.status = FALSE
+
+/obj/item/device/camera/broadcasting/proc/handle_rename(obj/item/camera, datum/component/label)
+ SIGNAL_HANDLER
+ if(!istype(label, /datum/component/label))
+ return
+ linked_cam.c_tag = get_broadcast_name()
/obj/item/device/camera/broadcasting/proc/clear_broadcast()
if(!QDELETED(linked_cam))
@@ -385,6 +403,12 @@
return src_label_component.label_name
return "Broadcast [serial_number]"
+/obj/item/device/camera/broadcasting/hear_talk(mob/living/sourcemob, message, verb = "says", datum/language/language, italics = FALSE)
+ SEND_SIGNAL(src, COMSIG_BROADCAST_HEAR_TALK, sourcemob, message, verb, language, italics, get_dist(sourcemob, src) < 3)
+
+/obj/item/device/camera/broadcasting/see_emote(mob/living/sourcemob, emote, audible = FALSE)
+ SEND_SIGNAL(src, COMSIG_BROADCAST_SEE_EMOTE, sourcemob, emote, audible, get_dist(sourcemob, src) < 3 && audible)
+
/obj/item/photo/proc/construct(datum/picture/P)
icon = P.fields["icon"]
tiny = P.fields["tiny"]
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 76550fbe079b..8ec24526fc1e 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -1372,3 +1372,7 @@ GLOBAL_LIST_INIT(apc_wire_descriptions, list(
crash_break_probability = 0
#undef APC_UPDATE_ICON_COOLDOWN
+
+// apc that start at zero charge.
+/obj/structure/machinery/power/apc/nocharge
+ start_charge = 0
diff --git a/code/modules/projectiles/gun_attachables.dm b/code/modules/projectiles/gun_attachables.dm
index a2801821349c..5e2fc8459377 100644
--- a/code/modules/projectiles/gun_attachables.dm
+++ b/code/modules/projectiles/gun_attachables.dm
@@ -1761,8 +1761,6 @@ Defined in conflicts.dm of the #defines folder.
//but at the same time you are slow when 2 handed
aim_speed_mod = CONFIG_GET(number/slowdown_med)
- matter = list("wood" = 2000)
-
select_gamemode_skin(type)
/obj/item/attachable/stock/double
diff --git a/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm b/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm
index 356d0e6c3b48..1f0ae4aed571 100644
--- a/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm
+++ b/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm
@@ -189,12 +189,18 @@
smoke.set_up(1, 0, backblast_loc, turn(user.dir, 180))
smoke.start()
playsound(src, 'sound/weapons/gun_rocketlauncher.ogg', 100, TRUE, 10)
- for(var/mob/living/carbon/C in backblast_loc)
- if(C.body_position == STANDING_UP && !HAS_TRAIT(C, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
- C.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
- C.apply_effect(4, STUN) //For good measure
- C.apply_effect(6, STUTTER)
- C.emote("pain")
+ for(var/mob/living/carbon/mob in backblast_loc)
+ if(mob.body_position != STANDING_UP || HAS_TRAIT(mob, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
+ continue
+ to_chat(mob, SPAN_BOLDWARNING("You got hit by the backblast!"))
+ mob.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
+ var/knockdown_amount = 6
+ if(isxeno(mob))
+ var/mob/living/carbon/xenomorph/xeno = mob
+ knockdown_amount = knockdown_amount * (1 - xeno.caste?.xeno_explosion_resistance / 100)
+ mob.KnockDown(knockdown_amount)
+ mob.apply_effect(6, STUTTER)
+ mob.emote("pain")
//-------------------------------------------------------
//M5 RPG'S MEAN FUCKING COUSIN
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index edb565158185..cd69c1940aba 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -358,14 +358,7 @@
SEND_SIGNAL(src, COMSIG_BULLET_TERMINAL)
// Check we can reach the turf at all based on pathed grid
- var/proj_dir = get_dir(current_turf, next_turf)
- if((proj_dir & (proj_dir - 1)) && !current_turf.Adjacent(next_turf))
- ammo.on_hit_turf(current_turf, src)
- current_turf.bullet_act(src)
- return TRUE
-
- // Check for hits that would occur when moving to turf, such as a blocking cade
- if(scan_a_turf(next_turf, proj_dir))
+ if(check_canhit(current_turf, next_turf))
return TRUE
// Actually move
@@ -533,7 +526,8 @@
else
direct_hit = TRUE
- SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L)
+ if(firer)
+ SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L)
// At present, Xenos have no inherent effects or localized damage stemming from limb targeting
// Therefore we exempt the shooter from direct hit accuracy penalties as well,
@@ -600,6 +594,19 @@
if(SEND_SIGNAL(src, COMSIG_BULLET_POST_HANDLE_MOB, L, .) & COMPONENT_BULLET_PASS_THROUGH)
return FALSE
+/obj/projectile/proc/check_canhit(turf/current_turf, turf/next_turf)
+ var/proj_dir = get_dir(current_turf, next_turf)
+ if((proj_dir & (proj_dir - 1)) && !current_turf.Adjacent(next_turf))
+ ammo.on_hit_turf(current_turf, src)
+ current_turf.bullet_act(src)
+ return TRUE
+
+ // Check for hits that would occur when moving to turf, such as a blocking cade
+ if(scan_a_turf(next_turf, proj_dir))
+ return TRUE
+
+ return FALSE
+
//----------------------------------------------------------
// \\
// HITTING THE TARGET \\
diff --git a/code/modules/reagents/chemistry_machinery/chem_master.dm b/code/modules/reagents/chemistry_machinery/chem_master.dm
index dc5206bb2df5..5b145f75940f 100644
--- a/code/modules/reagents/chemistry_machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry_machinery/chem_master.dm
@@ -141,7 +141,15 @@
if(length(label) < 3)
loaded_pill_bottle.maptext_label = label
loaded_pill_bottle.update_icon()
+ else if(href_list["setcolor"])
+ // Checking for state changes
+ if(!loaded_pill_bottle)
+ return
+
+ if(!Adjacent(usr))
+ return
+ loaded_pill_bottle.choose_color()
else if(href_list["close"])
close_browser(user, "chemmaster")
@@ -355,7 +363,8 @@
if(pill_maker)
if(loaded_pill_bottle)
dat += "Eject [loaded_pill_bottle] \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.max_storage_space]\] "
- dat += "Add label to [loaded_pill_bottle] \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.max_storage_space]\]