-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
revenant.dm
479 lines (425 loc) · 18.1 KB
/
revenant.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
//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
#define REVENANT_NAME_FILE "revenant_names.json"
/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"
var/stasis = FALSE
mob_biotypes = list(MOB_SPIRIT)
incorporeal_move = INCORPOREAL_MOVE_JAUNT
invisibility = INVISIBILITY_REVENANT
health = INFINITY //Revenants don't use health, they use essence instead
maxHealth = INFINITY
layer = GHOST_LAYER
healable = FALSE
spacewalk = TRUE
sight = SEE_SELF
throwforce = 0
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
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 = FALSE
density = FALSE
movement_type = FLYING
move_resist = MOVE_FORCE_OVERPOWERING
mob_size = MOB_SIZE_TINY
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
speed = 1
unique_name = TRUE
hud_possible = list(ANTAG_HUD)
hud_type = /datum/hud/revenant
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 = TRUE //If the revenant regenerates essence or not
var/essence_regen_amount = 5 //How much essence regenerates
var/essence_accumulated = 0 //How much essence the revenant has stolen
var/essence_excess = 0 //How much stolen essence avilable for unlocks
var/revealed = FALSE //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 = FALSE //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 = FALSE //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/generated_objectives_and_spells = FALSE
/mob/living/simple_animal/revenant/Initialize(mapload)
. = ..()
AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null))
AddSpell(new /obj/effect/proc_holder/spell/targeted/telepathy/revenant(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))
random_revenant_name()
/mob/living/simple_animal/revenant/canUseTopic()
return FALSE
/mob/living/simple_animal/revenant/proc/random_revenant_name()
var/built_name = ""
built_name += pick(strings(REVENANT_NAME_FILE, "spirit_type"))
built_name += " of "
built_name += pick(strings(REVENANT_NAME_FILE, "adverb"))
built_name += pick(strings(REVENANT_NAME_FILE, "theme"))
name = built_name
/mob/living/simple_animal/revenant/Login()
..()
to_chat(src, "<span class='deadsay'><span class='big bold'>You are a revenant.</span></span>")
to_chat(src, "<b>Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.</b>")
to_chat(src, "<b>You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.</b>")
to_chat(src, "<b>You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.</b>")
to_chat(src, "<b>To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.</b>")
to_chat(src, "<b><i>You do not remember anything of your past lives, nor will you remember anything about this one after your death.</i></b>")
to_chat(src, "<b>Be sure to read <a href=\"https://tgstation13.org/wiki/Revenant\">the wiki page</a> to learn more.</b>")
if(!generated_objectives_and_spells)
generated_objectives_and_spells = TRUE
mind.assigned_role = ROLE_REVENANT
mind.special_role = ROLE_REVENANT
SEND_SOUND(src, sound('sound/effects/ghost.ogg'))
mind.add_antag_datum(/datum/antagonist/revenant)
//Life, Stat, Hud Updates, and Say
/mob/living/simple_animal/revenant/Life()
if(stasis)
return
if(revealed && essence <= 0)
death()
if(unreveal_time && world.time >= unreveal_time)
unreveal_time = 0
revealed = FALSE
incorporeal_move = INCORPOREAL_MOVE_JAUNT
invisibility = INVISIBILITY_REVENANT
to_chat(src, "<span class='revenboldnotice'>You are once more concealed.</span>")
if(unstun_time && world.time >= unstun_time)
unstun_time = 0
notransform = FALSE
to_chat(src, "<span class='revenboldnotice'>You can move again!</span>")
if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
essence = min(essence_regen_cap, essence+essence_regen_amount)
update_action_buttons_icon() //because we update something required by our spells in life, we need to update our buttons
update_spooky_icon()
update_health_hud()
..()
/mob/living/simple_animal/revenant/Stat()
..()
if(statpanel("Status"))
stat(null, "Current essence: [essence]/[essence_regen_cap]E")
stat(null, "Stolen essence: [essence_accumulated]E")
stat(null, "Unused stolen essence: [essence_excess]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)
essencecolor = "#1D2953" //oh jeez you're dying
hud_used.healths.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='[essencecolor]'>[essence]E</font></div>"
/mob/living/simple_animal/revenant/med_hud_set_health()
return //we use no hud
/mob/living/simple_animal/revenant/med_hud_set_status()
return //we use no hud
/mob/living/simple_animal/revenant/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
if(!message)
return
src.log_talk(message, LOG_SAY)
var/rendered = "<span class='revennotice'><b>[src]</b> says, \"[message]\"</span>"
for(var/mob/M in GLOB.mob_list)
if(isrevenant(M))
to_chat(M, rendered)
else if(isobserver(M))
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
return
//Immunities
/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/structure/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/nullrod))
visible_message("<span class='warning'>[src] violently flinches!</span>", \
"<span class='revendanger'>As \the [W] passes through you, you feel your essence draining away!</span>")
adjustBruteLoss(25) //hella effective
inhibited = TRUE
update_action_buttons_icon()
addtimer(CALLBACK(src, .proc/reset_inhibit), 30)
/mob/living/simple_animal/revenant/proc/reset_inhibit()
inhibited = FALSE
update_action_buttons_icon()
/mob/living/simple_animal/revenant/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
if(!forced && !revealed)
return FALSE
. = amount
essence = max(0, essence-amount)
if(updating_health)
update_health_hud()
if(!essence)
death()
/mob/living/simple_animal/revenant/dust(just_ash, drop_items, force)
death()
/mob/living/simple_animal/revenant/gib()
death()
/mob/living/simple_animal/revenant/death()
if(!revealed || stasis) //Revenants cannot die if they aren't revealed //or are already dead
return 0
stasis = TRUE
to_chat(src, "<span class='revendanger'>NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]...</span>")
notransform = TRUE
revealed = TRUE
invisibility = 0
playsound(src, 'sound/effects/screech.ogg', 100, 1)
visible_message("<span class='warning'>[src] lets out a waning screech as violet mist swirls around its dissolving body!</span>")
icon_state = "revenant_draining"
for(var/i = alpha, i > 0, i -= 10)
stoplag()
alpha = i
visible_message("<span class='danger'>[src]'s body breaks apart into a fine pile of blue dust.</span>")
var/reforming_essence = essence_regen_cap //retain the gained essence capacity
var/obj/item/ectoplasm/revenant/R = new(get_turf(src))
R.essence = max(reforming_essence - 15 * perfectsouls, 75) //minus any perfect souls
R.old_key = client.key //If the essence reforms, the old revenant is put back in the body
R.revenant = src
invisibility = INVISIBILITY_ABSTRACT
revealed = FALSE
ghostize(0)//Don't re-enter invisible corpse
//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 = TRUE
invisibility = 0
incorporeal_move = FALSE
if(!unreveal_time)
to_chat(src, "<span class='revendanger'>You have been revealed!</span>")
unreveal_time = world.time + time
else
to_chat(src, "<span class='revenwarning'>You have been revealed!</span>")
unreveal_time = unreveal_time + time
update_spooky_icon()
/mob/living/simple_animal/revenant/proc/stun(time)
if(!src)
return
if(time <= 0)
return
notransform = TRUE
if(!unstun_time)
to_chat(src, "<span class='revendanger'>You cannot move!</span>")
unstun_time = world.time + time
else
to_chat(src, "<span class='revenwarning'>You cannot move!</span>")
unstun_time = unstun_time + time
update_spooky_icon()
/mob/living/simple_animal/revenant/proc/update_spooky_icon()
if(revealed)
if(notransform)
if(draining)
icon_state = icon_drain
else
icon_state = icon_stun
else
icon_state = icon_reveal
else
icon_state = icon_idle
/mob/living/simple_animal/revenant/proc/castcheck(essence_cost)
if(!src)
return
var/turf/T = get_turf(src)
if(isclosedturf(T))
to_chat(src, "<span class='revenwarning'>You cannot use abilities from inside of a wall.</span>")
return FALSE
for(var/obj/O in T)
if(O.density && !O.CanPass(src, T))
to_chat(src, "<span class='revenwarning'>You cannot use abilities inside of a dense object.</span>")
return FALSE
if(inhibited)
to_chat(src, "<span class='revenwarning'>Your powers have been suppressed by nulling energy!</span>")
return FALSE
if(!change_essence_amount(essence_cost, TRUE))
to_chat(src, "<span class='revenwarning'>You lack the essence to use that ability.</span>")
return FALSE
return TRUE
/mob/living/simple_animal/revenant/proc/unlock(essence_cost)
if(essence_excess < essence_cost)
return FALSE
essence_excess -= essence_cost
update_action_buttons_icon()
return TRUE
/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null)
if(!src)
return
if(essence + essence_amt < 0)
return
essence = max(0, essence+essence_amt)
update_health_hud()
if(essence_amt > 0)
essence_accumulated = max(0, essence_accumulated+essence_amt)
essence_excess = max(0, essence_excess+essence_amt)
update_action_buttons_icon()
if(!silent)
if(essence_amt > 0)
to_chat(src, "<span class='revennotice'>Gained [essence_amt]E[source ? " from [source]":""].</span>")
else
to_chat(src, "<span class='revenminor'>Lost [essence_amt]E[source ? " from [source]":""].</span>")
return 1
/mob/living/simple_animal/revenant/proc/death_reset()
revealed = FALSE
unreveal_time = 0
notransform = 0
unstun_time = 0
inhibited = FALSE
draining = FALSE
incorporeal_move = INCORPOREAL_MOVE_JAUNT
invisibility = INVISIBILITY_REVENANT
alpha=255
stasis = FALSE
//reforming
/obj/item/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 = WEIGHT_CLASS_SMALL
var/essence = 75 //the maximum essence of the reforming revenant
var/reforming = TRUE
var/inert = FALSE
var/old_key //key of the previous revenant, will have first pick on reform.
var/mob/living/simple_animal/revenant/revenant
/obj/item/ectoplasm/revenant/New()
..()
addtimer(CALLBACK(src, .proc/try_reform), 600)
/obj/item/ectoplasm/revenant/proc/scatter()
qdel(src)
/obj/item/ectoplasm/revenant/proc/try_reform()
if(reforming)
reforming = FALSE
reform()
else
inert = TRUE
visible_message("<span class='warning'>[src] settles down and seems lifeless.</span>")
/obj/item/ectoplasm/revenant/attack_self(mob/user)
if(!reforming || inert)
return ..()
user.visible_message("<span class='notice'>[user] scatters [src] in all directions.</span>", \
"<span class='notice'>You scatter [src] across the area. The particles slowly fade away.</span>")
user.dropItemToGround(src)
scatter()
/obj/item/ectoplasm/revenant/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..()
if(inert)
return
visible_message("<span class='notice'>[src] breaks into particles upon impact, which fade away to nothingness.</span>")
scatter()
/obj/item/ectoplasm/revenant/examine(mob/user)
..()
if(inert)
to_chat(user, "<span class='revennotice'>It seems inert.</span>")
else if(reforming)
to_chat(user, "<span class='revenwarning'>It is shifting and distorted. It would be wise to destroy this.</span>")
/obj/item/ectoplasm/revenant/proc/reform()
if(QDELETED(src) || QDELETED(revenant) || inert)
return
var/key_of_revenant
message_admins("Revenant ectoplasm was left undestroyed for 1 minute and is reforming into a new revenant.")
forceMove(drop_location()) //In case it's in a backpack or someone's hand
revenant.forceMove(loc)
if(old_key)
for(var/mob/M in GLOB.dead_mob_list)
if(M.client && M.client.key == old_key) //Only recreates the mob if the mob the client is in is dead
key_of_revenant = old_key
break
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 = pollCandidatesForMob("Do you want to be [revenant.name] (reforming)?", ROLE_REVENANT, null, ROLE_REVENANT, 50, revenant)
if(!LAZYLEN(candidates))
qdel(revenant)
message_admins("No candidates were found for the new revenant. Oh well!")
inert = TRUE
visible_message("<span class='revenwarning'>[src] settles down and seems lifeless.</span>")
return
var/mob/dead/observer/C = pick(candidates)
key_of_revenant = C.key
if(!key_of_revenant)
qdel(revenant)
message_admins("No ckey was found for the new revenant. Oh well!")
inert = TRUE
visible_message("<span class='revenwarning'>[src] settles down and seems lifeless.</span>")
return
message_admins("[key_of_revenant] has been [old_key == key_of_revenant ? "re":""]made into a revenant by reforming ectoplasm.")
log_game("[key_of_revenant] was [old_key == key_of_revenant ? "re":""]made as a revenant by reforming ectoplasm.")
visible_message("<span class='revenboldnotice'>[src] suddenly rises into the air before fading away.</span>")
revenant.essence = essence
revenant.essence_regen_cap = essence
revenant.death_reset()
revenant.key = key_of_revenant
revenant = null
qdel(src)
/obj/item/ectoplasm/revenant/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the shadow realm!</span>")
scatter()
return (OXYLOSS)
/obj/item/ectoplasm/revenant/Destroy()
if(!QDELETED(revenant))
qdel(revenant)
..()
//objectives
/datum/objective/revenant
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(!isrevenant(owner.current))
return FALSE
var/mob/living/simple_animal/revenant/R = owner.current
if(!R || R.stat == DEAD)
return FALSE
var/essence_stolen = R.essence_accumulated
if(essence_stolen < targetAmount)
return FALSE
return TRUE
/datum/objective/revenantFluff
/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 TRUE