Files
vgstation13/code/modules/mob/living/carbon/alien/special/facehugger.dm
2020-09-21 22:32:57 -04:00

639 lines
20 KiB
Plaintext

//TODO: Make these simple_animals
#define MIN_IMPREGNATION_TIME 10 SECONDS //time it takes to impregnate someone
#define MAX_IMPREGNATION_TIME 15 SECONDS
#define MIN_ACTIVE_TIME 20 SECONDS //time between being dropped and going idle
#define MAX_ACTIVE_TIME 40 SECONDS
#define CHANCE_TO_REMOVE_HEADWEAR 50
#define CHANCE_TO_REMOVE_SPECIAL_HEADWEAR 15
#define CHANCE_TO_DIE_AFTER_HEAD_DENIED 75
#define CHANCE_TO_NOT_REMOVE_MASKS 20
#define TIME_IDLE_AFTER_HEAD_DENIED 5 SECONDS
#define TIME_IDLE_AFTER_ATTACH_DENIED 15 SECONDS
/obj/item/clothing/mask/facehugger
name = "facehugger" //Let's call this 'alien' what it is. Come on Bay
desc = "It has some sort of a tube at the end of its tail."
icon = 'icons/mob/alien.dmi'
icon_state = "facehugger"
item_state = "facehugger"
w_class = W_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4
flags = FPRINT | PROXMOVE
clothing_flags = MASKINTERNALS
throw_range = 5
health = 5
plane = ABOVE_OBJ_PLANE
layer = FACEHUGGER_LAYER
var/real = TRUE //Facehuggers are real, toys are not.
var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case
var/sterile = FALSE
var/strength = 5
var/attached = FALSE
var/target_time = 0.5 // seconds
var/walk_speed = 1
var/nextwalk = FALSE
var/mob/living/target = null
/obj/item/clothing/mask/facehugger/can_contaminate()
return FALSE
/obj/item/clothing/mask/facehugger/Destroy()
processing_objects.Remove(src)
target = null
..()
/obj/item/clothing/mask/facehugger/process()
healthcheck()
followtarget()
spreadout()
/obj/item/clothing/mask/facehugger/proc/findtarget()
if(!real)
return
target = null
for(var/mob/living/T in hearers(src,4))
if(!CanHug(T, src))
continue
if(T && (!T.isUnconscious() ) )
target = T
/obj/item/clothing/mask/facehugger/proc/followtarget()
if(!real)
return // Why are you trying to path stupid toy
if(!target || target.isUnconscious() || target.status_flags & XENO_HOST)
findtarget()
return
if(loc && isturf(loc) && !attached && !stat && nextwalk <= world.time)
nextwalk = world.time + walk_speed
var/dist = get_dist(loc, target.loc)
if(dist > 4)
target = null
return //We'll let the facehugger do nothing for a bit, since it's fucking up.
var/obj/item/clothing/mask/facehugger/F = target.is_wearing_item(/obj/item/clothing/mask/facehugger, slot_wear_mask)
if(F && !F.sterile) // Toys won't prevent real huggers
findtarget()
return
else
step_towards(src, target, 0)
if(dist <= 1)
if(CanHug(target, src) && isturf(target.loc)) //Fix for hugging through mechs and closets
Attach(target)
return
else
walk(src,0)
findtarget()
return
/obj/item/clothing/mask/facehugger/proc/spreadout()
if(!target && isturf(loc) && stat == CONSCIOUS)
for(var/obj/item/clothing/mask/facehugger/F in loc)
if(F != src && F.stat != DEAD)
step(src, pick(alldirs), 0)
break
//END HUGGER MOVEMENT AI
/obj/item/clothing/mask/facehugger/attack_paw(user as mob) //can be picked up by aliens
if(isalien(user))
attack_hand(user)
return
else
..()
return
/obj/item/clothing/mask/facehugger/attack_hand(user as mob)
if((stat == CONSCIOUS && !sterile) && !isalien(user))
Attach(user)
return
// Colonial Marines code related to alien carriers
// else
// var/mob/living/carbon/alien/humanoid/carrier/carr = user
//
// if(carr && istype(carr, /mob/living/carbon/alien/humanoid/carrier))
// if(carr.facehuggers >= 6)
// carr << "You can't hold anymore facehuggers. You pick it up"
// ..()
// return
// if(stat != DEAD)
// carr << "You pick up a facehugger"
// carr.facehuggers += 1
// qdel(src)
//
// else
// user << "This facehugger is dead."
// ..()
else
..()
return
/obj/item/clothing/mask/facehugger/proc/healthcheck()
if(health <= 0)
icon_state = "[initial(icon_state)]_dead"
death()
/obj/item/clothing/mask/facehugger/attack(mob/living/M as mob, mob/user as mob)
..()
user.drop_from_inventory(src)
Attach(M)
/obj/item/clothing/mask/facehugger/New()
if(aliens_allowed)
..()
if(real) // Lamarr still tries to couple with heads, but toys won't
processing_objects.Add(src)
else if(!sterile)
qdel(src)
/obj/item/clothing/mask/facehugger/examine(mob/user)
..()
if(!real) //Toy facehuggers are a child, avoid confusing examine text.
return
switch(stat)
if(DEAD,UNCONSCIOUS)
to_chat(user, "<span class='deadsay'>\The [src] is not moving.</span>")
if(CONSCIOUS)
to_chat(user, "<span class='danger'>\The [src] seems active.</span>")
if (sterile)
to_chat(user, "<span class='danger'>It looks like \the [src]'s proboscis has been removed.</span>")
return
/obj/item/clothing/mask/facehugger/attackby(obj/item/weapon/W)
if(W.force)
health -= W.force
healthcheck()
/obj/item/clothing/mask/facehugger/bullet_act(var/obj/item/projectile/Proj)
if(Proj.damage)
health -= Proj.damage
healthcheck()
/obj/item/clothing/mask/facehugger/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(exposed_temperature > 300)
death()
return
/obj/item/clothing/mask/facehugger/equipped(mob/M)
Attach(M)
/obj/item/clothing/mask/facehugger/Crossed(atom/target) // Instead of HasEntered. Probably the right proc, probably.
HasProximity(target)
return
/obj/item/clothing/mask/facehugger/on_found(wearer, mob/finder as mob)
if(stat == CONSCIOUS)
return HasProximity(finder)
return FALSE
/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj)
if(ishuman(AM))
if(CanHug(AM, src))
var/mob/living/carbon/human/H = AM
if(!H.isUnconscious())
return Attach(H)
return FALSE
/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed)
..()
if(stat == CONSCIOUS)
icon_state = "[initial(icon_state)]_thrown"
spawn(15)
if(icon_state == "[initial(icon_state)]_thrown")
icon_state = "[initial(icon_state)]"
/obj/item/clothing/mask/facehugger/throw_impact(atom/hit_atom) //STOP LATCHING ONTO HEADLESS PEOPLE
..()
if(stat == CONSCIOUS)
icon_state = "[initial(icon_state)]"
if(ishuman(hit_atom))
var/mob/living/carbon/human/H = hit_atom
if(!H.isUnconscious())
Attach(H)
/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/L)
var/preggers = rand(MIN_IMPREGNATION_TIME,MAX_IMPREGNATION_TIME)
if(isalien(L))
return FALSE
if(L.status_flags & XENO_HOST)
visible_message("<span class='danger'>An alien tries to place a facehugger on [L] but it refuses sloppy seconds!</span>")
return FALSE
if(attached)
return FALSE
if(!Adjacent(L))
return FALSE
else
attached++
spawn(MAX_IMPREGNATION_TIME)
attached = FALSE
if(loc == L)
return FALSE
if(stat != CONSCIOUS)
return FALSE
if(!CanHug(L, src))
return FALSE
if(!sterile)
L.take_organ_damage(strength, 0) //done here so that even borgs and humans in helmets take damage
L.visible_message("<span class='danger'>\The [src] leaps at [L]'s face!</span>")
if(ishuman(L))
var/mob/living/carbon/human/H = L
var/obj/item/mouth_protection = H.get_body_part_coverage(MOUTH)
if(!real && mouth_protection)
return //Toys really shouldn't be forcefully removing gear
if(mouth_protection && mouth_protection != H.wear_mask) //can't be protected with your own mask, has to be a hat
var/rng = CHANCE_TO_REMOVE_HEADWEAR
if(istype(mouth_protection, /obj/item/clothing/head/helmet/space/rig))
rng = CHANCE_TO_REMOVE_SPECIAL_HEADWEAR
if(prob(rng)) // Temporary balance change, all mouth-covering hats will be more effective
H.visible_message("<span class='danger'>\The [src] smashes against [H]'s \the [mouth_protection], and rips it off in the process!</span>")
H.drop_from_inventory(mouth_protection)
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
return
else
H.visible_message("<span class='danger'>\The [src] bounces off of \the [mouth_protection]!</span>")
if(prob(CHANCE_TO_DIE_AFTER_HEAD_DENIED) && !sterile)
death()
return
else
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
return
if(iscarbon(L))
var/mob/living/carbon/target = L
var/obj/item/clothing/W = target.get_item_by_slot(slot_wear_mask)
var/obj/item/weapon/tank/had_internal = target.internal
if(W && W != src)
if(prob(CHANCE_TO_NOT_REMOVE_MASKS))
return FALSE
if(!W.canremove)
return FALSE
target.drop_from_inventory(W)
target.visible_message("<span class='danger'>\The [src] tears \the [W] off of [target]'s face!</span>")
forceMove(target)
target.equip_to_slot(src, slot_wear_mask)
target.update_inv_wear_mask()
target.internal = had_internal //Try to keep the host ALIVE
target.update_internals()
if(!sterile && target.hasmouth)
L.Paralyse((preggers/10)+10) //something like 25 ticks = 20 seconds with the default settings
else if (iscorgi(L))
var/mob/living/simple_animal/corgi/C = L
var/obj/item/clothing/head/headwear = C.inventory_head
var/rng = 100
var/obj/item/clothing/mask/facehugger/hugger = C.facehugger
if(hugger && !hugger.sterile)
return
if(headwear)
if(istype(headwear, /obj/item/clothing/head/cardborg))
rng = CHANCE_TO_REMOVE_HEADWEAR
else if(istype(headwear, /obj/item/clothing/head/helmet/space/rig))
rng = CHANCE_TO_REMOVE_SPECIAL_HEADWEAR
if(prob(rng))
C.visible_message("<span class='danger'>\The [src] smashes against [C]'s \the [headwear], and rips it off in the process!</span>")
C.drop_from_inventory(headwear)
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
else
C.visible_message("<span class='danger'>\The [src] bounces off of \the [headwear]!</span>")
if(prob(CHANCE_TO_DIE_AFTER_HEAD_DENIED) && !sterile)
death()
else
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
return
forceMove(C)
C.facehugger = src
C.wear_mask = src
C.regenerate_icons()
GoIdle(TIME_IDLE_AFTER_ATTACH_DENIED) //so it doesn't jump the people that tear it off
spawn(preggers)
Impregnate(L)
return FALSE
/obj/item/clothing/mask/facehugger/proc/Impregnate(mob/living/target as mob)
if(!target || target.wear_mask != src || target.stat == DEAD) //was taken off or something
return
var/mob/living/carbon/CA = target
if(!sterile && !(istype(CA) && !CA.hasmouth()))
var/obj/item/alien_embryo/E = new (target)
target.status_flags |= XENO_HOST
if(istype(target, /mob/living/carbon/human))
var/mob/living/carbon/human/T = target
var/datum/organ/external/chest/affected = T.get_organ(LIMB_CHEST)
affected.implants += E
target.visible_message("<span class='danger'>\The [src] falls limp after violating [target]'s face !</span>")
death()
//target.drop_from_inventory(src)
icon_state = "[initial(icon_state)]_impregnated"
if(iscorgi(target))
var/mob/living/simple_animal/corgi/C = target
forceMove(get_turf(C))
C.facehugger = null
C.regenerate_icons()
else
target.visible_message("<span class='danger'>\The [src] violates [target]'s face !</span>")
return
/obj/item/clothing/mask/facehugger/proc/GoActive()
if(stat == DEAD || stat == CONSCIOUS)
return
stat = CONSCIOUS
icon_state = "[initial(icon_state)]"
return
/obj/item/clothing/mask/facehugger/proc/GoIdle(var/delay)
if(stat == DEAD || stat == UNCONSCIOUS)
return
/* RemoveActiveIndicators() */
target = null
stat = UNCONSCIOUS
icon_state = "[initial(icon_state)]_inactive"
if(!delay)
delay = rand(MIN_ACTIVE_TIME,MAX_ACTIVE_TIME)
spawn(delay)
GoActive()
return
/obj/item/clothing/mask/facehugger/proc/death()
if(stat == DEAD || !real)
return
target = null
/* RemoveActiveIndicators() */
processing_objects.Remove(src)
icon_state = "[initial(icon_state)]_dead"
stat = DEAD
sterile = TRUE //Dead huggers can't make people pregnant, duh. This also makes them acidable to avoid acid cheesers AND prevents people from using dead huggers to avoid getting hugged.
visible_message("<span class='danger'>\The [src] curls up into a ball!</span>")
/proc/CanHug(mob/living/M, obj/item/clothing/mask/facehugger/hugger = null)
if(isalien(M))
return FALSE
if((M.status_flags & XENO_HOST) && !istype(hugger, /obj/item/clothing/mask/facehugger/headcrab))
return FALSE
if(iscorgi(M) && !istype(hugger, /obj/item/clothing/mask/facehugger/headcrab))
var/mob/living/simple_animal/corgi/corgi = M
if(corgi.facehugger && !corgi.facehugger.sterile)
return FALSE
return TRUE
if(!ishuman(M) && !ismonkey(M))
return FALSE
var/mob/living/carbon/C = M
var/obj/item/clothing/mask/facehugger/F = C.is_wearing_item(/obj/item/clothing/mask/facehugger, slot_wear_mask)
var/obj/item/clothing/mask/facehugger/F2 = C.is_wearing_item(/obj/item/clothing/mask/facehugger, slot_head)
if(F && (!F.sterile || hugger.sterile) && F != hugger) // Lamarr won't fight over faces and neither will normal huggers.
return FALSE
if(F2 && (!F2.sterile || hugger.sterile) && F2 != hugger)
return FALSE
return TRUE
/obj/item/clothing/mask/facehugger/acidable()
return sterile
////////////////////////////////////////////////////
//////////////// HEADCRABS ///////////////////////
////////////////////////////////////////////////////
/obj/item/clothing/mask/facehugger/headcrab
name = "headcrab" //womp womp
desc = "Get that thing away from me!" //TODO: think of a better quote
icon = 'icons/mob/alien.dmi'
icon_state = "headcrab"
item_state = "headcrab"
body_parts_covered = HEAD
slot_flags = SLOT_HEAD
clothing_flags = null
canremove = 0 //You need to resist out of it.
cant_remove_msg = "The headcrab is latched on tight!"
var/is_being_resisted = 0
var/escaping = 0 //If enabled the crab will try to escape.
/obj/item/clothing/mask/facehugger/headcrab/equipped(mob/living/carbon/human/H)
if(stat == CONSCIOUS)
Assimilate(H)
else if(stat == UNCONSCIOUS)
GoActive()
Assimilate(H)
/obj/item/clothing/mask/facehugger/headcrab/attack_hand(mob/user)
if(ishuman(user))
var/mob/living/carbon/human/target = user
if(target && target.head == src)
target.resist()
else
..()
/obj/item/clothing/mask/facehugger/headcrab/findtarget()
if(!real)
return
target = null
for(var/mob/living/carbon/human/T in hearers(src,6))
if(!CanHug(T, src))
continue
if(!T.isUnconscious())
target = T
/obj/item/clothing/mask/facehugger/headcrab/attackby(obj/item/weapon/W, mob/user)
if(ishuman(user))
var/mob/living/carbon/human/U = user
var/obj/item/clothing/mask/facehugger/headcrab/H = U.is_wearing_item(/obj/item/clothing/mask/facehugger/headcrab, slot_head)
if(H == src)
U.resist()
return
if(W.force)
health -= W.force
healthcheck()
/obj/item/clothing/mask/facehugger/headcrab/followtarget()
if(!real)
return // Why are you trying to path stupid toy
if(!target)
findtarget()
return
if(isturf(loc) && !attached && !stat && nextwalk <= world.time)
nextwalk = world.time + walk_speed
var/dist = get_dist(loc, target.loc)
if(dist > 6)
target = null
return //We'll let the facehugger do nothing for a bit, since it's fucking up.
var/obj/item/clothing/mask/facehugger/headcrab/F = target.is_wearing_item(/obj/item/clothing/mask/facehugger/headcrab, slot_head)
if(F && !F.sterile) // Toys won't prevent real huggers
findtarget()
return
else
playsound(src, pick('sound/items/headcrab_attack1.ogg', 'sound/items/headcrab_attack2.ogg', 'sound/items/headcrab_attack3.ogg'), 50, 0)
if(escaping) //If escaping, jump away from the nearest guy.
if(src.loc == target.loc) //If the target is on the same tile just jump a random direction away
var/turf/escape_tile = locate(src.x + rand(3,6)*(-1**rand(1,2)), src.y + rand(3,6)*(-1**rand(1,2)), src.z) //This is messy. For x and y coordinates, pick a random number between 3 and 6, then randomly make that number positive or negative.
throw_at(escape_tile, 4, 1)
else
var/turf/escape_tile = locate(src.x-(target.x-src.x)*2, src.y-(target.y-src.y)*2, target.z)
throw_at(escape_tile, 4, 1)
escaping = 0
sleep(50)
GoActive()
else
throw_at(target, 3, 1)
if(dist == 0)
if(CanHug(target, src) && isturf(target.loc)) //Fix for hugging through mechs and closets
Attach(target)
return
/obj/item/clothing/mask/facehugger/headcrab/examine(mob/user)
if(!real) //Toy facehuggers are a child, avoid confusing examine text.
return
switch(stat)
if(DEAD,UNCONSCIOUS)
to_chat(user, "<span class='deadsay'>\The [src] is not moving.</span>")
if(CONSCIOUS)
to_chat(user, "<span class='danger'>\The [src] seems active.</span>")
if (sterile)
to_chat(user, "<span class='danger'>It looks like \the [src]'s has been de-beaked.</span>")
return
/obj/item/clothing/mask/facehugger/headcrab/Attach(mob/living/L)
if(escaping)
return FALSE
if(isalien(L))
return FALSE
if(attached)
return FALSE
if(!Adjacent(L))
return FALSE
if(loc == L)
return FALSE
if(stat != CONSCIOUS)
return FALSE
if(!CanHug(L, src))
return FALSE
if(!sterile)
L.take_organ_damage(strength, 0) //done here so that even borgs and humans in helmets take damage
L.visible_message("<span class='danger'>\The [src] leaps at [L]'s face!</span>")
if(ishuman(L))
var/mob/living/carbon/human/H = L
var/obj/item/head_protection = H.get_body_part_coverage(HEAD)
if(!real && head_protection)
return FALSE//Toys really shouldn't be forcefully removing gear
if(head_protection)
var/rng = 80 //80% chance to remove the hat if it isn't a rig
if(istype(head_protection, /obj/item/clothing/head/helmet/space/rig))
rng = CHANCE_TO_REMOVE_SPECIAL_HEADWEAR
if(prob(rng)) // Temporary balance change, all mouth-covering hats will be more effective
H.visible_message("<span class='danger'>\The [src] smashes against [H]'s \the [head_protection], and rips it off in the process!</span>")
H.drop_from_inventory(head_protection)
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
return FALSE
else
H.visible_message("<span class='danger'>\The [src] bounces off of \the [head_protection]!</span>")
if(prob(CHANCE_TO_DIE_AFTER_HEAD_DENIED) && !sterile)
death()
return FALSE
else
GoIdle(TIME_IDLE_AFTER_HEAD_DENIED)
return FALSE
var/mob/living/carbon/target = L
var/obj/item/clothing/W = target.get_item_by_slot(slot_head)
if(W && W != src)
if(!W.canremove)
return FALSE
target.drop_from_inventory(W)
target.visible_message("<span class='danger'>\The [src] tears \the [W] off of [target]'s head!</span>")
forceMove(target)
target.audible_scream()
target.equip_to_slot(src, slot_head)
target.update_inv_head()
target.movement_speed_modifier -= 0.75 //Slow them down like a taser
spawn(30)
target.movement_speed_modifier += 0.75
target.update_inv_head() //Sometimes it doesnt work the first time
if(!sterile)
Assimilate(target)
return TRUE
GoIdle(TIME_IDLE_AFTER_ATTACH_DENIED)
return FALSE
/obj/item/clothing/mask/facehugger/headcrab/proc/Assimilate(mob/living/L)
if(!ishuman(L))
return
var/mob/living/carbon/human/target = L
if(target.head != src) //was taken off or something
return
while(target && target.head == src && !target.isDead() && !target.isInCrit()) //If they're still alive chew at their fuggin skull
playsound(src, 'sound/weapons/bite.ogg', 50, 1)
target.apply_damage(10, BRUTE, LIMB_HEAD)
target.update_inv_head()
sleep(30)
if(target && target.head == src && (target.isDead() || target.isInCrit())) //Once they die, start the zombification.
visible_message("<span class='danger'>[target.real_name] begins to shake and convulse violently!</span>")
to_chat(target, "<span class='sinister'>You feel your consciousness slipping away...</span>")
target.Jitter(500)
sleep(150)
target.remove_jitter()
if(target && target.head == src)
target.death(0)
visible_message("<span class='danger'>[target.real_name]'s flesh is violently torn apart!</span>")
hgibs(target.loc, target.virus2, target.dna)
var/mob/living/simple_animal/hostile/necro/zombie/headcrab/Z = target.make_zombie(retain_mind = 1, crabzombie = 1)
Z.crab = src