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