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"