diff --git a/_maps/map_files/TgStation/tgstation.2.1.3.dmm b/_maps/map_files/TgStation/tgstation.2.1.3.dmm
index de9bb2a67d4..63ff1f7300c 100644
--- a/_maps/map_files/TgStation/tgstation.2.1.3.dmm
+++ b/_maps/map_files/TgStation/tgstation.2.1.3.dmm
@@ -2122,6 +2122,7 @@
/obj/machinery/light/small{
dir = 8
},
+/obj/machinery/portable_atmospherics/canister/nitrous_oxide,
/turf/open/floor/plating/warnplate{
dir = 8
},
@@ -6665,9 +6666,6 @@
/area/hallway/primary/fore)
"anS" = (
/obj/machinery/hologram/holopad,
-/mob/living/simple_animal/bot/secbot/beepsky{
- name = "Officer Beepsky"
- },
/turf/open/floor/plasteel,
/area/hallway/primary/fore)
"anT" = (
@@ -7153,10 +7151,7 @@
},
/area/shuttle/labor)
"apb" = (
-/obj/machinery/door/airlock/glass_security{
- name = "N2O Storage";
- req_access_txt = "3"
- },
+/obj/structure/plasticflaps,
/turf/open/floor/plating,
/area/security/processing)
"apc" = (
@@ -7478,7 +7473,10 @@
/turf/open/space,
/area/space/nearstation)
"apR" = (
-/obj/machinery/portable_atmospherics/canister/nitrous_oxide,
+/obj/item/weapon/paper{
+ info = "01001001 00100000 01101000 01101111 01110000 01100101 00100000 01111001 01101111 01110101 00100000 01110011 01110100 01100001 01111001 00100000 01110011 01100001 01100110 01100101 00101110 00100000 01001100 01101111 01110110 01100101 00101100 00100000 01101101 01101111 01101101 00101110";
+ name = "Note from Beepsky's Mom"
+ },
/turf/open/floor/plating,
/area/security/processing)
"apS" = (
@@ -7928,6 +7926,11 @@
/obj/machinery/light/small{
dir = 4
},
+/obj/structure/table,
+/obj/machinery/cell_charger,
+/obj/item/weapon/stock_parts/cell/potato{
+ name = "\improper Beepsky's emergency battery"
+ },
/turf/open/floor/plating,
/area/security/processing)
"aqT" = (
@@ -60466,6 +60469,13 @@
/obj/item/clothing/under/burial,
/turf/open/floor/plasteel/grimy,
/area/chapel/office)
+"cCa" = (
+/obj/item/weapon/bedsheet/red,
+/mob/living/simple_animal/bot/secbot/beepsky{
+ name = "Officer Beepsky"
+ },
+/turf/open/floor/plating,
+/area/security/processing)
(1,1,1) = {"
aaa
@@ -84463,9 +84473,9 @@ aiT
ant
akI
aos
-aiU
-apR
+aiT
apR
+cCa
arP
asT
aqR
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index aacbfc3a556..ed2e7c1c94d 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -72,6 +72,8 @@
#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 isbot(A) (istype(A, /mob/living/simple_animal/bot))
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 638e259a3c5..f7a1f3c6641 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -21,7 +21,6 @@
#define ROLE_GANG "gangster"
#define ROLE_SHADOWLING "shadowling"
#define ROLE_ABDUCTOR "abductor"
-#define ROLE_REVENANT "revenant"
#define ROLE_HOG_GOD "hand of god: god"
#define ROLE_HOG_CULTIST "hand of god: cultist"
#define ROLE_DEVIL "devil"
@@ -45,7 +44,6 @@ var/global/list/special_roles = list(
ROLE_MONKEY = /datum/game_mode/monkey,
ROLE_GANG = /datum/game_mode/gang,
ROLE_SHADOWLING = /datum/game_mode/shadowling,
- ROLE_REVENANT,
ROLE_ABDUCTOR = /datum/game_mode/abduction,
ROLE_HOG_GOD = /datum/game_mode/hand_of_god,
ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god,
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index d01c22982f0..04d73a3e59a 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -24,6 +24,7 @@
#define MAX_MESSAGE_LEN 1024
#define MAX_NAME_LEN 26
#define MAX_BROADCAST_LEN 512
+#define MAX_CHARTER_LEN 50
//MINOR TWEAKS/MISC
#define AGE_MIN 17 //youngest a character can be
diff --git a/code/_onclick/hud/revenanthud.dm b/code/_onclick/hud/revenanthud.dm
deleted file mode 100644
index 492e9e67b41..00000000000
--- a/code/_onclick/hud/revenanthud.dm
+++ /dev/null
@@ -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)
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index b32de28b0a0..5f50f266f53 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -455,13 +455,6 @@
screen_loc = ui_health
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
name = "health doll"
screen_loc = ui_healthdoll
diff --git a/code/game/gamemodes/miniantags/readme.txt b/code/game/gamemodes/miniantags/readme.txt
index 7847ffb7df8..8ade34bf2e0 100644
--- a/code/game/gamemodes/miniantags/readme.txt
+++ b/code/game/gamemodes/miniantags/readme.txt
@@ -2,8 +2,10 @@ This folder contains all "mini-antagonists" - antagonists that can still spice u
Currently, that list consists of:
-Abductors
-Swarmers
+ -Prophets of sin
-The Jungle Fever virus (infected monkey bites human, human becomes another infected monkey)
-Morphs
- -Revenants
+ -Sintouched crewmembers
-Slaughter demons
+ -Umbras (formerly revenants)
Please update this if you add another mini-antagonist.
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
deleted file mode 100644
index 97db79c3a92..00000000000
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ /dev/null
@@ -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 << "[A]'s soul is dead and empty." //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 << "You are already siphoning the essence of a soul!"
- return
- if(!target.stat)
- src << "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 += rand(15, 20)
- src << "You search for the soul of [target]."
- if(do_after(src, rand(10, 20), 0, target)) //did they get deleted in that second?
- if(target.ckey)
- src << "Their soul burns with intelligence."
- essence_drained += rand(20, 30)
- if(target.stat != DEAD)
- src << "Their soul blazes with life!"
- essence_drained += rand(40, 50)
- else
- src << "Their soul is weak and faltering."
- if(do_after(src, rand(15, 20), 0, target)) //did they get deleted NOW?
- switch(essence_drained)
- if(1 to 30)
- src << "[target] will not yield much essence. Still, every bit counts."
- if(30 to 70)
- src << "[target] will yield an average amount of essence."
- if(70 to 90)
- src << "Such a feast! [target] will yield much essence to you."
- if(90 to INFINITY)
- src << "Ah, the perfect soul. [target] will yield massive amounts of essence to you."
- if(do_after(src, rand(15, 25), 0, target)) //how about now
- if(!target.stat)
- src << "They are now powerful enough to fight off your draining."
- target << "You feel something tugging across your body before subsiding."
- draining = 0
- essence_drained = 0
- return //hey, wait a minute...
- src << "You begin siphoning essence from [target]'s soul."
- if(target.stat != DEAD)
- target << "You feel a horribly unpleasant draining sensation as your grip on life weakens..."
- reveal(46)
- stun(46)
- target.visible_message("[target] suddenly rises slightly into the air, their skin turning an ashy gray.")
- 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 << "The absorption of [target]'s living soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap]."
- if(essence_drained > 90)
- essence_regen_cap += 15
- perfectsouls += 1
- src << "The perfection of [target]'s soul has increased your maximum essence level. Your new maximum essence is [essence_regen_cap]."
- src << "[target]'s soul has been considerably weakened and will yield no more essence for the time being."
- target.visible_message("[target] slumps onto the ground.", \
- "Violets lights, dancing in your vision, getting clo--")
- drained_mobs.Add(target)
- target.death(0)
- else
- qdel(B)
- src << "[target ? "[target] has":"They have"] been drawn out of your grasp. The link has been broken."
- draining = 0
- essence_drained = 0
- if(target) //Wait, target is WHERE NOW?
- target.visible_message("[target] slumps onto the ground.", \
- "Violets lights, dancing in your vision, receding--")
- return
- else
- src << "You are not close enough to siphon [target ? "[target]'s":"their"] soul. The link has been broken."
- 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 = "You toggle your night vision."
- 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 << "You transmit to [M]: [msg]"
- M << "An alien voice resonates from all around... [msg]"
- 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] \
- [user] \
- Revenant Transmit --> \
- [follow_whispee] \
- [M] \
- [msg]"
-
-
-
-/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 << "You have unlocked [initial(name)]!"
- 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("\The [L] suddenly flares brightly and begins to spark!")
- 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 << "You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")]."
- 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 << "You feel [pick("suddenly sick", "a surge of nausea", "like your skin is wrong")]."
- 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)
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_blight.dm b/code/game/gamemodes/miniantags/revenant/revenant_blight.dm
deleted file mode 100644
index 891bed3cd10..00000000000
--- a/code/game/gamemodes/miniantags/revenant/revenant_blight.dm
+++ /dev/null
@@ -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 << "You feel better."
- 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 << "You suddenly feel [pick("sick and tired", "disoriented", "tired and confused", "nauseated", "faint", "dizzy")]..."
- 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 << "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")]."
- 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("[affected_mob] looks terrifyingly gaunt...", "You suddenly feel like your skin is wrong...")
- var/old_color = affected_mob.color
- affected_mob.color = "#1d2953"
- spawn(100)
- if(affected_mob)
- affected_mob = old_color
- cure()
- else
- return
\ No newline at end of file
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm
deleted file mode 100644
index f73ac040966..00000000000
--- a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm
+++ /dev/null
@@ -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
diff --git a/code/game/gamemodes/miniantags/umbra/umbra.dm b/code/game/gamemodes/miniantags/umbra/umbra.dm
new file mode 100644
index 00000000000..29201fdfb64
--- /dev/null
+++ b/code/game/gamemodes/miniantags/umbra/umbra.dm
@@ -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 = "You are an umbra, 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 vitae.\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("[src] appears from nowhere, twitching and flickering!", "AAAH! SALT!")
+ 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 << "You feel [pick("watched", "not wholly yourself", "an intense craving for salt", "singularly odd", \
+ "a horrible dread in your heart")]."
+ if(UMBRA_POSSESSION_THRESHOLD_WARNING to UMBRA_POSSESSION_THRESHOLD_DANGER)
+ if(prob(2))
+ possessed << "[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")]."
+ if(prob(5))
+ possessed.emote("twitch")
+ if(UMBRA_POSSESSION_THRESHOLD_DANGER to UMBRA_POSSESSION_THRESHOLD_FORCED_OUT)
+ possessed << "GET OUT GET OUT GET OUT"
+ 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 << "You can't stay any longer - you retreat from [possessed]'s body!"
+ unpossess(TRUE)
+ return
+ if(time_possessing == UMBRA_POSSESSION_THRESHOLD_WARNING)
+ src << "[possessed] is starting to catch on to your presence. Be wary."
+ else if(time_possessing == UMBRA_POSSESSION_THRESHOLD_DANGER)
+ src << "[possessed] has noticed your presence and is forcing you out!"
+ possessed << "There's something in your head! You start trying to force it out--"
+ else if(time_possessing == UMBRA_POSSESSION_THRESHOLD_FORCED_OUT)
+ src << "You can't stay any longer! You flee from [possessed]..."
+ 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("An [name] appears from nowhere and begins to disintegrate!", \
+ "You feel your will faltering, and your form begins to break apart!")
+ flick("umbra_disintegrate", src)
+ sleep(12)
+ if(vitae)
+ visible_message("[src] breaks apart into a pile of ashes!", \
+ "You'll be back...")
+ var/obj/item/umbral_ashes/P = new(get_turf(src))
+ P.umbra_key = key
+ P.umbra_vitae = vitae
+ else
+ visible_message("[src] breaks apart and fades away!")
+ 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 << "You've become visible!"
+ alpha = 255
+ invisibility = FALSE
+ spawn(time)
+ alpha = initial(alpha)
+ if(!silent)
+ src << "You've become invisible again!"
+ 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 << "You can't move!"
+ notransform = TRUE
+ spawn(time)
+ if(!silent)
+ src << "You can move again!"
+ notransform = FALSE
+
+
+//Actions and interaction
+/mob/living/simple_animal/umbra/say() //Umbras can't directly speak
+ src << "You lack the power to speak out loud! Use Discordant Whisper instead."
+ 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 << "[L] has no vitae to drain!"
+ return
+ else if(L in recently_drained)
+ src << "[L]'s body is still recuperating! You can't risk draining any more vitae!"
+ return
+ else if(harvesting)
+ src << "You're already trying to harvest vitae!"
+ 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 << "You search for any vitae in [L]..."
+ if(!do_after(src, 30, target = L))
+ harvesting = FALSE
+ return
+ if(L.hellbound)
+ src << "[L] seems to be incapable of producing vitae!"
+ harvesting = FALSE
+ return
+ if(!L.stat)
+ src << "[L] is conscious again and their vitae is receding!"
+ L << "You're being watched."
+ harvesting = FALSE
+ return
+ var/vitae_yield = 1 //A bit of essence even if it's a weak soul
+ var/vitae_information = "[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 += "a bounty, more than you could ever dream of!"
+ vitae_information += " Now, then..."
+ src << vitae_information
+ if(!do_after(src, 30, target = L))
+ harvesting = FALSE
+ return
+ if(L.hellbound)
+ src << "[L] seems to have lost their vitae!"
+ harvesting = FALSE
+ return
+ if(!L.stat)
+ src << "[L] is conscious again and their vitae is receding!"
+ L << "A chill runs across your body."
+ harvesting = FALSE
+ return
+ immobilize(50)
+ reveal(50)
+ visible_message("[src] flickers into existence and reaches out towards [L]...")
+ L.visible_message("...who rises into the air, shuddering as purple light streams out of their body!")
+ 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("[src] winks out of existence, releasing its hold on [L]...")
+ L.visible_message("...who falls unceremoniously back to the ground.")
+ 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 << "You think that [L]'s body should be strong enough to produce vitae again."
+ 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 << "You feel the energy of an electromagnetic pulse revitalizing you!" //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 << "[amount > 0 ? "Gained" : "Lost"] [amount] vitae[source ? " from [source]" : ""]."
+ return vitae
+
+/mob/living/simple_animal/umbra/proc/unpossess(silent)
+ if(!possessed)
+ return
+ if(!silent)
+ src << "You free yourself from [possessed]'s body."
+ if(time_possessing >= UMBRA_POSSESSION_THRESHOLD_WARNING)
+ possessed << "You feel a horrible presence depart from you..."
+ 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 << "Objective #1: [L.explanation_text]"
diff --git a/code/game/gamemodes/miniantags/umbra/umbra_abilities.dm b/code/game/gamemodes/miniantags/umbra/umbra_abilities.dm
new file mode 100644
index 00000000000..6383074ea70
--- /dev/null
+++ b/code/game/gamemodes/miniantags/umbra/umbra_abilities.dm
@@ -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 = "You toggle your night vision."
+ 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 << "You can't send thoughts to the dead!"
+ 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 << "You whisper to [target]: \"[message]\""
+ target << "You hear an otherworldly voice... \"[message]\""
+ for(var/mob/dead/observer/O in dead_mob_list)
+ var/f1 = FOLLOW_LINK(O, user)
+ var/f2 = FOLLOW_LINK(O, target)
+ O << "[f1] [user] (Umbra Whisper): \"[message]\" to [f2] [target]"
+
+
+/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 << "Only humans can produce enough vitae to sustain you in this manner!"
+ revert_cast()
+ return
+ user.possessed = target
+ user.loc = target
+ user << "You silently enter [user.possessed]'s body and begin leeching vitae. You won't be able to do this for very long."
+ 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 << "You've already stolen [target]'s memories!"
+ revert_cast()
+ return
+ if(target.stat == DEAD)
+ user << "You can't glean any knowledge from a dead brain!"
+ revert_cast()
+ return
+ if(!target.mind)
+ user << "[target] is mindless and incapable of higher thought!"
+ revert_cast()
+ return
+ user.visible_message("[user] appears from nowhere, stretching out towards [target]!", "You try to copy [target]'s memories...")
+ target.visible_message("A blue beam arcs into [target]'s head, immobilizing them!", "Your mind freezes. You can't move. \
+ You can't think.")
+ 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("A chunk of biomatter travels along the beam and into [user]!", \
+ "Sweet, sweet thoughts. A lifetime of memories surges through you.")
+ target.visible_message("[target] silently collapses, motionless.", "Your mind blanks. Your thoughts have fled. Slowly, they begin to \
+ return... but you feel wrong.")
+ target.mind.show_memory(user, 0)
+ user.mind.memory += "[target.real_name]'s Memories:\n[target.mind.memory]\n\n"
+ target.Weaken(5)
+ user.lobotomized |= target
diff --git a/code/game/gamemodes/miniantags/umbra/umbra_objectives.dm b/code/game/gamemodes/miniantags/umbra/umbra_objectives.dm
new file mode 100644
index 00000000000..db1bd30be1a
--- /dev/null
+++ b/code/game/gamemodes/miniantags/umbra/umbra_objectives.dm
@@ -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
diff --git a/code/game/gamemodes/miniantags/umbra/umbra_spawn_event.dm b/code/game/gamemodes/miniantags/umbra/umbra_spawn_event.dm
new file mode 100644
index 00000000000..1c8526edf5b
--- /dev/null
+++ b/code/game/gamemodes/miniantags/umbra/umbra_spawn_event.dm
@@ -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
diff --git a/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm b/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm
new file mode 100644
index 00000000000..c4c168bf7c8
--- /dev/null
+++ b/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm
@@ -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("[user] scatters [src]!", "You scatter [src], which tremble and fade away.")
+ user.drop_item()
+ qdel(src)
+ return 1
+
+/obj/item/umbral_ashes/proc/reform()
+ if(!src)
+ return
+ visible_message("[src] hover into the air and reform!")
+ 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 << "Back... you're back. You can't remember what you were supposed to be doing here. Now... where were we?"
+ 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
diff --git a/code/game/objects/effects/decals/Cleanable/misc.dm b/code/game/objects/effects/decals/Cleanable/misc.dm
index f7f4f5fe953..ebf8ab3f8c5 100644
--- a/code/game/objects/effects/decals/Cleanable/misc.dm
+++ b/code/game/objects/effects/decals/Cleanable/misc.dm
@@ -158,4 +158,10 @@
/obj/effect/decal/cleanable/shreds/New()
pixel_x = rand(-5, 5)
pixel_y = rand(-5, 5)
- ..()
\ No newline at end of file
+ ..()
+
+/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"
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index d37e66d9876..12c96a5643a 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -254,17 +254,10 @@
icon_state = "sigilactiveoverlay"
alpha = 0
-
-/obj/effect/overlay/temp/revenant
+/obj/effect/overlay/temp/purple_sparkles
name = "spooky lights"
icon_state = "purplesparkles"
-/obj/effect/overlay/temp/revenant/cracks
- name = "glowing cracks"
- icon_state = "purplecrack"
- duration = 6
-
-
/obj/effect/overlay/temp/emp
name = "emp sparks"
icon_state = "empdisable"
diff --git a/code/game/objects/items/charter.dm b/code/game/objects/items/charter.dm
index 59c26499ac1..3d3e48e1eb7 100644
--- a/code/game/objects/items/charter.dm
+++ b/code/game/objects/items/charter.dm
@@ -14,7 +14,7 @@
user << "The crew has already settled into the shift. It probably wouldn't be good to rename the station right now."
return
- var/new_name = input(user, "What do you want to name [station_name()]? Keep in mind particularly terrible names may attract the attention of your employers.") as text|null
+ var/new_name = stripped_input(user, message="What do you want to name [station_name()]? Keep in mind particularly terrible names may attract the attention of your employers.", max_length=MAX_CHARTER_LEN)
if(new_name)
world.name = new_name
station_name = new_name
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index d97e1a7d464..a4742693017 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -119,13 +119,6 @@
else
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.")
- 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(src.makeShadowling())
message_admins("[key_name(usr)] created a shadowling.")
@@ -1608,7 +1601,7 @@
else if(href_list["adminchecklaws"])
output_ai_laws()
-
+
else if(href_list["admincheckdevilinfo"])
output_devil_info()
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index 72afd148095..1416f8e7c2b 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -22,7 +22,6 @@
Make Nuke Team (Requires Ghosts)
Make Centcom Response Team (Requires Ghosts)
Make Abductor Team (Requires Ghosts)
- Make Revenant (Requires Ghost)
"}
var/datum/browser/popup = new(usr, "oneclickantag", "Quick-Create Antagonist", 400, 400)
@@ -514,9 +513,6 @@
new /datum/round_event/ghost_role/abductor
return 1
-/datum/admins/proc/makeRevenant()
- new /datum/round_event/ghost_role/revenant
-
//Shadowling
/datum/admins/proc/makeShadowling()
var/datum/game_mode/shadowling/temp = new
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 33ff7efcaed..57efa80d79a 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -49,9 +49,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
var/B_gang = 4096
var/B_shadowling = 8192
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()
@@ -89,8 +88,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
be_special += ROLE_SHADOWLING
if(16384)
be_special += ROLE_ABDUCTOR
- if(32768)
- be_special += ROLE_REVENANT
/datum/preferences/proc/update_preferences(current_version)
diff --git a/code/modules/food_and_drinks/food/condiment.dm b/code/modules/food_and_drinks/food/condiment.dm
index d4314a1a280..9f5ed9a028b 100644
--- a/code/modules/food_and_drinks/food/condiment.dm
+++ b/code/modules/food_and_drinks/food/condiment.dm
@@ -133,6 +133,16 @@
desc = "Salt. From dead crew, presumably."
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 << "You don't have enough salt to make a pile!"
+ return
+ user.visible_message("[user] shakes some salt onto [target].", "You shake some salt onto [target].")
+ reagents.remove_reagent("sodiumchloride", 2)
+ new/obj/effect/decal/cleanable/salt(target)
+
/obj/item/weapon/reagent_containers/food/condiment/peppermill
name = "pepper mill"
desc = "Often used to flavor food or make people sneeze."
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 878e306f39d..a7d124ca828 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -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)
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.")
- message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts")
+ if(usr)
+ 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]?"
if(M.mind && M.mind.assigned_role)
poll_message = "[poll_message] Job:[M.mind.assigned_role]."
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 9d9b9c3a85d..06391e09f34 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -224,16 +224,23 @@
anim(mobloc,mob,'icons/mob/mob.dmi',,"shadow",,L.dir)
L.loc = get_step(L, 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)
+ for(var/obj/effect/decal/cleanable/salt/S in stepTurf)
+ U << "[S] bars your passage!"
+ U.reveal(2, TRUE)
+ U.immobilize(2, TRUE)
+ return
if(stepTurf.flags & NOJAUNT)
- L << "Holy energies block your path."
- L.notransform = 1
- spawn(2)
- L.notransform = 0
+ U << "Holy energies block your path!"
+ U.immobilize(2, TRUE)
else
- L.loc = get_step(L, direct)
- L.setDir(direct)
+ U.loc = get_step(L, direct)
+ U.setDir(direct)
return 1
diff --git a/code/modules/reagents/chemistry/reagents/blob_reagents.dm b/code/modules/reagents/chemistry/reagents/blob_reagents.dm
index 0d648c46fc2..f778f93029c 100644
--- a/code/modules/reagents/chemistry/reagents/blob_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/blob_reagents.dm
@@ -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)
reac_volume = ..()
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)
O.add_points(points)
O << "Gained [points] resources from the death of [M]."
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index dc8126d6f42..b027cd48161 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -247,12 +247,19 @@
reagent_state = SOLID
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))
return
if(M.has_bane(BANE_SALT))
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
name = "Black Pepper"
id = "blackpepper"
diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
index 45d773a6851..9590bb00bb9 100644
--- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
+++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
@@ -63,14 +63,6 @@
if(created_volume >= 150)
playsound(get_turf(holder.my_atom), 'sound/effects/pray.ogg', 80, 0, round(created_volume/48))
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 << "The power of [diety] compels you!"
- 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)))
if(iscultist(C) || is_handofgod_cultist(C) || C.dna.species.id == "shadowling" || C.dna.species.id == "l_shadowling")
C << "The divine explosion sears you!"
diff --git a/html/changelog.html b/html/changelog.html
index 8671dcc9850..cb468995759 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -74,12 +74,17 @@
Sigil of Submission is now a Script instead of a Driver; It's unlocked one tier up, so you have to rely on the Drivers you have until you get Scripts, instead of spamming both Driver sigils for free converts.
To replace Sigil of Submission in the Driver tier; Added Taunting Tirade, which is a chanted scripture with a very fast invocation, which, on chanting, confuses, dizzies, and briefly stuns nearby non-servants and allows the invoker a brief time to relocate before continuing the chant.
Ghosts can now see the Gateway to the Celestial Derelict's remaining time as a countdown.
+ Anima Fragments are now slightly slower if not at maximum health and no longer drop a soul vessel when destroyed.
Quiltyquilty updated:
- Patches now hold only 40u.
- Patches in first aid kits now only contain 20u of chemicals.
+ coiax updated:
+
+ - Polymorphed mobs now have the same name, and where possible, the same equipment as their previous form.
+
14 June 2016
Joan updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index aa44569af5b..9c955319b6c 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -5628,6 +5628,11 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
a brief time to relocate before continuing the chant.
- rscadd: Ghosts can now see the Gateway to the Celestial Derelict's remaining time
as a countdown.
+ - tweak: Anima Fragments are now slightly slower if not at maximum health and no
+ longer drop a soul vessel when destroyed.
Quiltyquilty:
- rscdel: Patches now hold only 40u.
- rscdel: Patches in first aid kits now only contain 20u of chemicals.
+ coiax:
+ - rscadd: Polymorphed mobs now have the same name, and where possible, the same
+ equipment as their previous form.
diff --git a/html/changelogs/AutoChangeLog-pr-18522.yml b/html/changelogs/AutoChangeLog-pr-18522.yml
new file mode 100644
index 00000000000..d04e4dcc96b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-18522.yml
@@ -0,0 +1,4 @@
+author: "Xhuis"
+delete-after: True
+changes:
+ - tweak: "Revenants have been renamed to umbras and have undergone some tweaks."
diff --git a/html/changelogs/AutoChangeLog-pr-18587.yml b/html/changelogs/AutoChangeLog-pr-18587.yml
deleted file mode 100644
index 8e67b92dcda..00000000000
--- a/html/changelogs/AutoChangeLog-pr-18587.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "coiax"
-delete-after: True
-changes:
- - rscadd: "Polymorphed mobs now have the same name, and where possible, the same equipment as their previous form."
diff --git a/html/changelogs/AutoChangeLog-pr-18590.yml b/html/changelogs/AutoChangeLog-pr-18590.yml
deleted file mode 100644
index 436f31cf4f8..00000000000
--- a/html/changelogs/AutoChangeLog-pr-18590.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Joan"
-delete-after: True
-changes:
- - tweak: "Anima Fragments are now slightly slower if not at maximum health and no longer drop a soul vessel when destroyed."
diff --git a/html/changelogs/AutoChangeLog-pr-18615.yml b/html/changelogs/AutoChangeLog-pr-18615.yml
new file mode 100644
index 00000000000..fad5f0f65a3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-18615.yml
@@ -0,0 +1,4 @@
+author: "Quiltyquilty"
+delete-after: True
+changes:
+ - rscadd: "Beepsky now has his own home in the labor shuttle room."
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index aebe5b5b744..125b02a8939 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/tomatodecal.dmi b/icons/effects/tomatodecal.dmi
index 8296ad0b495..e01da0a2262 100644
Binary files a/icons/effects/tomatodecal.dmi and b/icons/effects/tomatodecal.dmi differ
diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi
index 231201e8bd9..9fa064ed1ee 100644
Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 83fd34c03a9..d0d78177369 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/obj/magic.dmi b/icons/obj/magic.dmi
index 141b2df3f81..c376dc73219 100644
Binary files a/icons/obj/magic.dmi and b/icons/obj/magic.dmi differ
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index 77b29407827..8b248d523b5 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -73,6 +73,18 @@ h1.alert, h2.alert {color: #000000;}
.holoparasite {color: #35333a;}
.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;}
.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
.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_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;}
.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
diff --git a/tgstation.dme b/tgstation.dme
index 1cf39ce6000..b0ab0ac29f6 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -120,7 +120,6 @@
#include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\other_mobs.dm"
-#include "code\_onclick\hud\revenanthud.dm"
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\swarmer.dm"
@@ -366,13 +365,14 @@
#include "code\game\gamemodes\miniantags\hades\hades.dm"
#include "code\game\gamemodes\miniantags\monkey\monkey.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\slaughter\slaughter.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_challenge.dm"
#include "code\game\gamemodes\nuclear\nuclearbomb.dm"