diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm index 83084005dd5..b1f2431ccbb 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm @@ -33,3 +33,5 @@ #define COMSIG_RAPTOR_GROWTH_STAGE_CHANGE "raptor_growth_stage_change" /// Cancel raptor growth #define COMPONENT_CANCEL_RAPTOR_GROWTH (1<<0) +// Called when the revenant is to be released from its prison : (cause) +#define COMSIG_REVENANT_RELEASE "revenant_release" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index c41304f11b1..b3dbc04a26e 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -686,6 +686,9 @@ /// Sent from /datum/component/reflection when the reflection is updated to the mob reflecting: (atom/movable/reflecting_in, obj/effect/abstract/reflection) #define COMSIG_REFLECTION_UPDATED "reflection_updated" +/// Sent from /datum/component/reflection when the reflection is updated to the component parent: (obj/effect/abstract/reflection) +#define COMSIG_REFLECTED_IMAGE_UPDATED "reflected_image_updated" + /// From /datum/element/weapon_description: (list/readout) #define COMSIG_ITEM_WEAPON_LABEL_READOUT "item_weapon_label_readout" diff --git a/code/datums/components/reflection.dm b/code/datums/components/reflection.dm index 5e786b0c4ac..bd24a4e4f82 100644 --- a/code/datums/components/reflection.dm +++ b/code/datums/components/reflection.dm @@ -204,6 +204,7 @@ reflection.name = "[target.name]'s reflection" SEND_SIGNAL(target, COMSIG_REFLECTION_UPDATED, parent, reflection) + SEND_SIGNAL(parent, COMSIG_REFLECTED_IMAGE_UPDATED, reflection) ///Called when the target movable changes its appearance or dir. /datum/component/reflection/proc/update_reflection(atom/movable/source) diff --git a/code/datums/components/revenant_prison.dm b/code/datums/components/revenant_prison.dm new file mode 100644 index 00000000000..d5ba6529a62 --- /dev/null +++ b/code/datums/components/revenant_prison.dm @@ -0,0 +1,67 @@ +/datum/component/revenant_prison + // Whether a revenant should be created upon release + var/create_on_release = FALSE + // The revenant which is currently imprisoned + var/mob/living/basic/revenant/revenant + // ckey of the player who controlled it when it was imprisoned + var/old_ckey + +/datum/component/revenant_prison/Initialize(mob/living/basic/revenant/revenant, create_on_release = FALSE) + if(create_on_release) + return ..() + if(!istype(revenant) || !isobj(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + + src.revenant = revenant + revenant.dormant = TRUE + old_ckey = revenant.client?.ckey + revenant.forceMove(parent) + +/datum/component/revenant_prison/Destroy() + if(revenant?.client) + revenant.ghostize(can_reenter_corpse = FALSE) + QDEL_NULL(revenant) + return ..() + +/datum/component/revenant_prison/proc/on_parent_break(obj/source, damage_flags) + SIGNAL_HANDLER + source.visible_message(span_revenwarning("The revenant cackles as it escapes from [source]!")) + playsound(source.loc, 'sound/effects/chemistry/ahaha.ogg', 100, TRUE) + release_revenant(source, cause = "[parent] breaking") + +/datum/component/revenant_prison/proc/release_revenant(obj/source, cause) + SIGNAL_HANDLER + if(create_on_release) + revenant = new(get_turf(parent)) + if(!revenant) + qdel(src) + return + message_admins("[revenant] [ADMIN_FLW(revenant)] has been released from [source] [ADMIN_JMP(source)]. Cause: [cause]") + if(!revenant.reform(old_ckey)) + message_admins("Couldn't reform revenant upon release.") + revenant = null + qdel(src) + +/datum/component/revenant_prison/proc/shift_reflection(datum/source, obj/effect/abstract/reflection) + SIGNAL_HANDLER + apply_wibbly_filters(reflection) + +/datum/component/revenant_prison/proc/on_parent_examine(datum/source, mob/user, list/examine_list) + if(istype(parent, /obj/structure/mirror)) + examine_list += span_revenwarning("The reflection is shifting and distorted.") + +/datum/component/revenant_prison/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_parent_examine)) + RegisterSignal(parent, COMSIG_ATOM_BREAK, PROC_REF(on_parent_break)) + RegisterSignal(parent, COMSIG_REVENANT_RELEASE, PROC_REF(release_revenant)) + RegisterSignal(parent, COMSIG_REFLECTED_IMAGE_UPDATED, PROC_REF(shift_reflection)) + +/datum/component/revenant_prison/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) + UnregisterSignal(parent, COMSIG_ATOM_BREAK) + UnregisterSignal(parent, COMSIG_REVENANT_RELEASE) + UnregisterSignal(parent, COMSIG_REFLECTED_IMAGE_UPDATED) + +/datum/component/revenant_prison/PostTransfer(datum/new_parent) + revenant.forceMove(new_parent) diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 718a527a85c..ee4b16dc10d 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -15,6 +15,9 @@ #define PRIDE_MIRROR_OPTIONS list(CHANGE_HAIR, CHANGE_BEARD, CHANGE_RACE, CHANGE_SEX, CHANGE_EYES) #define MAGIC_MIRROR_OPTIONS list(CHANGE_HAIR, CHANGE_BEARD, CHANGE_RACE, CHANGE_SEX, CHANGE_EYES, CHANGE_NAME) +// Chance for the mirror to be haunted at creation +#define ROUNDSTART_CURSED_CHANCE 0.2 + /obj/structure/mirror name = "mirror" desc = "Mirror mirror on the wall, who's the most robust of them all?" @@ -29,7 +32,8 @@ ///Can this mirror be removed from walls with tools? var/deconstructable = TRUE var/list/mirror_options = INERT_MIRROR_OPTIONS - + // Can a revenant be imprisoned in this mirror? + var/cursable = TRUE ///Flags this race must have to be selectable with this type of mirror. var/race_flags = MIRROR_MAGIC ///List of all Races that can be chosen, decided by its Initialize. @@ -57,6 +61,8 @@ ) if(mapload) find_and_mount_on_atom() + if(prob(ROUNDSTART_CURSED_CHANCE) && cursable) + AddComponent(/datum/component/revenant_prison, create_on_release = TRUE) update_choices() register_context() @@ -72,10 +78,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28) /obj/structure/mirror/broken icon_state = "mirror_broke" + cursable = FALSE + broken = TRUE + desc = "Oh no, seven years of bad luck!" /obj/structure/mirror/broken/Initialize(mapload) . = ..() - atom_break(null, mapload) MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) @@ -302,13 +310,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters...")) unlucky_dude.AddComponent(/datum/component/omen, incidents_left = 7) -/obj/structure/mirror/atom_break(damage_flag, mapload) +/obj/structure/mirror/atom_break(damage_flag) . = ..() if(broken) return icon_state = "mirror_broke" - if(!mapload) - playsound(src, SFX_SHATTER, 70, TRUE) + playsound(src, SFX_SHATTER, 70, TRUE) if(desc == initial(desc)) desc = "Oh no, seven years of bad luck!" broken = TRUE @@ -361,6 +368,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) icon_state = "magic_mirror" mirror_options = MAGIC_MIRROR_OPTIONS deconstructable = FALSE + cursable = FALSE /obj/structure/mirror/magic/Initialize(mapload) . = ..() @@ -463,3 +471,5 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) #undef INERT_MIRROR_OPTIONS #undef PRIDE_MIRROR_OPTIONS #undef MAGIC_MIRROR_OPTIONS + +#undef ROUNDSTART_CURSED_CHANCE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 3bd92b48950..af30342e1fe 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -62,6 +62,8 @@ var/draining = FALSE /// Have we already given this revenant abilities? var/generated_objectives_and_spells = FALSE + /// ckey of the player who controlled this mob when it was killed + var/old_ckey = "" /// Lazylist of drained mobs to ensure that we don't steal a soul from someone twice var/list/drained_mobs = null @@ -343,10 +345,7 @@ visible_message(span_danger("[src]'s body breaks apart into a fine pile of blue dust.")) - var/obj/item/ectoplasm/revenant/goop = new(get_turf(src)) // the ectoplasm will handle moving us out of dormancy - goop.old_ckey = client.ckey - goop.revenant = src - forceMove(goop) + new /obj/item/ectoplasm/revenant(get_turf(src), src) // the ectoplasm will handle moving us out of dormancy /mob/living/basic/revenant/proc/on_move(datum/source, atom/entering_loc) SIGNAL_HANDLER @@ -519,4 +518,30 @@ if(!HAS_TRAIT(src, TRAIT_REVENANT_REVEALED) && !istype(reflecting_in, /obj/structure/mirror/magic)) apply_wibbly_filters(reflection) +/mob/living/basic/revenant/proc/get_new_user() + message_admins("A poll for the reforming revenant was created.") + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to be [span_notice(name)] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, checked_target = src, alert_pic = src, role_name_text = "reforming revenant", chat_text_border_icon = src) + if(!chosen_one) + message_admins("No candidates were found for the new revenant.") + visible_message(span_revenwarning("A blue dust appears from thin air and settles down.")) + new /obj/item/ectoplasm/revenant(get_turf(src)) // inert + qdel(src) + return + + PossessByPlayer(chosen_one.key) + message_admins("[chosen_one.key] has been made into the reformed revenant via poll.") + qdel(chosen_one) + +/mob/living/basic/revenant/proc/reform(cause) + if(QDELETED(src)) + return FALSE + + death_reset() + if(isnull(client)) + INVOKE_ASYNC(src, PROC_REF(get_new_user)) + return TRUE + + message_admins("[client.ckey] has been remade into a revenant.") + log_message("was remade as a revenant.", LOG_GAME) + return TRUE #undef REVENANT_STUNNED_TRAIT diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 2030315410c..86849f1ffd9 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -1,7 +1,7 @@ #define REVENANT_DEFILE_MIN_DAMAGE 30 #define REVENANT_DEFILE_MAX_DAMAGE 50 -//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob +//Transmit: the revenant's only direct way to communicate. Sends a single message silently to a single mob /datum/action/cooldown/spell/list_target/telepathy/revenant name = "Revenant Transmit" background_icon_state = "bg_revenant" @@ -12,6 +12,14 @@ antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND +/datum/action/cooldown/spell/list_target/telepathy/revenant/get_list_targets(atom/center, target_radius = 7) + if(!istype(center, /mob/living/basic/revenant)) + return ..() + var/mob/living/basic/revenant/revenant = center + if(!revenant.dormant) + return ..() + return ..(get_turf(revenant), 2) + /datum/action/cooldown/spell/aoe/revenant background_icon_state = "bg_revenant" overlay_icon_state = "bg_revenant_border" diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm index f691521b076..b9007a7f722 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm @@ -5,31 +5,36 @@ icon = 'icons/effects/effects.dmi' icon_state = "revenantEctoplasm" w_class = WEIGHT_CLASS_SMALL - /// Are we currently reforming? - var/reforming = TRUE - /// Are we inert (aka distorted such that we can't reform)? + // Can the revenant reform? var/inert = FALSE - /// The key of the revenant that we started the reform as - var/old_ckey - /// The revenant we're currently storing - var/mob/living/basic/revenant/revenant -/obj/item/ectoplasm/revenant/Initialize(mapload) +/obj/item/ectoplasm/revenant/Initialize(mapload, revenant) . = ..() - addtimer(CALLBACK(src, PROC_REF(try_reform)), 1 MINUTES) + inert = !revenant + if(revenant) + AddComponent(/datum/component/revenant_prison, revenant = revenant) + addtimer(CALLBACK(src, PROC_REF(reform)), 1 MINUTES) /obj/item/ectoplasm/revenant/Destroy() - if(!QDELETED(revenant)) - qdel(revenant) return ..() +/obj/item/ectoplasm/revenant/proc/check_for_mirrors(turf/location, radius) + PRIVATE_PROC(TRUE) + for(var/obj/structure/mirror/mirror in view(radius, location)) + if(mirror.cursable && !mirror.GetComponent(/datum/component/revenant_prison)) + return mirror + return null + /obj/item/ectoplasm/revenant/attack_self(mob/user) - if(!reforming || inert) + if(inert) return ..() user.visible_message( span_notice("[user] scatters [src] in all directions."), - span_notice("You scatter [src] across the area. The particles slowly fade away."), + span_notice("You scatter [src] across the area."), ) + var/obj/structure/mirror/nearby_mirror = check_for_mirrors(drop_location(), 5) + if(nearby_mirror) + transfer_to_mirror(nearby_mirror) user.dropItemToGround(src) qdel(src) @@ -37,14 +42,25 @@ . = ..() if(inert) return - visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) + var/obj/structure/mirror/nearby_mirror = check_for_mirrors(get_turf(hit_atom), 3) + if(!nearby_mirror) + visible_message(span_notice("[src] breaks into particles upon impact, which fade away to nothingness.")) + else + transfer_to_mirror(nearby_mirror) qdel(src) +/obj/item/ectoplasm/revenant/proc/transfer_to_mirror(obj/structure/mirror/nearby_mirror) + PRIVATE_PROC(TRUE) + nearby_mirror.TakeComponent(GetComponent(/datum/component/revenant_prison)) + nearby_mirror.visible_message(span_revenwarning("A dismal moan echoes as particles of [src] fall onto [nearby_mirror]!")) + log_game("A revenant was trapped inside [nearby_mirror]") + message_admins("A revenant was trapped inside [nearby_mirror] [ADMIN_JMP(nearby_mirror)]") + /obj/item/ectoplasm/revenant/examine(mob/user) . = ..() if(inert) . += span_revennotice("It seems inert.") - else if(reforming) + else . += span_revenwarning("It is shifting and distorted. It would be wise to destroy this.") /obj/item/ectoplasm/revenant/suicide_act(mob/living/user) @@ -52,45 +68,13 @@ qdel(src) return OXYLOSS -/obj/item/ectoplasm/revenant/proc/try_reform() - if(reforming) - reforming = FALSE - reform() - else - inert = TRUE - visible_message(span_warning("[src] settles down and seems lifeless.")) - /// Actually moves the revenant out of ourself /obj/item/ectoplasm/revenant/proc/reform() - if(QDELETED(src) || QDELETED(revenant) || inert) + if(QDELETED(src) || inert) + return + if(!GetComponent(/datum/component/revenant_prison)) return - message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.") - forceMove(drop_location()) //In case it's in a backpack or someone's hand - - var/user_name = old_ckey - if(isnull(revenant.client)) - var/mob/potential_user = get_new_user() - revenant.PossessByPlayer(potential_user.key) - user_name = potential_user.ckey - qdel(potential_user) - - message_admins("[user_name] has been [old_ckey == user_name ? "re":""]made into a revenant by reforming ectoplasm.") - revenant.log_message("was [old_ckey == user_name ? "re":""]made as a revenant by reforming ectoplasm.", LOG_GAME) + SEND_SIGNAL(src, COMSIG_REVENANT_RELEASE, cause = "ectoplasm reforming") visible_message(span_revenboldnotice("[src] suddenly rises into the air before fading away.")) - - revenant.death_reset() - revenant = null qdel(src) - -/// Handles giving the revenant a new client to control it -/obj/item/ectoplasm/revenant/proc/get_new_user() - message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to be [span_notice(revenant.name)] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, checked_target = revenant, alert_pic = revenant, role_name_text = "reforming revenant", chat_text_border_icon = revenant) - if(isnull(chosen_one)) - message_admins("No candidates were found for the new revenant.") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - qdel(revenant) - return null - return chosen_one diff --git a/tgstation.dme b/tgstation.dme index 9a999fb52ea..5a6c082f90d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1309,6 +1309,7 @@ #include "code\datums\components\religious_tool.dm" #include "code\datums\components\rename.dm" #include "code\datums\components\reskinnable_atom.dm" +#include "code\datums\components\revenant_prison.dm" #include "code\datums\components\revenge_ability.dm" #include "code\datums\components\rot.dm" #include "code\datums\components\scope.dm"