diff --git a/_maps/map_files/generic/z2.dmm b/_maps/map_files/generic/z2.dmm index 88e617b2e71..2b21a6efbfa 100644 --- a/_maps/map_files/generic/z2.dmm +++ b/_maps/map_files/generic/z2.dmm @@ -2766,7 +2766,9 @@ }, /obj/docking_port/mobile/assault_pod{ dwidth = 3; - name = "steel rain" + name = "steel rain"; + port_angle = 90; + preferred_direction = 4 }, /turf/open/floor/plating, /area/shuttle/assault_pod) @@ -6134,17 +6136,13 @@ /turf/open/floor/carpet, /area/hades) "tq" = ( -/obj/structure/table/wood, -/obj/item/weapon/kitchen/fork, -/turf/open/floor/carpet, -/area/hades) +/obj/structure/closet/cardboard, +/turf/open/floor/plating/airless, +/area/syndicate_mothership) "tr" = ( -/obj/structure/chair/wood/normal{ - icon_state = "wooden_chair"; - dir = 1 - }, -/turf/open/floor/carpet, -/area/hades) +/obj/structure/closet/cardboard/metal, +/turf/open/floor/plating/airless, +/area/syndicate_mothership) "ts" = ( /obj/structure/table/wood, /obj/item/weapon/storage/box/drinkingglasses, @@ -27137,13 +27135,13 @@ ab ab hi iR -iR +tq jF kn kI kn jF -iR +tr iR hi ab diff --git a/_maps/shuttles/emergency_bar.dmm b/_maps/shuttles/emergency_bar.dmm index 891792860f2..15abf97caec 100644 --- a/_maps/shuttles/emergency_bar.dmm +++ b/_maps/shuttles/emergency_bar.dmm @@ -767,7 +767,7 @@ aN aN aE aE -bt +ae bt bt bt diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index e59e326e194..f4a9088c345 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -77,8 +77,6 @@ #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 3dbc63b90fd..2427c1cc170 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -20,6 +20,7 @@ #define ROLE_MONKEY "monkey" #define ROLE_GANG "gangster" #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" @@ -42,6 +43,7 @@ var/global/list/special_roles = list( ROLE_NINJA, ROLE_MONKEY = /datum/game_mode/monkey, ROLE_GANG = /datum/game_mode/gang, + 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/_onclick/hud/revenanthud.dm b/code/_onclick/hud/revenanthud.dm new file mode 100644 index 00000000000..492e9e67b41 --- /dev/null +++ b/code/_onclick/hud/revenanthud.dm @@ -0,0 +1,10 @@ + +/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 5f50f266f53..b32de28b0a0 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -455,6 +455,13 @@ 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/datums/shuttles.dm b/code/datums/shuttles.dm index 6ecddf90420..48a433b485d 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -47,9 +47,9 @@ name = "The Emergency Escape Bar" description = "Features include sentient bar staff (a Bardrone and a \ Barmaid), bathroom, a quality lounge for the heads, and a \ - small gambling table." + large gathering table." admin_notes = "Bardrone and Barmaid are GODMODE, will be automatically \ - sentienced by the fun balloon at 60 seconds before arrival. No \ + sentienced by the fun balloon at 60 seconds before arrival. Has \ medical facilities." /datum/map_template/shuttle/emergency/birdboat diff --git a/code/game/gamemodes/clock_cult/clock_machines.dm b/code/game/gamemodes/clock_cult/clock_machines.dm index b2f06b43d2f..8db2ed225b2 100644 --- a/code/game/gamemodes/clock_cult/clock_machines.dm +++ b/code/game/gamemodes/clock_cult/clock_machines.dm @@ -142,14 +142,14 @@ health = 150 break_message = "The prism collapses with a heavy thud!" debris = list(/obj/item/clockwork/alloy_shards, /obj/item/clockwork/component/vanguard_cogwheel) - var/stored_alloy = 0 //250W = 1 alloy - var/max_alloy = 50000 + var/stored_alloy = 0 //2500W = 1 alloy = 100 liquified alloy + var/max_alloy = 25000 var/mob_cost = 200 var/structure_cost = 250 var/cyborg_cost = 300 /obj/structure/clockwork/powered/mending_motor/prefilled - stored_alloy = 5000 //starts with 20 alloy + stored_alloy = 2500 //starts with 1 replicant alloy/100 liquified alloy /obj/structure/clockwork/powered/mending_motor/total_accessable_power() . = ..() @@ -170,11 +170,11 @@ /obj/structure/clockwork/powered/mending_motor/examine(mob/user) ..() if(is_servant_of_ratvar(user) || isobserver(user)) - user << "It has [stored_alloy*0.004]/[max_alloy*0.004] units of replicant alloy, which is equivalent to [stored_alloy]W/[max_alloy]W of power." + user << "It contains [stored_alloy*0.04]/[max_alloy*0.04] units of liquified alloy, which is equivalent to [stored_alloy]W/[max_alloy]W of power." user << "It requires [mob_cost]W to heal clockwork mobs, [structure_cost]W for clockwork structures, and [cyborg_cost]W for cyborgs." /obj/structure/clockwork/powered/mending_motor/process() - if(!..()) + if(..() < mob_cost) visible_message("[src] emits an airy chuckling sound and falls dark!") toggle() return @@ -210,7 +210,7 @@ /obj/structure/clockwork/powered/mending_motor/attack_hand(mob/living/user) if(user.canUseTopic(src, BE_CLOSE)) - if(!total_accessable_power() >= 300) + if(total_accessable_power() < mob_cost) user << "[src] needs more power or replicant alloy to function!" return 0 toggle(0, user) diff --git a/code/game/gamemodes/clock_cult/clock_structures.dm b/code/game/gamemodes/clock_cult/clock_structures.dm index b3cd83bbbd0..d0627ce6ea4 100644 --- a/code/game/gamemodes/clock_cult/clock_structures.dm +++ b/code/game/gamemodes/clock_cult/clock_structures.dm @@ -832,7 +832,7 @@ icon_state = "sigiltransmission" color = "#EC8A2D" alpha = 50 - var/power_charge = 4000 //starts with 4000W by default + var/power_charge = 2500 //starts with 2500W by default /obj/effect/clockwork/sigil/transmission/examine(mob/user) ..() diff --git a/code/game/gamemodes/clock_cult/clockwork_proselytizer.dm b/code/game/gamemodes/clock_cult/clockwork_proselytizer.dm index fe418a803ea..00b27d33c11 100644 --- a/code/game/gamemodes/clock_cult/clockwork_proselytizer.dm +++ b/code/game/gamemodes/clock_cult/clockwork_proselytizer.dm @@ -7,14 +7,14 @@ w_class = 3 force = 5 flags = NOBLUDGEON - var/stored_alloy = 0 //Requires this to function; each chunk of replicant alloy provides 10 charge + var/stored_alloy = 0 //Requires this to function; each chunk of replicant alloy provides REPLICANT_ALLOY_UNIT var/max_alloy = REPLICANT_ALLOY_UNIT * 10 var/uses_alloy = TRUE var/metal_to_alloy = FALSE var/repairing = null //what we're currently repairing, if anything /obj/item/clockwork/clockwork_proselytizer/preloaded - stored_alloy = REPLICANT_ALLOY_UNIT + stored_alloy = REPLICANT_WALL_MINUS_FLOOR+REPLICANT_WALL_TOTAL /obj/item/clockwork/clockwork_proselytizer/scarab name = "scarab proselytizer" @@ -44,13 +44,26 @@ if(uses_alloy) user << "It has [stored_alloy]/[max_alloy] units of liquified alloy stored." user << "Use it on a Tinkerer's Cache, strike it with Replicant Alloy, or attack Replicant Alloy with it to add additional liquified alloy." + user << "Use it in-hand to remove stored liquified alloy." + +/obj/item/clockwork/clockwork_proselytizer/attack_self(mob/living/user) + if(is_servant_of_ratvar(user) && uses_alloy) + if(!can_use_alloy(REPLICANT_ALLOY_UNIT)) + user << "[src] [stored_alloy ? "Lacks enough":"Contains no"] alloy to reform[stored_alloy ? "":" any"] into solidified alloy!" + return + modify_stored_alloy(-REPLICANT_ALLOY_UNIT) + playsound(src, 'sound/items/Deconstruct.ogg', 50, 1) + new/obj/item/clockwork/component/replicant_alloy(user.loc) + user << "You force [stored_alloy ? "some":"all"] of the alloy in [src]'s compartments to reform and solidify. \ + It now contains [stored_alloy]/[max_alloy] units of liquified alloy." /obj/item/clockwork/clockwork_proselytizer/attackby(obj/item/I, mob/living/user, params) if(istype(I, /obj/item/clockwork/component/replicant_alloy) && is_servant_of_ratvar(user) && uses_alloy) - if(stored_alloy + REPLICANT_ALLOY_UNIT >= max_alloy) + if(!can_use_alloy(-REPLICANT_ALLOY_UNIT)) user << "[src]'s replicant alloy compartments are full!" return 0 modify_stored_alloy(REPLICANT_ALLOY_UNIT) + playsound(user, 'sound/machines/click.ogg', 50, 1) user << "You force [I] to liquify and pour it into [src]'s compartments. It now contains [stored_alloy]/[max_alloy] units of liquified alloy." user.drop_item() qdel(I) @@ -297,10 +310,10 @@ return /obj/structure/clockwork/wall_gear/proselytize_vals(mob/living/user, obj/item/clockwork/clockwork_proselytizer/proselytizer) - return list("operation_time" = 10, "new_obj_type" = /obj/item/clockwork/component/replicant_alloy, "alloy_cost" = REPLICANT_ALLOY_UNIT-REPLICANT_WALL_MINUS_FLOOR, "spawn_dir" = SOUTH) + return list("operation_time" = 10, "new_obj_type" = /obj/effect/overlay/temp/ratvar/beam/itemconsume, "alloy_cost" = -REPLICANT_WALL_MINUS_FLOOR, "spawn_dir" = SOUTH) /obj/item/clockwork/alloy_shards/proselytize_vals(mob/living/user, obj/item/clockwork/clockwork_proselytizer/proselytizer) - return list("operation_time" = 5, "new_obj_type" = /obj/item/clockwork/component/replicant_alloy, "alloy_cost" = REPLICANT_ALLOY_UNIT-REPLICANT_STANDARD, "spawn_dir" = SOUTH) + return list("operation_time" = 5, "new_obj_type" = /obj/effect/overlay/temp/ratvar/beam/itemconsume, "alloy_cost" = -REPLICANT_STANDARD, "spawn_dir" = SOUTH) /obj/item/clockwork/component/replicant_alloy/proselytize_vals(mob/living/user, obj/item/clockwork/clockwork_proselytizer/proselytizer) return list("operation_time" = 0, "new_obj_type" = /obj/effect/overlay/temp/ratvar/beam/itemconsume, "alloy_cost" = -REPLICANT_ALLOY_UNIT, "spawn_dir" = SOUTH) diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm new file mode 100644 index 00000000000..e903d7b1c4a --- /dev/null +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -0,0 +1,449 @@ +//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_spoken = ALL + languages_understood = 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 << "
" + src << "You are a revenant." + src << "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant." + src << "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds." + src << "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable." + src << "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." + src << "You do not remember anything of your past lives, nor will you remember anything about this one after your death." + src << "Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant to learn more." + var/datum/objective/revenant/objective = new + objective.owner = src.mind + src.mind.objectives += objective + src << "Objective #1: [objective.explanation_text]" + var/datum/objective/revenantFluff/objective2 = new + objective2.owner = src.mind + src.mind.objectives += objective2 + src << "Objective #2: [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/blight(null)) + AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction(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 << "You are once more concealed." + if(unstun_time && world.time >= unstun_time) + unstun_time = 0 + notransform = 0 + src << "You can move again!" + 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 = "
[essence]E
" + +/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 = "[src] says, \"[message]\"" + 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. + +/mob/living/simple_animal/revenant/ratvar_act() + return //clocks get out reee + +//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("[src] violently flinches!", \ + "As \the [W] passes through you, you feel your essence draining away!") + adjustBruteLoss(25) //hella effective + inhibited = 1 + update_action_buttons_icon() + addtimer(src, "reset_inhibit", 30, FALSE) + +/mob/living/simple_animal/revenant/proc/reset_inhibit() + if(src) + 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) + death() + +/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 << "NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]..." + notransform = 1 + revealed = 1 + invisibility = 0 + playsound(src, 'sound/effects/screech.ogg', 100, 1) + visible_message("[src] lets out a waning screech as violet mist swirls around its dissolving body!") + icon_state = "revenant_draining" + for(var/i = alpha, i > 0, i -= 10) + stoplag() + alpha = i + visible_message("[src]'s body breaks apart into a fine pile of blue dust.") + 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 << "You have been revealed!" + unreveal_time = world.time + time + else + src << "You have been revealed!" + 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 << "You cannot move!" + unstun_time = world.time + time + else + src << "You cannot move!" + 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 << "You cannot use abilities from inside of a wall." + return 0 + for(var/obj/O in T) + if(O.density && !O.CanPass(src, T, 5)) + src << "You cannot use abilities inside of a dense object." + return 0 + if(inhibited) + src << "Your powers have been suppressed by nulling energy!" + return 0 + if(!change_essence_amount(essence_cost, 1)) + src << "You lack the essence to use that ability." + 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 << "Gained [essence_amt]E from [source]." + else + src << "Lost [essence_amt]E from [source]." + 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() + ..() + addtimer(src, "try_reform", 600, FALSE) + +/obj/item/weapon/ectoplasm/revenant/proc/try_reform() + if(src) + if(reforming) + reforming = 0 + reform() + else + inert = 1 + visible_message("[src] settles down and seems lifeless.") + +/obj/item/weapon/ectoplasm/revenant/attack_self(mob/user) + if(!reforming || inert) + return ..() + user.visible_message("[user] scatters [src] in all directions.", \ + "You scatter [src] across the area. The particles slowly fade away.") + user.drop_item() + qdel(src) + +/obj/item/weapon/ectoplasm/revenant/throw_impact(atom/hit_atom) + ..() + if(inert) + return + visible_message("[src] breaks into particles upon impact, which fade away to nothingness.") + qdel(src) + +/obj/item/weapon/ectoplasm/revenant/examine(mob/user) + ..() + if(inert) + user << "It seems inert." + else if(reforming) + user << "It is shifting and distorted. It would be wise to destroy this." + +/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("[src] settles down and seems lifeless.") + return + 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("[src] settles down and seems lifeless.") + return + 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("[src] suddenly rises into the air before fading away.") + qdel(src) + if(src) //Should never happen, but just in case + inert = 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 diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm new file mode 100644 index 00000000000..1e7ed6c4bce --- /dev/null +++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm @@ -0,0 +1,381 @@ + +//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) + 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 << "You hear something behind you talking... [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: \"[msg]\" to [follow_whispee] [M]" + + + +/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 = 15 + 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) + addtimer(src, "overload", 0, FALSE, T, user) + +/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user) + for(var/obj/machinery/light/L in T) + 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 = 20 + 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) + addtimer(src, "defile", 0, FALSE, T) + +/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/proc/defile(turf/T) + 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) + if(corpseholder.connected.loc == corpseholder) + corpseholder.open() + for(var/obj/machinery/dna_scannernew/dna in T) + dna.open_machine() + for(var/obj/structure/window/window in T) + 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) + 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 = 60 + unlock_amount = 200 + 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) + addtimer(src, "malfunction", 0, FALSE, T, user) + +/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/proc/malfunction(turf/T, mob/user) + for(var/mob/living/simple_animal/bot/bot in T) + 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) + 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) + 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) //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 + 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) + addtimer(src, "blight", 0, FALSE, T, user) + +/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/proc/blight(turf/T, mob/user) + for(var/mob/living/mob in T) + 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) //Fucking with botanists, the ability. + vine.color = "#823abb" + PoolOrNew(/obj/effect/overlay/temp/revenant, vine.loc) + QDEL_IN(vine, 10) + for(var/obj/effect/glowshroom/shroom in T) + shroom.color = "#823abb" + PoolOrNew(/obj/effect/overlay/temp/revenant, shroom.loc) + QDEL_IN(shroom, 10) + for(var/obj/machinery/hydroponics/tray in T) + 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 new file mode 100644 index 00000000000..19cb12d0205 --- /dev/null +++ b/code/game/gamemodes/miniantags/revenant/revenant_blight.dm @@ -0,0 +1,68 @@ +/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*6)) + 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 += 8 + affected_mob.adjustStaminaLoss(8) + PoolOrNew(/obj/effect/overlay/temp/revenant, affected_mob.loc) + if(!finalstage && stagedamage < stage) + stagedamage++ + affected_mob.adjustToxLoss(stage*2) //should, normally, do about 30 toxin damage. + PoolOrNew(/obj/effect/overlay/temp/revenant, affected_mob.loc) + if(!finalstage && prob(45)) + affected_mob.adjustStaminaLoss(stage) + ..() //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 new file mode 100644 index 00000000000..f73ac040966 --- /dev/null +++ b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm @@ -0,0 +1,63 @@ +#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 deleted file mode 100644 index c4a3f3e5df7..00000000000 --- a/code/game/gamemodes/miniantags/umbra/umbra.dm +++ /dev/null @@ -1,385 +0,0 @@ -#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" - unique_name = TRUE - gender = NEUTER - 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 = FALSE - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - 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) - unsuitable_atmos_damage = 0 - friendly = "passes through" - speak_emote = list("murmurs") - emote_hear = list("murmurs") - languages_spoken = ALL - languages_understood = ALL - status_flags = 0 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - incorporeal_move = 3 - flying = TRUE - wander = FALSE - stop_automated_movement = TRUE - mob_size = MOB_SIZE_TINY - anchored = TRUE - density = FALSE - sight = SEE_SELF - see_invisible = SEE_INVISIBLE_NOLIGHTING - see_in_dark = 8 - 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/image/ghost_image //So ghosts can see the umbra in the dark - 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() - ..() - ghost_image = image(icon, src, icon_state) - ghost_darkness_images |= ghost_image - updateallghostimages() - 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!" - if(!possessed) //To ensure that umbras don't escape their quarry - 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 - if(key) - O << "You were too late! The umbra is occupied." - 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 deleted file mode 100644 index eb860ee810b..00000000000 --- a/code/game/gamemodes/miniantags/umbra/umbra_abilities.dm +++ /dev/null @@ -1,125 +0,0 @@ -//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 - if(isguardian(target)) - user << "You see a confusing overlay of memories from two seperate viewpoints. Useless." - revert_cast() - return - if(user.possessed) - user << "Unpossess [user.possessed] first!" - 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:
[target.mind.memory]

" - 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 deleted file mode 100644 index 3b0e2e01561..00000000000 --- a/code/game/gamemodes/miniantags/umbra/umbra_objectives.dm +++ /dev/null @@ -1,28 +0,0 @@ -/* - -Possible objectives: - 1. Steal the memories of # humans. (# = 10-20) - -*/ - -/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 = rand(10, 20) - 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 deleted file mode 100644 index 38c5f441d32..00000000000 --- a/code/game/gamemodes/miniantags/umbra/umbra_spawn_event.dm +++ /dev/null @@ -1,18 +0,0 @@ -/datum/round_event_control/spawn_umbra - name = "Spawn Umbra" - typepath = /datum/round_event/ghost_role/umbra - weight = 0 //Admin-only for the time being - 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/I = image('icons/mob/mob.dmi', "umbra") - notify_ghosts("An umbra has formed in [get_area(U)]. Interact with it to take control of it.", 'sound/effects/ghost2.ogg', alert_overlay = I, source = U, action = NOTIFY_ATTACK) - spawned_mobs += U - return SUCCESSFUL_SPAWN diff --git a/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm b/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm deleted file mode 100644 index ea005b1d2c7..00000000000 --- a/code/game/gamemodes/miniantags/umbra/umbral_ashes.dm +++ /dev/null @@ -1,44 +0,0 @@ -#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/I = image('icons/mob/mob.dmi', "umbra") - notify_ghosts("An umbra has reformed in [get_area(U)]. Interact with it to take control of it.", 'sound/effects/ghost2.ogg', alert_overlay = I, source = U, action = NOTIFY_ATTACK) - 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/machinery/computer/gulag_teleporter.dm b/code/game/machinery/computer/gulag_teleporter.dm index c467e54b4fb..e1ea4f9c3d1 100644 --- a/code/game/machinery/computer/gulag_teleporter.dm +++ b/code/game/machinery/computer/gulag_teleporter.dm @@ -145,7 +145,7 @@ log_game("[user]([user.ckey] teleported [prisoner]([prisoner.ckey]) to the Labor Camp ([beacon.x], [beacon.y], [beacon.z]) for [id.goal] points.") teleporter.handle_prisoner(id, temporary_record) playsound(loc, "sound/weapons/emitter.ogg", 50, 1) - prisoner.forceMove(beacon) + prisoner.forceMove(get_turf(beacon)) prisoner.Weaken(2) // small travel dizziness prisoner << "The teleportation makes you a little dizzy." PoolOrNew(/obj/effect/particle_effect/sparks, prisoner.loc) diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm index e8106c5f095..5a4fab9fa02 100644 --- a/code/game/objects/effects/overlays.dm +++ b/code/game/objects/effects/overlays.dm @@ -257,10 +257,17 @@ icon_state = "sigilactiveoverlay" alpha = 0 -/obj/effect/overlay/temp/purple_sparkles + +/obj/effect/overlay/temp/revenant 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/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm index bced5d826bd..f450be51072 100644 --- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm +++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm @@ -11,6 +11,7 @@ open_sound = 'sound/effects/rustle2.ogg' cutting_sound = 'sound/items/poster_ripped.ogg' material_drop = /obj/item/stack/sheet/cardboard + var/move_speed_multiplier = 1 var/move_delay = 0 var/egged = 0 @@ -19,7 +20,7 @@ return move_delay = 1 if(step(src, direction)) - spawn(config.walk_speed) + spawn(config.walk_speed*move_speed_multiplier) move_delay = 0 else move_delay = 0 @@ -52,3 +53,17 @@ flick_overlay(I,viewing,8) I.alpha = 0 animate(I, pixel_z = 32, alpha = 255, time = 5, easing = ELASTIC_EASING) + + +/obj/structure/closet/cardboard/metal + name = "large metal box" + desc = "THE COWARDS! THE FOOLS!" + icon_state = "metalbox" + health = 500 + mob_storage_capacity = 5 + burn_state = FIRE_PROOF + move_speed_multiplier = 2 + cutting_tool = /obj/item/weapon/weldingtool + open_sound = 'sound/machines/click.ogg' + cutting_sound = 'sound/items/Welder.ogg' + material_drop = /obj/item/stack/sheet/plasteel \ No newline at end of file diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 68589d8f58c..9c6780d0f21 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -126,6 +126,13 @@ else message_admins("[key_name_admin(usr)] tried to start a clockwork cult. Unfortunately, there were no candidates available.") log_admin("[key_name(usr)] failed to start a clockwork cult.") + if("16") + 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.") else if(href_list["forceevent"]) if(!check_rights(R_FUN)) diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index 7b14e2b1aa0..1c5d3b54c67 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -22,6 +22,7 @@ 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) @@ -549,3 +550,7 @@ /datum/admins/proc/makeAbductorTeam() new /datum/round_event/ghost_role/abductor return 1 + +/datum/admins/proc/makeRevenant() + new /datum/round_event/ghost_role/revenant + return 1 diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 06391e09f34..d396b59715d 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -224,23 +224,20 @@ 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 and salt piles. Used by umbras. - if(!isumbra(L)) - L.incorporeal_move = 1 - return - var/mob/living/simple_animal/umbra/U = L + if(3) //Incorporeal move, but blocked by holy-watered tiles and salt piles. 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) + L << "[S] bars your passage!" + if(istype(L, /mob/living/simple_animal/revenant)) + var/mob/living/simple_animal/revenant/R = L + R.reveal(20) + R.stun(20) return if(stepTurf.flags & NOJAUNT) - U << "Holy energies block your path!" - U.immobilize(2, TRUE) + L << "Holy energies block your path." else - U.loc = get_step(L, direct) - U.setDir(direct) + L.loc = get_step(L, direct) + L.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 032c2d9c0b9..efd4a585fce 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/purple_sparkles, get_turf(M)) + PoolOrNew(/obj/effect/overlay/temp/revenant, 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/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 7b0decb376f..af70d5ddb6a 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -63,6 +63,15 @@ 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) + R.adjustHealth(50) + 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 << "The divine explosion sears you!" diff --git a/html/changelogs/AutoChangeLog-pr-18980.yml b/html/changelogs/AutoChangeLog-pr-18980.yml new file mode 100644 index 00000000000..edde9010e3c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-18980.yml @@ -0,0 +1,4 @@ +author: "incoming5643" +delete-after: True +changes: + - rscadd: "Nuke ops can now harness the power of METAL BOXES: special versions of cardboard boxes that can take a lot of abuse and can fit the whole team if need be. They're not nearly as light as their cardboard brothers though..." diff --git a/html/changelogs/AutoChangeLog-pr-19043.yml b/html/changelogs/AutoChangeLog-pr-19043.yml new file mode 100644 index 00000000000..d664bde3a9c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-19043.yml @@ -0,0 +1,5 @@ +author: "kevinz000" +delete-after: True +changes: + - rscadd: "Some new emojis have been added to OOC for your enjoyment. Try them out." + - rscadd: "New emojis are : coffee, trophy, tea, gear, supermatter, forceweapon, gift, kudzu, dosh, chrono, nya, and tophat. Type :nameofemoji: in OOC to use." diff --git a/html/changelogs/AutoChangeLog-pr-19055.yml b/html/changelogs/AutoChangeLog-pr-19055.yml new file mode 100644 index 00000000000..65110ca7452 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-19055.yml @@ -0,0 +1,4 @@ +author: "Joan" +delete-after: True +changes: + - rscadd: "Clockwork Proselytizers can now reform stored alloy into usable component alloy when used in-hand." diff --git a/html/changelogs/AutoChangeLog-pr-19056.yml b/html/changelogs/AutoChangeLog-pr-19056.yml new file mode 100644 index 00000000000..cd4d0f18bf6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-19056.yml @@ -0,0 +1,4 @@ +author: "Joan" +delete-after: True +changes: + - rscdel: "Mending motors start with 2500W of power in alloy, from 5000. Sigils of Transmission start with 2500W of power, from 4000. Clockwork Proselytizers start with 90 alloy, from 100." diff --git a/html/changelogs/AutoChangeLog-pr-19059.yml b/html/changelogs/AutoChangeLog-pr-19059.yml new file mode 100644 index 00000000000..70f6664ceac --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-19059.yml @@ -0,0 +1,5 @@ +author: "Joan" +delete-after: True +changes: + - rscdel: "Umbras have been removed." + - rscadd: "Revenants have been readded, though they are now vulnerable to salt piles." diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 0f92cebd831..8bb3de3ae43 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/effects/revenant.dmi b/icons/effects/revenant.dmi new file mode 100644 index 00000000000..61386fc459a Binary files /dev/null and b/icons/effects/revenant.dmi differ diff --git a/icons/emoji.dmi b/icons/emoji.dmi index 9dc3ff81eb4..045d3856530 100644 Binary files a/icons/emoji.dmi and b/icons/emoji.dmi differ diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index 23c68f8ae33..d41fc220901 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 16d3103afd9..9ec94d38800 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/obj/closet.dmi b/icons/obj/closet.dmi index 33cb50421a4..3f3eda012cc 100644 Binary files a/icons/obj/closet.dmi and b/icons/obj/closet.dmi differ diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi index fa2f7320c1c..b212714e6de 100644 Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 802b4b79c5b..f3eb84d2735 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -121,6 +121,7 @@ #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" @@ -369,14 +370,13 @@ #include "code\game\gamemodes\miniantags\hades\hades_chapel.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"