diff --git a/SQL/updates/53-54.sql b/SQL/updates/53-54.sql
index 303de0b32be3..c2fb1988a299 100644
--- a/SQL/updates/53-54.sql
+++ b/SQL/updates/53-54.sql
@@ -2,4 +2,4 @@
#Add a choice for what type of brain borgs want to have
ALTER TABLE `characters`
- ADD COLUMN `cyborg_brain_type` VARCHAR(11) NOT NULL DEFAULT 'MMI' AFTER `height`;
+ ADD COLUMN `cyborg_brain_type` ENUM('MMI', 'Robobrain', 'Positronic') NOT NULL DEFAULT 'MMI' AFTER `height`;
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
index cd0277ea77cc..ce511619893d 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
@@ -551,7 +551,6 @@
info = "Nothing of interest to report.";
name = "november report"
},
-/obj/item/pen,
/obj/item/tape,
/obj/item/radio/intercom{
freerange = 1;
@@ -559,7 +558,7 @@
name = "intercom"
},
/obj/item/paper_bin,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/effect/decal/cleanable/dirt,
/turf/simulated/floor/plasteel/dark,
/area/ruin/space/syndicate_listening_station)
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
index 52cb532a602d..b38b995bef2f 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
@@ -523,7 +523,7 @@
/obj/item/paper_bin{
pixel_x = -6
},
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/mineral/plastitanium,
/area/ruin/space/mech_transport)
"MA" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm b/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
index 7fcf54e36ba7..349e4181c28b 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
@@ -5200,6 +5200,7 @@
},
/obj/item/coin/antagtoken/syndicate,
/obj/structure/table,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plating/asteroid/ancient,
/area/ruin/space/moonbase19)
"rk" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
index 83a335cc9b13..643d69228c37 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
@@ -98,11 +98,11 @@
pixel_x = 4
},
/obj/item/paper/syndicate_druglab,
-/obj/item/pen,
/obj/item/flashlight/lamp/green/off{
pixel_y = 12;
pixel_x = -6
},
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/carpet/black,
/area/ruin/space/syndicate_druglab)
"qa" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
index 9c2fcf54cdc0..51db92f2ef44 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
@@ -2985,7 +2985,7 @@
dir = 8
},
/obj/item/hand_labeler,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
dir = 9;
icon_state = "darkgreen"
@@ -3423,7 +3423,7 @@
"th" = (
/obj/structure/table,
/obj/item/paper_bin,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/structure/extinguisher_cabinet{
name = "north bump";
pixel_y = 30
@@ -3760,7 +3760,7 @@
/area/ruin/unpowered/syndicate_space_base/service)
"vd" = (
/obj/structure/table,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/item/paper_bin,
/obj/item/stamp/syndicate,
/obj/machinery/light_switch{
@@ -8462,7 +8462,7 @@
dir = 8
},
/obj/item/hand_labeler,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
dir = 10;
icon_state = "darkgreen"
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
index eebd64133f93..c8c725c60a69 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
@@ -498,6 +498,7 @@
/obj/machinery/light/small{
dir = 4
},
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/engine,
/area/ruin/space/unpowered)
"tX" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
index fc3e95325ab7..5164e88c3292 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
@@ -665,7 +665,7 @@
"bV" = (
/obj/structure/table,
/obj/item/folder/syndicate/yellow,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index f10dc5d3334a..1d4d33ac2644 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -1061,3 +1061,6 @@
/// Used by admin-tooling to remove radiation
#define COMSIG_ADMIN_DECONTAMINATE "admin_decontaminate"
+
+/// Sent when bodies transfer between shades/shards and constructs
+#define COMSIG_SHADE_TO_CONSTRUCT_TRANSFER "shade_to_construct_transfer"
diff --git a/code/controllers/configuration/sections/general_configuration.dm b/code/controllers/configuration/sections/general_configuration.dm
index 923bc2f4e61a..40b1729b7603 100644
--- a/code/controllers/configuration/sections/general_configuration.dm
+++ b/code/controllers/configuration/sections/general_configuration.dm
@@ -18,7 +18,7 @@
var/allow_antag_hud = TRUE
/// Forbid players from rejoining if they use AntagHUD?
var/restrict_antag_hud_rejoin = TRUE
- /// Enable respanws by default?
+ /// Enable respawns by default?
var/respawn_enabled = FALSE
/// Enable CID randomiser buster?
var/enabled_cid_randomiser_buster = FALSE
@@ -94,6 +94,7 @@
CONFIG_LOAD_BOOL(allow_antag_hud, data["allow_antag_hud"])
CONFIG_LOAD_BOOL(restrict_antag_hud_rejoin, data["restrict_antag_hud_rejoin"])
CONFIG_LOAD_BOOL(enabled_cid_randomiser_buster, data["enable_cid_randomiser_buster"])
+ CONFIG_LOAD_BOOL(respawn_enabled, data["respawn_enabled"])
CONFIG_LOAD_BOOL(forbid_singulo_possession, data["prevent_admin_singlo_possession"])
CONFIG_LOAD_BOOL(popup_admin_pm, data["popup_admin_pm"])
CONFIG_LOAD_BOOL(allow_holidays, data["allow_holidays"])
diff --git a/code/datums/components/cult_held_body.dm b/code/datums/components/cult_held_body.dm
new file mode 100644
index 000000000000..24deac16f3b4
--- /dev/null
+++ b/code/datums/components/cult_held_body.dm
@@ -0,0 +1,76 @@
+/**
+ * A component for tracking and manipulating bodies held inside constructs/shades
+ * Will drop the body/brain when the parent dies or is deleted.
+ */
+/datum/component/construct_held_body
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ /// A reference to either a mob, or a brain organ that is held inside our parent. Will drop when parent dies/is deleted
+ var/atom/movable/held_body
+
+/datum/component/construct_held_body/Initialize(atom/movable/new_body)
+ . = ..()
+ add_body(new_body)
+
+/datum/component/construct_held_body/Destroy(force, silent)
+ if(held_body)
+ stack_trace("/datum/component/construct_held_body had a held body still despite being destroyed. Body is [held_body] ([held_body.type])")
+ held_body = null
+ return ..()
+
+/datum/component/construct_held_body/PostTransfer()
+ held_body.forceMove(parent) // forcemove them to the new parent
+
+/datum/component/construct_held_body/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOB_DEATH, PROC_REF(drop_body))
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(drop_body))
+ RegisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, PROC_REF(transfer_held_body))
+
+/datum/component/construct_held_body/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_MOB_DEATH)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER)
+
+/datum/component/construct_held_body/proc/add_body(atom/movable/new_body)
+ held_body = new_body
+ RegisterSignal(new_body, COMSIG_PARENT_QDELETING, PROC_REF(_null_held_body))
+ new_body.forceMove(parent)
+
+/datum/component/construct_held_body/proc/_null_held_body()
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ UnregisterSignal(held_body, COMSIG_PARENT_QDELETING)
+ held_body = null
+
+/datum/component/construct_held_body/proc/transfer_held_body(mob/living/current_parent, mob/living/new_body_holder)
+ SIGNAL_HANDLER // COMSIG_SHADE_TO_CONSTRUCT_TRANSFER
+ new_body_holder.TakeComponent(src)
+
+/datum/component/construct_held_body/proc/drop_body()
+ SIGNAL_HANDLER // COMSIG_MOB_DEATH + COMSIG_PARENT_QDELETING
+ INVOKE_ASYNC(src, PROC_REF(_drop_body))
+
+/datum/component/construct_held_body/proc/_drop_body() // call me lazy ig
+ if(!held_body) // Null check for empty bodies
+ return
+ var/mob/living/parent_mob = parent
+ held_body.forceMove(get_turf(parent))
+ SSticker.mode?.cult_team?.add_cult_immunity(held_body)
+
+ var/mob/living/held_mob = held_body
+ if(ismob(held_body)) // Check if the held_body is a mob
+ held_mob.key = parent_mob.key
+ held_mob.cancel_camera()
+ else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
+ var/obj/item/organ/internal/brain/brain = held_body
+ if(brain.brainmob) // Check if the brain has a brainmob
+ brain.brainmob.key = parent_mob.key // Set the key to the brainmob
+ held_mob = brain.brainmob
+
+ if(!istype(held_mob) || QDELETED(held_mob))
+ CRASH("/datum/component/construct_held_body/proc/_drop_body attempted to drop a body despite having no body, or a qdeleted body")
+
+ parent_mob.mind.transfer_to(held_mob) // Transfer the mind to the original mob
+ // goodbye construct antag datums!
+ held_mob.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
+ held_mob.mind.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
+ held_body = null
+ qdel(src) // our job here is done
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index 2190b08c3e15..fdf530ea1198 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -11,8 +11,8 @@
slot_flags = SLOT_FLAG_BELT
origin_tech = "bluespace=4;materials=5"
- /// The body/brain of the player inside this construct, transferred over from the soulstone.
- var/atom/movable/held_body
+ /// Should we show rays? Triggered by a held body
+ var/animate_rays = FALSE
/// Does this soulstone ask the victim whether they want to be turned into a shade
var/optional = FALSE
/// Can this soul stone be used by anyone, or only cultists/wizards?
@@ -26,13 +26,6 @@
var/opt_in = FALSE
var/purified = FALSE
-/obj/item/soulstone/proc/add_held_body(atom/movable/body)
- held_body = body
- RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
-
-/obj/item/soulstone/proc/remove_held_body()
- SIGNAL_HANDLER
- held_body = null
/obj/item/soulstone/proc/can_use(mob/living/user)
if(IS_CULTIST(user) && purified && !iswizard(user))
@@ -78,13 +71,12 @@
/obj/item/soulstone/Destroy() //Stops the shade from being qdel'd immediately and their ghost being sent back to the arrival shuttle.
for(var/mob/living/simple_animal/shade/A in src)
A.death()
- remove_held_body()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/item/soulstone/process()
. = ..()
- if(held_body)
+ if(animate_rays)
var/new_filter = isnull(get_filter("ray"))
if(!purified)
ray_filter_helper(1, 40,"#c926ae", 6, 20)
@@ -242,20 +234,19 @@
to_chat(user, "The shard feels too tough to shatter, you are not holy enough to free its captive!")
return
- if(!do_after_once(user, 10 SECONDS, FALSE, src) || !held_body)
+ if(!do_after_once(user, 10 SECONDS, FALSE, src))
return
- user.visible_message("[user] shatters the soulstone apart! Releasing [held_body] from their prison!", "You shatter the soulstone holding [held_body], binding them free!", "You hear something shatter with a ghastly crack.")
- if(ismob(held_body))
- var/mob/M = held_body
- M.key = S.key
- else if(istype(held_body, /obj/item/organ/internal/brain))
- var/obj/item/organ/internal/brain/B = held_body
- B.brainmob.key = S.key
- S.cancel_camera()
- held_body.forceMove(get_turf(src))
- SSticker.mode?.cult_team?.add_cult_immunity(held_body)
- remove_held_body()
+ if(!S)
+ return
+
+ var/datum/component/construct_held_body/body_holder = S.GetComponent(/datum/component/construct_held_body)
+ var/atom/movable/dropped_body = body_holder.held_body
+ body_holder.drop_body()
+ if(!dropped_body)
+ return
+
+ user.visible_message("[user] shatters the soulstone apart! Releasing [dropped_body] from their prison!", "You shatter the soulstone holding [dropped_body], binding them free!", "You hear something shatter with a ghastly crack.")
new /obj/effect/temp_visual/cult/sparks(get_turf(src))
playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
qdel(src)
@@ -276,6 +267,7 @@
was_used()
remove_filter("ray")
STOP_PROCESSING(SSobj, src)
+ animate_rays = FALSE
///////////////////////////Transferring to constructs/////////////////////////////////////////////////////
/obj/structure/constructshell
@@ -320,9 +312,8 @@
to_chat(user, "Capture failed! The soul has already fled its mortal frame. You attempt to bring it back...")
T.Paralyse(40 SECONDS)
if(!get_cult_ghost(T, user, TRUE))
- add_held_body(T)
- T.forceMove(src) //If we can't get a ghost, shard the body anyways.
- START_PROCESSING(SSobj, src)
+ // no luck, dont shard them.
+ to_chat(user, "No soul responds to the soul stone.")
if("VICTIM")
var/mob/living/carbon/human/T = target
@@ -361,6 +352,7 @@
name = "soulstone : [T.name]"
to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form")
to_chat(user, "Capture successful! [T.name]'s has been recaptured and stored within the soul stone.")
+ animate_rays = TRUE
START_PROCESSING(SSobj, src)
if("CONSTRUCT")
@@ -413,9 +405,8 @@
to_chat(src, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.")
else
to_chat(src, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.")
- SS.held_body.forceMove(src)
- add_held_body(SS.held_body)
- SS.remove_held_body()
+
+ SEND_SIGNAL(shade, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, src)
cancel_camera()
qdel(shell)
qdel(shade)
@@ -465,13 +456,13 @@
if(!isrobot(M))
for(var/obj/item/I in M)
M.unEquip(I)
+
+ var/target_body = M
if(isbrain(M))
- var/obj/item/organ/internal/brain/brain_obj = M.loc
- add_held_body(brain_obj)
- brain_obj.forceMove(src)
- else
- add_held_body(M)
- M.forceMove(src)
+ target_body = M.loc // get the brain organ instead of the brain mob
+
+ S.AddComponent(/datum/component/construct_held_body, target_body)
+ animate_rays = TRUE
/obj/item/soulstone/proc/get_shade_type()
if(purified)
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 7a51a6bc812d..aa68095ad627 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -46,38 +46,12 @@
set_light(2, 3, l_color = GET_CULT_DATA(construct_glow, LIGHT_COLOR_BLOOD_MAGIC))
-/mob/living/simple_animal/hostile/construct/Destroy()
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- remove_held_body()
- return ..()
-
/mob/living/simple_animal/hostile/construct/death(gibbed)
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- if(held_body) // Null check for empty bodies
- held_body.forceMove(get_turf(src))
- SSticker.mode?.cult_team?.add_cult_immunity(held_body)
- if(ismob(held_body)) // Check if the held_body is a mob
- held_body.key = key
- else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
- var/obj/item/organ/internal/brain/brain = held_body
- if(brain.brainmob) // Check if the brain has a brainmob
- brain.brainmob.key = key // Set the key to the brainmob
- brain.brainmob.mind.transfer_to(brain.brainmob) // Transfer the mind to the brainmob
- held_body.cancel_camera()
+ // we also drop our heldbody from the /construct_held_body component, as well as our cult/wiz construct antag datums
new /obj/effect/temp_visual/cult/sparks(get_turf(src))
playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
return ..()
-/mob/living/simple_animal/hostile/construct/proc/add_held_body(atom/movable/body)
- held_body = body
- RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
-
-/mob/living/simple_animal/hostile/construct/proc/remove_held_body()
- SIGNAL_HANDLER
- held_body = null
-
/mob/living/simple_animal/hostile/construct/examine(mob/user)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index d89aed14643b..2fecb79c254c 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -34,11 +34,6 @@
deathmessage = "lets out a contented sigh as their form unwinds."
var/holy = FALSE
-/mob/living/simple_animal/shade/Destroy()
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- return ..()
-
/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user) //Marker -Agouri
if(istype(O, /obj/item/soulstone))
var/obj/item/soulstone/SS = O
diff --git a/code/modules/projectiles/ammunition/ammo_casings.dm b/code/modules/projectiles/ammunition/ammo_casings.dm
index 7e43f0ada910..bc55296544c2 100644
--- a/code/modules/projectiles/ammunition/ammo_casings.dm
+++ b/code/modules/projectiles/ammunition/ammo_casings.dm
@@ -245,6 +245,7 @@
/obj/item/ammo_casing/shotgun/ion
name = "ion shell"
desc = "An advanced 12 gauge shell that fires a spread of ion bolts."
+ icon_state = "ionshell"
projectile_type = /obj/item/projectile/ion/weak
pellets = 4
variance = 35
diff --git a/config/example/config.toml b/config/example/config.toml
index 32e12967ef93..0f2eb8b35c1f 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -314,6 +314,8 @@ guest_ban = true
allow_antag_hud = true
# Forbid players from rejoining if they use antag hud
restrict_antag_hud_rejoin = true
+# Do we want to allow player respawns?
+respawn_enabled = false
# Enable/disable the buster for the CID randomiser DLL
enable_cid_randomiser_buster = false
# Prevent admins from possessing the singularity
diff --git a/paradise.dme b/paradise.dme
index 1155a1ab490a..63585d4056ae 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -403,6 +403,7 @@
#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\corpse_description.dm"
+#include "code\datums\components\cult_held_body.dm"
#include "code\datums\components\deadchat_control.dm"
#include "code\datums\components\decal.dm"
#include "code\datums\components\defibrillator.dm"