diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm
new file mode 100644
index 0000000000..6e44ec3197
--- /dev/null
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm
@@ -0,0 +1,111 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/lavaland/surface/outdoors)
+"b" = (
+/obj/structure/elite_tumor,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/lavaland/surface/outdoors)
+"c" = (
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/lavaland/surface/outdoors)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(3,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(4,1,1) = {"
+a
+a
+a
+c
+c
+c
+a
+a
+a
+"}
+(5,1,1) = {"
+a
+a
+a
+c
+b
+c
+a
+a
+a
+"}
+(6,1,1) = {"
+a
+a
+a
+c
+c
+c
+a
+a
+a
+"}
+(7,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(8,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(9,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 4f56b07fe7..f95ff93ac3 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -36,8 +36,10 @@
//////////////////////////////////////////////////////////////////
// /datum signals
-#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component)
-#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component)
+#define COMSIG_COMPONENT_ADDED "component_added" //sent to the new datum parent when a component is added to them: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing" //sent to the datum parent before a component is removed from them because of RemoveComponent: (/datum/component)
+#define COMSIG_COMPONENT_UNREGISTER_PARENT "component_unregister_parent" //sent to the component itself when unregistered from a parent
+#define COMSIG_COMPONENT_REGISTER_PARENT "component_register_parent" //sent to the component itself when registered to a parent
#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
#define COMSIG_PARENT_QDELETING "parent_qdeleting" //just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
@@ -140,19 +142,22 @@
#define HEARING_RAW_MESSAGE 4
/* #define HEARING_RADIO_FREQ 5
#define HEARING_SPANS 6
- #define HEARING_MESSAGE_MODE 7
+ #define HEARING_MESSAGE_MODE 7
#define HEARING_SOURCE 8*/
#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination)
// /mind signals
-#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to(): (new_character, old_character)
+#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character)
+ #define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening.
+#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character)
// /mob signals
#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A)
#define COMPONENT_ALLOW_EXAMINE 1
#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed)
-#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse)
+ #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat.
+#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize)
#define COMPONENT_BLOCK_GHOSTING 1
#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
@@ -164,7 +169,9 @@
#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params)
#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target)
-#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey()
+#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character)
+#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character)
+// #define COMPONENT_STOP_MIND_TRANSFER 1
#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): ()
#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list)
#define COMPONENT_UPPERCASE_SPEECH 1
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index d772680ce4..6d52f4640c 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -68,6 +68,8 @@
#define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage
+#define STATUS_EFFECT_NECKSLICE /datum/status_effect/neck_slice //Creates the flavor messages for the neck-slice
+
#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse
#define CURSE_BLINDING 1 //makes the edges of the target's screen obscured
#define CURSE_SPAWNING 2 //spawns creatures that attack the target only
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 8cc8960b01..d17db07b50 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -66,6 +66,7 @@
#define TRAIT_HUSK "husk"
#define TRAIT_NOCLONE "noclone"
#define TRAIT_CLUMSY "clumsy"
+#define TRAIT_CHUNKYFINGERS "chunkyfingers" //means that you can't use weapons with normal trigger guards.
#define TRAIT_DUMB "dumb"
#define TRAIT_MONKEYLIKE "monkeylike" //sets IsAdvancedToolUser to FALSE
#define TRAIT_PACIFISM "pacifism"
@@ -223,5 +224,6 @@
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
+#define SLEEPING_CARP_TRAIT "sleeping_carp"
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
#define MADE_UNCLONEABLE "made-uncloneable"
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index f7131e63fa..4a86f57fcd 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -95,4 +95,22 @@ GLOBAL_VAR_INIT(cmp_field, "name")
return sorttext(A.sample_object.name, B.sample_object.name)
/proc/cmp_numbered_displays_name_dsc(datum/numbered_display/A, datum/numbered_display/B)
- return sorttext(B.sample_object.name, A.sample_object.name)
\ No newline at end of file
+ return sorttext(B.sample_object.name, A.sample_object.name)
+
+/proc/cmp_quirk_asc(datum/quirk/A, datum/quirk/B)
+ var/a_sign = num2sign(initial(A.value) * -1)
+ var/b_sign = num2sign(initial(B.value) * -1)
+
+ // Neutral traits go last.
+ if(a_sign == 0)
+ a_sign = 2
+ if(b_sign == 0)
+ b_sign = 2
+
+ var/a_name = initial(A.name)
+ var/b_name = initial(B.name)
+
+ if(a_sign != b_sign)
+ return a_sign - b_sign
+ else
+ return sorttext(b_name, a_name)
\ No newline at end of file
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 9abe42ea8e..29f5331fd3 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1548,4 +1548,12 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
- return call(source, proctype)(arglist(arguments))
\ No newline at end of file
+ return call(source, proctype)(arglist(arguments))
+
+/proc/num2sign(numeric)
+ if(numeric > 0)
+ return 1
+ else if(numeric < 0)
+ return -1
+ else
+ return 0
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 167fa989b2..9d53703cdd 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -107,6 +107,10 @@
#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
#define ui_mood "EAST-1:28,CENTER-3:10"
+//living
+#define ui_living_pull "EAST-1:28,CENTER-2:15"
+#define ui_living_health "EAST-1:28,CENTER:15"
+
//borgs
#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index d2508b3e8e..858b8dbd06 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -113,7 +113,7 @@
name = "Show Buttons"
else
name = "Hide Buttons"
- UpdateIcon()
+ update_icon()
usr.update_action_buttons()
/obj/screen/movable/action_button/hide_toggle/AltClick(mob/user)
@@ -135,9 +135,9 @@
hide_icon = settings["toggle_icon"]
hide_state = settings["toggle_hide"]
show_state = settings["toggle_show"]
- UpdateIcon()
+ update_icon()
-/obj/screen/movable/action_button/hide_toggle/proc/UpdateIcon()
+/obj/screen/movable/action_button/hide_toggle/update_icon()
cut_overlays()
add_overlay(mutable_appearance(hide_icon, hidden ? show_state : hide_state))
diff --git a/code/_onclick/hud/lavaland_elite.dm b/code/_onclick/hud/lavaland_elite.dm
new file mode 100644
index 0000000000..277ea8b898
--- /dev/null
+++ b/code/_onclick/hud/lavaland_elite.dm
@@ -0,0 +1,16 @@
+/datum/hud/lavaland_elite
+ ui_style = 'icons/mob/screen_elite.dmi'
+
+/datum/hud/lavaland_elite/New(mob/living/simple_animal/hostile/asteroid/elite)
+ ..()
+
+ pull_icon = new /obj/screen/pull()
+ pull_icon.icon = ui_style
+ pull_icon.update_icon()
+ pull_icon.screen_loc = ui_living_pull
+ pull_icon.hud = src
+ static_inventory += pull_icon
+
+ healths = new /obj/screen/healths/lavaland_elite()
+ healths.hud = src
+ infodisplay += healths
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 5f128ff76d..86b4b0c024 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -644,6 +644,12 @@
screen_loc = ui_construct_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+/obj/screen/healths/lavaland_elite
+ icon = 'icons/mob/screen_elite.dmi'
+ icon_state = "elite_health0"
+ screen_loc = ui_health
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
/obj/screen/healthdoll
name = "health doll"
screen_loc = ui_healthdoll
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 704fa3763a..88637a3881 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -12,29 +12,43 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
var/list/quirk_names_by_path = list()
var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad
var/list/quirk_objects = list() //A list of all quirk objects in the game, since some may process
+ var/list/quirk_blacklist = list() //A list a list of quirks that can not be used with each other. Format: list(quirk1,quirk2),list(quirk3,quirk4)
/datum/controller/subsystem/processing/quirks/Initialize(timeofday)
if(!quirks.len)
SetupQuirks()
+ quirk_blacklist = list(list("Blind","Nearsighted"),list("Jolly","Depression","Apathetic"),list("Ageusia","Deviant Tastes"),list("Ananas Affinity","Ananas Aversion"))
return ..()
/datum/controller/subsystem/processing/quirks/proc/SetupQuirks()
- for(var/V in subtypesof(/datum/quirk))
+// Sort by Positive, Negative, Neutral; and then by name
+ var/list/quirk_list = sortList(subtypesof(/datum/quirk), /proc/cmp_quirk_asc)
+
+ for(var/V in quirk_list)
var/datum/quirk/T = V
quirks[initial(T.name)] = T
quirk_points[initial(T.name)] = initial(T.value)
quirk_names_by_path[T] = initial(T.name)
/datum/controller/subsystem/processing/quirks/proc/AssignQuirks(mob/living/user, client/cli, spawn_effects, roundstart = FALSE, datum/job/job, silent = FALSE, mob/to_chat_target)
- GenerateQuirks(cli)
- var/list/quirks = cli.prefs.character_quirks.Copy()
+ var/badquirk = FALSE
+ var/list/my_quirks = cli.prefs.all_quirks.Copy()
var/list/cut
- if(job && job.blacklisted_quirks)
- cut = filter_quirks(quirks, job)
- for(var/V in quirks)
- user.add_quirk(V, spawn_effects)
+ if(job?.blacklisted_quirks)
+ cut = filter_quirks(my_quirks, job)
+ for(var/V in my_quirks)
+ var/datum/quirk/Q = quirks[V]
+ if(Q)
+ user.add_quirk(Q, spawn_effects)
+ else
+ stack_trace("Invalid quirk \"[V]\" in client [cli.ckey] preferences")
+ cli.prefs.all_quirks -= V
+ badquirk = TRUE
+ if(badquirk)
+ cli.prefs.save_character()
if (!silent && LAZYLEN(cut))
to_chat(to_chat_target || user, "Some quirks have been cut from your character because of these quirks conflicting with your job assignment: [english_list(cut)].")
+
/datum/controller/subsystem/processing/quirks/proc/quirk_path_by_name(name)
return quirks[name]
@@ -49,7 +63,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
for(var/i in quirk_names)
. += quirk_points_by_name(i)
-/datum/controller/subsystem/processing/quirks/proc/filter_quirks(list/quirks, datum/job/job)
+/datum/controller/subsystem/processing/quirks/proc/filter_quirks(list/our_quirks, datum/job/job)
var/list/cut = list()
var/list/banned_names = list()
var/pointscut = 0
@@ -57,44 +71,31 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
var/name = quirk_name_by_path(i)
if(name)
banned_names += name
- var/list/blacklisted = quirks & banned_names
+ var/list/blacklisted = our_quirks & banned_names
if(length(blacklisted))
for(var/i in blacklisted)
- quirks -= i
+ our_quirks -= i
cut += i
pointscut += quirk_points_by_name(i)
if (pointscut != 0)
- var/list/pickquirks = list()
- var/k = 0
- while (k < 50) //chances and randoms
- k++
- if (!(pick(quirks) in pickquirks))
- pickquirks += pick(quirks)
- if (LAZYLEN(pickquirks) == LAZYLEN(quirks))
- break
- for (var/l in quirks) //make sure we get them all with no repeats
- if (LAZYLEN(pickquirks) == LAZYLEN(quirks))
- break
- if (!(l in pickquirks))
- pickquirks += l
- for (var/i in pickquirks)
+ for (var/i in shuffle(our_quirks))
if (quirk_points_by_name(i) < pointscut || (pointscut < 0) ? quirk_points_by_name(i) <= 0 : quirk_points_by_name(i) >= 0)
continue
else
- quirks -= i
+ our_quirks -= i
cut += i
pointscut += quirk_points_by_name(i)
if (pointscut == 0)
break
/* //Code to automatically reduce positive quirks until balance is even.
- var/points_used = total_points(quirks)
+ var/points_used = total_points(our_quirks)
if(points_used > 0)
//they owe us points, let's collect.
- for(var/i in quirks)
+ for(var/i in our_quirks)
var/points = quirk_points_by_name(i)
if(points > 0)
cut += i
- quirks -= i
+ our_quirks -= i
points_used -= points
if(points_used <= 0)
break
@@ -103,14 +104,9 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
//Nah, let's null all non-neutrals out.
if (pointscut != 0)// only if the pointscutting didn't work.
if(cut.len)
- for(var/i in quirks)
+ for(var/i in our_quirks)
if(quirk_points_by_name(i) != 0)
//cut += i -- Commented out: Only show the ones that triggered the quirk purge.
- quirks -= i
+ our_quirks -= i
return cut
-
-/datum/controller/subsystem/processing/quirks/proc/GenerateQuirks(client/user)
- if(user.prefs.character_quirks.len)
- return
- user.prefs.character_quirks = user.prefs.all_quirks
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 6f619fef0b..890725fbb1 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -384,8 +384,8 @@ SUBSYSTEM_DEF(ticker)
captainless=0
if(player.mind.assigned_role != player.mind.special_role)
SSjob.EquipRank(N, player.mind.assigned_role, 0)
- if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
- SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
+ if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
+ SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
CHECK_TICK
if(captainless)
for(var/mob/dead/new_player/N in GLOB.player_list)
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index f938a70308..72b9681f91 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -53,7 +53,7 @@
// If you want/expect to be moving the component around between parents, use this to register on the parent for signals
/datum/component/proc/RegisterWithParent()
- return
+ SEND_SIGNAL(src, COMSIG_COMPONENT_REGISTER_PARENT) //CITADEL EDIT
/datum/component/proc/Initialize(...)
return
@@ -85,7 +85,7 @@
UnregisterFromParent()
/datum/component/proc/UnregisterFromParent()
- return
+ SEND_SIGNAL(src, COMSIG_COMPONENT_UNREGISTER_PARENT) //CITADEL EDIT
/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE)
if(QDELETED(src) || QDELETED(target))
diff --git a/code/datums/components/bane.dm b/code/datums/components/bane.dm
index 84f8010270..bdfcfed517 100644
--- a/code/datums/components/bane.dm
+++ b/code/datums/components/bane.dm
@@ -19,12 +19,14 @@
src.damage_multiplier = damage_multiplier
/datum/component/bane/RegisterWithParent()
+ . = ..()
if(speciestype)
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/speciesCheck)
else
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/mobCheck)
/datum/component/bane/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK)
/datum/component/bane/proc/speciesCheck(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/bouncy.dm b/code/datums/components/bouncy.dm
index f6a2a89195..c7ca85455b 100644
--- a/code/datums/components/bouncy.dm
+++ b/code/datums/components/bouncy.dm
@@ -21,9 +21,11 @@
RegisterSignal(parent, bounce, .proc/bounce_up)
/datum/component/bouncy/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, bounce_signals, .proc/bounce_up)
/datum/component/bouncy/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, bounce_signals)
/datum/component/bouncy/proc/bounce_up(datum/source)
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index 1518a84456..d5af47ea1f 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -23,17 +23,51 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/onItemAttack)
/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user)
- if(user.a_intent == INTENT_HARM && M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
+ if(user.a_intent != INTENT_HARM)
+ return
+ if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
if(butchering_enabled && (can_be_blunt || source.get_sharpness()))
INVOKE_ASYNC(src, .proc/startButcher, source, M, user)
return COMPONENT_ITEM_NO_ATTACK
+ if(ishuman(M) && source.force && source.get_sharpness())
+ var/mob/living/carbon/human/H = M
+ if((H.health <= H.crit_threshold || (user.pulling == H && user.grab_state >= GRAB_NECK) || H.IsSleeping()) && user.zone_selected == BODY_ZONE_HEAD) // Only sleeping, neck grabbed, or crit, can be sliced.
+ if(H.has_status_effect(/datum/status_effect/neck_slice))
+ user.show_message("[H]'s neck has already been already cut, you can't make the bleeding any worse!", 1, \
+ "Their neck has already been already cut, you can't make the bleeding any worse!")
+ return COMPONENT_ITEM_NO_ATTACK
+ INVOKE_ASYNC(src, .proc/startNeckSlice, source, H, user)
+ return COMPONENT_ITEM_NO_ATTACK
+
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user)
to_chat(user, "You begin to butcher [M]...")
playsound(M.loc, butcher_sound, 50, TRUE, -1)
if(do_mob(user, M, speed) && M.Adjacent(source))
Butcher(user, M)
+/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
+ user.visible_message("[user] is slitting [H]'s throat!", \
+ "You start slicing [H]'s throat!", \
+ "You hear a cutting noise!", ignored_mobs = H)
+ H.show_message("Your throat is being slit by [user]!", 1, \
+ "Something is cutting into your neck!", NONE)
+ log_combat(user, H, "starts slicing the throat of")
+
+ playsound(H.loc, butcher_sound, 50, TRUE, -1)
+ if(do_mob(user, H, CLAMP(500 / source.force, 30, 100)) && H.Adjacent(source))
+ if(H.has_status_effect(/datum/status_effect/neck_slice))
+ user.show_message("[H]'s neck has already been already cut, you can't make the bleeding any worse!", 1, \
+ "Their neck has already been already cut, you can't make the bleeding any worse!")
+ return
+
+ H.visible_message("[user] slits [H]'s throat!", \
+ "[user] slits your throat...")
+ log_combat(user, H, "finishes slicing the throat of")
+ H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD)
+ H.bleed_rate = CLAMP(H.bleed_rate + 20, 0, 30)
+ H.apply_status_effect(/datum/status_effect/neck_slice)
+
/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
var/turf/T = meat.drop_location()
var/final_effectiveness = effectiveness - meat.butcher_difficulty
diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm
index 641dbdb1cf..60317797a7 100644
--- a/code/datums/components/decal.dm
+++ b/code/datums/components/decal.dm
@@ -17,6 +17,7 @@
apply()
/datum/component/decal/RegisterWithParent()
+ . = ..()
if(first_dir)
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
if(cleanable)
@@ -25,6 +26,7 @@
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
/datum/component/decal/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
/datum/component/decal/Destroy()
diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm
index 86e016784a..9e8493b6f4 100644
--- a/code/datums/components/fantasy/_fantasy.dm
+++ b/code/datums/components/fantasy/_fantasy.dm
@@ -30,11 +30,13 @@
return ..()
/datum/component/fantasy/RegisterWithParent()
+ . = ..()
var/obj/item/master = parent
originalName = master.name
modify()
/datum/component/fantasy/UnregisterFromParent()
+ . = ..()
unmodify()
/datum/component/fantasy/InheritComponent(datum/component/fantasy/newComp, original, list/arguments)
diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm
index b40383e828..13944b1200 100644
--- a/code/datums/components/igniter.dm
+++ b/code/datums/components/igniter.dm
@@ -9,6 +9,7 @@
src.fire_stacks = fire_stacks
/datum/component/igniter/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -17,6 +18,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/igniter/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/igniter/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/knockback.dm b/code/datums/components/knockback.dm
index b4fcaa2dd8..988a0e575e 100644
--- a/code/datums/components/knockback.dm
+++ b/code/datums/components/knockback.dm
@@ -10,6 +10,7 @@
src.throw_anchored = throw_anchored
/datum/component/knockback/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -18,6 +19,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/knockback/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/knockback/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/lifesteal.dm b/code/datums/components/lifesteal.dm
index c7a78e10a3..9d62d32866 100644
--- a/code/datums/components/lifesteal.dm
+++ b/code/datums/components/lifesteal.dm
@@ -10,6 +10,7 @@
src.flat_heal = flat_heal
/datum/component/lifesteal/RegisterWithParent()
+ . = ..()
if(isgun(parent))
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -18,6 +19,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/lifesteal/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/lifesteal/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index a0e6f97de0..2ecd77546d 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -150,15 +150,6 @@
if(9)
setSanity(sanity+0.4, maximum=SANITY_GREAT)
- if(HAS_TRAIT(owner, TRAIT_DEPRESSION))
- if(prob(0.05))
- add_event(null, "depression", /datum/mood_event/depression)
- clear_event(null, "jolly")
- if(HAS_TRAIT(owner, TRAIT_JOLLY))
- if(prob(0.05))
- add_event(null, "jolly", /datum/mood_event/jolly)
- clear_event(null, "depression")
-
HandleNutrition(owner)
/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)//I'm sure bunging this in here will have no negative repercussions.
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 362961a24f..0ef13b514b 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -34,6 +34,7 @@
cloud_sync()
/datum/component/nanites/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites)
RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data)
RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs)
@@ -57,6 +58,7 @@
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
/datum/component/nanites/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_HAS_NANITES,
COMSIG_NANITE_UI_DATA,
COMSIG_NANITE_GET_PROGRAMS,
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index efa0fd14d5..05174c196b 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -20,12 +20,14 @@
begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
/datum/component/orbiter/RegisterWithParent()
+ . = ..()
var/atom/target = parent
while(ismovableatom(target))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
target = target.loc
/datum/component/orbiter/UnregisterFromParent()
+ . = ..()
var/atom/target = parent
while(ismovableatom(target))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
diff --git a/code/datums/components/shrapnel.dm b/code/datums/components/shrapnel.dm
index a911221f26..4d1fe21b95 100644
--- a/code/datums/components/shrapnel.dm
+++ b/code/datums/components/shrapnel.dm
@@ -13,10 +13,12 @@
src.override_projectile_range = override_projectile_range
/datum/component/shrapnel/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
/datum/component/shrapnel/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT))
/datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm
index 552959603d..61718301b3 100644
--- a/code/datums/components/summoning.dm
+++ b/code/datums/components/summoning.dm
@@ -24,6 +24,7 @@
src.faction = faction
/datum/component/summoning/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -32,6 +33,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/summoning/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/summoning/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm
index 5917fc3009..ba028e2fd5 100644
--- a/code/datums/components/tactical.dm
+++ b/code/datums/components/tactical.dm
@@ -9,10 +9,12 @@
src.allowed_slot = allowed_slot
/datum/component/tactical/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)
/datum/component/tactical/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
unmodify()
diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm
index 7bad836e47..2f0405af2e 100644
--- a/code/datums/components/virtual_reality.dm
+++ b/code/datums/components/virtual_reality.dm
@@ -1,128 +1,245 @@
+/**
+ * The virtual reality turned component.
+ * Originally created to overcome issues of mob polymorphing locking the player inside virtual reality
+ * and allow for a more "immersive" virtual reality in a virtual reality experience.
+ * It relies on comically complex order of logic, expect things to break if procs such as mind/transfer_to() are revamped.
+ * In short, a barebone not so hardcoded VR framework.
+ * If you plan to add more devices that make use of this component, remember to isolate their code outta here where possible.
+ */
/datum/component/virtual_reality
can_transfer = TRUE
- var/datum/mind/mastermind // where is my mind t. pixies
+ //the player's mind (not the parent's), should something happen to them or to their mob.
+ var/datum/mind/mastermind
+ //the current mob's mind, which we need to keep track for mind transfer.
var/datum/mind/current_mind
- var/obj/machinery/vr_sleeper/vr_sleeper
+ //the action datum used by the mob to quit the vr session.
var/datum/action/quit_vr/quit_action
+ //This one's name should be self explainatory, currently used for emags.
var/you_die_in_the_game_you_die_for_real = FALSE
- var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death.
+ //Used to allow people to play recursively playing vr while playing vr without many issues.
+ var/datum/component/virtual_reality/level_below
+ var/datum/component/virtual_reality/level_above
+ //Used to stop the component from executing certain functions that'd cause us some issues otherwise.
+ //FALSE if there is a connected player, otherwise TRUE.
+ var/session_paused = TRUE
+ //Used to stop unwarranted behaviour from happening in cases where the master mind transference is unsupported. Set on Initialize().
+ var/allow_mastermind_transfer = FALSE
-/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE)
- if(!ismob(parent) || !istype(M))
+/datum/component/virtual_reality/Initialize(yolo = FALSE, _allow_mastermind_transfer = FALSE)
+ var/mob/M = parent
+ if(!istype(M) || !M.mind)
return COMPONENT_INCOMPATIBLE
- var/mob/vr_M = parent
- mastermind = M.mind
- RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
- RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player)
- RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player)
you_die_in_the_game_you_die_for_real = yolo
- quit_action = new()
- if(gaming_pod)
- vr_sleeper = gaming_pod
- RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once)
- RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality)
- vr_M.ckey = M.ckey
- var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality)
- if(clusterfk && !clusterfk.inception)
- clusterfk.inception = src
- SStgui.close_user_uis(M, src)
+ allow_mastermind_transfer = _allow_mastermind_transfer
+ quit_action = new
+
+/datum/component/virtual_reality/Destroy()
+ QDEL_NULL(quit_action)
+ if(level_above)
+ level_above.level_below = null
+ level_above = null
+ if(level_below)
+ level_below.level_above = null
+ level_below = null
+ return ..()
/datum/component/virtual_reality/RegisterWithParent()
+ . = ..()
var/mob/M = parent
current_mind = M.mind
+ if(!quit_action)
+ quit_action = new
quit_action.Grant(M)
- RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality)
+ RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger)
RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter)
- RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote)
- RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote)
- mastermind.current.audiovisual_redirect = M
- if(vr_sleeper)
- vr_sleeper.vr_mob = M
+ RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/on_player_transfer)
+ RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/on_player_transfer)
+ RegisterSignal(current_mind, COMSIG_PRE_MIND_TRANSFER, .proc/pre_player_transfer)
+ if(mastermind?.current)
+ mastermind.current.audiovisual_redirect = M
/datum/component/virtual_reality/UnregisterFromParent()
- quit_action.Remove(parent)
+ . = ..()
+ if(quit_action)
+ quit_action.Remove(parent)
+ UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE))
- UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER)
- UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
+ UnregisterSignal(current_mind, list(COMSIG_MIND_TRANSFER, COMSIG_PRE_MIND_TRANSFER))
current_mind = null
- mastermind.current.audiovisual_redirect = null
+ if(mastermind?.current)
+ mastermind.current.audiovisual_redirect = null
-/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
- if(vr_sleeper || !new_mob.mind)
- // Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up?
- revert_to_reality()
- return
- old_mob.audiovisual_redirect = null
- new_mob.audiovisual_redirect = parent
-
-/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
- if(source != quit_action)
- return COMPONENT_ACTION_BLOCK_TRIGGER
- revert_to_reality(signal_source)
+/**
+ * Called when attempting to connect a mob to a virtual reality mob.
+ * This will return FALSE if the mob is without player or dead. TRUE otherwise
+ */
+/datum/component/virtual_reality/proc/connect(mob/M)
+ var/mob/vr_M = parent
+ if(!M.mind || M.stat == DEAD || !vr_M.mind || vr_M.stat == DEAD)
+ return FALSE
+ var/datum/component/virtual_reality/VR = M.GetComponent(/datum/component/virtual_reality)
+ if(VR)
+ VR.level_below = src
+ level_above = VR
+ M.transfer_ckey(vr_M, FALSE)
+ mastermind = M.mind
+ mastermind.current.audiovisual_redirect = parent
+ RegisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER, .proc/switch_player)
+ RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
+ RegisterSignal(M, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked)
+ SStgui.close_user_uis(vr_M, src)
+ session_paused = FALSE
+ return TRUE
+/**
+ * emag_act() hook. Makes the game deadlier, killing the mastermind mob too should the parent die.
+ */
/datum/component/virtual_reality/proc/you_only_live_once()
- if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact)
+ if(you_die_in_the_game_you_die_for_real)
return FALSE
you_die_in_the_game_you_die_for_real = TRUE
return TRUE
-/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob)
- if(new_mob == mastermind.current)
- revert_to_reality(source)
- return TRUE
- new_mob.TakeComponent(src)
- return TRUE
+/**
+ * Called when the mastermind mind is transferred to another mob.
+ * This is pretty much just going to simply quit the session until machineries support polymorphed occupants etcetera.
+ */
+/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
+ if(session_paused)
+ return
+ if(!allow_mastermind_transfer)
+ quit()
+ return COMPONENT_STOP_MIND_TRANSFER
+ UnregisterSignal(old_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE))
+ RegisterSignal(new_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
+ RegisterSignal(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked)
+ old_mob.audiovisual_redirect = null
+ new_mob.audiovisual_redirect = parent
+/**
+ * Called to stop the player mind from being transferred should the new mob happen to be one of our masterminds'.
+ * Since the target's mind.current is going to be null'd in the mind transfer process,
+ * This has to be done in a different signal proc than on_player_transfer(), by then the mastermind.current will be null.
+ */
+/datum/component/virtual_reality/proc/pre_player_transfer(datum/source, mob/new_mob, mob/old_mob)
+ if(!mastermind || session_paused)
+ return
+ if(new_mob == mastermind.current)
+ quit()
+ return COMPONENT_STOP_MIND_TRANSFER
+ if(!level_above)
+ return
+ var/datum/component/virtual_reality/VR = level_above
+ while(VR)
+ if(VR.mastermind.current == new_mob)
+ VR.quit() //this will revert the ckey back to new_mob.
+ return COMPONENT_STOP_MIND_TRANSFER
+ VR = VR.level_above
+
+/**
+ * Called when someone or something else is somewhat about to replace the mastermind's mob key somehow.
+ * And potentially lock the player in a broken virtual reality plot. Not really something to be proud of.
+ */
+/datum/component/virtual_reality/proc/player_hijacked(datum/source, mob/our_character, mob/their_character)
+ if(session_paused)
+ return
+ if(!their_character)
+ quit(cleanup = TRUE)
+ return
+ var/will_it_be_handled_in_their_pre_player_transfer = FALSE
+ var/datum/component/virtual_reality/VR = src
+ while(VR)
+ if(VR.parent == their_character)
+ will_it_be_handled_in_their_pre_player_transfer = TRUE
+ break
+ VR = VR.level_below
+ if(!will_it_be_handled_in_their_pre_player_transfer) //it's not the player playing shenanigeans, abandon all ships.
+ quit(cleanup = TRUE)
+
+/**
+ * Takes care of moving the component from a mob to another when their mind or ckey is transferred.
+ * The very reason this component even exists (else one would be stuck playing as a monky if monkyified)
+ */
+/datum/component/virtual_reality/proc/on_player_transfer(datum/source, mob/new_mob, mob/old_mob)
+ new_mob.TakeComponent(src)
+
+/**
+ * Required for the component to be transferable from mob to mob.
+ */
/datum/component/virtual_reality/PostTransfer()
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE
+/**
+ *The following procs simply acts as hooks for quit(), since components do not use callbacks anymore
+ */
+/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
+ quit()
+ return COMPONENT_ACTION_BLOCK_TRIGGER
+
/datum/component/virtual_reality/proc/revert_to_reality(datum/source)
- quit_it()
+ quit()
/datum/component/virtual_reality/proc/game_over(datum/source)
- quit_it(TRUE, TRUE)
+ quit(you_die_in_the_game_you_die_for_real, TRUE)
+ return COMPONENT_BLOCK_DEATH_BROADCAST
-/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse)
- quit_it()
- return COMPONENT_BLOCK_GHOSTING
+/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse, special = FALSE, penalize = FALSE)
+ if(!special)
+ quit()
+ return COMPONENT_BLOCK_GHOSTING
-/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg)
+/datum/component/virtual_reality/proc/machine_destroyed(datum/source)
+ quit(cleanup = TRUE)
+
+/**
+ * Takes care of deleting itself, moving the player back to the mastermind's current and queueing the parent for deletion.
+ * It supports nested virtual realities by recursively calling vr_in_a_vr(), which in turns calls quit(),
+ * up to the deepest level, where the ckey will be transferred back to our mastermind's mob instead.
+ * The above operation is skipped when session_paused is TRUE (ergo no player in control of the current mob).
+ * vars:
+ * * deathcheck is used to kill the master, you want this FALSE unless for stuff that doesn't involve emagging.
+ * * cleanup is used to queue the parent for the next vr_clean_master's run, where they'll be deleted should they be dead.
+ * * mob/override is used for the recursive virtual reality explained above and shouldn't be used outside of vr_in_a_vr().
+ */
+/datum/component/virtual_reality/proc/quit(deathcheck = FALSE, cleanup = FALSE, mob/override)
var/mob/M = parent
- quit_it(FALSE, killme, player, yo_dawg)
- yo_dawg.inception = null
- if(killme)
- M.death(FALSE)
-
-/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override)
- var/mob/M = parent
- var/mob/dreamer = override ? override : mastermind.current
- if(!mastermind)
- to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...")
- else
- var/key_transfer = FALSE
- if(inception?.parent)
- inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src)
+ if(!session_paused)
+ session_paused = TRUE
+ var/mob/dreamer = override || mastermind.current
+ if(!dreamer) //This shouldn't happen.
+ stack_trace("virtual reality component quit() called without a mob to transfer the parent ckey to.")
+ to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...")
+ qdel(src)
+ return
+ if(level_below?.parent)
+ level_below.vr_in_a_vr(dreamer, deathcheck, (deathcheck && cleanup))
else
- key_transfer = TRUE
- if(key_transfer)
M.transfer_ckey(dreamer, FALSE)
- dreamer.stop_sound_channel(CHANNEL_HEARTBEAT)
- dreamer.audiovisual_redirect = null
- if(deathcheck && you_die_in_the_game_you_die_for_real)
- to_chat(mastermind, "You feel everything fading away...")
- dreamer.death(FALSE)
- if(cleanup)
- var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
- if(cleanbot)
- LAZYADD(cleanbot.corpse_party, M)
- if(vr_sleeper)
- vr_sleeper.vr_mob = null
- vr_sleeper = null
- qdel(src)
+ if(deathcheck)
+ to_chat(dreamer, "You feel everything fading away...")
+ dreamer.death(FALSE)
+ mastermind.current.audiovisual_redirect = null
+ if(!cleanup)
+ if(level_above)
+ level_above.level_below = null
+ level_above = null
+ UnregisterSignal(mastermind.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE))
+ UnregisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER)
+ mastermind = null
+ if(cleanup)
+ var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
+ if(cleanbot)
+ LAZYOR(cleanbot.corpse_party, M)
+ qdel(src)
-/datum/component/virtual_reality/Destroy()
- var/datum/action/quit_vr/delet_me = quit_action
- . = ..()
- qdel(delet_me)
\ No newline at end of file
+/**
+ * Used for recursive virtual realities shenanigeans and should be called only through the above proc.
+ */
+/datum/component/virtual_reality/proc/vr_in_a_vr(mob/player, deathcheck = FALSE, lethal_cleanup = FALSE)
+ var/mob/M = parent
+ quit(deathcheck, lethal_cleanup, player)
+ M.audiovisual_redirect = null
+ if(lethal_cleanup)
+ M.death(FALSE)
diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm
index 38b17993d8..d6c5c0bf83 100644
--- a/code/datums/components/wet_floor.dm
+++ b/code/datums/components/wet_floor.dm
@@ -34,10 +34,12 @@
last_process = world.time
/datum/component/wet_floor/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet)
RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry)
/datum/component/wet_floor/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY))
/datum/component/wet_floor/Destroy()
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 60bb24c8c2..cf33fa3633 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -255,7 +255,7 @@
M.fields["alg_d"] = "No allergies have been detected in this patient."
M.fields["cdi"] = "None"
M.fields["cdi_d"] = "No diseases have been diagnosed at the moment."
- M.fields["notes"] = "No notes."
+ M.fields["notes"] = H.get_trait_string(medical)
medical += M
//Security Record
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index e44134f01f..feac1de972 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -1398,3 +1398,29 @@
var/mob/living/carbon/human/H = locate(href_list["copyoutfit"]) in GLOB.carbon_list
if(istype(H))
H.copy_outfit()
+ else if(href_list["modquirks"])
+ if(!check_rights(R_SPAWN))
+ return
+
+ var/mob/living/carbon/human/H = locate(href_list["modquirks"]) in GLOB.mob_list
+ if(!istype(H))
+ to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
+ return
+
+ var/list/options = list("Clear"="Clear")
+ for(var/x in subtypesof(/datum/quirk))
+ var/datum/quirk/T = x
+ var/qname = initial(T.name)
+ options[H.has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T
+
+ var/result = input(usr, "Choose quirk to add/remove","Quirk Mod") as null|anything in options
+ if(result)
+ if(result == "Clear")
+ for(var/datum/quirk/q in H.roundstart_quirks)
+ H.remove_quirk(q.type)
+ else
+ var/T = options[result]
+ if(H.has_quirk(T))
+ H.remove_quirk(T)
+ else
+ H.add_quirk(T,TRUE)
diff --git a/code/datums/martial.dm b/code/datums/martial.dm
index 26a709590c..32850c69df 100644
--- a/code/datums/martial.dm
+++ b/code/datums/martial.dm
@@ -8,9 +8,7 @@
var/deflection_chance = 0 //Chance to deflect projectiles
var/reroute_deflection = FALSE //Delete the bullet, or actually deflect it in some direction?
var/block_chance = 0 //Chance to block melee attacks using items while on throw mode.
- var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not
var/help_verb
- var/no_guns = FALSE
var/pacifism_check = TRUE //are the martial arts combos/attacks unable to be used by pacifist.
var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
@@ -28,14 +26,16 @@
/datum/martial_art/proc/add_to_streak(element,mob/living/carbon/human/D)
if(D != current_target)
- current_target = D
- streak = ""
- restraining = 0
+ reset_streak(D)
streak = streak+element
if(length(streak) > max_streak_length)
streak = copytext(streak,2)
return
+/datum/martial_art/proc/reset_streak(mob/living/carbon/human/new_target)
+ current_target = new_target
+ streak = ""
+
/datum/martial_art/proc/basic_hit(mob/living/carbon/human/A,mob/living/carbon/human/D)
var/damage = rand(A.dna.species.punchdamagelow, A.dna.species.punchdamagehigh)
@@ -81,7 +81,7 @@
D.forcesay(GLOB.hit_appends)
return 1
-/datum/martial_art/proc/teach(mob/living/carbon/human/H,make_temporary=0)
+/datum/martial_art/proc/teach(mob/living/carbon/human/H, make_temporary = FALSE)
if(!istype(H) || !H.mind)
return FALSE
if(H.mind.martial_art)
diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm
index 73173a4a9a..09a493a670 100644
--- a/code/datums/martial/cqc.dm
+++ b/code/datums/martial/cqc.dm
@@ -9,24 +9,13 @@
id = MARTIALART_CQC
help_verb = /mob/living/carbon/human/proc/CQC_help
block_chance = 75
- var/just_a_cook = FALSE
- var/static/list/areas_under_siege = typecacheof(list(/area/crew_quarters/kitchen,
- /area/crew_quarters/cafeteria,
- /area/crew_quarters/bar))
+ var/old_grab_state = null
+ var/restraining = FALSE
-/datum/martial_art/cqc/under_siege
- name = "Close Quarters Cooking"
- just_a_cook = TRUE
-
-/datum/martial_art/cqc/proc/drop_restraining()
+/datum/martial_art/cqc/reset_streak(mob/living/carbon/human/new_target)
+ . = ..()
restraining = FALSE
-/datum/martial_art/cqc/can_use(mob/living/carbon/human/H)
- var/area/A = get_area(H)
- if(just_a_cook && !(is_type_in_typecache(A, areas_under_siege)))
- return FALSE
- return ..()
-
/datum/martial_art/cqc/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
@@ -75,6 +64,7 @@
D.apply_damage(10, BRUTE)
log_combat(A, D, "kicked (CQC)")
if(D.IsKnockdown() && !D.stat)
+ log_combat(A, D, "knocked out (Head kick)(CQC)")
D.visible_message("[A] kicks [D]'s head, knocking [D.p_them()] out!", \
"[A] kicks your head, knocking you out!")
playsound(get_turf(A), 'sound/weapons/genhit1.ogg', 50, 1, -1)
@@ -85,7 +75,8 @@
/datum/martial_art/cqc/proc/Pressure(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
- D.visible_message("[A] forces their arm on [D]'s neck!")
+ log_combat(A, D, "pressured (CQC)")
+ D.visible_message("[A] punches [D]'s neck!")
D.adjustStaminaLoss(60)
playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1)
return TRUE
@@ -96,18 +87,20 @@
if(!can_use(A))
return FALSE
if(!D.stat)
+ log_combat(A, D, "restrained (CQC)")
D.visible_message("[A] locks [D] into a restraining position!", \
"[A] locks you into a restraining position!")
D.adjustStaminaLoss(20)
D.Stun(100)
restraining = TRUE
- addtimer(CALLBACK(src, .proc/drop_restraining), 50, TIMER_UNIQUE)
+ addtimer(VARSET_CALLBACK(src, restraining, FALSE), 50, TIMER_UNIQUE)
return TRUE
/datum/martial_art/cqc/proc/Consecutive(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
if(!D.stat)
+ log_combat(A, D, "consecutive CQC'd (CQC)")
D.visible_message("[A] strikes [D]'s abdomen, neck and back consecutively", \
"[A] strikes your abdomen, neck and back consecutively!")
playsound(get_turf(D), 'sound/weapons/cqchit2.ogg', 50, 1, -1)
@@ -119,23 +112,20 @@
return TRUE
/datum/martial_art/cqc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
- if(!can_use(A))
- return FALSE
- add_to_streak("G",D)
- if(check_streak(A,D))
- return TRUE
- if(A == D) // no self grab.
- return FALSE
- if(A.grab_state >= GRAB_AGGRESSIVE)
+ if(A.a_intent == INTENT_GRAB && A!=D && can_use(A)) // A!=D prevents grabbing yourself
+ add_to_streak("G",D)
+ if(check_streak(A,D)) //if a combo is made no grab upgrade is done
+ return TRUE
+ old_grab_state = A.grab_state
D.grabbedby(A, 1)
- else
- A.start_pulling(D, 1)
- if(A.pulling)
- D.stop_pulling()
+ if(old_grab_state == GRAB_PASSIVE)
+ D.drop_all_held_items()
+ A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent
log_combat(A, D, "grabbed", addition="aggressively")
- A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab
-
- return TRUE
+ D.visible_message("[A] violently grabs [D]!", \
+ "[A] violently grabs you!")
+ return TRUE
+ return FALSE
/datum/martial_art/cqc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
@@ -190,6 +180,7 @@
playsound(D, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
log_combat(A, D, "disarmed (CQC)", "[I ? " grabbing \the [I]" : ""]")
if(restraining && A.pulling == D)
+ log_combat(A, D, "knocked out (Chokehold)(CQC)")
D.visible_message("[A] puts [D] into a chokehold!", \
"[A] puts you into a chokehold!")
D.SetSleeping(400)
@@ -208,9 +199,19 @@
to_chat(usr, "You try to remember some of the basics of CQC.")
to_chat(usr, "Slam: Grab Harm. Slam opponent into the ground, knocking them down.")
- to_chat(usr, "CQC Kick: Disarm Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
- to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a choke hold.")
+ to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
+ to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a chokehold.")
to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.")
to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.")
to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.")
+
+///Subtype of CQC. Only used for the chef.
+/datum/martial_art/cqc/under_siege
+ name = "Close Quarters Cooking"
+
+///Prevents use if the cook is not in the kitchen.
+/datum/martial_art/cqc/under_siege/can_use(mob/living/carbon/human/H) //this is used to make chef CQC only work in kitchen
+ if(!istype(get_area(H), /area/crew_quarters/kitchen))
+ return FALSE
+ return ..()
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index f89374dc2a..bb652208ee 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -9,35 +9,36 @@
id = MARTIALART_SLEEPINGCARP
deflection_chance = 100
reroute_deflection = TRUE
- no_guns = TRUE
allow_temp_override = FALSE
help_verb = /mob/living/carbon/human/proc/sleeping_carp_help
+ var/old_grab_state = null
/datum/martial_art/the_sleeping_carp/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(findtext(streak,WRIST_WRENCH_COMBO))
streak = ""
wristWrench(A,D)
- return 1
+ return TRUE
if(findtext(streak,BACK_KICK_COMBO))
streak = ""
backKick(A,D)
- return 1
+ return TRUE
if(findtext(streak,STOMACH_KNEE_COMBO))
streak = ""
kneeStomach(A,D)
- return 1
+ return TRUE
if(findtext(streak,HEAD_KICK_COMBO))
streak = ""
headKick(A,D)
- return 1
+ return TRUE
if(findtext(streak,ELBOW_DROP_COMBO))
streak = ""
elbowDrop(A,D)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/martial_art/the_sleeping_carp/proc/wristWrench(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsStun() && !D.IsKnockdown())
+ log_combat(A, D, "wrist wrenched (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
D.visible_message("[A] grabs [D]'s wrist and wrenches it sideways!", \
"[A] grabs your wrist and violently wrenches it to the side!")
@@ -46,24 +47,29 @@
D.dropItemToGround(D.get_active_held_item())
D.apply_damage(5, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
D.Knockdown(60)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
- return 1
- log_combat(A, D, "wrist wrenched (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/backKick(mob/living/carbon/human/A, mob/living/carbon/human/D)
- if(A.dir == D.dir && !D.stat && !D.IsKnockdown())
- A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
- D.visible_message("[A] kicks [D] in the back!", \
- "[A] kicks you in the back, making you stumble and fall!")
- step_to(D,get_step(D,D.dir),1)
- D.Knockdown(80)
- playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
- return 1
- log_combat(A, D, "back-kicked (Sleeping Carp)")
+ if(!D.stat && !D.IsKnockdown())
+ if(A.dir == D.dir)
+ log_combat(A, D, "back-kicked (Sleeping Carp)")
+ A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
+ D.visible_message("[A] kicks [D] in the back!", \
+ "[A] kicks you in the back, making you stumble and fall!")
+ step_to(D,get_step(D,D.dir),1)
+ D.Knockdown(80)
+ playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
+ return TRUE
+ else
+ log_combat(A, D, "missed a back-kick (Sleeping Carp) on")
+ D.visible_message("[A] tries to kick [D] in the back, but misses!", \
+ "[A] tries to kick you in the back, but misses!")
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/kneeStomach(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsKnockdown())
+ log_combat(A, D, "stomach kneed (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_KICK)
D.visible_message("[A] knees [D] in the stomach!", \
"[A] winds you with a knee in the stomach!")
@@ -71,12 +77,12 @@
D.losebreath += 3
D.Knockdown(40)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
- return 1
- log_combat(A, D, "stomach kneed (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/headKick(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsKnockdown())
+ log_combat(A, D, "head kicked (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_KICK)
D.visible_message("[A] kicks [D] in the head!", \
"[A] kicks you in the jaw!")
@@ -84,12 +90,12 @@
D.drop_all_held_items()
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
D.Knockdown(80)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
- return 1
- log_combat(A, D, "head kicked (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/elbowDrop(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(D.IsKnockdown() || D.resting || D.stat)
+ log_combat(A, D, "elbow dropped (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
D.visible_message("[A] elbow drops [D]!", \
"[A] piledrives you with their elbow!")
@@ -97,37 +103,29 @@
D.death() //FINISH HIM!
D.apply_damage(50, BRUTE, BODY_ZONE_CHEST)
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 75, 1, -1)
- return 1
- log_combat(A, D, "elbow dropped (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
- add_to_streak("G",D)
- if(check_streak(A,D))
- return 1
- if(A == D) //no self grab stun
- return FALSE
- if(A.grab_state >= GRAB_AGGRESSIVE)
+ if(A.a_intent == INTENT_GRAB && A!=D) // A!=D prevents grabbing yourself
+ add_to_streak("G",D)
+ if(check_streak(A,D)) //if a combo is made no grab upgrade is done
+ return TRUE
+ old_grab_state = A.grab_state
D.grabbedby(A, 1)
- else
- A.start_pulling(D, 1)
- if(A.pulling)
+ if(old_grab_state == GRAB_PASSIVE)
D.drop_all_held_items()
- D.stop_pulling()
- if(A.a_intent == INTENT_GRAB)
- log_combat(A, D, "grabbed", addition="aggressively")
- D.visible_message("[A] violently grabs [D]!", \
- "[A] violently grabs you!")
- A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab
- else
- log_combat(A, D, "grabbed", addition="passively")
- A.grab_state = GRAB_PASSIVE
- return 1
+ A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent
+ log_combat(A, D, "grabbed", addition="aggressively")
+ D.visible_message("[A] violently grabs [D]!", \
+ "[A] violently grabs you!")
+ return TRUE
+ return FALSE
/datum/martial_art/the_sleeping_carp/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
add_to_streak("H",D)
if(check_streak(A,D))
- return 1
+ return TRUE
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
var/atk_verb = pick("punches", "kicks", "chops", "hits", "slams")
D.visible_message("[A] [atk_verb] [D]!", \
@@ -138,15 +136,25 @@
D.visible_message("[D] stumbles and falls!", "The blow sends you to the ground!")
D.Knockdown(80)
log_combat(A, D, "[atk_verb] (Sleeping Carp)")
- return 1
+ return TRUE
/datum/martial_art/the_sleeping_carp/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
add_to_streak("D",D)
if(check_streak(A,D))
- return 1
+ return TRUE
return ..()
+/datum/martial_art/the_sleeping_carp/teach(mob/living/carbon/human/H, make_temporary = FALSE)
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(H, TRAIT_NOGUNS, SLEEPING_CARP_TRAIT)
+
+/datum/martial_art/the_sleeping_carp/on_remove(mob/living/carbon/human/H)
+ . = ..()
+ REMOVE_TRAIT(H, TRAIT_NOGUNS, SLEEPING_CARP_TRAIT)
+
/mob/living/carbon/human/proc/sleeping_carp_help()
set name = "Recall Teachings"
set desc = "Remember the martial techniques of the Sleeping Carp clan."
@@ -233,4 +241,4 @@
/obj/item/twohanded/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(wielded)
return ..()
- return 0
+ return FALSE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 0ded5f9912..d1b4e51a7d 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -88,6 +88,9 @@
/datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0)
var/old_character = current
+ var/signals = SEND_SIGNAL(new_character, COMSIG_MOB_PRE_PLAYER_CHANGE, new_character, old_character) | SEND_SIGNAL(src, COMSIG_PRE_MIND_TRANSFER, new_character, old_character)
+ if(signals & COMPONENT_STOP_MIND_TRANSFER)
+ return
if(current) // remove ourself from our old body's mind variable
current.mind = null
SStgui.on_transfer(current, new_character)
@@ -125,7 +128,6 @@
transfer_martial_arts(new_character)
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
- SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
//CIT CHANGE - makes arousal update when transfering bodies
if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs.
@@ -134,6 +136,8 @@
L.canbearoused = L.client.prefs.arousable //Technically this should make taking over a character mean the body gain the new minds setting...
L.update_arousal_hud() //Removes the old icon
+ SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
+
/datum/mind/proc/store_memory(new_text)
if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN)
memory += "[new_text]
"
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 6b4fa5e636..19be10e668 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -157,3 +157,8 @@
/datum/mood_event/fedprey
description = "It feels quite cozy in here.\n"
mood_change = 3
+
+/datum/mood_event/hope_lavaland
+ description = "What a peculiar emblem. It makes me feel hopeful for my future.\n"
+ mood_change = 5
+
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index 3d07e6066f..c536196e95 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -13,6 +13,7 @@
return
ADD_TRAIT(owner, TRAIT_STUNIMMUNE, TRAIT_HULK)
ADD_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
+ ADD_TRAIT(owner, TRAIT_CHUNKYFINGERS, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "hulk", /datum/mood_event/hulk)
RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
@@ -31,6 +32,7 @@
return
REMOVE_TRAIT(owner, TRAIT_STUNIMMUNE, TRAIT_HULK)
REMOVE_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
+ ADD_TRAIT(owner, TRAIT_CHUNKYFINGERS, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "hulk")
UnregisterSignal(owner, COMSIG_MOB_SAY)
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index 4f18e6fd62..7ea2d2615d 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -229,4 +229,13 @@
id = "puzzle"
description = "Mystery to be solved."
suffix = "lavaland_surface_puzzle.dmm"
- cost = 5
\ No newline at end of file
+ cost = 5
+
+/datum/map_template/ruin/lavaland/elite_tumor
+ name = "Pulsating Tumor"
+ id = "tumor"
+ description = "A strange tumor which houses a powerful beast..."
+ suffix = "lavaland_surface_elite_tumor.dmm"
+ cost = 5
+ always_place = TRUE
+ allow_duplicates = TRUE
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 96d293543d..f5f012e7f9 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -409,6 +409,19 @@
else
new /obj/effect/temp_visual/bleed(get_turf(owner))
+/datum/status_effect/neck_slice
+ id = "neck_slice"
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = null
+ duration = -1
+
+/datum/status_effect/neck_slice/tick()
+ var/mob/living/carbon/human/H = owner
+ if(H.stat == DEAD || H.bleed_rate <= 8)
+ H.remove_status_effect(/datum/status_effect/neck_slice)
+ if(prob(10))
+ H.emote(pick("gasp", "gag", "choke"))
+
/mob/living/proc/apply_necropolis_curse(set_curse, duration = 10 MINUTES)
var/datum/status_effect/necropolis_curse/C = has_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE)
if(!set_curse)
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 09a4f0b060..15ac70a2c7 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -8,12 +8,14 @@
mob_trait = TRAIT_ALCOHOL_TOLERANCE
gain_text = "You feel like you could drink a whole keg!"
lose_text = "You don't feel as resistant to alcohol anymore. Somehow."
+ medical_record_text = "Patient demonstrates a high tolerance for alcohol."
/datum/quirk/apathetic
name = "Apathetic"
desc = "You just don't care as much as other people. That's nice to have in a place like this, I guess."
value = 1
mood_quirk = TRUE
+ medical_record_text = "Patient was administered the Apathy Evaluation Scale but did not bother to complete it."
/datum/quirk/apathetic/add()
var/datum/component/mood/mood = quirk_holder.GetComponent(/datum/component/mood)
@@ -42,6 +44,7 @@
mob_trait = TRAIT_EMPATH
gain_text = "You feel in tune with those around you."
lose_text = "You feel isolated from others."
+ medical_record_text = "Patient is highly perceptive of and sensitive to social cues, or may possibly have ESP. Further testing needed."
/datum/quirk/freerunning
name = "Freerunning"
@@ -50,6 +53,7 @@
mob_trait = TRAIT_FREERUNNING
gain_text = "You feel lithe on your feet!"
lose_text = "You feel clumsy again."
+ medical_record_text = "Patient scored highly on cardio tests."
/datum/quirk/friendly
name = "Friendly"
@@ -59,6 +63,7 @@
gain_text = "You want to hug someone."
lose_text = "You no longer feel compelled to hug others."
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates low-inhibitions for physical contact and well-developed arms. Requesting another doctor take over this case."
/datum/quirk/jolly
name = "Jolly"
@@ -66,6 +71,11 @@
value = 1
mob_trait = TRAIT_JOLLY
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates constant euthymia irregular for environment. It's a bit much, to be honest."
+
+/datum/quirk/jolly/on_process()
+ if(prob(0.05))
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "jolly", /datum/mood_event/jolly)
/datum/quirk/light_step
name = "Light Step"
@@ -74,6 +84,7 @@
mob_trait = TRAIT_LIGHT_STEP
gain_text = "You walk with a little more litheness."
lose_text = "You start tromping around like a barbarian."
+ medical_record_text = "Patient's dexterity belies a strong capacity for stealth."
/datum/quirk/quick_step
name = "Quick Step"
@@ -82,6 +93,7 @@
mob_trait = TRAIT_SPEEDY_STEP
gain_text = "You feel determined. No time to lose."
lose_text = "You feel less determined. What's the rush, man?"
+ medical_record_text = "Patient scored highly on racewalking tests."
/datum/quirk/musician
name = "Musician"
@@ -90,6 +102,7 @@
mob_trait = TRAIT_MUSICIAN
gain_text = "You know everything about musical instruments."
lose_text = "You forget how musical instruments work."
+ medical_record_text = "Patient brain scans show a highly-developed auditory pathway."
/datum/quirk/musician/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -108,6 +121,7 @@
mob_trait = TRAIT_PHOTOGRAPHER
gain_text = "You know everything about photography."
lose_text = "You forget how photo cameras work."
+ medical_record_text = "Patient mentions photography as a stress-relieving hobby."
/datum/quirk/photographer/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -121,12 +135,14 @@
desc = "You know your body well, and can accurately assess the extent of your wounds."
value = 2
mob_trait = TRAIT_SELF_AWARE
+ medical_record_text = "Patient demonstrates an uncanny knack for self-diagnosis."
/datum/quirk/skittish
name = "Skittish"
desc = "You can conceal yourself in danger. Ctrl-shift-click a closed locker to jump into it, as long as you have access."
value = 2
mob_trait = TRAIT_SKITTISH
+ medical_record_text = "Patient demonstrates a high aversion to danger and has described hiding in containers out of fear."
/datum/quirk/spiritual
name = "Spiritual"
@@ -135,6 +151,7 @@
mob_trait = TRAIT_SPIRITUAL
gain_text = "You feel a little more faithful to the gods today."
lose_text = "You feel less faithful in the gods."
+ medical_record_text = "Patient reports a belief in a higher power."
/datum/quirk/tagger
name = "Tagger"
@@ -143,6 +160,7 @@
mob_trait = TRAIT_TAGGER
gain_text = "You know how to tag walls efficiently."
lose_text = "You forget how to tag walls properly."
+ medical_record_text = "Patient was recently seen for possible paint huffing incident."
/datum/quirk/tagger/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -158,6 +176,7 @@
mob_trait = TRAIT_VORACIOUS
gain_text = "You feel HONGRY."
lose_text = "You no longer feel HONGRY."
+ medical_record_text = "Patient demonstrates a disturbing capacity for eating."
/datum/quirk/trandening
name = "High Luminosity Eyes"
@@ -179,6 +198,7 @@
mob_trait = TRAIT_HIGH_BLOOD
gain_text = "You feel full of blood!"
lose_text = "You feel like your blood pressure went down."
+ medical_record_text = "Patient's blood tests report an abnormal concentration of red blood cells in their bloodstream."
/datum/quirk/bloodpressure/add()
var/mob/living/M = quirk_holder
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 2dd6ba2eba..3319259381 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -22,14 +22,19 @@
value = -1
gain_text = "You start feeling depressed."
lose_text = "You no longer feel depressed." //if only it were that easy!
- medical_record_text = "Patient has a severe mood disorder causing them to experience sudden moments of sadness."
+ medical_record_text = "Patient has a severe mood disorder, causing them to experience acute episodes of depression."
mood_quirk = TRUE
+/datum/quirk/depression/on_process()
+ if(prob(0.05))
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "depression", /datum/mood_event/depression)
+
/datum/quirk/family_heirloom
name = "Family Heirloom"
desc = "You are the current owner of an heirloom, passed down for generations. You have to keep it safe!"
value = -1
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates an unnatural attachment to a family heirloom."
var/obj/item/heirloom
var/where
@@ -143,6 +148,7 @@
name = "Nyctophobia"
desc = "As far as you can remember, you've always been afraid of the dark. While in the dark without a light source, you instinctually act careful, and constantly feel a sense of dread."
value = -1
+ medical_record_text = "Patient demonstrates a fear of the dark. (Seriously?)"
/datum/quirk/nyctophobia/on_process()
var/mob/living/carbon/human/H = quirk_holder
@@ -163,7 +169,8 @@
desc = "Bright lights irritate you. Your eyes start to water, your skin feels itchy against the photon radiation, and your hair gets dry and frizzy. Maybe it's a medical condition. If only Nanotrasen was more considerate of your needs..."
value = -1
gain_text = "The safty of light feels off..."
- lose_text = "Enlighing."
+ lose_text = "Enlightening."
+ medical_record_text = "Despite my warnings, the patient refuses turn on the lights, only to end up rolling down a full flight of stairs and into the cellar."
/datum/quirk/lightless/on_process()
var/turf/T = get_turf(quirk_holder)
@@ -379,7 +386,7 @@
value = -4
gain_text = "You can't see anything."
lose_text = "You miraculously gain back your vision."
- medical_record_text = "Subject has permanent blindness."
+ medical_record_text = "Patient has permanent blindness."
/datum/quirk/blindness/add()
quirk_holder.become_blind(ROUNDSTART_TRAIT)
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 9e05af03b6..eae2db6a5f 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -16,6 +16,7 @@
value = 0
gain_text = "You feel an intense craving for pineapple."
lose_text = "Your feelings towards pineapples seem to return to a lukewarm state."
+ medical_record_text = "Patient demonstrates a pathological love of pineapple."
/datum/quirk/pineapple_liker/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -34,6 +35,7 @@
value = 0
gain_text = "You find yourself pondering what kind of idiot actually enjoys pineapples..."
lose_text = "Your feelings towards pineapples seem to return to a lukewarm state."
+ medical_record_text = "Patient is correct to think that pineapple is disgusting."
/datum/quirk/pineapple_hater/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -52,6 +54,7 @@
value = 0
gain_text = "You start craving something that tastes strange."
lose_text = "You feel like eating normal food again."
+ medical_record_text = "Patient demonstrates irregular nutrition preferences."
/datum/quirk/deviant_tastes/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -92,7 +95,7 @@
value = 0
gain_text = "You feel more prudish."
lose_text = "You don't feel as prudish as before."
- medical_record_text = "Patient exhibits a special gene that makes them immune to Crocin and Hexacrocin."
+ medical_record_text = "Patient exhibits a special gene that makes them immune to aphrodisiacs."
/datum/quirk/libido
name = "Nymphomania"
@@ -134,6 +137,7 @@
value = 0
mob_trait = TRAIT_PHARMA
lose_text = "Your liver feels different."
+ medical_record_text = "Non-invasive tests report that the patient's metabolism is indeed incompatible with a certain \"stimulants\"."
var/active = FALSE
var/power = 0
var/cachedmoveCalc = 1
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 612c3cba42..f5e285a5c2 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -327,7 +327,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
for(var/obj/machinery/light/L in src)
L.update()
-/area/proc/updateicon()
+/area/proc/update_icon()
var/weather_icon
for(var/V in SSweather.processing)
var/datum/weather/W = V
@@ -337,7 +337,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(!weather_icon)
icon_state = null
-/area/space/updateicon()
+/area/space/update_icon()
icon_state = null
/*
@@ -370,7 +370,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/proc/power_change()
for(var/obj/machinery/M in src) // for each machine in the area
M.power_change() // reverify power status (to update icons etc.)
- updateicon()
+ update_icon()
/area/proc/usage(chan)
var/used = 0
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index ee96d1fa10..9b52ddda1e 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -265,6 +265,7 @@
armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
max_temperature = 35000
operation_req_access = list(ACCESS_SYNDICATE)
+ internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/honker/dark
max_equip = 3
spawn_tracked = FALSE
diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/Beacon.dm
index 3d0931d534..c98795c90b 100644
--- a/code/game/machinery/Beacon.dm
+++ b/code/game/machinery/Beacon.dm
@@ -25,10 +25,10 @@
// update the invisibility and icon
/obj/machinery/bluespace_beacon/hide(intact)
invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- updateicon()
+ update_icon()
// update the icon_state
-/obj/machinery/bluespace_beacon/proc/updateicon()
+/obj/machinery/bluespace_beacon/update_icon()
var/state="floor_beacon"
if(invisibility)
@@ -45,4 +45,4 @@
else if (Beacon.loc != loc)
Beacon.forceMove(loc)
- updateicon()
+ update_icon()
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index ff7383cd9e..5f44fccdac 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -181,8 +181,9 @@ Class Procs:
if(isliving(A))
var/mob/living/L = A
L.update_canmove()
- SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
- occupant = null
+ if(occupant)
+ SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
+ occupant = null
/obj/machinery/proc/can_be_occupant(atom/movable/am)
return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index 1839e44e3b..1c0635fd3d 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -13,7 +13,7 @@
var/chargelevel = -1
var/charge_rate = 500
-/obj/machinery/cell_charger/proc/updateicon()
+/obj/machinery/cell_charger/update_icon()
cut_overlays()
if(charging)
add_overlay(image(charging.icon, charging.icon_state))
@@ -53,7 +53,7 @@
charging = W
user.visible_message("[user] inserts a cell into [src].", "You insert a cell into [src].")
chargelevel = -1
- updateicon()
+ update_icon()
else
if(!charging && default_deconstruction_screwdriver(user, icon_state, icon_state, W))
return
@@ -76,7 +76,7 @@
charging.update_icon()
charging = null
chargelevel = -1
- updateicon()
+ update_icon()
/obj/machinery/cell_charger/attack_hand(mob/user)
. = ..()
@@ -127,4 +127,4 @@
use_power(charge_rate)
charging.give(charge_rate) //this is 2558, efficient batteries exist
- updateicon()
+ update_icon()
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index 694df02eb5..2a4b903906 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -21,9 +21,9 @@
name = "light switch ([area.name])"
on = area.lightswitch
- updateicon()
+ update_icon()
-/obj/machinery/light_switch/proc/updateicon()
+/obj/machinery/light_switch/update_icon()
if(stat & NOPOWER)
icon_state = "light-p"
else
@@ -41,11 +41,11 @@
on = !on
area.lightswitch = on
- area.updateicon()
+ area.update_icon()
for(var/obj/machinery/light_switch/L in area)
L.on = on
- L.updateicon()
+ L.update_icon()
area.power_change()
@@ -57,7 +57,7 @@
else
stat |= NOPOWER
- updateicon()
+ update_icon()
/obj/machinery/light_switch/emp_act(severity)
. = ..()
diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm
index 094db5a676..370621e0c8 100644
--- a/code/game/machinery/magnet.dm
+++ b/code/game/machinery/magnet.dm
@@ -46,10 +46,10 @@
// update the invisibility and icon
/obj/machinery/magnetic_module/hide(intact)
invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- updateicon()
+ update_icon()
// update the icon_state
-/obj/machinery/magnetic_module/proc/updateicon()
+/obj/machinery/magnetic_module/update_icon()
var/state="floor_magnet"
var/onstate=""
if(!on)
@@ -161,7 +161,7 @@
else
use_power = NO_POWER_USE
- updateicon()
+ update_icon()
/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the magneting
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index d64ae75e2c..0f57bea656 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -72,10 +72,10 @@
// hide the object if turf is intact
/obj/machinery/navbeacon/hide(intact)
invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- updateicon()
+ update_icon()
// update the icon_state
-/obj/machinery/navbeacon/proc/updateicon()
+/obj/machinery/navbeacon/update_icon()
var/state="navbeacon[open]"
if(invisibility)
@@ -94,7 +94,7 @@
user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.")
- updateicon()
+ update_icon()
else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda))
if(open)
diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm
index c7c180af41..0e6980d6b7 100644
--- a/code/game/mecha/combat/gygax.dm
+++ b/code/game/mecha/combat/gygax.dm
@@ -27,6 +27,7 @@
max_temperature = 35000
leg_overload_coeff = 100
operation_req_access = list(ACCESS_SYNDICATE)
+ internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/gygax/dark
max_equip = 4
spawn_tracked = FALSE
diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm
index 3a3d98ad1e..ffe318def5 100644
--- a/code/game/mecha/combat/honker.dm
+++ b/code/game/mecha/combat/honker.dm
@@ -10,6 +10,7 @@
max_temperature = 25000
infra_luminosity = 5
operation_req_access = list(ACCESS_THEATRE)
+ internals_req_access = list(ACCESS_THEATRE, ACCESS_ROBOTICS)
wreckage = /obj/structure/mecha_wreckage/honker
add_req_access = 0
max_equip = 3
diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm
index 42817b586c..fa9449937b 100644
--- a/code/game/mecha/combat/marauder.dm
+++ b/code/game/mecha/combat/marauder.dm
@@ -10,6 +10,7 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
infra_luminosity = 3
operation_req_access = list(ACCESS_CENT_SPECOPS)
+ internals_req_access = list(ACCESS_CENT_SPECOPS, ACCESS_ROBOTICS)
wreckage = /obj/structure/mecha_wreckage/marauder
add_req_access = 0
internal_damage_threshold = 25
@@ -46,6 +47,7 @@
name = "\improper Seraph"
icon_state = "seraph"
operation_req_access = list(ACCESS_CENT_SPECOPS)
+ internals_req_access = list(ACCESS_CENT_SPECOPS, ACCESS_ROBOTICS)
step_in = 3
max_integrity = 550
wreckage = /obj/structure/mecha_wreckage/seraph
@@ -72,6 +74,7 @@
name = "\improper Mauler"
icon_state = "mauler"
operation_req_access = list(ACCESS_SYNDICATE)
+ internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/mauler
max_equip = 5
diff --git a/code/game/mecha/combat/neovgre.dm b/code/game/mecha/combat/neovgre.dm
index b1f2cdd02a..8fc1dd97b2 100644
--- a/code/game/mecha/combat/neovgre.dm
+++ b/code/game/mecha/combat/neovgre.dm
@@ -12,6 +12,8 @@
layer = ABOVE_MOB_LAYER
breach_time = 100 //ten seconds till all goes to shit
recharge_rate = 100
+ internals_req_access = list()
+ add_req_access = 0
wreckage = /obj/structure/mecha_wreckage/durand/neovgre
spawn_tracked = FALSE
@@ -47,7 +49,7 @@
for(var/mob/M in src)
to_chat(M, "You are consumed by the fires raging within Neovgre...")
M.dust()
- playsound(src, 'sound/magic/lightning_chargeup.ogg', 100, 0)
+ playsound(src, 'sound/effects/neovgre_exploding.ogg', 100, 0)
src.visible_message("The reactor has gone critical, its going to blow!")
addtimer(CALLBACK(src,.proc/go_critical),breach_time)
diff --git a/code/game/mecha/combat/reticence.dm b/code/game/mecha/combat/reticence.dm
index 7e8c865517..446e2e853c 100644
--- a/code/game/mecha/combat/reticence.dm
+++ b/code/game/mecha/combat/reticence.dm
@@ -10,6 +10,7 @@
max_temperature = 15000
wreckage = /obj/structure/mecha_wreckage/reticence
operation_req_access = list(ACCESS_THEATRE)
+ internals_req_access = list(ACCESS_THEATRE, ACCESS_ROBOTICS)
add_req_access = 0
internal_damage_threshold = 25
max_equip = 2
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 35500bed07..7e860d9315 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -68,7 +68,7 @@
var/internal_damage = 0 //contains bitflags
var/list/operation_req_access = list()//required access level for mecha operation
- var/list/internals_req_access = list(ACCESS_ENGINE,ACCESS_ROBOTICS)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT
+ var/list/internals_req_access = list(ACCESS_ROBOTICS)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT
var/wreckage
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 7baea7f488..1654ecde41 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -69,14 +69,9 @@
if (!user.IsAdvancedToolUser())
to_chat(user, "You don't have the dexterity to do this!")
return
- if(HAS_TRAIT(user, TRAIT_NOGUNS))
+ if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS))
to_chat(user, "Your fingers can't press the button!")
return
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.dna.check_mutation(HULK))
- to_chat(user, "Your fingers can't press the button!")
- return
add_fingerprint(user)
diff --git a/code/game/objects/items/pneumaticCannon.dm b/code/game/objects/items/pneumaticCannon.dm
index 72d27ae544..e63b4d7a3a 100644
--- a/code/game/objects/items/pneumaticCannon.dm
+++ b/code/game/objects/items/pneumaticCannon.dm
@@ -213,7 +213,7 @@
loadedWeightClass -= I.w_class
else if (A == tank)
tank = null
- update_icons()
+ update_icon()
/obj/item/pneumatic_cannon/ghetto //Obtainable by improvised methods; more gas per use, less capacity, but smaller
name = "improvised pneumatic cannon"
@@ -239,14 +239,13 @@
return
to_chat(user, "You hook \the [thetank] up to \the [src].")
tank = thetank
- update_icons()
+ update_icon()
-/obj/item/pneumatic_cannon/proc/update_icons()
+/obj/item/pneumatic_cannon/update_icon()
cut_overlays()
if(!tank)
return
add_overlay(tank.icon_state)
- update_icon()
/obj/item/pneumatic_cannon/proc/fill_with_type(type, amount)
if(!ispath(type, /obj) && !ispath(type, /mob))
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 95110692c9..ab8030c9c2 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -24,7 +24,7 @@
/obj/item/robot_suit/New()
..()
- updateicon()
+ update_icon()
/obj/item/robot_suit/prebuilt/New()
l_arm = new(src)
@@ -39,7 +39,7 @@
chest.cell = new /obj/item/stock_parts/cell/high/plus(chest)
..()
-/obj/item/robot_suit/proc/updateicon()
+/obj/item/robot_suit/update_icon()
cut_overlays()
if(l_arm)
add_overlay("[l_arm.icon_state]+o")
@@ -96,7 +96,7 @@
to_chat(user, "You disassemble the cyborg shell.")
else
to_chat(user, "There is nothing to remove from the endoskeleton.")
- updateicon()
+ update_icon()
/obj/item/robot_suit/proc/put_in_hand_or_drop(mob/living/user, obj/item/I) //normal put_in_hands() drops the item ontop of the player, this drops it at the suit's loc
if(!user.put_in_hands(I))
@@ -160,7 +160,7 @@
W.icon_state = initial(W.icon_state)
W.cut_overlays()
src.l_leg = W
- src.updateicon()
+ update_icon()
else if(istype(W, /obj/item/bodypart/r_leg/robot))
if(src.r_leg)
@@ -170,7 +170,7 @@
W.icon_state = initial(W.icon_state)
W.cut_overlays()
src.r_leg = W
- src.updateicon()
+ update_icon()
else if(istype(W, /obj/item/bodypart/l_arm/robot))
if(src.l_arm)
@@ -180,7 +180,7 @@
W.icon_state = initial(W.icon_state)
W.cut_overlays()
src.l_arm = W
- src.updateicon()
+ update_icon()
else if(istype(W, /obj/item/bodypart/r_arm/robot))
if(src.r_arm)
@@ -190,7 +190,7 @@
W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb
W.cut_overlays()
src.r_arm = W
- src.updateicon()
+ update_icon()
else if(istype(W, /obj/item/bodypart/chest/robot))
var/obj/item/bodypart/chest/robot/CH = W
@@ -202,7 +202,7 @@
CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb
CH.cut_overlays()
src.chest = CH
- src.updateicon()
+ update_icon()
else if(!CH.wired)
to_chat(user, "You need to attach wires to it first!")
else
@@ -222,7 +222,7 @@
HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb
HD.cut_overlays()
src.head = HD
- src.updateicon()
+ update_icon()
else
to_chat(user, "You need to attach a flash to it first!")
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 82aea26eda..4fc31ea674 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -276,6 +276,33 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
/obj/item/stack/sheet/mineral/wood/fifty
amount = 50
+/*
+ * Bamboo
+ */
+
+GLOBAL_LIST_INIT(bamboo_recipes, list ( \
+ new/datum/stack_recipe("punji sticks trap", /obj/structure/punji_sticks, 5, time = 30, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("blow gun", /obj/item/gun/syringe/blowgun, 10, time = 70), \
+ ))
+
+/obj/item/stack/sheet/mineral/bamboo
+ name = "bamboo cuttings"
+ desc = "Finely cut bamboo sticks."
+ singular_name = "cut bamboo"
+ icon_state = "sheet-bamboo"
+ item_state = "sheet-bamboo"
+ icon = 'icons/obj/stack_objects.dmi'
+ throwforce = 15
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
+ resistance_flags = FLAMMABLE
+ merge_type = /obj/item/stack/sheet/mineral/bamboo
+ grind_results = list("carbon" = 5)
+
+/obj/item/stack/sheet/mineral/bamboo/Initialize(mapload, new_amount, merge = TRUE)
+ recipes = GLOB.bamboo_recipes
+ return ..()
+
+
/*
* Cloth
*/
diff --git a/code/game/objects/structures/divine.dm b/code/game/objects/structures/divine.dm
index 5fd480f6fd..b8137d831b 100644
--- a/code/game/objects/structures/divine.dm
+++ b/code/game/objects/structures/divine.dm
@@ -40,11 +40,11 @@
last_process = world.time
to_chat(user, "The water feels warm and soothing as you touch it. The fountain immediately dries up shortly afterwards.")
user.reagents.add_reagent("godblood",20)
- update_icons()
- addtimer(CALLBACK(src, .proc/update_icons), time_between_uses)
+ update_icon()
+ addtimer(CALLBACK(src, .proc/update_icon), time_between_uses)
-/obj/structure/healingfountain/proc/update_icons()
+/obj/structure/healingfountain/update_icon()
if(last_process + time_between_uses > world.time)
icon_state = "fountain"
else
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 9dee1e26cc..58588c5693 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -285,6 +285,13 @@
. += new /obj/item/shard(location)
/obj/structure/window/proc/can_be_rotated(mob/user,rotation_type)
+ if (get_dist(src,user) > 1)
+ if (iscarbon(user))
+ var/mob/living/carbon/H = user
+ if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
+ return FALSE
+ else
+ return FALSE
if(anchored)
to_chat(user, "[src] cannot be rotated while it is fastened to the floor!")
return FALSE
diff --git a/code/modules/VR/vr_mob.dm b/code/modules/VR/vr_mob.dm
index 5c0cea9f60..5f2caffc76 100644
--- a/code/modules/VR/vr_mob.dm
+++ b/code/modules/VR/vr_mob.dm
@@ -40,5 +40,5 @@
/datum/action/quit_vr/Trigger() //this merely a trigger for /datum/component/virtual_reality
. = ..()
- if(!.)
+ if(.) //The component was not there to block the trigger.
Remove(owner)
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index 72cbdc1409..7cda24d98b 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -52,18 +52,16 @@
flags_1 = NODECONSTRUCT_1
only_current_user_can_interact = TRUE
-/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
- return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
-
/obj/machinery/vr_sleeper/emag_act(mob/user)
. = ..()
if(!(obj_flags & EMAGGED))
return
- obj_flags |= EMAGGED
- you_die_in_the_game_you_die_for_real = TRUE
- sparks.start()
- addtimer(CALLBACK(src, .proc/emagNotify), 150)
- return TRUE
+ if(!only_current_user_can_interact)
+ obj_flags |= EMAGGED
+ you_die_in_the_game_you_die_for_real = TRUE
+ sparks.start()
+ addtimer(CALLBACK(src, .proc/emagNotify), 150)
+ return TRUE
/obj/machinery/vr_sleeper/update_icon()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
@@ -76,7 +74,7 @@
return ..()
/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user)
- if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
+ if(user.lying || !iscarbon(target) || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE, TRUE, NO_TK))
return
close_machine(target)
@@ -91,26 +89,25 @@
return
switch(action)
if("vr_connect")
- var/mob/living/carbon/human/human_occupant = occupant
- if(human_occupant && human_occupant.mind && usr == occupant)
-
- to_chat(occupant, "Transferring to virtual reality...")
- if(vr_mob && (!istype(vr_mob) || !vr_mob.InCritical()) && !vr_mob.GetComponent(/datum/component/virtual_reality))
- vr_mob.AddComponent(/datum/component/virtual_reality, human_occupant, src, you_die_in_the_game_you_die_for_real)
- to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
- else
+ var/mob/M = occupant
+ if(M?.mind && M == usr)
+ to_chat(M, "Transferring to virtual reality...")
+ var/datum/component/virtual_reality/VR
+ if(vr_mob)
+ VR = vr_mob.GetComponent(/datum/component/virtual_reality)
+ if(!(VR?.connect(M)))
if(allow_creating_vr_mobs)
- to_chat(occupant, "Virtual avatar not found, attempting to create one...")
+ to_chat(occupant, "Virtual avatar [vr_mob ? "corrupted" : "missing"], attempting to create one...")
var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint()
var/turf/T = get_turf(V)
if(T)
- SStgui.close_user_uis(occupant, src)
new_player(occupant, T, V.vr_outfit)
- to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
else
to_chat(occupant, "Virtual world misconfigured, aborting transfer")
else
to_chat(occupant, "The virtual world does not support the creation of new virtual avatars, aborting transfer")
+ else
+ to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
. = TRUE
if("delete_avatar")
if(!occupant || usr == occupant)
@@ -157,17 +154,31 @@
for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list)
GLOB.vr_spawnpoints[V.vr_category] = V
-/obj/machinery/vr_sleeper/proc/new_player(mob/living/carbon/human/H, location, datum/outfit/outfit, transfer = TRUE)
- if(!H)
+/obj/machinery/vr_sleeper/proc/new_player(mob/M, location, datum/outfit/outfit, transfer = TRUE)
+ if(!M)
return
cleanup_vr_mob()
vr_mob = new virtual_mob_type(location)
- if(vr_mob.build_virtual_character(H, outfit))
- var/mob/living/carbon/human/vr_H = vr_mob
- vr_H.updateappearance(TRUE, TRUE, TRUE)
- if(!transfer || !H.mind)
- return
- vr_mob.AddComponent(/datum/component/virtual_reality, H, src, you_die_in_the_game_you_die_for_real)
+ if(vr_mob.build_virtual_character(M, outfit) && iscarbon(vr_mob))
+ var/mob/living/carbon/C = vr_mob
+ C.updateappearance(TRUE, TRUE, TRUE)
+ var/datum/component/virtual_reality/VR = vr_mob.AddComponent(/datum/component/virtual_reality, you_die_in_the_game_you_die_for_real)
+ if(VR.connect(M))
+ RegisterSignal(VR, COMSIG_COMPONENT_UNREGISTER_PARENT, .proc/unset_vr_mob)
+ RegisterSignal(VR, COMSIG_COMPONENT_REGISTER_PARENT, .proc/set_vr_mob)
+ if(!only_current_user_can_interact)
+ VR.RegisterSignal(src, COMSIG_ATOM_EMAG_ACT, /datum/component/virtual_reality.proc/you_only_live_once)
+ VR.RegisterSignal(src, COMSIG_MACHINE_EJECT_OCCUPANT, /datum/component/virtual_reality.proc/revert_to_reality)
+ VR.RegisterSignal(src, COMSIG_PARENT_QDELETING, /datum/component/virtual_reality.proc/machine_destroyed)
+ to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!")
+ else
+ to_chat(M, "Transfer failed! virtual reality data likely corrupted!")
+
+/obj/machinery/vr_sleeper/proc/unset_vr_mob(datum/component/virtual_reality/VR)
+ vr_mob = null
+
+/obj/machinery/vr_sleeper/proc/set_vr_mob(datum/component/virtual_reality/VR)
+ vr_mob = VR.parent
/obj/machinery/vr_sleeper/proc/cleanup_vr_mob()
if(vr_mob)
@@ -222,6 +233,7 @@
qdel(C)
for (var/A in corpse_party)
var/mob/M = A
- if(get_area(M) == vr_area && M.stat == DEAD)
+ if(M && M.stat == DEAD && get_area(M) == vr_area)
qdel(M)
+ corpse_party -= M
addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
index 3d467350ff..755b324d71 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
@@ -146,6 +146,6 @@
/datum/clockwork_scripture/create_object/summon_arbiter/check_special_requirements()
if(GLOB.neovgre_exists)
- to_chat(invoker, "\"You've already got one...\"")
+ to_chat(invoker, "\"Only one of my weapons may exist in this temporal stream!\"")
return FALSE
return ..()
diff --git a/code/modules/cargo/packs/organic.dm b/code/modules/cargo/packs/organic.dm
index 3ffe24ac1c..e124fea345 100644
--- a/code/modules/cargo/packs/organic.dm
+++ b/code/modules/cargo/packs/organic.dm
@@ -47,6 +47,26 @@
/obj/item/storage/fancy/donut_box)
crate_name = "candy crate"
+/datum/supply_pack/organic/exoticseeds
+ name = "Exotic Seeds Crate"
+ desc = "Any entrepreneuring botanist's dream. Contains twelve different seeds, including three replica-pod seeds and two mystery seeds!"
+ cost = 1500
+ contains = list(/obj/item/seeds/nettle,
+ /obj/item/seeds/replicapod,
+ /obj/item/seeds/replicapod,
+ /obj/item/seeds/replicapod,
+ /obj/item/seeds/plump,
+ /obj/item/seeds/liberty,
+ /obj/item/seeds/amanita,
+ /obj/item/seeds/reishi,
+ /obj/item/seeds/banana,
+ /obj/item/seeds/bamboo,
+ /obj/item/seeds/eggplant/eggy,
+ /obj/item/seeds/random,
+ /obj/item/seeds/random)
+ crate_name = "exotic seeds crate"
+ crate_type = /obj/structure/closet/crate/hydroponics
+
/datum/supply_pack/organic/food
name = "Food Crate"
desc = "Get things cooking with this crate full of useful ingredients! Contains a two dozen eggs, three bananas, and two bags of flour and rice, two cartons of milk, soymilk, as well as salt and pepper shakers, an enzyme and sugar bottle, and three slabs of monkeymeat."
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index c476e5a310..b35e996bc6 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -170,12 +170,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/prefered_security_department = SEC_DEPT_RANDOM
var/custom_species = null
- //Quirk list
- var/list/positive_quirks = list()
- var/list/negative_quirks = list()
- var/list/neutral_quirks = list()
+ //Quirk list
var/list/all_quirks = list()
- var/list/character_quirks = list()
//Job preferences 2.0 - indexed by job title , no key or value implies never
var/list/job_preferences = list()
@@ -1200,7 +1196,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
Done"
dat += "
"
dat += "Current quirks: [all_quirks.len ? all_quirks.Join(", ") : "None"]"
- dat += "[positive_quirks.len] / [MAX_QUIRKS] max positive quirks
\
+ dat += "[GetPositiveQuirkCount()] / [MAX_QUIRKS] max positive quirks
\
Quirk balance remaining: [GetQuirkBalance()]
"
for(var/V in SSquirks.quirks)
var/datum/quirk/T = SSquirks.quirks[V]
@@ -1231,12 +1227,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
LOCKED: [lock_reason]
"
else
if(has_quirk)
- dat += "[quirk_name] - [initial(T.desc)] \
- [has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.)
"
+ dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \
+ [quirk_name] - [initial(T.desc)]
"
else
- dat += "[quirk_name] - [initial(T.desc)] \
- [has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.)
"
- dat += "
Reset Traits"
+ dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \
+ [quirk_name] - [initial(T.desc)]
"
+ dat += "
Reset Quirks"
var/datum/browser/popup = new(user, "mob_occupation", "Quirk Preferences
", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way
popup.set_window_options("can_close=0")
@@ -1250,6 +1246,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
bal -= initial(T.value)
return bal
+/datum/preferences/proc/GetPositiveQuirkCount()
+ . = 0
+ for(var/q in all_quirks)
+ if(SSquirks.quirk_points[q] > 0)
+ .++
+
/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess..
. = ..()
if(href_list["close"])
@@ -1315,43 +1317,30 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/quirk = href_list["trait"]
if(!SSquirks.quirks[quirk])
return
+ for(var/V in SSquirks.quirk_blacklist) //V is a list
+ var/list/L = V
+ for(var/Q in all_quirks)
+ if((quirk in L) && (Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details)
+ to_chat(user, "[quirk] is incompatible with [Q].")
+ return
var/value = SSquirks.quirk_points[quirk]
- if(value == 0)
- if(quirk in neutral_quirks)
- neutral_quirks -= quirk
- all_quirks -= quirk
- else
- neutral_quirks += quirk
- all_quirks += quirk
+ var/balance = GetQuirkBalance()
+ if(quirk in all_quirks)
+ if(balance + value < 0)
+ to_chat(user, "Refunding this would cause you to go below your balance!")
+ return
+ all_quirks -= quirk
else
- var/balance = GetQuirkBalance()
- if(quirk in positive_quirks)
- positive_quirks -= quirk
- all_quirks -= quirk
- else if(quirk in negative_quirks)
- if(balance + value < 0)
- to_chat(user, "Refunding this would cause you to go below your balance!")
- return
- negative_quirks -= quirk
- all_quirks -= quirk
- else if(value > 0)
- if(positive_quirks.len >= MAX_QUIRKS)
- to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!")
- return
- if(balance - value < 0)
- to_chat(user, "You don't have enough balance to gain this quirk!")
- return
- positive_quirks += quirk
- all_quirks += quirk
- else
- negative_quirks += quirk
- all_quirks += quirk
+ if(GetPositiveQuirkCount() >= MAX_QUIRKS)
+ to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!")
+ return
+ if(balance - value < 0)
+ to_chat(user, "You don't have enough balance to gain this quirk!")
+ return
+ all_quirks += quirk
SetQuirks(user)
if("reset")
all_quirks = list()
- positive_quirks = list()
- negative_quirks = list()
- neutral_quirks = list()
SetQuirks(user)
else
SetQuirks(user)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 29ea8f5821..f7f49a66af 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -112,7 +112,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(current_version < 24 && S["feature_exhibitionist"])
var/datum/quirk/exhibitionism/E
var/quirk_name = initial(E.name)
- neutral_quirks += quirk_name
all_quirks += quirk_name
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
@@ -386,9 +385,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
S["all_quirks"] >> all_quirks
- S["positive_quirks"] >> positive_quirks
- S["negative_quirks"] >> negative_quirks
- S["neutral_quirks"] >> neutral_quirks
//Citadel code
S["feature_genitals_use_skintone"] >> features["genitals_use_skintone"]
@@ -519,10 +515,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
all_quirks = SANITIZE_LIST(all_quirks)
- positive_quirks = SANITIZE_LIST(positive_quirks)
- negative_quirks = SANITIZE_LIST(negative_quirks)
- neutral_quirks = SANITIZE_LIST(neutral_quirks)
-
cit_character_pref_load(S)
return 1
@@ -598,9 +590,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
- WRITE_FILE(S["positive_quirks"] , positive_quirks)
- WRITE_FILE(S["negative_quirks"] , negative_quirks)
- WRITE_FILE(S["neutral_quirks"] , neutral_quirks)
cit_character_pref_save(S)
diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm
index 16992b3e96..b37e494233 100644
--- a/code/modules/hydroponics/grown/misc.dm
+++ b/code/modules/hydroponics/grown/misc.dm
@@ -141,8 +141,9 @@
endurance = 50
maturation = 3
yield = 4
- growthstages = 3
+ growthstages = 2
reagents_add = list("sugar" = 0.25)
+ mutatelist = list(/obj/item/seeds/bamboo)
/obj/item/reagent_containers/food/snacks/grown/sugarcane
seed = /obj/item/seeds/sugarcane
diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm
index ce0ca0220b..bac480bee6 100644
--- a/code/modules/hydroponics/grown/replicapod.dm
+++ b/code/modules/hydroponics/grown/replicapod.dm
@@ -41,7 +41,7 @@
blood_type = B.data["blood_type"]
features = B.data["features"]
factions = B.data["factions"]
- factions = B.data["quirks"]
+ quirks = B.data["quirks"]
contains_sample = TRUE
visible_message("The [src] is injected with a fresh blood sample.")
else
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index 9d7081ad65..982122e314 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -98,6 +98,49 @@
/obj/item/grown/log/steel/CheckAccepted(obj/item/I)
return FALSE
+/obj/item/seeds/bamboo
+ name = "pack of bamboo seeds"
+ desc = "A plant known for its flexible and resistant logs."
+ icon_state = "seed-bamboo"
+ species = "bamboo"
+ plantname = "Bamboo"
+ product = /obj/item/grown/log/bamboo
+ lifespan = 80
+ endurance = 70
+ maturation = 15
+ production = 2
+ yield = 5
+ potency = 50
+ growthstages = 2
+ growing_icon = 'icons/obj/hydroponics/growing.dmi'
+ icon_dead = "bamboo-dead"
+ genes = list(/datum/plant_gene/trait/repeated_harvest)
+
+/obj/item/grown/log/bamboo
+ seed = /obj/item/seeds/bamboo
+ name = "bamboo log"
+ desc = "A long and resistant bamboo log."
+ icon_state = "bamboo"
+ plank_type = /obj/item/stack/sheet/mineral/bamboo
+ plank_name = "bamboo sticks"
+
+/obj/item/grown/log/bamboo/CheckAccepted(obj/item/I)
+ return FALSE
+
+/obj/structure/punji_sticks
+ name = "punji sticks"
+ desc = "Don't step on this."
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "punji"
+ resistance_flags = FLAMMABLE
+ max_integrity = 30
+ density = FALSE
+ anchored = TRUE
+
+/obj/structure/punji_sticks/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/caltrop, 20, 30, 100, CALTROP_BYPASS_SHOES)
+
/////////BONFIRES//////////
/obj/structure/bonfire
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index fbbd43bbe1..5bdfd174b5 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -265,7 +265,7 @@ Works together with spawning an observer, noted above.
/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE)
penalize = suiciding || penalize // suicide squad.
- if(!key || cmptext(copytext(key,1,2),"@") || (!special && SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse) & COMPONENT_BLOCK_GHOSTING))
+ if(!key || cmptext(copytext(key,1,2),"@") || (SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special, penalize) & COMPONENT_BLOCK_GHOSTING))
return //mob has no key, is an aghost or some component hijacked.
stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc.
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index a18d249eef..310ab6beeb 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -250,7 +250,7 @@
if(bleedsuppress)
msg += "[t_He] [t_is] bandaged with something.\n"
else if(bleed_rate)
- if(reagents.has_reagent("heparin"))
+ if(bleed_rate >= 8) //8 is the rate at which heparin causes you to bleed
msg += "[t_He] [t_is] bleeding uncontrollably!\n"
else
msg += "[t_He] [t_is] bleeding!\n"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 3ae2e3bdbd..45211f7f44 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -858,6 +858,7 @@
.["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]"
.["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]"
.["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]"
+ .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]"
/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user)
if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS)
@@ -867,8 +868,9 @@
return
//If you dragged them to you and you're aggressively grabbing try to fireman carry them
else if(user != target)
- fireman_carry(target)
- return
+ if(user.a_intent == INTENT_GRAB)
+ fireman_carry(target)
+ return
. = ..()
//src is the user that will be carrying, target is the mob to be carried
@@ -890,7 +892,10 @@
return
visible_message("[src] fails to fireman carry [target]!")
else
- to_chat(src, "You can't fireman carry [target] while they're standing!")
+ if (ishuman(target))
+ to_chat(src, "You can't fireman carry [target] while they're standing!")
+ else
+ to_chat(src, "You can't seem to fireman carry that kind of species.")
/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target)
if(can_piggyback(target))
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 362a4aaa9d..042b4af4fe 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -23,7 +23,7 @@
if(!d_type)
return 0
var/protection = 0
- var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
+ var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id, wear_neck) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
for(var/bp in body_parts)
if(!bp)
continue
@@ -116,6 +116,10 @@
var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier
if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
return 1
+ if(wear_neck)
+ var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier
+ if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
+ return 1
return 0
/mob/living/carbon/human/proc/check_block()
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 8071d632ea..63d02af31a 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -119,16 +119,12 @@
. = ..()
if(G.trigger_guard == TRIGGER_GUARD_NORMAL)
- if(src.dna.check_mutation(HULK))
+ if(HAS_TRAIT(src, TRAIT_CHUNKYFINGERS))
to_chat(src, "Your meaty finger is much too large for the trigger guard!")
return FALSE
if(HAS_TRAIT(src, TRAIT_NOGUNS))
to_chat(src, "Your fingers don't fit in the trigger guard!")
return FALSE
- if(mind)
- if(mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry
- to_chat(src, "Use of ranged weaponry would bring dishonor to the clan.")
- return FALSE
return .
/*
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm
index ffd129ebf7..6e54e320ff 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm
@@ -4,7 +4,7 @@
say_mod = "gibbers"
sexes = FALSE
species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NOGUNS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutanttongue = /obj/item/organ/tongue/abductor
/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species)
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 48ce55a052..3d6c5092e1 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -3,7 +3,7 @@
name = "Golem"
id = "iron golem"
species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID)
mutant_organs = list(/obj/item/organ/adamantine_resonator)
speedmod = 2
@@ -88,7 +88,7 @@
fixed_mut_color = "a3d"
meat = /obj/item/stack/ore/plasma
//Can burn and takes damage from heat
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE
info_text = "As a Plasma Golem, you burn easily. Be careful, if you get hot enough while burning, you'll blow up!"
heatmod = 0 //fine until they blow up
prefix = "Plasma"
@@ -266,7 +266,7 @@
fixed_mut_color = "9E704B"
meat = /obj/item/stack/sheet/mineral/wood
//Can burn and take damage from heat
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
armor = 30
burnmod = 1.25
heatmod = 1.5
@@ -693,7 +693,7 @@
info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \
Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient."
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) //no mutcolors, and can burn
- inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS)
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_CHUNKYFINGERS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
armor = 15 //feels no pain, but not too resistant
burnmod = 2 // don't get burned
@@ -893,7 +893,7 @@
special_names = list("Box")
info_text = "As a Cardboard Golem, you aren't very strong, but you are a bit quicker and can easily create more brethren by using cardboard on yourself."
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
fixed_mut_color = "ffffff"
limbs_id = "c_golem" //special sprites
attack_verb = "bash"
@@ -936,7 +936,7 @@
name = "Leather Golem"
id = "leather golem"
special_names = list("Face", "Man", "Belt") //Ah dude 4 strength 4 stam leather belt AHHH
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER)
prefix = "Leather"
fixed_mut_color = "624a2e"
info_text = "As a Leather Golem, you are flammable, but you can grab things with incredible ease, allowing all your grabs to start at a strong level."
@@ -952,7 +952,7 @@
special_names = list("Boll","Weave")
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES)
fixed_mut_color = null
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well."
/datum/species/golem/durathread/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target)
@@ -974,7 +974,7 @@
fixed_mut_color = "ffffff"
attack_verb = "rattl"
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
- inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER)
+ inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER)
info_text = "As a Bone Golem, You have a powerful spell that lets you chill your enemies with fear, and milk heals you! Just make sure to watch our for bone-hurting juice."
var/datum/action/innate/bonechill/bonechill
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 069317d4a8..5f91e3ae66 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -116,6 +116,33 @@
return
to_chat(H, "...but there is not enough of you to go around! You must attain more mass to heal!")
+/datum/species/jelly/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/jelly/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/jelly/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/jelly/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
////////////////////////////////////////////////////////SLIMEPEOPLE///////////////////////////////////////////////////////////////////
//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death.
@@ -412,34 +439,6 @@
heatmod = 1
burnmod = 1
-/datum/species/jelly/roundstartslime/spec_death(gibbed, mob/living/carbon/human/H)
- if(H)
- stop_wagging_tail(H)
-
-/datum/species/jelly/roundstartslime/spec_stun(mob/living/carbon/human/H,amount)
- if(H)
- stop_wagging_tail(H)
- . = ..()
-
-/datum/species/jelly/roundstartslime/can_wag_tail(mob/living/carbon/human/H)
- return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
-
-/datum/species/jelly/roundstartslime/is_wagging_tail(mob/living/carbon/human/H)
- return ("mam_waggingtail" in mutant_bodyparts)
-
-/datum/species/jelly/roundstartslime/start_wagging_tail(mob/living/carbon/human/H)
- if("mam_tail" in mutant_bodyparts)
- mutant_bodyparts -= "mam_tail"
- mutant_bodyparts |= "mam_waggingtail"
- H.update_body()
-
-/datum/species/jelly/roundstartslime/stop_wagging_tail(mob/living/carbon/human/H)
- if("mam_waggingtail" in mutant_bodyparts)
- mutant_bodyparts -= "mam_waggingtail"
- mutant_bodyparts |= "mam_tail"
- H.update_body()
-
-
/datum/action/innate/slime_change
name = "Alter Form"
check_flags = AB_CHECK_CONSCIOUS
@@ -840,19 +839,16 @@
link_minds = new(src)
link_minds.Grant(C)
slimelink_owner = C
- link_mob(C)
+ link_mob(C, TRUE)
-/datum/species/jelly/stargazer/proc/link_mob(mob/living/M)
- if(QDELETED(M) || M.stat == DEAD)
+/datum/species/jelly/stargazer/proc/link_mob(mob/living/M, selflink = FALSE)
+ if(QDELETED(M) || (M in linked_mobs))
return FALSE
- if(HAS_TRAIT(M, TRAIT_MINDSHIELD)) //mindshield implant, no dice
- return FALSE
- if(M.anti_magic_check(FALSE, FALSE, TRUE, 0))
- return FALSE
- if(M in linked_mobs)
+ if(!selflink && (M.stat == DEAD || HAS_TRAIT(M, TRAIT_MINDSHIELD) || M.anti_magic_check(FALSE, FALSE, TRUE, 0)))
return FALSE
linked_mobs.Add(M)
- to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.")
+ if(!selflink)
+ to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.")
var/datum/action/innate/linked_speech/action = new(src)
linked_actions.Add(action)
action.Grant(M)
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index c0973102d6..9317a51050 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -94,7 +94,7 @@
id = "ashlizard"
limbs_id = "lizard"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE)
- inherent_traits = list(TRAIT_NOGUNS)
+ inherent_traits = list(TRAIT_CHUNKYFINGERS)
mutantlungs = /obj/item/organ/lungs/ashwalker
burnmod = 0.9
brutemod = 0.9
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index 90f63467a0..3247c96632 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -39,7 +39,7 @@
blacklisted = TRUE
no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE)
species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYES,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
mutanteyes = /obj/item/organ/eyes/night_vision/nightmare
mutant_organs = list(/obj/item/organ/heart/nightmare)
mutant_brain = /obj/item/organ/brain/nightmare
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index 0ff418d628..8345ef916d 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -62,12 +62,8 @@
unset_machine()
timeofdeath = world.time
tod = STATION_TIME_TIMESTAMP("hh:mm:ss")
- var/turf/T = get_turf(src)
for(var/obj/item/I in contents)
I.on_mob_death(src, gibbed)
- if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf))
- var/rendered = "[mind.name] has died at [get_area_name(T)]."
- deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
if(mind)
mind.store_memory("Time of death: [tod]", 0)
GLOB.alive_mob_list -= src
@@ -89,7 +85,12 @@
addtimer(CALLBACK(src, .proc/med_hud_set_status), (DEFIB_TIME_LIMIT * 10) + 1)
stop_pulling()
- SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
+ var/signal = SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
+
+ var/turf/T = get_turf(src)
+ if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf) && !(signal & COMPONENT_BLOCK_DEATH_BROADCAST))
+ var/rendered = "[mind.name] has died at [get_area_name(T)]."
+ deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
if (client)
client.move_delay = initial(client.move_delay)
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 6c1a2cfec9..041b367ebf 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -87,6 +87,12 @@
message_simple = S.deathmessage
. = ..()
message_simple = initial(message_simple)
+ if(. && user.deathsound)
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!L.can_speak_vocal() || L.oxyloss >= 50)
+ return //stop the sound if oxyloss too high/cant speak
+ playsound(user, user.deathsound, 200, TRUE, TRUE)
if(. && isalienadult(user))
playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1)
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 112add367f..f757203237 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -49,7 +49,7 @@
else
padloc = "(UNKNOWN)"
src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]")
- send_speech(message, 7, T, "robot", language)
+ send_speech(message, 7, T, "robot", message_language = language)
to_chat(src, "Holopad transmitted, [real_name] \"[message]\"")
else
to_chat(src, "No holopad connected.")
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 01edaba8ec..c5c2beb999 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -834,7 +834,7 @@
robot_suit.head.flash2.burn_out()
robot_suit.head.flash2 = null
robot_suit.head = null
- robot_suit.updateicon()
+ robot_suit.update_icon()
else
new /obj/item/robot_suit(T)
new /obj/item/bodypart/l_leg/robot(T)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
new file mode 100644
index 0000000000..04a1b4a468
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -0,0 +1,366 @@
+#define TUMOR_INACTIVE 0
+#define TUMOR_ACTIVE 1
+#define TUMOR_PASSIVE 2
+
+//Elite mining mobs
+/mob/living/simple_animal/hostile/asteroid/elite
+ name = "elite"
+ desc = "An elite monster, found in one of the strange tumors on lavaland."
+ icon = 'icons/mob/lavaland/lavaland_elites.dmi'
+ faction = list("boss")
+ robust_searching = TRUE
+ ranged_ignores_vision = TRUE
+ ranged = TRUE
+ obj_damage = 5
+ vision_range = 6
+ aggro_vision_range = 18
+ environment_smash = ENVIRONMENT_SMASH_NONE //This is to prevent elites smashing up the mining station, we'll make sure they can smash minerals fine below.
+ harm_intent_damage = 0 //Punching elites gets you nowhere
+ stat_attack = UNCONSCIOUS
+ layer = LARGE_MOB_LAYER
+ sentience_type = SENTIENCE_BOSS
+ hud_type = /datum/hud/lavaland_elite
+ var/chosen_attack = 1
+ var/list/attack_action_types = list()
+ var/can_talk = FALSE
+ var/obj/loot_drop = null
+
+//Gives player-controlled variants the ability to swap attacks
+/mob/living/simple_animal/hostile/asteroid/elite/Initialize(mapload)
+ . = ..()
+ for(var/action_type in attack_action_types)
+ var/datum/action/innate/elite_attack/attack_action = new action_type()
+ attack_action.Grant(src)
+
+//Prevents elites from attacking members of their faction (can't hurt themselves either) and lets them mine rock with an attack despite not being able to smash walls.
+/mob/living/simple_animal/hostile/asteroid/elite/AttackingTarget()
+ if(istype(target, /mob/living/simple_animal/hostile))
+ var/mob/living/simple_animal/hostile/M = target
+ if(faction_check_mob(M))
+ return FALSE
+ if(istype(target, /obj/structure/elite_tumor))
+ var/obj/structure/elite_tumor/T = target
+ if(T.mychild == src && T.activity == TUMOR_PASSIVE)
+ var/elite_remove = alert("Re-enter the tumor?", "Despawn yourself?", "Yes", "No")
+ if(elite_remove == "No" || !src || QDELETED(src))
+ return
+ T.mychild = null
+ T.activity = TUMOR_INACTIVE
+ T.icon_state = "advanced_tumor"
+ qdel(src)
+ return FALSE
+ . = ..()
+ if(ismineralturf(target))
+ var/turf/closed/mineral/M = target
+ M.gets_drilled()
+
+//Elites can't talk (normally)!
+/mob/living/simple_animal/hostile/asteroid/elite/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(can_talk)
+ . = ..()
+ return TRUE
+ return FALSE
+
+/*Basic setup for elite attacks, based on Whoneedspace's megafauna attack setup.
+While using this makes the system rely on OnFire, it still gives options for timers not tied to OnFire, and it makes using attacks consistent accross the board for player-controlled elites.*/
+
+/datum/action/innate/elite_attack
+ name = "Elite Attack"
+ icon_icon = 'icons/mob/actions/actions_elites.dmi'
+ button_icon_state = ""
+ background_icon_state = "bg_default"
+ var/mob/living/simple_animal/hostile/asteroid/elite/M
+ var/chosen_message
+ var/chosen_attack_num = 0
+
+/datum/action/innate/elite_attack/Grant(mob/living/L)
+ if(istype(L, /mob/living/simple_animal/hostile/asteroid/elite))
+ M = L
+ return ..()
+ return FALSE
+
+/datum/action/innate/elite_attack/Activate()
+ M.chosen_attack = chosen_attack_num
+ to_chat(M, chosen_message)
+
+/mob/living/simple_animal/hostile/asteroid/elite/updatehealth()
+ . = ..()
+ update_health_hud()
+
+/mob/living/simple_animal/hostile/asteroid/elite/update_health_hud()
+ if(hud_used)
+ var/severity = 0
+ var/healthpercent = (health/maxHealth) * 100
+ switch(healthpercent)
+ if(100 to INFINITY)
+ hud_used.healths.icon_state = "elite_health0"
+ if(80 to 100)
+ severity = 1
+ if(60 to 80)
+ severity = 2
+ if(40 to 60)
+ severity = 3
+ if(20 to 40)
+ severity = 4
+ if(10 to 20)
+ severity = 5
+ if(1 to 20)
+ severity = 6
+ else
+ severity = 7
+ hud_used.healths.icon_state = "elite_health[severity]"
+ if(severity > 0)
+ overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity)
+ else
+ clear_fullscreen("brute")
+
+//The Pulsing Tumor, the actual "spawn-point" of elites, handles the spawning, arena, and procs for dealing with basic scenarios.
+
+/obj/structure/elite_tumor
+ name = "pulsing tumor"
+ desc = "An odd, pulsing tumor sticking out of the ground. You feel compelled to reach out and touch it..."
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = INDESTRUCTIBLE
+ var/activity = TUMOR_INACTIVE
+ var/boosted = FALSE
+ var/times_won = 0
+ var/mob/living/carbon/human/activator = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/mychild = null
+ var/potentialspawns = list(/mob/living/simple_animal/hostile/asteroid/elite/broodmother,
+ /mob/living/simple_animal/hostile/asteroid/elite/pandora,
+ /mob/living/simple_animal/hostile/asteroid/elite/legionnaire,
+ /mob/living/simple_animal/hostile/asteroid/elite/herald)
+ icon = 'icons/obj/lavaland/tumor.dmi'
+ icon_state = "tumor"
+ pixel_x = -16
+ light_color = LIGHT_COLOR_RED
+ light_range = 3
+ anchored = TRUE
+ density = FALSE
+ var/obj/item/gps/internal = null
+
+/obj/item/gps/internal/elite
+ icon_state = null
+ gpstag = "Menacing Signal"
+ desc = "You're not quite sure how a signal can be menacing."
+ invisibility = 100
+
+/obj/structure/elite_tumor/attack_hand(mob/user)
+ . = ..()
+ if(ishuman(user))
+ switch(activity)
+ if(TUMOR_PASSIVE)
+ activity = TUMOR_ACTIVE
+ visible_message("[src] convulses as your arm enters its radius. Your instincts tell you to step back.")
+ activator = user
+ if(boosted)
+ mychild.playsound_local(get_turf(mychild), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(mychild, "Someone has activated your tumor. You will be returned to fight shortly, get ready!")
+ addtimer(CALLBACK(src, .proc/return_elite), 30)
+ INVOKE_ASYNC(src, .proc/arena_checks)
+ if(TUMOR_INACTIVE)
+ activity = TUMOR_ACTIVE
+ var/mob/dead/observer/elitemind = null
+ visible_message("[src] begins to convulse. Your instincts tell you to step back.")
+ activator = user
+ if(!boosted)
+ addtimer(CALLBACK(src, .proc/spawn_elite), 30)
+ return
+ visible_message("Something within [src] stirs...")
+ var/list/candidates = pollCandidatesForMob("Do you want to play as a lavaland elite?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, src, POLL_IGNORE_SENTIENCE_POTION)
+ if(candidates.len)
+ audible_message("The stirring sounds increase in volume!")
+ elitemind = pick(candidates)
+ elitemind.playsound_local(get_turf(elitemind), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(elitemind, "You have been chosen to play as a Lavaland Elite.\nIn a few seconds, you will be summoned on Lavaland as a monster to fight your activator, in a fight to the death.\nYour attacks can be switched using the buttons on the top left of the HUD, and used by clicking on targets or tiles similar to a gun.\nWhile the opponent might have an upper hand with powerful mining equipment and tools, you have great power normally limited by AI mobs.\nIf you want to win, you'll have to use your powers in creative ways to ensure the kill. It's suggested you try using them all as soon as possible.\nShould you win, you'll receive extra information regarding what to do after. Good luck!")
+ addtimer(CALLBACK(src, .proc/spawn_elite, elitemind), 100)
+ else
+ visible_message("The stirring stops, and nothing emerges. Perhaps try again later.")
+ activity = TUMOR_INACTIVE
+ activator = null
+
+
+obj/structure/elite_tumor/proc/spawn_elite(var/mob/dead/observer/elitemind)
+ var/selectedspawn = pick(potentialspawns)
+ mychild = new selectedspawn(loc)
+ visible_message("[mychild] emerges from [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ if(boosted)
+ mychild.key = elitemind.key
+ mychild.sentience_act()
+ icon_state = "tumor_popped"
+ INVOKE_ASYNC(src, .proc/arena_checks)
+
+obj/structure/elite_tumor/proc/return_elite()
+ mychild.forceMove(loc)
+ visible_message("[mychild] emerges from [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ mychild.revive(full_heal = TRUE, admin_revive = TRUE)
+ if(boosted)
+ mychild.maxHealth = mychild.maxHealth * 2
+ mychild.health = mychild.maxHealth
+
+/obj/structure/elite_tumor/Initialize(mapload)
+ . = ..()
+ internal = new/obj/item/gps/internal/elite(src)
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/elite_tumor/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ mychild = null
+ activator = null
+ return ..()
+
+/obj/structure/elite_tumor/process()
+ if(isturf(loc))
+ for(var/mob/living/simple_animal/hostile/asteroid/elite/elitehere in loc)
+ if(elitehere == mychild && activity == TUMOR_PASSIVE)
+ mychild.adjustHealth(-mychild.maxHealth*0.05)
+ var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(mychild))
+ H.color = "#FF0000"
+
+/obj/structure/elite_tumor/attackby(obj/item/I, mob/user, params)
+ . = ..()
+ if(istype(I, /obj/item/organ/regenerative_core) && activity == TUMOR_INACTIVE && !boosted)
+ var/obj/item/organ/regenerative_core/core = I
+ if(!core.preserved)
+ return
+ visible_message("As [user] drops the core into [src], [src] appears to swell.")
+ icon_state = "advanced_tumor"
+ boosted = TRUE
+ light_range = 6
+ desc = "[desc] This one seems to glow with a strong intensity."
+ qdel(core)
+ return TRUE
+
+/obj/structure/elite_tumor/proc/arena_checks()
+ if(activity != TUMOR_ACTIVE || QDELETED(src))
+ return
+ INVOKE_ASYNC(src, .proc/fighters_check) //Checks to see if our fighters died.
+ INVOKE_ASYNC(src, .proc/arena_trap) //Gets another arena trap queued up for when this one runs out.
+ INVOKE_ASYNC(src, .proc/border_check) //Checks to see if our fighters got out of the arena somehow.
+ addtimer(CALLBACK(src, .proc/arena_checks), 50)
+
+/obj/structure/elite_tumor/proc/fighters_check()
+ if(activator != null && activator.stat == DEAD || activity == TUMOR_ACTIVE && QDELETED(activator))
+ onEliteWon()
+ if(mychild != null && mychild.stat == DEAD || activity == TUMOR_ACTIVE && QDELETED(mychild))
+ onEliteLoss()
+
+/obj/structure/elite_tumor/proc/arena_trap()
+ var/turf/T = get_turf(src)
+ if(loc == null)
+ return
+ for(var/t in RANGE_TURFS(12, T))
+ if(get_dist(t, T) == 12)
+ var/obj/effect/temp_visual/elite_tumor_wall/newwall
+ newwall = new /obj/effect/temp_visual/elite_tumor_wall(t, src)
+ newwall.activator = src.activator
+ newwall.ourelite = src.mychild
+
+/obj/structure/elite_tumor/proc/border_check()
+ if(activator != null && get_dist(src, activator) >= 12)
+ activator.forceMove(loc)
+ visible_message("[activator] suddenly reappears above [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ if(mychild != null && get_dist(src, mychild) >= 12)
+ mychild.forceMove(loc)
+ visible_message("[mychild] suddenly reappears above [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+
+obj/structure/elite_tumor/proc/onEliteLoss()
+ playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, 0, 50, TRUE, TRUE)
+ visible_message("[src] begins to convulse violently before beginning to dissipate.")
+ visible_message("As [src] closes, something is forced up from down below.")
+ var/obj/structure/closet/crate/necropolis/tendril/lootbox = new /obj/structure/closet/crate/necropolis/tendril(loc)
+ if(!boosted)
+ mychild = null
+ activator = null
+ qdel(src)
+ return
+ var/lootpick = rand(1, 2)
+ if(lootpick == 1 && mychild.loot_drop != null)
+ new mychild.loot_drop(lootbox)
+ else
+ new /obj/item/tumor_shard(lootbox)
+ mychild = null
+ activator = null
+ qdel(src)
+
+obj/structure/elite_tumor/proc/onEliteWon()
+ activity = TUMOR_PASSIVE
+ activator = null
+ mychild.revive(full_heal = TRUE, admin_revive = TRUE)
+ if(boosted)
+ times_won++
+ mychild.maxHealth = mychild.maxHealth * 0.5
+ mychild.health = mychild.maxHealth
+ if(times_won == 1)
+ mychild.playsound_local(get_turf(mychild), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(mychild, "As the life in the activator's eyes fade, the forcefield around you dies out and you feel your power subside.\nDespite this inferno being your home, you feel as if you aren't welcome here anymore.\nWithout any guidance, your purpose is now for you to decide.")
+ to_chat(mychild, "Your max health has been halved, but can now heal by standing on your tumor. Note, it's your only way to heal.\nBear in mind, if anyone interacts with your tumor, you'll be resummoned here to carry out another fight. In such a case, you will regain your full max health.\nAlso, be weary of your fellow inhabitants, they likely won't be happy to see you!")
+ to_chat(mychild, "Note that you are a lavaland monster, and thus not allied to the station. You should not cooperate or act friendly with any station crew unless under extreme circumstances!")
+
+/obj/item/tumor_shard
+ name = "tumor shard"
+ desc = "A strange, sharp, crystal shard from an odd tumor on Lavaland. Stabbing the corpse of a lavaland elite with this will revive them, assuming their soul still lingers. Revived lavaland elites only have half their max health, but are completely loyal to their reviver."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "crevice_shard"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ item_state = "screwdriver_head"
+ throwforce = 5
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 3
+ throw_range = 5
+
+/obj/item/tumor_shard/afterattack(atom/target, mob/user, proximity_flag)
+ . = ..()
+ if(istype(target, /mob/living/simple_animal/hostile/asteroid/elite) && proximity_flag)
+ var/mob/living/simple_animal/hostile/asteroid/elite/E = target
+ if(E.stat != DEAD || E.sentience_type != SENTIENCE_BOSS || !E.key)
+ user.visible_message("It appears [E] is unable to be revived right now. Perhaps try again later.")
+ return
+ E.faction = list("neutral")
+ E.revive(full_heal = TRUE, admin_revive = TRUE)
+ user.visible_message("[user] stabs [E] with [src], reviving it.")
+ E.playsound_local(get_turf(E), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(E, "You have been revived by [user]. While you can't speak to them, you owe [user] a great debt. Assist [user.p_them()] in achieving [user.p_their()] goals, regardless of risk.Note that you now share the loyalties of [user]. You are expected not to intentionally sabotage their faction unless commanded to!")
+ E.maxHealth = E.maxHealth * 0.5
+ E.health = E.maxHealth
+ E.desc = "[E.desc] However, this one appears appears less wild in nature, and calmer around people."
+ E.sentience_type = SENTIENCE_ORGANIC
+ qdel(src)
+ else
+ to_chat(user, "[src] only works on the corpse of a sentient lavaland elite.")
+
+/obj/effect/temp_visual/elite_tumor_wall
+ name = "magic wall"
+ icon = 'icons/turf/walls/hierophant_wall_temp.dmi'
+ icon_state = "wall"
+ duration = 50
+ smooth = SMOOTH_TRUE
+ layer = BELOW_MOB_LAYER
+ var/mob/living/carbon/human/activator = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/ourelite = null
+ color = rgb(255,0,0)
+ light_range = MINIMUM_USEFUL_LIGHT_RANGE
+ light_color = LIGHT_COLOR_RED
+
+/obj/effect/temp_visual/elite_tumor_wall/Initialize(mapload, new_caster)
+ . = ..()
+ queue_smooth_neighbors(src)
+ queue_smooth(src)
+
+/obj/effect/temp_visual/elite_tumor_wall/Destroy()
+ queue_smooth_neighbors(src)
+ activator = null
+ ourelite = null
+ return ..()
+
+/obj/effect/temp_visual/elite_tumor_wall/CanPass(atom/movable/mover, turf/target)
+ if(mover == ourelite || mover == activator)
+ return FALSE
+ else
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
new file mode 100644
index 0000000000..c18a342206
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
@@ -0,0 +1,247 @@
+#define TENTACLE_PATCH 1
+#define SPAWN_CHILDREN 2
+#define RAGE 3
+#define CALL_CHILDREN 4
+
+/**
+ * # Goliath Broodmother
+ *
+ * A stronger, faster variation of the goliath. Has the ability to spawn baby goliaths, which it can later detonate at will.
+ * When it's health is below half, tendrils will spawn randomly around it. When it is below a quarter of health, this effect is doubled.
+ * It's attacks are as follows:
+ * - Spawns a 3x3/plus shape of tentacles on the target location
+ * - Spawns 2 baby goliaths on its tile, up to a max of 8. Children blow up when they die.
+ * - The broodmother lets out a noise, and is able to move faster for 6.5 seconds.
+ * - Summons your children around you.
+ * The broodmother is a fight revolving around stage control, as the activator has to manage the baby goliaths and the broodmother herself, along with all the tendrils.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother
+ name = "goliath broodmother"
+ desc = "An example of sexual dimorphism, this female goliath looks much different than the males of her species. She is, however, just as dangerous, if not more."
+ gender = FEMALE
+ icon_state = "broodmother"
+ icon_living = "broodmother"
+ icon_aggro = "broodmother"
+ icon_dead = "egg_sac"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ armour_penetration = 30
+ attacktext = "beats down on"
+ /*attack_verb_continuous = "beats down on"
+ attack_verb_simple = "beat down on"*/
+ attack_sound = 'sound/weapons/punch1.ogg'
+ throw_message = "does nothing to the rocky hide of the"
+ speed = 2
+ move_to_delay = 5
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathmessage = "explodes into gore!"
+ loot_drop = /obj/item/crusher_trophy/broodmother_tongue
+
+ attack_action_types = list(/datum/action/innate/elite_attack/tentacle_patch,
+ /datum/action/innate/elite_attack/spawn_children,
+ /datum/action/innate/elite_attack/rage,
+ /datum/action/innate/elite_attack/call_children)
+
+ var/rand_tent = 0
+ var/list/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/children_list = list()
+
+/datum/action/innate/elite_attack/tentacle_patch
+ name = "Tentacle Patch"
+ button_icon_state = "tentacle_patch"
+ chosen_message = "You are now attacking with a patch of tentacles."
+ chosen_attack_num = TENTACLE_PATCH
+
+/datum/action/innate/elite_attack/spawn_children
+ name = "Spawn Children"
+ button_icon_state = "spawn_children"
+ chosen_message = "You will spawn two children at your location to assist you in combat. You can have up to 8."
+ chosen_attack_num = SPAWN_CHILDREN
+
+/datum/action/innate/elite_attack/rage
+ name = "Rage"
+ button_icon_state = "rage"
+ chosen_message = "You will temporarily increase your movement speed."
+ chosen_attack_num = RAGE
+
+/datum/action/innate/elite_attack/call_children
+ name = "Call Children"
+ button_icon_state = "call_children"
+ chosen_message = "You will summon your children to your location."
+ chosen_attack_num = CALL_CHILDREN
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(TENTACLE_PATCH)
+ tentacle_patch(target)
+ if(SPAWN_CHILDREN)
+ spawn_children()
+ if(RAGE)
+ rage()
+ if(CALL_CHILDREN)
+ call_children()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(TENTACLE_PATCH)
+ tentacle_patch(target)
+ if(SPAWN_CHILDREN)
+ spawn_children()
+ if(RAGE)
+ rage()
+ if(CALL_CHILDREN)
+ call_children()
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/Life()
+ . = ..()
+ if(!.) //Checks if they are dead as a rock.
+ return
+ if(health < maxHealth * 0.5 && rand_tent < world.time)
+ rand_tent = world.time + 30
+ var/tentacle_amount = 5
+ if(health < maxHealth * 0.25)
+ tentacle_amount = 10
+ var/tentacle_loc = spiral_range_turfs(5, get_turf(src))
+ for(var/i in 1 to tentacle_amount)
+ var/turf/t = pick_n_take(tentacle_loc)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(t, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/tentacle_patch(var/target)
+ ranged_cooldown = world.time + 15
+ var/tturf = get_turf(target)
+ if(!isturf(tturf))
+ return
+ visible_message("[src] digs its tentacles under [target]!")
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(tturf, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/spawn_children(var/target)
+ ranged_cooldown = world.time + 40
+ visible_message("The ground churns behind [src]!")
+ for(var/i in 1 to 2)
+ if(children_list.len >= 8)
+ return
+ var/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/newchild = new /mob/living/simple_animal/hostile/asteroid/elite/broodmother_child(loc)
+ newchild.GiveTarget(target)
+ newchild.faction = faction.Copy()
+ visible_message("[newchild] appears below [src]!")
+ newchild.mother = src
+ children_list += newchild
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/rage()
+ ranged_cooldown = world.time + 70
+ playsound(src,'sound/spookoween/insane_low_laugh.ogg', 200, 1)
+ visible_message("[src] starts picking up speed!")
+ color = "#FF0000"
+ set_varspeed(0)
+ move_to_delay = 3
+ addtimer(CALLBACK(src, .proc/reset_rage), 65)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/reset_rage()
+ color = "#FFFFFF"
+ set_varspeed(2)
+ move_to_delay = 5
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/call_children()
+ ranged_cooldown = world.time + 60
+ visible_message("The ground shakes near [src]!")
+ var/list/directions = GLOB.cardinals.Copy() + GLOB.diagonals.Copy()
+ for(var/mob/child in children_list)
+ var/spawndir = pick_n_take(directions)
+ var/turf/T = get_step(src, spawndir)
+ if(T)
+ child.forceMove(T)
+ playsound(src, 'sound/effects/bamf.ogg', 100, 1)
+
+//The goliath's children. Pretty weak, simple mobs which are able to put a single tentacle under their target when at range.
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child
+ name = "baby goliath"
+ desc = "A young goliath recently born from it's mother. While they hatch from eggs, said eggs are incubated in the mother until they are ready to be born."
+ icon = 'icons/mob/lavaland/lavaland_monsters.dmi'
+ icon_state = "goliath_baby"
+ icon_living = "goliath_baby"
+ icon_aggro = "goliath_baby"
+ icon_dead = "goliath_baby_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 30
+ health = 30
+ melee_damage_lower = 5
+ melee_damage_upper = 5
+ attacktext = "bashes against"
+ /*attack_verb_continuous = "bashes against"
+ attack_verb_simple = "bash against"*/
+ attack_sound = 'sound/weapons/punch1.ogg'
+ throw_message = "does nothing to the rocky hide of the"
+ speed = 2
+ move_to_delay = 5
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ mouse_opacity = MOUSE_OPACITY_ICON
+ butcher_results = list()
+ guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide = 1)
+ deathmessage = "falls to the ground."
+ status_flags = CANPUSH
+ var/mob/living/simple_animal/hostile/asteroid/elite/broodmother/mother = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/OpenFire(target)
+ ranged_cooldown = world.time + 40
+ var/tturf = get_turf(target)
+ if(!isturf(tturf))
+ return
+ if(get_dist(src, target) <= 7)//Screen range check, so it can't attack people off-screen
+ visible_message("[src] digs one of its tentacles under [target]!")
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(tturf, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/death()
+ . = ..()
+ if(mother != null)
+ mother.children_list -= src
+ visible_message("[src] explodes!")
+ explosion(get_turf(loc),0,0,0,flame_range = 3, adminlog = FALSE)
+ gib()
+
+//Tentacles have less stun time compared to regular variant, to balance being able to use them much more often. Also, 10 more damage.
+/obj/effect/temp_visual/goliath_tentacle/broodmother/trip()
+ var/latched = FALSE
+ for(var/mob/living/L in loc)
+ if((!QDELETED(spawner) && spawner.faction_check_mob(L)) || L.stat == DEAD)
+ continue
+ visible_message("[src] grabs hold of [L]!")
+ L.Stun(10)
+ L.adjustBruteLoss(rand(30,35))
+ latched = TRUE
+ if(!latched)
+ retract()
+ else
+ deltimer(timerid)
+ timerid = addtimer(CALLBACK(src, .proc/retract), 10, TIMER_STOPPABLE)
+
+/obj/effect/temp_visual/goliath_tentacle/broodmother/patch/Initialize(mapload, new_spawner)
+ . = ..()
+ var/tentacle_locs = spiral_range_turfs(1, get_turf(src))
+ for(var/T in tentacle_locs)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner)
+ var/list/directions = GLOB.cardinals.Copy()
+ for(var/i in directions)
+ var/turf/T = get_step(get_turf(src), i)
+ T = get_step(T, i)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner)
+
+// Broodmother's loot: Broodmother Tongue
+/obj/item/crusher_trophy/broodmother_tongue
+ name = "broodmother tongue"
+ desc = "The tongue of a broodmother. If attached a certain way, makes for a suitable crusher trophy."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "broodmother_tongue"
+ denied_type = /obj/item/crusher_trophy/broodmother_tongue
+ bonus_value = 10
+
+/obj/item/crusher_trophy/broodmother_tongue/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a patch of goliath tentacles at the target's location"
+
+/obj/item/crusher_trophy/broodmother_tongue/on_mark_detonation(mob/living/target, mob/living/user)
+ if(rand(1, 100) <= bonus_value && target.stat != DEAD)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(get_turf(target), user)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
new file mode 100644
index 0000000000..f1e7494beb
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -0,0 +1,275 @@
+#define HERALD_TRISHOT 1
+#define HERALD_DIRECTIONALSHOT 2
+#define HERALD_TELESHOT 3
+#define HERALD_MIRROR 4
+
+/**
+ * # Herald
+ *
+ * A slow-moving projectile user with a few tricks up it's sleeve. Less unga-bunga than Colossus, with more cleverness in it's fighting style.
+ * As it's health gets lower, the amount of projectiles fired per-attack increases.
+ * It's attacks are as follows:
+ * - Fires three projectiles in a a given direction.
+ * - Fires a spread in every cardinal and diagonal direction at once, then does it again after a bit.
+ * - Shoots a single, golden bolt. Wherever it lands, the herald will be teleported to the location.
+ * - Spawns a mirror which reflects projectiles directly at the target.
+ * Herald is a more concentrated variation of the Colossus fight, having less projectiles overall, but more focused attacks.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald
+ name = "herald"
+ desc = "A monstrous beast which fires deadly projectiles at threats and prey."
+ icon_state = "herald"
+ icon_living = "herald"
+ icon_aggro = "herald"
+ icon_dead = "herald_dying"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ attacktext = "preaches to"
+ /*attack_verb_continuous = "preaches to"
+ attack_verb_simple = "preach to"*/
+ attack_sound = 'sound/magic/clockwork/ratvar_attack.ogg'
+ throw_message = "doesn't affect the purity of"
+ speed = 4
+ move_to_delay = 10
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/demon_dies.ogg'
+ deathmessage = "begins to shudder as it becomes transparent..."
+ loot_drop = /obj/item/clothing/neck/cloak/herald_cloak
+
+ can_talk = 1
+
+ attack_action_types = list(/datum/action/innate/elite_attack/herald_trishot,
+ /datum/action/innate/elite_attack/herald_directionalshot,
+ /datum/action/innate/elite_attack/herald_teleshot,
+ /datum/action/innate/elite_attack/herald_mirror)
+
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/my_mirror = null
+ var/is_mirror = FALSE
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/death()
+ . = ..()
+ if(!is_mirror)
+ addtimer(CALLBACK(src, .proc/become_ghost), 8)
+ if(my_mirror != null)
+ qdel(my_mirror)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost()
+ icon_state = "herald_ghost"
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ . = ..()
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+
+/datum/action/innate/elite_attack/herald_trishot
+ name = "Triple Shot"
+ button_icon_state = "herald_trishot"
+ chosen_message = "You are now firing three shots in your chosen direction."
+ chosen_attack_num = HERALD_TRISHOT
+
+/datum/action/innate/elite_attack/herald_directionalshot
+ name = "Circular Shot"
+ button_icon_state = "herald_directionalshot"
+ chosen_message = "You are firing projectiles in all directions."
+ chosen_attack_num = HERALD_DIRECTIONALSHOT
+
+/datum/action/innate/elite_attack/herald_teleshot
+ name = "Teleport Shot"
+ button_icon_state = "herald_teleshot"
+ chosen_message = "You will now fire a shot which teleports you where it lands."
+ chosen_attack_num = HERALD_TELESHOT
+
+/datum/action/innate/elite_attack/herald_mirror
+ name = "Summon Mirror"
+ button_icon_state = "herald_mirror"
+ chosen_message = "You will spawn a mirror which duplicates your attacks."
+ chosen_attack_num = HERALD_MIRROR
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(HERALD_TRISHOT)
+ herald_trishot(target)
+ if(my_mirror != null)
+ my_mirror.herald_trishot(target)
+ if(HERALD_DIRECTIONALSHOT)
+ herald_directionalshot()
+ if(my_mirror != null)
+ my_mirror.herald_directionalshot()
+ if(HERALD_TELESHOT)
+ herald_teleshot(target)
+ if(my_mirror != null)
+ my_mirror.herald_teleshot(target)
+ if(HERALD_MIRROR)
+ herald_mirror()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(HERALD_TRISHOT)
+ herald_trishot(target)
+ if(my_mirror != null)
+ my_mirror.herald_trishot(target)
+ if(HERALD_DIRECTIONALSHOT)
+ herald_directionalshot()
+ if(my_mirror != null)
+ my_mirror.herald_directionalshot()
+ if(HERALD_TELESHOT)
+ herald_teleshot(target)
+ if(my_mirror != null)
+ my_mirror.herald_teleshot(target)
+ if(HERALD_MIRROR)
+ herald_mirror()
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/shoot_projectile(turf/marker, set_angle, var/is_teleshot)
+ var/turf/startloc = get_turf(src)
+ var/obj/item/projectile/herald/H = null
+ if(!is_teleshot)
+ H = new /obj/item/projectile/herald(startloc)
+ else
+ H = new /obj/item/projectile/herald/teleshot(startloc)
+ H.preparePixelProjectile(marker, startloc)
+ H.firer = src
+ if(target)
+ H.original = target
+ H.fire(set_angle)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_trishot(target)
+ ranged_cooldown = world.time + 30
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ var/target_turf = get_turf(target)
+ var/angle_to_target = Get_Angle(src, target_turf)
+ shoot_projectile(target_turf, angle_to_target, FALSE)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 2)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 4)
+ if(health < maxHealth * 0.5)
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 10)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 12)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 14)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_circleshot()
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ for(var/i in directional_shot_angles)
+ shoot_projectile(get_turf(src), i, FALSE)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/unenrage()
+ if(stat == DEAD || is_mirror)
+ return
+ icon_state = "herald"
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_directionalshot()
+ ranged_cooldown = world.time + 50
+ if(!is_mirror)
+ icon_state = "herald_enraged"
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/herald_circleshot), 5)
+ if(health < maxHealth * 0.5)
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/herald_circleshot), 15)
+ addtimer(CALLBACK(src, .proc/unenrage), 20)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_teleshot(target)
+ ranged_cooldown = world.time + 30
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ var/target_turf = get_turf(target)
+ var/angle_to_target = Get_Angle(src, target_turf)
+ shoot_projectile(target_turf, angle_to_target, TRUE)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_mirror()
+ ranged_cooldown = world.time + 40
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ if(my_mirror != null)
+ qdel(my_mirror)
+ my_mirror = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/new_mirror = new /mob/living/simple_animal/hostile/asteroid/elite/herald/mirror(loc)
+ my_mirror = new_mirror
+ my_mirror.my_master = src
+ my_mirror.faction = faction.Copy()
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror
+ name = "herald's mirror"
+ desc = "This fiendish work of magic copies the herald's attacks. Seems logical to smash it."
+ health = 60
+ maxHealth = 60
+ icon_state = "herald_mirror"
+ deathmessage = "shatters violently!"
+ deathsound = 'sound/effects/glassbr1.ogg'
+ movement_type = FLYING
+ del_on_death = TRUE
+ is_mirror = TRUE
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/my_master = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Initialize()
+ ..()
+ toggle_ai(AI_OFF)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Destroy()
+ if(my_master != null)
+ my_master.my_mirror = null
+ . = ..()
+
+/obj/item/projectile/herald
+ name ="death bolt"
+ icon_state= "chronobolt"
+ damage = 15
+ armour_penetration = 60
+ speed = 2
+ eyeblur = 0
+ damage_type = BRUTE
+ pass_flags = PASSTABLE
+
+/obj/item/projectile/herald/teleshot
+ name ="golden bolt"
+ damage = 0
+ color = rgb(255,255,102)
+
+/obj/item/projectile/herald/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if(ismineralturf(target))
+ var/turf/closed/mineral/M = target
+ M.gets_drilled()
+ return
+ else if(isliving(target))
+ var/mob/living/L = target
+ var/mob/living/F = firer
+ if(F != null && istype(F, /mob/living/simple_animal/hostile/asteroid/elite) && F.faction_check_mob(L))
+ L.heal_overall_damage(damage)
+
+/obj/item/projectile/herald/teleshot/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ firer.forceMove(get_turf(src))
+
+//Herald's loot: Cloak of the Prophet
+
+/obj/item/clothing/neck/cloak/herald_cloak
+ name = "cloak of the prophet"
+ desc = "A cloak which protects you from the heresy of the world."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "herald_cloak"
+ body_parts_covered = CHEST|GROIN|ARMS
+ hit_reaction_chance = 10
+
+/obj/item/clothing/neck/cloak/herald_cloak/proc/reactionshot(mob/living/carbon/owner)
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ for(var/i in directional_shot_angles)
+ shoot_projectile(get_turf(owner), i, owner)
+
+/obj/item/clothing/neck/cloak/herald_cloak/proc/shoot_projectile(turf/marker, set_angle, mob/living/carbon/owner)
+ var/turf/startloc = get_turf(owner)
+ var/obj/item/projectile/herald/H = null
+ H = new /obj/item/projectile/herald(startloc)
+ H.preparePixelProjectile(marker, startloc)
+ H.firer = owner
+ H.fire(set_angle)
+
+/obj/item/clothing/neck/cloak/herald_cloak/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ . = ..()
+ if(rand(1,100) > hit_reaction_chance)
+ return
+ owner.visible_message("[owner]'s [src] emits a loud noise as [owner] is struck!")
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/reactionshot, owner), 10)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
new file mode 100644
index 0000000000..1bc9ea1e4e
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -0,0 +1,303 @@
+#define LEGIONNAIRE_CHARGE 1
+#define HEAD_DETACH 2
+#define BONFIRE_TELEPORT 3
+#define SPEW_SMOKE 4
+
+/**
+ * # Legionnaire
+ *
+ * A towering skeleton, embodying the power of Legion.
+ * As it's health gets lower, the head does more damage.
+ * It's attacks are as follows:
+ * - Charges at the target after a telegraph, throwing them across the arena should it connect.
+ * - Legionnaire's head detaches, attacking as it's own entity. Has abilities of it's own later into the fight. Once dead, regenerates after a brief period. If the skill is used while the head is off, it will be killed.
+ * - Leaves a pile of bones at your location. Upon using this skill again, you'll swap locations with the bone pile.
+ * - Spews a cloud of smoke from it's maw, wherever said maw is.
+ * A unique fight incorporating the head mechanic of legion into a whole new beast. Combatants will need to make sure the tag-team of head and body don't lure them into a deadly trap.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire
+ name = "legionnaire"
+ desc = "A towering skeleton, embodying the terrifying power of Legion."
+ icon_state = "legionnaire"
+ icon_living = "legionnaire"
+ icon_aggro = "legionnaire"
+ icon_dead = "legionnaire_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ attacktext = "slashes its arms at"
+ /*attack_verb_continuous = "slashes its arms at"
+ attack_verb_simple = "slash your arms at"*/
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+ throw_message = "doesn't affect the sturdiness of"
+ speed = 1
+ move_to_delay = 3
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/curse.ogg'
+ deathmessage = "'s arms reach out before it falls apart onto the floor, lifeless."
+ loot_drop = /obj/item/crusher_trophy/legionnaire_spine
+
+ attack_action_types = list(/datum/action/innate/elite_attack/legionnaire_charge,
+ /datum/action/innate/elite_attack/head_detach,
+ /datum/action/innate/elite_attack/bonfire_teleport,
+ /datum/action/innate/elite_attack/spew_smoke)
+
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/myhead = null
+ var/obj/structure/legionnaire_bonfire/mypile = null
+ var/has_head = TRUE
+
+/datum/action/innate/elite_attack/legionnaire_charge
+ name = "Legionnaire Charge"
+ button_icon_state = "legionnaire_charge"
+ chosen_message = "You will attempt to grab your opponent and throw them."
+ chosen_attack_num = LEGIONNAIRE_CHARGE
+
+/datum/action/innate/elite_attack/head_detach
+ name = "Release Head"
+ button_icon_state = "head_detach"
+ chosen_message = "You will now detach your head or kill it if it is already released."
+ chosen_attack_num = HEAD_DETACH
+
+/datum/action/innate/elite_attack/bonfire_teleport
+ name = "Bonfire Teleport"
+ button_icon_state = "bonfire_teleport"
+ chosen_message = "You will leave a bonfire. Second use will let you swap positions with it indefintiely. Using this move on the same tile as your active bonfire removes it."
+ chosen_attack_num = BONFIRE_TELEPORT
+
+/datum/action/innate/elite_attack/spew_smoke
+ name = "Spew Smoke"
+ button_icon_state = "spew_smoke"
+ chosen_message = "Your head will spew smoke in an area, wherever it may be."
+ chosen_attack_num = SPEW_SMOKE
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(LEGIONNAIRE_CHARGE)
+ legionnaire_charge(target)
+ if(HEAD_DETACH)
+ head_detach(target)
+ if(BONFIRE_TELEPORT)
+ bonfire_teleport()
+ if(SPEW_SMOKE)
+ spew_smoke()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(LEGIONNAIRE_CHARGE)
+ legionnaire_charge(target)
+ if(HEAD_DETACH)
+ head_detach(target)
+ if(BONFIRE_TELEPORT)
+ bonfire_teleport()
+ if(SPEW_SMOKE)
+ spew_smoke()
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge(target)
+ ranged_cooldown = world.time + 50
+ var/dir_to_target = get_dir(get_turf(src), get_turf(target))
+ var/turf/T = get_step(get_turf(src), dir_to_target)
+ for(var/i in 1 to 4)
+ new /obj/effect/temp_visual/dragon_swoop/legionnaire(T)
+ T = get_step(T, dir_to_target)
+ playsound(src,'sound/magic/demon_attack1.ogg', 200, 1)
+ visible_message("[src] prepares to charge!")
+ addtimer(CALLBACK(src, .proc/legionnaire_charge_2, dir_to_target, 0), 5)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge_2(var/move_dir, var/times_ran)
+ if(times_ran >= 4)
+ return
+ var/turf/T = get_step(get_turf(src), move_dir)
+ if(ismineralturf(T))
+ var/turf/closed/mineral/M = T
+ M.gets_drilled()
+ if(T.density)
+ return
+ for(var/obj/structure/window/W in T.contents)
+ return
+ for(var/obj/machinery/door/D in T.contents)
+ return
+ forceMove(T)
+ playsound(src,'sound/effects/bang.ogg', 200, 1)
+ var/list/hit_things = list()
+ var/throwtarget = get_edge_target_turf(src, move_dir)
+ for(var/mob/living/L in T.contents - hit_things - src)
+ if(faction_check_mob(L))
+ return
+ hit_things += L
+ visible_message("[src] attacks [L] with much force!")
+ to_chat(L, "[src] grabs you and throws you with much force!")
+ L.safe_throw_at(throwtarget, 10, 1, src)
+ //L.Paralyze(20)
+ L.Stun(20) //substituting this for the Paralyze from the line above, because we don't have tg paralysis stuff
+ L.adjustBruteLoss(50)
+ addtimer(CALLBACK(src, .proc/legionnaire_charge_2, move_dir, (times_ran + 1)), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/head_detach(target)
+ ranged_cooldown = world.time + 10
+ if(myhead != null)
+ myhead.adjustBruteLoss(600)
+ return
+ if(has_head)
+ has_head = FALSE
+ icon_state = "legionnaire_headless"
+ icon_living = "legionnaire_headless"
+ icon_aggro = "legionnaire_headless"
+ visible_message("[src]'s head flies off!")
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/newhead = new /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead(loc)
+ newhead.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
+ newhead.GiveTarget(target)
+ newhead.faction = faction.Copy()
+ myhead = newhead
+ myhead.body = src
+ if(health < maxHealth * 0.25)
+ myhead.melee_damage_lower = 30
+ myhead.melee_damage_upper = 30
+ else if(health < maxHealth * 0.5)
+ myhead.melee_damage_lower = 20
+ myhead.melee_damage_upper = 20
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/onHeadDeath()
+ myhead = null
+ addtimer(CALLBACK(src, .proc/regain_head), 50)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/regain_head()
+ has_head = TRUE
+ if(stat == DEAD)
+ return
+ icon_state = "legionnaire"
+ icon_living = "legionnaire"
+ icon_aggro = "legionnaire"
+ visible_message("The top of [src]'s spine leaks a black liquid, forming into a skull!")
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/bonfire_teleport()
+ ranged_cooldown = world.time + 5
+ if(mypile == null)
+ var/obj/structure/legionnaire_bonfire/newpile = new /obj/structure/legionnaire_bonfire(loc)
+ mypile = newpile
+ mypile.myowner = src
+ playsound(get_turf(src),'sound/items/fulext_deploy.wav', 200, 1)
+ visible_message("[src] summons a bonfire on [get_turf(src)]!")
+ return
+ else
+ var/turf/legionturf = get_turf(src)
+ var/turf/pileturf = get_turf(mypile)
+ if(legionturf == pileturf)
+ mypile.take_damage(100)
+ mypile = null
+ return
+ playsound(pileturf,'sound/items/fulext_deploy.wav', 200, 1)
+ playsound(legionturf,'sound/items/fulext_deploy.wav', 200, 1)
+ visible_message("[src] melts down into a burning pile of bones!")
+ forceMove(pileturf)
+ visible_message("[src] forms from the bonfire!")
+ mypile.forceMove(legionturf)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/spew_smoke()
+ ranged_cooldown = world.time + 60
+ var/turf/T = null
+ if(myhead != null)
+ T = get_turf(myhead)
+ else
+ T = get_turf(src)
+ if(myhead != null)
+ myhead.visible_message("[myhead] spews smoke from its maw!")
+ else if(!has_head)
+ visible_message("[src] spews smoke from the tip of their spine!")
+ else
+ visible_message("[src] spews smoke from its maw!")
+ var/datum/effect_system/smoke_spread/smoke = new
+ smoke.set_up(2, T)
+ smoke.start()
+
+//The legionnaire's head. Basically the same as any legion head, but we have to tell our creator when we die so they can generate another head.
+/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead
+ name = "legionnaire head"
+ desc = "The legionnaire's head floating by itself. One shouldn't get too close, though once it sees you, you really don't have a choice."
+ icon_state = "legionnaire_head"
+ icon_living = "legionnaire_head"
+ icon_aggro = "legionnaire_head"
+ icon_dead = "legionnaire_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 80
+ health = 80
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ attacktext = "bites at"
+ /*attack_verb_continuous = "bites at"
+ attack_verb_simple = "bite at"*/
+ attack_sound = 'sound/effects/curse1.ogg'
+ throw_message = "simply misses"
+ speed = 0
+ move_to_delay = 2
+ del_on_death = 1
+ deathmessage = "crumbles away!"
+ faction = list()
+ ranged = FALSE
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/body = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/death()
+ . = ..()
+ if(body)
+ body.onHeadDeath()
+
+//The legionnaire's bonfire, which can be swapped positions with. Also sets flammable living beings on fire when they walk over it.
+/obj/structure/legionnaire_bonfire
+ name = "bone pile"
+ desc = "A pile of bones which seems to occasionally move a little. It's probably a good idea to smash them."
+ icon = 'icons/obj/lavaland/legionnaire_bonfire.dmi'
+ icon_state = "bonfire"
+ max_integrity = 100
+ //move_resist = MOVE_FORCE_EXTREMELY_STRONG
+ anchored = TRUE
+ density = FALSE
+ light_range = 4
+ light_color = LIGHT_COLOR_RED
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/myowner = null
+
+
+/obj/structure/legionnaire_bonfire/Entered(atom/movable/mover, turf/target)
+ if(isliving(mover))
+ var/mob/living/L = mover
+ L.adjust_fire_stacks(3)
+ L.IgniteMob()
+ . = ..()
+
+/obj/structure/legionnaire_bonfire/Destroy()
+ if(myowner != null)
+ myowner.mypile = null
+ . = ..()
+
+//The visual effect which appears in front of legionnaire when he goes to charge.
+/obj/effect/temp_visual/dragon_swoop/legionnaire
+ duration = 10
+ color = rgb(0,0,0)
+
+/obj/effect/temp_visual/dragon_swoop/legionnaire/Initialize()
+ . = ..()
+ transform *= 0.33
+
+// Legionnaire's loot: Legionnaire Spine
+
+/obj/item/crusher_trophy/legionnaire_spine
+ name = "legionnaire spine"
+ desc = "The spine of a legionnaire. It almost feels like it's moving..."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "legionnaire_spine"
+ denied_type = /obj/item/crusher_trophy/legionnaire_spine
+ bonus_value = 20
+
+/obj/item/crusher_trophy/legionnaire_spine/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a loyal legion skull"
+
+/obj/item/crusher_trophy/legionnaire_spine/on_mark_detonation(mob/living/target, mob/living/user)
+ if(!rand(1, 100) <= bonus_value || target.stat == DEAD)
+ return
+ var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion(user.loc)
+ A.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
+ A.GiveTarget(target)
+ A.friends = user
+ A.faction = user.faction.Copy()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
new file mode 100644
index 0000000000..540470d505
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
@@ -0,0 +1,193 @@
+#define SINGULAR_SHOT 1
+#define MAGIC_BOX 2
+#define PANDORA_TELEPORT 3
+#define AOE_SQUARES 4
+
+/**
+ * # Pandora
+ *
+ * A box with a similar design to the Hierophant which trades large, single attacks for more frequent smaller ones.
+ * As it's health gets lower, the time between it's attacks decrease.
+ * It's attacks are as follows:
+ * - Fires hierophant blasts in a straight line. Can only fire in a straight line in 8 directions, being the diagonals and cardinals.
+ * - Creates a box of hierophant blasts around the target. If they try to run away to avoid it, they'll very likely get hit.
+ * - Teleports the pandora from one location to another, almost identical to Hierophant.
+ * - Spawns a 5x5 AOE at the location of choice, spreading out from the center.
+ * Pandora's fight mirrors Hierophant's closely, but has stark differences in attack effects. Instead of long-winded dodge times and long cooldowns, Pandora constantly attacks the opponent, but leaves itself open for attack.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora
+ name = "pandora"
+ desc = "A large magic box with similar power and design to the Hierophant. Once it opens, it's not easy to close it."
+ icon_state = "pandora"
+ icon_living = "pandora"
+ icon_aggro = "pandora"
+ icon_dead = "pandora_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ attacktext = "smashes into the side of"
+ /*attack_verb_continuous = "smashes into the side of"
+ attack_verb_simple = "smash into the side of"*/
+ attack_sound = 'sound/weapons/sonic_jackhammer.ogg'
+ throw_message = "merely dinks off of the"
+ speed = 4
+ move_to_delay = 10
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/repulse.ogg'
+ deathmessage = "'s lights flicker, before its top part falls down."
+ loot_drop = /obj/item/clothing/accessory/pandora_hope
+
+ attack_action_types = list(/datum/action/innate/elite_attack/singular_shot,
+ /datum/action/innate/elite_attack/magic_box,
+ /datum/action/innate/elite_attack/pandora_teleport,
+ /datum/action/innate/elite_attack/aoe_squares)
+
+ var/sing_shot_length = 8
+ var/cooldown_time = 20
+
+/datum/action/innate/elite_attack/singular_shot
+ name = "Singular Shot"
+ button_icon_state = "singular_shot"
+ chosen_message = "You are now creating a single linear magic square."
+ chosen_attack_num = SINGULAR_SHOT
+
+/datum/action/innate/elite_attack/magic_box
+ name = "Magic Box"
+ button_icon_state = "magic_box"
+ chosen_message = "You are now attacking with a box of magic squares."
+ chosen_attack_num = MAGIC_BOX
+
+/datum/action/innate/elite_attack/pandora_teleport
+ name = "Line Teleport"
+ button_icon_state = "pandora_teleport"
+ chosen_message = "You will now teleport to your target."
+ chosen_attack_num = PANDORA_TELEPORT
+
+/datum/action/innate/elite_attack/aoe_squares
+ name = "AOE Blast"
+ button_icon_state = "aoe_squares"
+ chosen_message = "Your attacks will spawn an AOE blast at your target location."
+ chosen_attack_num = AOE_SQUARES
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(SINGULAR_SHOT)
+ singular_shot(target)
+ if(MAGIC_BOX)
+ magic_box(target)
+ if(PANDORA_TELEPORT)
+ pandora_teleport(target)
+ if(AOE_SQUARES)
+ aoe_squares(target)
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(SINGULAR_SHOT)
+ singular_shot(target)
+ if(MAGIC_BOX)
+ magic_box(target)
+ if(PANDORA_TELEPORT)
+ pandora_teleport(target)
+ if(AOE_SQUARES)
+ aoe_squares(target)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/Life()
+ . = ..()
+ if(health >= maxHealth * 0.5)
+ cooldown_time = 20
+ return
+ if(health < maxHealth * 0.5 && health > maxHealth * 0.25)
+ cooldown_time = 15
+ return
+ else
+ cooldown_time = 10
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/singular_shot(target)
+ ranged_cooldown = world.time + (cooldown_time * 0.5)
+ var/dir_to_target = get_dir(get_turf(src), get_turf(target))
+ var/turf/T = get_step(get_turf(src), dir_to_target)
+ singular_shot_line(sing_shot_length, dir_to_target, T)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/singular_shot_line(var/procsleft, var/angleused, var/turf/T)
+ if(procsleft <= 0)
+ return
+ new /obj/effect/temp_visual/hierophant/blast/pandora(T, src)
+ T = get_step(T, angleused)
+ procsleft = procsleft - 1
+ addtimer(CALLBACK(src, .proc/singular_shot_line, procsleft, angleused, T), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/magic_box(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ for(var/t in spiral_range_turfs(3, T))
+ if(get_dist(t, T) > 1)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ var/turf/source = get_turf(src)
+ new /obj/effect/temp_visual/hierophant/telegraph(T, src)
+ new /obj/effect/temp_visual/hierophant/telegraph(source, src)
+ playsound(source,'sound/machines/airlockopen.ogg', 200, 1)
+ addtimer(CALLBACK(src, .proc/pandora_teleport_2, T, source), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport_2(var/turf/T, var/turf/source)
+ new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, src)
+ new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, src)
+ for(var/t in RANGE_TURFS(1, T))
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ for(var/t in RANGE_TURFS(1, source))
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out
+ visible_message("[src] fades out!")
+ density = FALSE
+ addtimer(CALLBACK(src, .proc/pandora_teleport_3, T), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport_3(var/turf/T)
+ forceMove(T)
+ animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN
+ density = TRUE
+ visible_message("[src] fades in!")
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/aoe_squares(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(T, src)
+ var/max_size = 2
+ addtimer(CALLBACK(src, .proc/aoe_squares_2, T, 0, max_size), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/aoe_squares_2(var/turf/T, var/ring, var/max_size)
+ if(ring > max_size)
+ return
+ for(var/t in spiral_range_turfs(ring, T))
+ if(get_dist(t, T) == ring)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ addtimer(CALLBACK(src, .proc/aoe_squares_2, T, (ring + 1), max_size), 2)
+
+//The specific version of hiero's squares pandora uses
+/obj/effect/temp_visual/hierophant/blast/pandora
+ damage = 20
+ monster_damage_boost = FALSE
+
+//Pandora's loot: Hope
+/obj/item/clothing/accessory/pandora_hope
+ name = "Hope"
+ desc = "Found at the bottom of Pandora. After all the evil was released, this was the only thing left inside."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "hope"
+ resistance_flags = FIRE_PROOF
+
+/obj/item/clothing/accessory/pandora_hope/on_uniform_equip(obj/item/clothing/under/U, user)
+ var/mob/living/L = user
+ if(L && L.mind)
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "hope_lavaland", /datum/mood_event/hope_lavaland)
+
+/obj/item/clothing/accessory/pandora_hope/on_uniform_dropped(obj/item/clothing/under/U, user)
+ var/mob/living/L = user
+ if(L && L.mind)
+ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "hope_lavaland")
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 0880f7f432..facc86da46 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -140,24 +140,28 @@
/////////////////////////////////// DISABILITIES ////////////////////////////////////
-/mob/living/proc/add_quirk(quirk, spawn_effects) //separate proc due to the way these ones are handled
- if(HAS_TRAIT(src, quirk))
+/mob/living/proc/add_quirk(quirktype, spawn_effects) //separate proc due to the way these ones are handled
+ if(has_quirk(quirktype))
return
- if(!SSquirks || !SSquirks.quirks[quirk])
+ var/datum/quirk/T = quirktype
+ var/qname = initial(T.name)
+ if(!SSquirks || !SSquirks.quirks[qname])
return
- var/datum/quirk/T = SSquirks.quirks[quirk]
- new T (src, spawn_effects)
+ new quirktype (src, spawn_effects)
return TRUE
-/mob/living/proc/remove_quirk(quirk)
- var/datum/quirk/T = roundstart_quirks[quirk]
- if(T)
- qdel(T)
- return TRUE
-
-/mob/living/proc/has_quirk(quirk)
- return roundstart_quirks[quirk]
+/mob/living/proc/remove_quirk(quirktype)
+ for(var/datum/quirk/Q in roundstart_quirks)
+ if(Q.type == quirktype)
+ qdel(Q)
+ return TRUE
+ return FALSE
+/mob/living/proc/has_quirk(quirktype)
+ for(var/datum/quirk/Q in roundstart_quirks)
+ if(Q.type == quirktype)
+ return TRUE
+ return FALSE
/////////////////////////////////// TRAIT PROCS ////////////////////////////////////
/mob/living/proc/cure_blind(list/sources)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index d4528fb91f..a47ea9d5a0 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -465,9 +465,10 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA
/mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE)
if(!ckey || !new_mob)
CRASH("transfer_ckey() called [ckey ? "" : "on a ckey-less mob[new_mob ? "" : " and "]"][new_mob ? "" : "without a valid mob target"]!")
+ SEND_SIGNAL(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, new_mob, src)
+ new_mob.ckey = ckey
if(send_signal)
SEND_SIGNAL(src, COMSIG_MOB_KEY_CHANGE, new_mob, src)
- new_mob.ckey = ckey
return TRUE
/mob/verb/cancel_camera()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 0cb886f11b..a9f4c94397 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -99,6 +99,12 @@
var/obj/control_object //Used by admins to possess objects. All mobs should have this var
var/atom/movable/remote_control //Calls relaymove() to whatever it is
+ /**
+ * The sound made on death
+ *
+ * leave null for no sound. used for *deathgasp
+ */
+ var/deathsound = null
var/turf/listed_turf = null //the current turf being examined in the stat panel
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 47cbbff418..8428467a87 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -41,6 +41,13 @@
/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0)
+ if (get_dist(src,user) > 1)
+ if (iscarbon(user))
+ var/mob/living/carbon/H = user
+ if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
+ return FALSE
+ else
+ return FALSE
if(!stored_card)
to_chat(user, "There is no card in \the [src].")
return FALSE
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index c68e1ad119..e4bc45dbc5 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -73,6 +73,13 @@
/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0)
+ if (get_dist(src,user) > 1)
+ if (iscarbon(user))
+ var/mob/living/carbon/H = user
+ if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
+ return FALSE
+ else
+ return FALSE
if(!stored_card && !stored_card2)
to_chat(user, "There are no cards in \the [src].")
return FALSE
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 5da29bf5ac..2652026a11 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -1113,9 +1113,9 @@
if(terminal && terminal.powernet)
terminal.add_load(amount)
-/obj/machinery/power/apc/avail()
+/obj/machinery/power/apc/avail(amount)
if(terminal)
- return terminal.avail()
+ return terminal.avail(amount)
else
return 0
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index a3dc6e7394..cc03976f79 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -217,9 +217,9 @@ By design, d1 is the smallest direction and d2 is the highest
else
return 0
-/obj/structure/cable/proc/avail()
+/obj/structure/cable/proc/avail(amount)
if(powernet)
- return powernet.avail
+ return amount ? powernet.avail >= amount : powernet.avail
else
return 0
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index 58a259de3a..d2d3d60066 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -46,9 +46,9 @@
else
return 0
-/obj/machinery/power/proc/avail()
+/obj/machinery/power/proc/avail(amount)
if(powernet)
- return powernet.avail
+ return amount ? powernet.avail >= amount : powernet.avail
else
return 0
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index b1b0d2d718..054b91f273 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -110,7 +110,7 @@
if(!user.transferItemToLoc(W, src))
return
loaded_tank = W
- update_icons()
+ update_icon()
else if(W.GetID())
if(allowed(user))
if(active)
@@ -197,14 +197,14 @@
if(active)
toggle_power()
else
- update_icons()
+ update_icon()
/obj/machinery/power/rad_collector/rad_act(pulse_strength)
. = ..()
if(loaded_tank && active && pulse_strength > RAD_COLLECTOR_EFFICIENCY)
stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT
-/obj/machinery/power/rad_collector/proc/update_icons()
+/obj/machinery/power/rad_collector/update_icon()
cut_overlays()
if(loaded_tank)
add_overlay("ptank")
@@ -222,7 +222,7 @@
else
icon_state = "ca"
flick("ca_deactive", src)
- update_icons()
+ update_icon()
return
#undef RAD_COLLECTOR_EFFICIENCY
diff --git a/code/modules/projectiles/guns/misc/syringe_gun.dm b/code/modules/projectiles/guns/misc/syringe_gun.dm
index 8a9d1c5b6b..d947e3155d 100644
--- a/code/modules/projectiles/guns/misc/syringe_gun.dm
+++ b/code/modules/projectiles/guns/misc/syringe_gun.dm
@@ -151,3 +151,17 @@
max_syringes = 1
desc = "[initial(desc)] It has a [B] strapped to it, but it doesn't seem to be doing anything."
..()
+
+/obj/item/gun/syringe/blowgun
+ name = "blowgun"
+ desc = "Fire syringes at a short distance."
+ icon_state = "blowgun"
+ item_state = "blowgun"
+ fire_sound = 'sound/items/syringeproj.ogg'
+
+/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ visible_message("[user] starts aiming with a blowgun!")
+ if(do_after(user, 25, target = src))
+ user.adjustStaminaLoss(20)
+ user.adjustOxyLoss(20)
+ ..()
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index a3dd3c8049..c6aa51deda 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -161,7 +161,7 @@
. = ..()
if(A == beaker)
beaker = null
- cut_overlays()
+ update_icon()
/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 06e33ddf9f..1ac62ba651 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -58,14 +58,14 @@
if(bottle)
bottle.ex_act(severity, target)
-/obj/machinery/chem_master/handle_atom_del(atom/A)
- ..()
+/obj/machinery/chem_master/Exited(atom/movable/A, atom/newloc)
+ . = ..()
if(A == beaker)
beaker = null
- reagents.clear_reagents()
update_icon()
- else if(A == bottle)
+ if(A == bottle)
bottle = null
+ update_icon()
/obj/machinery/chem_master/update_icon()
cut_overlays()
@@ -103,6 +103,10 @@
updateUsrDialog()
update_icon()
else if(!condi && istype(I, /obj/item/storage/pill_bottle))
+ . = TRUE // no afterattack
+ if(panel_open)
+ to_chat(user, "You can't use the [src.name] while its panel is opened!")
+ return
if(!user.transferItemToLoc(I, src))
return
replace_pillbottle(user, I)
@@ -115,38 +119,37 @@
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- replace_beaker(user)
+ if(beaker)
+ replace_beaker(user)
+ else if(bottle)
+ replace_pillbottle(user)
return TRUE
/obj/machinery/chem_master/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(beaker)
- beaker.forceMove(drop_location())
+ var/obj/item/reagent_containers/B = beaker
+ B.forceMove(drop_location())
if(user && Adjacent(user) && !issiliconoradminghost(user))
- user.put_in_hands(beaker)
+ user.put_in_hands(B)
if(new_beaker)
beaker = new_beaker
- else
- beaker = null
update_icon()
- return TRUE
/obj/machinery/chem_master/proc/replace_pillbottle(mob/living/user, obj/item/storage/pill_bottle/new_bottle)
if(bottle)
- bottle.forceMove(drop_location())
+ var/obj/item/storage/pill_bottle/B = bottle
+ B.forceMove(drop_location())
if(user && Adjacent(user) && !issiliconoradminghost(user))
- user.put_in_hands(beaker)
+ user.put_in_hands(B)
else
- adjust_item_drop_location(bottle)
+ adjust_item_drop_location(B)
if(new_bottle)
bottle = new_bottle
- else
- bottle = null
- update_icon()
- return TRUE
/obj/machinery/chem_master/on_deconstruction()
- replace_beaker(usr)
- replace_pillbottle(usr)
+ var/atom/A = drop_location()
+ beaker.forceMove(A)
+ bottle.forceMove(A)
return ..()
/obj/machinery/chem_master/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
@@ -261,15 +264,16 @@
if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
return
var/obj/item/reagent_containers/pill/P
- var/target_loc = bottle ? bottle : drop_location()
+ var/target_loc = drop_location()
var/drop_threshold = INFINITY
if(bottle)
var/datum/component/storage/STRB = bottle.GetComponent(/datum/component/storage)
if(STRB)
drop_threshold = STRB.max_items - bottle.contents.len
+ target_loc = bottle
for(var/i in 1 to amount)
- if(i < drop_threshold)
+ if(i <= drop_threshold)
P = new(target_loc)
else
P = new(drop_location())
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index eceb1cdd29..512b8a21a1 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1,5 +1,5 @@
/datum/reagent/blood
- data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_HUMAN, "blood_type"= null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null)
+ data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_HUMAN, "blood_type"= null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
name = "Blood"
id = "blood"
value = 1
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index eed1e4aefd..7ccdb76dd9 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -1043,14 +1043,21 @@
display_name = "Alien Technology"
description = "Things used by the greys."
prereq_ids = list("biotech","engineering")
- boost_item_paths = list(/obj/item/gun/energy/alien, /obj/item/scalpel/alien, /obj/item/hemostat/alien, /obj/item/retractor/alien, /obj/item/circular_saw/alien,
- /obj/item/cautery/alien, /obj/item/surgicaldrill/alien, /obj/item/screwdriver/abductor, /obj/item/wrench/abductor, /obj/item/crowbar/abductor, /obj/item/multitool/abductor, /obj/item/stock_parts/cell/infinite/abductor,
- /obj/item/weldingtool/abductor, /obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/abductor_baton, /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
export_price = 20000
hidden = TRUE
design_ids = list("alienalloy")
+/datum/techweb_node/alientech/New()
+ . = ..()
+ boost_item_paths = typesof(/obj/item/gun/energy/alien, /obj/item/scalpel/alien, /obj/item/hemostat/alien,
+ /obj/item/retractor/alien, /obj/item/circular_saw/alien, /obj/item/cautery/alien,
+ /obj/item/surgicaldrill/alien, /obj/item/screwdriver/abductor, /obj/item/wrench/abductor,
+ /obj/item/crowbar/abductor, /obj/item/multitool/abductor,
+ /obj/item/stock_parts/cell/infinite/abductor, /obj/item/weldingtool/abductor,
+ /obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor,
+ /obj/item/abductor_baton, /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor)
+
/datum/techweb_node/alien_bio
id = "alien_bio"
display_name = "Alien Biological Tools"
diff --git a/html/changelogs/AutoChangeLog-pr-10026.yml b/html/changelogs/AutoChangeLog-pr-10026.yml
new file mode 100644
index 0000000000..6e8062874b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10026.yml
@@ -0,0 +1,4 @@
+author: "Linzolle"
+delete-after: True
+changes:
+ - rscadd: "neck slice. harm intent someone's head while they are unconscious or in a neck grab to make them bleed uncontrollably."
diff --git a/html/changelogs/AutoChangeLog-pr-10072.yml b/html/changelogs/AutoChangeLog-pr-10072.yml
new file mode 100644
index 0000000000..9a6f45e61e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10072.yml
@@ -0,0 +1,5 @@
+author: "Useroth"
+delete-after: True
+changes:
+ - rscadd: "bamboo which can be used to build punji sticks/ blowguns available as a sugarcane mutation or in exotic seed crate"
+ - tweak: "changed the sugar cane growth stages because fuck if I know why, but it was in the PR"
diff --git a/html/changelogs/AutoChangeLog-pr-10076.yml b/html/changelogs/AutoChangeLog-pr-10076.yml
new file mode 100644
index 0000000000..0e04ceddbc
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10076.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixed alien tech node not being unlockable with subtypes of the accepted items."
diff --git a/html/changelogs/AutoChangeLog-pr-10111.yml b/html/changelogs/AutoChangeLog-pr-10111.yml
new file mode 100644
index 0000000000..0a80f01eef
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10111.yml
@@ -0,0 +1,4 @@
+author: "kappa-sama"
+delete-after: True
+changes:
+ - bugfix: "you can now strip people while aggrograbbing or higher"
diff --git a/html/changelogs/AutoChangeLog-pr-10115.yml b/html/changelogs/AutoChangeLog-pr-10115.yml
new file mode 100644
index 0000000000..f7a60e4911
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10115.yml
@@ -0,0 +1,5 @@
+author: "Useroth"
+delete-after: True
+changes:
+ - rscadd: "New lavaland ruin: Pulsating tumor"
+ - rscadd: "New class of lavaland mobs, a bit weaker than megafauna but still stronger than most of what you normally see"
diff --git a/html/changelogs/AutoChangeLog-pr-10153.yml b/html/changelogs/AutoChangeLog-pr-10153.yml
new file mode 100644
index 0000000000..7196583de9
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10153.yml
@@ -0,0 +1,4 @@
+author: "Linzolle"
+delete-after: True
+changes:
+ - bugfix: "fireman failure has a different message depending on the circumstance"
diff --git a/html/changelogs/AutoChangeLog-pr-10155.yml b/html/changelogs/AutoChangeLog-pr-10155.yml
new file mode 100644
index 0000000000..d82ea5ed43
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10155.yml
@@ -0,0 +1,4 @@
+author: "Seris02"
+delete-after: True
+changes:
+ - bugfix: "distance checks"
diff --git a/html/changelogs/AutoChangeLog-pr-10157.yml b/html/changelogs/AutoChangeLog-pr-10157.yml
new file mode 100644
index 0000000000..6ec495b7fc
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10157.yml
@@ -0,0 +1,5 @@
+author: "ShizCalev"
+delete-after: True
+changes:
+ - bugfix: "Fixed floodlights not turning off properly when they're underpowered."
+ - bugfix: "Fixed emitters not changing icons properly when they're underpowered."
diff --git a/html/changelogs/AutoChangeLog-pr-10172.yml b/html/changelogs/AutoChangeLog-pr-10172.yml
new file mode 100644
index 0000000000..ce76e04d13
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10172.yml
@@ -0,0 +1,11 @@
+author: "Mickyan, nemvar, RaveRadbury, AnturK, SpaceManiac"
+delete-after: True
+changes:
+ - bugfix: "Certain incompatible quirks can no longer be taken together."
+ - bugfix: "If an admin sends a ghost back to the lobby, they can now choose a different set of quirks."
+ - spellcheck: "the quirk menu went through some minor formatting changes."
+ - bugfix: "Podcloning now lets you keep your quirks."
+ - rscadd: "Quirks have flavor text in medical records."
+ - spellcheck: "All quirk medical records refer to \"Patient\", removing a few instances of \"Subject\"."
+ - tweak: "Quirks no longer apply to off-station roundstart antagonists."
+ - code_imp: "Mood quirks are now only processed by the quirk holders"
diff --git a/html/changelogs/AutoChangeLog-pr-10180.yml b/html/changelogs/AutoChangeLog-pr-10180.yml
new file mode 100644
index 0000000000..0a22910940
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10180.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixes a ghostchat eavesdropping exploit concerning VR."
+ - bugfix: "Fixes VR deaths being broadcasted in deadchat."
diff --git a/html/changelogs/AutoChangeLog-pr-10181.yml b/html/changelogs/AutoChangeLog-pr-10181.yml
new file mode 100644
index 0000000000..bb00ab1012
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10181.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixed a few pill bottle issues with the ChemMaster."
diff --git a/html/changelogs/AutoChangeLog-pr-10203.yml b/html/changelogs/AutoChangeLog-pr-10203.yml
new file mode 100644
index 0000000000..7cb4df817b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10203.yml
@@ -0,0 +1,5 @@
+author: "DeltaFire15"
+delete-after: True
+changes:
+ - tweak: "changed mecha internals access for some special mechs."
+ - tweak: "no more mech maintenance access for engineers."
diff --git a/html/changelogs/AutoChangeLog-pr-10216.yml b/html/changelogs/AutoChangeLog-pr-10216.yml
new file mode 100644
index 0000000000..69daa62448
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10216.yml
@@ -0,0 +1,8 @@
+author: "Nervere and subject217, Militaires, py01, nemvar"
+delete-after: True
+changes:
+ - balance: "The cook's CQC now only works when in the kitchen or the kitchen backroom."
+ - spellcheck: "corrected CQC help instructions"
+ - bugfix: "CQC and Sleeping Carp are properly logged."
+ - tweak: "CQC can passively grab targets when not on grab intent. Passive grabs do not count towards combos for CQC or Sleeping carp."
+ - code_imp: "Martial Art and NOGUN cleanup."
diff --git a/html/changelogs/AutoChangeLog-pr-10225.yml b/html/changelogs/AutoChangeLog-pr-10225.yml
new file mode 100644
index 0000000000..964cc68223
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10225.yml
@@ -0,0 +1,5 @@
+author: "BlueWildrose"
+delete-after: True
+changes:
+ - bugfix: "Fixed stargazers being unable to link to themselves if mindshielded or if holding psionic shielding devices (tinfoil hats) when the species is set."
+ - bugfix: "Fixes non-roundstart slimes being unable to wag their tail."
diff --git a/icons/mob/accessories.dmi b/icons/mob/accessories.dmi
index 68f13c8875..cda7cca404 100644
Binary files a/icons/mob/accessories.dmi and b/icons/mob/accessories.dmi differ
diff --git a/icons/mob/actions/actions_elites.dmi b/icons/mob/actions/actions_elites.dmi
new file mode 100644
index 0000000000..335261b0f6
Binary files /dev/null and b/icons/mob/actions/actions_elites.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index 8978d17237..ea185c5ad7 100644
Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi
index 3f8a876d43..1d380e34bc 100644
Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ
diff --git a/icons/mob/lavaland/lavaland_elites.dmi b/icons/mob/lavaland/lavaland_elites.dmi
new file mode 100644
index 0000000000..69032735d9
Binary files /dev/null and b/icons/mob/lavaland/lavaland_elites.dmi differ
diff --git a/icons/mob/neck.dmi b/icons/mob/neck.dmi
index 5eb270d23f..de59a136d9 100644
Binary files a/icons/mob/neck.dmi and b/icons/mob/neck.dmi differ
diff --git a/icons/mob/screen_elite.dmi b/icons/mob/screen_elite.dmi
new file mode 100644
index 0000000000..f407fb79e4
Binary files /dev/null and b/icons/mob/screen_elite.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 6d5365f7fa..24ec5797d4 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi
index dd4d1e1f93..37adf54711 100644
Binary files a/icons/obj/hydroponics/equipment.dmi and b/icons/obj/hydroponics/equipment.dmi differ
diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi
index 45e73c9281..469b1e1aff 100644
Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ
diff --git a/icons/obj/hydroponics/harvest.dmi b/icons/obj/hydroponics/harvest.dmi
index 9d4eefc3bb..a57719fb3a 100644
Binary files a/icons/obj/hydroponics/harvest.dmi and b/icons/obj/hydroponics/harvest.dmi differ
diff --git a/icons/obj/hydroponics/seeds.dmi b/icons/obj/hydroponics/seeds.dmi
index d8fcaa6258..8695d03b3c 100644
Binary files a/icons/obj/hydroponics/seeds.dmi and b/icons/obj/hydroponics/seeds.dmi differ
diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi
index 7f11ba29d4..7ae1ed5a0e 100644
Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ
diff --git a/icons/obj/lavaland/elite_trophies.dmi b/icons/obj/lavaland/elite_trophies.dmi
new file mode 100644
index 0000000000..d194c93853
Binary files /dev/null and b/icons/obj/lavaland/elite_trophies.dmi differ
diff --git a/icons/obj/lavaland/legionnaire_bonfire.dmi b/icons/obj/lavaland/legionnaire_bonfire.dmi
new file mode 100644
index 0000000000..aed00ed001
Binary files /dev/null and b/icons/obj/lavaland/legionnaire_bonfire.dmi differ
diff --git a/icons/obj/lavaland/tumor.dmi b/icons/obj/lavaland/tumor.dmi
new file mode 100644
index 0000000000..a41224c823
Binary files /dev/null and b/icons/obj/lavaland/tumor.dmi differ
diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi
index 7cb212384a..a80dc92b9a 100644
Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ
diff --git a/modular_citadel/code/modules/arousal/organs/breasts.dm b/modular_citadel/code/modules/arousal/organs/breasts.dm
index a82d02703d..f4ba95d830 100644
--- a/modular_citadel/code/modules/arousal/organs/breasts.dm
+++ b/modular_citadel/code/modules/arousal/organs/breasts.dm
@@ -119,7 +119,7 @@
shape = D.features["breasts_shape"]
fluid_id = D.features["breasts_fluid"]
if(!D.features["breasts_producing"])
- DISABLE_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION)
+ DISABLE_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH)
if(!isnum(size))
cached_size = breast_values[size]
else
diff --git a/sound/effects/neovgre_exploding.ogg b/sound/effects/neovgre_exploding.ogg
new file mode 100644
index 0000000000..6ca2db05c3
Binary files /dev/null and b/sound/effects/neovgre_exploding.ogg differ
diff --git a/sound/magic/curse.ogg b/sound/magic/curse.ogg
new file mode 100644
index 0000000000..bda610416d
Binary files /dev/null and b/sound/magic/curse.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index b1e984ab26..e13584b7e3 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -201,6 +201,7 @@
#include "code\_onclick\hud\hud.dm"
#include "code\_onclick\hud\hud_cit.dm"
#include "code\_onclick\hud\human.dm"
+#include "code\_onclick\hud\lavaland_elite.dm"
#include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\parallax.dm"
@@ -2339,6 +2340,11 @@
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\necropolis_tendril.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\elite.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\goliath_broodmother.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\herald.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\legionnaire.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\bat.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\frog.dm"