Complete Revenant Rewrite (#18522)

I'm unhappy with the way revenants are right now, and my code for them is pretty unsatisfactory in comparison to what I know now. Although revenants will still fill the same role of just being spookier ghosts, they'll be a bit more passive - incapable, for instance, of giving diseases to people. The new revenants will be called umbras and will use vitae instead of essence.

Total change list:

    Revenants have been renamed to umbras. Essence has been renamed to vitae. This may be temporary.
    Umbra spawn events are now weighted higher and spawn an unoccupied umbra. Ghosts are alerted to the umbra's position and may interact with it to take control of it.
    Umbras' health is not based on vitae but has a hard cap at 100.
    Umbras have a passive vitae drain each tick, defaulting at 0.01. If the umbra runs out of vitae, they will die irrevocably. They also slowly regenerate health by doing this.
    When an umbra dies, they leave behind umbral ashes that reform after one minute. They're difficult to see and can be scattered by activating them, although they also have high research levels if you're fast enough.
    Harvesting vitae from critical targets no longer kills them. Harvesting a target in general prohibits them from being harvested until five minutes later, but they can be drained again after that.
    EMPs revitalize umbras and give them hefty amounts of vitae due to their physical nature.
    Umbras have four abilities: Toggle Nightvision, Discordant Whisper, Possess, and Thoughtsteal.
        Toggle Nightvision is self-explanatory.
        Discordant Whisper is identical to the original revenant's transmit.
        Possess allows the umbra to slip into a human's body unnoticed. While in their body, umbras will slowly drain vitae from the human at a tiny rate - not enough to cause harm, but enough to induce adverse effects in the clueless human. These effects intensify over time and eventually lead to the umbra being forced out of their host.
        Thoughtsteal paralyzes a living human for several seconds while the umbra steals their memories. After several seconds, the umbra copies the notes of the target's memories and turns invisible - the hapless victim is stunned for several seconds afterwards and can't be Thoughtstolen by the same umbra again. Umbras have an objective to steal the memories of 25% of the station's population.
    Salt piles have been added, created by salt shaker or just by splashing salt. These piles will prevent an umbra from passing and reveal them briefly if they try.
This commit is contained in:
Xhuis
2016-06-16 18:58:07 -04:00
committed by oranges
parent d4769b4ee2
commit 527dddd95d
32 changed files with 643 additions and 1034 deletions

View File

@@ -72,6 +72,8 @@
#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian)) #define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian))
#define isumbra(A) (istype(A, /mob/living/simple_animal/umbra))
#define islimb(A) (istype(A, /obj/item/bodypart)) #define islimb(A) (istype(A, /obj/item/bodypart))
#define isbot(A) (istype(A, /mob/living/simple_animal/bot)) #define isbot(A) (istype(A, /mob/living/simple_animal/bot))

View File

@@ -21,7 +21,6 @@
#define ROLE_GANG "gangster" #define ROLE_GANG "gangster"
#define ROLE_SHADOWLING "shadowling" #define ROLE_SHADOWLING "shadowling"
#define ROLE_ABDUCTOR "abductor" #define ROLE_ABDUCTOR "abductor"
#define ROLE_REVENANT "revenant"
#define ROLE_HOG_GOD "hand of god: god" #define ROLE_HOG_GOD "hand of god: god"
#define ROLE_HOG_CULTIST "hand of god: cultist" #define ROLE_HOG_CULTIST "hand of god: cultist"
#define ROLE_DEVIL "devil" #define ROLE_DEVIL "devil"
@@ -45,7 +44,6 @@ var/global/list/special_roles = list(
ROLE_MONKEY = /datum/game_mode/monkey, ROLE_MONKEY = /datum/game_mode/monkey,
ROLE_GANG = /datum/game_mode/gang, ROLE_GANG = /datum/game_mode/gang,
ROLE_SHADOWLING = /datum/game_mode/shadowling, ROLE_SHADOWLING = /datum/game_mode/shadowling,
ROLE_REVENANT,
ROLE_ABDUCTOR = /datum/game_mode/abduction, ROLE_ABDUCTOR = /datum/game_mode/abduction,
ROLE_HOG_GOD = /datum/game_mode/hand_of_god, ROLE_HOG_GOD = /datum/game_mode/hand_of_god,
ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god, ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god,

View File

@@ -1,10 +0,0 @@
/datum/hud/revenant/New(mob/owner)
..()
healths = new /obj/screen/healths/revenant()
infodisplay += healths
/mob/living/simple_animal/revenant/create_mob_hud()
if(client && !hud_used)
hud_used = new /datum/hud/revenant(src)

View File

@@ -455,13 +455,6 @@
screen_loc = ui_health screen_loc = ui_health
mouse_opacity = 0 mouse_opacity = 0
/obj/screen/healths/revenant
name = "essence"
icon = 'icons/mob/actions.dmi'
icon_state = "bg_revenant"
screen_loc = ui_health
mouse_opacity = 0
/obj/screen/healthdoll /obj/screen/healthdoll
name = "health doll" name = "health doll"
screen_loc = ui_healthdoll screen_loc = ui_healthdoll

View File

@@ -2,8 +2,10 @@ This folder contains all "mini-antagonists" - antagonists that can still spice u
Currently, that list consists of: Currently, that list consists of:
-Abductors -Abductors
-Swarmers -Swarmers
-Prophets of sin
-The Jungle Fever virus (infected monkey bites human, human becomes another infected monkey) -The Jungle Fever virus (infected monkey bites human, human becomes another infected monkey)
-Morphs -Morphs
-Revenants -Sintouched crewmembers
-Slaughter demons -Slaughter demons
-Umbras (formerly revenants)
Please update this if you add another mini-antagonist. Please update this if you add another mini-antagonist.

View File

@@ -1,442 +0,0 @@
//Revenants: based off of wraiths from Goon
//"Ghosts" that are invisible and move like ghosts, cannot take damage while invisible
//Don't hear deadchat and are NOT normal ghosts
//Admin-spawn or random event
#define INVISIBILITY_REVENANT 50
/mob/living/simple_animal/revenant
name = "revenant"
desc = "A malevolent spirit."
icon = 'icons/mob/mob.dmi'
icon_state = "revenant_idle"
var/icon_idle = "revenant_idle"
var/icon_reveal = "revenant_revealed"
var/icon_stun = "revenant_stun"
var/icon_drain = "revenant_draining"
incorporeal_move = 3
invisibility = INVISIBILITY_REVENANT
health = INFINITY //Revenants don't use health, they use essence instead
maxHealth = INFINITY
layer = GHOST_LAYER
healable = 0
sight = SEE_SELF
see_invisible = SEE_INVISIBLE_NOLIGHTING
see_in_dark = 8
languages = ALL
response_help = "passes through"
response_disarm = "swings through"
response_harm = "punches through"
unsuitable_atmos_damage = 0
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway.
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = INFINITY
harm_intent_damage = 0
friendly = "touches"
status_flags = 0
wander = 0
density = 0
flying = 1
anchored = 1
mob_size = MOB_SIZE_TINY
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
speed = 1
unique_name = 1
hud_possible = list(ANTAG_HUD)
var/essence = 75 //The resource, and health, of revenants.
var/essence_regen_cap = 75 //The regeneration cap of essence (go figure); regenerates every Life() tick up to this amount.
var/essence_regenerating = 1 //If the revenant regenerates essence or not; 1 for yes, 0 for no
var/essence_regen_amount = 5 //How much essence regenerates
var/essence_accumulated = 0 //How much essence the revenant has stolen
var/revealed = 0 //If the revenant can take damage from normal sources.
var/unreveal_time = 0 //How long the revenant is revealed for, is about 2 seconds times this var.
var/unstun_time = 0 //How long the revenant is stunned for, is about 2 seconds times this var.
var/inhibited = 0 //If the revenant's abilities are blocked by a chaplain's power.
var/essence_drained = 0 //How much essence the revenant will drain from the corpse it's feasting on.
var/draining = 0 //If the revenant is draining someone.
var/list/drained_mobs = list() //Cannot harvest the same mob twice
var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?)
var/image/ghostimage = null //Visible to ghost with darkness off
/mob/living/simple_animal/revenant/New()
..()
ghostimage = image(src.icon,src,src.icon_state)
ghost_darkness_images |= ghostimage
updateallghostimages()
spawn(5)
if(src.mind)
src.mind.remove_all_antag()
src.mind.wipe_memory()
src << 'sound/effects/ghost.ogg'
src << "<br>"
src << "<span class='deadsay'><font size=3><b>You are a revenant.</b></font></span>"
src << "<b>Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.</b>"
src << "<b>You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.</b>"
src << "<b>You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.</b>"
src << "<b>To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.</b>"
src << "<b><i>You do not remember anything of your past lives, nor will you remember anything about this one after your death.</i></b>"
src << "<b>Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant to learn more.</b>"
var/datum/objective/revenant/objective = new
objective.owner = src.mind
src.mind.objectives += objective
src << "<b>Objective #1</b>: [objective.explanation_text]"
var/datum/objective/revenantFluff/objective2 = new
objective2.owner = src.mind
src.mind.objectives += objective2
src << "<b>Objective #2</b>: [objective2.explanation_text]"
ticker.mode.traitors |= src.mind //Necessary for announcing
AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null))
AddSpell(new /obj/effect/proc_holder/spell/targeted/revenant_transmit(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/overload(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/blight(null))
//Life, Stat, Hud Updates, and Say
/mob/living/simple_animal/revenant/Life()
if(revealed && essence <= 0)
death()
if(unreveal_time && world.time >= unreveal_time)
unreveal_time = 0
revealed = 0
incorporeal_move = 3
invisibility = INVISIBILITY_REVENANT
src << "<span class='revenboldnotice'>You are once more concealed.</span>"
if(unstun_time && world.time >= unstun_time)
unstun_time = 0
notransform = 0
src << "<span class='revenboldnotice'>You can move again!</span>"
if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
essence = min(essence_regen_cap, essence+essence_regen_amount)
update_action_buttons_icon() //because we update something required by our spells in life, we need to update our buttons
update_spooky_icon()
update_health_hud()
..()
/mob/living/simple_animal/revenant/Stat()
..()
if(statpanel("Status"))
stat(null, "Current essence: [essence]/[essence_regen_cap]E")
stat(null, "Stolen essence: [essence_accumulated]E")
stat(null, "Stolen perfect souls: [perfectsouls]")
/mob/living/simple_animal/revenant/update_health_hud()
if(hud_used)
var/essencecolor = "#8F48C6"
if(essence > essence_regen_cap)
essencecolor = "#9A5ACB" //oh boy you've got a lot of essence
else if(essence == 0)
essencecolor = "#1D2953" //oh jeez you're dying
hud_used.healths.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='[essencecolor]'>[essence]E</font></div>"
/mob/living/simple_animal/revenant/med_hud_set_health()
return //we use no hud
/mob/living/simple_animal/revenant/med_hud_set_status()
return //we use no hud
/mob/living/simple_animal/revenant/say(message)
if(!message)
return
log_say("[key_name(src)] : [message]")
var/rendered = "<span class='revennotice'><b>[src]</b> says, \"[message]\"</span>"
for(var/mob/M in mob_list)
if(istype(M, /mob/living/simple_animal/revenant))
M << rendered
if(isobserver(M))
var/link = FOLLOW_LINK(M, src)
M << "[link] [rendered]"
return
//Immunities
/mob/living/simple_animal/revenant/Process_Spacemove(movement_dir = 0)
return 1
/mob/living/simple_animal/revenant/ex_act(severity, target)
return 1 //Immune to the effects of explosions.
/mob/living/simple_animal/revenant/blob_act(obj/effect/blob/B)
return //blah blah blobs aren't in tune with the spirit world, or something.
/mob/living/simple_animal/revenant/singularity_act()
return //don't walk into the singularity expecting to find corpses, okay?
/mob/living/simple_animal/revenant/narsie_act()
return //most humans will now be either bones or harvesters, but we're still un-alive.
//damage, gibbing, and dying
/mob/living/simple_animal/revenant/attackby(obj/item/W, mob/living/user, params)
. = ..()
if(istype(W, /obj/item/weapon/nullrod))
visible_message("<span class='warning'>[src] violently flinches!</span>", \
"<span class='revendanger'>As \the [W] passes through you, you feel your essence draining away!</span>")
adjustBruteLoss(25) //hella effective
inhibited = 1
update_action_buttons_icon()
spawn(30)
inhibited = 0
update_action_buttons_icon()
/mob/living/simple_animal/revenant/adjustHealth(amount)
if(!revealed)
return 0
. = amount
essence = max(0, essence-amount)
update_health_hud()
if(essence == 0)
src << "<span class='revendanger'>You feel your essence fraying!</span>"
/mob/living/simple_animal/revenant/dust()
death()
/mob/living/simple_animal/revenant/gib()
death()
/mob/living/simple_animal/revenant/death()
if(!revealed || stat == DEAD) //Revenants cannot die if they aren't revealed //or are already dead
return 0
..(1)
ghost_darkness_images -= ghostimage
updateallghostimages()
src << "<span class='revendanger'>NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]...</span>"
notransform = 1
revealed = 1
invisibility = 0
playsound(src, 'sound/effects/screech.ogg', 100, 1)
visible_message("<span class='warning'>[src] lets out a waning screech as violet mist swirls around its dissolving body!</span>")
icon_state = "revenant_draining"
for(var/i = alpha, i > 0, i -= 10)
stoplag()
alpha = i
visible_message("<span class='danger'>[src]'s body breaks apart into a fine pile of blue dust.</span>")
var/reforming_essence = essence_regen_cap //retain the gained essence capacity
var/obj/item/weapon/ectoplasm/revenant/R = new(get_turf(src))
R.essence = max(reforming_essence - 15 * perfectsouls, 75) //minus any perfect souls
R.client_to_revive = src.client //If the essence reforms, the old revenant is put back in the body
ghostize()
qdel(src)
return
//reveal, stun, icon updates, cast checks, and essence changing
/mob/living/simple_animal/revenant/proc/reveal(time)
if(!src)
return
if(time <= 0)
return
revealed = 1
invisibility = 0
incorporeal_move = 0
if(!unreveal_time)
src << "<span class='revendanger'>You have been revealed!</span>"
unreveal_time = world.time + time
else
src << "<span class='revenwarning'>You have been revealed!</span>"
unreveal_time = unreveal_time + time
update_spooky_icon()
/mob/living/simple_animal/revenant/proc/stun(time)
if(!src)
return
if(time <= 0)
return
notransform = 1
if(!unstun_time)
src << "<span class='revendanger'>You cannot move!</span>"
unstun_time = world.time + time
else
src << "<span class='revenwarning'>You cannot move!</span>"
unstun_time = unstun_time + time
update_spooky_icon()
/mob/living/simple_animal/revenant/proc/update_spooky_icon()
if(revealed)
if(notransform)
if(draining)
icon_state = icon_drain
else
icon_state = icon_stun
else
icon_state = icon_reveal
else
icon_state = icon_idle
if(ghostimage)
ghostimage.icon_state = src.icon_state
updateallghostimages()
/mob/living/simple_animal/revenant/proc/castcheck(essence_cost)
if(!src)
return
var/turf/T = get_turf(src)
if(istype(T, /turf/closed))
src << "<span class='revenwarning'>You cannot use abilities from inside of a wall.</span>"
return 0
for(var/obj/O in T)
if(O.density && !O.CanPass(src, T, 5))
src << "<span class='revenwarning'>You cannot use abilities inside of a dense object.</span>"
return 0
if(src.inhibited)
src << "<span class='revenwarning'>Your powers have been suppressed by nulling energy!</span>"
return 0
if(!src.change_essence_amount(essence_cost, 1))
src << "<span class='revenwarning'>You lack the essence to use that ability.</span>"
return 0
return 1
/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = 0, source = null)
if(!src)
return
if(essence + essence_amt <= 0)
return
essence = max(0, essence+essence_amt)
update_action_buttons_icon()
update_health_hud()
if(essence_amt > 0)
essence_accumulated = max(0, essence_accumulated+essence_amt)
if(!silent)
if(essence_amt > 0)
src << "<span class='revennotice'>Gained [essence_amt]E from [source].</span>"
else
src << "<span class='revenminor'>Lost [essence_amt]E from [source].</span>"
return 1
//reforming
/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/essence = 75 //the maximum essence of the reforming revenant
var/reforming = 1
var/inert = 0
var/client/client_to_revive
/obj/item/weapon/ectoplasm/revenant/New()
..()
spawn(600) //1 minute
if(src && reforming)
reforming = 0
return reform()
else
inert = 1
visible_message("<span class='warning'>[src] settles down and seems lifeless.</span>")
return
/obj/item/weapon/ectoplasm/revenant/attack_self(mob/user)
if(!reforming || inert)
return ..()
user.visible_message("<span class='notice'>[user] scatters [src] in all directions.</span>", \
"<span class='notice'>You scatter [src] across the area. The particles slowly fade away.</span>")
user.drop_item()
qdel(src)
/obj/item/weapon/ectoplasm/revenant/throw_impact(atom/hit_atom)
..()
if(inert)
return
visible_message("<span class='notice'>[src] breaks into particles upon impact, which fade away to nothingness.</span>")
qdel(src)
/obj/item/weapon/ectoplasm/revenant/examine(mob/user)
..()
if(inert)
user << "<span class='revennotice'>It seems inert.</span>"
else if(reforming)
user << "<span class='revenwarning'>It is shifting and distorted. It would be wise to destroy this.</span>"
/obj/item/weapon/ectoplasm/revenant/proc/reform()
if(!src || qdeleted(src) || inert)
return
var/key_of_revenant
message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.")
loc = get_turf(src) //In case it's in a backpack or someone's hand
var/mob/living/simple_animal/revenant/R = new(get_turf(src))
if(client_to_revive)
for(var/mob/M in dead_mob_list)
if(M.client == client_to_revive) //Only recreates the mob if the mob the client is in is dead
R.client = client_to_revive
key_of_revenant = client_to_revive.key
if(!key_of_revenant)
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/list/candidates = get_candidates(ROLE_REVENANT)
if(!candidates.len)
qdel(R)
message_admins("No candidates were found for the new revenant. Oh well!")
inert = 1
visible_message("<span class='revenwarning'>[src] settles down and seems lifeless.</span>")
return 0
var/client/C = pick(candidates)
key_of_revenant = C.key
if(!key_of_revenant)
qdel(R)
message_admins("No ckey was found for the new revenant. Oh well!")
inert = 1
visible_message("<span class='revenwarning'>[src] settles down and seems lifeless.</span>")
return 0
var/datum/mind/player_mind = new /datum/mind(key_of_revenant)
R.essence_regen_cap = essence
R.essence = R.essence_regen_cap
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 [client_to_revive ? "re":""]made into a revenant by reforming ectoplasm.")
log_game("[key_of_revenant] was [client_to_revive ? "re":""]made as a revenant by reforming ectoplasm.")
visible_message("<span class='revenboldnotice'>[src] suddenly rises into the air before fading away.</span>")
qdel(src)
if(src) //Should never happen, but just in case
inert = 1
return 1
//objectives
/datum/objective/revenant
dangerrating = 10
var/targetAmount = 100
/datum/objective/revenant/New()
targetAmount = rand(350,600)
explanation_text = "Absorb [targetAmount] points of essence from humans."
..()
/datum/objective/revenant/check_completion()
if(!istype(owner.current, /mob/living/simple_animal/revenant))
return 0
var/mob/living/simple_animal/revenant/R = owner.current
if(!R || R.stat == DEAD)
return 0
var/essence_stolen = R.essence_accumulated
if(essence_stolen < targetAmount)
return 0
return 1
/datum/objective/revenantFluff
dangerrating = 0
/datum/objective/revenantFluff/New()
var/list/explanationTexts = list("Assist and exacerbate existing threats at critical moments.", \
"Avoid killing in plain sight.", \
"Cause as much chaos and anger as you can without being killed.", \
"Damage and render as much of the station rusted and unusable as possible.", \
"Disable and cause malfunctions in as many machines as possible.", \
"Ensure that any holy weapons are rendered unusable.", \
"Hinder the crew while attempting to avoid being noticed.", \
"Make the crew as miserable as possible.", \
"Make the clown as miserable as possible.", \
"Make the captain as miserable as possible.", \
"Prevent the use of energy weapons where possible.")
explanation_text = pick(explanationTexts)
..()
/datum/objective/revenantFluff/check_completion()
return 1

View File

@@ -1,385 +0,0 @@
//Harvest; activated ly clicking the target, will try to drain their essence.
/mob/living/simple_animal/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly.
A.examine(src)
if(ishuman(A))
if(A in drained_mobs)
src << "<span class='revenwarning'>[A]'s soul is dead and empty.</span>" //feedback at any range
else if(in_range(src, A))
Harvest(A)
/mob/living/simple_animal/revenant/proc/Harvest(mob/living/carbon/human/target)
if(!castcheck(0))
return
if(draining)
src << "<span class='revenwarning'>You are already siphoning the essence of a soul!</span>"
return
if(!target.stat)
src << "<span class='revennotice'>This being's soul is too strong to harvest.</span>"
if(prob(10))
target << "You feel as if you are being watched."
return
draining = 1
essence_drained += rand(15, 20)
src << "<span class='revennotice'>You search for the soul of [target].</span>"
if(do_after(src, rand(10, 20), 0, target)) //did they get deleted in that second?
if(target.ckey)
src << "<span class='revennotice'>Their soul burns with intelligence.</span>"
essence_drained += rand(20, 30)
if(target.stat != DEAD)
src << "<span class='revennotice'>Their soul blazes with life!</span>"
essence_drained += rand(40, 50)
else
src << "<span class='revennotice'>Their soul is weak and faltering.</span>"
if(do_after(src, rand(15, 20), 0, target)) //did they get deleted NOW?
switch(essence_drained)
if(1 to 30)
src << "<span class='revennotice'>[target] will not yield much essence. Still, every bit counts.</span>"
if(30 to 70)
src << "<span class='revennotice'>[target] will yield an average amount of essence.</span>"
if(70 to 90)
src << "<span class='revenboldnotice'>Such a feast! [target] will yield much essence to you.</span>"
if(90 to INFINITY)
src << "<span class='revenbignotice'>Ah, the perfect soul. [target] will yield massive amounts of essence to you.</span>"
if(do_after(src, rand(15, 25), 0, target)) //how about now
if(!target.stat)
src << "<span class='revenwarning'>They are now powerful enough to fight off your draining.</span>"
target << "<span class='boldannounce'>You feel something tugging across your body before subsiding.</span>"
draining = 0
essence_drained = 0
return //hey, wait a minute...
src << "<span class='revenminor'>You begin siphoning essence from [target]'s soul.</span>"
if(target.stat != DEAD)
target << "<span class='warning'>You feel a horribly unpleasant draining sensation as your grip on life weakens...</span>"
reveal(46)
stun(46)
target.visible_message("<span class='warning'>[target] suddenly rises slightly into the air, their skin turning an ashy gray.</span>")
var/datum/beam/B = Beam(target,icon_state="drain_life",icon='icons/effects/effects.dmi',time=46)
if(do_after(src, 46, 0, target)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining.
qdel(B)
change_essence_amount(essence_drained, 0, target)
if(essence_drained <= 90 && target.stat != DEAD)
essence_regen_cap += 5
src << "<span class='revenboldnotice'>The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].</span>"
if(essence_drained > 90)
essence_regen_cap += 15
perfectsouls += 1
src << "<span class='revenboldnotice'>The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap].</span>"
src << "<span class='revennotice'>[target]'s soul has been considerably weakened and will yield no more essence for the time being.</span>"
target.visible_message("<span class='warning'>[target] slumps onto the ground.</span>", \
"<span class='revenwarning'>Violets lights, dancing in your vision, getting clo--</span>")
drained_mobs.Add(target)
target.death(0)
else
qdel(B)
src << "<span class='revenwarning'>[target ? "[target] has":"They have"] been drawn out of your grasp. The link has been broken.</span>"
draining = 0
essence_drained = 0
if(target) //Wait, target is WHERE NOW?
target.visible_message("<span class='warning'>[target] slumps onto the ground.</span>", \
"<span class='revenwarning'>Violets lights, dancing in your vision, receding--</span>")
return
else
src << "<span class='revenwarning'>You are not close enough to siphon [target ? "[target]'s":"their"] soul. The link has been broken.</span>"
draining = 0
essence_drained = 0
return
draining = 0
essence_drained = 0
return
//Toggle night vision: lets the revenant toggle its night vision
/obj/effect/proc_holder/spell/targeted/night_vision/revenant
charge_max = 0
panel = "Revenant Abilities"
message = "<span class='revennotice'>You toggle your night vision.</span>"
action_icon_state = "r_nightvision"
action_background_icon_state = "bg_revenant"
//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob
/obj/effect/proc_holder/spell/targeted/revenant_transmit
name = "Transmit"
desc = "Telepathically transmits a message to the target."
panel = "Revenant Abilities"
charge_max = 0
clothes_req = 0
range = 7
include_user = 0
action_icon_state = "r_transmit"
action_background_icon_state = "bg_revenant"
/obj/effect/proc_holder/spell/targeted/revenant_transmit/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
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
log_say("RevenantTransmit: [key_name(user)]->[key_name(M)] : [msg]")
user << "<span class='revenboldnotice'>You transmit to [M]:</span> <span class='revennotice'>[msg]</span>"
M << "<span class='revenboldnotice'>An alien voice resonates from all around...</span> <span class='revennotice'>[msg]</span>"
for(var/ded in dead_mob_list)
if(!isobserver(ded))
continue
var/follow_rev = FOLLOW_LINK(ded, user)
var/follow_whispee = FOLLOW_LINK(ded, M)
ded << "[follow_rev] \
<span class='name'>[user]</span> \
<span class='revenboldnotice'>Revenant Transmit --></span> \
[follow_whispee] \
<span class='name'>[M]</span> \
<span class='revennotice'>[msg]</span>"
/obj/effect/proc_holder/spell/aoe_turf/revenant
clothes_req = 0
action_background_icon_state = "bg_revenant"
panel = "Revenant Abilities (Locked)"
name = "Report this to a coder"
var/reveal = 80 //How long it reveals the revenant in deciseconds
var/stun = 20 //How long it stuns the revenant in deciseconds
var/locked = 1 //If it's locked and needs to be unlocked before use
var/unlock_amount = 100 //How much essence it costs to unlock
var/cast_amount = 50 //How much essence it costs to use
/obj/effect/proc_holder/spell/aoe_turf/revenant/New()
..()
if(locked)
name = "[initial(name)] ([unlock_amount]E)"
else
name = "[initial(name)] ([cast_amount]E)"
/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr)
if(!istype(user)) //Badmins, no. Badmins, don't do it.
if(charge_counter < charge_max)
return 0
return 1
if(user.inhibited)
return 0
if(charge_counter < charge_max)
return 0
if(locked)
if(user.essence <= unlock_amount)
return 0
if(user.essence <= cast_amount)
return 0
return 1
/obj/effect/proc_holder/spell/aoe_turf/revenant/proc/attempt_cast(mob/living/simple_animal/revenant/user = usr)
if(!istype(user)) //If you're not a revenant, it works. Please, please, please don't give this to a non-revenant.
name = "[initial(name)]"
if(locked)
panel = "Revenant Abilities"
locked = 0
return 1
if(locked)
if(!user.castcheck(-unlock_amount))
charge_counter = charge_max
return 0
name = "[initial(name)] ([cast_amount]E)"
user << "<span class='revennotice'>You have unlocked [initial(name)]!</span>"
panel = "Revenant Abilities"
locked = 0
charge_counter = charge_max
return 0
if(!user.castcheck(-cast_amount))
charge_counter = charge_max
return 0
name = "[initial(name)] ([cast_amount]E)"
user.reveal(reveal)
user.stun(stun)
if(action)
action.UpdateButtonIcon()
return 1
//Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people.
/obj/effect/proc_holder/spell/aoe_turf/revenant/overload
name = "Overload Lights"
desc = "Directs a large amount of essence into nearby electrical lights, causing lights to shock those nearby."
charge_max = 200
range = 5
stun = 30
cast_amount = 40
var/shock_range = 2
var/shock_damage = 20
action_icon_state = "overload_lights"
/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
if(attempt_cast(user))
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("<span class='warning'><b>\The [L] suddenly flares brightly and begins to spark!</span>")
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(4, 0, L)
s.start()
PoolOrNew(/obj/effect/overlay/temp/revenant, L.loc)
sleep(20)
if(!L.on) //wait, wait, don't shock me
return
flick("[L.base_state]2", L)
for(var/mob/living/carbon/human/M in view(shock_range, L))
if(M == user)
continue
L.Beam(M,icon_state="purple_lightning",icon='icons/effects/effects.dmi',time=5)
M.electrocute_act(shock_damage, L, safety=1)
var/datum/effect_system/spark_spread/z = new /datum/effect_system/spark_spread
z.set_up(4, 0, M)
z.start()
playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
//Defile: Corrupts nearby stuff, unblesses floor tiles.
/obj/effect/proc_holder/spell/aoe_turf/revenant/defile
name = "Defile"
desc = "Twists and corrupts the nearby area as well as dispelling holy auras on floors."
charge_max = 150
range = 4
stun = 10
reveal = 40
unlock_amount = 75
cast_amount = 30
action_icon_state = "defile"
/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
if(attempt_cast(user))
for(var/turf/T in targets)
spawn(0)
if(T.flags & NOJAUNT)
T.flags -= NOJAUNT
PoolOrNew(/obj/effect/overlay/temp/revenant, T)
if(!istype(T, /turf/open/floor/plating) && !istype(T, /turf/open/floor/engine/cult) && istype(T, /turf/open/floor) && prob(15))
var/turf/open/floor/floor = T
if(floor.intact)
floor.builtin_tile.loc = floor
floor.broken = 0
floor.burnt = 0
floor.make_plating(1)
if(T.type == /turf/closed/wall && prob(15))
PoolOrNew(/obj/effect/overlay/temp/revenant, T)
T.ChangeTurf(/turf/closed/wall/rust)
if(T.type == /turf/closed/wall/r_wall && prob(10))
PoolOrNew(/obj/effect/overlay/temp/revenant, T)
T.ChangeTurf(/turf/closed/wall/r_wall/rust)
for(var/obj/structure/closet/closet in T.contents)
closet.open()
for(var/obj/structure/bodycontainer/corpseholder in T.contents)
if(corpseholder.connected.loc == corpseholder)
corpseholder.open()
for(var/obj/machinery/dna_scannernew/dna in T.contents)
dna.open_machine()
for(var/obj/structure/window/window in T.contents)
window.take_damage(rand(30,80))
if(window && window.fulltile)
PoolOrNew(/obj/effect/overlay/temp/revenant/cracks, window.loc)
for(var/obj/machinery/light/light in T.contents)
light.flicker(20) //spooky
//Malfunction: Makes bad stuff happen to robots and machines.
/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction
name = "Malfunction"
desc = "Corrupts and damages nearby machines and mechanical objects."
charge_max = 200
range = 4
cast_amount = 45
unlock_amount = 150
action_icon_state = "malfunction"
//A note to future coders: do not replace this with an EMP because it will wreck malf AIs and gang dominators and everyone will hate you.
/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
if(attempt_cast(user))
for(var/turf/T in targets)
spawn(0)
for(var/mob/living/simple_animal/bot/bot in T.contents)
if(!bot.emagged)
PoolOrNew(/obj/effect/overlay/temp/revenant, bot.loc)
bot.locked = 0
bot.open = 1
bot.emag_act()
for(var/mob/living/carbon/human/human in T.contents)
if(human == user)
continue
human << "<span class='revenwarning'>You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")].</span>"
PoolOrNew(/obj/effect/overlay/temp/revenant, human.loc)
human.emp_act(1)
for(var/obj/thing in T.contents)
if(istype(thing, /obj/machinery/dominator) || istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes)) //Doesn't work on dominators, SMES and APCs, to prevent kekkery
continue
if(prob(20))
if(prob(50))
PoolOrNew(/obj/effect/overlay/temp/revenant, thing.loc)
thing.emag_act(null)
else
if(!istype(thing, /obj/machinery/clonepod)) //I hate everything but mostly the fact there's no better way to do this without just not affecting it at all
thing.emp_act(1)
for(var/mob/living/silicon/robot/S in T.contents) //Only works on cyborgs, not AI
playsound(S, 'sound/machines/warning-buzzer.ogg', 50, 1)
PoolOrNew(/obj/effect/overlay/temp/revenant, S.loc)
S.spark_system.start()
S.emp_act(1)
//Blight: Infects nearby humans and in general messes living stuff up.
/obj/effect/proc_holder/spell/aoe_turf/revenant/blight
name = "Blight"
desc = "Causes nearby living things to waste away."
charge_max = 200
range = 3
reveal = 50
cast_amount = 50
unlock_amount = 200
action_icon_state = "blight"
/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
if(attempt_cast(user))
for(var/turf/T in targets)
spawn(0)
for(var/mob/living/mob in T.contents)
if(mob == user)
continue
PoolOrNew(/obj/effect/overlay/temp/revenant, mob.loc)
if(iscarbon(mob))
if(ishuman(mob))
var/mob/living/carbon/human/H = mob
if(H.dna && H.dna.species)
H.dna.species.handle_mutant_bodyparts(H,"#1d2953")
H.dna.species.handle_hair(H,"#1d2953")
var/old_color = H.color
H.color = "#1d2953"
spawn(20)
if(H && H.dna && H.dna.species)
H.dna.species.handle_mutant_bodyparts(H)
H.dna.species.handle_hair(H)
H.color = old_color
var/blightfound = 0
for(var/datum/disease/revblight/blight in H.viruses)
blightfound = 1
if(blight.stage < 5)
blight.stage++
if(!blightfound)
H.AddDisease(new /datum/disease/revblight)
H << "<span class='revenminor'>You feel [pick("suddenly sick", "a surge of nausea", "like your skin is <span class='italics'>wrong</span>")].</span>"
else
if(mob.reagents)
mob.reagents.add_reagent("plasma", 5)
else
mob.adjustToxLoss(5)
for(var/obj/effect/spacevine/vine in T.contents) //Fucking with botanists, the ability.
vine.color = "#823abb"
PoolOrNew(/obj/effect/overlay/temp/revenant, vine.loc)
spawn(10)
if(vine)
qdel(vine)
for(var/obj/effect/glowshroom/shroom in T.contents)
shroom.color = "#823abb"
PoolOrNew(/obj/effect/overlay/temp/revenant, shroom.loc)
spawn(10)
if(shroom)
qdel(shroom)
for(var/obj/machinery/hydroponics/tray in T.contents)
PoolOrNew(/obj/effect/overlay/temp/revenant, tray.loc)
tray.pestlevel = rand(8, 10)
tray.weedlevel = rand(8, 10)
tray.toxic = rand(45, 55)

