diff --git a/code/__DEFINES/__game.dm b/code/__DEFINES/__game.dm
index 7cb7440ba8da..ead4c9665c7c 100644
--- a/code/__DEFINES/__game.dm
+++ b/code/__DEFINES/__game.dm
@@ -152,6 +152,7 @@ block( \
//toggles_admin
/// Splits admin tabs in Statpanel
#define SPLIT_ADMIN_TABS (1<<0)
+#define ADMIN_STEALTHMODE (1<<1)
//=================================================
diff --git a/code/__DEFINES/equipment.dm b/code/__DEFINES/equipment.dm
index f0688282572d..375dd0db540d 100644
--- a/code/__DEFINES/equipment.dm
+++ b/code/__DEFINES/equipment.dm
@@ -194,11 +194,11 @@
//===========================================================================================
//Marine armor only, use for flags_marine_armor.
-#define ARMOR_SQUAD_OVERLAY 1
-#define ARMOR_LAMP_OVERLAY 2
-#define ARMOR_LAMP_ON 4
-#define ARMOR_IS_REINFORCED 8
-#define SYNTH_ALLOWED 16
+#define ARMOR_SQUAD_OVERLAY (1<<0)
+#define ARMOR_LAMP_OVERLAY (1<<1)
+#define ARMOR_LAMP_ON (1<<2)
+#define ARMOR_IS_REINFORCED (1<<3)
+#define SYNTH_ALLOWED (1<<4)
//===========================================================================================
//===========================================================================================
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 1f6c700158ad..9cd69e61c8b2 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -195,22 +195,22 @@
//=================================================
//Species flags.
-#define NO_BLOOD (1<<0)
-#define NO_BREATHE (1<<1)
+#define NO_BLOOD (1<<0)
+#define NO_BREATHE (1<<1)
#define NO_CLONE_LOSS (1<<2)
-#define NO_SLIP (1<<3)
+#define NO_SLIP (1<<3)
#define NO_POISON (1<<4)
-#define NO_CHEM_METABOLIZATION (1<<5) //Prevents reagents from acting on_mob_life().
+#define NO_CHEM_METABOLIZATION (1<<5) //Prevents reagents from acting on_mob_life().
#define HAS_SKIN_TONE (1<<6)
-#define HAS_SKIN_COLOR (1<<7)
-#define HAS_LIPS (1<<8)
+#define HAS_SKIN_COLOR (1<<7)
+#define HAS_LIPS (1<<8)
#define HAS_UNDERWEAR (1<<9)
-#define IS_WHITELISTED (1<<10)
-#define IS_SYNTHETIC (1<<11)
-#define NO_NEURO (1<<12)
+#define IS_WHITELISTED (1<<10)
+#define IS_SYNTHETIC (1<<11)
+#define NO_NEURO (1<<12)
#define SPECIAL_BONEBREAK (1<<13) //species do not get their bonebreak chance modified by endurance
-#define NO_SHRAPNEL (1<<14)
-#define HAS_HARDCRIT (1<<15)
+#define NO_SHRAPNEL (1<<14)
+#define HAS_HARDCRIT (1<<15)
//=================================================
diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index b0e97e05e9b2..c561a64ebf58 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "6.7.0"
+#define TGS_DMAPI_VERSION "7.0.1"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -73,12 +73,12 @@
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path.
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
-/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND.
-#define TGS_EVENT_BYOND_INSTALL_START 5
-/// When a BYOND install operation fails. Parameters: Error message
-#define TGS_EVENT_BYOND_INSTALL_FAIL 6
-/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND.
-#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7
+/// Before a engine install operation begins. Parameters: Version string of the installing engine.
+#define TGS_EVENT_ENGINE_INSTALL_START 5
+/// When a engine install operation fails. Parameters: Error message
+#define TGS_EVENT_ENGINE_INSTALL_FAIL 6
+/// When the active engine version changes. Parameters: (Nullable) Version string of the current engine, version string of the new engine.
+#define TGS_EVENT_ENGINE_ACTIVE_VERSION_CHANGE 7
/// When the compiler starts running. Parameters: Game directory path, origin commit SHA.
#define TGS_EVENT_COMPILE_START 8
/// When a compile is cancelled. No parameters.
@@ -108,7 +108,7 @@
// #define TGS_EVENT_DREAM_DAEMON_LAUNCH 22
/// After a single submodule update is performed. Parameters: Updated submodule name.
#define TGS_EVENT_REPO_SUBMODULE_UPDATE 23
-/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, byond version.
+/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, version string of the used engine.
#define TGS_EVENT_PRE_DREAM_MAKER 24
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path.
#define TGS_EVENT_DEPLOYMENT_CLEANUP 25
@@ -122,6 +122,7 @@
/// The watchdog will restart on reboot.
#define TGS_REBOOT_MODE_RESTART 2
+// Note that security levels are currently meaningless in OpenDream
/// DreamDaemon Trusted security level.
#define TGS_SECURITY_TRUSTED 0
/// DreamDaemon Safe security level.
@@ -136,6 +137,11 @@
/// DreamDaemon invisible visibility level.
#define TGS_VISIBILITY_INVISIBLE 2
+/// The Build Your Own Net Dream engine.
+#define TGS_ENGINE_TYPE_BYOND 0
+/// The OpenDream engine.
+#define TGS_ENGINE_TYPE_OPENDREAM 1
+
//REQUIRED HOOKS
/**
@@ -449,6 +455,10 @@
/world/proc/TgsVersion()
return
+/// Returns the running engine type
+/world/proc/TgsEngine()
+ return
+
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsApiVersion()
return
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index 590ee9b97d41..e3a35d0c4744 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -177,6 +177,20 @@
/// The time until you can re-corrupt a comms relay after the last pylon was destroyed
#define XENO_PYLON_DESTRUCTION_DELAY (5 MINUTES)
+/// Evolution boost during hijack
+#define XENO_HIJACK_EVILUTION_BUFF 10
+
+/// For how long the buff lasts
+#define XENO_HIJACK_EVILUTION_TIME (3 MINUTES)
+
+/// If this is marine to xeno ratio during hijack, xenos see marines on tacmap
+#define HIJACK_RATIO_FOR_TACMAP 0.8
+
+/// Xenos need to have their number to marines ratio lower than this to get larvae from pylons
+#define ENDGAME_LARVA_CAP_MULTIPLIER 0.5
+
+/// What percent of their numbers xeno get from pylons
+#define LARVA_ADDITION_MULTIPLIER 0.10
/// The time against away_timer when an AFK xeno larva can be replaced
#define XENO_LEAVE_TIMER_LARVA 80 //80 seconds
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 545b4fb76db2..05fa7c69e50f 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -933,86 +933,103 @@ GLOBAL_DATUM(action_purple_power_up, /image)
if(!GLOB.busy_indicator_clock)
GLOB.busy_indicator_clock = image('icons/mob/mob.dmi', null, "busy_generic", "pixel_y" = 22)
GLOB.busy_indicator_clock.layer = FLY_LAYER
+ GLOB.busy_indicator_clock.plane = ABOVE_HUD_PLANE
return GLOB.busy_indicator_clock
else if(busy_type == BUSY_ICON_MEDICAL)
if(!GLOB.busy_indicator_medical)
GLOB.busy_indicator_medical = image('icons/mob/mob.dmi', null, "busy_medical", "pixel_y" = 0) //This shows directly on top of the mob, no offset!
GLOB.busy_indicator_medical.layer = FLY_LAYER
+ GLOB.busy_indicator_medical.plane = ABOVE_HUD_PLANE
return GLOB.busy_indicator_medical
else if(busy_type == BUSY_ICON_BUILD)
if(!GLOB.busy_indicator_build)
GLOB.busy_indicator_build = image('icons/mob/mob.dmi', null, "busy_build", "pixel_y" = 22)
GLOB.busy_indicator_build.layer = FLY_LAYER
+ GLOB.busy_indicator_build.plane = ABOVE_HUD_PLANE
return GLOB.busy_indicator_build
else if(busy_type == BUSY_ICON_FRIENDLY)
if(!GLOB.busy_indicator_friendly)
GLOB.busy_indicator_friendly = image('icons/mob/mob.dmi', null, "busy_friendly", "pixel_y" = 22)
GLOB.busy_indicator_friendly.layer = FLY_LAYER
+ GLOB.busy_indicator_friendly.plane = ABOVE_HUD_PLANE
return GLOB.busy_indicator_friendly
else if(busy_type == BUSY_ICON_HOSTILE)
if(!GLOB.busy_indicator_hostile)
GLOB.busy_indicator_hostile = image('icons/mob/mob.dmi', null, "busy_hostile", "pixel_y" = 22)
GLOB.busy_indicator_hostile.layer = FLY_LAYER
+ GLOB.busy_indicator_hostile.plane = ABOVE_HUD_PLANE
return GLOB.busy_indicator_hostile
else if(busy_type == EMOTE_ICON_HIGHFIVE)
if(!GLOB.emote_indicator_highfive)
GLOB.emote_indicator_highfive = image('icons/mob/mob.dmi', null, "emote_highfive", "pixel_y" = 22)
GLOB.emote_indicator_highfive.layer = FLY_LAYER
+ GLOB.emote_indicator_highfive.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_highfive
else if(busy_type == EMOTE_ICON_FISTBUMP)
if(!GLOB.emote_indicator_fistbump)
GLOB.emote_indicator_fistbump = image('icons/mob/mob.dmi', null, "emote_fistbump", "pixel_y" = 22)
GLOB.emote_indicator_fistbump.layer = FLY_LAYER
+ GLOB.emote_indicator_fistbump.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_fistbump
else if(busy_type == EMOTE_ICON_ROCK_PAPER_SCISSORS)
if(!GLOB.emote_indicator_rock_paper_scissors)
GLOB.emote_indicator_rock_paper_scissors = image('icons/mob/mob.dmi', null, "emote_rps", "pixel_y" = 22)
GLOB.emote_indicator_rock_paper_scissors.layer = FLY_LAYER
+ GLOB.emote_indicator_rock_paper_scissors.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_rock_paper_scissors
else if(busy_type == EMOTE_ICON_ROCK)
if(!GLOB.emote_indicator_rock)
GLOB.emote_indicator_rock = image('icons/mob/mob.dmi', null, "emote_rock", "pixel_y" = 22)
GLOB.emote_indicator_rock.layer = FLY_LAYER
+ GLOB.emote_indicator_rock.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_rock
else if(busy_type == EMOTE_ICON_PAPER)
if(!GLOB.emote_indicator_paper)
GLOB.emote_indicator_paper = image('icons/mob/mob.dmi', null, "emote_paper", "pixel_y" = 22)
GLOB.emote_indicator_paper.layer = FLY_LAYER
+ GLOB.emote_indicator_paper.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_paper
else if(busy_type == EMOTE_ICON_SCISSORS)
if(!GLOB.emote_indicator_scissors)
GLOB.emote_indicator_scissors = image('icons/mob/mob.dmi', null, "emote_scissors", "pixel_y" = 22)
GLOB.emote_indicator_scissors.layer = FLY_LAYER
+ GLOB.emote_indicator_scissors.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_scissors
else if(busy_type == EMOTE_ICON_HEADBUTT)
if(!GLOB.emote_indicator_headbutt)
GLOB.emote_indicator_headbutt = image('icons/mob/mob.dmi', null, "emote_headbutt", "pixel_y" = 22)
GLOB.emote_indicator_headbutt.layer = FLY_LAYER
+ GLOB.emote_indicator_headbutt.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_headbutt
else if(busy_type == EMOTE_ICON_TAILSWIPE)
if(!GLOB.emote_indicator_tailswipe)
GLOB.emote_indicator_tailswipe = image('icons/mob/mob.dmi', null, "emote_tailswipe", "pixel_y" = 22)
GLOB.emote_indicator_tailswipe.layer = FLY_LAYER
+ GLOB.emote_indicator_tailswipe.plane = ABOVE_HUD_PLANE
return GLOB.emote_indicator_tailswipe
else if(busy_type == ACTION_RED_POWER_UP)
if(!GLOB.action_red_power_up)
GLOB.action_red_power_up = image('icons/effects/effects.dmi', null, "anger", "pixel_x" = 16)
GLOB.action_red_power_up.layer = FLY_LAYER
+ GLOB.action_red_power_up.plane = ABOVE_HUD_PLANE
return GLOB.action_red_power_up
else if(busy_type == ACTION_GREEN_POWER_UP)
if(!GLOB.action_green_power_up)
GLOB.action_green_power_up = image('icons/effects/effects.dmi', null, "vitality", "pixel_x" = 16)
GLOB.action_green_power_up.layer = FLY_LAYER
+ GLOB.action_green_power_up.plane = ABOVE_HUD_PLANE
return GLOB.action_green_power_up
else if(busy_type == ACTION_BLUE_POWER_UP)
if(!GLOB.action_blue_power_up)
GLOB.action_blue_power_up = image('icons/effects/effects.dmi', null, "shock", "pixel_x" = 16)
GLOB.action_blue_power_up.layer = FLY_LAYER
+ GLOB.action_blue_power_up.plane = ABOVE_HUD_PLANE
return GLOB.action_blue_power_up
else if(busy_type == ACTION_PURPLE_POWER_UP)
if(!GLOB.action_purple_power_up)
GLOB.action_purple_power_up = image('icons/effects/effects.dmi', null, "pain", "pixel_x" = 16)
GLOB.action_purple_power_up.layer = FLY_LAYER
+ GLOB.action_purple_power_up.plane = ABOVE_HUD_PLANE
return GLOB.action_purple_power_up
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 6573b9672a43..59d14f2e0fed 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -87,6 +87,7 @@ DEFINE_BITFIELD(flags_ammo_behaviour, list(
"AMMO_IGNORE_RESIST" = AMMO_IGNORE_RESIST,
"AMMO_BALLISTIC" = AMMO_BALLISTIC,
"AMMO_IGNORE_COVER" = AMMO_IGNORE_COVER,
+ "AMMO_ANTIVEHICLE" = AMMO_ANTIVEHICLE,
"AMMO_STOPPED_BY_COVER" = AMMO_STOPPED_BY_COVER,
"AMMO_SPECIAL_EMBED" = AMMO_SPECIAL_EMBED,
"AMMO_STRIKES_SURFACE" = AMMO_STRIKES_SURFACE,
@@ -97,7 +98,6 @@ DEFINE_BITFIELD(flags_ammo_behaviour, list(
"AMMO_FLAME" = AMMO_FLAME,
))
-
DEFINE_BITFIELD(projectile_flags, list(
"PROJECTILE_SHRAPNEL" = PROJECTILE_SHRAPNEL,
"PROJECTILE_BULLSEYE" = PROJECTILE_BULLSEYE,
@@ -120,6 +120,7 @@ DEFINE_BITFIELD(flags_gun_features, list(
"GUN_ANTIQUE" = GUN_ANTIQUE,
"GUN_RECOIL_BUILDUP" = GUN_RECOIL_BUILDUP,
"GUN_SUPPORT_PLATFORM" = GUN_SUPPORT_PLATFORM,
+ "GUN_NO_DESCRIPTION" = GUN_NO_DESCRIPTION,
))
DEFINE_BITFIELD(flags_magazine, list(
@@ -150,6 +151,7 @@ DEFINE_BITFIELD(flags_atom, list(
"INITIALIZED" = INITIALIZED,
"ATOM_DECORATED" = ATOM_DECORATED,
"USES_HEARING" = USES_HEARING,
+ "HTML_USE_INITAL_ICON" = HTML_USE_INITAL_ICON,
))
DEFINE_BITFIELD(flags_item, list(
@@ -181,7 +183,7 @@ DEFINE_BITFIELD(flags_inv_hide, list(
"HIDETOPHAIR" = HIDETOPHAIR,
"HIDEALLHAIR" = HIDEALLHAIR,
"HIDETAIL" = HIDETAIL,
- "HIDEFACE" = HIDEFACE
+ "HIDEFACE" = HIDEFACE,
))
DEFINE_BITFIELD(flags_inventory, list(
@@ -420,6 +422,7 @@ DEFINE_BITFIELD(toggleable_flags, list(
"MODE_NO_COMBAT_CAS" = MODE_NO_COMBAT_CAS,
"MODE_LZ_PROTECTION" = MODE_LZ_PROTECTION,
"MODE_SHIPSIDE_SD" = MODE_SHIPSIDE_SD,
+ "MODE_HARDCORE_PERMA" = MODE_HARDCORE_PERMA,
"MODE_DISPOSABLE_MOBS" = MODE_DISPOSABLE_MOBS,
"MODE_BYPASS_JOE" = MODE_BYPASS_JOE,
))
@@ -453,7 +456,9 @@ DEFINE_BITFIELD(fire_immunity, list(
"FIRE_IMMUNITY_NO_DAMAGE" = FIRE_IMMUNITY_NO_DAMAGE,
"FIRE_IMMUNITY_NO_IGNITE" = FIRE_IMMUNITY_NO_IGNITE,
"FIRE_IMMUNITY_XENO_FRENZY" = FIRE_IMMUNITY_XENO_FRENZY,
+ "FIRE_VULNERABILITY" = FIRE_VULNERABILITY,
))
+
DEFINE_BITFIELD(vend_flags, list(
"VEND_TO_HAND" = VEND_TO_HAND,
"VEND_UNIFORM_RANKS" = VEND_UNIFORM_RANKS,
@@ -475,3 +480,122 @@ DEFINE_BITFIELD(vehicle_flags, list(
"VEHICLE_CLASS_HEAVY" = VEHICLE_CLASS_HEAVY,
"VEHICLE_BYPASS_BLOCKERS" = VEHICLE_BYPASS_BLOCKERS,
))
+
+DEFINE_BITFIELD(flags_pass, list(
+ "PASS_THROUGH" = PASS_THROUGH,
+ "PASS_AROUND" = PASS_AROUND,
+ "PASS_OVER_THROW_ITEM" = PASS_OVER_THROW_ITEM,
+ "PASS_OVER_THROW_MOB" = PASS_OVER_THROW_MOB,
+ "PASS_OVER_FIRE" = PASS_OVER_FIRE,
+ "PASS_OVER_ACID_SPRAY" = PASS_OVER_ACID_SPRAY,
+ "PASS_UNDER" = PASS_UNDER,
+ "PASS_GLASS" = PASS_GLASS,
+ "PASS_MOB_IS_XENO" = PASS_MOB_IS_XENO,
+ "PASS_MOB_IS_HUMAN" = PASS_MOB_IS_HUMAN,
+ "PASS_MOB_IS_OTHER" = PASS_MOB_IS_OTHER,
+ "PASS_MOB_THRU_XENO" = PASS_MOB_THRU_XENO,
+ "PASS_MOB_THRU_HUMAN" = PASS_MOB_THRU_HUMAN,
+ "PASS_MOB_THRU_OTHER" = PASS_MOB_THRU_OTHER,
+ "PASS_TYPE_CRAWLER" = PASS_TYPE_CRAWLER,
+ "PASS_HIGH_OVER_ONLY" = PASS_HIGH_OVER_ONLY,
+ "PASS_BUILDING_ONLY" = PASS_BUILDING_ONLY,
+ "PASS_CRUSHER_CHARGE" = PASS_CRUSHER_CHARGE,
+))
+
+DEFINE_BITFIELD(flags_can_pass_all, list(
+ "PASS_THROUGH" = PASS_THROUGH,
+ "PASS_AROUND" = PASS_AROUND,
+ "PASS_OVER_THROW_ITEM" = PASS_OVER_THROW_ITEM,
+ "PASS_OVER_THROW_MOB" = PASS_OVER_THROW_MOB,
+ "PASS_OVER_FIRE" = PASS_OVER_FIRE,
+ "PASS_OVER_ACID_SPRAY" = PASS_OVER_ACID_SPRAY,
+ "PASS_UNDER" = PASS_UNDER,
+ "PASS_GLASS" = PASS_GLASS,
+ "PASS_MOB_IS_XENO" = PASS_MOB_IS_XENO,
+ "PASS_MOB_IS_HUMAN" = PASS_MOB_IS_HUMAN,
+ "PASS_MOB_IS_OTHER" = PASS_MOB_IS_OTHER,
+ "PASS_MOB_THRU_XENO" = PASS_MOB_THRU_XENO,
+ "PASS_MOB_THRU_HUMAN" = PASS_MOB_THRU_HUMAN,
+ "PASS_MOB_THRU_OTHER" = PASS_MOB_THRU_OTHER,
+ "PASS_TYPE_CRAWLER" = PASS_TYPE_CRAWLER,
+ "PASS_HIGH_OVER_ONLY" = PASS_HIGH_OVER_ONLY,
+ "PASS_BUILDING_ONLY" = PASS_BUILDING_ONLY,
+ "PASS_CRUSHER_CHARGE" = PASS_CRUSHER_CHARGE,
+))
+
+DEFINE_BITFIELD(flags_can_pass_front, list(
+ "PASS_THROUGH" = PASS_THROUGH,
+ "PASS_AROUND" = PASS_AROUND,
+ "PASS_OVER_THROW_ITEM" = PASS_OVER_THROW_ITEM,
+ "PASS_OVER_THROW_MOB" = PASS_OVER_THROW_MOB,
+ "PASS_OVER_FIRE" = PASS_OVER_FIRE,
+ "PASS_OVER_ACID_SPRAY" = PASS_OVER_ACID_SPRAY,
+ "PASS_UNDER" = PASS_UNDER,
+ "PASS_GLASS" = PASS_GLASS,
+ "PASS_MOB_IS_XENO" = PASS_MOB_IS_XENO,
+ "PASS_MOB_IS_HUMAN" = PASS_MOB_IS_HUMAN,
+ "PASS_MOB_IS_OTHER" = PASS_MOB_IS_OTHER,
+ "PASS_MOB_THRU_XENO" = PASS_MOB_THRU_XENO,
+ "PASS_MOB_THRU_HUMAN" = PASS_MOB_THRU_HUMAN,
+ "PASS_MOB_THRU_OTHER" = PASS_MOB_THRU_OTHER,
+ "PASS_TYPE_CRAWLER" = PASS_TYPE_CRAWLER,
+ "PASS_HIGH_OVER_ONLY" = PASS_HIGH_OVER_ONLY,
+ "PASS_BUILDING_ONLY" = PASS_BUILDING_ONLY,
+ "PASS_CRUSHER_CHARGE" = PASS_CRUSHER_CHARGE,
+))
+
+DEFINE_BITFIELD(flags_can_pass_behind, list(
+ "PASS_THROUGH" = PASS_THROUGH,
+ "PASS_AROUND" = PASS_AROUND,
+ "PASS_OVER_THROW_ITEM" = PASS_OVER_THROW_ITEM,
+ "PASS_OVER_THROW_MOB" = PASS_OVER_THROW_MOB,
+ "PASS_OVER_FIRE" = PASS_OVER_FIRE,
+ "PASS_OVER_ACID_SPRAY" = PASS_OVER_ACID_SPRAY,
+ "PASS_UNDER" = PASS_UNDER,
+ "PASS_GLASS" = PASS_GLASS,
+ "PASS_MOB_IS_XENO" = PASS_MOB_IS_XENO,
+ "PASS_MOB_IS_HUMAN" = PASS_MOB_IS_HUMAN,
+ "PASS_MOB_IS_OTHER" = PASS_MOB_IS_OTHER,
+ "PASS_MOB_THRU_XENO" = PASS_MOB_THRU_XENO,
+ "PASS_MOB_THRU_HUMAN" = PASS_MOB_THRU_HUMAN,
+ "PASS_MOB_THRU_OTHER" = PASS_MOB_THRU_OTHER,
+ "PASS_TYPE_CRAWLER" = PASS_TYPE_CRAWLER,
+ "PASS_HIGH_OVER_ONLY" = PASS_HIGH_OVER_ONLY,
+ "PASS_BUILDING_ONLY" = PASS_BUILDING_ONLY,
+ "PASS_CRUSHER_CHARGE" = PASS_CRUSHER_CHARGE,
+))
+
+DEFINE_BITFIELD(sight, list(
+ "BLIND" = BLIND,
+ "SEE_BLACKNESS" = SEE_BLACKNESS,
+ "SEE_INFRA" = SEE_INFRA,
+ "SEE_MOBS" = SEE_MOBS,
+ "SEE_OBJS" = SEE_OBJS,
+ "SEE_PIXELS" = SEE_PIXELS,
+ "SEE_SELF" = SEE_SELF,
+ "SEE_THRU" = SEE_THRU,
+ "SEE_TURFS" = SEE_TURFS,
+))
+
+DEFINE_BITFIELD(vision_flags, list(
+ "BLIND" = BLIND,
+ "SEE_BLACKNESS" = SEE_BLACKNESS,
+ "SEE_INFRA" = SEE_INFRA,
+ "SEE_MOBS" = SEE_MOBS,
+ "SEE_OBJS" = SEE_OBJS,
+ "SEE_PIXELS" = SEE_PIXELS,
+ "SEE_SELF" = SEE_SELF,
+ "SEE_THRU" = SEE_THRU,
+ "SEE_TURFS" = SEE_TURFS,
+))
+
+DEFINE_BITFIELD(vis_flags, list(
+ "VIS_HIDE" = VIS_HIDE,
+ "VIS_INHERIT_DIR" = VIS_INHERIT_DIR,
+ "VIS_INHERIT_ICON" = VIS_INHERIT_ICON,
+ "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE,
+ "VIS_INHERIT_ID" = VIS_INHERIT_ID,
+ "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER,
+ "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE,
+ "VIS_UNDERLAY" = VIS_UNDERLAY,
+))
diff --git a/code/_onclick/hud/map_popups.dm b/code/_onclick/hud/map_popups.dm
index aed6b46a7905..26dc93bbff2b 100644
--- a/code/_onclick/hud/map_popups.dm
+++ b/code/_onclick/hud/map_popups.dm
@@ -118,10 +118,11 @@
* anyway. they're effectively qdel'd.
*/
/client/proc/clear_map(map_name)
- if(!map_name || !(map_name in screen_maps))
+ if(!map_name || !screen_maps[map_name])
return FALSE
for(var/atom/movable/screen/screen_obj in screen_maps[map_name])
screen_maps[map_name] -= screen_obj
+ remove_from_screen(screen_obj)
if(screen_obj.del_on_map_removal)
qdel(screen_obj)
screen_maps -= map_name
diff --git a/code/controllers/subsystem/minimap.dm b/code/controllers/subsystem/minimap.dm
index 66c42e3b5b3a..cbc9d2c421dd 100644
--- a/code/controllers/subsystem/minimap.dm
+++ b/code/controllers/subsystem/minimap.dm
@@ -692,6 +692,11 @@ SUBSYSTEM_DEF(minimaps)
if(faction == FACTION_NEUTRAL && isobserver(user))
faction = allowed_flags == MINIMAP_FLAG_XENO ? XENO_HIVE_NORMAL : FACTION_MARINE
+ if(is_xeno && xeno.hive.see_humans_on_tacmap && targeted_ztrait != ZTRAIT_MARINE_MAIN_SHIP)
+ allowed_flags |= MINIMAP_FLAG_USCM|MINIMAP_FLAG_PMC|MINIMAP_FLAG_UPP|MINIMAP_FLAG_CLF
+ targeted_ztrait = ZTRAIT_MARINE_MAIN_SHIP
+ map_holder = null
+
new_current_map = get_unannounced_tacmap_data_png(faction)
old_map = get_tacmap_data_png(faction)
current_svg = get_tacmap_data_svg(faction)
diff --git a/code/controllers/subsystem/x_evolution.dm b/code/controllers/subsystem/x_evolution.dm
index be787b37de80..2232147d2eb8 100644
--- a/code/controllers/subsystem/x_evolution.dm
+++ b/code/controllers/subsystem/x_evolution.dm
@@ -11,6 +11,7 @@ SUBSYSTEM_DEF(xevolution)
var/time_ratio_modifier = 0.4
var/list/boost_power = list()
+ var/list/overridden_power = list()
var/force_boost_power = FALSE // Debugging only
/datum/controller/subsystem/xevolution/Initialize(start_timeofday)
@@ -18,6 +19,7 @@ SUBSYSTEM_DEF(xevolution)
for(var/hivenumber in GLOB.hive_datum)
HS = GLOB.hive_datum[hivenumber]
boost_power[HS.hivenumber] = 1
+ overridden_power[HS.hivenumber] = FALSE
return SS_INIT_SUCCESS
/datum/controller/subsystem/xevolution/fire(resumed = FALSE)
@@ -27,6 +29,9 @@ SUBSYSTEM_DEF(xevolution)
if(!HS)
continue
+ if(overridden_power[HS.hivenumber])
+ continue
+
if(!HS.dynamic_evolution)
boost_power[HS.hivenumber] = HS.evolution_rate + HS.evolution_bonus
HS.hive_ui.update_burrowed_larva()
@@ -54,6 +59,12 @@ SUBSYSTEM_DEF(xevolution)
/datum/controller/subsystem/xevolution/proc/get_evolution_boost_power(hivenumber)
return boost_power[hivenumber]
+/datum/controller/subsystem/xevolution/proc/override_power(hivenumber, power, override)
+ var/datum/hive_status/hive_status = GLOB.hive_datum[hivenumber]
+ boost_power[hivenumber] = power
+ overridden_power[hivenumber] = override
+ hive_status.hive_ui.update_burrowed_larva()
+
#undef EVOLUTION_INCREMENT_TIME
#undef BOOST_POWER_MIN
#undef BOOST_POWER_MAX
diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm
index 00a5e86b5d60..9bc5b019b5cd 100644
--- a/code/datums/components/overlay_lighting.dm
+++ b/code/datums/components/overlay_lighting.dm
@@ -194,7 +194,7 @@
get_new_turfs()
-///Adds the luminosity and source for the afected movable atoms to keep track of their visibility.
+///Adds the luminosity and source for the affected movable atoms to keep track of their visibility.
/datum/component/overlay_lighting/proc/add_dynamic_lumi()
LAZYSET(current_holder.affected_movable_lights, src, lumcount_range + 1)
current_holder.underlays += visible_mask
@@ -202,7 +202,7 @@
if(directional)
current_holder.underlays += cone
-///Removes the luminosity and source for the afected movable atoms to keep track of their visibility.
+///Removes the luminosity and source for the affected movable atoms to keep track of their visibility.
/datum/component/overlay_lighting/proc/remove_dynamic_lumi()
LAZYREMOVE(current_holder.affected_movable_lights, src)
current_holder.underlays -= visible_mask
@@ -262,6 +262,9 @@
///Used to determine the new valid current_holder from the parent's loc.
/datum/component/overlay_lighting/proc/check_holder()
var/atom/movable/movable_parent = GET_PARENT
+ if(QDELETED(movable_parent))
+ set_holder(null)
+ return
if(isturf(movable_parent.loc))
set_holder(movable_parent)
return
@@ -270,13 +273,21 @@
set_holder(null)
return
if(isturf(inside.loc))
- set_holder(inside)
+ // storage items block light, also don't be moving into a qdeleted item
+ if(QDELETED(inside) || istype(inside, /obj/item/storage))
+ set_holder(null)
+ else
+ set_holder(inside)
return
set_holder(null)
///Called when the current_holder is qdeleted, to remove the light effect.
/datum/component/overlay_lighting/proc/on_holder_qdel(atom/movable/source, force)
+ SIGNAL_HANDLER
+ if(QDELETED(current_holder))
+ set_holder(null)
+ return
UnregisterSignal(current_holder, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
if(directional)
UnregisterSignal(current_holder, COMSIG_ATOM_DIR_CHANGE)
@@ -285,6 +296,7 @@
///Called when current_holder changes loc.
/datum/component/overlay_lighting/proc/on_holder_moved(atom/movable/source, OldLoc, Dir, Forced)
+ SIGNAL_HANDLER
if(!(overlay_lighting_flags & LIGHTING_ON))
return
make_luminosity_update()
@@ -443,8 +455,7 @@
. = lum_power
lum_power = new_lum_power
var/difference = . - lum_power
- for(var/t in affected_turfs)
- var/turf/lit_turf = t
+ for(var/turf/lit_turf as anything in affected_turfs)
lit_turf.dynamic_lumcount -= difference
///Here we append the behavior associated to changing lum_power.
diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm
index 2335a053412f..400c2f15cf9a 100644
--- a/code/datums/components/weed_food.dm
+++ b/code/datums/components/weed_food.dm
@@ -90,6 +90,7 @@
RegisterSignal(parent_mob, COMSIG_MOVABLE_MOVED, 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(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(on_forsaken))
if(parent_turf)
RegisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH, PROC_REF(on_update))
@@ -109,6 +110,7 @@
UnregisterSignal(parent_buckle, COSMIG_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
/datum/component/weed_food/proc/on_move()
@@ -178,6 +180,20 @@
UnregisterSignal(parent_nest, COMSIG_PARENT_QDELETING)
parent_nest = null
+/// SIGNAL_HANDLER for COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING
+/datum/component/weed_food/proc/on_forsaken()
+ SIGNAL_HANDLER
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
+ if(!merged)
+ return
+ if(!is_ground_level(parent_mob.z))
+ return
+
+ var/datum/hive_status/hive = GLOB.hive_datum[XENO_HIVE_FORSAKEN]
+ weed_appearance.color = hive.color
+
/**
* Try to start the process to turn into weeds
* Returns TRUE if started successfully
diff --git a/code/datums/medal_awards.dm b/code/datums/medal_awards.dm
index ba8847c03661..a3041c622bd6 100644
--- a/code/datums/medal_awards.dm
+++ b/code/datums/medal_awards.dm
@@ -11,6 +11,7 @@
GLOBAL_LIST_EMPTY(medal_awards)
GLOBAL_LIST_EMPTY(jelly_awards)
+GLOBAL_LIST_EMPTY(medal_recommendations)
/datum/recipient_awards
var/list/medal_names
@@ -160,7 +161,99 @@ GLOBAL_LIST_INIT(human_medals, list(MARINE_CONDUCT_MEDAL, MARINE_BRONZE_HEART_ME
return TRUE
-/proc/print_medal(mob/living/carbon/human/user, obj/printer)
+/proc/give_medal_award_prefilled(medal_location, mob/giving_mob, chosen_recipient, recipient_rank, recipient_ckey, reason, _medal_type)
+ var/list/recipient_ranks = list()
+ for(var/datum/data/record/record in GLOB.data_core.general)
+ var/recipient_name = record.fields["name"]
+ recipient_ranks[recipient_name] = record.fields["rank"]
+
+ if(!chosen_recipient)
+ return FALSE
+
+ // Pick a medal
+ var/medal_type = _medal_type
+ if(!medal_type)
+ return FALSE
+
+ // Write a citation
+ var/citation = strip_html(reason)
+ if(!citation)
+ return FALSE
+
+ // Get mob information
+ var/posthumous = TRUE
+ var/mob/recipient_mob
+ var/found_other = FALSE
+
+ for(var/mob/mob in GLOB.mob_list)
+ if(mob.real_name == chosen_recipient)
+ // Recipient: Check if they are dead, and get some info
+ // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct
+ if(isliving(mob) && mob.stat != DEAD)
+ posthumous = FALSE
+ recipient_mob = mob
+ if(found_other)
+ break
+ found_other = TRUE
+ if(!recipient_mob)
+ for(var/mob/mob in GLOB.dead_mob_list)
+ if(mob.real_name == chosen_recipient)
+ // Recipient: Check if they are dead?, and get some info
+ // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct
+ if(isliving(mob) && mob.stat != DEAD)
+ posthumous = FALSE
+ recipient_mob = mob
+ break
+
+ // Create the recipient_award
+ if(!GLOB.medal_awards[chosen_recipient])
+ GLOB.medal_awards[chosen_recipient] = new /datum/recipient_awards()
+ var/datum/recipient_awards/recipient_award = GLOB.medal_awards[chosen_recipient]
+ recipient_award.recipient_rank = recipient_rank
+ recipient_award.recipient_ckey = recipient_ckey
+ recipient_award.recipient_mob = recipient_mob
+ recipient_award.giver_mob += giving_mob
+ recipient_award.medal_names += medal_type
+ recipient_award.medal_citations += citation
+ recipient_award.posthumous += posthumous
+ recipient_award.giver_ckey += giving_mob.ckey
+
+ recipient_award.giver_rank += recipient_ranks[giving_mob.real_name] // Currently not used in marine award message
+ recipient_award.giver_name += giving_mob.real_name // Currently not used in marine award message
+
+ // Create an actual medal item
+ if(medal_location)
+ var/obj/item/clothing/accessory/medal/medal
+ switch(medal_type)
+ if(MARINE_CONDUCT_MEDAL)
+ medal = new /obj/item/clothing/accessory/medal/bronze/conduct(medal_location)
+ if(MARINE_BRONZE_HEART_MEDAL)
+ medal = new /obj/item/clothing/accessory/medal/bronze/heart(medal_location)
+ if(MARINE_VALOR_MEDAL)
+ medal = new /obj/item/clothing/accessory/medal/silver/valor(medal_location)
+ if(MARINE_HEROISM_MEDAL)
+ medal = new /obj/item/clothing/accessory/medal/gold/heroism(medal_location)
+ else
+ return FALSE
+ medal.recipient_name = chosen_recipient
+ medal.medal_citation = citation
+ medal.recipient_rank = recipient_rank
+ recipient_award.medal_items += medal
+ else
+ recipient_award.medal_items += null
+
+ // Recipient: Add the medal to the player's stats
+ if(recipient_ckey)
+ var/datum/entity/player_entity/recipient_player = setup_player_entity(recipient_ckey)
+ if(recipient_player)
+ recipient_player.track_medal_earned(medal_type, recipient_mob, recipient_rank, citation, giving_mob)
+
+ // Inform staff of success
+ message_admins("[key_name_admin(giving_mob)] awarded a [medal_type] to [chosen_recipient] for: \'[citation]\'.")
+
+ return TRUE
+
+/proc/open_medal_panel(mob/living/carbon/human/user, obj/printer)
var/obj/item/card/id/card = user.wear_id
if(!card)
to_chat(user, SPAN_WARNING("You must have an authenticated ID Card to award medals."))
@@ -178,8 +271,9 @@ GLOBAL_LIST_INIT(human_medals, list(MARINE_CONDUCT_MEDAL, MARINE_BRONZE_HEART_ME
user.visible_message("ERROR: ID card not registered for [user.real_name] in USCM registry. Potential medal fraud detected.")
return
- if(give_medal_award(get_turf(printer)))
- user.visible_message(SPAN_NOTICE("[printer] prints a medal."))
+ GLOB.ic_medals_panel.user_locs[WEAKREF(user)] = WEAKREF(printer)
+ GLOB.ic_medals_panel.tgui_interact(user)
+
GLOBAL_LIST_INIT(xeno_medals, list(XENO_SLAUGHTER_MEDAL, XENO_RESILIENCE_MEDAL, XENO_SABOTAGE_MEDAL, XENO_PROLIFERATION_MEDAL, XENO_REJUVENATION_MEDAL))
@@ -364,3 +458,200 @@ GLOBAL_LIST_INIT(xeno_medals, list(XENO_SLAUGHTER_MEDAL, XENO_RESILIENCE_MEDAL,
message_admins("[key_name_admin(usr)] deleted [recipient_name]'s [medal_type] for: \'[citation]\'.")
return TRUE
+
+/datum/medal_recommendation
+ var/recipient_rank
+ var/recipient_ckey
+ var/recipient_name
+ var/recommended_by_name
+ var/recommended_by_ckey
+ var/reason
+ var/recommended_by_rank
+
+
+/proc/add_medal_recommendation(mob/recommendation_giver)
+ // Pick a marine
+ var/list/possible_recipients = list()
+ var/list/recipient_ranks = list()
+ for(var/datum/data/record/record in GLOB.data_core.general)
+ var/recipient_name = record.fields["name"]
+ if(recipient_name == recommendation_giver.real_name)
+ continue
+ recipient_ranks[recipient_name] = record.fields["rank"]
+ possible_recipients += recipient_name
+ var/chosen_recipient = tgui_input_list(recommendation_giver, "Who do you want to recommend a medal for?", "Medal Recommendation", possible_recipients)
+ if(!chosen_recipient)
+ return FALSE
+
+ // Write a citation
+ var/reason = strip_html(tgui_input_text(recommendation_giver, "Why does this person deserve a medal?", "Medal Recommendation", null, MAX_PAPER_MESSAGE_LEN, TRUE), MAX_PAPER_MESSAGE_LEN)
+ if(!reason)
+ return FALSE
+
+ // Get mob information
+ var/recipient_rank = recipient_ranks[chosen_recipient]
+ var/recipient_ckey
+ var/mob/recipient_mob
+ var/found_other = FALSE
+
+ for(var/mob/mob in GLOB.mob_list)
+ if(mob.real_name == chosen_recipient)
+ // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct
+ recipient_ckey = mob.persistent_ckey
+ recipient_mob = mob
+ if(found_other)
+ break
+ found_other = TRUE
+ if(!recipient_mob)
+ for(var/mob/mob in GLOB.dead_mob_list)
+ if(mob.real_name == chosen_recipient)
+ // Recipient: Check if they are dead?, and get some info
+ // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct
+ recipient_ckey = mob.persistent_ckey
+ recipient_mob = mob
+ break
+
+ // Create the recipient_award
+ var/datum/medal_recommendation/recommendation = new /datum/medal_recommendation()
+ GLOB.medal_recommendations += recommendation
+
+ recommendation.recipient_rank = recipient_rank
+ recommendation.recipient_ckey = recipient_ckey
+ recommendation.recipient_name = recipient_mob.real_name
+ recommendation.recommended_by_name = recommendation_giver.real_name
+ recommendation.recommended_by_ckey = recommendation_giver.ckey
+ recommendation.recommended_by_rank = recipient_ranks[recommendation_giver.real_name]
+
+
+ recommendation.reason = reason
+
+ return TRUE
+
+
+GLOBAL_DATUM_INIT(ic_medals_panel, /datum/ic_medal_panel, new)
+
+/datum/ic_medal_panel
+ var/name = "Medals Panel"
+ var/list/datum/weakref/user_locs = list()
+
+/datum/ic_medal_panel/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "IcMedalsPanel", "Medals Panel")
+ ui.open()
+ ui.set_autoupdate(FALSE)
+
+/datum/ic_medal_panel/ui_state(mob/user)
+ var/datum/weakref/user_reference = WEAKREF(user)
+ var/datum/weakref/loc_reference = user_locs[user_reference]
+ if(istype(loc_reference?.resolve(), /obj/item))
+ return GLOB.not_incapacitated_and_inventory_state
+ else
+ return GLOB.not_incapacitated_and_adjacent_state
+
+/datum/ic_medal_panel/ui_host(mob/user)
+ . = ..()
+ var/datum/weakref/user_reference = WEAKREF(user)
+ var/datum/weakref/loc_reference = user_locs[user_reference]
+ . = loc_reference?.resolve()
+
+/datum/ic_medal_panel/ui_data(mob/user)
+ var/list/data = list()
+ data["recommendations"] = list()
+
+ for(var/datum/medal_recommendation/recommendation in GLOB.medal_recommendations)
+ var/recommendation_list = list()
+
+ recommendation_list["rank"] = recommendation.recipient_rank
+ recommendation_list["name"] = recommendation.recipient_name
+ recommendation_list["ref"] = REF(recommendation)
+ recommendation_list["recommender_name"] = recommendation.recommended_by_name
+ recommendation_list["reason"] = recommendation.reason
+ recommendation_list["recommender_rank"] = recommendation.recommended_by_rank
+
+ data["recommendations"] += list(recommendation_list)
+ return data
+
+/datum/ic_medal_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ var/mob/living/carbon/human/user = usr
+ var/obj/item/card/id/card = user.wear_id
+ if(!card)
+ to_chat(user, SPAN_WARNING("You must have an authenticated ID Card to award medals."))
+ return
+
+ if(!((card.paygrade in GLOB.co_paygrades) || (card.paygrade in GLOB.highcom_paygrades)))
+ to_chat(user, SPAN_WARNING("Only a Senior Officer can award medals!"))
+ return
+
+ if(!card.registered_ref)
+ user.visible_message("ERROR: ID card not registered in USCM registry. Potential medal fraud detected.")
+ return
+
+ var/real_owner_ref = card.registered_ref
+
+ if(real_owner_ref != WEAKREF(user))
+ user.visible_message("ERROR: ID card not registered for [user.real_name] in USCM registry. Potential medal fraud detected.")
+ return
+
+ var/datum/weakref/user_ref = WEAKREF(user)
+ var/datum/weakref/loc_ref = user_locs[user_ref]
+ var/atom/actual_loc = loc_ref?.resolve()
+ if(!actual_loc)
+ return
+
+ switch(action)
+ if("grant_new_medal")
+ if(give_medal_award(get_turf(actual_loc)))
+ actual_loc.visible_message(SPAN_NOTICE("[actual_loc] prints a medal."))
+ . = TRUE
+
+ if("approve_medal")
+ var/recommendation_ref = params["ref"]
+ var/medal_type = params["medal_type"]
+ if(!(medal_type in GLOB.human_medals))
+ return
+ var/datum/medal_recommendation/recommendation = locate(recommendation_ref) in GLOB.medal_recommendations
+ if(!recommendation)
+ return
+ if(recommendation.recipient_name == user.real_name)
+ to_chat(user, SPAN_WARNING("You cannot give medals to yourself!"))
+ return
+
+ var/choice = tgui_alert(user, "Would you like to change the medal text?", "Medal Citation", list("Yes", "No"))
+ var/medal_citation = recommendation.reason
+ if(choice == "Yes")
+ medal_citation = strip_html(tgui_input_text(user, "What should the medal citation read?", "Medal Citation", null, MAX_PAPER_MESSAGE_LEN, TRUE), MAX_PAPER_MESSAGE_LEN)
+
+ var/confirm_choice = tgui_alert(user, "Are you sure you want to give a medal to [recommendation.recipient_name]?", "Medal Confirmation", list("Yes", "No"))
+ if(confirm_choice != "Yes")
+ return
+
+ if(give_medal_award_prefilled(get_turf(actual_loc), user, recommendation.recipient_name, recommendation.recipient_rank, recommendation.recipient_ckey, medal_citation, medal_type, recommendation.recommended_by_ckey, recommendation.recommended_by_name))
+ GLOB.medal_recommendations -= recommendation
+ qdel(recommendation)
+ user.visible_message(SPAN_NOTICE("[actual_loc] prints a medal."))
+ . = TRUE
+
+ if("deny_medal")
+ var/recommendation_ref = params["ref"]
+ var/datum/medal_recommendation/recommendation = locate(recommendation_ref) in GLOB.medal_recommendations
+ if(!recommendation)
+ return
+ var/confirm = tgui_alert(user, "Are you sure you want to deny this medal recommendation?", "Medal Confirmation", list("Yes", "No"))
+ if(confirm != "Yes")
+ return
+ GLOB.medal_recommendations -= recommendation
+ qdel(recommendation)
+ . = TRUE
+
+/datum/ic_medal_panel/ui_close(mob/user)
+ . = ..()
+ user_locs -= WEAKREF(user)
+
+/datum/ic_medal_panel/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/medal)
+ )
diff --git a/code/game/camera_manager/camera_manager.dm b/code/game/camera_manager/camera_manager.dm
index 93d56aca443c..450c7c8beb64 100644
--- a/code/game/camera_manager/camera_manager.dm
+++ b/code/game/camera_manager/camera_manager.dm
@@ -25,13 +25,16 @@
. = ..()
map_name = "camera_manager_[REF(src)]_map"
cam_screen = new
+ cam_screen.icon = null
cam_screen.name = "screen"
cam_screen.assigned_map = map_name
cam_screen.del_on_map_removal = FALSE
cam_screen.screen_loc = "[map_name]:1,1"
+ cam_screen.appearance_flags |= TILE_BOUND
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
+ cam_background.appearance_flags |= TILE_BOUND
cam_plane_masters = list()
for(var/plane in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
@@ -42,14 +45,17 @@
. = ..()
range_turfs = null
current_area = null
- cam_plane_masters = null
+ QDEL_LIST_ASSOC_VAL(cam_plane_masters)
QDEL_NULL(cam_background)
QDEL_NULL(cam_screen)
if(current)
UnregisterSignal(current, COMSIG_PARENT_QDELETING)
+ current = null
+ last_camera_turf = null
/datum/component/camera_manager/proc/add_plane(atom/movable/screen/plane_master/instance)
instance.assigned_map = map_name
+ instance.appearance_flags |= TILE_BOUND
instance.del_on_map_removal = FALSE
if(instance.blend_mode_override)
instance.blend_mode = instance.blend_mode_override
@@ -61,8 +67,8 @@
var/client/user_client = user.client
if(!user_client)
return
- user_client.register_map_obj(cam_background)
user_client.register_map_obj(cam_screen)
+ user_client.register_map_obj(cam_background)
for(var/plane_id in cam_plane_masters)
user_client.register_map_obj(cam_plane_masters[plane_id])
@@ -71,14 +77,10 @@
var/client/user_client = user.client
if(!user_client)
return
- user_client.clear_map(cam_background)
- user_client.clear_map(cam_screen)
- for(var/plane_id in cam_plane_masters)
- user_client.clear_map(cam_plane_masters[plane_id])
+ user_client.clear_map(map_name)
/datum/component/camera_manager/RegisterWithParent()
. = ..()
- START_PROCESSING(SSdcs, src)
SEND_SIGNAL(parent, COMSIG_CAMERA_MAPNAME_ASSIGNED, map_name)
RegisterSignal(parent, COMSIG_CAMERA_REGISTER_UI, PROC_REF(register))
RegisterSignal(parent, COMSIG_CAMERA_UNREGISTER_UI, PROC_REF(unregister))
@@ -90,8 +92,6 @@
/datum/component/camera_manager/UnregisterFromParent()
. = ..()
- STOP_PROCESSING(SSdcs, src)
-
UnregisterSignal(parent, COMSIG_CAMERA_REGISTER_UI)
UnregisterSignal(parent, COMSIG_CAMERA_UNREGISTER_UI)
UnregisterSignal(parent, COMSIG_CAMERA_SET_NVG)
@@ -134,6 +134,8 @@
target_x = x
target_y = y
target_z = z
+ target_width = w
+ target_height = h
update_area_camera()
/datum/component/camera_manager/proc/enable_nvg(source, power, matrixcol)
@@ -152,10 +154,10 @@
/datum/component/camera_manager/proc/sync_lighting_plane_alpha(lighting_alpha)
var/atom/movable/screen/plane_master/lighting/lighting = cam_plane_masters["[LIGHTING_PLANE]"]
- if (lighting)
+ if(lighting)
lighting.alpha = lighting_alpha
var/atom/movable/screen/plane_master/lighting/exterior_lighting = cam_plane_masters["[EXTERIOR_LIGHTING_PLANE]"]
- if (exterior_lighting)
+ if(exterior_lighting)
exterior_lighting.alpha = min(GLOB.minimum_exterior_lighting_alpha, lighting_alpha)
/**
@@ -215,7 +217,7 @@
var/turf/target = locate(current_area.center_x, current_area.center_y, target_z)
var/list/visible_things = isXRay ? range("[x_size]x[y_size]", target) : view("[x_size]x[y_size]", target)
- src.render_objects(visible_things)
+ render_objects(visible_things)
/datum/component/camera_manager/proc/render_objects(list/visible_things)
var/list/visible_turfs = list()
diff --git a/code/game/cas_manager/datums/cas_fire_envelope.dm b/code/game/cas_manager/datums/cas_fire_envelope.dm
index 04cd688194dd..d9355cd005a9 100644
--- a/code/game/cas_manager/datums/cas_fire_envelope.dm
+++ b/code/game/cas_manager/datums/cas_fire_envelope.dm
@@ -19,7 +19,7 @@
var/datum/cas_signal/recorded_loc = null
var/obj/effect/firemission_guidance/guidance
-
+ var/atom/tracked_object
/datum/cas_fire_envelope/New()
..()
@@ -27,6 +27,7 @@
/datum/cas_fire_envelope/Destroy(force, ...)
linked_console = null
+ untrack_object()
return ..()
/datum/cas_fire_envelope/ui_data(mob/user)
@@ -151,7 +152,9 @@
recorded_loc = marker
return TRUE
-/datum/cas_fire_envelope/proc/change_current_loc(location)
+/datum/cas_fire_envelope/proc/change_current_loc(location, atom/object)
+ if(object)
+ untrack_object()
if(!location && guidance)
for(var/mob/M in guidance.users)
if(istype(M) && M.client)
@@ -162,6 +165,21 @@
guidance = new /obj/effect/firemission_guidance()
guidance.forceMove(location)
guidance.updateCameras(linked_console)
+ if(object)
+ tracked_object = object
+ RegisterSignal(tracked_object, COMSIG_PARENT_QDELETING, PROC_REF(on_tracked_object_del))
+
+/// Call to unregister the on_tracked_object_del behavior
+/datum/cas_fire_envelope/proc/untrack_object()
+ if(tracked_object)
+ UnregisterSignal(tracked_object, COMSIG_PARENT_QDELETING)
+ tracked_object = null
+
+/// Signal handler for when we are viewing a object in cam is qdel'd (but camera actually is actually some other obj)
+/datum/cas_fire_envelope/proc/on_tracked_object_del(atom/target)
+ SIGNAL_HANDLER
+ tracked_object = null
+ change_current_loc()
/datum/cas_fire_envelope/proc/user_is_guided(user)
return guidance && (user in guidance.users)
diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm
index 37131451ca07..be2b75e0e7f6 100644
--- a/code/game/jobs/role_authority.dm
+++ b/code/game/jobs/role_authority.dm
@@ -268,6 +268,7 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou
var/datum/job/PJ = temp_roles_for_mode[JOB_PREDATOR]
if(istype(PJ))
PJ.set_spawn_positions(GLOB.players_preassigned)
+ REDIS_PUBLISH("byond.round", "type" = "predator-round", "map" = SSmapping.configs[GROUND_MAP].map_name)
// Assign the roles, this time for real, respecting limits we have established.
var/list/roles_left = assign_roles(temp_roles_for_mode, unassigned_players)
diff --git a/code/game/machinery/computer/almayer_control.dm b/code/game/machinery/computer/almayer_control.dm
index e9b969e023b8..d38ccd725785 100644
--- a/code/game/machinery/computer/almayer_control.dm
+++ b/code/game/machinery/computer/almayer_control.dm
@@ -110,7 +110,7 @@
return
switch(action)
if("award")
- print_medal(usr, src)
+ open_medal_panel(usr, src)
. = TRUE
// evac stuff start \\
diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm
index 1a00e194b5eb..d7dbfb9717cc 100644
--- a/code/game/machinery/computer/camera_console.dm
+++ b/code/game/machinery/computer/camera_console.dm
@@ -58,9 +58,8 @@
SStgui.close_uis(src)
QDEL_NULL(current)
QDEL_NULL(cam_screen)
- qdel(cam_screen)
QDEL_NULL(cam_background)
- qdel(cam_background)
+ QDEL_NULL_LIST(cam_plane_masters)
last_camera_turf = null
concurrent_users = null
return ..()
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index bcc4c4ac3ec8..77c9bbacc293 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -130,7 +130,7 @@
cooldown_message = world.time
if("award")
- print_medal(usr, src)
+ open_medal_panel(usr, src)
if("evacuation_start")
if(state == STATE_EVACUATION)
diff --git a/code/game/machinery/computer/dropship_weapons.dm b/code/game/machinery/computer/dropship_weapons.dm
index db376c40029c..dce026f4ce33 100644
--- a/code/game/machinery/computer/dropship_weapons.dm
+++ b/code/game/machinery/computer/dropship_weapons.dm
@@ -33,6 +33,8 @@
var/camera_width = 11
var/camera_height = 11
var/camera_map_name
+ ///Tracks equipment with a camera that is deployed and we are viewing
+ var/obj/structure/dropship_equipment/camera_area_equipment = null
var/registered = FALSE
@@ -62,17 +64,20 @@
/obj/structure/machinery/computer/dropship_weapons/attack_hand(mob/user)
if(..())
return
- if(!allowed(user))
+ if(!allowed(user))
+ // TODO: Restore cas simulator
+ to_chat(user, SPAN_WARNING("Weapons modification access denied."))
+ return TRUE
// everyone can access the simulator, requested feature.
- to_chat(user, SPAN_WARNING("Weapons modification access denied, attempting to launch simulation."))
+ /*to_chat(user, SPAN_WARNING("Weapons modification access denied, attempting to launch simulation."))
if(!selected_firemission)
to_chat(user, SPAN_WARNING("Firemission must be selected before attempting to run the simulation"))
- return
+ return TRUE
tgui_interact(user)
- return 1
+ return FALSE*/
user.set_interaction(src)
ui_interact(user)
@@ -100,7 +105,7 @@
/obj/structure/machinery/computer/dropship_weapons/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 0)
var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttle_tag)
- if (!istype(dropship))
+ if(!istype(dropship))
return
var/screen_mode = 0
@@ -129,11 +134,6 @@
if(screen_mode != 3 || !selected_firemission || dropship.mode != SHUTTLE_CALL)
update_location(user, null)
- ui_data(user)
- if(!tacmap.map_holder)
- var/level = SSmapping.levels_by_trait(tacmap.targeted_ztrait)
- tacmap.map_holder = SSminimaps.fetch_tacmap_datum(level[1], tacmap.allowed_flags)
- user.client.register_map_obj(tacmap.map_holder.map)
tgui_interact(user)
/obj/structure/machinery/computer/dropship_weapons/tgui_interact(mob/user, datum/tgui/ui)
@@ -141,10 +141,15 @@
var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttle_tag)
RegisterSignal(dropship, COMSIG_DROPSHIP_ADD_EQUIPMENT, PROC_REF(equipment_update))
RegisterSignal(dropship, COMSIG_DROPSHIP_REMOVE_EQUIPMENT, PROC_REF(equipment_update))
- registered=TRUE
+ registered = TRUE
+
+ if(!tacmap.map_holder)
+ var/level = SSmapping.levels_by_trait(tacmap.targeted_ztrait)
+ tacmap.map_holder = SSminimaps.fetch_tacmap_datum(level[1], tacmap.allowed_flags)
ui = SStgui.try_update_ui(user, src, ui)
- if (!ui)
+ if(!ui)
+ user.client.register_map_obj(tacmap.map_holder.map)
SEND_SIGNAL(src, COMSIG_CAMERA_REGISTER_UI, user)
ui = new(user, src, "DropshipWeaponsConsole", "Weapons Console")
ui.open()
@@ -255,7 +260,7 @@
switch(action)
if("button_push")
playsound(src, get_sfx("terminal_button"), 25, FALSE)
- return TRUE
+ return FALSE
if("select_equipment")
var/base_tag = params["equipment_id"]
@@ -303,12 +308,13 @@
var/mount_point = equipment.ship_base.attach_id
if(mount_point != equipment_tag)
continue
- if (istype(equipment, /obj/structure/dropship_equipment/sentry_holder))
+ if(istype(equipment, /obj/structure/dropship_equipment/sentry_holder))
var/obj/structure/dropship_equipment/sentry_holder/sentry = equipment
var/obj/structure/machinery/defenses/sentry/defense = sentry.deployed_turret
- if (defense.has_camera)
+ if(defense.has_camera)
defense.set_range()
var/datum/shape/rectangle/current_bb = defense.range_bounds
+ camera_area_equipment = sentry
SEND_SIGNAL(src, COMSIG_CAMERA_SET_AREA, current_bb.center_x, current_bb.center_y, defense.loc.z, current_bb.width, current_bb.height)
return TRUE
@@ -329,6 +335,7 @@
if(medevac.linked_stretcher)
SEND_SIGNAL(src, COMSIG_CAMERA_SET_TARGET, medevac.linked_stretcher, 5, 5)
return TRUE
+
if("fulton-target")
var/equipment_tag = params["equipment_id"]
for(var/obj/structure/dropship_equipment/equipment as anything in shuttle.equipments)
@@ -340,6 +347,7 @@
var/target_ref = params["ref"]
fulton.automate_interact(user, target_ref)
return TRUE
+
if("fire-weapon")
var/weapon_tag = params["eqp_tag"]
var/obj/structure/dropship_equipment/weapon/DEW = get_weapon(weapon_tag)
@@ -347,19 +355,23 @@
return FALSE
var/datum/cas_signal/sig = get_cas_signal(camera_target_id)
-
if(!sig)
return FALSE
selected_equipment = DEW
- ui_open_fire(user, shuttle, camera_target_id)
+ if(ui_open_fire(user, shuttle, camera_target_id))
+ if(firemission_envelope)
+ firemission_envelope.untrack_object()
return TRUE
+
if("deploy-equipment")
var/equipment_tag = params["equipment_id"]
for(var/obj/structure/dropship_equipment/equipment as anything in shuttle.equipments)
var/mount_point = equipment.ship_base.attach_id
if(mount_point != equipment_tag)
continue
+ if(camera_area_equipment == equipment)
+ set_camera_target(null)
equipment.equipment_interact(user)
return TRUE
@@ -384,13 +396,8 @@
var/x_offset_value = params["x_offset_value"]
var/y_offset_value = params["y_offset_value"]
- var/datum/cas_iff_group/cas_group = GLOB.cas_groups[faction]
- var/datum/cas_signal/cas_sig
- for(var/X in cas_group.cas_signals)
- var/datum/cas_signal/LT = X
- if(LT.target_id == target_id && LT.valid_signal())
- cas_sig = LT
- break
+ camera_target_id = target_id
+ var/datum/cas_signal/cas_sig = get_cas_signal(camera_target_id, valid_only = TRUE)
// we don't want rapid offset changes to trigger admin warnings
// and block the user from accessing TGUI
// we change the minute_count
@@ -408,12 +415,14 @@
current.y + dy,
current.z)
- firemission_envelope.change_current_loc(new_target)
-
+ camera_area_equipment = null
+ firemission_envelope.change_current_loc(new_target, cas_sig)
return TRUE
+
if("nvg-enable")
SEND_SIGNAL(src, COMSIG_CAMERA_SET_NVG, 5, "#7aff7a")
return TRUE
+
if("nvg-disable")
SEND_SIGNAL(src, COMSIG_CAMERA_CLEAR_NVG)
return TRUE
@@ -447,24 +456,28 @@
/obj/structure/machinery/computer/dropship_weapons/proc/get_weapon(eqp_tag)
var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttle_tag)
- for(var/obj/structure/dropship_equipment/equipment in dropship.equipments)
- if(istype(equipment, /obj/structure/dropship_equipment/weapon))
- //is weapon
- if(selected_equipment == equipment)
- return equipment
+ var/obj/structure/dropship_equipment/equipment = dropship.equipments[eqp_tag]
+ if(istype(equipment, /obj/structure/dropship_equipment/weapon))
+ //is weapon
+ return equipment
return
-/obj/structure/machinery/computer/dropship_weapons/proc/get_cas_signal(target_ref)
+/obj/structure/machinery/computer/dropship_weapons/proc/get_cas_signal(target_ref, valid_only = FALSE)
if(!target_ref)
return
var/datum/cas_iff_group/cas_group = GLOB.cas_groups[faction]
for(var/datum/cas_signal/sig in cas_group.cas_signals)
if(sig.target_id == target_ref)
+ if(valid_only && !sig.valid_signal())
+ continue
return sig
-
/obj/structure/machinery/computer/dropship_weapons/proc/set_camera_target(target_ref)
+ camera_area_equipment = null
+ if(firemission_envelope)
+ firemission_envelope.untrack_object()
+
var/datum/cas_signal/target = get_cas_signal(target_ref)
camera_target_id = target_ref
if(!target)
diff --git a/code/game/machinery/computer/groundside_operations.dm b/code/game/machinery/computer/groundside_operations.dm
index 7b4c2d5df771..b5d1c112a3f8 100644
--- a/code/game/machinery/computer/groundside_operations.dm
+++ b/code/game/machinery/computer/groundside_operations.dm
@@ -235,7 +235,7 @@
log_announcement("[key_name(usr)] has announced the following: [input]")
if("award")
- print_medal(usr, src)
+ open_medal_panel(usr, src)
if("selectlz")
if(SSticker.mode.active_lz)
diff --git a/code/game/objects/effects/decals/posters.dm b/code/game/objects/effects/decals/posters.dm
index 7a8054efce1a..23f7b8c5296a 100644
--- a/code/game/objects/effects/decals/posters.dm
+++ b/code/game/objects/effects/decals/posters.dm
@@ -160,14 +160,14 @@
icon_state = "poster3"
/obj/structure/sign/poster/music/Initialize()
- serial_number = pick(3,5,25,26,29,38,39)
+ serial_number = pick(3,5,25,26,38,39)
.=..()
/obj/structure/sign/poster/pinup
icon_state = "poster12"
/obj/structure/sign/poster/pinup/Initialize()
- serial_number = pick(12,16,17)
+ serial_number = pick(12,16,17,29)
.=..()
/obj/structure/sign/poster/propaganda
diff --git a/code/game/objects/items/devices/cictablet.dm b/code/game/objects/items/devices/cictablet.dm
index 3f87b2bfbea2..4d6db2f7772d 100644
--- a/code/game/objects/items/devices/cictablet.dm
+++ b/code/game/objects/items/devices/cictablet.dm
@@ -123,7 +123,7 @@
if("award")
if(announcement_faction != FACTION_MARINE)
return
- print_medal(usr, src)
+ open_medal_panel(usr, src)
. = TRUE
if("mapview")
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index e5c717e699f0..9581f63679b9 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -405,6 +405,24 @@
var/datum/techtree/T = GET_TREE(TREE_MARINE)
T.enter_mob(usr)
+/obj/item/device/radio/headset/almayer/verb/give_medal_recommendation()
+ set name = "Give Medal Recommendation"
+ set desc = "Send a medal recommendation for approval by the Commanding Officer"
+ set category = "Object.Medals"
+ set src in usr
+
+ var/mob/living/carbon/human/wearer = usr
+ if(!istype(wearer))
+ return
+ var/obj/item/card/id/id_card = wearer.wear_id?.GetID()
+ if(!istype(id_card))
+ return
+ if(!(id_card.rank in list(JOB_SO, JOB_XO, JOB_SQUAD_LEADER)))
+ to_chat(wearer, SPAN_WARNING("Only Staff Officers, Executive Officers and Squad Leaders are permitted to give medal recommendations!"))
+ return
+ if(add_medal_recommendation(usr))
+ to_chat(usr, SPAN_NOTICE("Recommendation successfully submitted."))
+
/obj/item/device/radio/headset/almayer/ce
name = "chief engineer's headset"
desc = "The headset of the guy in charge of spooling engines, managing MTs, and tearing up the floor for scrap metal. Of robust and sturdy construction. Channels are as follows: :n - engineering, :v - marine command, :m - medical, :u - requisitions, :a - alpha squad, :b - bravo squad, :c - charlie squad, :d - delta squad."
diff --git a/code/game/objects/items/tools/misc_tools.dm b/code/game/objects/items/tools/misc_tools.dm
index 44aaab771db8..f868c6d5ab24 100644
--- a/code/game/objects/items/tools/misc_tools.dm
+++ b/code/game/objects/items/tools/misc_tools.dm
@@ -415,6 +415,10 @@
name = "\improper DENIED rubber stamp"
icon_state = "stamp-deny"
+/obj/item/tool/stamp/approved
+ name = "\improper APPROVED rubber stamp"
+ icon_state = "stamp-approve"
+
/obj/item/tool/stamp/clown
name = "clown's rubber stamp"
icon_state = "stamp-clown"
diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm
index 9d007a0c8c80..0c763378ede1 100644
--- a/code/game/objects/structures/noticeboard.dm
+++ b/code/game/objects/structures/noticeboard.dm
@@ -1,80 +1,111 @@
+#define MAX_NOTICES 8
+
/obj/structure/noticeboard
name = "notice board"
desc = "A board for pinning important notices upon."
icon = 'icons/obj/structures/props/stationobjs.dmi'
- icon_state = "nboard00"
+ icon_state = "noticeboard"
density = FALSE
anchored = TRUE
var/notices = 0
-/obj/structure/noticeboard/Initialize()
+/obj/structure/noticeboard/Initialize(mapload)
. = ..()
+ if(!mapload)
+ return
+
for(var/obj/item/I in loc)
- if(notices > 4) break
+ if(notices >= MAX_NOTICES)
+ break
if(istype(I, /obj/item/paper))
I.forceMove(src)
notices++
- icon_state = "nboard0[notices]"
+ update_overlays()
//attaching papers!!
-/obj/structure/noticeboard/attackby(obj/item/O as obj, mob/user as mob)
- if(istype(O, /obj/item/paper))
- if(notices < 5)
- O.add_fingerprint(user)
- add_fingerprint(user)
- user.drop_held_item()
- O.forceMove(src)
+/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params)
+ if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo))
+ if(!allowed(user))
+ to_chat(user, SPAN_WARNING("You are not authorized to add notices!"))
+ return
+ if(notices < MAX_NOTICES)
+ if(!user.drop_inv_item_to_loc(O, src))
+ return
notices++
- icon_state = "nboard0[notices]" //update sprite
- to_chat(user, SPAN_NOTICE("You pin the paper to the noticeboard."))
+ update_overlays()
+ to_chat(user, SPAN_NOTICE("You pin the [O] to the noticeboard."))
else
- to_chat(user, SPAN_NOTICE("You reach to pin your paper to the board but hesitate. You are certain your paper will not be seen among the many others already attached."))
+ to_chat(user, SPAN_WARNING("The notice board is full!"))
+ else if(istype(O, /obj/item/tool/pen))
+ user.set_interaction(src)
+ tgui_interact(user)
+ else
+ return ..()
-/obj/structure/noticeboard/attack_hand(user as mob)
- var/dat = "Noticeboard
"
- for(var/obj/item/paper/P in src)
- dat += "[P.name] Write Remove
"
- user << browse("
Notices[dat]","window=noticeboard")
- onclose(user, "noticeboard")
+/obj/structure/noticeboard/attack_hand(mob/user)
+ . = ..()
+ user.set_interaction(src)
+ tgui_interact(user)
+/obj/structure/noticeboard/ui_state(mob/user)
+ return GLOB.physical_state
-/obj/structure/noticeboard/Topic(href, href_list)
- ..()
- usr.set_interaction(src)
- if(href_list["remove"])
- if((usr.stat || usr.is_mob_restrained())) //For when a player is handcuffed while they have the notice window open
- return
- var/obj/item/P = locate(href_list["remove"])
- if((P && P.loc == src))
- P.forceMove(get_turf(src) )//dump paper on the floor because you're a clumsy fuck
- P.add_fingerprint(usr)
- add_fingerprint(usr)
- notices--
- icon_state = "nboard0[notices]"
+/obj/structure/noticeboard/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NoticeBoard", name)
+ ui.open()
- if(href_list["write"])
- if((usr.stat || usr.is_mob_restrained())) //For when a player is handcuffed while they have the notice window open
- return
- var/obj/item/P = locate(href_list["write"])
+/obj/structure/noticeboard/ui_data(mob/user)
+ var/list/data = list()
+ data["allowed"] = allowed(user)
+ data["items"] = list()
+ for(var/obj/item/content in contents)
+ var/list/content_data = list(
+ name = content.name,
+ ref = REF(content)
+ )
+ data["items"] += list(content_data)
+ return data
+
+/obj/structure/noticeboard/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ var/obj/item/item = locate(params["ref"]) in contents
+ if(!istype(item) || item.loc != src)
+ return
+
+ var/mob/user = usr
+
+ switch(action)
+ if("examine")
+ user.examinate(item)
+ return TRUE
+ if("write")
+ var/obj/item/writing_tool = user.get_held_item()
+ if(!istype(writing_tool, /obj/item/tool/pen))
+ balloon_alert(user, "you need a pen for that!")
+ return
+ item.attackby(writing_tool, user)
+ return TRUE
+ if("remove")
+ if(!allowed(user))
+ return
+ remove_item(item, user)
+ return TRUE
+
+/obj/structure/noticeboard/proc/update_overlays()
+ if(overlays) overlays.Cut()
+ if(notices)
+ overlays += image(icon, "notices_[notices]")
- if((P && P.loc == src)) //ifthe paper's on the board
- if(HAS_TRAIT(usr.r_hand, TRAIT_TOOL_PEN))
- add_fingerprint(usr)
- P.attackby(usr.r_hand, usr) //then do ittttt
- else
- if(HAS_TRAIT(usr.l_hand, TRAIT_TOOL_PEN)) //check other hand for pen
- add_fingerprint(usr)
- P.attackby(usr.l_hand, usr)
- else
- to_chat(usr, SPAN_NOTICE("You'll need something to write with!"))
+/obj/structure/noticeboard/proc/remove_item(obj/item/item, mob/user)
+ item.forceMove(loc)
+ if(user)
+ user.put_in_hands(item)
+ balloon_alert(user, "removed from board")
+ notices--
+ update_overlays()
- if(href_list["read"])
- var/obj/item/paper/P = locate(href_list["read"])
- if((P && P.loc == src))
- if(!( istype(usr, /mob/living/carbon/human) ))
- usr << browse("[P.name][stars(P.info)]", "window=[P.name]")
- onclose(usr, "[P.name]")
- else
- usr << browse("[P.name][P.info]", "window=[P.name]")
- onclose(usr, "[P.name]")
- return
diff --git a/code/game/verbs/who.dm b/code/game/verbs/who.dm
index 9cad56cdabe8..5871fdc7a152 100644
--- a/code/game/verbs/who.dm
+++ b/code/game/verbs/who.dm
@@ -42,6 +42,8 @@
var/list/Lines = list()
if(admin_holder && ((R_ADMIN & admin_holder.rights) || (R_MOD & admin_holder.rights)))
for(var/client/C in GLOB.clients)
+ if(!CLIENT_HAS_RIGHTS(src, R_STEALTH) && (CLIENT_IS_STEALTHED(C)))
+ continue
var/entry = "[C.key]"
if(C.mob) //Juuuust in case
if(istype(C.mob, /mob/new_player))
@@ -139,7 +141,7 @@
else
for(var/client/C in GLOB.clients)
- if(C.admin_holder && C.admin_holder.fakekey)
+ if((C.admin_holder && C.admin_holder.fakekey) || (CLIENT_IS_STEALTHED(C)))
continue
Lines += C.key
@@ -172,6 +174,8 @@
LAZYSET(listings, category, list())
for(var/client/C in GLOB.admins)
+ if(CLIENT_IS_STEALTHED(C) && !CLIENT_HAS_RIGHTS(src, R_STEALTH))
+ continue
if(C.admin_holder?.fakekey && !CLIENT_IS_STAFF(src))
continue
for(var/category in mappings)
@@ -187,7 +191,9 @@
for(var/srank in entry.admin_holder.extra_titles)
dat += " & [srank]"
if(CLIENT_IS_STAFF(src))
- if(entry.admin_holder?.fakekey)
+ if(CLIENT_IS_STEALTHED(entry))
+ dat += " (STEALTHED)"
+ else if(entry.admin_holder?.fakekey)
dat += " (HIDDEN)"
if(istype(entry.mob, /mob/dead/observer))
dat += " - Observing"
diff --git a/code/global.dm b/code/global.dm
index 6847fbd2b7fe..f141dc5d68ac 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -33,6 +33,7 @@
#define CLIENT_HAS_RIGHTS(cli, flags) ((cli?.admin_holder?.rights & flags) == flags)
#define CLIENT_IS_STAFF(cli) (cli?.admin_holder?.rights & (R_MOD|R_ADMIN))
#define CLIENT_IS_MENTOR(cli) CLIENT_HAS_RIGHTS(cli, R_MENTOR)
+#define CLIENT_IS_STEALTHED(cli) (CLIENT_HAS_RIGHTS(cli, R_STEALTH) && cli.prefs?.toggles_admin & ADMIN_STEALTHMODE)
#define AHOLD_IS_MOD(ahold) (ahold && (ahold.rights & R_MOD))
#define AHOLD_IS_ADMIN(ahold) (ahold && (ahold.rights & R_ADMIN))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 85996fca1927..4623df8a5dc5 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -250,6 +250,10 @@ GLOBAL_LIST_INIT(admin_verbs_color, list(
/client/proc/set_ooc_color_self
))
+GLOBAL_LIST_INIT(admin_verbs_stealth, list(
+ /client/proc/toggle_admin_stealth
+))
+
GLOBAL_LIST_INIT(admin_mob_event_verbs_hideable, list(
/client/proc/hide_event_mob_verbs,
/client/proc/cmd_admin_select_mob_rank,
@@ -341,6 +345,8 @@ GLOBAL_LIST_INIT(roundstart_mod_verbs, list(
add_verb(src, GLOB.admin_verbs_sounds)
if(CLIENT_HAS_RIGHTS(src, R_SPAWN))
add_verb(src, GLOB.admin_verbs_spawn)
+ if(CLIENT_HAS_RIGHTS(src, R_STEALTH))
+ add_verb(src, GLOB.admin_verbs_stealth)
if(GLOB.RoleAuthority && (GLOB.RoleAuthority.roles_whitelist[ckey] & WHITELIST_YAUTJA_LEADER))
add_verb(src, GLOB.clan_verbs)
@@ -370,6 +376,7 @@ GLOBAL_LIST_INIT(roundstart_mod_verbs, list(
GLOB.admin_mob_event_verbs_hideable,
GLOB.admin_verbs_hideable,
GLOB.debug_verbs,
+ GLOB.admin_verbs_stealth,
))
/client/proc/jobbans()
@@ -595,6 +602,14 @@ GLOBAL_LIST_INIT(roundstart_mod_verbs, list(
else
to_chat(usr, SPAN_BOLDNOTICE("You will no longer hear an audio cue for ARES and Prayer messages."))
+/client/proc/toggle_admin_stealth()
+ set name = "Toggle Admin Stealth"
+ set category = "Preferences"
+ prefs.toggles_admin ^= ADMIN_STEALTHMODE
+ if(prefs.toggles_admin & ADMIN_STEALTHMODE)
+ to_chat(usr, SPAN_BOLDNOTICE("You enabled admin stealth mode."))
+ else
+ to_chat(usr, SPAN_BOLDNOTICE("You disabled admin stealth mode."))
#undef MAX_WARNS
#undef AUTOBANTIME
diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm
index bead55f994ab..0fef0415bb38 100644
--- a/code/modules/admin/player_panel/player_panel.dm
+++ b/code/modules/admin/player_panel/player_panel.dm
@@ -188,6 +188,8 @@
for(var/mob/M in mobs)
if(!M.ckey)
continue
+ if(!CLIENT_HAS_RIGHTS(usr.client, R_STEALTH) && (M.client && (CLIENT_IS_STEALTHED(M.client))))
+ continue
var/color = i % 2 == 0 ? "#6289b7" : "#48709d"
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index a085cb7634d6..3dfe2d38d81f 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -330,7 +330,6 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list(
admin_holder = GLOB.admin_datums[ckey]
if(admin_holder)
admin_holder.associate(src)
- notify_login()
add_pref_verbs()
//preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum)
@@ -343,6 +342,8 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list(
prefs.last_id = computer_id //these are gonna be used for banning
fps = prefs.fps
+ notify_login()
+
load_xeno_name()
human_name_ban = prefs.human_name_ban
@@ -476,7 +477,7 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list(
SSping.currentrun -= src
log_access("Logout: [key_name(src)]")
- if(CLIENT_IS_STAFF(src))
+ if(CLIENT_IS_STAFF(src) && !CLIENT_IS_STEALTHED(src))
message_admins("Admin logout: [key_name(src)]")
var/list/adm = get_admin_counts(R_MOD)
@@ -493,7 +494,7 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list(
/// Handles login-related logging and associated notifications
/client/proc/notify_login()
log_access("Login: [key_name(src)] from [address ? address : "localhost"]-[computer_id] || BYOND v[byond_version].[byond_build]")
- if(CLIENT_IS_STAFF(src))
+ if(CLIENT_IS_STAFF(src) && !CLIENT_IS_STEALTHED(src))
message_admins("Admin login: [key_name(src)]")
var/list/adm = get_admin_counts(R_MOD)
diff --git a/code/modules/cm_aliens/structures/fruit.dm b/code/modules/cm_aliens/structures/fruit.dm
index a1d3624c7337..408ed5d951cc 100644
--- a/code/modules/cm_aliens/structures/fruit.dm
+++ b/code/modules/cm_aliens/structures/fruit.dm
@@ -403,7 +403,8 @@
if(cant_consume)
user.affected_message(affected_xeno,
SPAN_HELPFUL("You fail to [user == affected_xeno ? "eat" : "feed [affected_xeno]"] [current_fruit]."),
- SPAN_HELPFUL("[user] fails to feed you [current_fruit]."))
+ SPAN_HELPFUL("[user] fails to feed you [current_fruit]."),
+ SPAN_NOTICE("[user] fails to [user == affected_xeno ? "eat" : "feed [affected_xeno]"] [current_fruit]."))
return
user.affected_message(affected_xeno,
SPAN_HELPFUL("You start [user == affected_xeno ? "eating" : "feeding [affected_xeno]"] [current_fruit]."),
@@ -417,7 +418,8 @@
if(cant_consume) //Check again after the timer incase they ate another fruit
user.affected_message(affected_xeno,
SPAN_HELPFUL("You fail to [user == affected_xeno ? "eat" : "feed [affected_xeno]"] [current_fruit]."),
- SPAN_HELPFUL("[user] fails to feed you [current_fruit]."))
+ SPAN_HELPFUL("[user] fails to feed you [current_fruit]."),
+ SPAN_NOTICE("[user] fails to [user == affected_xeno ? "eat" : "feed [affected_xeno]"] [current_fruit]."))
return
user.affected_message(affected_xeno,
diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm
index 13f3e488459e..add9646c56ac 100644
--- a/code/modules/cm_aliens/structures/special/pylon_core.dm
+++ b/code/modules/cm_aliens/structures/special/pylon_core.dm
@@ -159,7 +159,7 @@
xeno_announcement(SPAN_XENOANNOUNCE("We have lost our control of the tall's communication relay at [get_area(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE)
else
xeno_announcement(SPAN_XENOANNOUNCE("Another hive has lost control of the tall's communication relay at [get_area(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE)
-
+ linked_hive.hive_ui.update_pylon_status()
return ..()
/// Checks if all comms towers are connected and then starts end game content on all pylons if they are
@@ -177,11 +177,9 @@
xeno_announcement(SPAN_XENOANNOUNCE("Another hive has harnessed the tall's communication relay at [get_area(src)].[linked_hive.faction_is_ally(checked_hive.name) ? "" : " Stop them!"]"), hivenumber, XENO_GENERAL_ANNOUNCE)
activated = TRUE
+ linked_hive.check_if_hit_larva_from_pylon_limit()
addtimer(CALLBACK(src, PROC_REF(give_larva)), XENO_PYLON_ACTIVATION_COOLDOWN, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_LOOP|TIMER_DELETE_ME)
-#define ENDGAME_LARVA_CAP_MULTIPLIER 0.4
-#define LARVA_ADDITION_MULTIPLIER 0.10
-
/// Looped proc via timer to give larva after time
/obj/effect/alien/resin/special/pylon/endgame/proc/give_larva()
if(!activated)
@@ -190,24 +188,13 @@
if(!linked_hive.hive_location || !linked_hive.living_xeno_queen)
return
- var/list/hive_xenos = linked_hive.totalXenos.Copy()
-
- for(var/mob/living/carbon/xenomorph/xeno in hive_xenos)
- if(!xeno.counts_for_slots)
- hive_xenos -= xeno
-
- var/real_total_xeno_count = length(hive_xenos) + linked_hive.stored_larva
-
- if(real_total_xeno_count > (length(GLOB.alive_human_list) * ENDGAME_LARVA_CAP_MULTIPLIER))
+ if(linked_hive.check_if_hit_larva_from_pylon_limit())
return
- linked_hive.partial_larva += real_total_xeno_count * LARVA_ADDITION_MULTIPLIER
+ linked_hive.partial_larva += (linked_hive.get_real_total_xeno_count() + linked_hive.stored_larva) * LARVA_ADDITION_MULTIPLIER
linked_hive.convert_partial_larva_to_full_larva()
linked_hive.hive_ui.update_burrowed_larva()
-#undef ENDGAME_LARVA_CAP_MULTIPLIER
-#undef LARVA_ADDITION_MULTIPLIER
-
//Hive Core - Generates strong weeds, supports other buildings
/obj/effect/alien/resin/special/pylon/core
name = XENO_STRUCTURE_CORE
diff --git a/code/modules/cm_aliens/structures/tunnel.dm b/code/modules/cm_aliens/structures/tunnel.dm
index 8e2993704f31..ac2fcdb87d29 100644
--- a/code/modules/cm_aliens/structures/tunnel.dm
+++ b/code/modules/cm_aliens/structures/tunnel.dm
@@ -48,6 +48,20 @@
if(resin_trap)
qdel(resin_trap)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
+
+/obj/structure/tunnel/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hive.tunnels -= src
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+ hive = GLOB.hive_datum[XENO_HIVE_FORSAKEN]
+ hive.tunnels += src
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
SSminimaps.add_marker(src, z, get_minimap_flag_for_faction(hivenumber), "xenotunnel")
/obj/structure/tunnel/Destroy()
diff --git a/code/modules/cm_marines/dropship_equipment.dm b/code/modules/cm_marines/dropship_equipment.dm
index 59aa1428e23e..785283541eb8 100644
--- a/code/modules/cm_marines/dropship_equipment.dm
+++ b/code/modules/cm_marines/dropship_equipment.dm
@@ -332,11 +332,11 @@
/obj/structure/dropship_equipment/mg_holder/ui_data(mob/user)
. = list()
- var/is_deployed = deployed_mg.loc == src
+ var/is_deployed = deployed_mg.loc != src
.["name"] = name
.["selection_state"] = list()
.["health"] = health
- .["health_max"] = 100
+ .["health_max"] = initial(health)
.["rounds"] = deployed_mg.rounds
.["max_rounds"] = deployed_mg.rounds_max
.["deployed"] = is_deployed
@@ -486,8 +486,6 @@
point_cost = 0
-#define LIGHTING_MAX_LUMINOSITY_SHIPLIGHTS 12
-
/obj/structure/dropship_equipment/electronics/spotlights
name = "\improper AN/LEN-15 Spotlight"
shorthand = "Spotlight"
@@ -502,7 +500,7 @@
if(spotlights_cooldown > world.time)
to_chat(user, SPAN_WARNING("[src] is busy."))
return //prevents spamming deployment/undeployment
- if(luminosity != brightness)
+ if(!light_on)
set_light(brightness)
icon_state = "spotlights_on"
to_chat(user, SPAN_NOTICE("You turn on [src]."))
@@ -515,13 +513,13 @@
/obj/structure/dropship_equipment/electronics/spotlights/update_equipment()
..()
if(ship_base)
- if(luminosity != brightness)
+ if(!light_on)
icon_state = "spotlights_off"
else
icon_state = "spotlights_on"
else
icon_state = "spotlights"
- if(luminosity)
+ if(light_on)
set_light(0)
/obj/structure/dropship_equipment/electronics/spotlights/on_launch()
@@ -530,7 +528,13 @@
/obj/structure/dropship_equipment/electronics/spotlights/on_arrival()
set_light(brightness)
-#undef LIGHTING_MAX_LUMINOSITY_SHIPLIGHTS
+/obj/structure/dropship_equipment/electronics/spotlights/ui_data(mob/user)
+ . = list()
+ var/is_deployed = light_on
+ .["name"] = name
+ .["health"] = health
+ .["health_max"] = initial(health)
+ .["deployed"] = is_deployed
@@ -887,7 +891,8 @@
if (evaccee_triagecard_color && evaccee_triagecard_color == "none")
evaccee_triagecard_color = null
- .["[evaccee_name] [evaccee_triagecard_color ? "\[" + uppertext(evaccee_triagecard_color) + "\]" : ""] ([AR.name])"] = MS
+ var/key_name = strip_improper("[evaccee_name] [evaccee_triagecard_color ? "\[" + uppertext(evaccee_triagecard_color) + "\]" : ""] ([AR.name])")
+ .[key_name] = MS
/obj/structure/dropship_equipment/medevac_system/proc/can_medevac(mob/user)
if(!linked_shuttle)
@@ -907,7 +912,7 @@
var/list/possible_stretchers = get_targets()
- if(!possible_stretchers.len)
+ if(!length(possible_stretchers))
to_chat(user, SPAN_WARNING("No active medevac stretcher detected."))
return FALSE
return TRUE
diff --git a/code/modules/cm_preds/yaut_weapons.dm b/code/modules/cm_preds/yaut_weapons.dm
index c9bb0fddeaa5..9cb8a8bef3fc 100644
--- a/code/modules/cm_preds/yaut_weapons.dm
+++ b/code/modules/cm_preds/yaut_weapons.dm
@@ -128,6 +128,7 @@
attack_speed = 5
attack_verb = list("sliced", "slashed", "jabbed", "torn", "gored")
force = MELEE_FORCE_TIER_5
+ has_speed_bonus = FALSE
/*#########################################
########### One Handed Weapons ############
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index ad5efc7da3a0..22bd9aa0cf61 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -262,6 +262,9 @@
var/mob/living/carbon/human/human_target = target
client.eye = human_target
+ observe_target_mob = human_target
+ RegisterSignal(observe_target_mob, COMSIG_PARENT_QDELETING, PROC_REF(clean_observe_target))
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(observer_move_react))
if(!human_target.hud_used)
return
@@ -290,19 +293,14 @@
break
- observe_target_mob = human_target
- RegisterSignal(observe_target_mob, COMSIG_PARENT_QDELETING, PROC_REF(clean_observe_target))
RegisterSignal(observe_target_mob, COMSIG_MOB_GHOSTIZE, PROC_REF(observe_target_ghosting))
RegisterSignal(observe_target_mob, COMSIG_MOB_NEW_MIND, PROC_REF(observe_target_new_mind))
RegisterSignal(observe_target_mob, COMSIG_MOB_LOGIN, PROC_REF(observe_target_login))
- RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(observer_move_react))
-
if(human_target.client)
observe_target_client = human_target.client
RegisterSignal(observe_target_client, COMSIG_CLIENT_SCREEN_ADD, PROC_REF(observe_target_screen_add))
RegisterSignal(observe_target_client, COMSIG_CLIENT_SCREEN_REMOVE, PROC_REF(observe_target_screen_remove))
- return
/mob/dead/observer/reset_perspective(atom/A)
if(observe_target_mob)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 5890a44f4168..af0da452d13e 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -75,14 +75,22 @@
// Finding the last guy for anti-delay.
if(SSticker.mode && SSticker.mode.is_in_endgame && SSticker.current_state != GAME_STATE_FINISHED && is_mainship_level(z))
var/mob/last_living_human
+ var/shipside_humans_count = 0
+ var/datum/hive_status/main_hive = GLOB.hive_datum[XENO_HIVE_NORMAL]
+ var/see_humans_on_tacmap = main_hive.see_humans_on_tacmap
for(var/mob/living/carbon/human/cur_human as anything in GLOB.alive_human_list)
if(!is_mainship_level(cur_human.z))
continue
- if(last_living_human)
+ shipside_humans_count++
+ if(last_living_human && see_humans_on_tacmap)
last_living_human = null
break
last_living_human = cur_human
- if(last_living_human && (GLOB.last_qm_callout + 2 MINUTES) < world.time)
+
+ if(!see_humans_on_tacmap && shipside_humans_count < (main_hive.get_real_total_xeno_count() * HIJACK_RATIO_FOR_TACMAP))
+ xeno_announcement("There is only a handful of tallhosts left, they are now visible on our hive mind map.", XENO_HIVE_NORMAL, SPAN_ANNOUNCEMENT_HEADER_BLUE("[QUEEN_MOTHER_ANNOUNCE]"))
+ main_hive.see_humans_on_tacmap = TRUE
+ if(last_living_human && shipside_humans_count <= 1 && (GLOB.last_qm_callout + 2 MINUTES) < world.time)
GLOB.last_qm_callout = world.time
// Tell the xenos where the human is.
xeno_announcement("I sense the last tallhost hiding in [get_area(last_living_human)].", XENO_HIVE_NORMAL, SPAN_ANNOUNCEMENT_HEADER_BLUE("[QUEEN_MOTHER_ANNOUNCE]"))
diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
index 3c8eb51a8155..7cc5850e3701 100644
--- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm
+++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
@@ -68,6 +68,10 @@
var/hardcore = FALSE
/// Set to false if you want to prevent getting burrowed larva from latejoin marines
var/latejoin_burrowed = TRUE
+ /// If hit limit of larva from pylons
+ var/hit_larva_pylon_limit = FALSE
+
+ var/see_humans_on_tacmap = FALSE
var/list/hive_inherant_traits
@@ -933,6 +937,30 @@
return TRUE
+// Get amount of real xenos, don't count lessers/huggers
+/datum/hive_status/proc/get_real_total_xeno_count()
+ var/count = 0
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos)
+ if(xeno.counts_for_slots)
+ count++
+ return count
+
+// Checks if we hit larva limit
+/datum/hive_status/proc/check_if_hit_larva_from_pylon_limit()
+ var/groundside_humans_weighted_count = 0
+ for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list)
+ if(!(isspecieshuman(current_human) || isspeciessynth(current_human)))
+ continue
+ var/datum/job/job = GLOB.RoleAuthority.roles_for_mode[current_human.job]
+ if(!job)
+ continue
+ var/turf/turf = get_turf(current_human)
+ if(is_ground_level(turf?.z))
+ groundside_humans_weighted_count += GLOB.RoleAuthority.calculate_role_weight(job)
+ hit_larva_pylon_limit = (get_real_total_xeno_count() + stored_larva) > (groundside_humans_weighted_count * ENDGAME_LARVA_CAP_MULTIPLIER)
+ hive_ui.update_pylon_status()
+ return hit_larva_pylon_limit
+
///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst
/datum/hive_status/proc/increase_larva_after_burst()
var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst)
@@ -1230,7 +1258,6 @@
if(target_hive.allies[name]) //autobreak alliance on betrayal
target_hive.change_stance(name, FALSE)
-
/datum/hive_status/corrupted/change_stance(faction, should_ally)
. = ..()
if(allies[faction])
@@ -1283,6 +1310,10 @@
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)
defectors.Cut()
+/datum/hive_status/proc/override_evilution(evil, override)
+ if(SSxevolution)
+ SSxevolution.override_power(hivenumber, evil, override)
+
//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/hive_status_ui.dm b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm
index 4e71b9a2202f..17514a31e502 100644
--- a/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm
+++ b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm
@@ -11,6 +11,7 @@
var/hive_location
var/burrowed_larva
var/evilution_level
+ var/pylon_status
var/data_initialized = FALSE
@@ -104,6 +105,7 @@
update_xeno_vitals()
update_xeno_keys(FALSE)
update_xeno_info(FALSE)
+ update_pylon_status(FALSE)
if(send_update)
SStgui.update_uis(src)
@@ -113,8 +115,19 @@
data_initialized = TRUE
update_all_xeno_data(FALSE)
update_burrowed_larva(FALSE)
+ update_pylon_status(FALSE)
SStgui.update_uis(src)
+/datum/hive_status_ui/proc/update_pylon_status(send_update = TRUE)
+ if(assoc_hive.get_structure_count(XENO_STRUCTURE_PYLON) < 1)
+ pylon_status = ""
+ else if(assoc_hive.hit_larva_pylon_limit)
+ pylon_status = "The hive's power has surpassed what the pylons can provide."
+ else
+ pylon_status = "Pylons are strengthening our numbers!"
+ if(send_update)
+ SStgui.update_uis(src)
+
/datum/hive_status_ui/ui_state(mob/user)
return GLOB.hive_state[assoc_hive.internal_faction]
@@ -141,6 +154,7 @@
.["hive_location"] = hive_location
.["burrowed_larva"] = burrowed_larva
.["evilution_level"] = evilution_level
+ .["pylon_status"] = pylon_status
var/mob/living/carbon/xenomorph/queen/Q = user
.["is_in_ovi"] = istype(Q) && Q.ovipositor
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 8220f60e77ed..2adfa1bde4c9 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -361,8 +361,8 @@
iscrayon = 1
- // if paper is not in usr, then it must be near them, or in a clipboard or folder, which must be in or near usr
- if(src.loc != usr && !src.Adjacent(usr) && !((istype(src.loc, /obj/item/clipboard) || istype(src.loc, /obj/item/folder)) && (src.loc.loc == usr || src.loc.Adjacent(usr)) ) )
+ // if paper is not in usr, then it must be near them, or in a clipboard, noticeboard or folder, which must be in or near usr
+ if(src.loc != usr && !src.Adjacent(usr) && !((istype(src.loc, /obj/item/clipboard) || istype(src.loc, /obj/structure/noticeboard) || istype(src.loc, /obj/item/folder)) && (src.loc.loc == usr || src.loc.Adjacent(usr)) ) )
return
t = replacetext(t, "\n", "
")
diff --git a/code/modules/shuttle/computers/dropship_computer.dm b/code/modules/shuttle/computers/dropship_computer.dm
index 86c0c86abfc2..d357a15b36f8 100644
--- a/code/modules/shuttle/computers/dropship_computer.dm
+++ b/code/modules/shuttle/computers/dropship_computer.dm
@@ -275,7 +275,6 @@
return
/obj/structure/machinery/computer/shuttle/dropship/flight/proc/hijack(mob/user, force = FALSE)
-
// select crash location
var/turf/source_turf = get_turf(src)
var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttleId)
@@ -307,7 +306,14 @@
hivenumber = xeno.hivenumber
xeno_message(SPAN_XENOANNOUNCE("The Queen has commanded the metal bird to depart for the metal hive in the sky! Rejoice!"), 3, hivenumber)
xeno_message(SPAN_XENOANNOUNCE("The hive swells with power! You will now steadily gain pooled larva over time."), 2, hivenumber)
- GLOB.hive_datum[hivenumber].abandon_on_hijack()
+ var/datum/hive_status/hive = GLOB.hive_datum[hivenumber]
+ hive.abandon_on_hijack()
+ var/original_evilution = hive.evolution_bonus
+ hive.override_evilution(XENO_HIJACK_EVILUTION_BUFF, TRUE)
+ if(hive.living_xeno_queen)
+ var/datum/action/xeno_action/onclick/grow_ovipositor/ovi_ability = get_xeno_action_by_type(hive.living_xeno_queen, /datum/action/xeno_action/onclick/grow_ovipositor)
+ ovi_ability.reduce_cooldown(ovi_ability.xeno_cooldown)
+ addtimer(CALLBACK(hive, TYPE_PROC_REF(/datum/hive_status, override_evilution), original_evilution, FALSE), XENO_HIJACK_EVILUTION_TIME)
// Notify the yautja too so they stop the hunt
message_all_yautja("The serpent Queen has commanded the landing shuttle to depart.")
diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm
index b9a9f27a28ae..8be96f27404a 100644
--- a/code/modules/tgs/core/core.dm
+++ b/code/modules/tgs/core/core.dm
@@ -42,11 +42,11 @@
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
- TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
+ TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
if(!api_datum)
- TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
+ TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
return
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
@@ -107,6 +107,13 @@
if(api)
return api.ApiVersion()
+/world/TgsEngine()
+#ifdef OPENDREAM
+ return TGS_ENGINE_TYPE_OPENDREAM
+#else
+ return TGS_ENGINE_TYPE_BYOND
+#endif
+
/world/TgsInstanceName()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm
index 83420d130a74..616263098fd3 100644
--- a/code/modules/tgs/v5/__interop_version.dm
+++ b/code/modules/tgs/v5/__interop_version.dm
@@ -1 +1 @@
-"5.7.0"
+"5.8.0"
diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm
index 48969c0c7d56..1c7d67d20cdf 100644
--- a/code/modules/tgs/v5/_defines.dm
+++ b/code/modules/tgs/v5/_defines.dm
@@ -8,7 +8,6 @@
#define DMAPI5_TOPIC_REQUEST_LIMIT 65528
#define DMAPI5_TOPIC_RESPONSE_LIMIT 65529
-#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0
#define DMAPI5_BRIDGE_COMMAND_STARTUP 1
#define DMAPI5_BRIDGE_COMMAND_PRIME 2
#define DMAPI5_BRIDGE_COMMAND_REBOOT 3
@@ -18,6 +17,7 @@
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
+#define DMAPI5_PARAMETER_TOPIC_PORT "topicPort"
#define DMAPI5_CHUNK "chunk"
#define DMAPI5_CHUNK_PAYLOAD "payload"
diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm
index 7226f29bba60..25d49b3e3bdb 100644
--- a/code/modules/tgs/v5/api.dm
+++ b/code/modules/tgs/v5/api.dm
@@ -17,6 +17,8 @@
var/list/chat_channels
var/initialized = FALSE
+ var/initial_bridge_request_received = FALSE
+ var/datum/tgs_version/interop_version
var/chunked_requests = 0
var/list/chunked_topics = list()
@@ -25,7 +27,8 @@
/datum/tgs_api/v5/New()
. = ..()
- TGS_DEBUG_LOG("V5 API created")
+ interop_version = version
+ TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
@@ -38,8 +41,8 @@
access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER]
var/datum/tgs_version/api_version = ApiVersion()
- version = null
- var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
+ version = null // we want this to be the TGS version, not the interop version
+ var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
if(!istype(bridge_response))
TGS_ERROR_LOG("Failed initial bridge request!")
return FALSE
@@ -53,7 +56,8 @@
TGS_INFO_LOG("DMAPI validation, exiting...")
TerminateWorld()
- version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION])
+ initial_bridge_request_received = TRUE
+ version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY]
instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME]
@@ -102,10 +106,17 @@
initialized = TRUE
return TRUE
+/datum/tgs_api/v5/proc/GetTopicPort()
+#if defined(OPENDREAM) && defined(OPENDREAM_TOPIC_PORT_EXISTS)
+ return "[world.opendream_topic_port]"
+#else
+ return null
+#endif
+
/datum/tgs_api/v5/proc/RequireInitialBridgeResponse()
TGS_DEBUG_LOG("RequireInitialBridgeResponse()")
var/logged = FALSE
- while(!version)
+ while(!initial_bridge_request_received)
if(!logged)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
logged = TRUE
diff --git a/code/modules/tgs/v5/bridge.dm b/code/modules/tgs/v5/bridge.dm
index 37f58bcdf632..a0ab35987670 100644
--- a/code/modules/tgs/v5/bridge.dm
+++ b/code/modules/tgs/v5/bridge.dm
@@ -48,7 +48,9 @@
var/json = CreateBridgeData(command, data, TRUE)
var/encoded_json = url_encode(json)
- var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
+ var/api_prefix = interop_version.minor >= 8 ? "api/" : ""
+
+ var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
return url
/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth)
@@ -81,11 +83,16 @@
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
return
- var/response_json = file2text(export_response["CONTENT"])
- if(!response_json)
+ var/content = export_response["CONTENT"]
+ if(!content)
TGS_ERROR_LOG("Failed bridge request, missing content!")
return
+ var/response_json = file2text(content)
+ if(!response_json)
+ TGS_ERROR_LOG("Failed bridge request, failed to load content!")
+ return
+
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm
index 2ef0c70a97fa..05e6c4e1b214 100644
--- a/code/modules/tgs/v5/topic.dm
+++ b/code/modules/tgs/v5/topic.dm
@@ -175,6 +175,7 @@
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
+ reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort()
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm
index fd1ed7e4cf54..d531d4b7b9dd 100644
--- a/code/modules/tgs/v5/undefs.dm
+++ b/code/modules/tgs/v5/undefs.dm
@@ -8,7 +8,6 @@
#undef DMAPI5_TOPIC_REQUEST_LIMIT
#undef DMAPI5_TOPIC_RESPONSE_LIMIT
-#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
#undef DMAPI5_BRIDGE_COMMAND_STARTUP
#undef DMAPI5_BRIDGE_COMMAND_PRIME
#undef DMAPI5_BRIDGE_COMMAND_REBOOT
@@ -18,6 +17,7 @@
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
+#undef DMAPI5_PARAMETER_TOPIC_PORT
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
diff --git a/html/changelogs/AutoChangeLog-pr-5377.yml b/html/changelogs/AutoChangeLog-pr-5377.yml
deleted file mode 100644
index 559f4548a8b3..000000000000
--- a/html/changelogs/AutoChangeLog-pr-5377.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "private-tristan"
-delete-after: True
-changes:
- - bugfix: "Trijent security southern hallway and engineering east tunnel no longer have 2 APCs"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-5404.yml b/html/changelogs/AutoChangeLog-pr-5404.yml
new file mode 100644
index 000000000000..344f97d43185
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5404.yml
@@ -0,0 +1,4 @@
+author: "Tsurupeta"
+delete-after: True
+changes:
+ - bugfix: "Regular expressions in chat highlights work again."
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-01.yml b/html/changelogs/archive/2024-01.yml
index a6f475198d7f..8efded23b919 100644
--- a/html/changelogs/archive/2024-01.yml
+++ b/html/changelogs/archive/2024-01.yml
@@ -80,3 +80,63 @@
than wrapping the minutes back to 0.
- ui: Separated xeno hivemind chat messages into their own toggleable category,
separate from 'Radio'.
+2024-01-06:
+ private-tristan:
+ - bugfix: Trijent security southern hallway and engineering east tunnel no longer
+ have 2 APCs
+2024-01-07:
+ BadAtThisGame302:
+ - qol: Spruced up the CLs office by adding a new plant, changing the stuck posters
+ to be random every round
+ - bugfix: Fixed the ATM Machine in the CLs office having Weyland-Yutani Automatic
+ Teller Machine and not Wey-Yu. Same thing, useless mapedit.
+ - bugfix: Fixes the PROUDLY REMEMBER IO! Posters in the CLs office (Again)
+ - bugfix: Puts the 29th Poster which was a pinup into the pinup posters and not
+ the music ones.
+ Birdtalon:
+ - bugfix: Runtime when consuming xeno fruits.
+ Blundir:
+ - rscadd: added approved stamp
+ - bugfix: fixed being unable to write on papers on noticeboard
+ - ui: changed noticeboard UI from scratch
+ - refactor: refactored noticeboard code
+ - maptweak: added noticeboard to req and approved stamp to QM's office, as well
+ as some pens in req area to interact with noticeboard
+ Drathek:
+ - refactor: Refactored the overlay_lighting component to better handle objects deleting
+ - bugfix: Fix putting lights in bags somereason keeping the light on
+ - ui: Polished various aspects of the new dropship weapons UI
+ - bugfix: Fixed CAS direct firing
+ - bugfix: Fixed Medevac buttons not moving the dropship (still currently requires
+ manual winch)
+ - bugfix: Fixed camera_manager resizing the view incorrectly because of overlay_lighting
+ - refactor: Ported some hard delete fixes for maps.
+ - bugfix: Fixed simulators detonation button
+ - bugfix: Fixed weeded corpses not changing color during forsaken conversion
+ - code_imp: Add missing bitfield definitions for variable viewer (VV)
+ LTNTS:
+ - bugfix: fixes trippy carrot cake sprite
+ SabreML:
+ - bugfix: Fixed observing a monkey/farwa/stok/etc. locking the camera onto it.
+ - bugfix: Fixed the 'busy' circle icon sometimes rendering behind object on the
+ tile above.
+ - spellcheck: Updated the night vision goggles 'Tip of the round' to instead relate
+ to night vision optics.
+ TheGamerdk:
+ - rscadd: SLs, SOs and XOs can now recommend people for medals! Do this by right-clicking
+ your headset, or using the new button in the object panel of the Status Panel!
+ ihatethisengine:
+ - balance: Pylons give larva only up to 50% of groundside (weighted) marines, instead
+ of 40% of all humans.
+ - balance: Xenos get major boost to evo speed for 3 minutes after hijack. Hijacking
+ resets ovi cooldown.
+ - rscadd: Xenos now see marines on the tacmap during hijack if xenos outnumber marines
+ for more than 25%.
+ private-tristan:
+ - spellcheck: Changed a tip related to runner pounce to be more accurate
+2024-01-08:
+ realforest2001:
+ - bugfix: Yautja scimitars no longer bypass attack delay.
+2024-01-09:
+ private-tristan:
+ - bugfix: Tunnels are now converted to forsaken on evac.
diff --git a/icons/obj/items/food.dmi b/icons/obj/items/food.dmi
index 9e746bb62656..12093cd2c45b 100644
Binary files a/icons/obj/items/food.dmi and b/icons/obj/items/food.dmi differ
diff --git a/icons/obj/items/paper.dmi b/icons/obj/items/paper.dmi
index c39fede23679..fa8858e8a17b 100644
Binary files a/icons/obj/items/paper.dmi and b/icons/obj/items/paper.dmi differ
diff --git a/icons/obj/structures/props/stationobjs.dmi b/icons/obj/structures/props/stationobjs.dmi
index 66c396f41d12..8d0bf3b9377d 100644
Binary files a/icons/obj/structures/props/stationobjs.dmi and b/icons/obj/structures/props/stationobjs.dmi differ
diff --git a/maps/map_files/USS_Almayer/USS_Almayer.dmm b/maps/map_files/USS_Almayer/USS_Almayer.dmm
index 26c9816ca0c3..5a64f8f16dc4 100644
--- a/maps/map_files/USS_Almayer/USS_Almayer.dmm
+++ b/maps/map_files/USS_Almayer/USS_Almayer.dmm
@@ -13828,10 +13828,7 @@
/obj/structure/machinery/photocopier{
anchored = 0
},
-/obj/structure/sign/poster{
- desc = "A large piece of cheap printed paper. This one proudly demands that you REMEMBER IO!";
- icon_state = "poster14";
- name = "propaganda poster";
+/obj/structure/sign/poster/art{
pixel_y = 32
},
/turf/open/floor/wood/ship,
@@ -17731,11 +17728,9 @@
},
/area/almayer/squads/req)
"bEt" = (
-/obj/structure/machinery/light{
- dir = 1
- },
-/obj/structure/sign/safety/commline_connection{
- pixel_y = 32
+/obj/structure/noticeboard{
+ pixel_x = -10;
+ pixel_y = 31
},
/turf/open/floor/almayer{
dir = 5;
@@ -28870,13 +28865,8 @@
/obj/structure/bed/chair{
dir = 1
},
-/obj/structure/sign/poster{
- desc = "Koorlander Golds, lovingly machine rolled for YOUR pleasure.";
- icon_state = "poster10";
- name = "Koorlander Gold Poster";
- pixel_x = 29;
- pixel_y = 6;
- serial_number = 10
+/obj/structure/sign/poster/ad{
+ pixel_x = 30
},
/turf/open/floor/wood/ship,
/area/almayer/command/corporateliaison)
@@ -30742,14 +30732,13 @@
/turf/open/floor/almayer,
/area/almayer/engineering/upper_engineering/port)
"eNw" = (
-/obj/structure/machinery/atm{
- name = "Weyland-Yutani Automatic Teller Machine";
- pixel_y = 30
- },
/obj/structure/surface/table/almayer,
/obj/item/spacecash/c1000/counterfeit,
/obj/item/storage/box/drinkingglasses,
/obj/item/storage/fancy/cigar,
+/obj/structure/machinery/atm{
+ pixel_y = 32
+ },
/turf/open/floor/almayer,
/area/almayer/command/corporateliaison)
"eNx" = (
@@ -34821,6 +34810,7 @@
/obj/structure/surface/table/almayer,
/obj/item/storage/firstaid/regular,
/obj/item/clipboard,
+/obj/item/tool/pen,
/turf/open/floor/almayer{
icon_state = "plate"
},
@@ -37220,15 +37210,12 @@
/obj/structure/flora/pottedplant{
icon_state = "pottedplant_21"
},
-/obj/structure/sign/poster{
- desc = "A large piece of cheap printed paper. This one proudly demands that you REMEMBER IO!";
- icon_state = "poster14";
- name = "propaganda poster";
- pixel_y = 32
- },
/obj/structure/sign/safety/escapepod{
pixel_x = -17
},
+/obj/structure/sign/poster/hero/voteno{
+ pixel_y = 32
+ },
/turf/open/floor/wood/ship,
/area/almayer/command/corporateliaison)
"hqU" = (
@@ -41181,6 +41168,10 @@
/obj/structure/machinery/prop/almayer/computer/PC{
dir = 4
},
+/obj/item/tool/stamp/approved{
+ pixel_y = -11;
+ pixel_x = -3
+ },
/turf/open/floor/almayer,
/area/almayer/squads/req)
"iVo" = (
@@ -48294,6 +48285,7 @@
/obj/structure/surface/table/almayer,
/obj/effect/spawner/random/toolbox,
/obj/item/clipboard,
+/obj/item/tool/pen,
/turf/open/floor/almayer{
dir = 8;
icon_state = "green"
@@ -59729,8 +59721,8 @@
"pWA" = (
/obj/structure/machinery/disposal,
/obj/structure/disposalpipe/trunk,
-/obj/structure/sign/safety/storage{
- pixel_x = -17
+/obj/structure/machinery/light{
+ dir = 8
},
/turf/open/floor/almayer{
dir = 8;
@@ -65349,6 +65341,14 @@
/obj/structure/pipes/vents/scrubber{
dir = 4
},
+/obj/structure/sign/safety/storage{
+ pixel_y = 7;
+ pixel_x = -17
+ },
+/obj/structure/sign/safety/commline_connection{
+ pixel_x = -17;
+ pixel_y = -7
+ },
/turf/open/floor/almayer{
dir = 8;
icon_state = "green"
@@ -67519,7 +67519,7 @@
/area/almayer/command/cic)
"sSP" = (
/obj/structure/flora/pottedplant{
- icon_state = "pottedplant_21"
+ icon_state = "pottedplant_29"
},
/turf/open/floor/wood/ship,
/area/almayer/command/corporateliaison)
@@ -70165,18 +70165,13 @@
/turf/open/floor/plating/plating_catwalk,
/area/almayer/hallways/hangar)
"tQM" = (
-/obj/structure/sign/poster{
- desc = "One of those hot, tanned babes back the beaches of good ol' Earth.";
- icon_state = "poster12";
- name = "Beach Babe Pinup";
- pixel_x = -30;
- pixel_y = 6;
- serial_number = 12
- },
/obj/effect/decal/warning_stripes{
icon_state = "E";
pixel_x = 1
},
+/obj/structure/sign/poster/pinup{
+ pixel_x = -30
+ },
/turf/open/floor/almayer{
icon_state = "dark_sterile"
},
@@ -123626,7 +123621,7 @@ bdl
lOr
lOr
iwI
-lOr
+bdl
bdl
bEt
bNP
diff --git a/strings/marinetips.txt b/strings/marinetips.txt
index 416c9ef0a4dd..89c55ab6d698 100644
--- a/strings/marinetips.txt
+++ b/strings/marinetips.txt
@@ -85,7 +85,7 @@ Any marine can perform CPR. On dead marines, this will increase the time they ha
If you've been pounced on and your squad is unloading into the target, you can hit the 'rest' button to stay down so you don't get filled with lead after getting up.
You can check the landing zone as a marine in the status panel.
The Colonial Marshals may come to crack down on too heavy of a Black Market usage.
-Functioning night vision goggles can be recharged with batteries. Broken night vision goggles can be repaired by an Engineer with a screwdriver. Not the loadout ones though, those cannot be fixed.
+A night vision HUD optic can be recharged by removing it with a screwdriver, then placing it into a recharger.
You can put a pistol belt on your suit slot. (Just grab a rifle instead.)
Alt-clicking the Squad Leader tracker lets you track your fireteam leader instead.
Armor has a randomized reduction in effectiveness, and does not protect the digits. Take the wiki damage values as a best-case scenario.
diff --git a/strings/xenotips.txt b/strings/xenotips.txt
index 8674146de655..04a6fe46ae65 100644
--- a/strings/xenotips.txt
+++ b/strings/xenotips.txt
@@ -24,7 +24,7 @@ If you have difficulty clicking marines, try using Directional Slashing, though
You can diagonally pounce through the corners of fire as a Lurker or Runner without getting ignited.
When playing as Xeno, consider aiming at the limbs instead of the chest. Marine armor doesn't protect the arms and legs as well as it does the body.
As Xeno, you can break Night-Vision goggles that some marines wear on their helmets. Just aim for the head and slash until the goggles shatter.
-Pounces are ineffective on marines who are laying down.
+You can dodge pounces that aren't aimed directly at you by laying down.
You may rest immediately during a pounce to pounce straight through mobs. It's not very practical or useful though.
Pouncing someone who is buckled to a chair will still stun them, but you won't jump into their tile and they will not be knocked to the ground.
Star shell dust from said grenades is just as meltable as normal flares.
diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js
index fe175ee6d94e..7a528cd4fd75 100644
--- a/tgui/packages/tgui-panel/chat/renderer.js
+++ b/tgui/packages/tgui-panel/chat/renderer.js
@@ -235,6 +235,8 @@ class ChatRenderer {
highlightWords.push(line);
}
}
+ const regexStr = regexExpressions.join('|');
+ const flags = 'g' + (matchCase ? '' : 'i');
// We wrap this in a try-catch to ensure that broken regex doesn't break
// the entire chat.
try {
diff --git a/tgui/packages/tgui/interfaces/CasSim.tsx b/tgui/packages/tgui/interfaces/CasSim.tsx
index ba5990bd81ce..cac23cde1833 100644
--- a/tgui/packages/tgui/interfaces/CasSim.tsx
+++ b/tgui/packages/tgui/interfaces/CasSim.tsx
@@ -3,7 +3,6 @@ import { Box, Button, Section, ProgressBar, NoticeBox, Stack } from '../componen
interface CasSimData {
configuration: any;
- looking: 0 | 1;
dummy_mode: string;
worldtime: number;
nextdetonationtime: number;
@@ -21,7 +20,7 @@ export const CasSim = (_props, context) => {
const timeLeft = data.nextdetonationtime - data.worldtime;
const timeLeftPct = timeLeft / data.detonation_cooldown;
- const canDetonate = timeLeft < 0 && data.configuration && data.looking;
+ const canDetonate = timeLeft < 0 && data.configuration && simulationView;
return (
diff --git a/tgui/packages/tgui/interfaces/DemoSim.jsx b/tgui/packages/tgui/interfaces/DemoSim.tsx
similarity index 92%
rename from tgui/packages/tgui/interfaces/DemoSim.jsx
rename to tgui/packages/tgui/interfaces/DemoSim.tsx
index b1a6b3349afe..87dfa81236be 100644
--- a/tgui/packages/tgui/interfaces/DemoSim.jsx
+++ b/tgui/packages/tgui/interfaces/DemoSim.tsx
@@ -2,8 +2,16 @@ import { useBackend, useLocalState } from '../backend';
import { Button, Section, ProgressBar, NoticeBox, Box, Stack } from '../components';
import { Window } from '../layouts';
+interface DemoSimData {
+ configuration: any;
+ dummy_mode: string;
+ worldtime: number;
+ nextdetonationtime: number;
+ detonation_cooldown: number;
+}
+
export const DemoSim = (_props, context) => {
- const { act, data } = useBackend(context);
+ const { act, data } = useBackend(context);
const [simulationView, setSimulationView] = useLocalState(
context,
'simulation_view',
@@ -13,7 +21,7 @@ export const DemoSim = (_props, context) => {
const timeLeft = data.nextdetonationtime - data.worldtime;
const timeLeftPct = timeLeft / data.detonation_cooldown;
- const canDetonate = timeLeft < 0 && data.configuration && data.looking;
+ const canDetonate = timeLeft < 0 && data.configuration && simulationView;
return (
diff --git a/tgui/packages/tgui/interfaces/DropshipWeaponsConsole.tsx b/tgui/packages/tgui/interfaces/DropshipWeaponsConsole.tsx
index 629191b3ba70..feb27a9d57c3 100644
--- a/tgui/packages/tgui/interfaces/DropshipWeaponsConsole.tsx
+++ b/tgui/packages/tgui/interfaces/DropshipWeaponsConsole.tsx
@@ -11,6 +11,7 @@ import { SupportMfdPanel } from './MfdPanels/SupportPanel';
import { FiremissionMfdPanel } from './MfdPanels/FiremissionPanel';
import { TargetAquisitionMfdPanel } from './MfdPanels/TargetAquisition';
import { mfdState } from './MfdPanels/stateManagers';
+import { otherMfdState } from './MfdPanels/stateManagers';
import { Dpad } from './common/Dpad';
export interface DropshipProps {
@@ -271,6 +272,7 @@ const WeaponsMfdPanel = (props, context) => {
const BaseMfdPanel = (props: MfdProps, context) => {
const { setPanelState } = mfdState(context, props.panelStateId);
+ const { otherPanelState } = otherMfdState(context, props.otherPanelStateId);
return (
{
]}
bottomButtons={[
{},
- { children: 'MAPS', onClick: () => setPanelState('map') },
- { children: 'CAMS', onClick: () => setPanelState('camera') },
+ {
+ children: otherPanelState !== 'map' ? 'MAPS' : undefined,
+ onClick: () => setPanelState('map'),
+ },
+ {
+ children: otherPanelState !== 'camera' ? 'CAMS' : undefined,
+ onClick: () => setPanelState('camera'),
+ },
]}>
@@ -337,7 +345,10 @@ export const DropshipWeaponsConsole = () => {
-
+
@@ -356,7 +367,10 @@ export const DropshipWeaponsConsole = () => {
-
+
diff --git a/tgui/packages/tgui/interfaces/HiveStatus.jsx b/tgui/packages/tgui/interfaces/HiveStatus.jsx
index 048eb1f6df7b..caecf087579f 100644
--- a/tgui/packages/tgui/interfaces/HiveStatus.jsx
+++ b/tgui/packages/tgui/interfaces/HiveStatus.jsx
@@ -113,6 +113,7 @@ const GeneralInformation = (props, context) => {
total_xenos,
burrowed_larva,
evilution_level,
+ pylon_status,
} = data;
return (
@@ -142,6 +143,11 @@ const GeneralInformation = (props, context) => {
Evilution: {evilution_level}
+ {pylon_status && (
+
+ {pylon_status}
+
+ )}
);
};
diff --git a/tgui/packages/tgui/interfaces/IcMedalsPanel.js b/tgui/packages/tgui/interfaces/IcMedalsPanel.js
new file mode 100644
index 000000000000..a873ab5368df
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/IcMedalsPanel.js
@@ -0,0 +1,170 @@
+import { useBackend, useLocalState } from '../backend';
+import { Window } from '../layouts';
+import { classes } from 'common/react';
+import { NoticeBox, Section, Box, Button, Stack, Flex } from '../components';
+
+export const IcMedalsPanel = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const CONDUCT_MEDAL = 'distinguished conduct medal';
+ const BRONZE_HEART_MEDAL = 'bronze heart medal';
+ const VALOR_MEDAL = 'medal of valor';
+ const HEROISM_MEDAL = 'medal of exceptional heroism';
+
+ const [recommendationMedalTypes, setRecommendationMedalTypes] = useLocalState(
+ context,
+ 'recommendation_types',
+ []
+ );
+
+ return (
+
+
+
+
+
+ {data.recommendations.map((recommendation, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Recommender: {recommendation.recommender_name} (
+ {recommendation.recommender_rank})
+
+ Reason: {recommendation.reason}
+
+
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/CameraPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/CameraPanel.tsx
index f3a1197e9802..8bf5807b9fcd 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/CameraPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/CameraPanel.tsx
@@ -11,9 +11,9 @@ export const CameraMfdPanel = (props: MfdProps, context) => {
return (
act('nvg-enable') },
- { children: 'nvgoff', onClick: () => act('nvg-disable') },
+ leftButtons={[
+ { children: 'NV-ON', onClick: () => act('nvg-enable') },
+ { children: 'NV-OFF', onClick: () => act('nvg-disable') },
]}
bottomButtons={[{ children: 'EXIT', onClick: () => setPanelState('') }]}>
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/EquipmentPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/EquipmentPanel.tsx
index 05a500563b52..8f2cff9f1842 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/EquipmentPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/EquipmentPanel.tsx
@@ -314,16 +314,6 @@ export const EquipmentMfdPanel = (props: MfdProps, context) => {
return (
setPanelState('firemission'),
- },
- {},
- {},
- ]}
leftButtons={[
weap2 ? generateButton(weap2) : {},
weap1 ? generateButton(weap1) : {},
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/FiremissionPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/FiremissionPanel.tsx
index fd71dab8f045..0eaa43f53478 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/FiremissionPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/FiremissionPanel.tsx
@@ -67,11 +67,7 @@ const FiremissionMfdHomePage = (props: MfdProps, context) => {
const firemission =
data.firemission_data.length > x ? data.firemission_data[x] : undefined;
return {
- children: firemission ? (
-
- FM {x + 1}
{firemission?.name}
-
- ) : undefined,
+ children: firemission ? FM {x + 1}
: undefined,
onClick: () => setSelectedFm(firemission?.name),
};
};
@@ -95,7 +91,6 @@ const FiremissionMfdHomePage = (props: MfdProps, context) => {
leftButtons={left_firemissions}
rightButtons={right_firemissions}
topButtons={[
- {},
{},
{},
fmName
@@ -110,10 +105,11 @@ const FiremissionMfdHomePage = (props: MfdProps, context) => {
},
}
: {},
+ {},
{
- children: ,
+ children: fmOffset > 0 ? : undefined,
onClick: () => {
- if (fmOffset >= 1) {
+ if (fmOffset > 0) {
setFmOffset(fmOffset - 1);
}
},
@@ -128,9 +124,12 @@ const FiremissionMfdHomePage = (props: MfdProps, context) => {
{},
{},
{
- children: ,
+ children:
+ fmOffset + 10 < data.firemission_data?.length ? (
+
+ ) : undefined,
onClick: () => {
- if (fmOffset + 8 < data.firemission_data.length) {
+ if (fmOffset + 10 < data.firemission_data?.length) {
setFmOffset(fmOffset + 1);
}
},
@@ -212,7 +211,10 @@ const ViewFiremissionMfdPanel = (
bottomButtons={[
{
children: 'EXIT',
- onClick: () => setPanelState(''),
+ onClick: () => {
+ setSelectedFm(undefined);
+ setPanelState('');
+ },
},
]}
rightButtons={editFm === true ? rightButtons : []}>
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/FultonPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/FultonPanel.tsx
index 05d33e51a8d1..5bb2f462d1a2 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/FultonPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/FultonPanel.tsx
@@ -55,7 +55,7 @@ export const FultonMfdPanel = (props: MfdProps, context) => {
{
children: ,
onClick: () => {
- if (fulltonOffset >= 1) {
+ if (fulltonOffset > 0) {
setFultonOffset(fulltonOffset - 1);
}
},
@@ -70,6 +70,9 @@ export const FultonMfdPanel = (props: MfdProps, context) => {
},
},
]}
+ topButtons={[
+ { children: 'EQUIP', onClick: () => setPanelState('equipment') },
+ ]}
bottomButtons={[
{
children: 'EXIT',
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/MGPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/MGPanel.tsx
index dc6df25e17c1..fac34ef3e49a 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/MGPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/MGPanel.tsx
@@ -7,6 +7,7 @@ import { EquipmentContext, MGSpec } from './types';
const MgPanel = (props: DropshipEquipment) => {
const mgData = props.data as MGSpec;
+
return (
@@ -44,6 +45,8 @@ export const MgMfdPanel = (props: MfdProps, context) => {
const { setPanelState } = mfdState(context, props.panelStateId);
const { equipmentState } = useEquipmentState(context, props.panelStateId);
const mg = data.equipment_data.find((x) => x.mount_point === equipmentState);
+ const deployLabel = (mg?.data?.deployed ?? 0) === 1 ? 'RETRACT' : 'DEPLOY';
+
return (
{
]}
leftButtons={[
{
- children: 'DEPLOY',
+ children: deployLabel,
onClick: () =>
act('deploy-equipment', { equipment_id: mg?.mount_point }),
},
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/MedevacPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/MedevacPanel.tsx
index 634b4ef2a52e..a804b4d92e59 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/MedevacPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/MedevacPanel.tsx
@@ -48,6 +48,7 @@ const MedevacOccupant = (props: { data: MedevacTargets }) => (
);
export const MedevacMfdPanel = (props: MfdProps, context) => {
+ const { data, act } = useBackend(context);
const [medevacOffset, setMedevacOffset] = useLocalState(
context,
`${props.panelStateId}_medevacoffset`,
@@ -56,8 +57,6 @@ export const MedevacMfdPanel = (props: MfdProps, context) => {
const { setPanelState } = mfdState(context, props.panelStateId);
const { equipmentState } = useEquipmentState(context, props.panelStateId);
- const { data, act } = useBackend(context);
-
const result = data.equipment_data.find(
(x) => x.mount_point === equipmentState
);
@@ -85,6 +84,7 @@ export const MedevacMfdPanel = (props: MfdProps, context) => {
const all_targets = range(medevacOffset, medevacOffset + 8)
.map((x) => data.medevac_targets[x])
.filter((x) => x !== undefined);
+
return (
{
{
children: ,
onClick: () => {
- if (medevacOffset >= 1) {
+ if (medevacOffset > 0) {
setMedevacOffset(medevacOffset - 1);
}
},
@@ -109,10 +109,7 @@ export const MedevacMfdPanel = (props: MfdProps, context) => {
},
]}
topButtons={[
- {
- children: 'EQUIP',
- onClick: () => setPanelState('equipment'),
- },
+ { children: 'EQUIP', onClick: () => setPanelState('equipment') },
]}
bottomButtons={[
{
@@ -168,7 +165,7 @@ export const MedevacMfdPanel = (props: MfdProps, context) => {
{all_targets.map((x) => (
<>
-
+
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/MultifunctionDisplay.tsx b/tgui/packages/tgui/interfaces/MfdPanels/MultifunctionDisplay.tsx
index 9df7eaffcde4..35f81b45ed3e 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/MultifunctionDisplay.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/MultifunctionDisplay.tsx
@@ -13,6 +13,7 @@ export interface MfdProps {
rightButtons?: Array;
bottomButtons?: Array;
children?: InfernoNode;
+ otherPanelStateId?: string;
}
export const MfdButton = (props: ButtonProps, context) => {
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/SentryPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/SentryPanel.tsx
index d8ea220ec986..c5f9bd04c1b0 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/SentryPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/SentryPanel.tsx
@@ -57,6 +57,7 @@ export const SentryMfdPanel = (props: MfdProps, context) => {
);
const deployLabel =
(sentry?.data?.deployed ?? 0) === 1 ? 'RETRACT' : 'DEPLOY';
+
return (
{
act('deploy-equipment', { equipment_id: sentry?.mount_point }),
},
{
- children: 'CAMERA',
+ children: sentry?.data?.camera_available ? 'CAMERA' : undefined,
onClick: () =>
act('set-camera-sentry', { equipment_id: sentry?.mount_point }),
},
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/SpotlightPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/SpotlightPanel.tsx
index ce241420497e..f3a5884f89ca 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/SpotlightPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/SpotlightPanel.tsx
@@ -33,6 +33,9 @@ export const SpotlightMfdPanel = (props: MfdProps, context) => {
const spotlight = data.equipment_data.find(
(x) => x.mount_point === equipmentState
);
+ const deployLabel =
+ (spotlight?.data?.deployed ?? 0) === 1 ? 'DISABLE' : 'ENABLE';
+
return (
{
]}
leftButtons={[
{
- children: 'DEPLOY',
+ children: deployLabel,
onClick: () =>
act('deploy-equipment', { equipment_id: spotlight?.mount_point }),
},
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/SupportPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/SupportPanel.tsx
index 1eca123173b0..5767b4960840 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/SupportPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/SupportPanel.tsx
@@ -36,6 +36,9 @@ export const SupportMfdPanel = (props: MfdProps, context) => {
return (
setPanelState('equipment') },
+ ]}
bottomButtons={[
{
children: 'EXIT',
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/TargetAquisition.tsx b/tgui/packages/tgui/interfaces/MfdPanels/TargetAquisition.tsx
index 49f22db18104..ebaacfb90337 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/TargetAquisition.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/TargetAquisition.tsx
@@ -59,7 +59,7 @@ const useTargetFiremissionSelect = (context) => {
};
};
-const useTargetOffset = (context, panelId: string) => {
+export const useTargetOffset = (context, panelId: string) => {
const [data, set] = useLocalState(context, `${panelId}_targetOffset`, 0);
return {
targetOffset: data,
@@ -79,7 +79,7 @@ const useTargetSubmenu = (context, panelId: string) => {
};
};
-const TargetLines = (props: { panelId: string }, context) => {
+export const TargetLines = (props: { panelId: string }, context) => {
const { data } = useBackend<
EquipmentContext & FiremissionContext & TargetContext
>(context);
@@ -140,6 +140,34 @@ const leftButtonGenerator = (context, panelId: string) => {
useTargetFiremissionSelect(context);
const { weaponSelected, setWeaponSelected } = useWeaponSelectedState(context);
const weapons = data.equipment_data.filter((x) => x.is_weapon);
+ const [fmOffset] = useLocalState(
+ context,
+ `${panelId}_fm_strike_select_offset`,
+ 0
+ );
+ const firemission_mapper = (x: number) => {
+ if (x === 0) {
+ return {
+ children: 'CANCEL',
+ onClick: () => {
+ setFiremissionSelected(undefined);
+ setStrikeMode(undefined);
+ setLeftButtonMode(undefined);
+ },
+ };
+ }
+ x -= 1;
+ const firemission =
+ data.firemission_data.length > x ? data.firemission_data[x] : undefined;
+ return {
+ children: firemission ? FM {x + 1}
: undefined,
+ onClick: () => {
+ setFiremissionSelected(data.firemission_data[x]);
+ setLeftButtonMode(undefined);
+ },
+ };
+ };
+
if (leftButtonMode === undefined) {
return [
{
@@ -154,26 +182,30 @@ const leftButtonGenerator = (context, panelId: string) => {
}
if (leftButtonMode === 'STRIKE') {
if (strikeMode === 'weapon' && weaponSelected === undefined) {
- return weapons.map((x) => {
- return {
- children: x.shorthand,
+ const cancelButton = [
+ {
+ children: 'CANCEL',
onClick: () => {
- setWeaponSelected(x.mount_point);
+ setFiremissionSelected(undefined);
+ setStrikeMode(undefined);
setLeftButtonMode(undefined);
},
- };
- });
+ },
+ ];
+ return cancelButton.concat(
+ weapons.map((x) => {
+ return {
+ children: x.shorthand,
+ onClick: () => {
+ setWeaponSelected(x.eqp_tag);
+ setLeftButtonMode(undefined);
+ },
+ };
+ })
+ );
}
if (strikeMode === 'firemission' && firemissionSelected === undefined) {
- return data.firemission_data.map((x) => {
- return {
- children: x.name,
- onClick: () => {
- setFiremissionSelected(x);
- setLeftButtonMode(undefined);
- },
- };
- });
+ return range(fmOffset, fmOffset + 5).map(firemission_mapper);
}
return [
{ children: 'CANCEL', onClick: () => setLeftButtonMode(undefined) },
@@ -230,7 +262,7 @@ const leftButtonGenerator = (context, panelId: string) => {
return [];
};
-const lazeMapper = (context, offset) => {
+export const lazeMapper = (context, offset) => {
const { act, data } = useBackend(context);
const { setSelectedTarget } = useLazeTarget(context);
@@ -271,6 +303,21 @@ const lazeMapper = (context, offset) => {
};
};
+export const getLastTargetName = (data) => {
+ const target = data.targets_data[data.targets_data.length - 1] ?? undefined;
+ const isDebug = target?.target_name.includes('debug');
+ if (isDebug) {
+ return 'debug ' + target.target_name.split(' ')[3];
+ }
+ const label = target?.target_name.split(' ')[0] ?? '';
+ const squad = label[0] ?? undefined;
+ const number = label.split('-')[1] ?? undefined;
+
+ return squad !== undefined && number !== undefined
+ ? `${squad}-${number}`
+ : target?.target_name;
+};
+
export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
const { panelStateId } = props;
@@ -288,45 +335,37 @@ export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
context,
panelStateId
);
+ const [fmOffset, setFmOffset] = useLocalState(
+ context,
+ `${props.panelStateId}_fm_strike_select_offset`,
+ 0
+ );
+ const { leftButtonMode } = useTargetSubmenu(context, props.panelStateId);
const { fmXOffsetValue } = useFiremissionXOffsetValue(context);
const { fmYOffsetValue } = useFiremissionYOffsetValue(context);
- const lazes = range(0, 5).map((x) =>
- x > data.targets_data.length ? undefined : data.targets_data[x]
- );
-
const strikeConfigLabel =
strikeMode === 'weapon'
- ? data.equipment_data.find((x) => x.mount_point === weaponSelected)?.name
+ ? data.equipment_data.find((x) => x.eqp_tag === weaponSelected)?.name
: firemissionSelected !== undefined
? data.firemission_data.find(
(x) => x.mission_tag === firemissionSelected.mission_tag
)?.name
: 'NONE';
- const lazeIndex = lazes.findIndex((x) => x?.target_tag === selectedTarget);
- const strikeReady = strikeMode !== undefined && lazeIndex !== -1;
+ const strikeReady =
+ selectedTarget !== undefined &&
+ strikeDirection !== undefined &&
+ ((strikeMode === 'weapon' &&
+ weaponSelected !== undefined &&
+ data.equipment_data.find((x) => x.eqp_tag === weaponSelected)) ||
+ (strikeMode === 'firemission' && firemissionSelected !== undefined));
const targets = range(targetOffset, targetOffset + 5).map((x) =>
lazeMapper(context, x)
);
- const getLastName = () => {
- const target = data.targets_data[data.targets_data.length - 1] ?? undefined;
- const isDebug = target?.target_name.includes('debug');
- if (isDebug) {
- return 'debug ' + target.target_name.split(' ')[3];
- }
- const label = target?.target_name.split(' ')[0] ?? '';
- const squad = label[0] ?? undefined;
- const number = label.split('-')[1] ?? undefined;
-
- return squad !== undefined && number !== undefined
- ? `${squad}-${number}`
- : target?.target_name;
- };
-
if (
selectedTarget &&
data.targets_data.find((x) => `${x.target_tag}` === `${selectedTarget}`) ===
@@ -361,7 +400,20 @@ export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
}
},
},
- {},
+ {
+ children:
+ leftButtonMode === 'STRIKE' &&
+ strikeMode === 'firemission' &&
+ firemissionSelected === undefined &&
+ fmOffset > 0 ? (
+
+ ) : undefined,
+ onClick: () => {
+ if (fmOffset > 0) {
+ setFmOffset(fmOffset - 1);
+ }
+ },
+ },
{},
{},
{
@@ -378,16 +430,29 @@ export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
children: 'EXIT',
onClick: () => setPanelState(''),
},
- {},
+ {
+ children:
+ leftButtonMode === 'STRIKE' &&
+ strikeMode === 'firemission' &&
+ firemissionSelected === undefined &&
+ fmOffset + 4 < data.firemission_data?.length ? (
+
+ ) : undefined,
+ onClick: () => {
+ if (fmOffset + 4 < data.firemission_data?.length) {
+ setFmOffset(fmOffset + 1);
+ }
+ },
+ },
{},
{},
{
children:
- targetOffset < lazes.length ? (
+ targetOffset + 5 < data.targets_data?.length ? (
) : undefined,
onClick: () => {
- if (targetOffset < lazes.length) {
+ if (targetOffset + 5 < data.targets_data?.length) {
setTargetOffset(targetOffset + 1);
}
},
@@ -443,8 +508,9 @@ export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
Target selected:{' '}
- {lazes.find((x) => x?.target_tag === selectedTarget)
- ?.target_name ?? 'NONE'}
+ {data.targets_data.find(
+ (x) => x?.target_tag === selectedTarget
+ )?.target_name ?? 'NONE'}
@@ -502,7 +568,7 @@ export const TargetAquisitionMfdPanel = (props: MfdProps, context) => {
LATEST
- {getLastName()}
+ {getLastTargetName(data)}
>
)}
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/WeaponPanel.tsx b/tgui/packages/tgui/interfaces/MfdPanels/WeaponPanel.tsx
index 090001aa3ac9..cec542fdc3d1 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/WeaponPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MfdPanels/WeaponPanel.tsx
@@ -1,10 +1,11 @@
import { range } from 'common/collections';
import { useBackend } from '../../backend';
-import { Box, Stack } from '../../components';
+import { Box, Icon, Stack } from '../../components';
import { DropshipEquipment } from '../DropshipWeaponsConsole';
import { MfdProps, MfdPanel } from './MultifunctionDisplay';
import { mfdState, useWeaponState } from './stateManagers';
import { LazeTarget } from './types';
+import { getLastTargetName, lazeMapper, TargetLines, useTargetOffset } from './TargetAquisition';
const EmptyWeaponPanel = (props, context) => {
return Nothing Listed
;
@@ -14,30 +15,12 @@ interface EquipmentContext {
targets_data: Array;
}
-const getLazeButtonProps = (context) => {
- const { act, data } = useBackend(context);
- const lazes = range(0, 5).map((x) =>
- x > data.targets_data.length ? undefined : data.targets_data[x]
- );
- const get_laze = (index: number) => {
- const laze = lazes.find((_, i) => i === index);
- if (laze === undefined) {
- return {
- children: '',
- onClick: () => act('set-camera', { equipment_id: null }),
- };
- }
- return {
- children: laze?.target_name.split(' ')[0] ?? 'NONE',
- onClick: laze
- ? () => act('set-camera', { 'equipment_id': laze.target_tag })
- : undefined,
- };
- };
- return [get_laze(0), get_laze(1), get_laze(2), get_laze(3), get_laze(4)];
-};
+const WeaponPanel = (
+ props: { panelId: string; equipment: DropshipEquipment },
+ context
+) => {
+ const { data } = useBackend(context);
-const WeaponPanel = (props: { equipment: DropshipEquipment }, context) => {
return (
@@ -45,7 +28,7 @@ const WeaponPanel = (props: { equipment: DropshipEquipment }, context) => {
ACTIONS
- {false && (
+ {true && (
{
-
@@ -146,7 +135,14 @@ export const WeaponMfdPanel = (props: MfdProps, context) => {
const { setPanelState } = mfdState(context, props.panelStateId);
const { weaponState } = useWeaponState(context, props.panelStateId);
const { data, act } = useBackend(context);
+ const { targetOffset, setTargetOffset } = useTargetOffset(
+ context,
+ props.panelStateId
+ );
const weap = data.equipment_data.find((x) => x.mount_point === weaponState);
+ const targets = range(targetOffset, targetOffset + 5).map((x) =>
+ lazeMapper(context, x)
+ );
return (
{
onClick: () => setPanelState(''),
},
{},
+ {},
+ {},
+ {
+ children:
+ targetOffset + 5 < data.targets_data?.length ? (
+
+ ) : undefined,
+ onClick: () => {
+ if (targetOffset + 5 < data.targets_data?.length) {
+ setTargetOffset(targetOffset + 1);
+ }
+ },
+ },
]}
topButtons={[
+ { children: 'EQUIP', onClick: () => setPanelState('equipment') },
+ {},
+ {},
+ {},
{
- children: 'EQUIP',
- onClick: () => setPanelState('equipment'),
+ children: targetOffset > 0 ? : undefined,
+ onClick: () => {
+ if (targetOffset > 0) {
+ setTargetOffset(targetOffset - 1);
+ }
+ },
},
]}
- rightButtons={getLazeButtonProps(context)}>
+ rightButtons={targets}>
- {weap ? : }
+ {weap ? (
+
+ ) : (
+
+ )}
);
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/stateManagers.ts b/tgui/packages/tgui/interfaces/MfdPanels/stateManagers.ts
index e639938eabf8..4b1729dce0f7 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/stateManagers.ts
+++ b/tgui/packages/tgui/interfaces/MfdPanels/stateManagers.ts
@@ -60,6 +60,17 @@ export const mfdState = (context, panelId: string) => {
};
};
+export const otherMfdState = (context, otherPanelId: string | undefined) => {
+ const [data] = useSharedState(
+ context,
+ `${otherPanelId}_panelstate`,
+ ''
+ );
+ return {
+ otherPanelState: data,
+ };
+};
+
export const useWeaponState = (context, panelId: string) => {
const [data, set] = useSharedState(
context,
diff --git a/tgui/packages/tgui/interfaces/MfdPanels/types.ts b/tgui/packages/tgui/interfaces/MfdPanels/types.ts
index 78e7c3314b30..c20449ece428 100644
--- a/tgui/packages/tgui/interfaces/MfdPanels/types.ts
+++ b/tgui/packages/tgui/interfaces/MfdPanels/types.ts
@@ -66,7 +66,7 @@ export type SentrySpec = {
kills: number;
iff_status: string[];
camera_available: number;
- deployed: number;
+ deployed: 0 | 1;
};
export type SpotlightSpec = {
diff --git a/tgui/packages/tgui/interfaces/NoticeBoard.tsx b/tgui/packages/tgui/interfaces/NoticeBoard.tsx
new file mode 100644
index 000000000000..6cc761c0fb6f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NoticeBoard.tsx
@@ -0,0 +1,56 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../backend';
+import { Box, Button, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ allowed: BooleanLike;
+ items: { ref: string; name: string }[];
+};
+
+export const NoticeBoard = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { allowed, items = [] } = data;
+
+ return (
+
+
+ {!items.length ? (
+
+
+ The notice board is empty!
+
+
+ ) : (
+ items.map((item) => (
+
+
+ {item.name}
+
+
+ act('examine', { ref: item.ref })}
+ />
+ act('write', { ref: item.ref })}
+ />
+ act('remove', { ref: item.ref })}
+ />
+
+
+ ))
+ )}
+
+
+ );
+};