diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index affe84a0156..4ea37560a7f 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -37,4 +37,5 @@
#define BE_VAMPIRE 2048
#define BE_MUTINEER 4096
#define BE_BLOB 8192
-#define BE_SHADOWLING 16384
\ No newline at end of file
+#define BE_SHADOWLING 16384
+#define BE_REVENANT 32768
\ No newline at end of file
diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm
index 30974675e07..872e469a293 100644
--- a/code/_globalvars/configuration.dm
+++ b/code/_globalvars/configuration.dm
@@ -49,7 +49,8 @@ var/list/be_special_flags = list(
"raider" = BE_RAIDER,
"vampire" = BE_VAMPIRE,
"mutineer" = BE_MUTINEER,
- "blob" = BE_BLOB
+ "blob" = BE_BLOB,
+ "Revenant" = BE_REVENANT
)
//Random event stuff, apparently used
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 4347ae160a3..9a30aa3a75c 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -134,7 +134,8 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Vermin Infestation",/datum/event/infestation, 100, list(ASSIGNMENT_JANITOR = 100)),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 0, list(ASSIGNMENT_ENGINEER = 30, ASSIGNMENT_GARDENER = 50)),
// NON-BAY EVENTS
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Cargo Bonus", /datum/event/cargo_bonus, 100)
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Cargo Bonus", /datum/event/cargo_bonus, 100),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Revenant", /datum/event/revenant, 50)
)
/datum/event_container/moderate
diff --git a/code/modules/events/event_dynamic.dm b/code/modules/events/event_dynamic.dm
index 3838980832d..c0f6c3b4381 100644
--- a/code/modules/events/event_dynamic.dm
+++ b/code/modules/events/event_dynamic.dm
@@ -50,6 +50,7 @@ var/global/list/possibleEvents = list()
//possibleEvents[/datum/event/mundane_news] = 300
possibleEvents[/datum/event/cargo_bonus] = 150
+ possibleEvents[/datum/event/revenant] = 75
possibleEvents[/datum/event/pda_spam] = max(min(25, player_list.len) * 4, 200)
possibleEvents[/datum/event/money_lotto] = max(min(5, player_list.len), 50)
diff --git a/code/modules/mob/living/simple_animal/revenant/revenant.dm b/code/modules/mob/living/simple_animal/revenant/revenant.dm
new file mode 100644
index 00000000000..953a6f371de
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/revenant/revenant.dm
@@ -0,0 +1,199 @@
+//Revenants: based off of wraiths from Goon
+//"Ghosts" that are invisible and move like ghosts, cannot take damage while invsible
+//Don't hear deadchat and are NOT normal ghosts
+//Admin-spawn or random event
+
+/mob/living/simple_animal/revenant
+ name = "revenant"
+ desc = "A malevolent spirit."
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "revenant_idle"
+ incorporeal_move = 1
+ invisibility = INVISIBILITY_OBSERVER
+ health = 25
+ maxHealth = 25
+ see_in_dark = 255
+ see_invisible = SEE_INVISIBLE_OBSERVER
+ universal_understand = 1
+ response_help = "passes through"
+ response_disarm = "swings at"
+ response_harm = "punches"
+ minbodytemp = 0
+ maxbodytemp = INFINITY
+ harm_intent_damage = 5
+ speak_emote = list("hisses", "spits", "growls")
+ friendly = "touches"
+ status_flags = 0
+ wander = 0
+ density = 0
+
+ var/essence = 25 //The resource of revenants. Max health is equal to twice this amount
+ 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/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.
+
+/mob/living/simple_animal/revenant/Life()
+ ..()
+ if(essence < essence_min)
+ essence = essence_min
+ if(strikes > 0)
+ 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()
+ 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.
+
+/mob/living/simple_animal/revenant/ClickOn(var/atom/A, var/params) //Copypaste from ghost code - revenants can't interact with the world directly.
+ if(client.buildmode)
+ build_click(src, client.buildmode, params, A)
+ return
+
+ var/list/modifiers = params2list(params)
+ if(modifiers["middle"])
+ MiddleClickOn(A)
+ return
+ if(modifiers["shift"])
+ ShiftClickOn(A)
+ return
+ if(modifiers["alt"])
+ AltClickOn(A)
+ return
+ if(modifiers["ctrl"])
+ CtrlClickOn(A)
+ return
+
+ if(world.time <= next_move)
+ return
+ A.attack_ghost(src)
+
+/mob/living/simple_animal/revenant/say(message)
+ return 0 //Revenants cannot speak out loud.
+
+/mob/living/simple_animal/revenant/Stat()
+ ..()
+ if(statpanel("Status"))
+ stat(null, "Current essence: [essence]E")
+
+/mob/living/simple_animal/revenant/New()
+ ..()
+ spawn(5)
+ if(src.mind)
+ 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 << "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 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 << "
"
+ if(!src.giveSpells())
+ message_admins("Revenant was created but has no mind. Trying again in five seconds.")
+ spawn(50)
+ if(!src.giveSpells())
+ message_admins("Revenant still has no mind. Deleting...")
+ qdel(src)
+
+/mob/living/simple_animal/revenant/proc/giveSpells()
+ if(src.mind)
+ 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
+ return 1
+ return 0
+
+/mob/living/simple_animal/revenant/death()
+ 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.")
+ 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
+ spawn(30)
+ src.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)
+ 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
+
+
+
+/mob/living/simple_animal/revenant/proc/change_essence_amount(var/essence_amt, var/silent = 0, var/source = null)
+ var/mob/living/simple_animal/revenant/user = usr
+ if(!istype(usr) || !usr)
+ return
+ if(!essence_amt)
+ return
+ user.essence += essence_amt
+ if(!silent)
+ if(essence_amt >= 0)
+ user << "Gained [essence_amt]E from [source]."
+ else
+ user << "Lost [essence_amt]E from [source]."
+ return 1
+
+
+
+/mob/living/simple_animal/revenant/proc/reveal(var/time, var/stun)
+ var/mob/living/simple_animal/revenant/R = usr
+ if(!istype(usr) || !usr)
+ return
+ R.revealed = 1
+ R.invisibility = 0
+ if(stun)
+ R.notransform = 1
+ spawn(time)
+ R.revealed = 0
+ R.invisibility = INVISIBILITY_OBSERVER
+ if(stun)
+ R.notransform = 0
diff --git a/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm b/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm
new file mode 100644
index 00000000000..9233819e109
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/revenant/revenant_abilities.dm
@@ -0,0 +1,167 @@
+//Harvest Essence: The bread and butter of the revenant. The basic way of harvesting additional essence.
+/obj/effect/proc_holder/spell/wizard/targeted/revenant_harvest
+ name = "Harvest (0E)"
+ desc = "Siphons the lingering spectral essence from a human, empowering yourself."
+ panel = "Revenant Abilities"
+ charge_max = 100 //Short cooldown
+ clothes_req = 0
+ range = 5
+ var/essence_drained = 0
+ var/draining
+ 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())
+ charge_counter = charge_max
+ return
+ for(var/mob/living/carbon/human/target in targets)
+ spawn(0)
+ if(draining)
+ user << "You are already siphoning the essence of a soul!"
+ return
+ if(target in drained_mobs)
+ user << "[target]'s soul is dead and empty."
+ return
+ if(target.stat != DEAD)
+ user << "This being's soul is too strong to harvest."
+ if(prob(10))
+ target << "You feel as if you are being watched."
+ return
+ draining = 1
+ essence_drained = 1
+ user << "You search for the still-living soul of [target]."
+ sleep(10)
+ if(target.ckey)
+ user << "Their soul burns with intelligence."
+ essence_drained += 3
+ sleep(20)
+ switch(essence_drained)
+ if(1 to 2)
+ user << "[target] will not yield much essence. Still, every bit counts."
+ if(3 to 4)
+ user << "[target] will yield an average amount of essence."
+ if(5 to INFINITY)
+ user << "Such a feast! [target] will yield much essence to you."
+ sleep(30)
+ if(!in_range(user, target))
+ user << "You are not close enough to siphon [target]'s soul. The link has been broken."
+ draining = 0
+ return
+ if(!target.stat)
+ user << "They are now powerful enough to fight off your draining."
+ target << "You feel something tugging across your body before subsiding."
+ user << "You begin siphoning essence from [target]'s soul. You can not move while this is happening."
+ if(target.stat != DEAD)
+ target << "You feel a horribly unpleasant draining sensation as your grip on life weakens..."
+ user.icon_state = "revenant_draining"
+ user.notransform = 1
+ user.revealed = 1
+ user.invisibility = 0
+ target.visible_message("[target] suddenly rises slightly into the air, their skin turning an ashy gray.")
+ target.Beam(user,icon_state="drain_life",icon='icons/effects/effects.dmi',time=80)
+ target.death(0)
+ target.visible_message("[target] gently slumps back onto the ground.")
+ user.icon_state = "revenant_idle"
+ user.change_essence_amount(essence_drained * 5, 0, target)
+ user << "[target]'s soul has been considerably weakened and will yield no more essence for the time being."
+ user.revealed = 0
+ user.notransform = 0
+ user.invisibility = INVISIBILITY_OBSERVER
+ drained_mobs.Add(target)
+ draining = 0
+
+
+//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob for 5E.
+/obj/effect/proc_holder/spell/wizard/targeted/revenant_transmit
+ name = "Transmit (5E)"
+ desc = "Telepathically transmits a message to the target."
+ panel = "Revenant Abilities (Locked)"
+ charge_max = 50
+ clothes_req = 0
+ range = -1
+ include_user = 1
+ 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]!"
+ charge_counter = charge_max
+ return
+ if(locked && essence_check(5, 1))
+ usr << "You have unlocked Transmit!"
+ locked = 0
+ charge_counter = charge_max
+ panel = "Revenant Abilities"
+ 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, "")
+ if(!msg)
+ charge_counter = charge_max
+ return
+ usr << "You transmit to [M]: [msg]"
+ M << "A strange voice resonates in your head... [msg]"
+
+
+//Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people.
+/obj/effect/proc_holder/spell/wizard/aoe_turf/revenant_light
+ name = "Overload Light (25E)"
+ desc = "Directs a large amount of essence into an electrical light, causing an impressive light show."
+ panel = "Revenant Abilities (Locked)"
+ charge_max = 300
+ clothes_req = 0
+ range = 1
+ 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
+ charge_counter = charge_max
+ return
+ if(locked)
+ charge_counter = charge_max
+ return
+ if(!user.wallcheck())
+ charge_counter = charge_max
+ return
+ if(!essence_check(25))
+ charge_counter = charge_max
+ return
+ for(var/turf/T in targets)
+ spawn(0)
+ for(var/obj/machinery/light/L in T.contents)
+ spawn(0)
+ if(!L.on)
+ return
+ L.visible_message("\The [L] suddenly flares brightly and begins to spark!")
+ sleep(20)
+ for(var/mob/living/M in orange(4, L))
+ if(M == user)
+ return
+ M.Beam(L,icon_state="lightning",icon='icons/effects/effects.dmi',time=5)
+ M.electrocute_act(25, "[L.name]")
+ playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
+ user.reveal(50, 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
new file mode 100644
index 00000000000..bd44a92f228
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/revenant/revenant_spawn_event.dm
@@ -0,0 +1,54 @@
+/datum/event/revenant
+ var/key_of_revenant
+
+
+
+/datum/event/revenant/proc/get_revenant(var/end_if_fail = 0)
+ key_of_revenant = null
+ if(!key_of_revenant)
+ var/list/candidates = get_candidates(BE_REVENANT)
+ if(!candidates.len)
+ if(end_if_fail)
+ return 0
+ return find_revenant()
+ var/client/C = pick(candidates)
+ key_of_revenant = C.key
+ if(!key_of_revenant)
+ if(end_if_fail)
+ return 0
+ return find_revenant()
+ var/datum/mind/player_mind = new /datum/mind(key_of_revenant)
+ player_mind.active = 1
+ var/list/spawn_locs = list()
+ 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)
+ 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.")
+ 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...")
+ 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.")
+ return 0
+ message_admins("Unfortunately, no candidates were available for becoming a Revenant. Shutting down. :(")
+ return kill()
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 62998f37b7c..9d109c9727c 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 2a003edd4f0..6ef7e6484e5 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/paradise.dme b/paradise.dme
index eecaee51b2c..9e53039679d 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1436,6 +1436,9 @@
#include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\undead.dm"
+#include "code\modules\mob\living\simple_animal\revenant\revenant.dm"
+#include "code\modules\mob\living\simple_animal\revenant\revenant_abilities.dm"
+#include "code\modules\mob\living\simple_animal\revenant\revenant_spawn_event.dm"
#include "code\modules\mob\new_player\login.dm"
#include "code\modules\mob\new_player\logout.dm"
#include "code\modules\mob\new_player\new_player.dm"