View File

@@ -1,68 +0,0 @@
/datum/disease/revblight
name = "Unnatural Wasting"
max_stages = 5
stage_prob = 10
spread_flags = NON_CONTAGIOUS
cure_text = "Holy water or extensive rest."
spread_text = "A burst of unholy energy"
cures = list("holywater")
cure_chance = 50 //higher chance to cure, because revenants are assholes
agent = "Unholy Forces"
viable_mobtypes = list(/mob/living/carbon/human)
disease_flags = CURABLE
permeability_mod = 1
severity = BIOHAZARD
var/stagedamage = 0 //Highest stage reached.
var/finalstage = 0 //Because we're spawning off the cure in the final stage, we need to check if we've done the final stage's effects.
/datum/disease/revblight/cure()
if(affected_mob)
affected_mob << "<span class='notice'>You feel better.</span>"
if(affected_mob.dna && affected_mob.dna.species)
affected_mob.dna.species.handle_mutant_bodyparts(affected_mob)
..()
/datum/disease/revblight/stage_act()
if(!finalstage && affected_mob.lying && prob(stage*5))
cure()
return
if(!finalstage && prob(stage*3))
affected_mob << "<span class='revennotice'>You suddenly feel [pick("sick and tired", "disoriented", "tired and confused", "nauseated", "faint", "dizzy")]...</span>"
affected_mob.confused += 10
affected_mob.adjustStaminaLoss(10)
PoolOrNew(/obj/effect/overlay/temp/revenant, affected_mob.loc)
if(!finalstage && stagedamage < stage)
stagedamage++
affected_mob.adjustToxLoss(stage*3) //should, normally, do about 45 toxin damage.
PoolOrNew(/obj/effect/overlay/temp/revenant, affected_mob.loc)
if(!finalstage && prob(45))
affected_mob.adjustStaminaLoss(stage*2)
..() //So we don't increase a stage before applying the stage damage.
switch(stage)
if(2)
if(prob(5))
affected_mob.emote("pale")
if(3)
if(prob(10))
affected_mob.emote(pick("pale","shiver"))
if(4)
if(prob(15))
affected_mob.emote(pick("pale","shiver","cries"))
if(5)
if(!finalstage)
finalstage = 1
affected_mob << "<span class='revenbignotice'>You feel like [pick("nothing's worth it anymore", "nobody ever needed your help", "nothing you did mattered", "everything you tried to do was worthless")].</span>"
affected_mob.adjustStaminaLoss(45)
PoolOrNew(/obj/effect/overlay/temp/revenant, affected_mob.loc)
if(affected_mob.dna && affected_mob.dna.species)
affected_mob.dna.species.handle_mutant_bodyparts(affected_mob,"#1d2953")
affected_mob.dna.species.handle_hair(affected_mob,"#1d2953")
affected_mob.visible_message("<span class='warning'>[affected_mob] looks terrifyingly gaunt...</span>", "<span class='revennotice'>You suddenly feel like your skin is <span class='italics'>wrong</span>...</span>")
var/old_color = affected_mob.color
affected_mob.color = "#1d2953"
spawn(100)
if(affected_mob)
affected_mob = old_color
cure()
else
return

