diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 1585302f64b..73b92eda82d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -90,6 +90,9 @@ datum/mind proc/store_memory(new_text) memory += "[new_text]
" + proc/wipe_memory() + memory = null + proc/show_memory(mob/recipient) var/output = "[current.real_name]'s Memory
" output += memory diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index e861ac1786a..be8743242a3 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -304,8 +304,6 @@ M.take_damage(100, "brute") /turf/proc/Bless() - if(flags & NOJAUNT) - return flags |= NOJAUNT ///////////////////////////////////////////////////////////////////////// diff --git a/code/modules/mob/living/simple_animal/revenant/revenant.dm b/code/modules/mob/living/simple_animal/revenant/revenant.dm index 953a6f371de..1dc15bbac83 100644 --- a/code/modules/mob/living/simple_animal/revenant/revenant.dm +++ b/code/modules/mob/living/simple_animal/revenant/revenant.dm @@ -8,7 +8,7 @@ desc = "A malevolent spirit." icon = 'icons/mob/mob.dmi' icon_state = "revenant_idle" - incorporeal_move = 1 + incorporeal_move = 3 invisibility = INVISIBILITY_OBSERVER health = 25 maxHealth = 25 @@ -31,7 +31,7 @@ var/essence_regen_cap = 25 //The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount. var/essence_regen = 1 //If the revenant regenerates essence or not; 1 for yes, 0 for no var/essence_min = 1 //The minimum amount of essence a revenant can have; by default, it never drops below one - var/strikes = 3 //How many times a revenant can die before dying for good + var/strikes = 0 //How many times a revenant can die before dying for good var/revealed = 0 //If the revenant can take damage from normal sources. var/inhibited = 0 //If the revenant's abilities are blocked by a chaplain's power. @@ -43,24 +43,13 @@ strikes-- src << "Your essence has dropped below critical levels. You barely manage to save yourself - [strikes ? "you can't keep this up!" : "next time, it's death."]" else if(strikes <= 0) - src << "NO! No... it's too late, you can feel yourself fading..." - src.notransform = 1 - src.revealed = 1 - src.invisibility = 0 - playsound(src, 'sound/effects/screech.ogg', 100, 1) - src.visible_message("The revenant lets out a waning screech as violet mist swirls around its dissolving body!") - src.icon_state = "revenant_draining" - sleep(30) - src.death() + death() maxHealth = essence * 2 if(!revealed) health = maxHealth //Heals to full when not revealed if(essence_regen && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate essence++ -/mob/living/simple_animal/revenant/Process_Spacemove(var/check_drift = 0) - return 1 //Mainly to prevent the no-grav effect - /mob/living/simple_animal/revenant/ex_act(severity) return 1 //Immune to the effects of explosions. @@ -99,16 +88,25 @@ ..() spawn(5) if(src.mind) + src.mind.wipe_memory() src << 'sound/effects/ghost.ogg' - src.store_memory("I am a revenant. My spectral form has been empowered. My only goal is to gather essence from the humans of [world.name].") src << "
" - src << "You are a revenant!" + src << "You are a revenant." src << "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant." - src << "You are not dead, not alive, but somewhere in between. You are capable of very limited interaction with both worlds." + src << "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds." src << "You are invincible and invisible to everyone but other ghosts. Some abilities may change this." - src << "Your goal is to gather essence from humans. Your essence passively regenerates up to 25E over time. You can use the Harvest abilities to gather more from corpses." - src << "Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant !" - src << "
" + src << "To function, you are to drain the life essence from humans. This essence is a resource and will power all of your abilities." + src << "You do not remember anything of your past lives, nor will you remember anything about this one after your death." + src << "Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant to learn more." + var/datum/objective/revenant/objective = new + objective.owner = src + src.mind.objectives += objective + src << "Objective #1: [objective.explanation_text]" + var/datum/objective/revenantFluff/objective2 = new + objective2.owner = src + src.mind.objectives += objective2 + src << "Objective #2: [objective2.explanation_text]" + ticker.mode.traitors |= src.mind //Necessary for announcing if(!src.giveSpells()) message_admins("Revenant was created but has no mind. Trying again in five seconds.") spawn(50) @@ -121,50 +119,58 @@ src.mind.spell_list += new /obj/effect/proc_holder/spell/wizard/targeted/revenant_harvest src.mind.spell_list += new /obj/effect/proc_holder/spell/wizard/targeted/revenant_transmit src.mind.spell_list += new /obj/effect/proc_holder/spell/wizard/aoe_turf/revenant_light + src.mind.spell_list += new /obj/effect/proc_holder/spell/wizard/aoe_turf/revenantDefile return 1 return 0 /mob/living/simple_animal/revenant/death() - if(!strikes) + if(strikes) return 0 //Impossible to die with strikes still active ..(1) - reveal(10, 1) - visible_message("[src] pulses with an eldritch purple light as its form unwinds into smoke.") + src << "NO! No... it's too late, you can feel yourself fading..." + notransform = 1 + revealed = 1 + invisibility = 0 + playsound(src, 'sound/effects/screech.ogg', 100, 1) + visible_message("[src] lets out a waning screech as violet mist swirls around its dissolving body!") + icon_state = "revenant_draining" + for(var/i = alpha, i > 0, i -= 10) + sleep(0.1) + alpha = i + visible_message("[src]'s body breaks apart into blue dust.") + new /obj/item/weapon/ectoplasm/revenant(get_turf(src)) ghostize() qdel(src) return + /mob/living/simple_animal/revenant/attackby(obj/item/W, mob/living/user, params) ..() if(istype(W, /obj/item/weapon/nullrod)) - src.visible_message("The revenant screeches and flails!", \ - "The null rod invokes agony in you! You feel your essence draining away!") - src.essence -= 25 //hella effective - src.inhibited = 1 + visible_message("[src] violently flinches!", \ + "The null rod invokes agony in you! You feel your essence draining away!") + essence -= 25 //hella effective + inhibited = 1 spawn(30) - src.inhibited = 0 + inhibited = 0 -/obj/effect/proc_holder/spell/proc/essence_check(var/essence_cost, var/silent = 0) - var/mob/living/simple_animal/revenant/W = usr - if(!istype(usr) || !usr) +/mob/living/simple_animal/revenant/proc/castcheck(var/essence_cost) + var/mob/living/simple_animal/revenant/user = usr + if(!istype(user) || !user) return - if(W.essence < essence_cost) - if(!silent) - W << "You need [essence_cost]E to use [name] but you only have [W.essence]E available. Harvest some more things." - return 0 - W.essence -= essence_cost - return 1 - - - -/mob/living/simple_animal/revenant/proc/wallcheck() var/turf/T = get_turf(usr) - if(!istype(T, /turf/simulated/wall)) - return 1 - usr << "You can't use abilities from a wall. What did you expect?" - return 0 + if(istype(T, /turf/simulated/wall)) + user << "You cannot use abilities from inside of a wall." + return 0 + if(!user.change_essence_amount(essence_cost, 1)) + user << "You lack the essence to use that ability." + return 0 + if(user.inhibited) + user << "Your powers have been suppressed by holy energies!" + return 0 + return 1 @@ -172,14 +178,15 @@ var/mob/living/simple_animal/revenant/user = usr if(!istype(usr) || !usr) return - if(!essence_amt) + if(user.essence + essence_amt <= 0) return user.essence += essence_amt + user.essence = Clamp(user.essence, 0, INFINITY) if(!silent) - if(essence_amt >= 0) - user << "Gained [essence_amt]E from [source]." + if(essence_amt > 0) + user << "Gained [essence_amt]E from [source]." else - user << "Lost [essence_amt]E from [source]." + user << "Lost [essence_amt]E from [source]." return 1 @@ -192,8 +199,117 @@ R.invisibility = 0 if(stun) R.notransform = 1 + R << "You have been revealed [stun ? "and cannot move" : ""]." spawn(time) R.revealed = 0 R.invisibility = INVISIBILITY_OBSERVER if(stun) R.notransform = 0 + R << "You are once more concealed [stun ? "and can move again" : ""]." + +/datum/objective/revenant + var/targetAmount = 100 + +/datum/objective/revenant/New() + targetAmount = rand(100,200) + explanation_text = "Absorb [targetAmount] points of essence." + ..() + +/datum/objective/revenant/check_completion() + if(!istype(owner.current, /mob/living/simple_animal/revenant) || !owner.current) + return 0 + var/mob/living/simple_animal/revenant/R = owner.current + if(!R || R.stat == DEAD) + return 0 + var/essenceAccumulated = R.essence + if(essenceAccumulated < targetAmount) + return 0 + return 1 + +/datum/objective/revenantFluff + +/datum/objective/revenantFluff/New() + var/list/explanationTexts = list("Attempt to make your presence unknown to the crew.", \ + "Collaborate with existing antagonists aboard the station to gain essence.", \ + "Remain nonlethal and only absorb bodies that have already died.", \ + "Use your environments to eliminate isolated people.", \ + "If there is a chaplain aboard the station, ensure they are killed.", \ + "Hinder the crew without killing them.") + explanation_text = pick(explanationTexts) + ..() + +/datum/objective/revenantFluff/check_completion() + return 1 + + +/obj/item/weapon/ectoplasm/revenant + name = "glimmering residue" + desc = "A pile of fine blue dust. Small tendrils of violet mist swirl around it." + icon = 'icons/effects/effects.dmi' + icon_state = "revenantEctoplasm" + w_class = 2 + var/reforming = 0 + var/reformed = 0 + +/obj/item/weapon/ectoplasm/revenant/New() + ..() + reforming = 1 + spawn(1800) //3 minutes + if(src && reforming) + return reform() + if(src && !reforming) + visible_message("[src] settles down and seems lifeless.") + return + +/obj/item/weapon/ectoplasm/revenant/attack_hand(mob/user) + if(reformed) + user << "[src] keeps slipping out of your hands, you can't get a hold on it!" + return + ..() + +/obj/item/weapon/ectoplasm/revenant/attack_self(mob/user) + if(!reforming) + return ..() + user.visible_message("[user] scatters [src] in all directions.", \ + "You scatter [src] across the area. The particles slowly fade away.") + user.drop_item() + qdel(src) + +/obj/item/weapon/ectoplasm/revenant/throw_impact(atom/hit_atom) + ..() + visible_message("[src] breaks into particles upon impact, which fade away to nothingness.") + qdel(src) + +/obj/item/weapon/ectoplasm/revenant/examine(mob/user) + ..() + if(reforming) + user << "It is shifting and distorted. It would be wise to destroy this." + else if(!reforming) + user << "It seems inert." + +/obj/item/weapon/ectoplasm/revenant/proc/reform() + if(!reforming || !src) + return + message_admins("Revenant ectoplasm was left undestroyed for 3 minutes and has reformed into a new revenant.") + loc = get_turf(src) //In case it's in a backpack or someone's hand + visible_message("[src] suddenly rises into the air before fading away.") + var/mob/living/simple_animal/revenant/R = new(get_turf(src)) + qdel(src) + var/list/candidates = get_candidates(BE_REVENANT) + if(!candidates.len) + message_admins("No candidates were found for the new revenant. Oh well!") + return 0 + var/client/C = pick(candidates) + var/key_of_revenant = C.key + if(!key_of_revenant) + message_admins("No ckey was found for the new revenant. Oh well!") + return 0 + var/datum/mind/player_mind = new /datum/mind(key_of_revenant) + player_mind.active = 1 + player_mind.transfer_to(R) + player_mind.assigned_role = "revenant" + player_mind.special_role = "Revenant" + ticker.mode.traitors |= player_mind + message_admins("[key_of_revenant] has been made into a revenant by reforming ectoplasm.") + log_game("[key_of_revenant] was spawned as a revenant by reforming ectoplasm.") + return 1 diff --git a/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm b/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm index 9233819e109..e3197e90306 100644 --- a/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm @@ -11,11 +11,7 @@ var/list/drained_mobs = list() //Cannot harvest the same mob twice /obj/effect/proc_holder/spell/wizard/targeted/revenant_harvest/cast(list/targets, var/mob/living/simple_animal/revenant/user = usr) - if(user.inhibited) - user << "Something is blocking the use of [src]!" - charge_counter = charge_max - return - if(!user.wallcheck()) + if(!user.castcheck(0)) charge_counter = charge_max return for(var/mob/living/carbon/human/target in targets) @@ -87,11 +83,10 @@ var/locked = 1 /obj/effect/proc_holder/spell/wizard/targeted/revenant_transmit/cast(list/targets, var/mob/living/simple_animal/revenant/user = usr) - if(user.inhibited) - user << "Something is blocking the use of [src]!" + if(!user.castcheck(-5)) charge_counter = charge_max return - if(locked && essence_check(5, 1)) + if(locked) usr << "You have unlocked Transmit!" locked = 0 charge_counter = charge_max @@ -99,15 +94,6 @@ range = 7 include_user = 0 return - if(locked) - charge_counter = charge_max - return - if(!user.wallcheck()) - charge_counter = charge_max - return - if(!essence_check(5)) - charge_counter = charge_max - return for(var/mob/living/M in targets) spawn(0) var/msg = stripped_input(usr, "What do you wish to tell [M]?", null, "") @@ -129,25 +115,14 @@ var/locked = 1 /obj/effect/proc_holder/spell/wizard/aoe_turf/revenant_light/cast(list/targets, var/mob/living/simple_animal/revenant/user = usr) - if(user.inhibited) - user << "Something is blocking the use of [src]!" - charge_counter = charge_max - return - if(locked && essence_check(25, 1)) - user << "You have unlocked Overload Light!" - name = "Overload Light (25E)" - panel = "Revenant Abilities" - locked = 0 - range = 5 + if(!user.castcheck(-25)) charge_counter = charge_max return if(locked) - charge_counter = charge_max - return - if(!user.wallcheck()) - charge_counter = charge_max - return - if(!essence_check(25)) + user << "You have unlocked Overload Light!" + panel = "Revenant Abilities" + locked = 0 + range = 5 charge_counter = charge_max return for(var/turf/T in targets) @@ -165,3 +140,49 @@ M.electrocute_act(25, "[L.name]") playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1) user.reveal(50, 1) + + +//Defile: Corrupts nearby stuff, unblesses floor tiles. +/obj/effect/proc_holder/spell/wizard/aoe_turf/revenantDefile + name = "Defile (30E)" + desc = "Twists and corrupts certain nearby objects. Also dispels holy auras on floors, but not salt lines." + panel = "Revenant Abilities (Locked)" + charge_max = 300 + clothes_req = 0 + range = 1 + var/locked = 1 + +/obj/effect/proc_holder/spell/wizard/aoe_turf/revenantDefile/cast(list/targets, var/mob/living/simple_animal/revenant/user = usr) + if(!user.castcheck(-30)) + charge_counter = charge_max + return + if(locked) + user << "You have unlocked Defile!" + panel = "Revenant Abilities" + locked = 0 + range = 4 + charge_counter = charge_max + return + for(var/turf/T in targets) + spawn(0) + if(T.flags & NOJAUNT) + T.flags -= NOJAUNT + for(var/obj/machinery/bot/secbot/secbot in T.contents) //Not including ED-209 + secbot.emagged = 2 + secbot.Emag(null) + for(var/obj/machinery/bot/cleanbot/cleanbot in T.contents) + cleanbot.emagged = 2 + cleanbot.Emag(null) + for(var/mob/living/carbon/human/human in T.contents) + human << "You suddenly feel tired." + human.adjustStaminaLoss(25) + for(var/mob/living/silicon/robot/robot in T.contents) + robot.visible_message("[robot] lets out an alarm!", \ + "01001111 01010110 01000101 01010010 01001100 01001111 01000001 01000100") //Translates to "OVERLOAD" + robot << 'sound/misc/interference.ogg' + playsound(robot, 'sound/machines/warning-buzzer.ogg', 50, 1) + for(var/obj/structure/window/window in T.contents) + window.hit(rand(10,50)) + for(var/obj/machinery/light/light in T.contents) + light.flicker() //spooky + user.reveal(30, 1) diff --git a/code/modules/mob/living/simple_animal/revenant/revenant_spawn_event.dm b/code/modules/mob/living/simple_animal/revenant/revenant_spawn_event.dm index bd44a92f228..0f1b3545391 100644 --- a/code/modules/mob/living/simple_animal/revenant/revenant_spawn_event.dm +++ b/code/modules/mob/living/simple_animal/revenant/revenant_spawn_event.dm @@ -1,9 +1,16 @@ +#define REVENANT_SPAWN_THRESHOLD 10 + /datum/event/revenant var/key_of_revenant - /datum/event/revenant/proc/get_revenant(var/end_if_fail = 0) + var/deadMobs = 0 + for(var/mob/M in dead_mob_list) + deadMobs++ + if(deadMobs < REVENANT_SPAWN_THRESHOLD) + message_admins("Random event attempted to spawn a revenant, but there were only [deadMobs]/[REVENANT_SPAWN_THRESHOLD] dead mobs.") + return key_of_revenant = null if(!key_of_revenant) var/list/candidates = get_candidates(BE_REVENANT) @@ -23,32 +30,38 @@ for(var/obj/effect/landmark/L in landmarks_list) if(isturf(L.loc)) switch(L.name) - if("carpspawn") + if("revenantspawn") spawn_locs += L.loc - if(!spawn_locs) + if(!spawn_locs) //If we can't find any revenant spawns, try the carp spawns + for(var/obj/effect/landmark/L in landmarks_list) + if(isturf(L.loc)) + switch(L.name) + if("carpspawn") + spawn_locs += L.loc + if(!spawn_locs) //If we can't find either, just spawn the revenant at the player's location + spawn_locs += get_turf(player_mind.current) + if(!spawn_locs) //If we can't find THAT, then just retry return find_revenant() var/mob/living/simple_animal/revenant/revvie = new /mob/living/simple_animal/revenant/(pick(spawn_locs)) player_mind.transfer_to(revvie) player_mind.assigned_role = "revenant" player_mind.special_role = "Revenant" ticker.mode.traitors |= player_mind - message_admins("[key_of_revenant] has been made into a Revenant by an event.") - log_game("[key_of_revenant] was spawned as a Revenant by an event.") + message_admins("[key_of_revenant] has been made into a revenant by an event.") + log_game("[key_of_revenant] was spawned as a revenant by an event.") return 1 - /datum/event/revenant/start() get_revenant() - /datum/event/revenant/proc/find_revenant() - message_admins("Attempted to spawn a Revenant but there was no players available. Will try again momentarily, bear with me...") + message_admins("An event failed to spawn a revenant. Retrying momentarily...") spawn(50) if(get_revenant(1)) - message_admins("Hooray! Situation has been resolved, [key_of_revenant] has been spawned as a Revenant.") - log_game("[key_of_revenant] was spawned as a Revenant by an event.") + message_admins("[key_of_revenant] has been spawned as a revenant.") + log_game("[key_of_revenant] was spawned as a revenant by an event.") return 0 - message_admins("Unfortunately, no candidates were available for becoming a Revenant. Shutting down. :(") + message_admins("No candidates were available for becoming a revenant.") return kill() diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 0407a0edc07..999bd0145fc 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -440,6 +440,16 @@ anim(mobloc,mob,'icons/mob/mob.dmi',,"shadow",,L.dir) L.loc = get_step(L, direct) L.dir = direct + if(3) //Incorporeal move, but blocked by holy-watered tiles + var/turf/simulated/floor/stepTurf = get_step(L, direct) + if(stepTurf.flags & NOJAUNT) + L << "Holy energies block your path." + L.notransform = 1 + spawn(2) + L.notransform = 0 + else + L.loc = get_step(L, direct) + L.dir = direct return 1 diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index fd0b5638d68..23378218fbd 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -515,6 +515,15 @@ datum M.mind.vampire.nullified = max(5, M.mind.vampire.nullified + 2) return + + reaction_turf(var/turf/simulated/T, var/volume) + ..() + if(!istype(T)) return + if(volume>=10) + for(var/obj/effect/rune/R in T) + qdel(R) + T.Bless() + serotrotium name = "Serotrotium" id = "serotrotium" diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 9d109c9727c..7a45b29f2ec 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