mirror of
https://github.com/goonstation/goonstation-2016.git
synced 2026-07-10 00:22:22 +01:00
622 lines
17 KiB
Plaintext
622 lines
17 KiB
Plaintext
// Wraith
|
|
// Technically neither living nor dead benefits us in such a way that we should be subclassing them.
|
|
|
|
/mob/wraith
|
|
name = "Wraith"
|
|
real_name = "Wraith" //todo: construct name from a user input (e.g. <x> the Impaler)
|
|
desc = "Jesus Christ, how spooky."
|
|
icon = 'icons/mob/mob.dmi'
|
|
icon_state = "wraith"
|
|
layer = NOLIGHT_EFFECTS_LAYER_BASE
|
|
density = 0
|
|
canmove = 1
|
|
blinded = 0
|
|
anchored = 1
|
|
alpha = 180
|
|
stat = 2 // = technically, we hear ghost chat.
|
|
var/deaths = 0
|
|
|
|
var/haunting = 0
|
|
var/hauntBonus = 0
|
|
var/justdied = 0
|
|
//////////////
|
|
// Wraith Overrides
|
|
//////////////
|
|
|
|
proc/make_name()
|
|
var/len = rand(4, 8)
|
|
var/vowel_prob = 0
|
|
var/list/con = list("x", "z", "n", "k", "s", "l", "t", "r", "sh", "m", "d")
|
|
var/list/vow = list("y", "o", "a", "ae", "u", "ou")
|
|
var/theName = ""
|
|
for (var/i = 1, i <= len, i++)
|
|
if (prob(vowel_prob))
|
|
vowel_prob = 0
|
|
theName += pick(vow)
|
|
else
|
|
vowel_prob += rand(15, 40)
|
|
theName += pick(con)
|
|
var/fc = copytext(theName, 1, 2)
|
|
theName = "[uppertext(fc)][copytext(theName, 2)]"
|
|
return theName
|
|
|
|
|
|
New(var/mob/M)
|
|
. = ..()
|
|
src.invisibility = 16
|
|
//src.sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
|
|
src.sight |= SEE_SELF // let's not make it see through walls
|
|
src.see_invisible = 16
|
|
src.a_intent = "disarm"
|
|
src.see_in_dark = SEE_DARK_FULL
|
|
src.abilityHolder = new /datum/abilityHolder/wraith(src)
|
|
src.abilityHolder.points = 50
|
|
|
|
name = make_name() + "[pick(" the Impaler", " the Tormentor", " the Forsaken", " the Destroyer", " the Devourer", " the Tyrant", " the Overlord", " the Damned", " the Desolator", " the Exiled")]"
|
|
real_name = name
|
|
|
|
is_spacefaring()
|
|
return !density
|
|
|
|
movement_delay()
|
|
if (density)
|
|
return 4
|
|
return -1
|
|
|
|
meteorhit()
|
|
return
|
|
|
|
Login()
|
|
..()
|
|
src.updateButtons()
|
|
|
|
|
|
disposing()
|
|
..()
|
|
|
|
Stat()
|
|
..()
|
|
stat("Health:", src.health)
|
|
|
|
Life(parent)
|
|
if (..(parent))
|
|
return 1
|
|
|
|
if (src.client)
|
|
src.antagonist_overlay_refresh(0, 0)
|
|
|
|
if (!src.abilityHolder)
|
|
src.abilityHolder = new /datum/abilityHolder/wraith(src)
|
|
|
|
if (src.haunting)
|
|
src.hauntBonus = 0
|
|
for (var/mob/living/carbon/human/H in viewers(6, src))
|
|
if (!H.stat && !H.bioHolder.HasEffect("revenant"))
|
|
src.hauntBonus += 5
|
|
src.abilityHolder.addBonus(src.hauntBonus)
|
|
|
|
src.abilityHolder.generatePoints()
|
|
|
|
if (src.health < 1)
|
|
src.death(0)
|
|
return
|
|
else if (src.health < src.max_health)
|
|
src.health++
|
|
|
|
// No log entries for unaffected mobs (Convair880).
|
|
ex_act(severity)
|
|
return
|
|
|
|
death(gibbed)
|
|
//Todo: some cool-ass effects here
|
|
|
|
//Back to square one with you!
|
|
|
|
var/datum/abilityHolder/wraith/W = src.abilityHolder
|
|
if(istype(W))
|
|
W.corpsecount = 0
|
|
src.abilityHolder.points = 0
|
|
src.abilityHolder.regenRate = 1
|
|
src.health = initial(src.health) // oh sweet jesus it spammed so hard
|
|
src.haunting = 0
|
|
src.hauntBonus = 0
|
|
deaths++
|
|
src.makeIncorporeal()
|
|
if (src.mind)
|
|
for (var/datum/objective/specialist/wraith/WO in src.mind.objectives)
|
|
WO.onWeakened()
|
|
if (deaths < 2)
|
|
boutput(src, "<span style=\"color:red\"><b>You have been defeated...for now. The strain of banishment has weakened you, and you will not survive another.</b></span>")
|
|
src.justdied = 1
|
|
src.set_loc(pick(latejoin))
|
|
spawn(150) //15 seconds
|
|
src.justdied = 0
|
|
else
|
|
boutput(src, "<span style=\"color:red\"><b>Your connection with the mortal realm is severed. You have been permanently banished.</b></span>")
|
|
if (src.mind)
|
|
for (var/datum/objective/specialist/wraith/WO in src.mind.objectives)
|
|
WO.onBanished()
|
|
src.ghostize()
|
|
qdel(src)
|
|
|
|
proc/onAbsorb(var/mob/M)
|
|
if (src.mind)
|
|
for (var/datum/objective/specialist/wraith/WO in src.mind.objectives)
|
|
WO.onAbsorb(M)
|
|
|
|
CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
|
|
if (src.density) return 0
|
|
else return 1
|
|
|
|
|
|
projCanHit(datum/projectile/P)
|
|
if (src.density) return 1
|
|
else return 0
|
|
|
|
|
|
bullet_act(var/obj/projectile/P)
|
|
var/damage = 0
|
|
damage = round((P.power*P.proj_data.ks_ratio), 1.0)
|
|
|
|
switch (P.proj_data.damage_type)
|
|
if (D_KINETIC)
|
|
src.TakeDamage(null, damage, 0)
|
|
if (D_PIERCING)
|
|
src.TakeDamage(null, damage / 2.0, 0)
|
|
if (D_SLASHING)
|
|
src.TakeDamage(null, damage, 0)
|
|
if (D_BURNING)
|
|
src.TakeDamage(null, 0, damage)
|
|
if (D_ENERGY)
|
|
src.TakeDamage(null, 0, damage)
|
|
|
|
if(!P.proj_data.silentshot)
|
|
src.visible_message("<span style=\"color:red\">[src] is hit by the [P]!</span>")
|
|
|
|
|
|
TakeDamage(zone, brute, burn)
|
|
if (!src.density)
|
|
return
|
|
health -= burn
|
|
health -= brute * 3
|
|
health = min(max_health, health)
|
|
if (src.health <= 0)
|
|
src.death(0)
|
|
|
|
HealDamage(zone, brute, burn)
|
|
TakeDamage(zone, -(brute / 3), -burn)
|
|
|
|
updatehealth()
|
|
return
|
|
|
|
Move(var/turf/NewLoc, direct)
|
|
if (loc)
|
|
if (!isturf(loc) && !density)
|
|
loc = get_turf(loc)
|
|
else
|
|
loc = locate(1,1,1)
|
|
|
|
if(!canmove) return
|
|
|
|
if(!isturf(src.loc)) src.set_loc(get_turf(src))
|
|
|
|
if (NewLoc)
|
|
if (isrestrictedz(NewLoc.z) && !restricted_z_allowed(src, NewLoc) && !(src.client && src.client.holder))
|
|
var/OS = observer_start.len ? pick(observer_start) : locate(1, 1, 1)
|
|
if (OS)
|
|
src.set_loc(OS)
|
|
else
|
|
src.z = 1
|
|
return
|
|
|
|
var/mydir = get_dir(src, NewLoc)
|
|
var/salted = 0
|
|
if (mydir == NORTH || mydir == EAST || mydir == WEST || mydir == SOUTH)
|
|
if (src.density && !NewLoc.Enter(src))
|
|
return
|
|
|
|
else
|
|
var/turf/vertical
|
|
var/turf/horizontal
|
|
var/blocked = 1
|
|
if (mydir & NORTH)
|
|
vertical = get_step(src, NORTH)
|
|
else
|
|
vertical = get_step(src, SOUTH)
|
|
|
|
if (mydir & WEST)
|
|
horizontal = get_step(src, WEST)
|
|
else
|
|
horizontal = get_step(src, EAST)
|
|
|
|
var/turf/oldloc = loc
|
|
var/horiz = 0
|
|
var/vert = 0
|
|
|
|
if (!src.density || vertical.Enter(src))
|
|
vert = 1
|
|
loc = vertical
|
|
if (!src.density || NewLoc.Enter(src))
|
|
blocked = 0
|
|
for(var/obj/decal/cleanable/saltpile/A in vertical)
|
|
if (istype(A)) salted = 1
|
|
if (salted) break
|
|
loc = oldloc
|
|
|
|
if (!src.density || horizontal.Enter(src))
|
|
horiz = 1
|
|
loc = horizontal
|
|
if (!src.density || NewLoc.Enter(src))
|
|
blocked = 0
|
|
for(var/obj/decal/cleanable/saltpile/A in horizontal)
|
|
if (istype(A)) salted = 1
|
|
if (salted) break
|
|
loc = oldloc
|
|
|
|
if (blocked)
|
|
if (horiz)
|
|
Move(horizontal)
|
|
return
|
|
else if (vert)
|
|
Move(vertical)
|
|
return
|
|
return
|
|
|
|
for(var/obj/decal/cleanable/saltpile/A in NewLoc)
|
|
if (istype(A)) salted = 1
|
|
if (salted) break
|
|
|
|
dir = get_dir(loc, NewLoc)
|
|
src.set_loc(NewLoc)
|
|
NewLoc.HasEntered(src)
|
|
|
|
//if tile contains salt, wraith becomes corporeal
|
|
if (salted && !src.density && !src.justdied)
|
|
src.makeCorporeal()
|
|
boutput(src, "<span style=\"color:red\">You have passed over salt! You now interact with the mortal realm...</span>")
|
|
spawn(600) //one minute
|
|
src.makeIncorporeal()
|
|
|
|
return
|
|
|
|
//Z level boundary stuff
|
|
if((direct & NORTH) && src.y < world.maxy)
|
|
src.y++
|
|
if((direct & SOUTH) && src.y > 1)
|
|
src.y--
|
|
if((direct & EAST) && src.x < world.maxx)
|
|
src.x++
|
|
if((direct & WEST) && src.x > 1)
|
|
src.x--
|
|
|
|
|
|
can_use_hands()
|
|
if (src.density) return 1
|
|
else return 0
|
|
|
|
|
|
is_active()
|
|
if (src.density) return 1
|
|
else return 0
|
|
|
|
put_in_hand(obj/item/I, hand)
|
|
return 0
|
|
|
|
south_east()
|
|
Move(get_step(src, SOUTHEAST))
|
|
|
|
swap_hand()
|
|
Move(get_step(src, NORTHEAST))
|
|
|
|
drop_item_v()
|
|
Move(get_step(src, NORTHWEST))
|
|
|
|
equipped()
|
|
return 0
|
|
|
|
click(atom/target)
|
|
. = ..()
|
|
if (. == 100)
|
|
return 100
|
|
if (!density)
|
|
target.examine()
|
|
|
|
say(var/message)
|
|
message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
|
|
if (!message)
|
|
return
|
|
|
|
if (src.density) //If corporeal speak to the living (garbled)
|
|
logTheThing("diary", src, null, "(WRAITH): [message]", "say")
|
|
|
|
if (src.client && src.client.ismuted())
|
|
boutput(src, "You are currently muted and may not speak.")
|
|
return
|
|
|
|
if (copytext(message, 1, 2) == "*")
|
|
src.emote(copytext(message, 2))
|
|
return
|
|
else
|
|
src.emote(pick("hiss", "murmur", "drone", "wheeze", "grustle", "rattle"))
|
|
|
|
//Todo: random pick of spooky things or maybe parse the original message somehow
|
|
/*var/rendered = "<strong>[src.name]</strong> screeches incomprehensibly!"
|
|
|
|
var/list/listening = all_hearers(null, src)
|
|
listening -= src
|
|
listening += src
|
|
|
|
for (var/mob/M in listening)
|
|
M.show_message(rendered, 2)*/
|
|
|
|
else //Speak in ghostchat if not corporeal
|
|
if (copytext(message, 1, 2) == "*")
|
|
return
|
|
|
|
logTheThing("diary", src, null, "(WRAITH): [message]", "say")
|
|
|
|
if (src.client && src.client.ismuted())
|
|
boutput(src, "You are currently muted and may not speak.")
|
|
return
|
|
|
|
. = src.say_dead(message, 1)
|
|
|
|
emote(var/act)
|
|
if (!density)
|
|
return
|
|
var/acts = null
|
|
switch (act)
|
|
if ("hiss")
|
|
acts = "hisses"
|
|
if ("murmur")
|
|
acts = "murmurs"
|
|
if ("drone")
|
|
acts = "drones"
|
|
if ("wheeze")
|
|
acts = "wheezes"
|
|
if ("grustle")
|
|
acts = "grustles"
|
|
if ("rattle")
|
|
acts = "rattles"
|
|
|
|
if (acts)
|
|
for (var/mob/M in hearers(src, null))
|
|
M.show_message("<span style=\"color:red\">[src] [acts]!</span>")
|
|
|
|
attack_hand(var/mob/user)
|
|
if (user.a_intent != "harm")
|
|
visible_message("[user] pets [src]!")
|
|
else
|
|
visible_message("[user] punches [src]!")
|
|
TakeDamage("chest", 1, 0)
|
|
|
|
|
|
|
|
//////////////
|
|
// Wraith Procs
|
|
//////////////
|
|
proc
|
|
|
|
makeCorporeal()
|
|
if (!src.density)
|
|
src.density = 1
|
|
src.invisibility = 0
|
|
src.alpha = 255
|
|
src.see_invisible = 0
|
|
src.visible_message(pick("<span style=\"color:red\">A horrible apparition fades into view!</span>", "<span style=\"color:red\">A pool of shadow forms!</span>"), pick("<span style=\"color:red\">A shell of ectoplasm forms around you!</span>", "<span style=\"color:red\">You manifest!</span>"))
|
|
|
|
makeIncorporeal()
|
|
if (src.density)
|
|
src.visible_message(pick("<span style=\"color:red\">[src] vanishes!</span>", "<span style=\"color:red\">The wraith dissolves into shadow!</span>"), pick("<span style=\"color:blue\">The ectoplasm around you dissipates!</span>", "<span style=\"color:blue\">You fade into the aether!</span>"))
|
|
src.density = 0
|
|
src.invisibility = 10
|
|
src.alpha = 160
|
|
src.see_invisible = 16
|
|
|
|
haunt()
|
|
if (src.density)
|
|
src.show_message("<span style=\"color:red\">You are already corporeal! You cannot use this ability.</span>")
|
|
return 1
|
|
|
|
src.makeCorporeal()
|
|
src.haunting = 1
|
|
|
|
spawn (300)
|
|
src.makeIncorporeal()
|
|
src.haunting = 0
|
|
|
|
return 0
|
|
|
|
|
|
addAllAbilities()
|
|
src.addAbility(/datum/targetable/wraithAbility/help)
|
|
src.addAbility(/datum/targetable/wraithAbility/absorbCorpse)
|
|
src.addAbility(/datum/targetable/wraithAbility/possessObject)
|
|
src.addAbility(/datum/targetable/wraithAbility/makeRevenant)
|
|
src.addAbility(/datum/targetable/wraithAbility/decay)
|
|
src.addAbility(/datum/targetable/wraithAbility/command)
|
|
src.addAbility(/datum/targetable/wraithAbility/raiseSkeleton)
|
|
src.addAbility(/datum/targetable/wraithAbility/animateObject)
|
|
src.addAbility(/datum/targetable/wraithAbility/haunt)
|
|
src.addAbility(/datum/targetable/wraithAbility/poltergeist)
|
|
src.addAbility(/datum/targetable/wraithAbility/whisper)
|
|
|
|
|
|
removeAllAbilities()
|
|
src.removeAbility(/datum/targetable/wraithAbility/help)
|
|
src.removeAbility(/datum/targetable/wraithAbility/absorbCorpse)
|
|
src.removeAbility(/datum/targetable/wraithAbility/possessObject)
|
|
src.removeAbility(/datum/targetable/wraithAbility/makeRevenant)
|
|
src.removeAbility(/datum/targetable/wraithAbility/decay)
|
|
src.removeAbility(/datum/targetable/wraithAbility/command)
|
|
src.removeAbility(/datum/targetable/wraithAbility/raiseSkeleton)
|
|
src.removeAbility(/datum/targetable/wraithAbility/animateObject)
|
|
src.removeAbility(/datum/targetable/wraithAbility/haunt)
|
|
src.removeAbility(/datum/targetable/wraithAbility/poltergeist)
|
|
src.removeAbility(/datum/targetable/wraithAbility/whisper)
|
|
|
|
addAbility(var/abilityType)
|
|
abilityHolder.addAbility(abilityType)
|
|
|
|
|
|
removeAbility(var/abilityType)
|
|
abilityHolder.removeAbility(abilityType)
|
|
|
|
|
|
getAbility(var/abilityType)
|
|
return abilityHolder.getAbility(abilityType)
|
|
|
|
|
|
updateButtons()
|
|
abilityHolder.updateButtons()
|
|
|
|
makeRevenant(var/mob/M as mob)
|
|
if (!ishuman(M))
|
|
boutput(usr, "<span style=\"color:red\">You can only extend your consciousness into humans corpses.</span>")
|
|
return 1
|
|
var/mob/living/carbon/human/H = M
|
|
if (H.stat != 2)
|
|
boutput(usr, "<span style=\"color:red\">A living consciousness possesses this body. You cannot force your way in.</span>")
|
|
return 1
|
|
if (H.decomp_stage == 4)
|
|
boutput(usr, "<span style=\"color:red\">This corpse is no good for this!</span>")
|
|
return 1
|
|
if (H.is_changeling())
|
|
boutput(usr, "<span style=\"color:red\">What is this? An exquisite genetic structure. It forcibly resists your will, even in death.</span>")
|
|
return 1
|
|
if (!H.bioHolder)
|
|
message_admins("[key_name(src)] tried to possess [M] as a revenant but failed due to a missing bioholder.")
|
|
boutput(usr, "<span style=\"color:red\">Failed.</span>")
|
|
return 1
|
|
// Happens in wraithPossess() already.
|
|
//src.abilityHolder.suspendAllAbilities()
|
|
var/datum/bioEffect/hidden/revenant/R = H.bioHolder.AddEffect("revenant")
|
|
if (H.bioHolder.HasEffect("revenant")) // make sure we didn't get deleted on the way - should probably make a better check than this. whatever.
|
|
R.wraithPossess(src)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
//////////////
|
|
// Wraith Verbs
|
|
//////////////
|
|
|
|
/*verb
|
|
makeCorporealDebug()
|
|
src.makeCorporeal()
|
|
|
|
|
|
makeIncorporealDebug()
|
|
src.makeIncorporeal()
|
|
|
|
|
|
givePointsDebug()
|
|
src.abilityHolder.points = 99999*/
|
|
|
|
|
|
//////////////
|
|
// Related procs and verbs
|
|
//////////////
|
|
|
|
// i am dumb - marq
|
|
/mob/proc/wraithize()
|
|
if (src.mind || src.client)
|
|
message_admins("[key_name(usr)] made [key_name(src)] a wraith.")
|
|
logTheThing("admin", usr, src, "made %target% a wraith.")
|
|
return make_wraith()
|
|
return null
|
|
|
|
/mob/proc/make_wraith()
|
|
if (src.mind || src.client)
|
|
var/mob/wraith/W = new/mob/wraith(src)
|
|
|
|
var/turf/T = get_turf(src)
|
|
if (!(T && isturf(T)) || ((isrestrictedz(T.z) || T.z != 1) && !(src.client && src.client.holder)))
|
|
var/OS = observer_start.len ? pick(observer_start) : locate(1, 1, 1)
|
|
if (OS)
|
|
W.set_loc(OS)
|
|
else
|
|
W.z = 1
|
|
else
|
|
W.set_loc(T)
|
|
|
|
if (src.mind)
|
|
src.mind.transfer_to(W)
|
|
else
|
|
var/key = src.client.key
|
|
if (src.client)
|
|
src.client.mob = W
|
|
W.mind = new /datum/mind()
|
|
W.mind.key = key
|
|
W.mind.current = W
|
|
ticker.minds += W.mind
|
|
src.loc = null
|
|
|
|
var/this = src
|
|
src = null
|
|
qdel(this)
|
|
|
|
W.addAllAbilities()
|
|
boutput(W, "<B>You are a wraith! Terrorize the mortals and drive them into releasing their life essence!</B>")
|
|
boutput(W, "Your astral powers enable you to survive one banishment. Beware of salt.")
|
|
boutput(W, "Use the question mark button in the lower right corner to get help on your abilities.")
|
|
|
|
return W
|
|
return null
|
|
|
|
/proc/visibleBodies(var/mob/M)
|
|
var/list/ret = new
|
|
for (var/mob/living/carbon/human/H in view(M))
|
|
if (istype(H) && H.stat == 2 && H.decomp_stage < 4)
|
|
ret += H
|
|
return ret
|
|
|
|
/proc/generate_wraith_objectives(var/datum/mind/traitor)
|
|
switch (rand(1,3))
|
|
if (1)
|
|
var/datum/objective/specialist/wraith/murder/M1 = new
|
|
M1.owner = traitor
|
|
M1.set_up()
|
|
traitor.objectives += M1
|
|
var/datum/objective/specialist/wraith/murder/M2 = new
|
|
M2.owner = traitor
|
|
M2.set_up()
|
|
traitor.objectives += M2
|
|
var/datum/objective/specialist/wraith/murder/M3 = new
|
|
M3.owner = traitor
|
|
M3.set_up()
|
|
traitor.objectives += M3
|
|
if (2)
|
|
var/datum/objective/specialist/wraith/absorb/A1 = new
|
|
A1.owner = traitor
|
|
A1.set_up()
|
|
traitor.objectives += A1
|
|
var/datum/objective/specialist/wraith/prevent/P2 = new
|
|
P2.owner = traitor
|
|
P2.set_up()
|
|
traitor.objectives += P2
|
|
if (3)
|
|
var/datum/objective/specialist/wraith/absorb/A1 = new
|
|
A1.owner = traitor
|
|
A1.set_up()
|
|
traitor.objectives += A1
|
|
var/datum/objective/specialist/wraith/murder/absorb/M2 = new
|
|
M2.owner = traitor
|
|
M2.set_up()
|
|
traitor.objectives += M2
|
|
switch (rand(1,3))
|
|
if (1)
|
|
var/datum/objective/specialist/wraith/travel/T = new
|
|
T.owner = traitor
|
|
T.set_up()
|
|
traitor.objectives += T
|
|
if (2)
|
|
var/datum/objective/specialist/wraith/survive/T = new
|
|
T.owner = traitor
|
|
T.set_up()
|
|
traitor.objectives += T
|
|
if (3)
|
|
var/datum/objective/specialist/wraith/flawless/T = new
|
|
T.owner = traitor
|
|
T.set_up()
|
|
traitor.objectives += T |