View File

@@ -1,63 +0,0 @@
#define REVENANT_SPAWN_THRESHOLD 20
/datum/round_event_control/revenant
name = "Spawn Revenant" // Did you mean 'griefghost'?
typepath = /datum/round_event/ghost_role/revenant
weight = 7
max_occurrences = 1
earliest_start = 12000 //Meant to mix things up early-game.
min_players = 5
/datum/round_event/ghost_role/revenant
var/force_spawn
role_name = "revenant"
/datum/round_event/ghost_role/revenant/New(my_force_spawn = FALSE)
..()
force_spawn = my_force_spawn
/datum/round_event/ghost_role/revenant/spawn_role()
if(!force_spawn)
var/deadMobs = 0
for(var/mob/M in dead_mob_list)
deadMobs++
if(deadMobs < REVENANT_SPAWN_THRESHOLD)
message_admins("Event attempted to spawn a revenant, but there were only [deadMobs]/[REVENANT_SPAWN_THRESHOLD] dead mobs.")
return WAITING_FOR_SOMETHING
var/list/mob/dead/observer/candidates = get_candidates("revenant", null, ROLE_REVENANT)
if(!candidates.len)
return NOT_ENOUGH_PLAYERS
var/mob/dead/observer/selected = popleft(candidates)
var/datum/mind/player_mind = new /datum/mind(selected.key)
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("revenantspawn")
spawn_locs += L.loc
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 give up and cry
return MAP_ERROR
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("[player_mind.key] has been made into a revenant by an event.")
log_game("[player_mind.key] was spawned as a revenant by an event.")
spawned_mobs += revvie
return SUCCESSFUL_SPAWN

