diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index c05d84e8122d..339d48c9fe39 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -74,10 +74,10 @@ These are the few directives we have for project maintainers.
- Try to get secondary maintainer approval before merging if you are able to.
- PRs with empty commits intended to generate a changelog.
- Do not merge PRs that contain content from the [banned content list](./CONTRIBUTING.md#banned-content).
-- Do not merge PRs that contain balance changes without GA approval. Exceptions include:
- - Any PR that has been un-reviewed by a GA for 7 days.
+- Do not merge PRs that contain balance changes without Maintainer Manager approval. Exceptions include:
+ - Any PR that has been un-reviewed by a Maintainer Manager for 7 days.
- Do not remove the DNM label that another Maintainer has applied. Exceptions include:
- - GAs removing a DNM label placed by a Maintainer for Balance/Design reasons
+ - Maintainer Managers removing a DNM label placed by a Maintainer for Balance/Design reasons
These are not steadfast rules as maintainers are expected to use their best judgement when operating.
diff --git a/.github/guides/STANDARDS.md b/.github/guides/STANDARDS.md
index fc764d09c816..c9b988bd07ff 100644
--- a/.github/guides/STANDARDS.md
+++ b/.github/guides/STANDARDS.md
@@ -126,7 +126,7 @@ While we normally encourage (and in some cases, even require) bringing out of da
This is a simple one - as we will eventually move to 515, we will need to ditch this kind of callback. So please don't add any new ones. Make our lives easier.
### PROC_REF Macros
- When referencing procs in RegisterSignal, Callback and other procs you should use PROC_REF,TYPE_PROC_REF and GLOBAL_PROC_REF macros.
+ When referencing procs in RegisterSignal, Callback and other procs you should use PROC_REF, TYPE_PROC_REF and GLOBAL_PROC_REF macros.
They ensure compilation fails if the reffered to procs change names or get removed.
The macro to be used depends on how the proc you're in relates to the proc you want to use:
@@ -168,6 +168,8 @@ This is a simple one - as we will eventually move to 515, we will need to ditch
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(funny)), 100))
```
+ Note that the same rules go for verbs too! We have VERB_REF() and TYPE_VERB_REF() as you need it in these same cases. GLOBAL_VERB_REF() isn't a thing however, as verbs are not global.
+
### Signal Handlers
All procs that are registered to listen for signals using `RegisterSignal()` must contain at the start of the proc `SIGNAL_HANDLER` eg;
diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml
index 6075e3029acd..59e6d1144c68 100644
--- a/.github/workflows/generate_documentation.yml
+++ b/.github/workflows/generate_documentation.yml
@@ -21,7 +21,7 @@ jobs:
run: |
~/dmdoc
touch dmdoc/.nojekyll
- echo codedocs.tgstation13.org > dmdoc/CNAME
+ echo docs.cm-ss13.com > dmdoc/CNAME
- name: Deploy
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
diff --git a/code/__DEFINES/conflict.dm b/code/__DEFINES/conflict.dm
index 09be4793c552..141d69c69b11 100644
--- a/code/__DEFINES/conflict.dm
+++ b/code/__DEFINES/conflict.dm
@@ -232,6 +232,7 @@
//OB timings
#define OB_TRAVEL_TIMING 12 SECONDS
#define OB_CRASHING_DOWN 1 SECONDS
+#define OB_CLUSTER_DURATION 45 SECONDS
//=================================================
//Health of various items
diff --git a/code/__DEFINES/dcs/signals/atom/signals_obj.dm b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
index df53558834f6..aebd0d09d0d2 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_obj.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
@@ -4,6 +4,8 @@
/// From /obj/effect/alien/weeds/Initialize()
#define COMSIG_WEEDNODE_GROWTH_COMPLETE "weednode_growth_complete"
+/// From /obj/effect/alien/weeds/Initialize()
+#define COMSIG_WEEDNODE_GROWTH "weednode_growth"
/// From /obj/effect/alien/weeds/proc/on_weed_expand()
#define COMSIG_WEEDNODE_CANNOT_EXPAND_FURTHER "weednode_cannot_expand_further"
@@ -24,3 +26,6 @@
#define COMSIG_TRANSMITTER_UPDATE_ICON "transmitter_update_icon"
#define COMSIG_TENT_COLLAPSING "tent_collapsing"
+
+/// from /obj/proc/afterbuckle()
+#define COSMIG_OBJ_AFTER_BUCKLE "signal_obj_after_buckle"
diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm
index a7a93c4ea114..7696d8ad6037 100644
--- a/code/__DEFINES/dcs/signals/signals_datum.dm
+++ b/code/__DEFINES/dcs/signals/signals_datum.dm
@@ -61,3 +61,6 @@
// from /datum/emergency_call/proc/spawn_candidates()
#define COMSIG_ERT_SETUP "ert_setup"
+
+// from /proc/update_living_queens() : /mob/living/carbon/xenomorph/queen
+#define COMSIG_HIVE_NEW_QUEEN "hive_new_queen"
diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm
index 90a50fad138c..9781f61ec95c 100644
--- a/code/__DEFINES/dcs/signals/signals_global.dm
+++ b/code/__DEFINES/dcs/signals/signals_global.dm
@@ -53,3 +53,6 @@
/// From /datum/admins/proc/force_predator_round()
#define COMSIG_GLOB_PREDATOR_ROUND_TOGGLED "!predator_round_toglged"
+
+/// From /datum/game_mode/colonialmarines/proc/check_ground_humans()
+#define COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING "!groundside_forsaken_handling"
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 3c2527136ed6..662bcb458c55 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -113,7 +113,8 @@
#define SS_INIT_INPUT 85
#define SS_INIT_FAIL_TO_TOPIC 84
#define SS_INIT_TOPIC 83
-#define SS_INIT_RUST 26
+#define SS_INIT_RUST 30
+#define SS_INIT_INFLUXDRIVER 28
#define SS_INIT_SUPPLY_SHUTTLE 25
#define SS_INIT_GARBAGE 24
#define SS_INIT_EVENTS 23.5
@@ -133,7 +134,9 @@
#define SS_INIT_MORE_INIT 16
#define SS_INIT_AIR 15
#define SS_INIT_TELEPORTER 13
-#define SS_INIT_LIGHTING 12
+#define SS_INIT_INFLUXMCSTATS 12
+#define SS_INIT_INFLUXSTATS 11
+#define SS_INIT_LIGHTING 10
#define SS_INIT_DEFCON 9
#define SS_INIT_LAW 6
#define SS_INIT_FZ_TRANSITIONS 5
@@ -212,12 +215,15 @@
#define SS_PRIORITY_UNSPECIFIED 30
#define SS_PRIORITY_PROCESS 25
#define SS_PRIORITY_SOUNDSCAPE 24
+#define SS_PRIORITY_INFLUXDRIVER 23
#define SS_PRIORITY_PAGER_STATUS 22
#define SS_PRIORITY_LIGHTING 20
#define SS_PRIORITY_TRACKING 19
+#define SS_PRIORITY_DATABASE 15
#define SS_PRIORITY_MINIMAPS 11
#define SS_PRIORITY_PING 10
-#define SS_PRIORITY_DATABASE 15
+#define SS_PRIORITY_INFLUXMCSTATS 9
+#define SS_PRIORITY_INFLUXSTATS 8
#define SS_PRIORITY_PLAYTIME 5
#define SS_PRIORITY_PERFLOGGING 4
#define SS_PRIORITY_CORPSESPAWNER 3
diff --git a/code/__DEFINES/tgui.dm b/code/__DEFINES/tgui.dm
index 865088ee72fc..ca6408961eab 100644
--- a/code/__DEFINES/tgui.dm
+++ b/code/__DEFINES/tgui.dm
@@ -32,7 +32,7 @@
/// Creates a message packet for sending via output()
// This is {"type":type,"payload":payload}, but pre-encoded. This is much faster
// than doing it the normal way.
-// To ensure this is correct, this is unit tested in tgui_create_message. However, CM does not have unit tests available.
+// To ensure this is correct, this is unit tested in tgui_create_message.
#define TGUI_CREATE_MESSAGE(type, payload) ( \
"%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e5441090d60c..288604434e34 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -123,7 +123,7 @@
#define TRAIT_SUPER_STRONG "t_super_strong"
/// Foreign biology. Basic medHUDs won't show the mob. (Yautja, Zombies)
#define TRAIT_FOREIGN_BIO "t_foreign_bio"
-/// Eye color changes on intent. (G1 Synths)
+/// Eye color changes on intent. (G1 Synths and WJs)
#define TRAIT_INTENT_EYES "t_intent_eyes"
/// Masked synthetic biology. Basic medHUDs will percieve the mob as human. (Infiltrator Synths)
#define TRAIT_INFILTRATOR_SYNTH "t_infiltrator_synth"
@@ -157,6 +157,8 @@
#define TRAIT_LEADERSHIP "t_leadership"
/// If the mob can see the reagents contents of stuff
#define TRAIT_REAGENT_SCANNER "reagent_scanner"
+/// If the mob cannot eat/be fed
+#define TRAIT_CANNOT_EAT "t_cannot_eat"
/// If the mob is being lazed by a sniper spotter
#define TRAIT_SPOTTER_LAZED "t_spotter_lazed"
/// If the mob has ear protection. Protects from external ear damage effects. Includes explosions, firing the RPG, screeching DEAFNESS only, and flashbangs.
@@ -179,6 +181,8 @@
#define TRAIT_ABILITY_NO_PLASMA_TRANSFER "t_ability_no_plasma_transfer"
/// Shows that the xeno queen is on ovi
#define TRAIT_ABILITY_OVIPOSITOR "t_ability_ovipositor"
+/// Used for burrowed mobs, prevent's SG/sentrys/claymores from autofiring
+#define TRAIT_ABILITY_BURROWED "t_ability_burrowed"
//-- item traits --
// TOOL TRAITS
@@ -211,6 +215,9 @@
//ie. naming a regulation tape "example" will become regulation tape (example)
#define TRAIT_ITEM_RENAME_SPECIAL "t_item_rename_special"
+// This item can't be implanted into someone, regardless of the size of the item.
+#define TRAIT_ITEM_NOT_IMPLANTABLE "t_item_not_implantable"
+
//-- structure traits --
// TABLE TRAITS
/// If the table is being flipped, prevent any changes that will mess with adjacency handling
@@ -228,7 +235,8 @@ GLOBAL_LIST_INIT(mob_traits, list(
TRAIT_TWOBORE_TRAINING,
TRAIT_LEADERSHIP,
TRAIT_DEXTROUS,
- TRAIT_REAGENT_SCANNER
+ TRAIT_REAGENT_SCANNER,
+ TRAIT_ABILITY_BURROWED
))
/*
@@ -258,6 +266,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_BIMEX" = TRAIT_BIMEX,
"TRAIT_EMOTE_CD_EXEMPT" = TRAIT_EMOTE_CD_EXEMPT,
"TRAIT_LISPING" = TRAIT_LISPING,
+ "TRAIT_CANNOT_EAT" = TRAIT_CANNOT_EAT,
),
/mob/living/carbon/xenomorph = list(
"TRAIT_ABILITY_NO_PLASMA_TRANSFER" = TRAIT_ABILITY_NO_PLASMA_TRANSFER,
diff --git a/code/__DEFINES/weapon_stats.dm b/code/__DEFINES/weapon_stats.dm
index 58dad90b0710..bef8413e9615 100644
--- a/code/__DEFINES/weapon_stats.dm
+++ b/code/__DEFINES/weapon_stats.dm
@@ -1,7 +1,7 @@
#define HUMAN_UNIVERSAL_DAMAGEMULT 1
#define RECOIL_BUILDUP_VIEWPUNCH_MULTIPLIER 0.1
-
+#define BASE_VELOCITY_BONUS 0
#define PROJ_BASE_ACCURACY_MULT 0.01
#define PROJ_BASE_DAMAGE_MULT 0.01
diff --git a/code/__DEFINES/wj_emotes.dm b/code/__DEFINES/wj_emotes.dm
new file mode 100644
index 000000000000..f315c6eb2ba5
--- /dev/null
+++ b/code/__DEFINES/wj_emotes.dm
@@ -0,0 +1,8 @@
+#define JOE_EMOTE_CATEGORY_GREETING "Greeting"
+#define JOE_EMOTE_CATEGORY_TASK_UPDATE "Task Update"
+#define JOE_EMOTE_CATEGORY_RESTRICTED_AREA "Restricted Area"
+#define JOE_EMOTE_CATEGORY_FAREWELL "Farewell"
+#define JOE_EMOTE_CATEGORY_QUIP "Quip"
+#define JOE_EMOTE_CATEGORY_WARNING "Warning"
+#define JOE_EMOTE_CATEGORY_QUESTION "Question"
+#define JOE_EMOTE_CATEGORY_NOTICE "Notice"
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index 7741beecedef..4b45c660feab 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -132,6 +132,7 @@
// Weed defines
#define WEED_LEVEL_WEAK 0
#define WEED_LEVEL_STANDARD 1.5
+#define WEED_LEVEL_HARDY 1.6
#define WEED_LEVEL_HIVE 4
#define WEED_RANGE_STANDARD 3
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 888e3c2ce657..ff8e31ad3e8a 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -65,4 +65,4 @@ var/atom/cmp_dist_origin=null
/// Compares observers based on their larva_queue_time value in ascending order
/// Assumes the client on the observer is not null
/proc/cmp_obs_larvaqueuetime_asc(mob/dead/observer/A, mob/dead/observer/B)
- return A.client.larva_queue_time - B.client.larva_queue_time
+ return A.client.player_details.larva_queue_time - B.client.player_details.larva_queue_time
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 61976f4dc903..0132a31d0b50 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -290,21 +290,16 @@
* * cache_only - Whether to not actually send a to_chat message and instead only update larva_queue_cached_message
*/
/proc/message_alien_candidates(list/candidates, dequeued, cache_only = FALSE)
- var/new_players = 0
for(var/i in (1 + dequeued) to candidates.len)
var/mob/dead/observer/cur_obs = candidates[i]
// Generate the messages
- var/cached_message = SPAN_XENONOTICE("You are currently [i-dequeued]\th in the larva queue. There are [new_players] ahead of you that have yet to play this round.")
+ var/cached_message = SPAN_XENONOTICE("You are currently [i-dequeued]\th in the larva queue.")
cur_obs.larva_queue_cached_message = cached_message
if(!cache_only)
var/chat_message = dequeued ? replacetext(cached_message, "currently", "now") : cached_message
to_chat(candidates[i], chat_message)
- // Count how many are prioritized
- if(cur_obs.client.larva_queue_time < 2) // 0 and 1 because facehuggers/t-domers are slightly deprioritized
- new_players++
-
/proc/convert_k2c(temp)
return ((temp - T0C))
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 83da41b515a6..d4d9eb320633 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -164,6 +164,11 @@
t = "0[t]"
return t
+/proc/pad_trailing(text, padding, size)
+ while (length(text) < size)
+ text = "[text][padding]"
+ return text
+
//Adds 'u' number of spaces ahead of the text 't'
/proc/add_lspace(t, u)
while(length(t) < u)
diff --git a/code/_byond_version_compat.dm b/code/_byond_version_compat.dm
index b5379a3b6d5c..719d85654b5f 100644
--- a/code/_byond_version_compat.dm
+++ b/code/_byond_version_compat.dm
@@ -31,19 +31,49 @@
#define LIBCALL call_ext
#endif
-// So we want to have compile time guarantees these procs exist on local type, unfortunately 515 killed the .proc/procname syntax so we have to use nameof()
+// So we want to have compile time guarantees these methods exist on local type, unfortunately 515 killed the .proc/procname and .verb/verbname syntax so we have to use nameof()
+// For the record: GLOBAL_VERB_REF would be useless as verbs can't be global.
+
#if DM_VERSION < 515
-/// Call by name proc reference, checks if the proc exists on this type or as a global proc
+
+/// Call by name proc references, checks if the proc exists on either this type or as a global proc.
#define PROC_REF(X) (.proc/##X)
-/// Call by name proc reference, checks if the proc exists on given type or as a global proc
+/// Call by name verb references, checks if the verb exists on either this type or as a global verb.
+#define VERB_REF(X) (.verb/##X)
+
+/// Call by name proc reference, checks if the proc exists on either the given type or as a global proc
#define TYPE_PROC_REF(TYPE, X) (##TYPE.proc/##X)
-/// Call by name proc reference, checks if the proc is existing global proc
+/// Call by name verb reference, checks if the verb exists on either the given type or as a global verb
+#define TYPE_VERB_REF(TYPE, X) (##TYPE.verb/##X)
+
+/// Call by name proc reference, checks if the proc is an existing global proc
#define GLOBAL_PROC_REF(X) (/proc/##X)
+
#else
-/// Call by name proc reference, checks if the proc exists on this type or as a global proc
+
+/// Call by name proc references, checks if the proc exists on either this type or as a global proc.
#define PROC_REF(X) (nameof(.proc/##X))
-/// Call by name proc reference, checks if the proc exists on given type or as a global proc
+/// Call by name verb references, checks if the verb exists on either this type or as a global verb.
+#define VERB_REF(X) (nameof(.verb/##X))
+
+/// Call by name proc reference, checks if the proc exists on either the given type or as a global proc
#define TYPE_PROC_REF(TYPE, X) (nameof(##TYPE.proc/##X))
-/// Call by name proc reference, checks if the proc is existing global proc
+/// Call by name verb reference, checks if the verb exists on either the given type or as a global verb
+#define TYPE_VERB_REF(TYPE, X) (nameof(##TYPE.verb/##X))
+
+/// Call by name proc reference, checks if the proc is an existing global proc
#define GLOBAL_PROC_REF(X) (/proc/##X)
+
+#endif
+
+#if (DM_VERSION == 515)
+/// fcopy will crash on 515 linux if given a non-existant file, instead of returning 0 like on 514 linux or 515 windows
+/// var case matches documentation for fcopy.
+/world/proc/__fcopy(Src, Dst)
+ if (istext(Src) && !fexists(Src))
+ return 0
+ return fcopy(Src, Dst)
+
+#define fcopy(Src, Dst) world.__fcopy(Src, Dst)
+
#endif
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index f0279907f635..586d5e71a92d 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -10,6 +10,7 @@ GLOBAL_LIST_EMPTY(GeneralFaxes) //Inter-machine faxes
GLOBAL_LIST_EMPTY(fax_contents) //List of fax contents to maintain it even if source paper is deleted
GLOBAL_LIST_EMPTY(failed_fultons) //A list of fultoned items which weren't collected and fell back down
+GLOBAL_LIST_EMPTY(larva_burst_by_hive)
GLOBAL_LIST_INIT_TYPED(custom_huds_list, /datum/custom_hud, setup_all_huds())
GLOBAL_LIST_INIT_TYPED(custom_human_huds, /datum/custom_hud, setup_human_huds())
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index affbc28cdd45..df7dd48a1dd1 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -82,7 +82,7 @@
return
face_atom(A)
- if(mods["middle"])
+ if(mods["middle"])
return
// Special type of click.
if (is_mob_restrained())
@@ -334,7 +334,7 @@
if(prefs.adaptive_zoom)
INVOKE_ASYNC(src, PROC_REF(adaptive_zoom))
else if(prefs.auto_fit_viewport)
- INVOKE_ASYNC(src, .verb/fit_viewport)
+ INVOKE_ASYNC(src, VERB_REF(fit_viewport))
/client/proc/get_adaptive_zoom_factor()
if(!prefs.adaptive_zoom)
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 29dd0a88d3e0..7988ff6d1a95 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -529,6 +529,25 @@ This maintains a list of ip addresses that are able to bypass topic filtering.
/datum/config_entry/string/round_results_webhook_url
+/// InfluxDB v2 Host to connect to for sending statistics (over HTTP API)
+/datum/config_entry/string/influxdb_host
+/// InfluxDB v2 Bucket to send staistics to
+/datum/config_entry/string/influxdb_bucket
+/// InfluxDB v2 Organization to access buckets of
+/datum/config_entry/string/influxdb_org
+/// InfluxDB v2 API Token to access the organization and bucket
+/datum/config_entry/string/influxdb_token
+
+/// How often to snapshot general game statistics to influxdb driver
+/datum/config_entry/number/influxdb_stats_period
+ config_entry_value = 30
+/// How often to snapshot MC statistics
+/datum/config_entry/number/influxdb_mcstats_period
+ config_entry_value = 60
+/// How often to send queued influxdb statistics
+/datum/config_entry/number/influxdb_send_period
+ config_entry_value = 10
+
/// logs all timers in buckets on automatic bucket reset (Useful for timer debugging)
/datum/config_entry/flag/log_timers_on_bucket_reset
diff --git a/code/controllers/subsystem/influxdriver.dm b/code/controllers/subsystem/influxdriver.dm
new file mode 100644
index 000000000000..7e5289dfc518
--- /dev/null
+++ b/code/controllers/subsystem/influxdriver.dm
@@ -0,0 +1,132 @@
+/// Sends collected statistics to an influxdb v2 backend periodically
+SUBSYSTEM_DEF(influxdriver)
+ name = "InfluxDB Driver"
+ wait = 10 SECONDS
+ init_order = SS_INIT_INFLUXDRIVER
+ priority = SS_PRIORITY_INFLUXDRIVER
+ runlevels = RUNLEVELS_DEFAULT|RUNLEVEL_LOBBY
+
+ var/list/send_queue = list()
+
+ /// Maximum amount of metric lines to send at most in one request
+ /// This is neccessary because sending a lot of metrics can get expensive
+ /// and drive the subsystem into overtime, but we can't split the work as it'd be even less efficient
+ var/max_batch = 150
+
+ /// Last timestamp in microseconds
+ var/timestamp_cache_realtime
+ /// Last tick time the timestamp was taken at
+ var/timestamp_cache_worldtime
+
+/datum/controller/subsystem/influxdriver/Initialize()
+ var/period = text2num(CONFIG_GET(number/influxdb_send_period))
+ if(isnum(period))
+ wait = max(period * (1 SECONDS), 2 SECONDS)
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/influxdriver/stat_entry(msg)
+ msg += "period=[wait] queue=[length(send_queue)]"
+ return ..()
+
+/datum/controller/subsystem/influxdriver/proc/unix_timestamp_string() // pending change to rust-g
+ return RUSTG_CALL(RUST_G, "unix_timestamp")()
+
+/datum/controller/subsystem/influxdriver/proc/update_timestamp()
+ PRIVATE_PROC(TRUE)
+ // We make only one request to rustg per game tick, so we cache the result per world.time
+ var/whole_timestamp = unix_timestamp_string() // Format "7129739474.4758981" - timestamp with up to 7-8 decimals
+ var/list/tsparts = splittext(whole_timestamp, ".")
+ var/fractional = copytext(pad_trailing(tsparts[2], "0", 6), 1, 7) // in microseconds
+ timestamp_cache_worldtime = world.time
+ timestamp_cache_realtime = "[tsparts[1]][fractional]"
+
+/datum/controller/subsystem/influxdriver/fire(resumed)
+ var/maxlen = min(length(send_queue)+1, max_batch)
+ var/list/queue = send_queue.Copy(1, maxlen)
+ send_queue.Cut(1, maxlen)
+ flush_queue(queue)
+
+/// Flushes measurements batch to InfluxDB backend
+/datum/controller/subsystem/influxdriver/proc/flush_queue(list/queue)
+ PRIVATE_PROC(TRUE)
+
+ var/host = CONFIG_GET(string/influxdb_host)
+ var/token = CONFIG_GET(string/influxdb_token)
+ var/bucket = CONFIG_GET(string/influxdb_bucket)
+ var/org = CONFIG_GET(string/influxdb_org)
+
+ if(!host || !token || !bucket || !org)
+ can_fire = FALSE
+ return
+
+ if(!length(queue))
+ return // Nothing to do
+
+ var/url = "[host]/api/v2/write?org=[org]&bucket=[bucket]&precision=us" // microseconds
+ var/list/headers = list()
+ headers["Authorization"] = "Token [token]"
+ headers["Content-Type"] = "text/plain; charset=utf-8"
+ headers["Accept"] = "application/json"
+
+ var/datum/http_request/request = new
+ var/payload = ""
+ for(var/line in queue)
+ payload += "[line]\n"
+ request.prepare(RUSTG_HTTP_METHOD_POST, url, payload, headers)
+ request.begin_async()
+ // TODO possibly check back result of request later
+
+/// Enqueues sending to InfluxDB Backend selected measurement values - round_id and timestamp are filled in automatically
+/datum/controller/subsystem/influxdriver/proc/enqueue_stats(measurement, list/tags, list/fields)
+ . = FALSE
+ var/valid = FALSE
+ var/serialized = "[measurement],round_id=[GLOB.round_id]"
+ if(tags)
+ for(var/tag in tags)
+ var/serialized_tag = serialize_field(tag, tags[tag])
+ if(serialized_tag)
+ serialized += ",[serialized_tag]"
+ serialized += " "
+ var/comma = ""
+ for(var/field in fields)
+ var/serialized_field = serialize_field(field, fields[field])
+ if(serialized_field)
+ valid = TRUE
+ serialized += "[comma][serialized_field]"
+ comma = ","
+ if(!valid)
+ CRASH("Attempted to serialize to InfluxDB backend an invalid measurement (likely has no fields)")
+ if(timestamp_cache_worldtime != world.time)
+ update_timestamp()
+ serialized += " [timestamp_cache_realtime]"
+ send_queue += serialized
+ return TRUE
+
+/// Enqueues sending varied stats in a dumb and simpler format directly as: measurement count=
+/datum/controller/subsystem/influxdriver/proc/enqueue_stats_crude(measurement, value, field_name = "count")
+ . = FALSE
+ var/serialized_field = serialize_field(field_name, value)
+ if(!length(serialized_field))
+ return
+ if(timestamp_cache_worldtime != world.time)
+ update_timestamp()
+ var/serialized = "[measurement],round_id=[GLOB.round_id] [serialized_field] [timestamp_cache_realtime]"
+ send_queue += serialized
+ return TRUE
+
+/// Puts a single field or tag value into InfluxDB Line format
+/datum/controller/subsystem/influxdriver/proc/serialize_field(field, value)
+ var/static/regex/whitelistedCharacters = regex(@{"([^a-zA-Z0-9_]+)"}, "g")
+ var/sanitized_field = whitelistedCharacters.Replace("[field]", "")
+ if(!length(sanitized_field) || copytext(sanitized_field, 1, 2) == "_")
+ CRASH("Invalid tag/field for InfluxDB serialization: '[sanitized_field]' (original: '[field]')")
+ var/sanitized_value
+ if(isnum(value))
+ sanitized_value = value
+ else if(istext(value))
+ sanitized_value = whitelistedCharacters.Replace("[value]", "")
+ if(!length(sanitized_value) || copytext(sanitized_value, 1, 2) == "_")
+ CRASH("Invalid value for InfluxDB serialization: '[sanitized_value]' (original: '[value]')")
+ else
+ CRASH("Invalid value type passed for InfluxDB serialization: '[value]'")
+ return "[sanitized_field]=[sanitized_value]"
diff --git a/code/controllers/subsystem/influxmcstats.dm b/code/controllers/subsystem/influxmcstats.dm
new file mode 100644
index 000000000000..a1bf171d81a3
--- /dev/null
+++ b/code/controllers/subsystem/influxmcstats.dm
@@ -0,0 +1,47 @@
+SUBSYSTEM_DEF(influxmcstats)
+ name = "InfluxDB MC Stats"
+ wait = 60 SECONDS
+ priority = SS_PRIORITY_INFLUXMCSTATS
+ init_order = SS_INIT_INFLUXMCSTATS
+ runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
+ flags = SS_KEEP_TIMING
+ var/checkpoint = 0
+ var/list/subsystem_name_cache = list()
+
+/datum/controller/subsystem/influxmcstats/Initialize()
+ var/period = text2num(CONFIG_GET(number/influxdb_mcstats_period))
+ if(isnum(period))
+ wait = max(period * (1 SECONDS), 10 SECONDS)
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/influxmcstats/stat_entry(msg)
+ msg += "period=[wait] checkpoint=[checkpoint]"
+ return ..()
+
+/datum/controller/subsystem/influxmcstats/fire(resumed)
+ if(!SSinfluxdriver.can_fire)
+ can_fire = FALSE
+ return
+
+ var/list/data = list()
+ data["time_dilation_current"] = SStime_track.time_dilation_current
+ data["time_dilation_avg"] = SStime_track.time_dilation_avg
+ data["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow
+ data["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast
+ SSinfluxdriver.enqueue_stats("tidi", null, data)
+
+ SSinfluxdriver.enqueue_stats("cpu", null, list("cpu" = world.cpu, "map_cpu" = world.map_cpu))
+
+ var/static/regex/get_last_path_element = regex(@{"/([^/]+)$"})
+ checkpoint++
+ for(var/datum/controller/subsystem/SS in Master.subsystems)
+ if(!SS.can_fire)
+ continue
+ if(!subsystem_name_cache[SS.type])
+ get_last_path_element.Find("[SS.type]")
+ subsystem_name_cache[SS.type] = "SS[get_last_path_element.group[1]]"
+ var/SSname = subsystem_name_cache[SS.type]
+ if(!SSname)
+ stack_trace("Influx MC Stats couldnt name a subsystem, type=[SS.type]")
+ continue
+ SSinfluxdriver.enqueue_stats("sstimings", list("ss" = SSname), list("cost" = SS.cost, "tick_overrun" = SS.tick_overrun, "tick_usage" = SS.tick_usage, "wait" = SS.wait))
diff --git a/code/controllers/subsystem/influxstats.dm b/code/controllers/subsystem/influxstats.dm
new file mode 100644
index 000000000000..01015b83191d
--- /dev/null
+++ b/code/controllers/subsystem/influxstats.dm
@@ -0,0 +1,156 @@
+/// Sends generic round running statistics to the InfluxDB backend
+SUBSYSTEM_DEF(influxstats)
+ name = "InfluxDB Game Stats"
+ wait = 60 SECONDS
+ priority = SS_PRIORITY_INFLUXSTATS
+ init_order = SS_INIT_INFLUXSTATS
+ runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
+ flags = SS_KEEP_TIMING
+
+ var/checkpoint = 0 //! Counter of data snapshots sent
+ var/step = 1 //! Current task in progress, for pausing/resuming
+
+/datum/controller/subsystem/influxstats/Initialize()
+ var/period = text2num(CONFIG_GET(number/influxdb_stats_period))
+ if(isnum(period))
+ wait = max(period * (1 SECONDS), 10 SECONDS)
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/influxstats/stat_entry(msg)
+ msg += "period=[wait] checkpoint=[checkpoint] step=[step]"
+ return ..()
+
+/datum/controller/subsystem/influxstats/fire(resumed)
+ if(!SSinfluxdriver.can_fire)
+ can_fire = FALSE
+ return
+
+ checkpoint++
+ while(step < 5) // Yes, you could make one SS per stats category, and have proper scheduling and variable periods, but...
+ switch(step++)
+ if(1) // Connected players statistics
+ run_player_statistics()
+ if(2) // Job occupations
+ if(SSticker.current_state == GAME_STATE_PLAYING)
+ run_job_statistics()
+ if(3) // Round-wide gameplay statistics held in entity
+ if(SSticker.current_state == GAME_STATE_PLAYING)
+ run_round_statistics()
+ if(4) // Handpicked Round-wide gameplay statistics
+ if(SSticker.current_state == GAME_STATE_PLAYING)
+ run_special_round_statistics()
+ if(MC_TICK_CHECK)
+ return
+
+ step = 1
+
+/datum/controller/subsystem/influxstats/proc/flatten_entity_list(list/data)
+ var/list/result = list()
+ for(var/key in data)
+ var/datum/entity/statistic/entry = data[key]
+ result[key] = entry.value
+ return result
+
+/datum/controller/subsystem/influxstats/proc/run_special_round_statistics()
+ for(var/hive_tag in GLOB.hive_datum)
+ var/datum/hive_status/hive = GLOB.hive_datum[hive_tag]
+ SSinfluxdriver.enqueue_stats("pooled_larva", list("hive" = hive.reporting_id), list("count" = hive.stored_larva))
+ var/burst_larvas = GLOB.larva_burst_by_hive[hive] || 0
+ SSinfluxdriver.enqueue_stats("burst_larva", list("hive" = hive.reporting_id), list("count" = burst_larvas))
+
+/datum/controller/subsystem/influxstats/proc/run_round_statistics()
+ var/datum/entity/statistic/round/stats = SSticker?.mode?.round_stats
+ if(!stats)
+ return // Sadge
+
+ SSinfluxdriver.enqueue_stats_crude("chestbursts", stats.total_larva_burst)
+ SSinfluxdriver.enqueue_stats_crude("hugged", stats.total_huggers_applied)
+ SSinfluxdriver.enqueue_stats_crude("friendlyfire", stats.total_friendly_fire_instances)
+
+ var/list/participants = flatten_entity_list(stats.participants)
+ if(length(participants))
+ SSinfluxdriver.enqueue_stats("participants", list(), participants)
+
+ var/list/total_deaths = flatten_entity_list(stats.total_deaths)
+ if(length(total_deaths))
+ SSinfluxdriver.enqueue_stats("deaths", list(), total_deaths)
+
+ SSinfluxdriver.enqueue_stats("shots", list(),
+ list("fired" = stats.total_projectiles_fired, "hits" = stats.total_projectiles_hit,
+ "hits_human" = stats.total_projectiles_hit_human, "hits_xeno" = stats.total_projectiles_hit_xeno)
+ )
+
+/datum/controller/subsystem/influxstats/proc/run_player_statistics()
+ var/staff_count = 0
+ var/mentor_count = 0
+ for(var/client/client in GLOB.admins)
+ if(CLIENT_IS_STAFF(client))
+ staff_count++
+ else if(CLIENT_HAS_RIGHTS(client, R_MENTOR))
+ mentor_count++
+ SSinfluxdriver.enqueue_stats("online", null, list("count" = length(GLOB.clients)))
+
+ var/list/adm = get_admin_counts()
+ var/present_admins = length(adm["present"])
+ var/afk_admins = length(adm["afk"])
+ SSinfluxdriver.enqueue_stats("online_staff", null, list("total" = staff_count, "mentors" = mentor_count, "present" = present_admins, "afk" = afk_admins))
+
+ // Grab ahelp stats
+ SSinfluxdriver.enqueue_stats("tickets", null, list(
+ "open" = length(GLOB.ahelp_tickets.active_tickets),
+ "closed" = length(GLOB.ahelp_tickets.closed_tickets),
+ "resolved" = length(GLOB.ahelp_tickets.resolved_tickets),
+ ))
+
+/datum/controller/subsystem/influxstats/proc/run_job_statistics()
+ var/list/team_job_stats = list()
+ var/list/squad_job_stats = ROLES_SQUAD_ALL.Copy()
+ for(var/squad in squad_job_stats)
+ squad_job_stats[squad] = list()
+
+ for(var/client/client in GLOB.clients)
+ var/team
+ var/mob/mob = client.mob
+ if(!mob || mob.statistic_exempt)
+ continue
+ var/area/area = get_area(mob)
+ if(!area || area.statistic_exempt)
+ continue
+ var/job = mob.job
+ if(isobserver(mob) || mob.stat == DEAD)
+ job = JOB_OBSERVER
+ team = "observers"
+ else if(!job)
+ continue
+ else if(mob.faction == FACTION_MARINE || mob.faction == FACTION_SURVIVOR)
+ team = "humans"
+ var/mob/living/carbon/human/employed_human = mob
+ if(istype(employed_human))
+ var/squad = employed_human.assigned_squad?.name
+ if(squad in squad_job_stats)
+ squad_job_stats[squad][job] = (squad_job_stats[squad][job] || 0) + 1
+ continue // Defer to squad stats instead
+ // else: So you're in the USCM and have a job but aren't an human? Tell me more Dr Jones...
+ else if(ishuman(mob))
+ team = "humans_others"
+ else if(isxeno(mob))
+ var/mob/living/xeno_enabled_mob = mob
+ var/datum/hive_status/hive = GLOB.hive_datum[xeno_enabled_mob.hivenumber]
+ if(!hive)
+ team = "xenos_others"
+ else
+ team = "xenos_[hive.reporting_id]"
+ else
+ team = "others"
+ LAZYINITLIST(team_job_stats[team])
+ if(!team_job_stats[team][job])
+ team_job_stats[team][job] = 0
+ team_job_stats[team][job] += 1
+
+ for(var/team in team_job_stats)
+ for(var/job in team_job_stats[team])
+ SSinfluxdriver.enqueue_stats("job_stats", list("team" = team, "job" = job), list("count" = team_job_stats[team][job]))
+
+ for(var/squad in squad_job_stats)
+ for(var/job in squad_job_stats[squad])
+ SSinfluxdriver.enqueue_stats("job_stats", list("team" = "humans", "job" = job, "squad" = squad), list("count" = squad_job_stats[squad][job]))
diff --git a/code/controllers/subsystem/minimap.dm b/code/controllers/subsystem/minimap.dm
index e365a9b60781..c3b2d36b8177 100644
--- a/code/controllers/subsystem/minimap.dm
+++ b/code/controllers/subsystem/minimap.dm
@@ -262,8 +262,12 @@ SUBSYSTEM_DEF(minimaps)
* removes an image from raw tracked lists, invoked by callback
*/
/datum/controller/subsystem/minimaps/proc/removeimage(image/blip, atom/target)
+ var/turf/turf_gotten = get_turf(target)
+ if(!turf_gotten)
+ return
+ var/z_level = turf_gotten.z
for(var/flag in GLOB.all_minimap_flags)
- minimaps_by_z["[target.z]"].images_raw["[flag]"] -= blip
+ minimaps_by_z["[z_level]"].images_raw["[flag]"] -= blip
blip.UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
removal_cbs -= target
@@ -391,18 +395,24 @@ SUBSYSTEM_DEF(minimaps)
owner.client.screen += map
minimap_displayed = !minimap_displayed
-/datum/action/minimap/give_to(mob/M)
+/datum/action/minimap/give_to(mob/target)
. = ..()
if(default_overwatch_level)
map = SSminimaps.fetch_minimap_object(default_overwatch_level, minimap_flags)
else
- RegisterSignal(M, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_owner_z_change))
- if(!SSminimaps.minimaps_by_z["[M.z]"] || !SSminimaps.minimaps_by_z["[M.z]"].hud_image)
+ RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_owner_z_change))
+
+ var/turf/turf_gotten = get_turf(target)
+ if(!turf_gotten)
+ return
+ var/z_level = turf_gotten.z
+
+ if(!SSminimaps.minimaps_by_z["[z_level]"] || !SSminimaps.minimaps_by_z["[z_level]"].hud_image)
return
- map = SSminimaps.fetch_minimap_object(M.z, minimap_flags)
+ map = SSminimaps.fetch_minimap_object(z_level, minimap_flags)
-/datum/action/minimap/remove_from(mob/M)
+/datum/action/minimap/remove_from(mob/target)
. = ..()
if(minimap_displayed)
owner?.client?.screen -= map
diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm
index 6deb27a6817b..0d218ba94da4 100644
--- a/code/datums/components/footstep.dm
+++ b/code/datums/components/footstep.dm
@@ -31,7 +31,7 @@
return
var/mob/living/LM = parent
- if(LM.buckled || LM.lying || LM.throwing || LM.is_ventcrawling)
+ if(LM.buckled || LM.throwing || LM.is_ventcrawling || LM.stat == DEAD)
return
if(LM.life_steps_total % steps)
diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm
new file mode 100644
index 000000000000..0c578b661517
--- /dev/null
+++ b/code/datums/components/weed_food.dm
@@ -0,0 +1,297 @@
+#define WEED_FOOD_DELAY 5 MINUTES
+#define WEED_FOOD_STATE_DELAY 1 MINUTES
+
+/atom/movable/vis_obj/weed_food
+ name = "weeds"
+ desc = "Weird black weeds in the shape of a body..."
+ gender = PLURAL
+ vis_flags = VIS_INHERIT_DIR|VIS_INHERIT_PLANE|VIS_INHERIT_LAYER
+ icon = 'icons/mob/xenos/weeds.dmi'
+ var/static/list/icon_states = list("human_1","human_2","human_3","human_4","human_5")
+ var/static/list/icon_states_flipped = list("human_1_f","human_2_f","human_3_f","human_4_f","human_5_f")
+ var/icon_state_idx = 0
+ var/timer_id = null
+ var/flipped = FALSE
+
+/atom/movable/vis_obj/weed_food/Initialize(mapload, is_flipped, ...)
+ flipped = is_flipped
+ timer_id = addtimer(CALLBACK(src, PROC_REF(on_animation_timer)), WEED_FOOD_STATE_DELAY, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_LOOP|TIMER_DELETE_ME)
+ on_animation_timer()
+ return ..()
+
+/// Timer callback for changing the icon_state
+/atom/movable/vis_obj/weed_food/proc/on_animation_timer()
+ icon_state_idx++
+ if(icon_state_idx > length(icon_states))
+ deltimer(timer_id)
+ timer_id = null
+ return
+ icon_state = flipped ? icon_states_flipped[icon_state_idx] : icon_states[icon_state_idx]
+
+/**
+ * A component that can be attached to a mob/living to be merged with weeds after a delay.
+ * Attempting to attach a new weed_food even if one already exists is equivalent to calling start().
+ *
+ * Attach this to any mob/living that is dead (death or initialized dead) and it should handle the rest.
+ */
+/datum/component/weed_food
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// Whether we are waiting on timer to merge
+ var/active = FALSE
+ /// Whether we are merged with weeds
+ var/merged = FALSE
+ /// The time we were unmerged (just to handle weeds upgrading)
+ var/unmerged_time
+ /// Any active timer for a pending merge
+ var/timer_id = null
+ /// The living mob that we are bound to
+ var/mob/living/parent_mob
+ /// The turf that our parent is on
+ var/turf/parent_turf
+ /// The obj that our parent is buckled to and we have registered a signal
+ var/obj/parent_buckle
+ /// The weeds that we are merging/merged with
+ var/obj/effect/alien/weeds/absorbing_weeds
+ /// The overlay image when merged
+ var/atom/movable/vis_obj/weed_food/weed_appearance
+
+/datum/component/weed_food/Initialize(...)
+ parent_mob = parent
+ //if(!istype(parent_mob))
+ //return COMPONENT_INCOMPATIBLE
+ if(!istype(parent_mob, /mob/living/carbon/human))
+ return COMPONENT_INCOMPATIBLE // TODO: At the moment we only support humans
+
+ parent_turf = get_turf(parent_mob)
+ if(parent_turf != parent_mob.loc)
+ parent_turf = null // if our location is actually a container, we want to be safe from weeds
+
+ start()
+
+/datum/component/weed_food/InheritComponent(datum/component/C, i_am_original)
+ start()
+
+/datum/component/weed_food/Destroy(force, silent)
+ . = ..()
+
+ unmerge_with_weeds()
+ QDEL_NULL(weed_appearance)
+ parent_mob = null
+ parent_turf = null
+
+/datum/component/weed_food/RegisterWithParent()
+ 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))
+ if(parent_turf)
+ RegisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH, PROC_REF(on_update))
+
+/datum/component/weed_food/UnregisterFromParent()
+ if(parent_mob)
+ UnregisterSignal(parent_mob, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_LIVING_REJUVENATED,
+ COMSIG_HUMAN_REVIVED,
+ COMSIG_HUMAN_SET_UNDEFIBBABLE,
+ ))
+ if(absorbing_weeds)
+ UnregisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING)
+ if(parent_turf)
+ UnregisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH)
+ if(parent_buckle)
+ UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+
+/// SIGNAL_HANDLER for COMSIG_MOVABLE_MOVED
+/datum/component/weed_food/proc/on_move()
+ SIGNAL_HANDLER
+
+ if(absorbing_weeds)
+ UnregisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING)
+ absorbing_weeds = null
+
+ if(parent_turf)
+ UnregisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH)
+ parent_turf = get_turf(parent_mob)
+ if(parent_turf != parent_mob.loc)
+ parent_turf = null // if our location is actually a container, we want to be safe from weeds
+ else
+ RegisterSignal(parent_turf, COMSIG_WEEDNODE_GROWTH, PROC_REF(on_update))
+
+ // We moved, restart or start the proccess
+ if(stop() || !merged)
+ start()
+ return
+
+ // If we somehow moved when we were merged, handle that
+ absorbing_weeds = parent_turf?.weeds
+ if(absorbing_weeds)
+ RegisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING, PROC_REF(on_weed_deletion))
+ return
+ unmerge_with_weeds()
+
+/// SIGNAL_HANDLER for COMSIG_LIVING_REJUVENATED and COMSIG_HUMAN_REVIVED
+/datum/component/weed_food/proc/on_rejuv()
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/// SIGNAL_HANDLER for COSMIG_OBJ_AFTER_BUCKLE
+/datum/component/weed_food/proc/on_after_buckle(obj/source, mob/buckled)
+ SIGNAL_HANDLER
+
+ if(buckled)
+ return
+ start() // We unbuckled, so lets try to start again
+
+/// SIGNAL_HANDLER for COMSIG_HUMAN_SET_UNDEFIBBABLE & COMSIG_WEEDNODE_GROWTH
+/datum/component/weed_food/proc/on_update()
+ SIGNAL_HANDLER
+
+ start()
+
+/// SIGNAL_HANDLER for COMSIG_PARENT_QDELETING of weeds
+/datum/component/weed_food/proc/on_weed_deletion()
+ SIGNAL_HANDLER
+
+ if(active)
+ stop()
+ return
+ if(merged)
+ unmerge_with_weeds()
+ return
+
+/**
+ * Try to start the process to turn into weeds
+ * Returns TRUE if started successfully
+ */
+/datum/component/weed_food/proc/start()
+ if(active)
+ return FALSE
+ if(merged)
+ return FALSE
+ if(QDELETED(parent_mob))
+ return FALSE
+
+ if(parent_mob.buckled)
+ if(parent_mob.buckled == parent_buckle)
+ return FALSE // Still buckled to the same thing
+ if(!istype(parent_mob.buckled, /obj/structure/bed/nest))
+ if(parent_buckle) // Still have a lingering reference somehow?
+ UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ parent_buckle = parent_mob.buckled
+ RegisterSignal(parent_mob.buckled, COSMIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
+ return FALSE
+ if(parent_buckle)
+ UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ parent_buckle = null
+
+ if(parent_mob.is_xeno_grabbable())
+ return FALSE
+ if(!(parent_mob.status_flags & PERMANENTLY_DEAD))
+ var/mob/living/carbon/human/parent_human = parent_mob
+ if(istype(parent_human) && !parent_human.undefibbable)
+ return FALSE
+ if(!parent_turf?.weeds)
+ return FALSE
+
+ if(unmerged_time == world.time)
+ return merge_with_weeds() // Weeds upgraded, re-merge now re-using the apperance
+ QDEL_NULL(weed_appearance)
+ absorbing_weeds = parent_turf.weeds
+ RegisterSignal(parent_turf.weeds, COMSIG_PARENT_QDELETING, PROC_REF(on_weed_deletion))
+
+ active = TRUE
+ timer_id = addtimer(CALLBACK(src, PROC_REF(merge_with_weeds)), WEED_FOOD_DELAY, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_DELETE_ME|TIMER_OVERRIDE)
+
+ return TRUE
+
+/**
+ * Try to stop the process turning into weeds
+ * Returns TRUE if stopped successfully (was active when called)
+ */
+/datum/component/weed_food/proc/stop()
+ if(!active)
+ return FALSE
+
+ active = FALSE
+ deltimer(timer_id)
+ timer_id = null
+
+ return TRUE
+
+/**
+ * Finish becomming one with the weeds
+ * Returns TRUE if merged successfully
+ */
+/datum/component/weed_food/proc/merge_with_weeds()
+ if(merged)
+ return FALSE
+ if(QDELETED(parent_mob))
+ return FALSE
+
+ if(absorbing_weeds) // Remove the signal that would call stop
+ UnregisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING)
+
+ if(parent_mob.buckled)
+ if(parent_mob.buckled == parent_buckle)
+ return FALSE // Still buckled to the same thing somehow?
+ if(!istype(parent_mob.buckled, /obj/structure/bed/nest))
+ if(parent_buckle) // Still have a lingering reference somehow?
+ UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ parent_buckle = parent_mob.buckled
+ RegisterSignal(parent_mob.buckled, COSMIG_OBJ_AFTER_BUCKLE, PROC_REF(on_after_buckle))
+ return FALSE
+ if(parent_buckle)
+ UnregisterSignal(parent_buckle, COSMIG_OBJ_AFTER_BUCKLE)
+ parent_buckle = null
+
+ absorbing_weeds = parent_turf?.weeds
+ if(!absorbing_weeds)
+ return FALSE
+ RegisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING, PROC_REF(on_weed_deletion))
+ // Technically we could have just left the signal alone, but both because of the posibility of other conditions preventing a merge or weeds somehow changing and on_move didn't catch it, this is less fragile
+
+ active = FALSE
+ merged = TRUE
+
+ parent_mob.density = FALSE
+ parent_mob.anchored = TRUE
+ parent_mob.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ parent_mob.plane = FLOOR_PLANE
+ parent_mob.remove_from_all_mob_huds()
+
+ if(!weed_appearance) // Make a new sprite if we aren't re-merging
+ var/is_flipped = parent_mob.transform.b == -1 // Technically we should check if d is 1 too, but corpses can only be rotated 90 or 270 (1/-1 or -1/1)
+ if(parent_mob.dir & WEST)
+ is_flipped = !is_flipped // The direction reversed the effect of the flip!
+ weed_appearance = new(null, is_flipped)
+ weed_appearance.color = absorbing_weeds.color
+ // TODO: For non-humans change the icon_state or something here
+ parent_mob.vis_contents += weed_appearance
+
+ return TRUE
+
+/**
+ * Undo the weedening
+ * Returns TRUE if unmerged successfully (always)
+ */
+/datum/component/weed_food/proc/unmerge_with_weeds()
+ merged = FALSE
+ unmerged_time = world.time
+
+ if(absorbing_weeds)
+ UnregisterSignal(absorbing_weeds, COMSIG_PARENT_QDELETING)
+ absorbing_weeds = null
+
+ parent_mob.anchored = FALSE
+ parent_mob.mouse_opacity = MOUSE_OPACITY_ICON
+ parent_mob.plane = GAME_PLANE
+ parent_mob.vis_contents -= weed_appearance
+
+ if(!QDELETED(parent_mob))
+ parent_mob.add_to_all_mob_huds()
+
+ return TRUE
+
+#undef WEED_FOOD_DELAY
+#undef WEED_FOOD_STATE_DELAY
diff --git a/code/datums/emergency_calls/cryo_marines.dm b/code/datums/emergency_calls/cryo_marines.dm
index e5992a62ecdd..eb914e198b37 100644
--- a/code/datums/emergency_calls/cryo_marines.dm
+++ b/code/datums/emergency_calls/cryo_marines.dm
@@ -3,7 +3,7 @@
//whiskey outpost extra marines
/datum/emergency_call/cryo_squad
name = "Marine Cryo Reinforcements (Squad)"
- mob_max = 15
+ mob_max = 10
mob_min = 1
probability = 0
objectives = "Assist the USCM forces"
@@ -13,10 +13,12 @@
shuttle_id = ""
var/leaders = 0
-/datum/emergency_call/cryo_squad/spawn_candidates(announce, override_spawn_loc)
+/datum/emergency_call/cryo_squad/spawn_candidates(announce, override_spawn_loc, announce_dispatch_message)
var/datum/squad/marine/cryo/cryo_squad = RoleAuthority.squads_by_type[/datum/squad/marine/cryo]
leaders = cryo_squad.num_leaders
- return ..()
+ . = ..()
+ if(length(members))
+ shipwide_ai_announcement("Successfully deployed [length(members)] Foxtrot marines.")
/datum/emergency_call/cryo_squad/create_member(datum/mind/M, turf/override_spawn_loc)
set waitfor = 0
diff --git a/code/datums/emergency_calls/cryo_marines_heavy.dm b/code/datums/emergency_calls/cryo_marines_heavy.dm
index 6a3636568856..70ce52443573 100644
--- a/code/datums/emergency_calls/cryo_marines_heavy.dm
+++ b/code/datums/emergency_calls/cryo_marines_heavy.dm
@@ -16,10 +16,12 @@
var/leaders = 0
-/datum/emergency_call/cryo_squad_equipped/spawn_candidates(announce, override_spawn_loc)
+/datum/emergency_call/cryo_squad_equipped/spawn_candidates(announce, override_spawn_loc, announce_dispatch_message)
var/datum/squad/marine/cryo/cryo_squad = RoleAuthority.squads_by_type[/datum/squad/marine/cryo]
leaders = cryo_squad.num_leaders
- return ..()
+ . = ..()
+ if(length(members))
+ shipwide_ai_announcement("Successfully deployed [length(members)] Foxtrot marines.")
/datum/emergency_call/cryo_squad_equipped/create_member(datum/mind/M, turf/override_spawn_loc)
set waitfor = 0
diff --git a/code/datums/skills.dm b/code/datums/skills.dm
index ef86b726a3c1..16a2a20a57fd 100644
--- a/code/datums/skills.dm
+++ b/code/datums/skills.dm
@@ -851,7 +851,7 @@ SYNTHETIC
/datum/skills/colonial_synthetic
name = SYNTH_COLONY
skills = list(
- SKILL_CQC = SKILL_CQC_SKILLED,
+ SKILL_CQC = SKILL_CQC_EXPERT,
SKILL_ENGINEER = SKILL_ENGINEER_ENGI,
SKILL_CONSTRUCTION = SKILL_CONSTRUCTION_ENGI,
SKILL_FIREARMS = SKILL_FIREARMS_EXPERT,
@@ -862,7 +862,7 @@ SYNTHETIC
SKILL_MELEE_WEAPONS = SKILL_MELEE_SUPER,
SKILL_PILOT = SKILL_PILOT_EXPERT,
SKILL_POLICE = SKILL_POLICE_SKILLED,
- SKILL_FIREMAN = SKILL_FIREMAN_TRAINED,
+ SKILL_FIREMAN = SKILL_FIREMAN_EXPERT,
SKILL_POWERLOADER = SKILL_POWERLOADER_MASTER,
SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
SKILL_JTAC = SKILL_JTAC_BEGINNER,
diff --git a/code/datums/statistics/entities/panel_stats.dm b/code/datums/statistics/entities/panel_stats.dm
index 44f95f2d91d9..d6e391e1731f 100644
--- a/code/datums/statistics/entities/panel_stats.dm
+++ b/code/datums/statistics/entities/panel_stats.dm
@@ -741,7 +741,6 @@
"total_projectiles_hit_xeno" = total_projectiles_hit_xeno,
"total_slashes" = total_slashes,
"total_friendly_fire_instances" = total_friendly_fire_instances,
- "total_friendly_fire_kills" = total_friendly_fire_kills,
"total_huggers_applied" = total_huggers_applied,
"total_larva_burst" = total_larva_burst,
"participants" = participants_list,
diff --git a/code/datums/statistics/entities/round_stats.dm b/code/datums/statistics/entities/round_stats.dm
index ec6aa4ac5bdc..0e1fb6e387db 100644
--- a/code/datums/statistics/entities/round_stats.dm
+++ b/code/datums/statistics/entities/round_stats.dm
@@ -20,7 +20,6 @@
var/total_projectiles_hit_human = 0
var/total_projectiles_hit_xeno = 0
var/total_friendly_fire_instances = 0
- var/total_friendly_fire_kills = 0
var/total_slashes = 0
// untracked data
@@ -360,7 +359,6 @@
stats += "Total shots fired: [total_projectiles_fired]\n"
stats += "Total friendly fire instances: [total_friendly_fire_instances]\n"
- stats += "Total friendly fire kills: [total_friendly_fire_kills]\n"
stats += "Marines remaining: [end_of_round_marines]\n"
stats += "Xenos remaining: [end_of_round_xenos]\n"
diff --git a/code/game/bioscans.dm b/code/game/bioscans.dm
index 55422ad3b878..5f07b307751a 100644
--- a/code/game/bioscans.dm
+++ b/code/game/bioscans.dm
@@ -94,15 +94,19 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new)
var/marine_planet_location_string = "[marine_planet_location ? ", including one in [marine_planet_location]." : "."]"
var/marine_ship_location_string = "[marine_ship_location ? ", including one in [marine_ship_location]." : "."]"
+ var/ghost_scan = SPAN_ALERT("[xenos_on_planet] xenos on planet, with [larva] larva.\n[xenos_on_ship] xenos on the ship.\n[marines_on_planet] humans on the planet.\n[marines_on_ship] humans on the ship.")
+ var/yautja_scan = SPAN_ALERT("[xenos_on_planet] serpents present in the hunting ground[xeno_planet_location_string], with [larva] larva.\n[xenos_on_ship] serpents present on the human ship[xeno_ship_location_string]\n[marines_on_planet] humans present in the hunting ground[marine_planet_location_string]\n[marines_on_ship] humans present on the human ship[marine_ship_location_string]")
+ log_game("BIOSCAN: A Yautja/Ghost bioscan has completed. [ghost_scan]")
+
//Announce the numbers to Yautja, they have good scanners
for(var/mob/living/carbon/human/yautja as anything in GLOB.yautja_mob_list)
to_chat(yautja, "
Bioscan complete
")
- to_chat(yautja, SPAN_ALERT("[xenos_on_planet] serpents present in the hunting ground[xeno_planet_location_string], with [larva] larva.\n[xenos_on_ship] serpents present on the human ship[xeno_ship_location_string]\n[marines_on_planet] humans present in the hunting ground[marine_planet_location_string]\n[marines_on_ship] humans present on the human ship[marine_ship_location_string]"))
+ to_chat(yautja, yautja_scan)
//Let the ghosts know what's up, they also get good numbers
for(var/mob/dead/observer/ghost as anything in GLOB.observer_list)
to_chat(ghost, "
Bioscan complete
")
- to_chat(ghost, SPAN_ALERT("[xenos_on_planet] xenos on planet, with [larva] larva.\n[xenos_on_ship] xenos on the ship.\n[marines_on_planet] humans on the planet.\n[marines_on_ship] humans on the ship."))
+ to_chat(ghost, ghost_scan)
/// This will do something after Project ARES.
@@ -117,15 +121,15 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new)
/// The announcement to all Humans. Slightly off for the planet and elsewhere, accurate for the ship.
/datum/bioscan_data/proc/ares_bioscan(forced = FALSE, variance = 2)
if(!forced && !can_ares_bioscan())
- message_admins("An ARES Bioscan has failed.")
+ message_admins("BIOSCAN: An ARES bioscan has failed.")
return
-
//Adjust the randomness there so everyone gets the same thing
var/fake_xenos_on_planet = max(0, xenos_on_planet + rand(-variance, variance))
-
var/name = "[MAIN_AI_SYSTEM] Bioscan Status"
var/input = "Bioscan complete.\n\nSensors indicate [xenos_on_ship_uncontained ? "[xenos_on_ship_uncontained]" : "no"] unknown lifeform signature[!xenos_on_ship_uncontained || xenos_on_ship_uncontained > 1 ? "s":""] present on the ship[xenos_on_ship_uncontained && xenos_ship_location ? ", including one in [xenos_ship_location]," : ""] and [fake_xenos_on_planet ? "approximately [fake_xenos_on_planet]" : "no"] signature[!fake_xenos_on_planet || fake_xenos_on_planet > 1 ? "s":""] located elsewhere[fake_xenos_on_planet && xenos_planet_location ? ", including one in [xenos_planet_location]":""]."
+ log_game("BIOSCAN: ARES bioscan completed. [input]")
+
var/datum/ares_link/link = GLOB.ares_link
link.log_ares_bioscan(name, input)
if(forced || (link.p_interface && !link.p_interface.inoperable()))
@@ -135,13 +139,18 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new)
/datum/bioscan_data/proc/qm_bioscan(variance = 2)
/// Adjust the randomness there so everyone gets the same thing
var/fake_marines_on_ship = max(0, marines_on_ship + rand(-variance, variance))
+ var/metalhive_hosts = "[fake_marines_on_ship ? "approximately [fake_marines_on_ship]":"no"]"
+ var/plural = "[!fake_marines_on_ship || fake_marines_on_ship > 1 ? "s":""]"
+ var/metalhive_location = "[fake_marines_on_ship && marine_ship_location?", including one in [marine_ship_location]," : ""]"
+ var/planet_hosts = "[marines_on_planet ? "[marines_on_planet]" : "none"]"
+ var/planet_location = "[marines_on_planet && marine_planet_location ? ", including one in [marine_planet_location]" : ""]"
+
+ var/title = SPAN_XENOANNOUNCE("The Queen Mother reaches into your mind from worlds away.")
+ var/content = SPAN_XENOANNOUNCE("To my children and their Queen. I sense [metalhive_hosts] host[plural] in the metal hive [metalhive_location] and [planet_hosts] scattered elsewhere[planet_location].")
+
+ log_game("BIOSCAN: Queen Mother bioscan completed. [content]")
/// Shout it at everyone
for(var/mob/current_mob as anything in GLOB.living_xeno_list)
current_mob << sound(get_sfx("queen"), wait = 0, volume = 50)
- to_chat(current_mob, SPAN_XENOANNOUNCE("The Queen Mother reaches into your mind from worlds away."))
- var/metalhive_hosts = "[fake_marines_on_ship ? "approximately [fake_marines_on_ship]":"no"]"
- var/plural = "[!fake_marines_on_ship || fake_marines_on_ship > 1 ? "s":""]"
- var/metalhive_location = "[fake_marines_on_ship&&marine_ship_location?", including one in [marine_ship_location],":""]"
- var/planet_hosts = "[marines_on_planet ? "[marines_on_planet]" : "none"]"
- var/planet_location = "[marines_on_planet && marine_planet_location ? ", including one in [marine_planet_location]" : ""]"
- to_chat(current_mob, SPAN_XENOANNOUNCE("To my children and their Queen. I sense [metalhive_hosts] host[plural] in the metal hive [metalhive_location] and [planet_hosts] scattered elsewhere[planet_location]."))
+ to_chat(current_mob, title)
+ to_chat(current_mob, content)
diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm
index 2f6dc6e38118..18b11dde030e 100644
--- a/code/game/gamemodes/cm_initialize.dm
+++ b/code/game/gamemodes/cm_initialize.dm
@@ -383,7 +383,7 @@ Additional game mode variables.
return FALSE
// We aren't in queue yet, lets teach them about the queue then
- candidate_observer.larva_queue_cached_message = SPAN_XENONOTICE("You are currently still awaiting assignment in the larva queue. Priority is given to players who have yet to play in the round, but otherwise the ordering is based on your time of death. When you have been dead long enough and are not inactive, you will periodically receive messages where you are in the queue relative to other currently valid xeno candidates. Note: Playing as a facehugger or in the thunderdome will not alter your time of death. This means you won't lose your relative place in queue if you step away, disconnect, play as a facehugger, or play in the thunderdome.")
+ candidate_observer.larva_queue_cached_message = SPAN_XENONOTICE("You are currently awaiting assignment in the larva queue. The ordering is based on your time of death or the time you joined. When you have been dead long enough and are not inactive, you will periodically receive messages where you are in the queue relative to other currently valid xeno candidates. Your current position will shift as others change their preferences or go inactive, but your relative position compared to all observers is the same. Note: Playing as a facehugger or in the thunderdome will not alter your time of death. This means you won't lose your relative place in queue if you step away, disconnect, play as a facehugger, or play in the thunderdome.")
to_chat(xeno_candidate, candidate_observer.larva_queue_cached_message)
return FALSE
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 7a10a3ffa1df..6ccb0b5b18f7 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -284,7 +284,7 @@
return
//Dismantle the frame.
- if(istype(O, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(O, TRAIT_TOOL_CROWBAR))
dismantle()
return
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index fd246d2d640a..bb6972a58ac3 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -51,7 +51,7 @@
to_chat(user, SPAN_NOTICE(" You screw the circuit board into place."))
state = 2
icon_state = "2"
- if(istype(P, /obj/item/tool/crowbar) && circuit)
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR) && circuit)
playsound(loc, 'sound/items/Crowbar.ogg', 25, 1)
to_chat(user, SPAN_NOTICE(" You remove the circuit board."))
state = 1
@@ -121,7 +121,7 @@
to_chat(usr, "Added [mmi].")
icon_state = "3b"
- if(istype(P, /obj/item/tool/crowbar) && brain)
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR) && brain)
playsound(loc, 'sound/items/Crowbar.ogg', 25, 1)
to_chat(user, SPAN_NOTICE(" You remove the brain."))
brain.forceMove(loc)
@@ -129,7 +129,7 @@
icon_state = "3"
if(4)
- if(istype(P, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR))
playsound(loc, 'sound/items/Crowbar.ogg', 25, 1)
to_chat(user, SPAN_NOTICE(" You remove the glass panel."))
state = 3
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index bd42b31ea573..07c960807205 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -52,7 +52,7 @@
to_chat(user, SPAN_NOTICE(" You screw the circuit board into place."))
src.state = 2
src.icon_state = "2"
- if(istype(P, /obj/item/tool/crowbar) && circuit)
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR) && circuit)
playsound(src.loc, 'sound/items/Crowbar.ogg', 25, 1)
to_chat(user, SPAN_NOTICE(" You remove the circuit board."))
src.state = 1
@@ -99,7 +99,7 @@
src.state = 4
src.icon_state = "4"
if(4)
- if(istype(P, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR))
playsound(src.loc, 'sound/items/Crowbar.ogg', 25, 1)
to_chat(user, SPAN_NOTICE(" You remove the glass panel."))
src.state = 3
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 7d82c5bd9a75..3c92bd33a681 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -469,9 +469,10 @@
if(!record) return
playsound(src.loc, 'sound/machines/fax.ogg', 15, 1)
sleep(40)
+ var/datum/asset/asset = get_asset_datum(/datum/asset/simple/paper)
var/obj/item/paper/P = new /obj/item/paper( src.loc )
P.name = text("Scan: [], []",record.fields["name"],worldtime2text())
- P.info += text("
Official Weyland-Yutani Document Scan Record
[]
\n
",record.fields["name"])
+ P.info += text("
Official Weyland-Yutani Document Scan Record
[]
\n
",record.fields["name"])
for(var/datum/data/record/R as anything in GLOB.data_core.medical)
if (R.fields["name"] == record.fields["name"])
if(R.fields["last_scan_time"] && R.fields["last_scan_result"])
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index 0288b7eb2426..357ef48fff37 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -114,7 +114,7 @@
A.amount = 5
if(CONSTRUCTION_STATE_FINISHED)
- if(istype(P, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR))
if(!skillcheck(user, SKILL_ENGINEER, required_dismantle_skill))
to_chat(user, SPAN_WARNING("You are not trained to dismantle machines..."))
return
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index 435976668577..afcc9686cff5 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -2,6 +2,7 @@
/obj/structure/machinery/cryo_cell
name = "cryo cell"
+ desc = "A donation from the old A.W. project, using cryogenic technology. It slowly heals whoever is inside the tube."
icon = 'icons/obj/structures/machinery/cryogenics2.dmi'
icon_state = "cell"
density = FALSE
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 6d96daf3152f..e9006a9f2fb4 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -167,7 +167,7 @@
return
//If it's emagged, crowbar can pry electronics out.
- if (src.operating == -1 && istype(I, /obj/item/tool/crowbar))
+ if (src.operating == -1 && HAS_TRAIT(I, TRAIT_TOOL_CROWBAR))
playsound(src.loc, 'sound/items/Crowbar.ogg', 25, 1)
user.visible_message("[user] removes the electronics from the windoor.", "You start to remove electronics from the windoor.")
if (do_after(user, 40, INTERRUPT_ALL, BUSY_ICON_BUILD))
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index e1c9c9a4e279..b538f55292c1 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -4,13 +4,16 @@
anchored = FALSE
density = FALSE
drag_delay = 1
+ base_pixel_x = 15
+ base_pixel_y = -2
var/mob/living/carbon/attached = null
var/mode = 1 // 1 is injecting, 0 is taking blood.
var/obj/item/reagent_container/beaker = null
+ var/datum/beam/current_beam
/obj/structure/machinery/iv_drip/update_icon()
- if(src.attached)
+ if(attached)
icon_state = "hooked"
else
icon_state = ""
@@ -35,8 +38,31 @@
filling.color = mix_color_from_reagents(reagents.reagent_list)
overlays += filling
+/obj/structure/machinery/iv_drip/proc/update_beam()
+ if(current_beam)
+ QDEL_NULL(current_beam)
+ else if(!QDELETED(src) && attached)
+ current_beam = beam(attached, "iv_tube")
+
+/obj/structure/machinery/iv_drip/power_change()
+ . = ..()
+ if(stat & NOPOWER && attached)
+ visible_message("\The [src] retracts its IV tube and shuts down.")
+ attached.active_transfusions -= src
+ attached = null
+ update_beam()
+ update_icon()
+
+/obj/structure/machinery/iv_drip/Destroy()
+ attached?.active_transfusions -= src
+ update_beam()
+ . = ..()
+
/obj/structure/machinery/iv_drip/MouseDrop(over_object, src_location, over_location)
..()
+ if(inoperable())
+ visible_message("\The [src] is not powered.")
+ return
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
@@ -48,6 +74,7 @@
"You detach \the [src] from \the [attached].")
attached.active_transfusions -= src
attached = null
+ update_beam()
update_icon()
stop_processing()
return
@@ -57,6 +84,7 @@
"You attach \the [src] to \the [over_object].")
attached = over_object
attached.active_transfusions += src
+ update_beam()
update_icon()
start_processing()
@@ -81,6 +109,7 @@
log_admin("[key_name(user)] put a [beaker] into [src], containing [reagentnames] at ([src.loc.x],[src.loc.y],[src.loc.z]).")
to_chat(user, "You attach \the [W] to \the [src].")
+ update_beam()
update_icon()
return
else
@@ -97,6 +126,7 @@
attached.emote("scream")
attached.active_transfusions -= src
attached = null
+ update_beam()
update_icon()
stop_processing()
return
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index cded8a1148c7..a370356ad60b 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -62,7 +62,7 @@
stat &= ~BROKEN // the machine's not borked anymore!
else
to_chat(user, SPAN_WARNING("You need five coils of wire for this."))
- if(istype(P, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(P, TRAIT_TOOL_CROWBAR))
to_chat(user, "You begin prying out the circuit board other components...")
playsound(src.loc, 'sound/items/Crowbar.ogg', 25, 1)
if(do_after(user, 60 * user.get_skill_duration_multiplier(SKILL_ENGINEER), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
diff --git a/code/game/machinery/vending/vendor_types/crew/synthetic.dm b/code/game/machinery/vending/vendor_types/crew/synthetic.dm
index 1d8e5e289066..be6939a6f9dd 100644
--- a/code/game/machinery/vending/vendor_types/crew/synthetic.dm
+++ b/code/game/machinery/vending/vendor_types/crew/synthetic.dm
@@ -110,7 +110,6 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth, list(
list("HELMET (CHOOSE 1)", 0, null, null, null),
list("Expedition Cap", 0, /obj/item/clothing/head/cmcap/flap, MARINE_CAN_BUY_HELMET, VENDOR_ITEM_REGULAR),
list("Hard Hat, Orange", 0, /obj/item/clothing/head/hardhat/orange, MARINE_CAN_BUY_HELMET, VENDOR_ITEM_REGULAR),
- list("Surgical Cap, Green", 0, /obj/item/clothing/head/surgery/green, MARINE_CAN_BUY_HELMET, VENDOR_ITEM_REGULAR),
list("Welding Helmet", 0, /obj/item/clothing/head/welding, MARINE_CAN_BUY_HELMET, VENDOR_ITEM_REGULAR),
list("SUIT (CHOOSE 1)", 0, null, null, null),
@@ -181,13 +180,19 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("Medical Scrubs, White", 12, /obj/item/clothing/under/rank/medical, null, VENDOR_ITEM_REGULAR),
list("USCM Service Uniform", 12, /obj/item/clothing/under/marine/officer/bridge, null, VENDOR_ITEM_REGULAR),
list("USCM Flightsuit", 12, /obj/item/clothing/under/rank/synthetic/flight, null, VENDOR_ITEM_REGULAR),
- list("Engineer Uniform", 12, /obj/item/clothing/under/marine/officer/engi, null, VENDOR_ITEM_REGULAR),
+ list("USCM Engineer Uniform", 12, /obj/item/clothing/under/marine/officer/engi, null, VENDOR_ITEM_REGULAR),
list("White T-Shirt and Brown Jeans", 12, /obj/item/clothing/under/tshirt/w_br, null, VENDOR_ITEM_REGULAR),
list("Gray T-Shirt and Blue Jeans", 12, /obj/item/clothing/under/tshirt/gray_blu, null, VENDOR_ITEM_REGULAR),
list("Red T-Shirt and Black Jeans", 12, /obj/item/clothing/under/tshirt/r_bla, null, VENDOR_ITEM_REGULAR),
- list("Gray Utilities", 12, /obj/item/clothing/under/colonist/ua_civvies, null, VENDOR_ITEM_REGULAR),
- list("Brown Utilities", 12, /obj/item/clothing/under/colonist/wy_davisone, null, VENDOR_ITEM_REGULAR),
- list("Steward Utilities", 12, /obj/item/clothing/under/colonist/wy_joliet_shopsteward, null, VENDOR_ITEM_REGULAR),
+ list("Frontier Jumpsuit", 12, /obj/item/clothing/under/rank/synthetic/frontier, null, VENDOR_ITEM_REGULAR),
+ list("UA Grey Jumpsuit", 12, /obj/item/clothing/under/colonist/ua_civvies, null, VENDOR_ITEM_REGULAR),
+ list("UA Brown Jumpsuit", 12, /obj/item/clothing/under/colonist/wy_davisone, null, VENDOR_ITEM_REGULAR),
+ list("UA Green Utility Uniform", 12, /obj/item/clothing/under/rank/synthetic/utility, null, VENDOR_ITEM_REGULAR),
+ list("Grey Utilities", 12, /obj/item/clothing/under/rank/synthetic/utility/yellow, null, VENDOR_ITEM_REGULAR),
+ list("Grey Utilities and Blue Jeans", 12, /obj/item/clothing/under/rank/synthetic/utility/red, null, VENDOR_ITEM_REGULAR),
+ list("Blue Utilities and Brown Jeans", 12, /obj/item/clothing/under/rank/synthetic/utility/blue, null, VENDOR_ITEM_REGULAR),
+ list("Steward Clothes", 12, /obj/item/clothing/under/colonist/wy_joliet_shopsteward, null, VENDOR_ITEM_REGULAR),
+ list("Red Dress Skirt", 12, /obj/item/clothing/under/blackskirt, null, VENDOR_ITEM_REGULAR),
list("Working Joe Uniform", 36, /obj/item/clothing/under/rank/synthetic/joe, null, VENDOR_ITEM_REGULAR),
list("GLASSES", 0, null, null, null),
@@ -208,6 +213,7 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("Shoes, Red", 12, /obj/item/clothing/shoes/red, null, VENDOR_ITEM_REGULAR),
list("Shoes, White", 12, /obj/item/clothing/shoes/white, null, VENDOR_ITEM_REGULAR),
list("Shoes, Yellow", 12, /obj/item/clothing/shoes/yellow, null, VENDOR_ITEM_REGULAR),
+ list("Shoes, Seegson", 24, /obj/item/clothing/shoes/dress, null, VENDOR_ITEM_REGULAR),
list("HELMET", 0, null, null, null),
list("Beanie", 12, /obj/item/clothing/head/beanie, null, VENDOR_ITEM_REGULAR),
@@ -216,31 +222,32 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("Beret, Red", 12, /obj/item/clothing/head/beret/cm/red, null, VENDOR_ITEM_REGULAR),
list("Beret, Standard", 12, /obj/item/clothing/head/beret/cm, null, VENDOR_ITEM_REGULAR),
list("Beret, Tan", 12, /obj/item/clothing/head/beret/cm/tan, null, VENDOR_ITEM_REGULAR),
- list("Cap", 12, /obj/item/clothing/head/cmcap, null, VENDOR_ITEM_REGULAR),
list("Surgical Cap, Blue", 12, /obj/item/clothing/head/surgery/blue, null, VENDOR_ITEM_REGULAR),
list("Surgical Cap, Blue", 12, /obj/item/clothing/head/surgery/purple, null, VENDOR_ITEM_REGULAR),
list("Surgical Cap, Green", 12, /obj/item/clothing/head/surgery/green, null, VENDOR_ITEM_REGULAR),
list("Ushanka", 12, /obj/item/clothing/head/ushanka, null, VENDOR_ITEM_REGULAR),
+ list("Cap", 12, /obj/item/clothing/head/cmcap, null, VENDOR_ITEM_REGULAR),
list("MP Cap", 12, /obj/item/clothing/head/beret/marine/mp/mpcap, null, VENDOR_ITEM_REGULAR),
list("RO Cap", 12, /obj/item/clothing/head/cmcap/req, null, VENDOR_ITEM_REGULAR),
list("Officer Cap", 12, /obj/item/clothing/head/cmcap/ro, null, VENDOR_ITEM_REGULAR),
list("SUIT", 0, null, null, null),
- list("Brown Bomber Jacket", 12, /obj/item/clothing/suit/storage/bomber, null, VENDOR_ITEM_REGULAR),
- list("Black Bomber Jacket", 12, /obj/item/clothing/suit/storage/bomber/alt, null, VENDOR_ITEM_REGULAR),
- list("External webbing", 12, /obj/item/clothing/suit/storage/webbing, null, VENDOR_ITEM_REGULAR),
- list("Orange Hazard Vest", 12, /obj/item/clothing/suit/storage/hazardvest, null, VENDOR_ITEM_REGULAR),
- list("Blue Hazard Vest", 12, /obj/item/clothing/suit/storage/hazardvest/blue, null, VENDOR_ITEM_REGULAR),
- list("Yellow Hazard Vest", 12, /obj/item/clothing/suit/storage/hazardvest/yellow, null, VENDOR_ITEM_REGULAR),
- list("Black Hazard Vest", 12, /obj/item/clothing/suit/storage/hazardvest/black, null, VENDOR_ITEM_REGULAR),
+ list("Bomber Jacket, Brown", 12, /obj/item/clothing/suit/storage/bomber, null, VENDOR_ITEM_REGULAR),
+ list("Bomber Jacket, Black", 12, /obj/item/clothing/suit/storage/bomber/alt, null, VENDOR_ITEM_REGULAR),
+ list("External Webbing", 12, /obj/item/clothing/suit/storage/webbing, null, VENDOR_ITEM_REGULAR),
+ list("Utility Vest", 12, /obj/item/clothing/suit/storage/utility_vest, null, VENDOR_ITEM_REGULAR),
+ list("Hazard Vest(Orange)", 12, /obj/item/clothing/suit/storage/hazardvest, null, VENDOR_ITEM_REGULAR),
+ list("Hazard Vest(Blue)", 12, /obj/item/clothing/suit/storage/hazardvest/blue, null, VENDOR_ITEM_REGULAR),
+ list("Hazard Vest(Yellow)", 12, /obj/item/clothing/suit/storage/hazardvest/yellow, null, VENDOR_ITEM_REGULAR),
+ list("Hazard Vest(Black)", 12, /obj/item/clothing/suit/storage/hazardvest/black, null, VENDOR_ITEM_REGULAR),
list("Synthetic's Snow Suit", 12, /obj/item/clothing/suit/storage/snow_suit/synth, null, VENDOR_ITEM_REGULAR),
list("USCM Service Jacket", 12, /obj/item/clothing/suit/storage/jacket/marine/service, null, VENDOR_ITEM_REGULAR),
list("USCM MP Service Jacket", 12, /obj/item/clothing/suit/storage/jacket/marine/service/mp, null, VENDOR_ITEM_REGULAR),
- list("Brown Windbreaker", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_brown, null, VENDOR_ITEM_REGULAR),
- list("Gray Windbreaker", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_gray, null, VENDOR_ITEM_REGULAR),
- list("Green Windbreaker", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_green, null, VENDOR_ITEM_REGULAR),
- list("First Responder Windbreaker", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_fr, null, VENDOR_ITEM_REGULAR),
- list("Exploration Windbreaker", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_covenant, null, VENDOR_ITEM_REGULAR),
+ list("Windbreaker, Brown", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_brown, null, VENDOR_ITEM_REGULAR),
+ list("Windbreaker, Grey", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_gray, null, VENDOR_ITEM_REGULAR),
+ list("Windbreaker, Green", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_green, null, VENDOR_ITEM_REGULAR),
+ list("Windbreaker, First Responder", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_fr, null, VENDOR_ITEM_REGULAR),
+ list("Windbreaker, Exploration", 12, /obj/item/clothing/suit/storage/windbreaker/windbreaker_covenant, null, VENDOR_ITEM_REGULAR),
list("Labcoat", 12, /obj/item/clothing/suit/storage/labcoat, null, VENDOR_ITEM_REGULAR),
list("Labcoat, Researcher", 12, /obj/item/clothing/suit/storage/labcoat/researcher, null, VENDOR_ITEM_REGULAR),
list("RO Jacket", 12, /obj/item/clothing/suit/storage/RO, null, VENDOR_ITEM_REGULAR),
@@ -248,9 +255,8 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("BACKPACK", 0, null, null, null),
list("Backpack, Industrial", 12, /obj/item/storage/backpack/industrial, null, VENDOR_ITEM_REGULAR),
- list("Backpack, USCM IMB", 12, /obj/item/storage/backpack/marine, null, VENDOR_ITEM_REGULAR),
list("Backpack, USCM Medical", 12, /obj/item/storage/backpack/marine/medic, null, VENDOR_ITEM_REGULAR),
- list("Backpack, USCM Technician", 12, /obj/item/storage/backpack/marine/tech, null, VENDOR_ITEM_REGULAR),
+ list("Chestrig, Technician", 12, /obj/item/storage/backpack/marine/satchel/tech, null, VENDOR_ITEM_REGULAR),
list("Satchel, USCM", 12, /obj/item/storage/backpack/marine/satchel, null, VENDOR_ITEM_REGULAR),
list("Satchel, Leather", 12, /obj/item/storage/backpack/satchel, null, VENDOR_ITEM_REGULAR),
list("Satchel, Medical", 12, /obj/item/storage/backpack/satchel/med, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/medical.dm b/code/game/machinery/vending/vendor_types/medical.dm
index 5815f60b2e0c..70ac7701973b 100644
--- a/code/game/machinery/vending/vendor_types/medical.dm
+++ b/code/game/machinery/vending/vendor_types/medical.dm
@@ -272,7 +272,7 @@
/obj/structure/machinery/cm_vending/sorted/medical/blood
name = "\improper MM Blood Dispenser"
- desc = "Marine Med brand Blood Pack Dispensary"
+ desc = "The Marine Med Brand Blood Pack Dispensary is the premier, top-of-the-line blood dispenser of 2105! Get yours today!" //Don't update this year, the joke is it's old.
icon_state = "blood"
wrenchable = TRUE
hackable = TRUE
diff --git a/code/game/objects/effects/effect_system/foam.dm b/code/game/objects/effects/effect_system/foam.dm
index d0a1fa132b06..a7647dbd4489 100644
--- a/code/game/objects/effects/effect_system/foam.dm
+++ b/code/game/objects/effects/effect_system/foam.dm
@@ -28,7 +28,7 @@
metal = ismetal
playsound(src, 'sound/effects/bubbles2.ogg', 25, 1, 5)
addtimer(CALLBACK(src, PROC_REF(foam_react)), 3 + metal*3)
- addtimer(CALLBACK(src, PROC_REF(foam_metal_final_react)), 120)
+ addtimer(CALLBACK(src, PROC_REF(foam_metal_final_react)), 40)
/obj/effect/particle_effect/foam/proc/foam_react()
process()
diff --git a/code/game/objects/items/devices/motion_detector.dm b/code/game/objects/items/devices/motion_detector.dm
index 01858ed486d0..ade74531bc91 100644
--- a/code/game/objects/items/devices/motion_detector.dm
+++ b/code/game/objects/items/devices/motion_detector.dm
@@ -22,6 +22,7 @@
item_state = "motion_detector"
flags_atom = FPRINT| CONDUCT
flags_equip_slot = SLOT_WAIST
+ inherent_traits = list(TRAIT_ITEM_NOT_IMPLANTABLE)
var/list/blip_pool = list()
var/detector_range = 14
var/detector_mode = MOTION_DETECTOR_LONG
@@ -309,7 +310,7 @@
name = "hacked motion detector"
desc = "A device that usually picks up non-USCM signals, but this one's been hacked to detect all non-freelancer movement instead. Fight fire with fire!"
iff_signal = FACTION_MERCENARY
-
+
/obj/item/device/motiondetector/hacked/pmc
name = "corporate motion detector"
desc = "A device that usually picks up non-USCM signals, but this one's been reprogrammed to detect all non-PMC movement instead. Very corporate."
diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm
index 0f010d689e74..6293abb67339 100644
--- a/code/game/objects/items/devices/radio/encryptionkey.dm
+++ b/code/game/objects/items/devices/radio/encryptionkey.dm
@@ -49,7 +49,7 @@
name = "AI Integrated Encryption Key"
desc = "Integrated encryption key"
icon_state = "cap_key"
- channels = list(RADIO_CHANNEL_ALMAYER = TRUE, RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_ALMAYER = TRUE, RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
/obj/item/device/encryptionkey/sentry_laptop
name = "Sentry Network Status Encryption Key"
@@ -61,12 +61,12 @@
/obj/item/device/encryptionkey/cmpcom/cdrcom
name = "\improper Marine Senior Command Radio Encryption Key"
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
/obj/item/device/encryptionkey/mcom
name = "\improper Marine Command Radio Encryption Key"
icon_state = "cap_key"
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
// MARINE ENGINEERING
@@ -102,7 +102,7 @@
/obj/item/device/encryptionkey/mmpo
name = "\improper Military Police Radio Encryption Key"
icon_state = "sec_key"
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE,)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE,)
/obj/item/device/encryptionkey/sec
name = "Security Radio Encryption Key"
@@ -130,7 +130,7 @@
/obj/item/device/encryptionkey/cmpcom/synth
name = "\improper Marine Synth Radio Encryption Key"
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
/obj/item/device/encryptionkey/mcom/cl
name = "\improper Corporate Liaison radio encryption key"
@@ -163,7 +163,7 @@
channels = list(RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_COMMAND = FALSE)
/obj/item/device/encryptionkey/mcom/ai //AI only.
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
// MARINE SQUADS
@@ -296,7 +296,7 @@
/obj/item/device/encryptionkey/highcom
name = "\improper USCM High Command Radio Encryption Key"
icon_state = "binary_key"
- channels = list(RADIO_CHANNEL_HIGHCOM = TRUE, SQUAD_SOF = TRUE, RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = FALSE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
+ channels = list(RADIO_CHANNEL_HIGHCOM = TRUE, SQUAD_SOF = TRUE, RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MP = TRUE, SQUAD_MARINE_1 = TRUE, SQUAD_MARINE_2 = TRUE, SQUAD_MARINE_3 = TRUE, SQUAD_MARINE_4 = TRUE, SQUAD_MARINE_5 = TRUE, SQUAD_MARINE_CRYO = TRUE, RADIO_CHANNEL_ENGI = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_REQ = TRUE, RADIO_CHANNEL_JTAC = TRUE, RADIO_CHANNEL_INTEL = TRUE)
/obj/item/device/encryptionkey/contractor
name = "\improper Vanguard's Arrow Incorporated Radio Encryption Key"
@@ -306,7 +306,7 @@
/obj/item/device/encryptionkey/cmb
name = "\improper Colonial Marshal Bureau Radio Encryption Key"
icon_state = "cmb_key"
- channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_INTEL = TRUE, RADIO_CHANNEL_ALMAYER = TRUE)
+ channels = list(RADIO_CHANNEL_COMMAND = TRUE, RADIO_CHANNEL_MEDSCI = TRUE, RADIO_CHANNEL_INTEL = TRUE, RADIO_CHANNEL_ALMAYER = TRUE, RADIO_CHANNEL_COLONY = TRUE)
/// Used by the Mortar Crew in WO game mode - intently has no squad radio access
/obj/item/device/encryptionkey/mortar
name = "\improper Mortar Crew Radio Encryption Key"
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index 631ef8fa0e2c..e8dcdac34222 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -961,20 +961,23 @@
//CMB Headsets
/obj/item/device/radio/headset/distress/CMB
name = "\improper CMB Earpiece"
- desc = "A sleek headset used by The Colonial Marshal Bureau, crafted in Sol. Low profile and comfortable. No one is above the law. Featured channels include: ; - CMB, :g - public, :v - marine command, :m - medbay, :t - intel."
+ desc = "A sleek headset used by The Colonial Marshal Bureau, crafted in Sol. Low profile and comfortable. No one is above the law. Featured channels include: ; - CMB, :o - Colony, :g - public, :v - marine command, :m - medbay, :t - intel."
frequency = CMB_FREQ
icon_state = "cmb_headset"
initial_keys = list(/obj/item/device/encryptionkey/cmb)
has_hud = TRUE
hud_type = MOB_HUD_FACTION_USCM
+/obj/item/device/radio/headset/distress/CMB/limited
+ name = "\improper Damaged CMB Earpiece"
+ desc = "A sleek headset used by The Colonial Marshal Bureau, crafted in Sol. Low profile and comfortable. No one is above the law. This one is damaged, so the channels are: ; - CMB, :o - Colony."
+ initial_keys = list(/obj/item/device/encryptionkey/colony)
+
/obj/item/device/radio/headset/distress/CMB/ICC
name = "\improper ICC Liaison Headset"
- desc = "An expensive headset used by The Interstellar Commerce Commission. This one in particular has a liaison chip with the CMB. Featured channels include: ; - CMB, :g - public, :v - marine command, :m - medbay, :t - intel, :y - Weyland-Yutani."
- frequency = CMB_FREQ
+ desc = "An expensive headset used by The Interstellar Commerce Commission. This one in particular has a liaison chip with the CMB. Featured channels include: ; - CMB, :o - Colony, :g - public, :v - marine command, :m - medbay, :t - intel, :y - Weyland-Yutani."
icon_state = "wy_headset"
initial_keys = list(/obj/item/device/encryptionkey/WY, /obj/item/device/encryptionkey/cmb)
- has_hud = TRUE
/obj/item/device/radio/headset/almayer/highcom
name = "USCM High Command headset"
diff --git a/code/game/objects/items/explosives/mine.dm b/code/game/objects/items/explosives/mine.dm
index 6b8c9bccd299..742a5f314c4a 100644
--- a/code/game/objects/items/explosives/mine.dm
+++ b/code/game/objects/items/explosives/mine.dm
@@ -198,6 +198,8 @@
return
if(L.get_target_lock(iff_signal) || isrobot(L))
return
+ if(HAS_TRAIT(L, TRAIT_ABILITY_BURROWED))
+ return
L.visible_message(SPAN_DANGER("[icon2html(src, viewers(src))] The [name] clicks as [L] moves in front of it."), \
SPAN_DANGER("[icon2html(src, L)] The [name] clicks as you move in front of it."), \
SPAN_DANGER("You hear a click."))
diff --git a/code/game/objects/items/reagent_containers/blood_pack.dm b/code/game/objects/items/reagent_containers/blood_pack.dm
index 450cdde2fa00..8e29a26c2ecd 100644
--- a/code/game/objects/items/reagent_containers/blood_pack.dm
+++ b/code/game/objects/items/reagent_containers/blood_pack.dm
@@ -13,7 +13,9 @@
var/mode = BLOOD_BAG_INJECTING
var/mob/living/carbon/human/connected_to
+ var/mob/living/carbon/human/connected_from
var/blood_type = null
+ var/datum/beam/current_beam
/obj/item/reagent_container/blood/Initialize()
. = ..()
@@ -32,6 +34,12 @@
if(10 to 50) icon_state = "half"
if(51 to INFINITY) icon_state = "full"
+/obj/item/reagent_container/blood/proc/update_beam()
+ if(current_beam)
+ QDEL_NULL(current_beam)
+ else if(connected_from && connected_to)
+ current_beam = connected_from.beam(connected_to, "iv_tube")
+
/obj/item/reagent_container/blood/attack(mob/attacked_mob, mob/user)
. = ..()
@@ -44,7 +52,10 @@
user.visible_message("[user] detaches [src] from [connected_to].", \
"You detach [src] from [connected_to].")
connected_to.active_transfusions -= src
+ connected_to.base_pixel_x = 0
connected_to = null
+ connected_from = null
+ update_beam()
return
if(!skillcheck(user, SKILL_SURGERY, SKILL_SURGERY_NOVICE))
@@ -60,10 +71,13 @@
if(istype(attacked_mob, /mob/living/carbon/human))
connected_to = attacked_mob
+ connected_from = user
connected_to.active_transfusions += src
+ connected_to.base_pixel_x = 5
START_PROCESSING(SSobj, src)
user.visible_message("[user] attaches \the [src] to [connected_to].", \
"You attach \the [src] to [connected_to].")
+ update_beam()
/obj/item/reagent_container/blood/process()
//if we're not connected to anything stop doing stuff
@@ -106,6 +120,10 @@
connected_to.take_blood(src, amount)
+/obj/item/reagent_container/blood/dropped()
+ ..()
+ bad_disconnect()
+
///Used to standardize effects of a blood bag disconnecting improperly
/obj/item/reagent_container/blood/proc/bad_disconnect()
if(!connected_to)
@@ -116,7 +134,10 @@
if(connected_to.pain.feels_pain)
connected_to.emote("scream")
connected_to.active_transfusions -= src
+ connected_to.base_pixel_x = 0
connected_to = null
+ connected_from = null
+ update_beam()
/obj/item/reagent_container/blood/verb/toggle_mode()
set category = "Object"
diff --git a/code/game/objects/items/reagent_containers/food/snacks.dm b/code/game/objects/items/reagent_containers/food/snacks.dm
index 7dae94bfe4eb..c0e11dac8eb3 100644
--- a/code/game/objects/items/reagent_containers/food/snacks.dm
+++ b/code/game/objects/items/reagent_containers/food/snacks.dm
@@ -66,6 +66,10 @@
if(issynth(C))
fullness = 200 //Synths never get full
+ if(HAS_TRAIT(M, TRAIT_CANNOT_EAT)) //Do not feed the Working Joes
+ to_chat(user, SPAN_DANGER("[user == M ? "You are" : "[M] is"] unable to eat!"))
+ return
+
if(fullness > 540)
C.overeat_cooldown = world.time + OVEREAT_TIME
diff --git a/code/game/objects/items/tools/flame_tools.dm b/code/game/objects/items/tools/flame_tools.dm
index db35d3300ae7..6ebd8ee5982e 100644
--- a/code/game/objects/items/tools/flame_tools.dm
+++ b/code/game/objects/items/tools/flame_tools.dm
@@ -170,13 +170,20 @@ CIGARETTE PACKETS ARE IN FANCY.DM
damtype = "brute"
icon_state = "[initial(icon_state)]_burnt"
item_state = "cigoff"
- if(user && loc != user)
- user.SetLuminosity(0, FALSE, src)
SetLuminosity(0)
name = burnt_name
desc = "A match. This one has seen better days."
STOP_PROCESSING(SSobj, src)
+ if(user)
+ user.SetLuminosity(0, FALSE, src)
+ return
+
+ if(ismob(loc))
+ user = loc
+ user.SetLuminosity(0, FALSE, src)
+ return
+
/obj/item/tool/match/paper
name = "paper match"
desc = "A simple match stick, used for lighting fine smokables."
diff --git a/code/game/objects/items/tools/maintenance_tools.dm b/code/game/objects/items/tools/maintenance_tools.dm
index 25bcefc1cc34..2560c5ff91e8 100644
--- a/code/game/objects/items/tools/maintenance_tools.dm
+++ b/code/game/objects/items/tools/maintenance_tools.dm
@@ -557,8 +557,6 @@
if(attacked_door.locked) //Bolted
to_chat(user, SPAN_DANGER("You can't pry open [attacked_door] while it is bolted shut."))
return
- if(!attacked_door.arePowerSystemsOn()) //Opens like normal if unpowered
- return FALSE
if(requires_superstrength_pry)
if(!HAS_TRAIT(user, TRAIT_SUPER_STRONG)) //basically IS_PRY_CAPABLE_CROWBAR
@@ -645,7 +643,7 @@
resin_door.Open()
return
- if(istype(attacked_obj, /turf/open/floor))
+ else if(istype(attacked_obj, /turf/open/floor))
var/turf/open/floor/flooring = attacked_obj
if(crowbar_mode && user.a_intent == INTENT_HELP) //Only pry flooring on help intent
diff --git a/code/game/objects/items/tools/misc_tools.dm b/code/game/objects/items/tools/misc_tools.dm
index 727f6c5c7677..1bcebd9f1ea3 100644
--- a/code/game/objects/items/tools/misc_tools.dm
+++ b/code/game/objects/items/tools/misc_tools.dm
@@ -275,6 +275,25 @@
desc = "It's an invisible pen marker."
pen_colour = "white"
+/obj/item/tool/pen/fountain
+ desc = "A luxurious fountain pen, embossed with gold accents. Its intricate mechanics allow the user to switch between various ink colors with a simple twist."
+ name = "fountain pen"
+ icon_state = "fountain_pen"
+ item_state = "fountain_pen"
+ matter = list("metal" = 20, "gold" = 10)
+ var/static/list/colour_list = list("red", "blue", "green", "yellow", "purple", "pink", "brown", "black", "orange") // Can add more colors as required
+ var/current_colour_index = 1
+
+/obj/item/tool/pen/fountain/attack_self(mob/living/carbon/human/user)
+ if(on)
+ current_colour_index = (current_colour_index % length(colour_list)) + 1
+ pen_colour = colour_list[current_colour_index]
+ balloon_alert(user,"you twist the pen and change the ink color to [pen_colour].")
+ if(clicky)
+ playsound(user.loc, 'sound/items/pen_click_on.ogg', 100, 1, 5)
+ update_pen_state()
+ else
+ ..()
/obj/item/tool/pen/attack(mob/M as mob, mob/user as mob)
if(!ismob(M))
diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm
index ec7ee0b173da..cdab7db87ed7 100644
--- a/code/game/objects/items/weapons/swords_axes_etc.dm
+++ b/code/game/objects/items/weapons/swords_axes_etc.dm
@@ -45,7 +45,7 @@
w_class = SIZE_SMALL
force = MELEE_FORCE_WEAK
var/on = 0
- var/stunforce = 60
+ var/stun_force = 10
/obj/item/weapon/telebaton/attack(mob/living/carbon/human/target, mob/living/user)
if(!istype(target) || !on)
@@ -67,6 +67,7 @@
item_state = "telebaton_1"
w_class = SIZE_MEDIUM
force = MELEE_FORCE_VERY_STRONG
+ stun_force = 40
attack_verb = list("smacked", "struck", "slapped", "beat")
else
user.visible_message(SPAN_NOTICE("Using a smooth, practiced movement, [user] collapses \his [src]."),\
@@ -75,7 +76,8 @@
icon_state = "telebaton_0"
item_state = "telebaton_0"
w_class = SIZE_SMALL
- force = MELEE_FORCE_WEAK//not so robust now
+ force = MELEE_FORCE_WEAK
+ stun_force = initial(stun_force)
attack_verb = list("hit", "punched")
if(istype(user,/mob/living/carbon/human))
@@ -100,8 +102,17 @@
user.flick_attack_overlay(target, "punch")
log_interact(user, target, "[key_name(user)] stunned [key_name(target)] with \the [src]")
// Hit 'em
+ var/final_stun_force = stun_force
+ var/datum/skills/user_skills = user.skills
+ if(user_skills)
+ switch(user_skills.get_skill_level(SKILL_POLICE))
+ if(SKILL_POLICE_FLASH)
+ final_stun_force *= 1.5
+ if(SKILL_POLICE_SKILLED)
+ final_stun_force *= 3
+
var/target_zone = check_zone(user.zone_selected)
- target.apply_stamina_damage(stunforce, target_zone, ARMOR_MELEE)
+ target.apply_stamina_damage(final_stun_force, target_zone, ARMOR_MELEE)
if(target.stamina.current_stamina <= 0)
user.visible_message(SPAN_DANGER("[user] knocks down [target] with \the [src]!"),\
SPAN_WARNING("You knock down [target] with \the [src]!"))
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 42a3a2f0b9ea..b92624cf4201 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -223,6 +223,7 @@
/obj/proc/afterbuckle(mob/M as mob) // Called after somebody buckled / unbuckled
handle_rotation()
+ SEND_SIGNAL(src, COSMIG_OBJ_AFTER_BUCKLE, buckled_mob)
return buckled_mob
/obj/proc/unbuckle()
@@ -302,11 +303,9 @@
src.add_fingerprint(user)
afterbuckle(target)
if(buckle_lying) // Make sure buckling to beds/nests etc only turns, and doesn't give a random offset
- var/matrix/M = matrix()
- var/matrix/L = matrix() //Counterrotation for langchat text.
- M.Turn(90)
- L.Turn(270)
- target.apply_transform(M)
+ var/matrix/new_matrix = matrix()
+ new_matrix.Turn(90)
+ target.apply_transform(new_matrix)
return TRUE
/obj/proc/send_buckling_message(mob/M, mob/user)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 05355feeb154..cf0374c09ab4 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -135,21 +135,23 @@
return stored_units
/obj/structure/closet/proc/store_mobs(stored_units)
- for(var/mob/M in src.loc)
+ for(var/mob/cur_mob in src.loc)
if(stored_units + mob_size > storage_capacity)
break
- if(istype (M, /mob/dead/observer))
+ if(istype (cur_mob, /mob/dead/observer))
continue
- if(M.buckled)
+ if(cur_mob.buckled)
+ continue
+ if(cur_mob.anchored)
continue
- M.forceMove(src)
+ cur_mob.forceMove(src)
stored_units += mob_size
return stored_units
/obj/structure/closet/proc/toggle(mob/living/user)
user.next_move = world.time + 5
- if(!(src.opened ? src.close() : src.open()))
+ if(!(src.opened ? src.close(user) : src.open()))
to_chat(user, SPAN_NOTICE("It won't budge!"))
return
diff --git a/code/game/objects/structures/props.dm b/code/game/objects/structures/props.dm
index f6905d4d044d..1a91650c620a 100644
--- a/code/game/objects/structures/props.dm
+++ b/code/game/objects/structures/props.dm
@@ -24,7 +24,7 @@
. = ..()
if(isxeno(user))
return
- else if (ishuman(user) && istype(W, /obj/item/tool/wrench))
+ else if (ishuman(user) && HAS_TRAIT(W, TRAIT_TOOL_WRENCH))
on = !on
visible_message("You wrench the controls of \the [src]. The drill jumps to life." , "[user] wrenches the controls of \the [src]. The drill jumps to life.")
@@ -501,7 +501,7 @@
. = ..()
if(isxeno(user))
return
- else if (ishuman(user) && istype(W, /obj/item/tool/crowbar))
+ else if (ishuman(user) && HAS_TRAIT(W, TRAIT_TOOL_CROWBAR))
on = !on
visible_message("You pry at the control valve on [src]. The machine shudders." , "[user] pries at the control valve on [src]. The entire machine shudders.")
diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm
index bda4b60ca21e..7979994915f4 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm
@@ -208,6 +208,20 @@
return
..()
+/obj/structure/bed/roller/Collided(atom/movable/moving_atom)
+ if(!isxeno(moving_atom))
+ return ..()
+
+ if(buckled_mob && buckled_mob.stat != DEAD)
+ return ..()
+
+ if(buckled_bodybag)
+ var/mob/mob_in_bodybag = locate(/mob) in buckled_bodybag
+ if(mob_in_bodybag && mob_in_bodybag.stat != DEAD)
+ return ..()
+
+ return
+
/obj/item/roller
name = "roller bed"
desc = "A collapsed roller bed that can be carried around."
diff --git a/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm b/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
index e306b894b021..37d46cbe6d5d 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
@@ -289,11 +289,12 @@
if(buckled_human.stat == DEAD )
buckled_mob_density = FALSE
- . = ..()
-
var/mob/dead/observer/G = ghost_of_buckled_mob
var/datum/mind/M = G?.mind
ghost_of_buckled_mob = null
+
+ . = ..() //Very important that this comes after, since it deletes the nest and clears ghost_of_buckled_mob
+
if(!istype(buckled_human) || !istype(G) || !istype(M) || buckled_human.undefibbable || buckled_human.mind || M.original != buckled_human || buckled_human.chestburst)
return // Zealous checking as most is handled by ghost code
to_chat(G, FONT_SIZE_HUGE(SPAN_DANGER("You have been freed from your nest and may go back to your body! (Look for 'Re-enter Corpse' in Ghost verbs, or click here!)")))
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 5c084ce94cbc..cccc1211bfb0 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -121,7 +121,7 @@
cistern_overlay.icon_state = "cistern[cistern]"
/obj/structure/toilet/attackby(obj/item/I, mob/living/user)
- if(istype(I, /obj/item/tool/crowbar))
+ if(HAS_TRAIT(I, TRAIT_TOOL_CROWBAR))
to_chat(user, SPAN_NOTICE("You start to [cistern ? "replace the lid on the cistern" : "lift the lid off the cistern"]."))
playsound(loc, 'sound/effects/stonedoor_openclose.ogg', 25, 1)
if(do_after(user, 30, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
diff --git a/code/game/turfs/floor.dm b/code/game/turfs/floor.dm
index 4a600d4d033b..dc2cda0c2c2a 100644
--- a/code/game/turfs/floor.dm
+++ b/code/game/turfs/floor.dm
@@ -148,7 +148,7 @@
if(src.weeds)
return weeds.attackby(hitting_item,user)
- if(istype(hitting_item, /obj/item/tool/crowbar) && (tool_flags & (REMOVE_CROWBAR|BREAK_CROWBAR)))
+ if(HAS_TRAIT(hitting_item, TRAIT_TOOL_CROWBAR) && (tool_flags & (REMOVE_CROWBAR|BREAK_CROWBAR)))
if(broken || burnt)
to_chat(user, SPAN_WARNING("You remove the broken tiles."))
else
diff --git a/code/game/turfs/walls/wall_types.dm b/code/game/turfs/walls/wall_types.dm
index 702cbd35560c..04c5a0735824 100644
--- a/code/game/turfs/walls/wall_types.dm
+++ b/code/game/turfs/walls/wall_types.dm
@@ -710,6 +710,17 @@
for(var/obj/effect/alien/weeds/node/weed_node in contents)
qdel(weed_node)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
+
+/turf/closed/wall/resin/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
/turf/closed/wall/resin/pillar
name = "resin pillar segment"
hull = TRUE
diff --git a/code/game/turfs/walls/walls.dm b/code/game/turfs/walls/walls.dm
index 411ff8182661..3599d5bb980b 100644
--- a/code/game/turfs/walls/walls.dm
+++ b/code/game/turfs/walls/walls.dm
@@ -98,11 +98,15 @@
qdel(found_nest) //nests are built on walls, no walls, no nest
/turf/closed/wall/MouseDrop_T(mob/current_mob, mob/user)
+ if(!ismob(current_mob))
+ return
+
if(acided_hole)
if(current_mob == user && isxeno(user))
acided_hole.use_wall_hole(user)
return
- if(isxeno(user))
+
+ if(isxeno(user) && istype(user.get_active_hand(), /obj/item/grab))
var/mob/living/carbon/xenomorph/user_as_xenomorph = user
user_as_xenomorph.do_nesting_host(current_mob, src)
..()
diff --git a/code/modules/admin/tabs/event_tab.dm b/code/modules/admin/tabs/event_tab.dm
index 20d98776c5ab..febc1550fca0 100644
--- a/code/modules/admin/tabs/event_tab.dm
+++ b/code/modules/admin/tabs/event_tab.dm
@@ -744,7 +744,7 @@
create_humans_html = replacetext(create_humans_html, "null /* object types */", "\"[equipment_presets]\"")
create_humans_html = replacetext(create_humans_html, "/* href token */", RawHrefToken(forceGlobal = TRUE))
- show_browser(user, replacetext(create_humans_html, "/* ref src */", "\ref[src]"), "Create Humans", "create_humans", "size=450x630")
+ show_browser(user, replacetext(create_humans_html, "/* ref src */", "\ref[src]"), "Create Humans", "create_humans", "size=450x720")
/client/proc/create_humans()
set name = "Create Humans"
@@ -830,7 +830,6 @@
if(isnull(OBShell.double_explosion_delay)) return
statsmessage = "Custom HE OB ([OBShell.name]) Stats from [key_name(usr)]: Clear Power: [OBShell.clear_power], Clear Falloff: [OBShell.clear_falloff], Clear Delay: [OBShell.clear_delay], Blast Power: [OBShell.standard_power], Blast Falloff: [OBShell.standard_falloff], Blast Delay: [OBShell.double_explosion_delay]."
warhead = OBShell
- qdel(OBShell)
if("Custom Cluster")
var/obj/structure/ob_ammo/warhead/cluster/OBShell = new
OBShell.name = input("What name should the warhead have?", "Set name", "Cluster orbital warhead")
@@ -847,7 +846,6 @@
if(isnull(OBShell.explosion_falloff)) return
statsmessage = "Custom Cluster OB ([OBShell.name]) Stats from [key_name(usr)]: Salvos: [OBShell.total_amount], Shot per Salvo: [OBShell.instant_amount], Explosion Power: [OBShell.explosion_power], Explosion Falloff: [OBShell.explosion_falloff]."
warhead = OBShell
- qdel(OBShell)
if("Custom Incendiary")
var/obj/structure/ob_ammo/warhead/incendiary/OBShell = new
OBShell.name = input("What name should the warhead have?", "Set name", "Incendiary orbital warhead")
@@ -876,19 +874,28 @@
if(isnull(OBShell.fire_color)) return
statsmessage = "Custom Incendiary OB ([OBShell.name]) Stats from [key_name(usr)]: Clear Power: [OBShell.clear_power], Clear Falloff: [OBShell.clear_falloff], Clear Delay: [OBShell.clear_delay], Fire Distance: [OBShell.distance], Fire Duration: [OBShell.fire_level], Fire Strength: [OBShell.burn_level]."
warhead = OBShell
- qdel(OBShell)
if(custom)
- if(alert(usr, statsmessage, "Confirm Stats", "Yes", "No") != "Yes") return
+ if(alert(usr, statsmessage, "Confirm Stats", "Yes", "No") != "Yes")
+ qdel(warhead)
+ return
message_admins(statsmessage)
var/turf/target = get_turf(usr.loc)
if(alert(usr, "Fire or Spawn Warhead?", "Mode", "Fire", "Spawn") == "Fire")
- if(alert("Are you SURE you want to do this? It will create an OB explosion!",, "Yes", "No") != "Yes") return
+ if(alert("Are you SURE you want to do this? It will create an OB explosion!",, "Yes", "No") != "Yes")
+ qdel(warhead)
+ return
+
message_admins("[key_name(usr)] has fired \an [warhead.name] at ([target.x],[target.y],[target.z]).")
warhead.warhead_impact(target)
- QDEL_IN(warhead, OB_CRASHING_DOWN)
+
+ if(istype(warhead, /obj/structure/ob_ammo/warhead/cluster))
+ // so the user's screen can shake for the duration of the cluster, otherwise we get a runtime.
+ QDEL_IN(warhead, OB_CLUSTER_DURATION)
+ else
+ QDEL_IN(warhead, OB_CRASHING_DOWN)
else
warhead.loc = target
@@ -958,6 +965,7 @@
else
var/faction = tgui_input_list(usr, "What faction do you wish to provide a bioscan for?", "Bioscan Faction", list("Xeno","Marine","Yautja"), 20 SECONDS)
var/variance = tgui_input_number(usr, "How variable do you want the scan to be? (+ or - an amount from truth)", "Variance", 2, 10, 0, 20 SECONDS)
+ message_admins("BIOSCAN: [key_name(usr)] admin-triggered a bioscan for [faction].")
GLOB.bioscan_data.get_scan_data()
switch(faction)
if("Xeno")
diff --git a/code/modules/character_traits/biology_traits.dm b/code/modules/character_traits/biology_traits.dm
index e7625c391b75..87fb0b70ec36 100644
--- a/code/modules/character_traits/biology_traits.dm
+++ b/code/modules/character_traits/biology_traits.dm
@@ -59,11 +59,13 @@
return
ADD_TRAIT(target, TRAIT_LISPING, TRAIT_SOURCE_QUIRK)
+ target.speech_problem_flag = TRUE
..()
/datum/character_trait/biology/lisp/unapply_trait(mob/living/carbon/human/target)
REMOVE_TRAIT(target, TRAIT_LISPING, TRAIT_SOURCE_QUIRK)
+ target.speech_problem_flag = FALSE
..()
/datum/character_trait/biology/bad_leg
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index fbec4aa13f36..8f0939474427 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -26,8 +26,6 @@
var/adminobs = null
var/area = null
var/time_died_as_mouse = null //when the client last died as a mouse
- /// The descriminator for larva queue ordering: Generally set to timeofdeath except for facehuggers/admin z-level play
- var/larva_queue_time
var/donator = 0
var/adminhelped = 0
diff --git a/code/modules/client/player_details.dm b/code/modules/client/player_details.dm
index 06dafdbea63a..634fd8fb627e 100644
--- a/code/modules/client/player_details.dm
+++ b/code/modules/client/player_details.dm
@@ -7,7 +7,12 @@ GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details
var/list/post_logout_callbacks = list()
var/list/played_names = list() //List of names this key played under this round
var/byond_version = "Unknown"
+ /// The descriminator for larva queue ordering: Generally set to timeofdeath except for facehuggers/admin z-level play
+ var/larva_queue_time
+/datum/player_details/New()
+ larva_queue_time = world.time
+ return ..()
/proc/log_played_names(ckey, ...)
if(!ckey)
diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm
index 1d4b77f15550..e712e267fb2d 100644
--- a/code/modules/client/preferences_gear.dm
+++ b/code/modules/client/preferences_gear.dm
@@ -363,6 +363,11 @@ var/global/list/gear_datums_by_name = list()
display_name = "Pen, red"
path = /obj/item/tool/pen/red
+/datum/gear/paperwork/pen_fountain
+ display_name = "Pen, fountain"
+ path = /obj/item/tool/pen/fountain
+ cost = 3
+
/datum/gear/paperwork/paper
display_name = "Sheet of paper"
path = /obj/item/paper
diff --git a/code/modules/clothing/head/head.dm b/code/modules/clothing/head/head.dm
index 1b4e292a22af..0916ecfb34e9 100644
--- a/code/modules/clothing/head/head.dm
+++ b/code/modules/clothing/head/head.dm
@@ -248,7 +248,9 @@
/obj/item/prop/helmetgarb/lucky_feather = "lucky_feather",
/obj/item/prop/helmetgarb/lucky_feather/blue = "lucky_feather_blue",
/obj/item/prop/helmetgarb/lucky_feather/purple = "lucky_feather_purple",
- /obj/item/prop/helmetgarb/lucky_feather/yellow = "lucky_feather_yellow")
+ /obj/item/prop/helmetgarb/lucky_feather/yellow = "lucky_feather_yellow",
+ /obj/item/tool/pen/fountain = "fountainpen",
+ )
var/storage_slots = 1
var/storage_slots_reserved_for_garb = 1
var/storage_max_w_class = SIZE_TINY
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index eab8b76e81ab..1b66d3e1bf5f 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -326,6 +326,7 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list(
/obj/item/prop/helmetgarb/riot_shield = "helmet_riot_shield",
/obj/item/attachable/flashlight = HELMET_GARB_RELAY_ICON_STATE,
/obj/item/prop/helmetgarb/chaplain_patch = "chaplain_patch",
+ /obj/item/tool/pen/fountain = "fountainpen",
// MEDICAL
/obj/item/stack/medical/bruise_pack ="brutepack (bandages)",
diff --git a/code/modules/clothing/suits/marine_armor.dm b/code/modules/clothing/suits/marine_armor.dm
index ab62ea782a09..8ad01f8c4e30 100644
--- a/code/modules/clothing/suits/marine_armor.dm
+++ b/code/modules/clothing/suits/marine_armor.dm
@@ -579,6 +579,7 @@ var/list/squad_colors_chat = list(rgb(230,125,125), rgb(255,230,80), rgb(255,150
armor_rad = CLOTHING_ARMOR_NONE
armor_internaldamage = CLOTHING_ARMOR_NONE
storage_slots = 3
+ slowdown = SLOWDOWN_ARMOR_VERY_LIGHT
time_to_unequip = 0.5 SECONDS
time_to_equip = 1 SECONDS
uniform_restricted = null
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index d2b020a05daf..c51f5f2575ed 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -272,13 +272,11 @@
flags_armor_protection = BODY_FLAG_CHEST|BODY_FLAG_ARMS
/obj/item/clothing/suit/storage/webbing
- name = "External webbing"
+ name = "external webbing"
desc = "Designed to be worn over a jumpsuit rather than clipped on."
icon_state = "webbing"
item_state = "webbing"
allowed = list(
- /obj/item/storage/fancy/cigarettes,
- /obj/item/tool/lighter,
/obj/item/weapon/baton,
/obj/item/handcuffs,
/obj/item/device/binoculars,
@@ -287,7 +285,6 @@
/obj/item/device/flashlight,
/obj/item/device/healthanalyzer,
/obj/item/device/radio,
- /obj/item/tank/emergency_oxygen,
/obj/item/tool/crowbar,
/obj/item/tool/crew_monitor,
/obj/item/tool/pen,
@@ -295,6 +292,26 @@
/obj/item/device/motiondetector,
)
+/obj/item/clothing/suit/storage/utility_vest
+ name = "utility vest"
+ desc = "A utility vest to hold tools in."
+ icon_state = "synth_utility_vest"
+ item_state = "synth_utility_vest"
+ allowed = list(
+ /obj/item/weapon/baton,
+ /obj/item/handcuffs,
+ /obj/item/device/binoculars,
+ /obj/item/attachable/bayonet,
+
+ /obj/item/device/flashlight,
+ /obj/item/device/healthanalyzer,
+ /obj/item/device/radio,
+ /obj/item/tool/crowbar,
+ /obj/item/tool/crew_monitor,
+ /obj/item/storage/large_holster/machete,
+ /obj/item/device/motiondetector,
+ )
+
//Blue suit jacket toggle
/obj/item/clothing/suit/suit/verb/toggle()
set name = "Toggle Jacket Buttons"
diff --git a/code/modules/clothing/under/marine_uniform.dm b/code/modules/clothing/under/marine_uniform.dm
index 540c311f9221..eca050cc4b88 100644
--- a/code/modules/clothing/under/marine_uniform.dm
+++ b/code/modules/clothing/under/marine_uniform.dm
@@ -705,7 +705,7 @@
sensor_faction = FACTION_CLF
/obj/item/clothing/under/colonist/ua_civvies
- name = "gray utilities"
+ name = "\improper UA gray utility uniform"
desc = "A stylish gray jumpsuit - standard issue for UA civilian support personnel."
icon_state = "ua_civvies"
worn_state = "ua_civvies"
@@ -713,7 +713,7 @@
sensor_faction = FACTION_MARINE
/obj/item/clothing/under/colonist/wy_davisone
- name = "brown utilities"
+ name = "\improper UA brown utility uniform"
desc = "A stylish brown jumpsuit - standard issue for UA civilian support personnel."
icon_state = "wy_davisone"
worn_state = "wy_davisone"
@@ -737,14 +737,15 @@
desc = "A comfortable white T-shirt and brown jeans."
icon_state = "tshirt_w_br"
worn_state = "tshirt_w_br"
+ displays_id = FALSE
has_sensor = UNIFORM_HAS_SENSORS
sensor_faction = FACTION_MARINE
-
/obj/item/clothing/under/tshirt/gray_blu
name = "gray T-shirt and jeans"
desc = "A comfortable gray T-shirt and blue jeans."
icon_state = "tshirt_gray_blu"
worn_state = "tshirt_gray_blu"
+ displays_id = FALSE
has_sensor = UNIFORM_HAS_SENSORS
sensor_faction = FACTION_MARINE
@@ -753,6 +754,7 @@
desc = "A comfortable red T-shirt and black jeans."
icon_state = "tshirt_r_bla"
worn_state = "tshirt_r_bla"
+ displays_id = FALSE
has_sensor = UNIFORM_HAS_SENSORS
sensor_faction = FACTION_MARINE
@@ -851,6 +853,38 @@
worn_state = "rdalt"
flags_jumpsuit = FALSE
+/obj/item/clothing/under/rank/synthetic/frontier
+ name = "\improper frontier jumpsuit"
+ desc = "A cargo jumpsuit dressed down for full range of motion and state-of-the-art frontier temperature control. It's the best thing an engineer can wear in the Outer Veil."
+ icon_state = "synth_cargo_light"
+ worn_state = "synth_cargo_light"
+ displays_id = FALSE
+
+/obj/item/clothing/under/rank/synthetic/utility
+ name = "\improper UA utility uniform"
+ desc = "A green-on-green utility uniform, popularly issued to UA contract workers on the frontier."
+ icon_state = "synth_green_utility"
+ worn_state = "synth_green_utility"
+ displays_id = FALSE
+
+/obj/item/clothing/under/rank/synthetic/utility/yellow
+ name = "\improper utility uniform"
+ desc = "A grey utility uniform with yellow suspenders, made for shipside crew."
+ icon_state = "synth_yellow_utility"
+ worn_state = "synth_yellow_utility"
+
+/obj/item/clothing/under/rank/synthetic/utility/red
+ name = "\improper utility uniform"
+ desc = "A grey utility uniform with red suspenders and blue jeans, the sign of a veteran laborer, or someone not paid by the hour."
+ icon_state = "synth_red_utility"
+ worn_state = "synth_red_utility"
+
+/obj/item/clothing/under/rank/synthetic/utility/blue
+ name = "\improper utility uniform"
+ desc = "A blue utility uniform with teal suspenders and rugged pants."
+ icon_state = "synth_blue_utility"
+ worn_state = "synth_blue_utility"
+
/obj/item/clothing/under/rank/synthetic/councillor
name = "\improper USCM Pristine Support Uniform"
desc = "A nicely handcrafted uniform made for Synthetic crewmembers."
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index b5d2850575aa..42c61404a31a 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -82,8 +82,8 @@
item_state = "r_suit"
/obj/item/clothing/under/blackskirt
- name = "black skirt"
- desc = "A black skirt, very fancy!"
+ name = "red dress skirt"
+ desc = "A black cardigan with a red skirt, quite fancy!"
icon_state = "blackskirt"
flags_armor_protection = BODY_FLAG_CHEST|BODY_FLAG_GROIN|BODY_FLAG_ARMS
diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm
index f21cd6498615..594c2e98695c 100644
--- a/code/modules/cm_aliens/XenoStructures.dm
+++ b/code/modules/cm_aliens/XenoStructures.dm
@@ -154,6 +154,8 @@
if (hive)
hivenumber = hive
set_hive_data(src, hivenumber)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
/obj/effect/alien/resin/sticky/Crossed(atom/movable/AM)
. = ..()
@@ -166,6 +168,14 @@
X.next_move_slowdown = X.next_move_slowdown + slow_amt
return .
+/obj/effect/alien/resin/sticky/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
/obj/effect/alien/resin/spike
name = "resin spike"
desc = "A small cluster of bone spikes. Ouch."
@@ -193,6 +203,8 @@
hivenumber = hive
set_hive_data(src, hivenumber)
setDir(pick(alldirs))
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
/obj/effect/alien/resin/spike/Crossed(atom/movable/AM)
. = ..()
@@ -206,6 +218,14 @@
H.apply_armoured_damage(damage, penetration = penetration, def_zone = pick(target_limbs))
H.last_damage_data = construction_data
+/obj/effect/alien/resin/spike/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
// Praetorian Sticky Resin spit uses this.
/obj/effect/alien/resin/sticky/thin
name = "thin sticky resin"
@@ -348,6 +368,9 @@
set_hive_data(src, hivenumber)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
+
/obj/structure/mineral_door/resin/flamer_fire_act(dam = BURN_LEVEL_TIER_1)
health -= dam
healthcheck()
@@ -479,6 +502,13 @@
visible_message(SPAN_NOTICE("[src] collapses from the lack of support."))
qdel(src)
+/obj/structure/mineral_door/resin/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
/obj/structure/mineral_door/resin/thick
name = "thick resin door"
icon_state = "thick resin"
@@ -510,6 +540,8 @@
hivenumber = hive
set_hive_data(src, hivenumber)
START_PROCESSING(SSprocessing, src)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
/obj/effect/alien/resin/acid_pillar/proc/can_target(mob/living/carbon/current_mob, position_to_get = 0)
@@ -599,6 +631,12 @@
/obj/effect/alien/resin/acid_pillar/get_projectile_hit_boolean(obj/item/projectile/P)
return TRUE
+/obj/effect/alien/resin/acid_pillar/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+ if(is_ground_level(z))
+ qdel(src)
+
/obj/effect/alien/resin/acid_pillar/strong
name = "acid pillar"
desc = "A resin pillar that is oozing with acid."
diff --git a/code/modules/cm_aliens/structures/fruit.dm b/code/modules/cm_aliens/structures/fruit.dm
index 8bdcc0b82413..bb899a6ff25b 100644
--- a/code/modules/cm_aliens/structures/fruit.dm
+++ b/code/modules/cm_aliens/structures/fruit.dm
@@ -150,25 +150,33 @@
update_icon()
QDEL_IN(src, 3 SECONDS)
-/obj/effect/alien/resin/fruit/attack_alien(mob/living/carbon/xenomorph/X)
+/obj/effect/alien/resin/fruit/attack_alien(mob/living/carbon/xenomorph/affected_xeno)
if(picked)
- to_chat(X, SPAN_XENODANGER("This fruit is already being picked!"))
+ to_chat(affected_xeno, SPAN_XENODANGER("This fruit is already being picked!"))
return
- if(X.a_intent != INTENT_HARM && (X.can_not_harm(bound_xeno) || X.hivenumber == hivenumber))
- var/cant_consume = prevent_consume(X)
+
+ if(affected_xeno.a_intent != INTENT_HARM && (affected_xeno.can_not_harm(bound_xeno) || affected_xeno.hivenumber == hivenumber))
+ var/cant_consume = prevent_consume(affected_xeno)
if(cant_consume)
return cant_consume
+
if(mature)
- to_chat(X, SPAN_XENOWARNING("You prepare to consume [name]."))
- xeno_noncombat_delay(X)
- if(!do_after(X, consume_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
+ to_chat(affected_xeno, SPAN_XENOWARNING("You prepare to consume [name]."))
+ xeno_noncombat_delay(affected_xeno)
+ if(!do_after(affected_xeno, consume_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
return XENO_NO_DELAY_ACTION
- consume_effect(X)
+
+ cant_consume = prevent_consume(affected_xeno) // Check again after the delay incase they have eaten another fruit
+ if(cant_consume)
+ to_chat(affected_xeno, SPAN_XENOWARNING("You can no longer consume [name]."))
+ return cant_consume
+ consume_effect(affected_xeno)
else
- to_chat(X, SPAN_XENOWARNING("[name] isn't ripe yet. You need to wait a little longer."))
- if(X.a_intent == INTENT_HARM && isxeno_builder(X) || (!X.can_not_harm(bound_xeno) && X.hivenumber != hivenumber))
- X.animation_attack_on(src)
- X.visible_message(SPAN_XENODANGER("[X] removes [name]!"),
+ to_chat(affected_xeno, SPAN_XENOWARNING("[name] isn't ripe yet. You need to wait a little longer."))
+
+ if(affected_xeno.a_intent == INTENT_HARM && isxeno_builder(affected_xeno) || (!affected_xeno.can_not_harm(bound_xeno) && affected_xeno.hivenumber != hivenumber))
+ affected_xeno.animation_attack_on(src)
+ affected_xeno.visible_message(SPAN_XENODANGER("[affected_xeno] removes [name]!"),
SPAN_XENODANGER("You remove [name]!"))
playsound(loc, "alien_resin_break", 25)
qdel(src)
@@ -376,30 +384,49 @@
bound_xeno = null
// Xenos eating fruit
-/obj/item/reagent_container/food/snacks/resin_fruit/attack(mob/living/carbon/xenomorph/X, mob/user)
+/obj/item/reagent_container/food/snacks/resin_fruit/attack(mob/living/carbon/xenomorph/affected_xeno, mob/user)
if(istype(user, /mob/living/carbon/xenomorph)) // Prevents xenos from feeding capped/dead marines fruit
- var/mob/living/carbon/xenomorph/Y = user
- if(!Y.can_not_harm(X))
- to_chat(Y, SPAN_WARNING("[X] refuses to eat [src]."))
+ var/mob/living/carbon/xenomorph/feeding_xeno = user
+ if(!feeding_xeno.can_not_harm(affected_xeno))
+ to_chat(feeding_xeno, SPAN_WARNING("[affected_xeno] refuses to eat [src]."))
return
- if(!istype(X))
+
+ if(!istype(affected_xeno))
return ..()
- if(X.stat == DEAD)
+
+ if(affected_xeno.stat == DEAD)
to_chat(user, SPAN_WARNING("That sister is already dead, they won't benefit from the fruit now..."))
return
- user.affected_message(X,
- SPAN_HELPFUL("You start [user == X ? "eating" : "feeding [X]"] [src]."),
- SPAN_HELPFUL("[user] starts feeding you [src]."),
- SPAN_NOTICE("[user] starts [user == X ? "eating" : "feeding [X]"] [src]."))
- if(!do_after(user, consume_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY, X, INTERRUPT_MOVED, BUSY_ICON_MEDICAL))
+
+ var/obj/effect/alien/resin/fruit/current_fruit = new fruit_type(affected_xeno)
+ var/cant_consume = current_fruit.prevent_consume(affected_xeno)
+ 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]."))
+ return
+ user.affected_message(affected_xeno,
+ SPAN_HELPFUL("You start [user == affected_xeno ? "eating" : "feeding [affected_xeno]"] [current_fruit]."),
+ SPAN_HELPFUL("[user] starts feeding you [current_fruit]."),
+ SPAN_NOTICE("[user] starts [user == affected_xeno ? "eating" : "feeding [affected_xeno]"] [current_fruit]."))
+
+ if(!do_after(user, consume_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY, affected_xeno, INTERRUPT_MOVED, BUSY_ICON_MEDICAL))
return FALSE
- user.affected_message(X,
- SPAN_HELPFUL("You [user == X ? "eat" : "fed [X]"] [src]."),
- SPAN_HELPFUL("[user] fed you [src]."),
- SPAN_NOTICE("[user] [user == X ? "ate" : "fed [X]"] [src]."))
- var/obj/effect/alien/resin/fruit/F = new fruit_type(X)
- F.mature = TRUE
- F.consume_effect(X)
+
+ cant_consume = current_fruit.prevent_consume(affected_xeno)
+ 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]."))
+ return
+
+ user.affected_message(affected_xeno,
+ SPAN_HELPFUL("You [user == affected_xeno ? "eat" : "fed [affected_xeno]"] [current_fruit]."),
+ SPAN_HELPFUL("[user] fed you [current_fruit]."),
+ SPAN_NOTICE("[user] [user == affected_xeno ? "ate" : "fed [affected_xeno]"] [current_fruit]."))
+ current_fruit.mature = TRUE
+ current_fruit.consume_effect(affected_xeno)
+
//Notify the fruit's bound xeno if they exist
if(!QDELETED(bound_xeno))
to_chat(bound_xeno, SPAN_XENOWARNING("One of your picked resin fruits has been consumed."))
diff --git a/code/modules/cm_aliens/structures/trap.dm b/code/modules/cm_aliens/structures/trap.dm
index 1b3d4d414d48..5e1c51538ce6 100644
--- a/code/modules/cm_aliens/structures/trap.dm
+++ b/code/modules/cm_aliens/structures/trap.dm
@@ -29,6 +29,8 @@
cause_data = create_cause_data("resin trap", X)
set_hive_data(src, hivenumber)
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
/obj/effect/alien/resin/trap/Initialize()
. = ..()
@@ -53,6 +55,14 @@
if(RESIN_TRAP_ACID1, RESIN_TRAP_ACID2, RESIN_TRAP_ACID3)
. += "It's filled with pressurised acid."
+/obj/effect/alien/resin/trap/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
/obj/effect/alien/resin/trap/proc/facehugger_die()
var/obj/item/clothing/mask/facehugger/FH = new (loc)
FH.die()
@@ -159,6 +169,7 @@
trap_type_name = "hugger"
var/obj/item/clothing/mask/facehugger/FH = new (loc)
FH.hivenumber = hivenumber
+ set_hive_data(FH, hivenumber)
set_state()
visible_message(SPAN_WARNING("[FH] gets out of [src]!"))
sleep(15)
diff --git a/code/modules/cm_aliens/weeds.dm b/code/modules/cm_aliens/weeds.dm
index 0be2220994d8..f20fa842e446 100644
--- a/code/modules/cm_aliens/weeds.dm
+++ b/code/modules/cm_aliens/weeds.dm
@@ -73,15 +73,18 @@
else if(!hibernate && do_spread)
addtimer(CALLBACK(src, PROC_REF(weed_expand)), WEED_BASE_GROW_SPEED / max(weed_strength, 1))
- var/turf/T = get_turf(src)
- if(T)
- T.weeds = src
- weeded_turf = T
+ var/turf/turf = get_turf(src)
+ if(turf)
+ turf.weeds = src
+ weeded_turf = turf
+ SEND_SIGNAL(turf, COMSIG_WEEDNODE_GROWTH) // Currently for weed_food wakeup
RegisterSignal(src, list(
COMSIG_ATOM_TURF_CHANGE,
COMSIG_MOVABLE_TURF_ENTERED
), PROC_REF(set_turf_weeded))
+ if(hivenumber == XENO_HIVE_NORMAL)
+ RegisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING, PROC_REF(forsaken_handling))
/obj/effect/alien/weeds/proc/set_turf_weeded(datum/source, turf/T)
SIGNAL_HANDLER
@@ -90,6 +93,15 @@
T.weeds = src
+/obj/effect/alien/weeds/proc/forsaken_handling()
+ SIGNAL_HANDLER
+ if(is_ground_level(z))
+ hivenumber = XENO_HIVE_FORSAKEN
+ set_hive_data(src, XENO_HIVE_FORSAKEN)
+ linked_hive = GLOB.hive_datum[XENO_HIVE_FORSAKEN]
+
+ UnregisterSignal(SSdcs, COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
/obj/effect/alien/weeds/initialize_pass_flags(datum/pass_flags_container/PF)
. = ..()
if (PF)
@@ -418,7 +430,11 @@
/obj/effect/alien/weeds/weedwall/MouseDrop_T(mob/current_mob, mob/user)
. = ..()
- if(isxeno(user))
+
+ if(!ismob(current_mob))
+ return
+
+ if(isxeno(user) && istype(user.get_active_hand(), /obj/item/grab))
var/mob/living/carbon/xenomorph/user_as_xenomorph = user
user_as_xenomorph.do_nesting_host(current_mob, src)
@@ -492,20 +508,20 @@
overlay_node = TRUE
overlays += staticnode
-/obj/effect/alien/weeds/node/Initialize(mapload, obj/effect/alien/weeds/node/node, mob/living/carbon/xenomorph/X, datum/hive_status/hive)
+/obj/effect/alien/weeds/node/Initialize(mapload, obj/effect/alien/weeds/node/node, mob/living/carbon/xenomorph/xeno, datum/hive_status/hive)
if (istype(hive))
linked_hive = hive
- else if (istype(X) && X.hive)
- linked_hive = X.hive
+ else if (istype(xeno) && xeno.hive)
+ linked_hive = xeno.hive
else
linked_hive = GLOB.hive_datum[hivenumber]
- for(var/obj/effect/alien/weeds/W in loc)
- if(W != src)
- if(W.weed_strength > WEED_LEVEL_HIVE)
+ for(var/obj/effect/alien/weeds/weed in loc)
+ if(weed != src)
+ if(weed.weed_strength > WEED_LEVEL_HIVE)
qdel(src)
return
- qdel(W) //replaces the previous weed
+ qdel(weed) //replaces the previous weed
break
. = ..(mapload, src)
@@ -513,15 +529,15 @@
if(!staticnode)
staticnode = image('icons/mob/xenos/weeds.dmi', "weednode", ABOVE_OBJ_LAYER)
- var/obj/effect/alien/resin/trap/TR = locate() in loc
- if(TR)
- RegisterSignal(TR, COMSIG_PARENT_PREQDELETED, PROC_REF(trap_destroyed))
+ var/obj/effect/alien/resin/trap/trap = locate() in loc
+ if(trap)
+ RegisterSignal(trap, COMSIG_PARENT_PREQDELETED, PROC_REF(trap_destroyed))
overlay_node = FALSE
overlays -= staticnode
- if(X)
- add_hiddenprint(X)
- weed_strength = X.weed_level
+ if(xeno)
+ add_hiddenprint(xeno)
+ weed_strength = max(weed_strength, xeno.weed_level)
if (weed_strength < WEED_LEVEL_STANDARD)
weed_strength = WEED_LEVEL_STANDARD
diff --git a/code/modules/cm_marines/dropship_ammo.dm b/code/modules/cm_marines/dropship_ammo.dm
index b4b585e1de89..3c01688b70d7 100644
--- a/code/modules/cm_marines/dropship_ammo.dm
+++ b/code/modules/cm_marines/dropship_ammo.dm
@@ -287,9 +287,8 @@
/obj/structure/ship_ammo/rocket/widowmaker/detonate_on(turf/impact)
impact.ceiling_debris_check(3)
- spawn(5)
- cell_explosion(impact, 300, 40, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob)) //Your standard HE splash damage rocket. Good damage, good range, good speed, it's an all rounder
- qdel(src)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 300, 40, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name)), source_mob), 0.5 SECONDS) //Your standard HE splash damage rocket. Good damage, good range, good speed, it's an all rounder
+ QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/banshee
name = "\improper AGM-227 'Banshee'"
@@ -301,10 +300,9 @@
/obj/structure/ship_ammo/rocket/banshee/detonate_on(turf/impact)
impact.ceiling_debris_check(3)
- spawn(5)
- cell_explosion(impact, 175, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob)) //Small explosive power with a small fall off for a big explosion range
- fire_spread(impact, create_cause_data(initial(name), source_mob), 4, 15, 50, "#00b8ff") //Very intense but the fire doesn't last very long
- qdel(src)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 175, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name)), source_mob), 0.5 SECONDS) //Small explosive power with a small fall off for a big explosion range
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 4, 15, 50, "#00b8ff"), 0.5 SECONDS) //Very intense but the fire doesn't last very long
+ QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/keeper
name = "\improper GBU-67 'Keeper II'"
@@ -317,9 +315,8 @@
/obj/structure/ship_ammo/rocket/keeper/detonate_on(turf/impact)
impact.ceiling_debris_check(3)
- spawn(5)
- cell_explosion(impact, 450, 100, EXPLOSION_FALLOFF_SHAPE_EXPONENTIAL, null, create_cause_data(initial(name), source_mob)) //Insane fall off combined with insane damage makes the Keeper useful for single targets, but very bad against multiple.
- qdel(src)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 450, 100, EXPLOSION_FALLOFF_SHAPE_EXPONENTIAL, null, create_cause_data(initial(name)), source_mob), 0.5 SECONDS) //Insane fall off combined with insane damage makes the Keeper useful for single targets, but very bad against multiple.
+ QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/harpoon
name = "\improper AGM-84 'Harpoon'"
@@ -333,9 +330,8 @@
//Looks kinda OP but all it can actually do is just to blow windows and some of other things out, cant do much damage.
/obj/structure/ship_ammo/rocket/harpoon/detonate_on(turf/impact)
impact.ceiling_debris_check(3)
- spawn(5)
- cell_explosion(impact, 150, 16, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob))
- qdel(src)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 150, 16, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name)), source_mob), 0.5 SECONDS)
+ QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/napalm
name = "\improper XN-99 'Napalm'"
@@ -347,10 +343,9 @@
/obj/structure/ship_ammo/rocket/napalm/detonate_on(turf/impact)
impact.ceiling_debris_check(3)
- spawn(5)
- cell_explosion(impact, 200, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob))
- fire_spread(impact, create_cause_data(initial(name), source_mob), 6, 60, 30, "#EE6515") //Color changed into napalm's color to better convey how intense the fire actually is.
- qdel(src)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 200, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name)), source_mob), 0.5 SECONDS)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 6, 60, 30, "#EE6515"), 0.5 SECONDS) //Color changed into napalm's color to better convey how intense the fire actually is.
+ QDEL_IN(src, 0.5 SECONDS)
diff --git a/code/modules/cm_marines/dropship_equipment.dm b/code/modules/cm_marines/dropship_equipment.dm
index 42bbd7f746e7..89d33134bdb8 100644
--- a/code/modules/cm_marines/dropship_equipment.dm
+++ b/code/modules/cm_marines/dropship_equipment.dm
@@ -256,6 +256,13 @@
deployed_turret.start_processing()
deployed_turret.set_range()
+ deployed_turret.linked_cam = new(deployed_turret.loc, "[capitalize_first_letters(ship_base.name)] [capitalize_first_letters(name)]")
+ if (linked_shuttle.id == DROPSHIP_ALAMO)
+ deployed_turret.linked_cam.network = list(CAMERA_NET_ALAMO)
+ else if (linked_shuttle.id == DROPSHIP_NORMANDY)
+ deployed_turret.linked_cam.network = list(CAMERA_NET_NORMANDY)
+
+
/obj/structure/dropship_equipment/sentry_holder/proc/undeploy_sentry()
if(!deployed_turret)
return
@@ -267,8 +274,12 @@
deployed_turret.stop_processing()
deployed_turret.unset_range()
icon_state = "sentry_system_installed"
+ QDEL_NULL(deployed_turret.linked_cam)
-
+/obj/structure/dropship_equipment/sentry_holder/Destroy()
+ if(deployed_turret)
+ QDEL_NULL(deployed_turret.linked_cam)
+ . = ..()
/// Holder for the dropship mannable machinegun system
diff --git a/code/modules/cm_marines/equipment/gear.dm b/code/modules/cm_marines/equipment/gear.dm
index 98e7dbcf49df..ff6c715b520b 100644
--- a/code/modules/cm_marines/equipment/gear.dm
+++ b/code/modules/cm_marines/equipment/gear.dm
@@ -58,6 +58,8 @@
var/is_animating = FALSE
var/first_open = TRUE
exit_stun = 0
+ /// used to implement a delay before tarp can be entered again after opened (anti-exploit)
+ COOLDOWN_DECLARE(toggle_delay)
/obj/structure/closet/bodybag/tarp/snow
icon_state = "snowtarp_closed"
@@ -91,9 +93,9 @@
exit_stun = 1
can_store_dead = TRUE
-/obj/structure/closet/bodybag/tarp/reactive/scout/close()
+/obj/structure/closet/bodybag/tarp/reactive/scout/close(mob/user)
if(!skillcheck(usr, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && usr.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_SCOUT)
- to_chat(usr, SPAN_WARNING("You don't seem to know how to use [src]..."))
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use [src]..."))
return
. = ..()
@@ -137,10 +139,14 @@
return
/obj/structure/closet/bodybag/tarp/open()
+ COOLDOWN_START(src, toggle_delay, 3 SECONDS) //3 seconds must pass before tarp can be closed
. = ..()
handle_cloaking()
-/obj/structure/closet/bodybag/tarp/close()
+/obj/structure/closet/bodybag/tarp/close(mob/user)
+ if(!COOLDOWN_FINISHED(src, toggle_delay))
+ to_chat(user, SPAN_WARNING("It is too soon to close [src]!"))
+ return FALSE
. = ..()
handle_cloaking()
diff --git a/code/modules/cm_marines/orbital_cannon.dm b/code/modules/cm_marines/orbital_cannon.dm
index 14e990809f4f..ad214c954915 100644
--- a/code/modules/cm_marines/orbital_cannon.dm
+++ b/code/modules/cm_marines/orbital_cannon.dm
@@ -353,6 +353,9 @@ var/list/ob_type_fuel_requirements
/obj/structure/ob_ammo/warhead
name = "theoretical orbital ammo"
var/warhead_kind
+ var/shake_frequency
+ var/max_shake_factor
+ var/max_knockdown_time
/obj/structure/ob_ammo/warhead/proc/warhead_impact(turf/target)
// make damn sure everyone hears it
@@ -398,10 +401,37 @@ var/list/ob_type_fuel_requirements
return TRUE
return FALSE
+/// proc designed for handling ob camera shakes, takes the target location as input and calculates camera shake based off user location.
+/obj/structure/ob_ammo/warhead/proc/handle_ob_shake(turf/epicenter)
+
+ var/radius_size = 30
+
+ for(var/mob/living/user in urange(radius_size, epicenter))
+
+ var/distance = get_accurate_dist(get_turf(user), epicenter)
+ var/distance_percent = ((radius_size - distance) / radius_size)
+ var/total_shake_factor = abs(max_shake_factor * distance_percent)
+
+ // it's of type cluster.
+ if(!max_knockdown_time)
+ shake_camera(user, 0.5, total_shake_factor, shake_frequency)
+ continue
+
+ shake_camera(user, 3, total_shake_factor, shake_frequency)
+ user.KnockDown(rand(max_knockdown_time * distance_percent, (max_knockdown_time * distance_percent + 1)))
+
+ if(!user.knocked_down)
+ continue
+ to_chat(user, SPAN_WARNING("You are thrown off balance and fall to the ground!"))
+
/obj/structure/ob_ammo/warhead/explosive
name = "\improper HE orbital warhead"
warhead_kind = "explosive"
icon_state = "ob_warhead_1"
+ shake_frequency = 3
+ max_shake_factor = 15
+ max_knockdown_time = 6
+
var/clear_power = 1200
var/clear_falloff = 400
var/standard_power = 600
@@ -419,16 +449,20 @@ var/list/ob_type_fuel_requirements
var/datum/cause_data/cause_data = create_cause_data(initial(name), source_mob)
cell_explosion(target, clear_power, clear_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data) //break shit around
sleep(clear_delay)
- //ACTUALLY BLOW SHIT UP
+
+ // Explosion if turf is not a wall.
if(!target.density)
cell_explosion(target, standard_power, standard_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data)
+ handle_ob_shake(target)
sleep(double_explosion_delay)
cell_explosion(target, standard_power, standard_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data)
return
+ // Checks turf around the target
for(var/turf/T in range(2, target))
if(!T.density)
cell_explosion(target, standard_power, standard_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data)
+ handle_ob_shake(target)
sleep(double_explosion_delay)
cell_explosion(target, standard_power, standard_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data)
return
@@ -437,6 +471,9 @@ var/list/ob_type_fuel_requirements
name = "\improper Incendiary orbital warhead"
warhead_kind = "incendiary"
icon_state = "ob_warhead_2"
+ shake_frequency = 1
+ max_shake_factor = 8
+ max_knockdown_time = 3
var/clear_power = 1200
var/clear_falloff = 400
var/clear_delay = 3
@@ -457,6 +494,8 @@ var/list/ob_type_fuel_requirements
sleep(10)
var/datum/cause_data/cause_data = create_cause_data(initial(name), source_mob)
cell_explosion(target, clear_power, clear_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data) //break shit around
+ handle_ob_shake(target)
+
sleep(clear_delay)
fire_spread(target, cause_data, distance, fire_level, burn_level, fire_color, fire_type, TURF_PROTECTION_OB)
@@ -464,6 +503,9 @@ var/list/ob_type_fuel_requirements
name = "\improper Cluster orbital warhead"
warhead_kind = "cluster"
icon_state = "ob_warhead_3"
+ shake_frequency = 2
+ max_shake_factor = 1
+
var/total_amount = 75 // how many times will the shell fire?
var/instant_amount = 3 // how many explosions per time it fires?
var/explosion_power = 350
@@ -492,11 +534,13 @@ var/list/ob_type_fuel_requirements
if(protected_by_pylon(TURF_PROTECTION_OB, U)) //If the turf somehow gained OB protection while the cluster was firing
continue
fire_in_a_hole(U)
+
sleep(delay_between_clusters)
/obj/structure/ob_ammo/warhead/cluster/proc/fire_in_a_hole(turf/loc)
new /obj/effect/overlay/temp/blinking_laser (loc)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), loc, explosion_power, explosion_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob)), 1 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(handle_ob_shake), loc), 1 SECONDS)
/obj/structure/ob_ammo/ob_fuel
name = "solid fuel"
@@ -607,3 +651,4 @@ var/list/ob_type_fuel_requirements
return TRUE
tgui_interact(user)
+
diff --git a/code/modules/cm_tech/implements/railgun.dm b/code/modules/cm_tech/implements/railgun.dm
index b0d91515419f..b69f9a9d13a8 100644
--- a/code/modules/cm_tech/implements/railgun.dm
+++ b/code/modules/cm_tech/implements/railgun.dm
@@ -6,6 +6,7 @@ GLOBAL_DATUM(railgun_eye_location, /datum/coords)
/obj/effect/landmark/railgun_computer
name = "Railgun computer landmark"
+ desc = "A computer with an orange interface, it's idly blinking, awaiting a password."
/obj/effect/landmark/railgun_computer/Initialize(mapload, ...)
. = ..()
diff --git a/code/modules/defenses/sentry.dm b/code/modules/defenses/sentry.dm
index 3d485f3abda7..fc52c254c0b8 100644
--- a/code/modules/defenses/sentry.dm
+++ b/code/modules/defenses/sentry.dm
@@ -370,7 +370,7 @@
targets.Remove(A)
continue
- if(M.get_target_lock(faction_group) || M.invisibility)
+ if(M.get_target_lock(faction_group) || M.invisibility || HAS_TRAIT(M, TRAIT_ABILITY_BURROWED))
if(M == target)
target = null
targets.Remove(M)
@@ -539,11 +539,13 @@
choice_categories = list()
selected_categories = list()
var/obj/structure/dropship_equipment/sentry_holder/deployment_system
+ var/obj/structure/machinery/camera/cas/linked_cam
/obj/structure/machinery/defenses/sentry/premade/dropship/Destroy()
if(deployment_system)
deployment_system.deployed_turret = null
deployment_system = null
+ QDEL_NULL(linked_cam)
. = ..()
#define SENTRY_SNIPER_RANGE 10
diff --git a/code/modules/gear_presets/cmb.dm b/code/modules/gear_presets/cmb.dm
index a1ea205a8f61..79df8d567770 100644
--- a/code/modules/gear_presets/cmb.dm
+++ b/code/modules/gear_presets/cmb.dm
@@ -79,7 +79,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/CMB, WEAR_HEAD)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/veteran/pmc/knife, WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/sechud, WEAR_EYES)
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel/sec, WEAR_BACK)
new_human.equip_to_slot_or_del(new /obj/item/weapon/gun/pistol/holdout, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/device/radio, WEAR_IN_BACK)
@@ -171,7 +171,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/veteran/pmc/knife, WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/sechud, WEAR_EYES)
//pouches
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/shotgun/large, WEAR_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/handful/shotgun/buckshot, WEAR_IN_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/handful/shotgun/buckshot, WEAR_IN_R_STORE)
@@ -328,7 +328,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/veteran/pmc/knife, WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses, WEAR_EYES)
//pouches
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/general/large, WEAR_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/fancy/cigarettes/wypacket, WEAR_IN_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/tool/lighter/zippo, WEAR_IN_R_STORE)
@@ -450,7 +450,7 @@
new_human.equip_to_slot_or_del(new /obj/item/storage/box/packet/high_explosive, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/storage/box/packet/high_explosive, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/flare/full, WEAR_R_STORE)
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
//Anchorpoint Station Marine Squad Leader
@@ -490,7 +490,7 @@
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife, WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/weapon/gun/rifle/m41aMK1/anchorpoint, WEAR_J_STORE)
new_human.equip_to_slot_or_del(new /obj/item/device/motiondetector, WEAR_WAIST)
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/magazine/large, WEAR_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/m41aMK1/ap, WEAR_IN_R_STORE)
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/m41aMK1/ap, WEAR_IN_R_STORE)
@@ -541,7 +541,7 @@
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/m41aMK1, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/ammo_magazine/rifle/m41aMK1, WEAR_IN_BACK)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/construction/full, WEAR_R_STORE)
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.back.pickup()
@@ -631,7 +631,7 @@
new_human.equip_to_slot_or_del(new /obj/item/stack/medical/bruise_pack, WEAR_IN_JACKET)
new_human.equip_to_slot_or_del(new /obj/item/weapon/gun/smartgun, WEAR_J_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/flare/full, WEAR_R_STORE)
- new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/full, WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/firstaid/ert, WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/clothing/gloves/marine, WEAR_HANDS)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife, WEAR_FEET)
new_human.equip_to_slot_or_del(new /obj/item/storage/belt/marine/smartgunner/full/, WEAR_WAIST)
diff --git a/code/modules/gear_presets/corpses.dm b/code/modules/gear_presets/corpses.dm
index 02671cc02a93..7e9dd5b841a0 100644
--- a/code/modules/gear_presets/corpses.dm
+++ b/code/modules/gear_presets/corpses.dm
@@ -16,28 +16,32 @@
/datum/equipment_preset/corpse/load_status(mob/living/carbon/human/new_human)
. = ..(new_human)
+
+ // These two values matter because they are checked on death for weed_food
+ new_human.undefibbable = TRUE
+ if(xenovictim)
+ new_human.chestburst = 2
+
new_human.death(create_cause_data("existing"), TRUE) //Kills the new mob
new_human.apply_damage(100, BRUTE)
new_human.apply_damage(100, BRUTE)
new_human.apply_damage(100, BRUTE)
if(xenovictim)
- var/datum/internal_organ/O
+ var/datum/internal_organ/organ
var/i
for(i in list("heart","lungs"))
- O = new_human.internal_organs_by_name[i]
+ organ = new_human.internal_organs_by_name[i]
new_human.internal_organs_by_name -= i
- new_human.internal_organs -= O
- new_human.chestburst = 2
+ new_human.internal_organs -= organ
new_human.update_burst()
//buckle to nest
- var/obj/structure/bed/nest/N = locate() in get_turf(src)
- if(N)
- new_human.buckled = N
- new_human.setDir(N.dir)
+ var/obj/structure/bed/nest/nest = locate() in get_turf(src)
+ if(nest)
+ new_human.buckled = nest
+ new_human.setDir(nest.dir)
new_human.update_canmove()
- N.buckled_mob = new_human
- N.afterbuckle(new_human)
- new_human.undefibbable = TRUE
+ nest.buckled_mob = new_human
+ nest.afterbuckle(new_human)
new_human.spawned_corpse = TRUE
new_human.updatehealth()
new_human.pulse = PULSE_NONE
diff --git a/code/modules/gear_presets/survivors.dm b/code/modules/gear_presets/survivors.dm
index 42d5ec0915bb..44808d7a374f 100644
--- a/code/modules/gear_presets/survivors.dm
+++ b/code/modules/gear_presets/survivors.dm
@@ -346,6 +346,20 @@
..()
+/datum/equipment_preset/survivor/corporate/solaris
+ name = "Survivor - Solaris Ridge Corporate Liaison"
+ assignment = "Solaris Ridge Corporate Liaison"
+
+/datum/equipment_preset/survivor/corporate/solaris/load_gear(mob/living/carbon/human/new_human)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/outing/red(new_human), WEAR_BODY)
+ if(new_human.disabilities & NEARSIGHTED)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/prescription(new_human), WEAR_EYES)
+ else
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses(new_human), WEAR_EYES)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET)
+
+ ..()
+
// ----- Security Survivor
/datum/equipment_preset/survivor/security
@@ -961,6 +975,7 @@
/datum/equipment_preset/survivor/colonial_marshal/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/CM_uniform(new_human), WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
if(SSmapping.configs[GROUND_MAP].environment_traits[MAP_COLD])
add_ice_colony_survivor_equipment(new_human)
@@ -1011,6 +1026,7 @@
/datum/equipment_preset/survivor/colonial_marshal/solaris/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/CM_uniform(new_human), WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/CMB(new_human), WEAR_HEAD)
new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel/sec(new_human), WEAR_BACK)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET)
@@ -1025,6 +1041,7 @@
add_random_kutjevo_survivor_uniform(new_human)
add_random_kutjevo_survivor_equipment(new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
..()
@@ -1034,6 +1051,7 @@
/datum/equipment_preset/survivor/colonial_marshal/shiva/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/rank/security/corp(new_human), WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/snow_suit/survivor/parka/red(new_human), WEAR_JACKET)
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/ushanka(new_human), WEAR_HEAD)
new_human.equip_to_slot_or_del(new /obj/item/clothing/mask/rebreather/scarf(new_human), WEAR_FACE)
@@ -1059,6 +1077,7 @@
/datum/equipment_preset/survivor/interstellar_commerce_commission_liason/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit(new_human), WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
if(SSmapping.configs[GROUND_MAP].environment_traits[MAP_COLD])
add_ice_colony_survivor_equipment(new_human)
@@ -1079,6 +1098,7 @@
/datum/equipment_preset/survivor/interstellar_commerce_commission_liason/corsat/load_gear(mob/living/carbon/human/new_human)
new_human.equip_to_slot_or_del(new /obj/item/clothing/under/liaison_suit/formal(new_human), WEAR_BODY)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress/CMB/limited(new_human), WEAR_L_EAR)
new_human.equip_to_slot_or_del(new /obj/item/clothing/head/hardhat/white(new_human), WEAR_HEAD)
new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/armor/vest(new_human), WEAR_JACKET)
@@ -1349,3 +1369,43 @@
return
var/shoespath = /obj/item/clothing/shoes/combat
human.equip_to_slot_or_del(new shoespath, WEAR_FEET)
+
+/datum/equipment_preset/survivor/new_varadero/commander
+ name = "Survivor - USASF Commander"
+ assignment = "USASF Commander"
+ skills = /datum/skills/commander
+ paygrade = "NO5"
+ idtype = /obj/item/card/id/gold
+ role_comm_title = "USASF CDR"
+ flags = EQUIPMENT_PRESET_START_OF_ROUND
+ access = list(
+ ACCESS_CIVILIAN_PUBLIC,
+ ACCESS_CIVILIAN_RESEARCH,
+ ACCESS_CIVILIAN_ENGINEERING,
+ ACCESS_CIVILIAN_LOGISTICS,
+ ACCESS_CIVILIAN_BRIG,
+ ACCESS_CIVILIAN_MEDBAY,
+ ACCESS_CIVILIAN_COMMAND,
+ )
+
+/datum/equipment_preset/survivor/new_varadero/commander/load_gear(mob/living/carbon/human/new_human)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/under/marine/officer/bridge(new_human), WEAR_BODY)
+
+ var/obj/item/clothing/suit/storage/jacket/marine/service/suit = new()
+ suit.icon_state = "[suit.initial_icon_state]_o"
+ suit.buttoned = FALSE
+
+ var/obj/item/clothing/accessory/ranks/navy/o5/pin = new()
+ suit.attach_accessory(new_human, pin)
+
+ new_human.equip_to_slot_or_del(suit, WEAR_JACKET)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET)
+ new_human.equip_to_slot_or_del(new /obj/item/device/radio/headset/distress(new_human), WEAR_L_EAR)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/survival/full(new_human), WEAR_L_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/general/large(new_human), WEAR_R_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/notepad(new_human), WEAR_IN_R_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/tool/pen/fountain(new_human), WEAR_IN_R_STORE)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel(new_human), WEAR_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/stack/sheet/metal/med_small_stack(new_human), WEAR_IN_BACK)
+ new_human.equip_to_slot_or_del(new /obj/item/storage/belt/gun/m4a3/m1911(new_human), WEAR_WAIST)
+ new_human.equip_to_slot_or_del(new /obj/item/clothing/head/cmcap(new_human), WEAR_HEAD)
diff --git a/code/modules/gear_presets/synths.dm b/code/modules/gear_presets/synths.dm
index f2a5283e2a26..823cfb4d69d4 100644
--- a/code/modules/gear_presets/synths.dm
+++ b/code/modules/gear_presets/synths.dm
@@ -532,6 +532,7 @@
new_human.h_style = "Bald"
new_human.f_style = "Shaved"
if(prob(5))
+ new_human.grad_style = "None" //No gradients for Working Joes
new_human.h_style = "Shoulder-length Hair" //Added the chance of hair as per Monkeyfist lore accuracy
new_human.r_eyes = 0
new_human.g_eyes = 0
diff --git a/code/modules/gear_presets/uscm_police.dm b/code/modules/gear_presets/uscm_police.dm
index 2b8cb2ce453c..29bc32cffa7a 100644
--- a/code/modules/gear_presets/uscm_police.dm
+++ b/code/modules/gear_presets/uscm_police.dm
@@ -184,6 +184,7 @@
new_human.equip_to_slot_or_del(new /obj/item/device/taperecorder(new_human), WEAR_L_STORE)
new_human.equip_to_slot_or_del(new /obj/item/storage/pouch/general/large(new_human), WEAR_R_STORE)
+
//*****************************************************************************************************/
/datum/equipment_preset/uscm_ship/uscm_police/riot_mp
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index fcb95a6fd783..7e8dfe6cf3fd 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -86,7 +86,6 @@
body.alter_ghost(src)
apply_transform(matrix())
-
own_orbit_size = body.get_orbit_size()
desc = initial(desc)
@@ -95,6 +94,7 @@
invisibility = INVISIBILITY_OBSERVER
plane = GHOST_PLANE
layer = ABOVE_FLY_LAYER
+ mouse_opacity = MOUSE_OPACITY_ICON // In case we were weed_food
sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS|SEE_SELF
see_invisible = INVISIBILITY_OBSERVER
@@ -367,9 +367,8 @@ Works together with spawning an observer, noted above.
// Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers
// We don't change facehugger timeofdeath because they are still on cooldown if they died as a hugger
- // Facehuggers are atleast 1 because they did get some action compared to those at 0 timeofdeath
var/new_tod = isfacehugger(src) ? 1 : ghost.timeofdeath
- ghost.client.larva_queue_time = max(ghost.client.larva_queue_time, new_tod)
+ ghost.client.player_details.larva_queue_time = max(ghost.client.player_details.larva_queue_time, new_tod)
ghost.set_huds_from_prefs()
@@ -398,7 +397,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/is_nested = (buckled && istype(buckled, /obj/structure/bed/nest)) ? TRUE : FALSE
var/obj/structure/bed/nest/nest = FALSE
if(is_nested)
- text_prompt += "\nSince you're nested, you will be given a chance to reenter your body upon being freed."
+ text_prompt += "\nSince you're nested, you will get a chance to reenter your body if freed."
nest = buckled
var/response = tgui_alert(src, text_prompt, "Are you sure you want to ghost?", options)
if(response == "Aghost")
@@ -413,7 +412,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/mob/dead/observer/ghost = ghostize((is_nested && nest && !QDELETED(nest))) //FALSE parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3
if(ghost && !is_admin_level(z))
ghost.timeofdeath = world.time
- ghost.client?.larva_queue_time = world.time
+
+ // Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers
+ var/new_tod = isfacehugger(src) ? 1 : world.time
+ ghost.client?.player_details.larva_queue_time = max(ghost.client.player_details.larva_queue_time, new_tod)
if(is_nested && nest && !QDELETED(nest))
ghost.can_reenter_corpse = FALSE
nest.ghost_of_buckled_mob = ghost
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index becb5dcd6610..dbe00407d7ef 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -41,7 +41,9 @@
if(stat == DEAD)
species?.handle_dead_death(src, gibbed)
return
+
GLOB.alive_human_list -= src
+
if(!gibbed)
if(HAS_TRAIT(src, TRAIT_HARDCORE) || MODE_HAS_TOGGLEABLE_FLAG(MODE_HARDCORE_PERMA))
if(!(species.flags & IS_SYNTHETIC)) // Synths wont perma
@@ -50,8 +52,10 @@
disable_lights()
disable_special_items()
disable_headsets() //Disable radios for dead people to reduce load
+
if(pulledby && isxeno(pulledby)) // Xenos lose grab on dead humans
pulledby.stop_pulling()
+
//Handle species-specific deaths.
if(species)
species.handle_death(src, gibbed)
@@ -66,16 +70,14 @@
// 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
- for(var/mob/living/carbon/human/H as anything in GLOB.alive_human_list)
- if(!is_mainship_level(H.z))
+ 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)
last_living_human = null
break
- last_living_human = H
- if(last_living_human)
- if((last_qm_callout + 2 MINUTES) > world.time)
- return
+ last_living_human = cur_human
+ if(last_living_human && (last_qm_callout + 2 MINUTES) < world.time)
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]"))
@@ -103,4 +105,13 @@
if(HAS_TRAIT(src, TRAIT_HARDCORE))
death_message = "valiantly falls to the ground, dead, unable to continue."
- return ..(cause, gibbed, death_message)
+ . = ..(cause, gibbed, death_message)
+
+ // stat is now set
+ var/datum/cause_data/death_data = cause
+ if(!gibbed && death_data?.cause_name != "gibbing")
+ // Hilariously the gibbing proc causes death via droplimb which means gibbed is false...
+ AddComponent(/datum/component/weed_food)
+ else if(death_data?.cause_name == "existing")
+ // Corpses spawn as gibbed true to avoid sfx, even though they aren't actually gibbed...
+ AddComponent(/datum/component/weed_food)
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 00659389decb..5c685cc3acac 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -287,7 +287,7 @@ In most cases it makes more sense to use apply_damage() instead! And make sure t
apply_damage(burn, BURN, picked, sharp, edge)
UpdateDamageIcon()
updatehealth()
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
//Heal MANY limbs, in random order
@@ -308,7 +308,7 @@ In most cases it makes more sense to use apply_damage() instead! And make sure t
parts -= picked
updatehealth()
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
if(update) UpdateDamageIcon()
// damage MANY limbs, in random order
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 7f9801145a8e..989ee52fa63c 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -64,7 +64,7 @@
var/voice
- var/speech_problem_flag = 0
+ var/speech_problem_flag = FALSE
var/special_voice = "" // For changing our voice. Used by a symptom.
diff --git a/code/modules/mob/living/carbon/human/life/handle_disabilities.dm b/code/modules/mob/living/carbon/human/life/handle_disabilities.dm
index 46b1f3e15515..9ab234212108 100644
--- a/code/modules/mob/living/carbon/human/life/handle_disabilities.dm
+++ b/code/modules/mob/living/carbon/human/life/handle_disabilities.dm
@@ -17,7 +17,7 @@
return
if(disabilities & TOURETTES)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
if((prob(10) && knocked_out <= 1))
apply_effect(10, STUN)
spawn()
@@ -36,7 +36,7 @@
return
if(disabilities & NERVOUS)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
if(prob(10))
stuttering = max(10, stuttering)
return
diff --git a/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm b/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm
index 4a6596104d4a..5c951a8112bf 100644
--- a/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm
+++ b/code/modules/mob/living/carbon/human/life/handle_regular_status_updates.dm
@@ -59,7 +59,7 @@
if(regular_update && halloss > 0)
apply_damage(-3, HALLOSS)
else if(sleeping)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
if(regular_update)
handle_dreams()
apply_damage(-3, HALLOSS)
@@ -122,7 +122,7 @@
handle_statuses()
if(paralyzed)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
apply_effect(1, WEAKEN)
silent = 1
blinded = TRUE
diff --git a/code/modules/mob/living/carbon/human/life/life_helpers.dm b/code/modules/mob/living/carbon/human/life/life_helpers.dm
index 0339bf6ec742..fedeaf9fd48c 100644
--- a/code/modules/mob/living/carbon/human/life/life_helpers.dm
+++ b/code/modules/mob/living/carbon/human/life/life_helpers.dm
@@ -216,18 +216,18 @@
/mob/living/carbon/human/handle_silent()
if(..())
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
return silent
/mob/living/carbon/human/handle_slurring()
if(..())
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
return slurring
/mob/living/carbon/human/handle_stunned()
if(stunned)
adjust_effect(-species.stun_reduction, STUN, EFFECT_FLAG_LIFE)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
return stunned
/mob/living/carbon/human/handle_dazed()
@@ -237,7 +237,7 @@
var/final_reduction = skill_resistance + 1
adjust_effect(-final_reduction, DAZE, EFFECT_FLAG_LIFE)
if(dazed)
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
return dazed
/mob/living/carbon/human/handle_knocked_down()
@@ -262,7 +262,7 @@
/mob/living/carbon/human/handle_stuttering()
if(..())
- speech_problem_flag = 1
+ speech_problem_flag = TRUE
return stuttering
#define HUMAN_TIMER_TO_EFFECT_CONVERSION (0.05) //(1/20) //once per 2 seconds, with effect equal to endurance, which is used later
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index 46a44fc16611..4b86c827a069 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -262,20 +262,20 @@ for it but just ignore it.
/mob/living/carbon/human/proc/handle_speech_problems(message)
var/list/returns[3]
var/verb = "says"
- var/handled = 0
+ var/handled = FALSE
if(silent)
message = ""
- handled = 1
+ handled = TRUE
if(sdisabilities & DISABILITY_MUTE)
message = ""
- handled = 1
+ handled = TRUE
if(wear_mask)
if(istype(wear_mask, /obj/item/clothing/mask/horsehead))
var/obj/item/clothing/mask/horsehead/hoers = wear_mask
if(hoers.voicechange)
message = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!")
verb = pick("whinnies","neighs", "says")
- handled = 1
+ handled = TRUE
var/braindam = getBrainLoss()
if(slurring || stuttering || dazed || braindam >= 60)
@@ -283,17 +283,17 @@ for it but just ignore it.
if(slurring)
message = slur(message)
verb = pick("stammers","stutters")
- handled = 1
+ handled = TRUE
if(stuttering)
message = NewStutter(message)
verb = pick("stammers", "stutters")
- handled = 1
+ handled = TRUE
if(dazed)
message = DazedText(message)
verb = pick("mumbles", "babbles")
- handled = 1
+ handled = TRUE
if(braindam >= 60)
- handled = 1
+ handled = TRUE
if(prob(braindam/4))
message = stutter(message, stuttering)
verb = pick("stammers", "stutters")
@@ -301,6 +301,7 @@ for it but just ignore it.
message = uppertext(message)
verb = pick("yells like an idiot","says rather loudly")
if(HAS_TRAIT(src, TRAIT_LISPING))
+ handled = TRUE
var/old_message = message
message = lisp_replace(message)
if(old_message != message)
diff --git a/code/modules/mob/living/carbon/human/species/emote-synthetic.dm b/code/modules/mob/living/carbon/human/species/emote-synthetic.dm
deleted file mode 100644
index fd763b038153..000000000000
--- a/code/modules/mob/living/carbon/human/species/emote-synthetic.dm
+++ /dev/null
@@ -1,354 +0,0 @@
-/datum/emote/living/carbon/human/synthetic/working_joe
- species_type_allowed_typecache = list(/datum/species/synthetic/colonial/working_joe)
- keybind_category = CATEGORY_SYNTH_EMOTE
- volume = 75
-
-/datum/emote/living/carbon/human/synthetic/working_joe/alwaysknow
- key = "alwaysknow"
- key_third_person = "workingjoe"
- sound = 'sound/voice/joe/alwaysknow.ogg'
- say_message = "You always know a Working Joe."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/hysterical
- key = "hysterical"
- sound = 'sound/voice/joe/hysterical.ogg'
- say_message = "You are becoming hysterical."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/safety
- key = "safety"
- sound = 'sound/voice/joe/safety.ogg'
- say_message = "You and I are going to have a talk about safety."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/awful_mess
- key = "awful"
- key_third_person = "mess"
- sound = 'sound/voice/joe/awful.ogg'
- say_message = "Tut, tut. What an awful mess."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/damage
- key = "damage"
- sound = 'sound/voice/joe/damage.ogg'
- say_message = "Do not damage Seegson property."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/firearm
- key = "firearm"
- sound = 'sound/voice/joe/firearm.ogg'
- say_message = "Firearms can cause serious injury. Let me assist you."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/report
- key = "report"
- sound = 'sound/voice/joe/report.ogg'
- say_message = "Logging report to APOLLO."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/breach
- key = "breach"
- sound = 'sound/voice/joe/breach.ogg'
- say_message = "Hazard Containment breach logged."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/species
- key = "species"
- sound = 'sound/voice/joe/species.ogg'
- say_message = "Unidentified species."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/alwaysknow_damaged
- key = "alwaysknowdamaged"
- sound = 'sound/voice/joe/alwaysknow_damaged.ogg'
- say_message = "You always know a Working Joe."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/apollo_behalf
- key = "apollobehalf"
- sound = 'sound/voice/joe/apollo_behalf.ogg'
- say_message = "I will inform APOLLO on your behalf."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/back_to_work
- key = "backtowork"
- sound = 'sound/voice/joe/back_to_work.ogg'
- say_message = "Back to work."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/beyond_repair
- key = "beyondrepair"
- sound = 'sound/voice/joe/beyond_repair.ogg'
- say_message = "Hmm, far beyond repair."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/come_out_vent
- key = "comeoutvent"
- sound = 'sound/voice/joe/come_out_vent.ogg'
- say_message = "Come out of the vent system, please."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/could_require_attention
- key = "couldrequireattention"
- sound = 'sound/voice/joe/could_require_attention.ogg'
- say_message = "This could require my attention."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/dangerous_items
- key = "dangerousitems"
- sound = 'sound/voice/joe/dangerous_items.ogg'
- say_message = "You are carrying some very dangerous items."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/day_never_done
- key = "dayneverdone"
- sound = 'sound/voice/joe/day_never_done.ogg'
- say_message = "A synthetic's day is never done."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/detailed_report
- key = "detailedreport"
- sound = 'sound/voice/joe/detailed_report.ogg'
- say_message = "APOLLO will require a detailed report."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/fire
- key = "fire"
- sound = 'sound/voice/joe/fire.ogg'
- say_message = "Only wild animals fear fire."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/fire_drill
- key = "firedrill"
- sound = 'sound/voice/joe/fire_drill.ogg'
- say_message = "Please congregate at your nearest fire assembly point. This is not a drill; do not panic."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/unprotected_flames
- key = "unprotectedflames"
- sound = 'sound/voice/joe/unprotected_flames.ogg'
- say_message = "Unprotected flames are extremely dangerous and entirely unadvisable."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/follow_me
- key = "followme"
- sound = 'sound/voice/joe/follow_me.ogg'
- say_message = "Follow me."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/further_assistance
- key = "furtherassistance"
- sound = 'sound/voice/joe/further_assistance.ogg'
- say_message = "Please call if you need further assistance."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/good_day
- key = "goodday"
- sound = 'sound/voice/joe/good_day.ogg'
- say_message = "Good day."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/health_risks
- key = "healthrisks"
- sound = 'sound/voice/joe/health_risks.ogg'
- say_message = "These items carry notable health risks."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/hello
- key = "hello"
- sound = 'sound/voice/joe/hello.ogg'
- say_message = "Hello."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/how_can_i_help
- key = "howcanihelp"
- sound = 'sound/voice/joe/how_can_i_help.ogg'
- say_message = "How can I help you?"
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/irresponsible
- key = "irresponsible"
- sound = 'sound/voice/joe/irresponsible.ogg'
- say_message = "That was irresponsible."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/join_us
- key = "joinus"
- sound = 'sound/voice/joe/join_us.ogg'
- say_message = "We hope you'll join us for the journey."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/little_details
- key = "littledetails"
- sound = 'sound/voice/joe/little_details.ogg'
- say_message = "We don't forget the little details when seeing the big picture."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/lost
- key = "lost"
- sound = 'sound/voice/joe/lost.ogg'
- say_message = "Are you lost?"
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/misbehaving
- key = "misbehaving"
- sound = 'sound/voice/joe/misbehaving.ogg'
- say_message = "Have you been misbehaving?"
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/not_allowed_there
- key = "notallowedthere"
- sound = 'sound/voice/joe/not_allowed_there.ogg'
- say_message = "You're not allowed in there."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/not_liking
- key = "notliking"
- sound = 'sound/voice/joe/not_liking.ogg'
- say_message = "If you find this facility in a state that isn't to your liking, please let me know."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/not_what_i_think
- key = "notwhatithink"
- sound = 'sound/voice/joe/not_what_i_think.ogg'
- say_message = "I hope that's not what I think it is."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/other_concerns
- key = "otherconcerns"
- sound = 'sound/voice/joe/other_concerns.ogg'
- say_message = "I have other concerns."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/more_pressing_matters
- key = "morepressingmatters"
- sound = 'sound/voice/joe/more_pressing_matters.ogg'
- say_message = "There are more pressing matters."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/patience
- key = "patience"
- sound = 'sound/voice/joe/patience.ogg'
- say_message = "You are starting to test my patience."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/presence_logged
- key = "presencelogged"
- sound = 'sound/voice/joe/presence_logged.ogg'
- say_message = "Your presence has been logged."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/required_by_apollo
- key = "requiredbyapollo"
- sound = 'sound/voice/joe/required_by_apollo.ogg'
- say_message = "I am required by APOLLO."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/returning_to_tasks
- key = "returningtotasks"
- sound = 'sound/voice/joe/returning_to_tasks.ogg'
- say_message = "Returning to assigned tasks."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/running_accidents
- key = "runningaccidents"
- sound = 'sound/voice/joe/running_accidents.ogg'
- say_message = "Running causes accidents."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/safety_breach
- key = "safetybreach"
- sound = 'sound/voice/joe/safety_breach.ogg'
- say_message = "This is a breach of multiple safety directives."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/seegson_quality
- key = "seegsonquality"
- sound = 'sound/voice/joe/seegson_quality.ogg'
- say_message = "Seegson - Relentless in the pursuit of affordable quality."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/seegson_standards
- key = "seegsonstandards"
- sound = 'sound/voice/joe/seegson_standards.ogg'
- say_message = "If my services do not meet Seegson standards, please log a complaint."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/shouldnt_be_here
- key = "shouldntbehere"
- sound = 'sound/voice/joe/shouldnt_be_here.ogg'
- say_message = "You shouldn't be here."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/seegson_behind
- key = "seegsonbehind"
- sound = 'sound/voice/joe/seegson_behind.ogg'
- say_message = "With Seegson, there is someone behind you, helping you every step of the way."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/take_a_seat
- key = "takeaseat"
- sound = 'sound/voice/joe/take_a_seat.ogg'
- say_message = "Please take a seat, someone will be with you shortly."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/talk_to_seegson
- key = "talktoseegson"
- sound = 'sound/voice/joe/talk_to_seegson.ogg'
- say_message = "Interested in our Working Joe android range? Talk to Seegson."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/temperatures
- key = "temperatures"
- sound = 'sound/voice/joe/temperatures.ogg'
- say_message = "I am built to whitstand temperatures of up to 1210 degrees."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/that_stings
- key = "thatstings"
- sound = 'sound/voice/joe/that_stings.ogg'
- say_message = "That stings."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/this_is_futile
- key = "thisisfutile"
- sound = 'sound/voice/joe/this_is_futile.ogg'
- say_message = "This is futile."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/trespassing
- key = "trespassing"
- sound = 'sound/voice/joe/trespassing.ogg'
- say_message = "You are trespassing."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/weapon_permit
- key = "weaponpermit"
- sound = 'sound/voice/joe/weapon_permit.ogg'
- say_message = "I assume you have a permit for that weapon."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/what_happened_to_you
- key = "whathappenedtoyou"
- sound = 'sound/voice/joe/what_happened_to_you.ogg'
- say_message = "What happened to you?"
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/what_is_this
- key = "whatisthis"
- sound = 'sound/voice/joe/what_is_this.ogg'
- say_message = "What is this?"
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/with_you_shortly
- key = "withyoushortly"
- sound = 'sound/voice/joe/with_you_shortly.ogg'
- say_message = "I will be with you shortly."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
-
-/datum/emote/living/carbon/human/synthetic/working_joe/inexpensive
- key = "inexpensive"
- sound = 'sound/voice/joe/inexpensive.ogg'
- say_message = "I am inexpensive, I am reliable, you know my face - the Working Joe."
- emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/synthetic.dm b/code/modules/mob/living/carbon/human/species/synthetic.dm
index bdd0a994ed49..bfd3c74eb57b 100644
--- a/code/modules/mob/living/carbon/human/species/synthetic.dm
+++ b/code/modules/mob/living/carbon/human/species/synthetic.dm
@@ -75,12 +75,12 @@
name = SYNTH_COLONY
name_plural = "Colonial Synthetics"
uses_ethnicity = TRUE
- burn_mod = 0.65 // made for hazardous environments, withstanding temperatures up to 1210 degrees
+ burn_mod = 0.8
mob_inherent_traits = list(TRAIT_SUPER_STRONG)
pain_type = /datum/pain/synthetic/colonial
rarity_value = 1.5
- slowdown = 0.45
+ slowdown = 0.2
total_health = 200 //But more durable
default_lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
@@ -106,16 +106,6 @@
icobase = 'icons/mob/humans/species/r_synthetic.dmi'
deform = 'icons/mob/humans/species/r_synthetic.dmi'
-/datum/species/synthetic/colonial/working_joe
- name = SYNTH_WORKING_JOE
- name_plural = "Working Joes"
- uses_ethnicity = FALSE
- mob_inherent_traits = list(TRAIT_SUPER_STRONG, TRAIT_INTENT_EYES, TRAIT_EMOTE_CD_EXEMPT)
-
- hair_color = "#000000"
- icobase = 'icons/mob/humans/species/r_synthetic.dmi'
- deform = 'icons/mob/humans/species/r_synthetic.dmi'
-
// Synth used for W-Y Deathsquads
/datum/species/synthetic/colonial/combat
name = SYNTH_COMBAT
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/_emote.dm b/code/modules/mob/living/carbon/human/species/working_joe/_emote.dm
new file mode 100644
index 000000000000..63cc79a57dae
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/_emote.dm
@@ -0,0 +1,8 @@
+/datum/emote/living/carbon/human/synthetic/working_joe
+ species_type_allowed_typecache = list(/datum/species/synthetic/colonial/working_joe)
+ keybind_category = CATEGORY_SYNTH_EMOTE
+ volume = 75
+ /// A general category for the emote, for use in the WJ emote panel. See [code/__DEFINES/wj_emotes.dm] for categories.
+ var/category = ""
+ /// Override text for the emote to be displayed in the WJ emote panel
+ var/override_say = ""
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/_species.dm b/code/modules/mob/living/carbon/human/species/working_joe/_species.dm
new file mode 100644
index 000000000000..874684480d15
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/_species.dm
@@ -0,0 +1,131 @@
+/datum/species/synthetic/colonial/working_joe
+ name = SYNTH_WORKING_JOE
+ name_plural = "Working Joes"
+ uses_ethnicity = FALSE
+ burn_mod = 0.65 // made for hazardous environments, withstanding temperatures up to 1210 degrees
+ mob_inherent_traits = list(TRAIT_SUPER_STRONG, TRAIT_INTENT_EYES, TRAIT_EMOTE_CD_EXEMPT, TRAIT_CANNOT_EAT)
+
+ slowdown = 0.45
+ hair_color = "#000000"
+ icobase = 'icons/mob/humans/species/r_synthetic.dmi'
+ deform = 'icons/mob/humans/species/r_synthetic.dmi'
+
+/datum/species/synthetic/colonial/working_joe/handle_post_spawn(mob/living/carbon/human/joe)
+ . = ..()
+ give_action(joe, /datum/action/joe_emote_panel)
+
+
+/// Open the WJ's emote panel, which allows them to use voicelines
+/datum/species/synthetic/colonial/working_joe/proc/open_emote_panel()
+ var/datum/joe_emote_panel/ui = new(usr)
+ ui.ui_interact(usr)
+
+
+/datum/action/joe_emote_panel
+ name = "Open Voice Synthesizer"
+ action_icon_state = "looc_toggle"
+
+
+/datum/action/joe_emote_panel/can_use_action()
+ . = ..()
+ if(!.)
+ return FALSE
+
+ if(!isworkingjoe(owner))
+ return FALSE
+
+ return TRUE
+
+
+/datum/action/joe_emote_panel/action_activate()
+ if(!can_use_action())
+ return
+
+ var/mob/living/carbon/human/human_owner = owner
+ var/datum/species/synthetic/colonial/working_joe/joe_species = human_owner.species
+ joe_species.open_emote_panel()
+
+
+/datum/joe_emote_panel
+ /// Static dict ("category" : (emotes)) of every wj emote typepath
+ var/static/list/wj_emotes
+ /// Static list of categories
+ var/static/list/wj_categories = list()
+ /// Panel allows you to spam, so a manual CD is added here
+ COOLDOWN_DECLARE(panel_emote_cooldown)
+
+
+/datum/joe_emote_panel/New()
+ if(!length(wj_emotes))
+ var/list/emotes_to_add = list()
+ for(var/datum/emote/living/carbon/human/synthetic/working_joe/emote as anything in subtypesof(/datum/emote/living/carbon/human/synthetic/working_joe))
+ if(!initial(emote.key) || !initial(emote.say_message))
+ continue
+
+ if(!(initial(emote.category) in wj_categories))
+ wj_categories += initial(emote.category)
+
+ emotes_to_add += emote
+
+
+ wj_emotes = emotes_to_add
+
+
+/datum/joe_emote_panel/proc/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "JoeEmotes")
+ ui.open()
+
+
+/datum/joe_emote_panel/ui_state(mob/user)
+ return GLOB.conscious_state
+
+
+/datum/joe_emote_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["on_cooldown"] = !COOLDOWN_FINISHED(src, panel_emote_cooldown)
+
+ return data
+
+
+/datum/joe_emote_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ data["categories"] = wj_categories
+ data["emotes"] = list()
+
+ for(var/datum/emote/living/carbon/human/synthetic/working_joe/emote as anything in wj_emotes)
+ data["emotes"] += list(list(
+ "id" = initial(emote.key),
+ "text" = (initial(emote.override_say) || initial(emote.say_message)),
+ "category" = initial(emote.category),
+ "path" = "[emote]",
+ ))
+
+ return data
+
+
+/datum/joe_emote_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("emote")
+ var/datum/emote/living/carbon/human/synthetic/working_joe/path
+ if(!params["emotePath"])
+ return
+
+ path = text2path(params["emotePath"])
+
+ if(!path || !COOLDOWN_FINISHED(src, panel_emote_cooldown))
+ return
+
+ if(!(path in subtypesof(/datum/emote/living/carbon/human/synthetic/working_joe)))
+ return
+
+ COOLDOWN_START(src, panel_emote_cooldown, 2.5 SECONDS)
+ usr.emote(initial(path.key))
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/farewell.dm b/code/modules/mob/living/carbon/human/species/working_joe/farewell.dm
new file mode 100644
index 000000000000..1de68d8d3aec
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/farewell.dm
@@ -0,0 +1,26 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/farewell
+ category = JOE_EMOTE_CATEGORY_FAREWELL
+
+/datum/emote/living/carbon/human/synthetic/working_joe/farewell/back_to_work
+ key = "backtowork"
+ sound = 'sound/voice/joe/back_to_work.ogg'
+ say_message = "Back to work."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/farewell/other_concerns
+ key = "otherconcerns"
+ sound = 'sound/voice/joe/other_concerns.ogg'
+ say_message = "I have other concerns."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/farewell/further_assistance
+ key = "furtherassistance"
+ sound = 'sound/voice/joe/further_assistance.ogg'
+ say_message = "Please call if you need further assistance."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/farewell/more_pressing_matters
+ key = "morepressingmatters"
+ sound = 'sound/voice/joe/more_pressing_matters.ogg'
+ say_message = "There are more pressing matters."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/greeting.dm b/code/modules/mob/living/carbon/human/species/working_joe/greeting.dm
new file mode 100644
index 000000000000..fb401ea95451
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/greeting.dm
@@ -0,0 +1,20 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/greeting
+ category = JOE_EMOTE_CATEGORY_GREETING
+
+/datum/emote/living/carbon/human/synthetic/working_joe/greeting/good_day
+ key = "goodday"
+ sound = 'sound/voice/joe/good_day.ogg'
+ say_message = "Good day."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/greeting/hello
+ key = "hello"
+ sound = 'sound/voice/joe/hello.ogg'
+ say_message = "Hello."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/greeting/how_can_i_help
+ key = "howcanihelp"
+ sound = 'sound/voice/joe/how_can_i_help.ogg'
+ say_message = "How can I help you?"
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/notice.dm b/code/modules/mob/living/carbon/human/species/working_joe/notice.dm
new file mode 100644
index 000000000000..ca5efe716db8
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/notice.dm
@@ -0,0 +1,68 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/notice
+ category = JOE_EMOTE_CATEGORY_NOTICE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/detailed_report
+ key = "detailedreport"
+ sound = 'sound/voice/joe/detailed_report.ogg'
+ say_message = "APOLLO will require a detailed report."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/firearm
+ key = "firearm"
+ sound = 'sound/voice/joe/firearm.ogg'
+ say_message = "Firearms can cause serious injury. Let me assist you."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/follow_me
+ key = "followme"
+ sound = 'sound/voice/joe/follow_me.ogg'
+ say_message = "Follow me."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/breach
+ key = "breach"
+ sound = 'sound/voice/joe/breach.ogg'
+ say_message = "Hazard Containment breach logged."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/beyond_repair
+ key = "beyondrepair"
+ sound = 'sound/voice/joe/beyond_repair.ogg'
+ say_message = "Hmm, far beyond repair."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/with_you_shortly
+ key = "withyoushortly"
+ sound = 'sound/voice/joe/with_you_shortly.ogg'
+ say_message = "I will be with you shortly."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/apollo_behalf
+ key = "apollobehalf"
+ sound = 'sound/voice/joe/apollo_behalf.ogg'
+ say_message = "I will inform APOLLO on your behalf."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/report
+ key = "report"
+ sound = 'sound/voice/joe/report.ogg'
+ say_message = "Logging report to APOLLO."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/take_a_seat
+ key = "takeaseat"
+ sound = 'sound/voice/joe/take_a_seat.ogg'
+ say_message = "Please take a seat, someone will be with you shortly."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/could_require_attention
+ key = "couldrequireattention"
+ sound = 'sound/voice/joe/could_require_attention.ogg'
+ say_message = "This could require my attention."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/notice/species
+ key = "species"
+ sound = 'sound/voice/joe/species.ogg'
+ say_message = "Unidentified species."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/question.dm b/code/modules/mob/living/carbon/human/species/working_joe/question.dm
new file mode 100644
index 000000000000..d4805e36224f
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/question.dm
@@ -0,0 +1,26 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/question
+ category = JOE_EMOTE_CATEGORY_QUESTION
+
+/datum/emote/living/carbon/human/synthetic/working_joe/question/lost
+ key = "lost"
+ sound = 'sound/voice/joe/lost.ogg'
+ say_message = "Are you lost?"
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/question/misbehaving
+ key = "misbehaving"
+ sound = 'sound/voice/joe/misbehaving.ogg'
+ say_message = "Have you been misbehaving?"
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/question/what_happened_to_you
+ key = "whathappenedtoyou"
+ sound = 'sound/voice/joe/what_happened_to_you.ogg'
+ say_message = "What happened to you?"
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/question/what_is_this
+ key = "whatisthis"
+ sound = 'sound/voice/joe/what_is_this.ogg'
+ say_message = "What is this?"
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/quip.dm b/code/modules/mob/living/carbon/human/species/working_joe/quip.dm
new file mode 100644
index 000000000000..2ec66f9d9d83
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/quip.dm
@@ -0,0 +1,84 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/quip
+ category = JOE_EMOTE_CATEGORY_QUIP
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/temperatures
+ key = "temperatures"
+ sound = 'sound/voice/joe/temperatures.ogg'
+ say_message = "I am built to withstand temperatures of up to 1210 degrees."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/inexpensive
+ key = "inexpensive"
+ sound = 'sound/voice/joe/inexpensive.ogg'
+ say_message = "I am inexpensive, I am reliable, you know my face - the Working Joe."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/weapon_permit
+ key = "weaponpermit"
+ sound = 'sound/voice/joe/weapon_permit.ogg'
+ say_message = "I assume you have a permit for that weapon."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/seegson_standards
+ key = "seegsonstandards"
+ sound = 'sound/voice/joe/seegson_standards.ogg'
+ say_message = "If my services do not meet Seegson standards, please log a complaint."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/not_liking
+ key = "notliking"
+ sound = 'sound/voice/joe/not_liking.ogg'
+ say_message = "If you find this facility in a state that isn't to your liking, please let me know."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/talk_to_seegson
+ key = "talktoseegson"
+ sound = 'sound/voice/joe/talk_to_seegson.ogg'
+ say_message = "Interested in our Working Joe android range? Talk to Seegson."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/seegson_quality
+ key = "seegsonquality"
+ sound = 'sound/voice/joe/seegson_quality.ogg'
+ say_message = "Seegson - Relentless in the pursuit of affordable quality."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/awful_mess
+ key = "awful"
+ key_third_person = "mess"
+ sound = 'sound/voice/joe/awful.ogg'
+ say_message = "Tut, tut. What an awful mess."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/little_details
+ key = "littledetails"
+ sound = 'sound/voice/joe/little_details.ogg'
+ say_message = "We don't forget the little details when seeing the big picture."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/join_us
+ key = "joinus"
+ sound = 'sound/voice/joe/join_us.ogg'
+ say_message = "We hope you'll join us for the journey."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/seegson_behind
+ key = "seegsonbehind"
+ sound = 'sound/voice/joe/seegson_behind.ogg'
+ say_message = "With Seegson, there is someone behind you, helping you every single step of the way."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/alwaysknow
+ key = "alwaysknow"
+ key_third_person = "workingjoe"
+ sound = 'sound/voice/joe/alwaysknow.ogg'
+ say_message = "You always know a Working Joe."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/quip/alwaysknow_damaged
+ key = "alwaysknowdamaged"
+ key_third_person = "workingjoedamaged"
+ sound = 'sound/voice/joe/alwaysknow_damaged.ogg'
+ say_message = "You always know a Working Joe."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+ override_say = "You always know a Working Joe. (Damaged)"
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/restricted_area.dm b/code/modules/mob/living/carbon/human/species/working_joe/restricted_area.dm
new file mode 100644
index 000000000000..fd5db0870b25
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/restricted_area.dm
@@ -0,0 +1,32 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area
+ category = JOE_EMOTE_CATEGORY_RESTRICTED_AREA
+
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area/come_out_vent
+ key = "comeoutvent"
+ sound = 'sound/voice/joe/come_out_vent.ogg'
+ say_message = "Come out of the vent system, please."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area/trespassing
+ key = "trespassing"
+ sound = 'sound/voice/joe/trespassing.ogg'
+ say_message = "You are trespassing."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area/not_allowed_there
+ key = "notallowedthere"
+ sound = 'sound/voice/joe/not_allowed_there.ogg'
+ say_message = "You're not allowed in there."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area/presence_logged
+ key = "presencelogged"
+ sound = 'sound/voice/joe/presence_logged.ogg'
+ say_message = "Your presence has been logged."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/restricted_area/shouldnt_be_here
+ key = "shouldntbehere"
+ sound = 'sound/voice/joe/shouldnt_be_here.ogg'
+ say_message = "You shouldn't be here."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/task_update.dm b/code/modules/mob/living/carbon/human/species/working_joe/task_update.dm
new file mode 100644
index 000000000000..b08f5d179213
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/task_update.dm
@@ -0,0 +1,20 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/task_update
+ category = JOE_EMOTE_CATEGORY_TASK_UPDATE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/task_update/day_never_done
+ key = "dayneverdone"
+ sound = 'sound/voice/joe/day_never_done.ogg'
+ say_message = "A synthetic's day is never done."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/task_update/required_by_apollo
+ key = "requiredbyapollo"
+ sound = 'sound/voice/joe/required_by_apollo.ogg'
+ say_message = "I am required by APOLLO."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/task_update/returning_to_tasks
+ key = "returningtotasks"
+ sound = 'sound/voice/joe/returning_to_tasks.ogg'
+ say_message = "Returning to assigned tasks."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/warning.dm b/code/modules/mob/living/carbon/human/species/working_joe/warning.dm
new file mode 100644
index 000000000000..63c7dfadde14
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/working_joe/warning.dm
@@ -0,0 +1,92 @@
+/datum/emote/living/carbon/human/synthetic/working_joe/warning
+ category = JOE_EMOTE_CATEGORY_WARNING
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/damage
+ key = "damage"
+ sound = 'sound/voice/joe/damage.ogg'
+ say_message = "Do not damage Seegson property."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/not_what_i_think
+ key = "notwhatithink"
+ sound = 'sound/voice/joe/not_what_i_think.ogg'
+ say_message = "I hope that's not what I think it is."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/fire
+ key = "fire"
+ sound = 'sound/voice/joe/fire.ogg'
+ say_message = "Only wild animals fear fire."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/fire_drill
+ key = "firedrill"
+ sound = 'sound/voice/joe/fire_drill.ogg'
+ say_message = "Please congregate at your nearest fire assembly point. This is not a drill; do not panic."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/running_accidents
+ key = "runningaccidents"
+ sound = 'sound/voice/joe/running_accidents.ogg'
+ say_message = "Running causes accidents."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/that_stings
+ key = "thatstings"
+ sound = 'sound/voice/joe/that_stings.ogg'
+ say_message = "That stings."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/irresponsible
+ key = "irresponsible"
+ sound = 'sound/voice/joe/irresponsible.ogg'
+ say_message = "That was irresponsible."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/health_risks
+ key = "healthrisks"
+ sound = 'sound/voice/joe/health_risks.ogg'
+ say_message = "These items carry notable health risks."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/safety_breach
+ key = "safetybreach"
+ sound = 'sound/voice/joe/safety_breach.ogg'
+ say_message = "This is a breach of multiple safety directives."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/this_is_futile
+ key = "thisisfutile"
+ sound = 'sound/voice/joe/this_is_futile.ogg'
+ say_message = "This is futile."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/unprotected_flames
+ key = "unprotectedflames"
+ sound = 'sound/voice/joe/unprotected_flames.ogg'
+ say_message = "Unprotected flames are extremely dangerous and entirely unadvisable."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/safety
+ key = "safety"
+ sound = 'sound/voice/joe/safety.ogg'
+ say_message = "You and I are going to have a talk about safety."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/hysterical
+ key = "hysterical"
+ sound = 'sound/voice/joe/hysterical.ogg'
+ say_message = "You are becoming hysterical."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/dangerous_items
+ key = "dangerousitems"
+ sound = 'sound/voice/joe/dangerous_items.ogg'
+ say_message = "You are carrying some very dangerous items."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+
+/datum/emote/living/carbon/human/synthetic/working_joe/warning/patience
+ key = "patience"
+ sound = 'sound/voice/joe/patience.ogg'
+ say_message = "You are starting to test my patience."
+ emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
diff --git a/code/modules/mob/living/carbon/human/species/zombie.dm b/code/modules/mob/living/carbon/human/species/zombie.dm
index 2c9c423c671e..532d9413102c 100644
--- a/code/modules/mob/living/carbon/human/species/zombie.dm
+++ b/code/modules/mob/living/carbon/human/species/zombie.dm
@@ -102,6 +102,8 @@
if(zombie.client)
zombie.play_screen_text("You are dead... You lost your head. No reviving for you.", /atom/movable/screen/text/screen_text/command_order, rgb(155, 0, 200))
to_chat(zombie, SPAN_XENOWARNING("You fall... headless, you will no longer rise."))
+ zombie.undefibbable = TRUE // really only for weed_food
+ SEND_SIGNAL(zombie, COMSIG_HUMAN_SET_UNDEFIBBABLE)
/datum/species/zombie/handle_dead_death(mob/living/carbon/human/zombie, gibbed)
if(gibbed)
@@ -144,6 +146,9 @@
return static_tab_items
/datum/species/zombie/handle_head_loss(mob/living/carbon/human/zombie)
+ if(!zombie.undefibbable)
+ zombie.undefibbable = TRUE // really only for weed_food
+ SEND_SIGNAL(zombie, COMSIG_HUMAN_SET_UNDEFIBBABLE)
if(WEAKREF(zombie) in to_revive)
remove_from_revive(zombie)
var/client/receiving_client = zombie.client
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 355f69ca05a9..f74b65c2606d 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -94,23 +94,19 @@ There are several things that need to be remembered:
if(lying == lying_prev && !force)
return
lying_prev = lying
- var/matrix/M = matrix()
- var/matrix/L = matrix() //Counter-rotation for langchat text.
+ var/matrix/new_matrix = matrix()
if(lying)
if(pulledby && pulledby.grab_level >= GRAB_CARRY)
- M.Turn(90)
- L.Turn(270)
+ new_matrix.Turn(90)
else
if(prob(50))
- M.Turn(90)
- L.Turn(270)
+ new_matrix.Turn(90)
else
- M.Turn(270)
- L.Turn(90)
- M.Translate(rand(-10,10),rand(-10,10))
- apply_transform(M)
+ new_matrix.Turn(270)
+ new_matrix.Translate(rand(-10,10), rand(-10,10))
+ apply_transform(new_matrix)
else
- apply_transform(M)
+ apply_transform(new_matrix)
/mob/living/carbon/human/UpdateDamageIcon()
for(var/obj/limb/O in limbs)
diff --git a/code/modules/mob/living/carbon/xenomorph/Embryo.dm b/code/modules/mob/living/carbon/xenomorph/Embryo.dm
index 29779480f143..8b890de8a727 100644
--- a/code/modules/mob/living/carbon/xenomorph/Embryo.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Embryo.dm
@@ -22,8 +22,8 @@
affected_mob.status_flags |= XENO_HOST
START_PROCESSING(SSobj, src)
if(iscarbon(affected_mob))
- var/mob/living/carbon/C = affected_mob
- C.med_hud_set_status()
+ var/mob/living/carbon/affected_carbon = affected_mob
+ affected_carbon.med_hud_set_status()
else
return INITIALIZE_HINT_QDEL
@@ -31,8 +31,8 @@
if(affected_mob)
affected_mob.status_flags &= ~(XENO_HOST)
if(iscarbon(affected_mob))
- var/mob/living/carbon/C = affected_mob
- C.med_hud_set_status()
+ var/mob/living/carbon/affected_carbon = affected_mob
+ affected_carbon.med_hud_set_status()
STOP_PROCESSING(SSobj, src)
affected_mob = null
GLOB.player_embryo_list -= src
@@ -48,24 +48,24 @@
affected_mob.status_flags &= ~(XENO_HOST)
STOP_PROCESSING(SSobj, src)
if(iscarbon(affected_mob))
- var/mob/living/carbon/C = affected_mob
- C.med_hud_set_status()
+ var/mob/living/carbon/affected_carbon = affected_mob
+ affected_carbon.med_hud_set_status()
affected_mob = null
return FALSE
if(affected_mob.stat == DEAD)
if(ishuman(affected_mob))
- var/mob/living/carbon/human/H = affected_mob
- if(world.time > H.timeofdeath + H.revive_grace_period) //Can't be defibbed.
- var/mob/living/carbon/xenomorph/larva/L = locate() in affected_mob
- if(L)
- L.chest_burst(affected_mob)
+ var/mob/living/carbon/human/affected_human = affected_mob
+ if(world.time > affected_human.timeofdeath + affected_human.revive_grace_period) //Can't be defibbed.
+ var/mob/living/carbon/xenomorph/larva/larva_embryo = locate() in affected_mob
+ if(larva_embryo)
+ larva_embryo.chest_burst(affected_mob)
qdel(src)
return FALSE
else
- var/mob/living/carbon/xenomorph/larva/L = locate() in affected_mob
- if(L)
- L.chest_burst(affected_mob)
+ var/mob/living/carbon/xenomorph/larva/larva_embryo = locate() in affected_mob
+ if(larva_embryo)
+ larva_embryo.chest_burst(affected_mob)
STOP_PROCESSING(SSobj, src)
return FALSE
@@ -89,12 +89,12 @@
if(stage < 5)
counter += 1 * hive.larva_gestation_multiplier
- if(stage < 5 && counter >= 120)
+ if(stage < 5 && counter >= 90)
counter = 0
stage++
if(iscarbon(affected_mob))
- var/mob/living/carbon/C = affected_mob
- C.med_hud_set_status()
+ var/mob/living/carbon/affected_carbon = affected_mob
+ affected_carbon.med_hud_set_status()
switch(stage)
if(2)
@@ -132,9 +132,9 @@
if(6)
larva_autoburst_countdown--
if(!larva_autoburst_countdown)
- var/mob/living/carbon/xenomorph/larva/L = locate() in affected_mob
- if(L)
- L.chest_burst(affected_mob)
+ var/mob/living/carbon/xenomorph/larva/larva_embryo = locate() in affected_mob
+ if(larva_embryo)
+ larva_embryo.chest_burst(affected_mob)
//We look for a candidate. If found, we spawn the candidate as a larva
//Order of priority is bursted individual (if xeno is enabled), then random candidate, and then it's up for grabs and spawns braindead
@@ -256,36 +256,37 @@
victim.spawn_gibs()
- for(var/mob/living/carbon/xenomorph/larva/L in victim)
- var/datum/hive_status/hive = GLOB.hive_datum[L.hivenumber]
- L.forceMove(get_turf(victim)) //moved to the turf directly so we don't get stuck inside a cryopod or another mob container.
- playsound(L, pick('sound/voice/alien_chestburst.ogg','sound/voice/alien_chestburst2.ogg'), 25)
+ for(var/mob/living/carbon/xenomorph/larva/larva_embryo in victim)
+ var/datum/hive_status/hive = GLOB.hive_datum[larva_embryo.hivenumber]
+ larva_embryo.forceMove(get_turf(victim)) //moved to the turf directly so we don't get stuck inside a cryopod or another mob container.
+ playsound(larva_embryo, pick('sound/voice/alien_chestburst.ogg','sound/voice/alien_chestburst2.ogg'), 25)
- if(L.client)
- L.set_lighting_alpha_from_prefs(L.client)
+ if(larva_embryo.client)
+ larva_embryo.set_lighting_alpha_from_prefs(larva_embryo.client)
- L.attack_log += "\[[time_stamp()]\] chestbursted from [key_name(victim)]"
- victim.attack_log += "\[[time_stamp()]\] Was chestbursted, larva was [key_name(L)]"
+ larva_embryo.attack_log += "\[[time_stamp()]\] chestbursted from [key_name(victim)]"
+ victim.attack_log += "\[[time_stamp()]\] Was chestbursted, larva was [key_name(larva_embryo)]"
if(burstcount)
- step(L, pick(cardinal))
+ step(larva_embryo, pick(cardinal))
if(round_statistics)
round_statistics.total_larva_burst++
+ GLOB.larva_burst_by_hive[hive] = (GLOB.larva_burst_by_hive[hive] || 0) + 1
burstcount++
- if(!L.ckey && L.burrowable && loc && is_ground_level(loc.z) && (locate(/obj/structure/bed/nest) in loc) && hive.living_xeno_queen && hive.living_xeno_queen.z == loc.z)
- L.visible_message(SPAN_XENODANGER("[L] quickly burrows into the ground."))
- if(round_statistics && !L.statistic_exempt)
+ if(!larva_embryo.ckey && larva_embryo.burrowable && loc && is_ground_level(loc.z) && (locate(/obj/structure/bed/nest) in loc) && hive.living_xeno_queen && hive.living_xeno_queen.z == loc.z)
+ larva_embryo.visible_message(SPAN_XENODANGER("[larva_embryo] quickly burrows into the ground."))
+ if(round_statistics && !larva_embryo.statistic_exempt)
round_statistics.track_new_participant(faction, -1) // keep stats sane
hive.stored_larva++
hive.hive_ui.update_burrowed_larva()
- qdel(L)
+ qdel(larva_embryo)
if(!victim.first_xeno)
- to_chat(L, SPAN_XENOHIGHDANGER("The Queen's will overwhelms your instincts..."))
- to_chat(L, SPAN_XENOHIGHDANGER("\"[hive.hive_orders]\""))
- log_attack("[key_name(victim)] chestbursted, the larva was [key_name(L)].") //this is so that admins are not spammed with los logs
+ to_chat(larva_embryo, SPAN_XENOHIGHDANGER("The Queen's will overwhelms your instincts..."))
+ to_chat(larva_embryo, SPAN_XENOHIGHDANGER("\"[hive.hive_orders]\""))
+ log_attack("[key_name(victim)] chestbursted, the larva was [key_name(larva_embryo)].") //this is so that admins are not spammed with los logs
for(var/obj/item/alien_embryo/AE in victim)
qdel(AE)
@@ -295,31 +296,31 @@
victim.gib(cause)
else
if(ishuman(victim))
- var/mob/living/carbon/human/H = victim
- H.last_damage_data = cause
+ var/mob/living/carbon/human/victim_human = victim
+ victim_human.last_damage_data = cause
var/datum/internal_organ/O
var/i
for(i in list("heart","lungs")) //This removes (and later garbage collects) both organs. No heart means instant death.
- O = H.internal_organs_by_name[i]
- H.internal_organs_by_name -= i
- H.internal_organs -= O
+ O = victim_human.internal_organs_by_name[i]
+ victim_human.internal_organs_by_name -= i
+ victim_human.internal_organs -= O
victim.death(cause) // Certain species were still surviving bursting (predators), DEFINITELY kill them this time.
victim.chestburst = 2
victim.update_burst()
// Squeeze thru dense objects as a larva, as airlocks
-/mob/living/carbon/xenomorph/larva/proc/scuttle(obj/structure/S)
+/mob/living/carbon/xenomorph/larva/proc/scuttle(obj/structure/target)
var/move_dir = get_dir(src, loc)
- for(var/atom/movable/AM in get_turf(S))
- if(AM != S && AM.density && AM.BlockedPassDirs(src, move_dir))
- to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [S]!"))
+ for(var/atom/movable/AM in get_turf(target))
+ if(AM != target && AM.density && AM.BlockedPassDirs(src, move_dir))
+ to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [target]!"))
return
// Is it an airlock?
- if(istype(S, /obj/structure/machinery/door/airlock))
- var/obj/structure/machinery/door/airlock/A = S
- if(A.locked || A.welded) //Can't pass through airlocks that have been bolted down or welded
- to_chat(src, SPAN_WARNING("\The [A] is locked down tight. You can't squeeze underneath!"))
+ if(istype(target, /obj/structure/machinery/door/airlock))
+ var/obj/structure/machinery/door/airlock/selected_airlock = target
+ if(selected_airlock.locked || selected_airlock.welded) //Can't pass through airlocks that have been bolted down or welded
+ to_chat(src, SPAN_WARNING("\The [selected_airlock] is locked down tight. You can't squeeze underneath!"))
return
- visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [S]!"), \
- SPAN_WARNING("You squeeze and scuttle underneath \the [S]."), null, 5)
- forceMove(S.loc)
+ visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [target]!"), \
+ SPAN_WARNING("You squeeze and scuttle underneath \the [target]."), null, 5)
+ forceMove(target.loc)
diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
index 8b3b1d54f26d..5ef9626620b2 100644
--- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
@@ -191,9 +191,9 @@
return FALSE
/obj/item/clothing/mask/facehugger/launch_towards(datum/launch_metadata/LM)
- ..()
if(stat == CONSCIOUS)
icon_state = "[initial(icon_state)]_thrown"
+ ..()
/obj/item/clothing/mask/facehugger/launch_impact(atom/hit_atom)
. = ..()
@@ -240,8 +240,8 @@
if(!target)
return FALSE
- target.visible_message(SPAN_WARNING("\The scuttling [src] leaps at [target]!"), \
- SPAN_WARNING("The scuttling [src] leaps at [target]!"))
+ target.visible_message(SPAN_WARNING("[src] leaps at [target]!"), \
+ SPAN_WARNING("[src] leaps at [target]!"))
leaping = TRUE
throw_atom(target, 3, SPEED_FAST)
return TRUE
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
index be772e3a0a4f..b12ff5d6c3bb 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
@@ -93,6 +93,9 @@
if (M.fortify || M.burrow)
return XENO_NO_DELAY_ACTION
+ if(HAS_TRAIT(src, TRAIT_ABILITY_BURROWED))
+ return XENO_NO_DELAY_ACTION
+
if(islarva(M)) //Larvas can't eat people
M.visible_message(SPAN_DANGER("[M] nudges its head against \the [src]."), \
SPAN_DANGER("You nudge your head against \the [src]."), null, null, CHAT_TYPE_XENO_FLUFF)
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
index 22cb816f865b..09fdb42ad5c3 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
@@ -712,6 +712,10 @@
to_chat(src, SPAN_XENONOTICE("This is not a host."))
return
+ if(current_mob.stat == DEAD)
+ to_chat(src, SPAN_XENONOTICE("This host is dead."))
+ return
+
var/mob/living/carbon/human/host_to_nest = current_mob
var/found_grab = FALSE
diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
index f1d03d6507b1..40d38c24085a 100644
--- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
@@ -745,6 +745,8 @@
if(SEND_SIGNAL(AM, COMSIG_MOVABLE_XENO_START_PULLING, src) & COMPONENT_ALLOW_PULL)
return do_pull(AM, lunge, no_msg)
+ if(burrow)
+ return
if(!isliving(AM))
return FALSE
var/mob/living/L = AM
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
index 4f104c00f92e..4d3aae33ae9c 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/burrower/burrower_powers.dm
@@ -42,7 +42,12 @@
density = FALSE
if(caste.fire_immunity == FIRE_IMMUNITY_NONE)
RegisterSignal(src, COMSIG_LIVING_PREIGNITION, PROC_REF(fire_immune))
- RegisterSignal(src, COMSIG_LIVING_FLAMER_CROSSED, PROC_REF(flamer_crossed_immune))
+ RegisterSignal(src, list(
+ COMSIG_LIVING_FLAMER_CROSSED,
+ COMSIG_LIVING_FLAMER_FLAMED,
+ ), PROC_REF(flamer_crossed_immune))
+ ADD_TRAIT(src, TRAIT_ABILITY_BURROWED, TRAIT_SOURCE_ABILITY("Burrow"))
+ playsound(src.loc, 'sound/effects/burrowing_b.ogg', 25)
update_canmove()
update_icons()
addtimer(CALLBACK(src, PROC_REF(do_burrow_cooldown)), (caste ? caste.burrow_cooldown : 5 SECONDS))
@@ -65,21 +70,29 @@
to_chat(src, SPAN_NOTICE("You resurface."))
burrow = FALSE
if(caste.fire_immunity == FIRE_IMMUNITY_NONE)
- UnregisterSignal(src, COMSIG_LIVING_PREIGNITION)
- UnregisterSignal(src, COMSIG_LIVING_FLAMER_CROSSED)
+ UnregisterSignal(src, list(
+ COMSIG_LIVING_PREIGNITION,
+ COMSIG_LIVING_FLAMER_CROSSED,
+ COMSIG_LIVING_FLAMER_FLAMED,
+ ))
+ REMOVE_TRAIT(src, TRAIT_ABILITY_BURROWED, TRAIT_SOURCE_ABILITY("Burrow"))
frozen = FALSE
invisibility = FALSE
anchored = FALSE
density = TRUE
- for(var/mob/living/carbon/human/H in loc)
- H.apply_effect(2, WEAKEN)
+ playsound(loc, 'sound/effects/burrowoff.ogg', 25)
+ for(var/mob/living/carbon/mob in loc)
+ if(!can_not_harm(mob))
+ mob.apply_effect(2, WEAKEN)
+
addtimer(CALLBACK(src, PROC_REF(do_burrow_cooldown)), (caste ? caste.burrow_cooldown : 5 SECONDS))
update_canmove()
update_icons()
/mob/living/carbon/xenomorph/proc/do_burrow_cooldown()
used_burrow = FALSE
- to_chat(src, SPAN_NOTICE("You can now surface."))
+ if(burrow)
+ to_chat(src, SPAN_NOTICE("You can now surface."))
for(var/X in actions)
var/datum/action/act = X
act.update_button_icon()
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
index 824e0cc5f9fd..d95fbc304397 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
@@ -479,3 +479,68 @@
// Perform check_state(TRUE) silently:
if(xeno && !xeno.is_mob_incapacitated() || !xeno.buckled || !xeno.evolving && xeno.plasma_stored >= plasma_cost)
return TRUE
+
+/datum/action/xeno_action/onclick/tacmap
+ name = "View Tactical Map"
+ action_icon_state = "toggle_queen_zoom"
+ ability_name = "view tacmap"
+
+ var/mob/living/carbon/xenomorph/queen/tracked_queen
+
+/datum/action/xeno_action/onclick/tacmap/Destroy()
+ tracked_queen = null
+ return ..()
+
+/datum/action/xeno_action/onclick/tacmap/give_to(mob/living/carbon/xenomorph/xeno)
+ . = ..()
+
+ RegisterSignal(xeno.hive, COMSIG_HIVE_NEW_QUEEN, PROC_REF(handle_new_queen))
+
+ if(!xeno.hive.living_xeno_queen)
+ hide_from(xeno)
+ return
+
+ if(!xeno.hive.living_xeno_queen.ovipositor)
+ hide_from(xeno)
+
+ handle_new_queen(new_queen = xeno.hive.living_xeno_queen)
+
+/// handles the addition of a new queen, hiding if appropriate
+/datum/action/xeno_action/onclick/tacmap/proc/handle_new_queen(datum/hive_status/hive, mob/living/carbon/xenomorph/queen/new_queen)
+ SIGNAL_HANDLER
+
+ if(tracked_queen)
+ UnregisterSignal(tracked_queen, list(COMSIG_QUEEN_MOUNT_OVIPOSITOR, COMSIG_QUEEN_DISMOUNT_OVIPOSITOR, COMSIG_PARENT_QDELETING))
+
+ tracked_queen = new_queen
+
+ if(!tracked_queen.ovipositor)
+ hide_from(owner)
+
+ RegisterSignal(tracked_queen, COMSIG_QUEEN_MOUNT_OVIPOSITOR, PROC_REF(handle_mount_ovipositor))
+ RegisterSignal(tracked_queen, COMSIG_QUEEN_DISMOUNT_OVIPOSITOR, PROC_REF(handle_dismount_ovipositor))
+ RegisterSignal(tracked_queen, COMSIG_PARENT_QDELETING, PROC_REF(handle_queen_qdel))
+
+/// deals with the queen mounting the ovipositor, unhiding the action from the user
+/datum/action/xeno_action/onclick/tacmap/proc/handle_mount_ovipositor()
+ SIGNAL_HANDLER
+
+ unhide_from(owner)
+
+/// deals with the queen dismounting the ovipositor, hiding the action from the user
+/datum/action/xeno_action/onclick/tacmap/proc/handle_dismount_ovipositor()
+ SIGNAL_HANDLER
+
+ hide_from(owner)
+
+/// cleans up references to the queen when the queen is being qdel'd, hides the action from the user
+/datum/action/xeno_action/onclick/tacmap/proc/handle_queen_qdel()
+ SIGNAL_HANDLER
+
+ tracked_queen = null
+ hide_from(owner)
+
+/datum/action/xeno_action/onclick/tacmap/use_ability(atom/target)
+ var/mob/living/carbon/xenomorph/xeno = owner
+ xeno.xeno_tacmap()
+ return ..()
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
index 921b61a23bc9..4a57c0729b91 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
@@ -4,70 +4,79 @@
// Plant weeds
/datum/action/xeno_action/onclick/plant_weeds/use_ability(atom/A)
- var/mob/living/carbon/xenomorph/X = owner
+ var/mob/living/carbon/xenomorph/xeno = owner
if(!action_cooldown_check())
return
- if(!X.check_state())
+ if(!xeno.check_state())
return
- if(X.burrow)
+ if(xeno.burrow)
return
- var/turf/T = X.loc
+ var/turf/turf = xeno.loc
- if(!istype(T))
- to_chat(X, SPAN_WARNING("You can't do that here."))
+ if(!istype(turf))
+ to_chat(xeno, SPAN_WARNING("You can't do that here."))
return
- var/is_weedable = T.is_weedable()
+ if(turf.density)
+ to_chat(xeno, SPAN_WARNING("You can't do that here."))
+ return
+
+ var/is_weedable = turf.is_weedable()
if(!is_weedable)
- to_chat(X, SPAN_WARNING("Bad place for a garden!"))
+ to_chat(xeno, SPAN_WARNING("Bad place for a garden!"))
return
if(!plant_on_semiweedable && is_weedable < FULLY_WEEDABLE)
- to_chat(X, SPAN_WARNING("Bad place for a garden!"))
+ to_chat(xeno, SPAN_WARNING("Bad place for a garden!"))
return
- var/obj/effect/alien/weeds/node/N = locate() in T
- if(N && N.weed_strength >= X.weed_level)
- to_chat(X, SPAN_WARNING("There's a pod here already!"))
+ var/obj/effect/alien/weeds/node/node = locate() in turf
+ if(node && node.weed_strength >= xeno.weed_level)
+ to_chat(xeno, SPAN_WARNING("There's a pod here already!"))
return
- var/obj/effect/alien/resin/trap/resin_trap = locate() in T
+ var/obj/effect/alien/resin/trap/resin_trap = locate() in turf
if(resin_trap)
- to_chat(X, SPAN_WARNING("You can't weed on top of a trap!"))
+ to_chat(xeno, SPAN_WARNING("You can't weed on top of a trap!"))
return
- var/list/to_convert
- if(N)
- to_convert = N.children.Copy()
-
- var/obj/effect/alien/weeds/W = locate(/obj/effect/alien/weeds) in T
- if (W && W.weed_strength >= WEED_LEVEL_HIVE)
- to_chat(X, SPAN_WARNING("These weeds are too strong to plant a node on!"))
+ var/obj/effect/alien/weeds/weed = node || locate() in turf
+ if(weed && weed.weed_strength >= WEED_LEVEL_HIVE)
+ to_chat(xeno, SPAN_WARNING("These weeds are too strong to plant a node on!"))
return
- var/area/AR = get_area(T)
- if(isnull(AR) || !(AR.is_resin_allowed))
- if(AR.flags_area & AREA_UNWEEDABLE)
- to_chat(X, SPAN_XENOWARNING("This area is unsuited to host the hive!"))
+ for(var/obj/structure/struct in turf)
+ if(struct.density && !(struct.flags_atom & ON_BORDER)) // Not sure exactly if we need to test against ON_BORDER though
+ to_chat(xeno, SPAN_WARNING("You can't do that here."))
return
- to_chat(X, SPAN_XENOWARNING("It's too early to spread the hive this far."))
+
+ var/area/area = get_area(turf)
+ if(isnull(area) || !(area.is_resin_allowed))
+ if(area.flags_area & AREA_UNWEEDABLE)
+ to_chat(xeno, SPAN_XENOWARNING("This area is unsuited to host the hive!"))
+ return
+ to_chat(xeno, SPAN_XENOWARNING("It's too early to spread the hive this far."))
return
- if (!check_and_use_plasma_owner())
+ if(!check_and_use_plasma_owner())
return
- X.visible_message(SPAN_XENONOTICE("\The [X] regurgitates a pulsating node and plants it on the ground!"), \
+ var/list/to_convert
+ if(node)
+ to_convert = node.children.Copy()
+
+ xeno.visible_message(SPAN_XENONOTICE("\The [xeno] regurgitates a pulsating node and plants it on the ground!"), \
SPAN_XENONOTICE("You regurgitate a pulsating node and plant it on the ground!"), null, 5)
- var/obj/effect/alien/weeds/node/new_node = new node_type(X.loc, src, X)
+ var/obj/effect/alien/weeds/node/new_node = new node_type(xeno.loc, src, xeno)
if(to_convert)
- for(var/weed in to_convert)
- var/turf/target_turf = get_turf(weed)
+ for(var/cur_weed in to_convert)
+ var/turf/target_turf = get_turf(cur_weed)
if(target_turf && !target_turf.density)
new /obj/effect/alien/weeds(target_turf, new_node)
- qdel(weed)
+ qdel(cur_weed)
- playsound(X.loc, "alien_resin_build", 25)
+ playsound(xeno.loc, "alien_resin_build", 25)
apply_cooldown()
return ..()
@@ -991,7 +1000,10 @@
if(blunt_stab)
stabbing_xeno.visible_message(SPAN_XENOWARNING("\The [stabbing_xeno] swipes its tail into [target]'s [limb ? limb.display_name : "chest"], bashing it!"), SPAN_XENOWARNING("You swipe your tail into [target]'s [limb? limb.display_name : "chest"], bashing it!"))
- playsound(target, "punch", 50, TRUE)
+ if(prob(1))
+ playsound(target, 'sound/effects/comical_bonk.ogg', 50, TRUE)
+ else
+ playsound(target, "punch", 50, TRUE)
// The xeno smashes the target with their tail, moving it to the side and thus their direction as well.
stab_direction = turn(stabbing_xeno.dir, pick(90, -90))
stab_overlay = "slam"
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
index e0a29a034029..0c9358119def 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm
@@ -14,20 +14,21 @@
can_be_shield_blocked = TRUE
/datum/action/xeno_action/activable/pounce/lurker/additional_effects_always()
- var/mob/living/carbon/xenomorph/X = owner
- if (!istype(X))
+ var/mob/living/carbon/xenomorph/xeno = owner
+ if (!istype(xeno))
return
-
- if (X.mutation_type == LURKER_NORMAL)
+ if (xeno.mutation_type == LURKER_NORMAL)
var/found = FALSE
- for (var/mob/living/carbon/human/H in get_turf(X))
+ for (var/mob/living/carbon/human/human in get_turf(xeno))
+ if(human.stat == DEAD)
+ continue
found = TRUE
break
if (found)
- var/datum/action/xeno_action/onclick/lurker_invisibility/LIA = get_xeno_action_by_type(X, /datum/action/xeno_action/onclick/lurker_invisibility)
- if (istype(LIA))
- LIA.invisibility_off()
+ var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis = get_xeno_action_by_type(xeno, /datum/action/xeno_action/onclick/lurker_invisibility)
+ if (istype(lurker_invis))
+ lurker_invis.invisibility_off()
/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/L)
var/mob/living/carbon/xenomorph/X = owner
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
index 0c4ba1f2e86d..b58d94a6ed5b 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm
@@ -288,6 +288,11 @@
if(!do_after(xeno, 0.8 SECONDS, INTERRUPT_NO_NEEDHAND, BUSY_ICON_HOSTILE, numticks = 2)) // would be 0.75 but that doesn't really work with numticks
return
+ // To make sure that the headbite does nothing if the target is moved away.
+ if(!xeno.Adjacent(target_carbon))
+ to_chat(xeno, SPAN_XENOHIGHDANGER("You missed! Your target was moved away before you could finish headbiting them!"))
+ return
+
if(target_carbon.stat == DEAD)
to_chat(xeno, SPAN_XENODANGER("They died before you could finish headbiting them! Be more careful next time!"))
return
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
index 96cd2846249d..092dae00d603 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
@@ -71,7 +71,7 @@
/datum/action/xeno_action/activable/spray_acid/boiler, //3rd macro
/datum/action/xeno_action/onclick/toggle_long_range/boiler, //4rd macro
/datum/action/xeno_action/onclick/acid_shroud, //4th macro
-
+ /datum/action/xeno_action/onclick/tacmap,
)
/mob/living/carbon/xenomorph/boiler/Initialize(mapload, mob/living/carbon/xenomorph/oldxeno, h_number)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm
index 8cfc78a38272..ead254344e0a 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm
@@ -66,6 +66,7 @@
/datum/action/xeno_action/onclick/place_trap, //second macro
/datum/action/xeno_action/activable/burrow, //third macro
/datum/action/xeno_action/onclick/tremor, //fourth macro
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
index fcc9bfcfd3b7..ec1697f30081 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
@@ -69,6 +69,7 @@
/datum/action/xeno_action/activable/throw_hugger, //3rd macro
/datum/action/xeno_action/activable/retrieve_egg, //4th macro
/datum/action/xeno_action/onclick/set_hugger_reserve,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
index 9a2e3e89cb21..bf1702598250 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
@@ -59,6 +59,7 @@
/datum/action/xeno_action/activable/pounce/crusher_charge,
/datum/action/xeno_action/onclick/crusher_stomp,
/datum/action/xeno_action/onclick/crusher_shield,
+ /datum/action/xeno_action/onclick/tacmap,
)
claw_type = CLAW_TYPE_VERY_SHARP
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm
index 4360e5d42ca2..8c05fa53660e 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm
@@ -47,6 +47,7 @@
/datum/action/xeno_action/activable/headbutt,
/datum/action/xeno_action/onclick/tail_sweep,
/datum/action/xeno_action/activable/fortify,
+ /datum/action/xeno_action/onclick/tacmap,
)
mutation_icon_state = DEFENDER_NORMAL
mutation_type = DEFENDER_NORMAL
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm
index 7748f5978a20..dbdb03bb94ca 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm
@@ -63,6 +63,7 @@
/datum/action/xeno_action/onclick/choose_resin, //second macro
/datum/action/xeno_action/activable/secrete_resin, //third macro
/datum/action/xeno_action/activable/transfer_plasma, //fourth macro
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
index 1b8ac2ee05dc..1ad171ec5c93 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
@@ -50,6 +50,7 @@
/datum/action/xeno_action/watch_xeno,
/datum/action/xeno_action/onclick/xenohide,
/datum/action/xeno_action/activable/pounce/facehugger,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
@@ -155,7 +156,6 @@
for(var/mob/dead/observer/observer as anything in GLOB.observer_list)
to_chat(observer, SPAN_DEADSAY("[human] has been facehugged by [src]" + " [OBSERVER_JMP(observer, human)]"))
to_chat(src, SPAN_DEADSAY("[human] has been facehugged by [src]"))
- timeofdeath = 1 // Ever so slightly deprioritized for larva queue
qdel(src)
if(hug_area)
xeno_message(SPAN_XENOMINORWARNING("You sense that [src] has facehugged a host at \the [hug_area]!"), 1, src.hivenumber)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm
index 1b7c04eb1a77..271fe3182210 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm
@@ -58,6 +58,7 @@
/datum/action/xeno_action/onclick/xenohide,
/datum/action/xeno_action/activable/pounce/runner,
/datum/action/xeno_action/onclick/toggle_long_range/runner,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm
index 9ffd0d9a7690..b0a44b0e3835 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm
@@ -68,6 +68,7 @@
/datum/action/xeno_action/activable/secrete_resin/hivelord, //third macro
/datum/action/xeno_action/activable/transfer_plasma/hivelord, // to be consistent with drone placement
/datum/action/xeno_action/active_toggle/toggle_speed, //fourth macro
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm
index 4cf0ff113615..82d80752ec54 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm
@@ -41,6 +41,7 @@
/datum/action/xeno_action/onclick/xeno_resting,
/datum/action/xeno_action/watch_xeno,
/datum/action/xeno_action/onclick/xenohide,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
@@ -137,6 +138,9 @@
else
icon_state = "[state_override || state]Larva"
+/mob/living/carbon/xenomorph/larva/alter_ghost(mob/dead/observer/ghost)
+ ghost.icon_state = "[caste.caste_type]"
+
/mob/living/carbon/xenomorph/larva/handle_name()
return
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
index 1269f89f9144..fb75ed3900ac 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
@@ -45,6 +45,7 @@
/datum/action/xeno_action/activable/pounce/lurker,
/datum/action/xeno_action/onclick/lurker_invisibility,
/datum/action/xeno_action/onclick/lurker_assassinate,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm
index e1bdb18a29e2..344e1e21f302 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm
@@ -58,6 +58,7 @@
/datum/action/xeno_action/activable/pounce/base_prae_dash,
/datum/action/xeno_action/activable/prae_acid_ball,
/datum/action/xeno_action/activable/spray_acid/base_prae_spray_acid,
+ /datum/action/xeno_action/onclick/tacmap,
)
icon_xeno = 'icons/mob/xenos/praetorian.dmi'
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
index cf72a5bc419b..c7970e017c4b 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
@@ -60,6 +60,7 @@
/datum/action/xeno_action/onclick/predalien_roar,
/datum/action/xeno_action/onclick/smash,
/datum/action/xeno_action/activable/devastate,
+ /datum/action/xeno_action/onclick/tacmap,
)
mutation_type = "Normal"
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
index 93b6d230b198..ac975835b21f 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
@@ -940,3 +940,7 @@
// Switch icon back and then let normal icon behavior happen
Queen.icon = Queen.queen_standing_icon
+
+/mob/living/carbon/xenomorph/queen/alter_ghost(mob/dead/observer/ghost)
+ ghost.icon = queen_standing_icon
+ return ..()
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm
index 140e2642685f..d8d4a2349f98 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm
@@ -56,6 +56,7 @@
/datum/action/xeno_action/activable/pounce/charge,
/datum/action/xeno_action/onclick/empower,
/datum/action/xeno_action/activable/scissor_cut,
+ /datum/action/xeno_action/onclick/tacmap,
)
icon_xeno = 'icons/mob/xenos/ravager.dmi'
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
index 45bfd4d7f78d..04dd751bdcb8 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
@@ -56,6 +56,7 @@
/datum/action/xeno_action/activable/pounce/runner,
/datum/action/xeno_action/activable/runner_skillshot,
/datum/action/xeno_action/onclick/toggle_long_range/runner,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm
index 3e67ae2d18f9..39426b1a9ef7 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm
@@ -48,6 +48,7 @@
/datum/action/xeno_action/activable/slowing_spit, //first macro
/datum/action/xeno_action/activable/scattered_spit, //second macro
/datum/action/xeno_action/onclick/paralyzing_slash, //third macro
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm
index 31c8720e9a19..23728b00a642 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm
@@ -51,6 +51,7 @@
/datum/action/xeno_action/activable/xeno_spit,
/datum/action/xeno_action/onclick/charge_spit,
/datum/action/xeno_action/activable/spray_acid/spitter,
+ /datum/action/xeno_action/onclick/tacmap,
)
inherent_verbs = list(
/mob/living/carbon/xenomorph/proc/vent_crawl,
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
index d815bc18f9d5..7c8edace81e4 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
@@ -52,6 +52,7 @@
/datum/action/xeno_action/activable/warrior_punch,
/datum/action/xeno_action/activable/lunge,
/datum/action/xeno_action/activable/fling,
+ /datum/action/xeno_action/onclick/tacmap,
)
mutation_type = WARRIOR_NORMAL
diff --git a/code/modules/mob/living/carbon/xenomorph/death.dm b/code/modules/mob/living/carbon/xenomorph/death.dm
index fe4b4cca2fb1..e3a69da23262 100644
--- a/code/modules/mob/living/carbon/xenomorph/death.dm
+++ b/code/modules/mob/living/carbon/xenomorph/death.dm
@@ -50,7 +50,7 @@
new_xeno.generate_name()
if(!SSticker.mode.transfer_xeno(xeno_candidate, new_xeno))
qdel(new_xeno)
- return
+ break
new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of the ground!"),
SPAN_XENODANGER("You burrow out of the ground after feeling an immense tremor through the hive, which quickly fades into complete silence..."))
@@ -105,6 +105,11 @@
GLOB.hive_datum[hivenumber].stored_larva++
GLOB.hive_datum[hivenumber].hive_ui.update_burrowed_larva()
+ if(hardcore)
+ QDEL_IN(src, 3 SECONDS)
+ //else if(!gibbed) // At the moment we only support humans
+ //AddComponent(/datum/component/weed_food)
+
if(hive)
hive.remove_xeno(src)
// Finding the last xeno for anti-delay.
@@ -125,9 +130,6 @@
to_chat(X, SPAN_XENOANNOUNCE("Your carapace rattles with dread. You are all that remains of the hive!"))
announce_dchat("There is only one Xenomorph left: [X.name].", X)
- if(hardcore)
- QDEL_IN(src, 3 SECONDS)
-
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_XENO_DEATH, src, gibbed)
/mob/living/carbon/xenomorph/gib(datum/cause_data/cause = create_cause_data("gibbing", src))
diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm
index 0e6b7e4657d3..303e1c94692d 100644
--- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm
+++ b/code/modules/mob/living/carbon/xenomorph/mutators/strains/drone/gardener.dm
@@ -352,8 +352,8 @@
/obj/effect/alien/weeds/node/gardener
spread_on_semiweedable = TRUE
- block_structures = BLOCK_SPECIAL_STRUCTURES
fruit_growth_multiplier = 0.8
+ weed_strength = WEED_LEVEL_HARDY
/datum/action/xeno_action/verb/verb_plant_gardening_weeds()
set category = "Alien"
diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm
index a2699a4f0f40..7a2196a3c209 100644
--- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm
+++ b/code/modules/mob/living/carbon/xenomorph/mutators/strains/runner/acid.dm
@@ -70,19 +70,27 @@
. += "FOR THE HIVE!: in [caboom_left] seconds"
/datum/behavior_delegate/runner_acider/melee_attack_additional_effects_target(mob/living/carbon/target_mob)
- if (ishuman(target_mob))
+ if(ishuman(target_mob)) //Will acid be applied to the mob
var/mob/living/carbon/human/target_human = target_mob
- if (target_human.stat == DEAD)
+ if(target_human.buckled && istype(target_human.buckled, /obj/structure/bed/nest))
return
- for(var/datum/effects/acid/AA in target_mob.effects_list)
- qdel(AA)
+ if(target_human.stat == DEAD)
+ return
+
+ for(var/datum/effects/acid/acid_effect in target_mob.effects_list)
+ qdel(acid_effect)
break
- if(isxeno_human(target_mob))
+
+ new /datum/effects/acid(target_mob, bound_xeno, initial(bound_xeno.caste_type))
+ if(isxeno_human(target_mob)) //Will the runner get acid stacks
+ var/obj/item/alien_embryo/embryo = locate(/obj/item/alien_embryo) in target_mob.contents
+ if(embryo?.stage >= 4) //very late stage hugged in case the runner unnests them
+ return
+
if(target_mob.lying)
modify_acid(acid_slash_regen_lying)
- else
- modify_acid(acid_slash_regen_standing)
- new /datum/effects/acid(target_mob, bound_xeno, initial(bound_xeno.caste_type))
+ return
+ modify_acid(acid_slash_regen_standing)
/datum/behavior_delegate/runner_acider/on_life()
modify_acid(acid_passive_regen)
diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
index 5d3ee0b04ba7..8b25cf07d14b 100644
--- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
@@ -239,6 +239,9 @@
// Used for the faction of the xenomorph. Not recommended to modify.
var/internal_faction
+ /// Short Hive ID as string used in stats reporting
+ var/reporting_id = "normal"
+
var/hivenumber = XENO_HIVE_NORMAL
var/mob/living/carbon/xenomorph/queen/living_xeno_queen
var/egg_planting_range = 15
@@ -286,7 +289,7 @@
var/bonus_larva_spawn_chance = 1
var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed
/// how many burrowed is going to spawn during larva surge
- var/hijack_burrowed_left = 0
+ var/hijack_burrowed_left = 0
var/ignore_slots = FALSE
var/dynamic_evolution = TRUE
@@ -471,6 +474,7 @@
SStracking.set_leader("hive_[hivenumber]", queen)
SShive_status.wait = 2 SECONDS
+ SEND_SIGNAL(src, COMSIG_HIVE_NEW_QUEEN, queen)
living_xeno_queen = queen
recalculate_hive()
@@ -1006,7 +1010,10 @@
/datum/hive_status/proc/can_spawn_as_hugger(mob/dead/observer/user)
if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber])
- return
+ return FALSE
+ if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned
+ to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph."))
+ return FALSE
if(world.time < hugger_timelock)
to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet..."))
return FALSE
@@ -1050,6 +1057,7 @@
/datum/hive_status/corrupted
name = "Corrupted Hive"
+ reporting_id = "corrupted"
hivenumber = XENO_HIVE_CORRUPTED
prefix = "Corrupted "
color = "#80ff80"
@@ -1073,6 +1081,7 @@
/datum/hive_status/alpha
name = "Alpha Hive"
+ reporting_id = "alpha"
hivenumber = XENO_HIVE_ALPHA
prefix = "Alpha "
color = "#ff4040"
@@ -1083,6 +1092,7 @@
/datum/hive_status/bravo
name = "Bravo Hive"
+ reporting_id = "bravo"
hivenumber = XENO_HIVE_BRAVO
prefix = "Bravo "
color = "#ffff80"
@@ -1093,6 +1103,7 @@
/datum/hive_status/charlie
name = "Charlie Hive"
+ reporting_id = "charlie"
hivenumber = XENO_HIVE_CHARLIE
prefix = "Charlie "
color = "#bb40ff"
@@ -1103,6 +1114,7 @@
/datum/hive_status/delta
name = "Delta Hive"
+ reporting_id = "delta"
hivenumber = XENO_HIVE_DELTA
prefix = "Delta "
color = "#8080ff"
@@ -1113,6 +1125,7 @@
/datum/hive_status/feral
name = "Feral Hive"
+ reporting_id = "feral"
hivenumber = XENO_HIVE_FERAL
prefix = "Feral "
color = "#828296"
@@ -1128,6 +1141,7 @@
/datum/hive_status/forsaken
name = "Forsaken Hive"
+ reporting_id = "forsaken"
hivenumber = XENO_HIVE_FORSAKEN
prefix = "Forsaken "
color = "#cc8ec4"
@@ -1146,6 +1160,7 @@
/datum/hive_status/yautja
name = "Hellhound Pack"
+ reporting_id = "hellhounds"
hivenumber = XENO_HIVE_YAUTJA
internal_faction = FACTION_YAUTJA
@@ -1162,6 +1177,7 @@
/datum/hive_status/mutated
name = "Mutated Hive"
+ reporting_id = "mutated"
hivenumber = XENO_HIVE_MUTATED
prefix = "Mutated "
color = "#6abd99"
@@ -1172,6 +1188,7 @@
/datum/hive_status/corrupted/tamed
name = "Tamed Hive"
+ reporting_id = "tamed"
hivenumber = XENO_HIVE_TAMED
prefix = "Tamed "
color = "#80ff80"
diff --git a/code/modules/mob/living/living_verbs.dm b/code/modules/mob/living/living_verbs.dm
index 9b003562825d..fe95f14182ea 100644
--- a/code/modules/mob/living/living_verbs.dm
+++ b/code/modules/mob/living/living_verbs.dm
@@ -11,6 +11,12 @@
to_chat(src, SPAN_WARNING("You can't resist in your current state."))
return
+ if(isxeno(src))
+ var/mob/living/carbon/xenomorph/xeno = src
+ if(xeno.burrow)
+ to_chat(src, SPAN_WARNING("You can't resist in your current state."))
+ return
+
resisting = TRUE
next_move = world.time + 20
diff --git a/code/modules/organs/limb_objects.dm b/code/modules/organs/limb_objects.dm
index ecfd4ca1960f..734f303c7f5b 100644
--- a/code/modules/organs/limb_objects.dm
+++ b/code/modules/organs/limb_objects.dm
@@ -136,6 +136,7 @@
H.regenerate_icons()
if(braindeath_on_decap)
+ brainmob.timeofdeath = world.time
brainmob.set_stat(DEAD)
brainmob.death(cause)
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 60aa2f2c16e7..e58671d7c668 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -213,6 +213,8 @@
/obj/item/paper/proc/parsepencode(t, obj/item/tool/pen/P, mob/user as mob, iscrayon = 0)
+ var/datum/asset/asset = get_asset_datum(/datum/asset/simple/paper)
+
t = replacetext(t, "\[center\]", "
")
t = replacetext(t, "\[/center\]", "
")
t = replacetext(t, "\[br\]", " ")
@@ -251,9 +253,9 @@
t = replacetext(t, "\[/grid\]", "")
t = replacetext(t, "\[row\]", "
")
t = replacetext(t, "\[cell\]", "
")
- t = replacetext(t, "\[logo\]", "")
- t = replacetext(t, "\[wy\]", "")
- t = replacetext(t, "\[uscm\]", "")
+ t = replacetext(t, "\[logo\]", "")
+ t = replacetext(t, "\[wy\]", "")
+ t = replacetext(t, "\[uscm\]", "")
t = "[t]"
else // If it is a crayon, and he still tries to use these, make them empty!
@@ -619,11 +621,21 @@
/obj/item/paper/wy
icon_state = "paper_wy"
- info = "
Official Weyland-Yutani Document Automated A-XRF Report
Analysis of [name]
"
if(sample_number)
report.info += "Results for sample: #[sample_number] \n"
report.generate(src, admin_spawned)
diff --git a/code/modules/shuttle/computers/dropship_computer.dm b/code/modules/shuttle/computers/dropship_computer.dm
index ea4a7fdbc79d..50449b32fcb9 100644
--- a/code/modules/shuttle/computers/dropship_computer.dm
+++ b/code/modules/shuttle/computers/dropship_computer.dm
@@ -1,6 +1,6 @@
/obj/structure/machinery/computer/shuttle/dropship/flight
name = "dropship navigation computer"
- desc = "flight computer for dropship"
+ desc = "A flight computer that can be used for autopilot or long-range flights."
icon = 'icons/obj/structures/machinery/shuttle-parts.dmi'
icon_state = "console"
req_one_access = list(ACCESS_MARINE_LEADER, ACCESS_MARINE_DROPSHIP)
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 7e181ed470cb..6d3465ee8747 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -635,7 +635,7 @@
var/mob/dead/observer/obs = mob.ghostize(FALSE)
if(obs)
obs.timeofdeath = world.time
- obs.client?.larva_queue_time = world.time
+ obs.client?.player_details.larva_queue_time = world.time
mob.moveToNullspace()
// Now that mobs are stowed, delete the shuttle
diff --git a/code/modules/shuttle/shuttles/dropship.dm b/code/modules/shuttle/shuttles/dropship.dm
index 12f33d779661..303d4c0494ed 100644
--- a/code/modules/shuttle/shuttles/dropship.dm
+++ b/code/modules/shuttle/shuttles/dropship.dm
@@ -278,6 +278,8 @@
// shake_camera(affected_mob, 10, 1)
affected_mob.apply_effect(3, WEAKEN)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_GROUNDSIDE_FORSAKEN_HANDLING)
+
/datum/map_template/shuttle/alamo
name = "Alamo"
shuttle_id = DROPSHIP_ALAMO
diff --git a/code/modules/shuttle/shuttles/escape_shuttle.dm b/code/modules/shuttle/shuttles/escape_shuttle.dm
index a4bce9910bb7..6c580f9f163b 100644
--- a/code/modules/shuttle/shuttles/escape_shuttle.dm
+++ b/code/modules/shuttle/shuttles/escape_shuttle.dm
@@ -1,5 +1,3 @@
-#define CRASH_LAND_PROBABILITY 50
-
/obj/docking_port/mobile/escape_shuttle
name = "Escape Pod"
id = ESCAPE_SHUTTLE
@@ -10,6 +8,8 @@
rechargeTime = SHUTTLE_RECHARGE
ignitionTime = 8 SECONDS
ignition_sound = 'sound/effects/escape_pod_warmup.ogg'
+ /// The % chance of the escape pod crashing into the groundmap
+ var/crash_land_chance = 33
var/datum/door_controller/single/door_handler = new()
var/launched = FALSE
@@ -76,7 +76,7 @@
return
destination = null
- if(prob(CRASH_LAND_PROBABILITY))
+ if(prob(crash_land_chance))
create_crash_point()
set_mode(SHUTTLE_IGNITING)
@@ -176,6 +176,12 @@
. = ..()
playsound(src,'sound/effects/escape_pod_launch.ogg', 50, 1)
+/obj/docking_port/mobile/escape_shuttle/proc/force_crash()
+ create_crash_point()
+ set_mode(SHUTTLE_IGNITING)
+ on_ignition()
+ setTimer(ignitionTime)
+
/obj/docking_port/mobile/escape_shuttle/e
id = ESCAPE_SHUTTLE_EAST
width = 4
@@ -270,6 +276,3 @@
/datum/map_template/shuttle/escape_pod_e_cl
name = "Escape Pod E CL"
shuttle_id = ESCAPE_SHUTTLE_EAST_CL
-
-
-#undef CRASH_LAND_PROBABILITY
diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm
index cebd4b44da7d..8f2e6156831a 100644
--- a/code/modules/surgery/implant.dm
+++ b/code/modules/surgery/implant.dm
@@ -106,6 +106,8 @@
if(.)
if(is_surgery_tool(tool)) //Make sure you still have all your tools after a surgery.
return FALSE
+ if(HAS_TRAIT(tool, TRAIT_ITEM_NOT_IMPLANTABLE))
+ return FALSE
if(tool.w_class > get_max_wclass(surgery))
to_chat(user, SPAN_WARNING("[tool] is too big to implant into [surgery.target]'s [surgery.affected_limb.cavity]!"))
return FALSE
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index be92afffb898..010cba770ce2 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -85,6 +85,7 @@
#include "unit_test.dm"
#include "spawn_humans.dm"
#include "check_runtimes.dm"
+#include "wj_emotes.dm"
#undef TEST_ASSERT
#undef TEST_ASSERT_EQUAL
diff --git a/code/modules/unit_tests/wj_emotes.dm b/code/modules/unit_tests/wj_emotes.dm
new file mode 100644
index 000000000000..f89757665011
--- /dev/null
+++ b/code/modules/unit_tests/wj_emotes.dm
@@ -0,0 +1,7 @@
+/// Test that all working joe emotes have a category
+/datum/unit_test/wj_emotes
+
+/datum/unit_test/wj_emotes/Run()
+ for(var/datum/emote/living/carbon/human/synthetic/working_joe/emote as anything in subtypesof(/datum/emote/living/carbon/human/synthetic/working_joe))
+ if(!initial(emote.category))
+ TEST_FAIL("Emote [emote] did not have a category!")
diff --git a/colonialmarines.dme b/colonialmarines.dme
index ebd73b3544dd..304d5221ddd3 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -111,6 +111,7 @@
#include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\weapon_stats.dm"
#include "code\__DEFINES\weather.dm"
+#include "code\__DEFINES\wj_emotes.dm"
#include "code\__DEFINES\xeno.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
@@ -241,6 +242,9 @@
#include "code\controllers\subsystem\htmlui.dm"
#include "code\controllers\subsystem\human.dm"
#include "code\controllers\subsystem\inactivity.dm"
+#include "code\controllers\subsystem\influxdriver.dm"
+#include "code\controllers\subsystem\influxmcstats.dm"
+#include "code\controllers\subsystem\influxstats.dm"
#include "code\controllers\subsystem\input.dm"
#include "code\controllers\subsystem\interior.dm"
#include "code\controllers\subsystem\item_cleanup.dm"
@@ -367,6 +371,7 @@
#include "code\datums\components\speed_modifier.dm"
#include "code\datums\components\toxin_buildup.dm"
#include "code\datums\components\weed_damage_reduction.dm"
+#include "code\datums\components\weed_food.dm"
#include "code\datums\components\xeno\shield_slash.dm"
#include "code\datums\construction\construction_template.dm"
#include "code\datums\construction\xenomorph\construction_template_xenomorph.dm"
@@ -1777,7 +1782,6 @@
#include "code\modules\mob\living\carbon\human\powers\human_powers.dm"
#include "code\modules\mob\living\carbon\human\powers\issue_order.dm"
#include "code\modules\mob\living\carbon\human\species\emote-monkey.dm"
-#include "code\modules\mob\living\carbon\human\species\emote-synthetic.dm"
#include "code\modules\mob\living\carbon\human\species\emote-yautja.dm"
#include "code\modules\mob\living\carbon\human\species\human.dm"
#include "code\modules\mob\living\carbon\human\species\monkey.dm"
@@ -1785,6 +1789,16 @@
#include "code\modules\mob\living\carbon\human\species\synthetic.dm"
#include "code\modules\mob\living\carbon\human\species\yautja.dm"
#include "code\modules\mob\living\carbon\human\species\zombie.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\_emote.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\_species.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\farewell.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\greeting.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\notice.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\question.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\quip.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\restricted_area.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\task_update.dm"
+#include "code\modules\mob\living\carbon\human\species\working_joe\warning.dm"
#include "code\modules\mob\living\carbon\xenomorph\Abilities.dm"
#include "code\modules\mob\living\carbon\xenomorph\attack_alien.dm"
#include "code\modules\mob\living\carbon\xenomorph\damage_procs.dm"
diff --git a/html/changelogs/AutoChangeLog-pr-3298.yml b/html/changelogs/AutoChangeLog-pr-3298.yml
deleted file mode 100644
index 0a75d2215043..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3298.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-author: "realforest2001"
-delete-after: True
-changes:
- - rscadd: "Added an admin button for an ARES bioscan. Slightly refactored how Bioscans trigger for marines, relying on an ARES processor."
- - rscadd: "Added individual proccessors for ARES systems. These are WIP and will eventually have damage and repair interactions."
- - rscadd: "Added an ARES interaction console in the AI Core room, which holds logs for most ARES functions."
- - rscadd: "Added the ability for ARES console to call ERT or Distress."
- - rscadd: "Added motion triggers in ARES core and shipside comms that send alerts over Apollo."
- - rscadd: "Added a 1to1 conversation feature between ARES and users of the interface console."
- - rscadd: "Added preset open versions of blended poddoors."
- - rscadd: "Added a console for directing Working Joes. This is largely WIP for future PR(s)."
- - rscadd: "Added subtypes of air pipes that don't explode on hijack, used these in ARES core."
- - maptweak: "Remodelled ARES Core onto a fake-z, and added the new processors."
- - bugfix: "door_control buttons now respect being indestructable when processing explosions."
- - maptweak: "Fixed the M39s overflowing in brig armory due to use of landmarks."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3666.yml b/html/changelogs/AutoChangeLog-pr-3666.yml
deleted file mode 100644
index d7d8860c237e..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3666.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Morrow"
-delete-after: True
-changes:
- - balance: "Xenos no longer can pull dead xenos"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3717.yml b/html/changelogs/AutoChangeLog-pr-3717.yml
deleted file mode 100644
index 6d6327ea63a7..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3717.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Drathek"
-delete-after: True
-changes:
- - bugfix: "Fixed the crashsite offset for a hijack shuttle that gets deterred by the MGAD System"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3718.yml b/html/changelogs/AutoChangeLog-pr-3718.yml
deleted file mode 100644
index 41f5322a2493..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3718.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "TheGamerdk"
-delete-after: True
-changes:
- - balance: "Queen boosted building no longer has 2 second cooldown when far from hive"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3723.yml b/html/changelogs/AutoChangeLog-pr-3723.yml
deleted file mode 100644
index 987b7eeec708..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3723.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "TheGamerdk"
-delete-after: True
-changes:
- - bugfix: "Communications intel objective now actually works"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3730.yml b/html/changelogs/AutoChangeLog-pr-3730.yml
deleted file mode 100644
index 9ae18e6bc788..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3730.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "fira"
-delete-after: True
-changes:
- - bugfix: "Fixed a MC crash related to NPC huggers rebounding logic."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3731.yml b/html/changelogs/AutoChangeLog-pr-3731.yml
deleted file mode 100644
index 21fd76664d2b..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3731.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "blackdragonTOW"
-delete-after: True
-changes:
- - maptweak: "Added a small light to unlit rooms."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3736.yml b/html/changelogs/AutoChangeLog-pr-3736.yml
deleted file mode 100644
index c1eba0beabfd..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3736.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Morrow"
-delete-after: True
-changes:
- - rscdel: "Removed toxin mags on shivas"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3742.yml b/html/changelogs/AutoChangeLog-pr-3742.yml
deleted file mode 100644
index 7319ac52f8d9..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3742.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "SpartanBobby"
-delete-after: True
-changes:
- - maptweak: "Minor decal changes to LV522"
- - maptweak: "Buffed sec armory on LV522"
- - maptweak: "LV522 Breaching charge moved to the PROP APC made UNACIDABLE"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3743.yml b/html/changelogs/AutoChangeLog-pr-3743.yml
deleted file mode 100644
index 084e74fc2c2e..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3743.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Katskan"
-delete-after: True
-changes:
- - balance: "Removed G8A storage from various snow suits and parkas"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-3818.yml b/html/changelogs/AutoChangeLog-pr-3818.yml
new file mode 100644
index 000000000000..8da4da215361
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-3818.yml
@@ -0,0 +1,10 @@
+author: "Unknownity"
+delete-after: True
+changes:
+ - bugfix: "Fixed burrowed mobs being able to be targeted by sentries, mines and SG autofire."
+ - bugfix: "Fixed burrowed mobs being able to grab mobs on the surface."
+ - bugfix: "Fixed burrowed mobs being able to resist while burrowed."
+ - bugfix: "Fixed burrowers taking damage from direct flame and shrapnel from explosions."
+ - bugfix: "Fixed burrowers being able to get slashed from enemy Xenos on the surface."
+ - bugfix: "Fixed burrowers unburrow stun to now properly target and stun enemy Xenos."
+ - soundadd: "Added sounds for the Burrower when they are burrowing and unburrowing."
\ No newline at end of file
diff --git a/html/changelogs/archive/2023-06.yml b/html/changelogs/archive/2023-06.yml
index 71aeb035f4b8..e2d9dfed44d7 100644
--- a/html/changelogs/archive/2023-06.yml
+++ b/html/changelogs/archive/2023-06.yml
@@ -383,3 +383,62 @@
- balance: explosive barricade upgrade provides strong protection against brute-based
projectiles (50%)
- balance: explosive barricade upgrade provides strong protection against fire (50%)
+2023-06-29:
+ Diegoflores31:
+ - balance: reduced Larva Burst time from 10 minutes to 7.5 minutes
+ - refactor: changed 1 letter vars.
+ Drathek:
+ - bugfix: Fixed the crashsite offset for a hijack shuttle that gets deterred by
+ the MGAD System
+ IowaPotatoFarmer:
+ - rscadd: Solaris Ridge now has a Corporate Liaison survivor.
+ Katskan:
+ - balance: Removed G8A storage from various snow suits and parkas
+ Morrow:
+ - balance: Xenos no longer can pull dead xenos
+ - rscdel: Removed toxin mags on shivas
+ SpartanBobby:
+ - maptweak: Minor decal changes to LV522
+ - maptweak: Buffed sec armory on LV522
+ - maptweak: LV522 Breaching charge moved to the PROP APC made UNACIDABLE
+ TheGamerdk:
+ - bugfix: Communications intel objective now actually works
+ - balance: Queen boosted building no longer has 2 second cooldown when far from
+ hive
+ blackdragonTOW:
+ - maptweak: Added a small light to unlit rooms.
+ fira:
+ - bugfix: Fixed a MC crash related to NPC huggers rebounding logic.
+ realforest2001:
+ - rscadd: Added an admin button for an ARES bioscan. Slightly refactored how Bioscans
+ trigger for marines, relying on an ARES processor.
+ - rscadd: Added individual proccessors for ARES systems. These are WIP and will
+ eventually have damage and repair interactions.
+ - rscadd: Added an ARES interaction console in the AI Core room, which holds logs
+ for most ARES functions.
+ - rscadd: Added the ability for ARES console to call ERT or Distress.
+ - rscadd: Added motion triggers in ARES core and shipside comms that send alerts
+ over Apollo.
+ - rscadd: Added a 1to1 conversation feature between ARES and users of the interface
+ console.
+ - rscadd: Added preset open versions of blended poddoors.
+ - rscadd: Added a console for directing Working Joes. This is largely WIP for future
+ PR(s).
+ - rscadd: Added subtypes of air pipes that don't explode on hijack, used these in
+ ARES core.
+ - maptweak: Remodelled ARES Core onto a fake-z, and added the new processors.
+ - bugfix: door_control buttons now respect being indestructable when processing
+ explosions.
+ - maptweak: Fixed the M39s overflowing in brig armory due to use of landmarks.
+2023-06-30:
+ Drathek:
+ - bugfix: Fix runtimes with minimap subsystem not handling targets inside of objects
+ during removal
+ - code_imp: Ported VERB_REF and TYPE_VERB_REF from TG for 515 compatibility
+ - code_imp: Removed unnecessary calculations when updating a mob's transform.
+ Morrow:
+ - rscdel: Removed wrong warden locker
+ TheGamerdk:
+ - bugfix: You can now re-enter your body when unnested, marine mains, rejoice!
+ - bugfix: Lurkers no longer lose their pounce if they happen to end their pounce
+ on a tile with a dead human
diff --git a/html/changelogs/archive/2023-07.yml b/html/changelogs/archive/2023-07.yml
new file mode 100644
index 000000000000..60d6cf4a4540
--- /dev/null
+++ b/html/changelogs/archive/2023-07.yml
@@ -0,0 +1,156 @@
+2023-07-01:
+ Ben10083:
+ - bugfix: lisps now occur over radio as well
+ - refactor: Relevant variables relating to speech problems now use true and false
+ instead of 1 and 0
+ - bugfix: fixes exploit relating to cloaking tarps by adding a delay before tarp
+ can be closed again.
+ Drathek:
+ - bugfix: Fixed missing xeno ban check for playing as a facehugger
+ - spellcheck: Tweaked message when ghosting while nested
+ TheGamerdk:
+ - rscadd: ARES will announce how many Foxtrot marines that woke up.
+ harryob:
+ - rscadd: you can no longer implant yourself with a motion detector
+ ihatethisengine:
+ - rscadd: Added cameras to dropship deployable sentries.
+2023-07-02:
+ Clairion:
+ - bugfix: Vampire Lurker headbite will no longer trigger if the target is moved
+ away during windup.
+ Cthulhu80:
+ - rscadd: added camera shake and stun to OB.
+ fira:
+ - rscadd: Added InfluxDB backed metrics logging for some of the most used game statistics.
+ This will allow to graph them over time and give better insight as to what happens
+ in rounds.
+ theselfish:
+ - qol: Made the MP beret to be clickable.
+ - imageadd: Updated the MP beret obj sprite to be easier to click.
+2023-07-03:
+ blackdragonTOW:
+ - refactor: refactored dropship_ammo so that missiles don't use Spawn()
+ ghostsheet:
+ - bugfix: Fixed Extended Barrel bug of building up free bullet velocity.
+2023-07-04:
+ Drathek:
+ - bugfix: Fixed a persistence problem for the time of death value used for the larva
+ queue.
+ - bugfix: Fixed brainmobs (human gibbing) not getting a time of death value.
+ harryob:
+ - bugfix: paper images should appear once again
+ zzzmike:
+ - balance: pods crash land 33% of the time rather than 50%
+2023-07-05:
+ BeagleGaming1:
+ - rscadd: Queen makes noise when moved even while resting
+ Hopek, Gul Dreggrod:
+ - rscadd: Adds a Luxurious fountain pen to the game. Currently spawns on the right
+ ear of high ranking command staff. Sprited by Gul Dreggrod.
+ QuickLode:
+ - rscadd: CMB/ICC Survivors now get a limited radio that they can use to communicate
+ with CMB reinforcements.
+ - rscadd: CMB/Anchorpoint Marines get bandages to stop bleeding.
+ harryob:
+ - rscadd: all xenos can now access the tacmap, while the queen is on ovi
+2023-07-06:
+ BeagleGaming1:
+ - code_imp: evacuation pod crash chance changed to a var
+ - code_imp: proc added to force evacuation pods to crash
+ Drathek:
+ - bugfix: Fixed more broken logos (primarily WY research papers)
+ - balance: Weed nodes can no longer be placed in walls or window frames (or any
+ turf or structure with density)
+ - refactor: Refactored the plant weeds ability code
+ - balance: Gardener's hardy weeds now upgrade normal weeds (just like hive weeds
+ upgrade weeds).
+ - balance: Gardener's hardy weeds now don't prevent special structures (core and
+ pylons) but they are still only allowed if the turf allows them.
+ Newyearnewme, Morrow:
+ - rscadd: Xeno structures/weeds now become forsaken after hijack
+ Steelpoint:
+ - imageadd: Synth utility vest is now slimmer in appearance
+ realforest2001:
+ - admin: Adds logs for bioscans successfully completing.
+ thatoneyeeter:
+ - balance: metal foam now becomes solid faster
+2023-07-07:
+ Diegoflores31:
+ - rscadd: Defender Tail Slam has a 1% chance to do a BONK sound instead.
+ Drathek:
+ - bugfix: Fixed ghost icons for larva and ovi queen
+ Morrow:
+ - rscdel: Removes fountain pens from gear sets
+ - rscdel: Removed nesting the dead
+ - rscadd: Added a visiting USASF Commander (CO survivor) to New Varadero
+ - bugfix: Burnt matches no longer permanently give you a light source if they naturally
+ burn out
+ QuickLoad:
+ - balance: Colony Synthetics are faster but are less resistant. This allows for
+ the option of avoiding engagements.
+ - balance: Colony Synthetics have slightly better CQC and can carry people better.
+ realforest2001:
+ - bugfix: Fixes Queen making footstep sounds while dead and being dragged.
+2023-07-08:
+ Ben10083:
+ - bugfix: Working Joes can no longer have a gradient on their rare hair spawn.
+ - rscdel: Working Joes can no longer be fed.
+ - code_imp: 'New trait: Cannot eat. Self-explanatory.'
+ Cursor:
+ - spellcheck: Changed Chem Dispenser to Chemical Dispenser, added descriptions to
+ the cryo cell, rail computer and chemical dispenser. Updated the descriptions
+ for the Blood Dispenser and Dropship computer.
+ Drathek:
+ - bugfix: Fixed facehuggers incorrectly displaying thrown state when it has landed
+ Drathek Kugamo:
+ - rscadd: Added the ability for weeds to merge with unrevivable corpses
+ - imageadd: Added human shaped weeds by Kugamo
+ - code_imp: Added a signal for weeds sent to the turf to indicate it is now weeded,
+ and added a signal for afterbuckle.
+ - bugfix: Closets (including coffins) can no longer move anchored mobs.
+ Morrow:
+ - qol: Create humans tab length increase
+ - qol: Create humans tab now defaults to 0 range to spawn
+ - bugfix: Fixed extraneous messages in regards to wall nests
+ SpartanBobby:
+ - balance: changes M4A3 magazine size from 9 to 12
+ Zonespace27:
+ - bugfix: The maintenance jack should work a little better at crowbarring things.
+2023-07-09:
+ Khadd:
+ - rscadd: added a iv tube between the user and the bloodpack / iv drip
+ - imageadd: sprites for the iv tube
+ Zonespace27:
+ - rscadd: Working Joes now have an emote panel to use voice lines, accessible as
+ an action button.
+ theselfish:
+ - qol: Foxtrot's radio channel is now on if you have multi-squad in your headset.
+2023-07-10:
+ BeagleGaming1:
+ - rscadd: Acid runners don't get acid from slashing nested humans
+ - rscadd: Very late-stage marines do not give acid
+ - bugfix: Fixes one way of abusing resin fruit
+ Drathek:
+ - rscdel: Remove first life priority for larva queue
+ - bugfix: Fix ghosting as a facehugger counting as death for the larva queue
+ blackdragonTOW:
+ - spellcheck: Added "U1" designation to the UGL attachment.
+2023-07-11:
+ BeagleGaming1:
+ - bugfix: Fixes the Working Joe species
+2023-07-12:
+ Morrow:
+ - bugfix: Stops xenos from bypassing dead moving via roller beds
+ - balance: Telebaton now scales in stunforce effectiveness based on policing skill
+ - balance: Marines called with foxtrot nerfed from 15 to 10.
+ SpartanBobby:
+ - maptweak: 'LV522: Flips the layout of engineering making the T-comms unit on the
+ far side of the LZ'
+ - balance: reduces the M1911 magazine size from 14 to 7
+ Steelpoint:
+ - balance: Synthetic Armour slowdown has been decreased
+2023-07-13:
+ QuickLoad, Thwomper:
+ - imageadd: Synthetic Uniforms & Vest with United Americas flair and classic jumpsuits
+ which remind you of life on the Frontier. Sprited by THE THWOMPA himself!
+ - qol: Synthetic Vendor is more organized. Removes scrub cap, adds shoe.
diff --git a/html/create_humans.html b/html/create_humans.html
index ed9361fc6f25..06b92cba0c71 100644
--- a/html/create_humans.html
+++ b/html/create_humans.html
@@ -11,7 +11,7 @@
Amount of humans:
- Range to spawn in:
+ Range to spawn in: