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"