View File

@@ -0,0 +1,369 @@
#define UMBRA_INVISIBILITY 50
#define UMBRA_VITAE_DRAIN_RATE 0.01 //How much vitae is drained per tick to sustain the umbra. Set this to higher values to make umbras need to harvest vitae more often.
#define UMBRA_MAX_HARVEST_COOLDOWN 3000 //In deciseconds, how long it takes for a harvested target to become eligible for draining again.
#define UMBRA_POSSESSION_THRESHOLD_WARNING 60 //In ticks, how long it takes before a possessed human starts showing signs of possession
#define UMBRA_POSSESSION_THRESHOLD_DANGER 150 //In ticks, how long it takes before a possessed human starts forcing out the umbra
#define UMBRA_POSSESSION_THRESHOLD_FORCED_OUT 155 //In ticks, how long it takes before a possessed human forces out the umbra
/*
Umbras are residual entities created by the dying who possess enough anger or determination that they never fully pass on.
Physically, they're incorporeal and invisible. Umbras are formed of a steadily-decaying electromagnetic field with a drive to sustain itself.
Umbras do this by feeding on a substance, found in the dead or dying, known as vitae.
Vitae is most closely comparable to adrenaline in that is produced by creatures at times of distress. For this reason, almost all dead creatures have vitae in one way or another.
Biologically, it's indistinguishable from normal blood, but vitae is what allows creatures to survive grievous wounds or cling to life in critical condition. It provides a large amount of energy.
It's for this reason that umbras desire it. Vitae serves as a potent energy source to a living thing, and umbras can use the energy of this vitae to sustain themselves.
Without enough vitae, the field that sustains an umbra will break down and weaken. If the umbra has no vitae at all, it will permanently dissipate.
Umbras are not without their weaknesses. Despite being invisible to the naked eye and untouchable, certain things can restrict, weaken, or outright harm them.
Piles of salt on the ground will prevent an umbra's passage, making areas encircled in it completely inaccessible to even the most determined umbra.
In addition, objects and artifacts of a holy nature can force an umbra to manifest or draw away some of the energy that it's gleaned through vitae.
When an umbra dies, two things can occur. If the umbra died from passive vitae drain, it will be dead forever, with no way to bring it back.
However, if the umbra is slain forcibly and still has vitae, the vitae possesses enough power to coalesce a part of the umbra into umbral ashes.
These "ashes" will, given around a full minute, re-form into another umbra. This umbra typically possesses the memories and consciousness of the old one, but may be a completely new mind as well.
Although these umbral ashes make umbras resilient, they can be killed permanently by scattering the ashes or destroying them, thus separating the vitae from the umbra's remains.
*/
/mob/living/simple_animal/umbra
name = "umbra"
real_name = "umbra"
desc = "A translucent, cobalt-blue apparition floating several feet in the air."
invisibility = UMBRA_INVISIBILITY
icon = 'icons/mob/mob.dmi'
icon_state = "umbra"
icon_living = "umbra"
layer = GHOST_LAYER
alpha = 175 //To show invisibility
health = 100
maxHealth = 100
healable = 0
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
healable = FALSE
friendly = "passes through"
speak_emote = list("murmurs")
emote_hear = list("murmurs")
languages = ALL
incorporeal_move = 3
flying = TRUE
stop_automated_movement = TRUE
wander = FALSE
sight = SEE_SELF
see_invisible = SEE_INVISIBLE_NOLIGHTING
see_in_dark = 8
minbodytemp = 0
maxbodytemp = INFINITY
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
var/vitae = 10 //The amount of vitae captured by the umbra
var/vitae_cap = 100 //How much vitae a single umbra can hold
var/breaking_apart = FALSE //If the umbra is currently dying
var/harvesting = FALSE //If the umbra is harvesting a soul
var/list/recently_drained = list() //Mobs that have been drained in the last five minutes by the umbra
var/list/total_drained = list() //Mobs that have been drained, period
var/mob/living/carbon/human/possessed //The human that an umbra is inside of, if applicable
var/time_possessing = 0 //How long an umbra has been in possession of a single target.
var/list/lobotomized = list() //Mobs that have had their memories stolen by the umbra
var/playstyle_string = "<span class='umbra_large'><b>You are an umbra,</b></span><b> and you aren't quite sure how you're alive. You don't remember much, but you remember slipping away, \
lsoing your hold on life. You died, but here you are... somehow. You can't be quite sure how this happened, but you're pretty sure that it won't last long. Already you feel this strange \
form of life weakening. You need to find a way to sustain yourself, and you think you might have an idea.\n\
\n\
You seem to be invisible and incorporeal, so you don't think that anyone can see, feel, or otherwise perceive you right now, but there has to be some way that you managed not to just fully \
die and instead ended up like this. You think that if you find a corpse, or perhaps someone still alive but in critical condition, you'd be able to drain whatever kept you in this state \
from them. At first the idea sounds horrible - stealing their very life force - but as you continue to weaken it sounds more and more appealing... you think it'd be as simple as interacting \
with them. Something in the back of your mind tells you to call this life force <i>vitae</i>.\n\
\n\
In any case, you can definitely feel something else. You feel partially alive, but partially dead. Perhaps somewhere in between. Nonetheless, you might be able to influence both realms. \
You can see clearly in the dark, and you seem to have a sort of limited telepathic capabilities, plus some other things. You'll have to experiment with your new form to find out what you \
exactly you can do."
//Creation, destruction, life, and death
/mob/living/simple_animal/umbra/New()
..()
if(prob(1))
name = "grief ghost"
real_name = "grief ghost"
desc = "You wonder how something that produces so much salt can be weak to it."
AddSpell(new/obj/effect/proc_holder/spell/targeted/night_vision/umbra(null))
AddSpell(new/obj/effect/proc_holder/spell/targeted/discordant_whisper(null))
AddSpell(new/obj/effect/proc_holder/spell/targeted/possess(null))
AddSpell(new/obj/effect/proc_holder/spell/targeted/thoughtsteal(null))
/mob/living/simple_animal/umbra/Life()
..()
if(!possessed)
adjust_vitae(-UMBRA_VITAE_DRAIN_RATE, TRUE, "passive drain")
time_possessing = 0
else
if(possessed.reagents && possessed.reagents.has_reagent("sodiumchloride"))
unpossess(TRUE)
visible_message("<span class='warning'>[src] appears from nowhere, twitching and flickering!</span>", "<span class='userdanger'>AAAH! SALT!</span>")
immobilize(30)
reveal(50)
return
if(possessed.stat == DEAD)
unpossess()
return
adjust_vitae(UMBRA_VITAE_DRAIN_RATE, TRUE, "passive gain")
time_possessing++
switch(time_possessing)
if(0 to UMBRA_POSSESSION_THRESHOLD_WARNING)
if(prob(1))
possessed << "<span class='warning'>You feel [pick("watched", "not wholly yourself", "an intense craving for salt", "singularly odd", \
"a horrible dread in your heart")].</span>"
if(UMBRA_POSSESSION_THRESHOLD_WARNING to UMBRA_POSSESSION_THRESHOLD_DANGER)
if(prob(2))
possessed << "<span class='warning'>[pick("Another mind briefly touches yours, then fades", "Your vision briefly flares violet", "You feel a brief pain in your chest", \
"A murmur from within your mind, too quiet to understand", "You become uneasy for no explainable reason")].</span>"
if(prob(5))
possessed.emote("twitch")
if(UMBRA_POSSESSION_THRESHOLD_DANGER to UMBRA_POSSESSION_THRESHOLD_FORCED_OUT)
possessed << "<span class='userdanger'>GET OUT GET OUT GET OUT</span>"
possessed.confused = max(3, possessed.confused)
possessed.emote(pick("moan", "groan", "shiver", "twitch", "cry"))
flash_color(possessed, flash_color = "#5000A0", flash_time = 10)
if(UMBRA_POSSESSION_THRESHOLD_FORCED_OUT to INFINITY)
src << "<span class='userdanger'>You can't stay any longer - you retreat from [possessed]'s body!</span>"
unpossess(TRUE)
return
if(time_possessing == UMBRA_POSSESSION_THRESHOLD_WARNING)
src << "<span class='warning'>[possessed] is starting to catch on to your presence. Be wary.</span>"
else if(time_possessing == UMBRA_POSSESSION_THRESHOLD_DANGER)
src << "<span class='userdanger'>[possessed] has noticed your presence and is forcing you out!</span>"
possessed << "<span class='userdanger'>There's something in your head! You start trying to force it out--</span>"
else if(time_possessing == UMBRA_POSSESSION_THRESHOLD_FORCED_OUT)
src << "<span class='userdanger'>You can't stay any longer! You flee from [possessed]...</span>"
unpossess()
adjustBruteLoss(-1) //Vitae slowly heals the umbra as well
adjustFireLoss(-1)
if(!vitae)
death()
/mob/living/simple_animal/umbra/Stat()
..()
if(statpanel("Status"))
stat(null, "Vitae: [vitae]/[vitae_cap]")
stat(null, "Vitae Cost/Tick: [UMBRA_VITAE_DRAIN_RATE]")
var/drained_mobs = ""
var/length = recently_drained.len
for(var/mob/living/L in recently_drained)
if(!L)
recently_drained -= L
length--
else
drained_mobs += "[L.real_name][length > 1 ? ", " : ""]"
length--
stat(null, "Recently Drained Creatures: [drained_mobs ? "[drained_mobs]" : "None"]")
if(possessed)
stat(null, "Time in [possessed]: [time_possessing]/[UMBRA_POSSESSION_THRESHOLD_FORCED_OUT]")
/mob/living/simple_animal/umbra/death()
if(breaking_apart)
return
..(1)
unpossess(TRUE)
breaking_apart = TRUE
notransform = TRUE
invisibility = FALSE
visible_message("<span class='warning'>An [name] appears from nowhere and begins to disintegrate!</span>", \
"<span class='userdanger'>You feel your will faltering, and your form begins to break apart!</span>")
flick("umbra_disintegrate", src)
sleep(12)
if(vitae)
visible_message("<span class='warning'>[src] breaks apart into a pile of ashes!</span>", \
"<span class='umbra_emphasis'><font size=3>You'll</font> be <font size=1>back...</font></span>")
var/obj/item/umbral_ashes/P = new(get_turf(src))
P.umbra_key = key
P.umbra_vitae = vitae
else
visible_message("<span class='warning'>[src] breaks apart and fades away!</span>")
qdel(src)
/mob/living/simple_animal/umbra/proc/reveal(time, silent) //Makes the umbra visible for the designated amount of deciseconds
if(!time)
return
if(!silent)
src << "<span class='warning'>You've become visible!</span>"
alpha = 255
invisibility = FALSE
spawn(time)
alpha = initial(alpha)
if(!silent)
src << "<span class='umbra'>You've become invisible again!</span>"
invisibility = UMBRA_INVISIBILITY
/mob/living/simple_animal/umbra/proc/immobilize(time, silent) //Immobilizes the umbra for the designated amount of deciseconds
if(!time)
return
if(!silent)
src << "<span class='warning'>You can't move!</span>"
notransform = TRUE
spawn(time)
if(!silent)
src << "<span class='umbra'>You can move again!</span>"
notransform = FALSE
//Actions and interaction
/mob/living/simple_animal/umbra/say() //Umbras can't directly speak
src << "<span class='warning'>You lack the power to speak out loud! Use Discordant Whisper instead.</span>"
return
/mob/living/simple_animal/umbra/attack_ghost(mob/dead/observer/O)
if(key)
return
if(alert(O, "Become an umbra? You won't be clonable!",,"Yes", "No") == "No" || !O)
return
occupy(O)
notify_ghosts("The umbra at [get_area(src)] has been taken control of by [O].", source = src, action = NOTIFY_ORBIT)
src << playstyle_string
/mob/living/simple_animal/umbra/ClickOn(atom/A, params)
A.examine(src)
if(isliving(A) && Adjacent(A))
var/mob/living/L = A
if(L.health > 0 && L.stat != DEAD)
src << "<span class='warning'>[L] has no vitae to drain!</span>"
return
else if(L in recently_drained)
src << "<span class='warning'>[L]'s body is still recuperating! You can't risk draining any more vitae!</span>"
return
else if(harvesting)
src << "<span class='warning'>You're already trying to harvest vitae!</span>"
return
harvest_vitae(L)
/mob/living/simple_animal/umbra/proc/harvest_vitae(mob/living/L) //How umbras drain vitae from their targets
if(!L || L.health || L in recently_drained)
return
harvesting = TRUE
src << "<span class='umbra'>You search for any vitae in [L]...</span>"
if(!do_after(src, 30, target = L))
harvesting = FALSE
return
if(L.hellbound)
src << "<span class='warning'>[L] seems to be incapable of producing vitae!</span>"
harvesting = FALSE
return
if(!L.stat)
src << "<span class='warning'>[L] is conscious again and their vitae is receding!</span>"
L << "<span class='warning'>You're being watched.</span>"
harvesting = FALSE
return
var/vitae_yield = 1 //A bit of essence even if it's a weak soul
var/vitae_information = "<span class='umbra'>[L]'s vitae is "
if(ishuman(L))
vitae_information += "of the highest quality, "
vitae_yield += rand(10, 15)
if(L.mind)
if(!(L.mind in ticker.mode.devils))
vitae_information += "in copious amounts, "
else
vitae_information += "coming from multiple sources, "
for(var/i in 1 to (L.mind.devilinfo.soulsOwned.len))
vitae_yield += rand(5, 10) //You can drain all the souls that a devil has stolen!
vitae_yield += rand(10, 15)
if(L.stat == UNCONSCIOUS)
vitae_information += "still being produced, "
vitae_yield += rand(20, 25) //Significant bonus if the target is in critical condition instead of dead
else if(L.stat == DEAD)
vitae_information += "stagnant, "
vitae_yield += rand(1, 10)
vitae_information += "and ready for harvest. You'll absorb around [vitae_yield] vitae - "
switch(vitae_yield)
if(0 to 15)
vitae_information += "not much, but everything counts."
if(15 to 30)
vitae_information += "decent, but not great."
if(30 to 45)
vitae_information += "about what you could expect."
if(45 to 60)
vitae_information += "a good bit, more than you've come to expect."
if(75 to vitae_cap)
vitae_information += "<i>a bounty, more than you could ever dream of!</i>"
vitae_information += " Now, then...</span>"
src << vitae_information
if(!do_after(src, 30, target = L))
harvesting = FALSE
return
if(L.hellbound)
src << "<span class='warning'>[L] seems to have lost their vitae!</span>"
harvesting = FALSE
return
if(!L.stat)
src << "<span class='warning'>[L] is conscious again and their vitae is receding!</span>"
L << "<span class='warning'>A chill runs across your body.</span>"
harvesting = FALSE
return
immobilize(50)
reveal(50)
visible_message("<span class='warning'>[src] flickers into existence and reaches out towards [L]...</span>")
L.visible_message("<span class='warning'>...who rises into the air, shuddering as purple light streams out of their body!</span>")
animate(L, pixel_y = 5, time = 10)
Beam(L, icon_state = "drain_life", icon = 'icons/effects/effects.dmi', time = 50)
sleep(50)
adjust_vitae(vitae_yield, FALSE, "[L]. They won't yield any more for the time being")
recently_drained |= L
total_drained |= L
visible_message("<span class='warning'>[src] winks out of existence, releasing its hold on [L]...</span>")
L.visible_message("<span class='warning'>...who falls unceremoniously back to the ground.</span>")
animate(L, pixel_y = 0, time = 10)
addtimer(src, "harvest_cooldown", rand(UMBRA_MAX_HARVEST_COOLDOWN - 600, UMBRA_MAX_HARVEST_COOLDOWN), L)
harvesting = FALSE
return 1
/mob/living/simple_animal/umbra/proc/harvest_cooldown(mob/living/L) //After a while, mobs that have already been drained can be harvested again
if(!L)
return
src << "<span class='umbra'>You think that [L]'s body should be strong enough to produce vitae again.</span>"
recently_drained -= L
/mob/living/simple_animal/umbra/singularity_act() //Umbras are immune to most things that are catastrophic to normal humans
return
/mob/living/simple_animal/umbra/narsie_act()
return
/mob/living/simple_animal/umbra/ratvar_act()
return
/mob/living/simple_animal/umbra/blob_act(obj/effect/blob/B)
return
/mob/living/simple_animal/umbra/ex_act(severity)
return
/mob/living/simple_animal/umbra/emp_act(severity)
src << "<span class='umbra_bold'>You feel the energy of an electromagnetic pulse revitalizing you!</span>" //As they're composed of an EM field, umbras are strengthened by EMPs
adjust_vitae(50 - (severity * 10), TRUE)
//Helper procs
/mob/living/simple_animal/umbra/proc/adjust_vitae(amount, silent, source)
vitae = min(max(0, vitae + amount), vitae_cap)
if(!silent)
src << "<span class='umbra'>[amount > 0 ? "Gained" : "Lost"] [amount] vitae[source ? " from [source]" : ""].</span>"
return vitae
/mob/living/simple_animal/umbra/proc/unpossess(silent)
if(!possessed)
return
if(!silent)
src << "<span class='umbra'>You free yourself from [possessed]'s body.</span>"
if(time_possessing >= UMBRA_POSSESSION_THRESHOLD_WARNING)
possessed << "<span class='warning'>You feel a horrible presence depart from you...</span>"
loc = get_turf(possessed)
possessed = null
time_possessing = 0
notransform = FALSE
/mob/living/simple_animal/umbra/proc/occupy(mob/dead/observer/O)
if(!O)
return
key = O.key
mind.special_role = "Umbra"
var/datum/objective/umbra/lobotomize/L = new
mind.objectives += L
src << "<b>Objective #1:</b> [L.explanation_text]"

View File

@@ -0,0 +1,117 @@
//Unlike revenants, abilities used by umbras generally don't cost any vitae.
/obj/effect/proc_holder/spell/targeted/night_vision/umbra //Toggle Nightvision: Self-explanatory
panel = "Umbral Evocation"
message = "<span class='umbra'>You toggle your night vision.</span>"
charge_max = 0
action_icon_state = "umbral_sight"
action_background_icon_state = "bg_umbra"
/obj/effect/proc_holder/spell/targeted/discordant_whisper //Discordant Whisper: Sends a single, silent message to a creature that the umbra can see. Doesn't work on dead targets.
name = "Discordant Whisper"
desc = "Telepathically sends a single message to a target within range. Nobody else can perceive this message, and it works on unconscious and deafened targets."
panel = "Umbral Evocation"
range = 7
charge_max = 50
clothes_req = FALSE
include_user = FALSE
action_icon_state = "discordant_whisper"
action_background_icon_state = "bg_umbra"
/obj/effect/proc_holder/spell/targeted/discordant_whisper/cast(list/targets, mob/living/simple_animal/umbra/user)
if(!isumbra(user))
revert_cast()
return
var/mob/living/target = targets[1]
if(target.stat == DEAD)
user << "<span class='warning'>You can't send thoughts to the dead!</span>"
revert_cast()
return
var/message = stripped_input(user, "Enter a message to transmit to [target].", "Discordant Whisper")
if(!message || !target)
revert_cast()
return
log_say("UmbraWhisper: [key_name(user)] -> [key_name(target)]: [message]")
user << "<span class='umbra_bold'>You whisper to [target]:</span> <span class='umbra'>\"[message]\"</span>"
target << "<span class='umbra_emphasis'>You hear an otherworldly voice...</span> <span class='umbra'>\"[message]\"</span>"
for(var/mob/dead/observer/O in dead_mob_list)
var/f1 = FOLLOW_LINK(O, user)
var/f2 = FOLLOW_LINK(O, target)
O << "[f1] <span class='umbra_bold'>[user] (Umbra Whisper):</span> <span class='umbra'>\"[message]\"</span> to [f2] <span class='name'>[target]</span>"
/obj/effect/proc_holder/spell/targeted/possess //Possess: Occupies the body of a sapient and living human, slowly training vitae while they're conscious.
name = "Possess/Unpossess"
desc = "Enters and merges with the body of a nearby human. While inside of this human, you will very slowly generate vitae."
panel = "Umbral Evocation"
range = 1
charge_max = 600
clothes_req = FALSE
include_user = FALSE
action_icon_state = "possess"
action_background_icon_state = "bg_umbra"
/obj/effect/proc_holder/spell/targeted/possess/cast(list/targets, mob/living/simple_animal/umbra/user)
if(!isumbra(user))
revert_cast()
return
if(!user.possessed)
var/mob/living/carbon/human/target = targets[1]
if(!ishuman(target))
user << "<span class='warning'>Only humans can produce enough vitae to sustain you in this manner!</span>"
revert_cast()
return
user.possessed = target
user.loc = target
user << "<span class='umbra_emphasis'>You silently enter [user.possessed]'s body and begin leeching vitae. You won't be able to do this for very long.</span>"
user.notransform = TRUE
else
user.unpossess()
/obj/effect/proc_holder/spell/targeted/thoughtsteal //Thoughtsteal: Paralyzes and deals temporary brain damage to a target as the umbra drains it of vitae.
name = "Thoughtsteal"
desc = "Immobilizes a target as you copy their memories, gaining the knowledge of whatever they know."
panel = "Umbral Evocation"
range = 1
charge_max = 450
clothes_req = FALSE
include_user = FALSE
action_icon_state = "thoughtsteal"
action_background_icon_state = "bg_umbra"
/obj/effect/proc_holder/spell/targeted/thoughtsteal/cast(list/targets, mob/living/simple_animal/umbra/user)
if(!isumbra(user))
revert_cast()
return
var/mob/living/target = targets[1]
if(target in user.lobotomized)
user << "<span class='warning'>You've already stolen [target]'s memories!</span>"
revert_cast()
return
if(target.stat == DEAD)
user << "<span class='warning'>You can't glean any knowledge from a dead brain!</span>"
revert_cast()
return
if(!target.mind)
user << "<span class='warning'>[target] is mindless and incapable of higher thought!</span>"
revert_cast()
return
user.visible_message("<span class='warning'>[user] appears from nowhere, stretching out towards [target]!</span>", "<span class='umbra'>You try to copy [target]'s memories...</span>")
target.visible_message("<span class='warning'>A blue beam arcs into [target]'s head, immobilizing them!</span>", "<span class='userdanger'>Your mind freezes. You can't move. \
You can't think.</span>")
user.Beam(target, icon = 'icons/effects/beam.dmi', icon_state = "b_beam", time = 50)
user.immobilize(50)
user.reveal(50)
target.Stun(5)
if(!do_after(user, 50, target = target))
return
user.visible_message("<span class='warning'>A chunk of biomatter travels along the beam and into [user]!</span>", \
"<span class='umbra_bold'>Sweet, sweet thoughts. A lifetime of memories surges through you.</span>")
target.visible_message("<span class='warning'>[target] silently collapses, motionless.</span>", "<span class='userdanger'>Your mind blanks. Your thoughts have fled. Slowly, they begin to \
return... but you feel <i>wrong.</i></span>")
target.mind.show_memory(user, 0)
user.mind.memory += "<b><i>[target.real_name]'s Memories:</i></b>\n[target.mind.memory]\n\n"
target.Weaken(5)
user.lobotomized |= target

View File

@@ -0,0 +1,28 @@
/*
Possible objectives:
1. Steal the memories of # humans. (# = round population / 4)
*/
/datum/objective/umbra
explanation_text = "Be an umbra."
/datum/objective/umbra/check_completion()
return 1
/datum/objective/umbra/lobotomize
explanation_text = "Steal the memories of several humans."
/datum/objective/umbra/lobotomize/New()
target_amount = round(ticker.mode.num_players() / 4)
explanation_text = "Steal the memories of [target_amount] humans."
..()
/datum/objective/umbra/lobotomize/check_completion()
if(!isumbra(owner.current))
return 0
var/mob/living/simple_animal/umbra/U = owner.current
if(U.lobotomized.len >= target_amount)
return 1
return 0

View File

@@ -0,0 +1,17 @@
/datum/round_event_control/spawn_umbra
name = "Spawn Umbra"
typepath = /datum/round_event/ghost_role/umbra
weight = 15
earliest_start = 6000
max_occurrences = 3
/datum/round_event/ghost_role/umbra
minimum_required = 1
role_name = "umbra"
/datum/round_event/ghost_role/umbra/spawn_role()
message_admins("An unoccupied umbra was created by a random event.")
var/mob/living/simple_animal/umbra/U = new(pick(xeno_spawn))
var/image/alert_overlay = image('icons/mob/mob.dmi', "umbra")
notify_ghosts("An umbra has formed in [get_area(U)]. Interact with it to take control of it.", null, source = U, alert_overlay = alert_overlay)
return SUCCESSFUL_SPAWN

View File

@@ -0,0 +1,44 @@
#define UMBRA_ASHES_REFORM_TIME 600 //In deciseconds, how long ashes take to reform
/obj/item/umbral_ashes
name = "umbral ashes"
desc = "A shimmering pile of blue ashes with a glowing purple core."
icon = 'icons/obj/magic.dmi'
icon_state = "umbral_ashes"
w_class = 2
gender = PLURAL
origin_tech = "materials=6;bluespace=4;biotech=6" //Good origin tech if you think you have enough time to get them to research
var/umbra_key //The key of the umbra that these ashes came from
var/umbra_vitae //The vitae of the umbra that these ashes came from
/obj/item/umbral_ashes/New()
..()
addtimer(src, "reform", UMBRA_ASHES_REFORM_TIME)
/obj/item/umbral_ashes/attack_self(mob/living/user)
user.visible_message("<span class='warning'>[user] scatters [src]!</span>", "<span class='notice'>You scatter [src], which tremble and fade away.</span>")
user.drop_item()
qdel(src)
return 1
/obj/item/umbral_ashes/proc/reform()
if(!src)
return
visible_message("<span class='warning'>[src] hover into the air and reform!</span>")
flick("umbral_ashes_reforming", src)
animate(src, alpha = 0, time = 12)
sleep(12)
var/mob/living/simple_animal/umbra/U = new(get_turf(src))
U.key = umbra_key
if(!U.client)
U.key = null
var/image/alert_overlay = image('icons/mob/mob.dmi', "umbra")
notify_ghosts("An umbra has re-formed in [get_area(U)]. Interact with it to take control of it.", null, source = U, alert_overlay = alert_overlay)
else
U << "<span class='umbra_emphasis'>Back... you're back. You can't remember what you were supposed to be doing here. Now... where were we?</span>"
if(umbra_vitae)
U.vitae = umbra_vitae
U.alpha = 0
animate(U, alpha = initial(U.alpha), time = 10) //To give a fade-in effect for the newly-spawned
qdel(src)
return 1

View File

@@ -158,4 +158,10 @@
/obj/effect/decal/cleanable/shreds/New() /obj/effect/decal/cleanable/shreds/New()
pixel_x = rand(-5, 5) pixel_x = rand(-5, 5)
pixel_y = rand(-5, 5) pixel_y = rand(-5, 5)
..() ..()
/obj/effect/decal/cleanable/salt
name = "salt pile"
desc = "A sizable pile of table salt. Someone must be upset."
icon = 'icons/effects/tomatodecal.dmi'
icon_state = "salt_pile"

View File

@@ -254,17 +254,10 @@
icon_state = "sigilactiveoverlay" icon_state = "sigilactiveoverlay"
alpha = 0 alpha = 0
/obj/effect/overlay/temp/purple_sparkles
/obj/effect/overlay/temp/revenant
name = "spooky lights" name = "spooky lights"
icon_state = "purplesparkles" icon_state = "purplesparkles"
/obj/effect/overlay/temp/revenant/cracks
name = "glowing cracks"
icon_state = "purplecrack"
duration = 6
/obj/effect/overlay/temp/emp /obj/effect/overlay/temp/emp
name = "emp sparks" name = "emp sparks"
icon_state = "empdisable" icon_state = "empdisable"

View File

@@ -119,13 +119,6 @@
else else
message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.") message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.")
log_admin("[key_name(usr)] failed to create an abductor team.") log_admin("[key_name(usr)] failed to create an abductor team.")
if("15")
if(src.makeRevenant())
message_admins("[key_name(usr)] created a revenant.")
log_admin("[key_name(usr)] created a revenant.")
else
message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.")
log_admin("[key_name(usr)] failed to create a revenant.")
if("16") if("16")
if(src.makeShadowling()) if(src.makeShadowling())
message_admins("[key_name(usr)] created a shadowling.") message_admins("[key_name(usr)] created a shadowling.")
@@ -1608,7 +1601,7 @@
else if(href_list["adminchecklaws"]) else if(href_list["adminchecklaws"])
output_ai_laws() output_ai_laws()
else if(href_list["admincheckdevilinfo"]) else if(href_list["admincheckdevilinfo"])
output_devil_info() output_devil_info()

View File

@@ -22,7 +22,6 @@
<a href='?src=\ref[src];makeAntag=7'>Make Nuke Team (Requires Ghosts)</a><br> <a href='?src=\ref[src];makeAntag=7'>Make Nuke Team (Requires Ghosts)</a><br>
<a href='?src=\ref[src];makeAntag=13'>Make Centcom Response Team (Requires Ghosts)</a><br> <a href='?src=\ref[src];makeAntag=13'>Make Centcom Response Team (Requires Ghosts)</a><br>
<a href='?src=\ref[src];makeAntag=14'>Make Abductor Team (Requires Ghosts)</a><br> <a href='?src=\ref[src];makeAntag=14'>Make Abductor Team (Requires Ghosts)</a><br>
<a href='?src=\ref[src];makeAntag=15'>Make Revenant (Requires Ghost)</a><br>
"} "}
var/datum/browser/popup = new(usr, "oneclickantag", "Quick-Create Antagonist", 400, 400) var/datum/browser/popup = new(usr, "oneclickantag", "Quick-Create Antagonist", 400, 400)
@@ -514,9 +513,6 @@
new /datum/round_event/ghost_role/abductor new /datum/round_event/ghost_role/abductor
return 1 return 1
/datum/admins/proc/makeRevenant()
new /datum/round_event/ghost_role/revenant
//Shadowling //Shadowling
/datum/admins/proc/makeShadowling() /datum/admins/proc/makeShadowling()
var/datum/game_mode/shadowling/temp = new var/datum/game_mode/shadowling/temp = new

View File

@@ -49,9 +49,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
var/B_gang = 4096 var/B_gang = 4096
var/B_shadowling = 8192 var/B_shadowling = 8192
var/B_abductor = 16384 var/B_abductor = 16384
var/B_revenant = 32768
var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_shadowling,B_abductor,B_revenant) var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_shadowling,B_abductor)
be_special = list() be_special = list()
@@ -89,8 +88,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
be_special += ROLE_SHADOWLING be_special += ROLE_SHADOWLING
if(16384) if(16384)
be_special += ROLE_ABDUCTOR be_special += ROLE_ABDUCTOR
if(32768)
be_special += ROLE_REVENANT
/datum/preferences/proc/update_preferences(current_version) /datum/preferences/proc/update_preferences(current_version)

View File

@@ -133,6 +133,16 @@
desc = "Salt. From dead crew, presumably." desc = "Salt. From dead crew, presumably."
return (TOXLOSS) return (TOXLOSS)
/obj/item/weapon/reagent_containers/food/condiment/saltshaker/afterattack(obj/target, mob/living/user, proximity)
if(!proximity || !isturf(target))
return
if(!reagents.has_reagent("sodiumchloride", 2))
user << "<span class='warning'>You don't have enough salt to make a pile!</span>"
return
user.visible_message("<span class='notice'>[user] shakes some salt onto [target].</span>", "<span class='notice'>You shake some salt onto [target].</span>")
reagents.remove_reagent("sodiumchloride", 2)
new/obj/effect/decal/cleanable/salt(target)
/obj/item/weapon/reagent_containers/food/condiment/peppermill /obj/item/weapon/reagent_containers/food/condiment/peppermill
name = "pepper mill" name = "pepper mill"
desc = "Often used to flavor food or make people sneeze." desc = "Often used to flavor food or make people sneeze."

View File

@@ -445,8 +445,9 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
/proc/offer_control(mob/M) /proc/offer_control(mob/M)
M << "Control of your mob has been offered to dead players." M << "Control of your mob has been offered to dead players."
log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") if(usr)
message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts") log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.")
message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts")
var/poll_message = "Do you want to play as [M.real_name]?" var/poll_message = "Do you want to play as [M.real_name]?"
if(M.mind && M.mind.assigned_role) if(M.mind && M.mind.assigned_role)
poll_message = "[poll_message] Job:[M.mind.assigned_role]." poll_message = "[poll_message] Job:[M.mind.assigned_role]."

View File

@@ -224,16 +224,23 @@
anim(mobloc,mob,'icons/mob/mob.dmi',,"shadow",,L.dir) anim(mobloc,mob,'icons/mob/mob.dmi',,"shadow",,L.dir)
L.loc = get_step(L, direct) L.loc = get_step(L, direct)
L.setDir(direct) L.setDir(direct)
if(3) //Incorporeal move, but blocked by holy-watered tiles if(3) //Incorporeal move, but blocked by holy-watered tiles and salt piles. Used by umbras.
if(!isumbra(L))
L.incorporeal_move = 1
return
var/mob/living/simple_animal/umbra/U = L
var/turf/open/floor/stepTurf = get_step(L, direct) var/turf/open/floor/stepTurf = get_step(L, direct)
for(var/obj/effect/decal/cleanable/salt/S in stepTurf)
U << "<span class='warning'>[S] bars your passage!</span>"
U.reveal(2, TRUE)
U.immobilize(2, TRUE)
return
if(stepTurf.flags & NOJAUNT) if(stepTurf.flags & NOJAUNT)
L << "<span class='warning'>Holy energies block your path.</span>" U << "<span class='warning'>Holy energies block your path!</span>"
L.notransform = 1 U.immobilize(2, TRUE)
spawn(2)
L.notransform = 0
else else
L.loc = get_step(L, direct) U.loc = get_step(L, direct)
L.setDir(direct) U.setDir(direct)
return 1 return 1

View File

@@ -539,7 +539,7 @@
/datum/reagent/blob/adaptive_nexuses/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) /datum/reagent/blob/adaptive_nexuses/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..() reac_volume = ..()
if(O && ishuman(M) && M.stat == UNCONSCIOUS) if(O && ishuman(M) && M.stat == UNCONSCIOUS)
PoolOrNew(/obj/effect/overlay/temp/revenant, get_turf(M)) PoolOrNew(/obj/effect/overlay/temp/purple_sparkles, get_turf(M))
var/points = rand(5, 10) var/points = rand(5, 10)
O.add_points(points) O.add_points(points)
O << "<span class='notice'>Gained [points] resources from the death of [M].</span>" O << "<span class='notice'>Gained [points] resources from the death of [M].</span>"

View File

@@ -247,12 +247,19 @@
reagent_state = SOLID reagent_state = SOLID
color = "#FFFFFF" // rgb: 255,255,255 color = "#FFFFFF" // rgb: 255,255,255
/datum/reagent/consumable/sodiumchloride/reaction_mob(mob/living/M, method=TOUCH, reac_volume)//Splashing people with welding fuel to make them easy to ignite! /datum/reagent/consumable/sodiumchloride/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(!istype(M)) if(!istype(M))
return return
if(M.has_bane(BANE_SALT)) if(M.has_bane(BANE_SALT))
M.mind.disrupt_spells(-200) M.mind.disrupt_spells(-200)
/datum/reagent/consumable/sodiumchloride/reaction_turf(turf/T, reac_volume) //Creates an umbra-blocking salt pile
if(!istype(T))
return
if(reac_volume < 1)
return
new/obj/effect/decal/cleanable/salt(T)
/datum/reagent/consumable/blackpepper /datum/reagent/consumable/blackpepper
name = "Black Pepper" name = "Black Pepper"
id = "blackpepper" id = "blackpepper"

View File

@@ -63,14 +63,6 @@
if(created_volume >= 150) if(created_volume >= 150)
playsound(get_turf(holder.my_atom), 'sound/effects/pray.ogg', 80, 0, round(created_volume/48)) playsound(get_turf(holder.my_atom), 'sound/effects/pray.ogg', 80, 0, round(created_volume/48))
strengthdiv = 8 strengthdiv = 8
for(var/mob/living/simple_animal/revenant/R in get_hearers_in_view(7,get_turf(holder.my_atom)))
var/diety = ticker.Bible_deity_name
if(!ticker.Bible_deity_name)
diety = "Christ"
R << "<span class='userdanger'>The power of [diety] compels you!</span>"
R.stun(20)
R.reveal(100)
sleep(20)
for(var/mob/living/carbon/C in get_hearers_in_view(round(created_volume/48,1),get_turf(holder.my_atom))) for(var/mob/living/carbon/C in get_hearers_in_view(round(created_volume/48,1),get_turf(holder.my_atom)))
if(iscultist(C) || is_handofgod_cultist(C) || C.dna.species.id == "shadowling" || C.dna.species.id == "l_shadowling") if(iscultist(C) || is_handofgod_cultist(C) || C.dna.species.id == "shadowling" || C.dna.species.id == "l_shadowling")
C << "<span class='userdanger'>The divine explosion sears you!</span>" C << "<span class='userdanger'>The divine explosion sears you!</span>"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -73,6 +73,18 @@ h1.alert, h2.alert {color: #000000;}
.holoparasite {color: #35333a;} .holoparasite {color: #35333a;}
.holoparasitebold {color: #35333a; font-weight: bold;} .holoparasitebold {color: #35333a; font-weight: bold;}
.revennotice {color: #1d2953;}
.revenboldnotice {color: #1d2953; font-weight: bold;}
.revenbignotice {color: #1d2953; font-weight: bold; font-size: 3;}
.revenminor {color: #823abb}
.revenwarning {color: #760fbb; font-style: italic;}
.revendanger {color: #760fbb; font-weight: bold; font-size: 3;}
.umbra {color: #5000A0;}
.umbra_bold {color: #5000A0; font-weight: bold;}
.umbra_italics {color: #5000A0; font-style: italic;}
.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
.umbra_large {color: #5000A0; font-size: 3;}
.brass {color: #BE8700;} .brass {color: #BE8700;}
.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;} .heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
.large_brass {color: #BE8700; font-size: 3;} .large_brass {color: #BE8700; font-size: 3;}
@@ -91,13 +103,6 @@ h1.alert, h2.alert {color: #000000;}
.neovgre {color: #13143F; font-weight: bold; font-style: italic;} .neovgre {color: #13143F; font-weight: bold; font-style: italic;}
.neovgre_small {color: #13143F;} .neovgre_small {color: #13143F;}
.revennotice {color: #1d2953;}
.revenboldnotice {color: #1d2953; font-weight: bold;}
.revenbignotice {color: #1d2953; font-weight: bold; font-size: 3;}
.revenminor {color: #823abb}
.revenwarning {color: #760fbb; font-style: italic;}
.revendanger {color: #760fbb; font-weight: bold; font-size: 3;}
.newscaster {color: #800000;} .newscaster {color: #800000;}
.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;} .ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}

View File

@@ -120,7 +120,6 @@
#include "code\_onclick\hud\monkey.dm" #include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm" #include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\other_mobs.dm" #include "code\_onclick\hud\other_mobs.dm"
#include "code\_onclick\hud\revenanthud.dm"
#include "code\_onclick\hud\robot.dm" #include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm" #include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\swarmer.dm" #include "code\_onclick\hud\swarmer.dm"
@@ -366,13 +365,14 @@
#include "code\game\gamemodes\miniantags\hades\hades.dm" #include "code\game\gamemodes\miniantags\hades\hades.dm"
#include "code\game\gamemodes\miniantags\monkey\monkey.dm" #include "code\game\gamemodes\miniantags\monkey\monkey.dm"
#include "code\game\gamemodes\miniantags\morph\morph.dm" #include "code\game\gamemodes\miniantags\morph\morph.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_abilities.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_blight.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_spawn_event.dm"
#include "code\game\gamemodes\miniantags\sintouched\objectives.dm" #include "code\game\gamemodes\miniantags\sintouched\objectives.dm"
#include "code\game\gamemodes\miniantags\slaughter\slaughter.dm" #include "code\game\gamemodes\miniantags\slaughter\slaughter.dm"
#include "code\game\gamemodes\miniantags\slaughter\slaughterevent.dm" #include "code\game\gamemodes\miniantags\slaughter\slaughterevent.dm"
#include "code\game\gamemodes\miniantags\umbra\umbra.dm"
#include "code\game\gamemodes\miniantags\umbra\umbra_abilities.dm"
#include "code\game\gamemodes\miniantags\umbra\umbra_objectives.dm"
#include "code\game\gamemodes\miniantags\umbra\umbra_spawn_event.dm"
#include "code\game\gamemodes\miniantags\umbra\umbral_ashes.dm"
#include "code\game\gamemodes\nuclear\nuclear.dm" #include "code\game\gamemodes\nuclear\nuclear.dm"
#include "code\game\gamemodes\nuclear\nuclear_challenge.dm" #include "code\game\gamemodes\nuclear\nuclear_challenge.dm"
#include "code\game\gamemodes\nuclear\nuclearbomb.dm" #include "code\game\gamemodes\nuclear\nuclearbomb.dm"