Files
vgstation13/code/modules/mob/living/simple_animal/hostile/grue.dm
ShiftyRail d79c1fe070 Byond 516 v2.0 (#37553)
* The TGS thing

* Revert the 516 revert

* Further segment the world/New() proc

* Fixes an issue here
2025-05-12 00:50:25 -05:00

832 lines
34 KiB
Plaintext

/mob/living/simple_animal/hostile/grue
//Instead of initializing them here, many values that vary with life stage are set via the lifestage_updates proc, which is called during New() and also after moulting.
icon = 'icons/mob/grue.dmi'
speed=1
var/base_speed=1
can_butcher = TRUE
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/animal/grue
name = "grue"
real_name = "grue"
minbodytemp = 150 //resistant to cold
a_intent=I_HURT //Initialize these
m_intent=I_HURT
universal_speak = 0
universal_understand = 0
response_help = "touches"
response_disarm = "pushes"
response_harm = "punches"
faction = "grue" //Keep grues and grue eggs friendly to each other.
blood_color2=GRUE_BLOOD
//flesh_color2="#272728"
see_in_dark = 8
//VARS
var/nutrienergy = 0 //nutritive energy absorbed
var/maxnutrienergy = 200 //max nutrienergy
var/moultcost = 0 //nutritive energy needed to moult into next stage (irrelevant for adults)
var/ismoulting = FALSE //currently moulting (TRUE=is a chrysalis)
var/moulttime = 15 //time required to moult to a new form (life ticks)
var/moulttimer = 15 //moulting timer
var/hatched = FALSE //whether or not this grue hatched from an egg
var/channeling_flags = 0 // channeling the drain light ability or not
var/base_melee_dam_up = 5 //base melee damage upper
var/base_melee_dam_lw = 3 //base melee damage lower
var/lifestage=GRUE_ADULT //1=baby grue, 2=grueling, 3=(mature) grue
var/eatencount=0 //number of sentient carbons eaten, makes the grue more powerful
var/eatencharge=0 //power charged by eating sentient carbons, increments with eatencount but is spent on upgrades
var/spawncount=0 //how many eggs laid by this grue have successfully hatched
var/number = 1 //Appends a number to the grue to keep it distinguishable, the compiler doesn't play nicely with putting rand(1, 1000) here so it goes in New()
var/busy=FALSE //busy attempting to lay an egg or eat
var/eattime= 3.5 SECONDS //how long it takes to eat someone
var/digest = 0 //how many life ticks of healing left after feeding
var/digest_heal = -7.5 //how much health restored per life tick after feeding (negative heals)
var/digest_sp = 10 //how much nutritive energy gained per life tick after feeding
var/datum/grue_calc/grue/lightparams = new /datum/grue_calc/grue //used for light-related calculations
var/loop_active = FALSE //Prevents the loop from accidentally starting several times over
//AI related:
stop_automated_movement = TRUE //has custom light-related wander movement
wander = FALSE
/datum/grue_calc //used for light-related calculations
var/bright_limit_gain = 1 //maximum brightness on tile for health and power regen
var/bright_limit_drain = 3 //maximum brightness on tile to not drain health and power
var/hg_mult = 3 //base multiplier for health gained per tick when on dark tile
var/hd_mult = 3 //base multiplier for health drained per tick on bright tile (subject to further modification by how long the grue is exposed via accum_light_expos_mult)
var/dark_dim_light = GRUE_DARK //darkness level currently the grue is currently exposed to, GRUE_DARK=nice and dark (heals the grue), GRUE_DIM=passably dim, GRUE_LIGHT=too bright (burns the grue)
var/current_brightness = 0 //light level of current tile, range from 0 to 10
var/accum_light_expos_mult= 1 //used to scale light damage the longer the grue is exposed to light
var/list/accum_light_expos_gain_dark_dim_light=list(-3,-1,1) //light damage rate increases the longer the grue is exposed to light, but this effect dissipates after going back into darkness
/datum/grue_calc/proc/ddl_update(var/mob/living/G)
if(isturf(G.loc))
var/turf/T = G.loc
current_brightness=10*T.get_lumcount()
else //else, there's considered to be no light (vents, lockers, etc.)
current_brightness=0
if(current_brightness<=bright_limit_gain)
dark_dim_light=GRUE_DARK
else if(current_brightness>bright_limit_drain)
dark_dim_light=GRUE_LIGHT
else
dark_dim_light=GRUE_DIM
////Procs for light-related burning and healing, and nutrienergy updates:
////Shared by both grues and grue eggs:
/datum/grue_calc/proc/alem_adjust() //update multiplier for exposure-scaling light damage
accum_light_expos_mult=max(1,accum_light_expos_mult+accum_light_expos_gain_dark_dim_light[dark_dim_light+1]) //modify light damage multiplier based on how long the grue's been in light recently
////Grue specific:
/datum/grue_calc/grue/proc/get_light_damage(var/mob/living/simple_animal/hostile/grue/G) //specific to grues and not grue eggs
return (current_brightness-bright_limit_drain) * accum_light_expos_mult * hd_mult * lightresist * (G.maxHealth/250) //scale light damage by: how bright the light is, amount of recent light exposure, the base multiplier, lifestage-dependent resistance, and normalize by max health,
/datum/grue_calc/grue/proc/get_dark_heal(var/mob/living/simple_animal/hostile/grue/G) //specific to grues and not grue eggs
return -1 * (bright_limit_gain-current_brightness) * hg_mult * regenbonus * !(G.channeling_flags & GRUE_DRAINLIGHT) * (G.maxHealth/250) //scale dark healing by: how dark it is, the base multiplier, the bonus based on sentient beings eaten, disable if draining light, and normalize by max health.
/datum/grue_calc/grue //specific to grues and not their eggs
var/pg_mult = 0 //multiplier for nutrienergy gained per tick when on dark tile (0=disabled)
var/pd_mult = 0 //multiplier for nutrienergy drained per tick on bright tile (0=disabled)
var/list/speed_m_dark_dim_light=list(1/1.2,1/1.1,1) //speed modifiers based on light condition
var/list/base_speed_m_dark_dim_light=list(1/1.2,1/1.1,1) //base speed modifiers based on light condition
var/regenbonus=1 //bonus to health regen based on sentient beings eaten
var/base_regenbonus=1.5
var/lightresist=1 //scales light damage depending on life stage to make grues slightly more resistant to light as they mature. multiplicative (lower is more resistant).
/datum/grue_calc/grue/proc/nutri_adjust(var/mob/living/simple_animal/hostile/grue/G)
switch(dark_dim_light)
if(GRUE_DARK)
if(!G.ismoulting && !(G.channeling_flags & GRUE_DRAINLIGHT)) //Don't gain power in the dark if the grue is moulting or channeling drain light.
G.nutrienergy = min(G.maxnutrienergy,G.nutrienergy+pg_mult*(bright_limit_gain-current_brightness)) //gain power in dark (disabled while pd_mult = 0)
if(GRUE_LIGHT)
G.nutrienergy = max(0,G.nutrienergy-pd_mult*(current_brightness-bright_limit_drain)) //drain power in light (disabled while pd_mult = 0)
/datum/grue_calc/grue/proc/speed_adjust(var/mob/living/simple_animal/hostile/grue/G)
G.speed=G.base_speed*speed_m_dark_dim_light[dark_dim_light+1]
////Egg specific:
/datum/grue_calc/egg
hd_mult = 5 //eggs are more sensitive to light than hatched grues
//eggs have slightly simpler light damage and healing calculations due to having less multipliers
/datum/grue_calc/egg/proc/get_light_damage(var/mob/living/E)
return (current_brightness-bright_limit_drain) * accum_light_expos_mult * hd_mult * (E.maxHealth/250) //scale light damage by: how bright the light is, amount of recent light exposure, the base multiplier, and normalize by max health,
/datum/grue_calc/egg/proc/get_dark_heal(var/mob/living/E)
return -1 * (bright_limit_gain-current_brightness) * hg_mult * (E.maxHealth/250) //scale dark healing by: how dark it is, the base multiplier, and normalize by max health.
/mob/living/simple_animal/hostile/grue/has_hand_check()
return TRUE //Skip checking for hands so that grues can pull things.
/mob/living/simple_animal/hostile/grue/modMeat(mob/user, var/obj/theMeat)
theMeat.name="grue meat"
/mob/living/simple_animal/hostile/grue/regular_hud_updates()
..()
if(client && hud_used)
if(!healths || !healths2)
hud_used.grue_hud()
//health indicator
if(health >= maxHealth)
healths.icon_state = "health0"
else if(health >= 4*maxHealth/5)
healths.icon_state = "health1"
else if(health >= 3*maxHealth/5)
healths.icon_state = "health2"
else if(health >= 2*maxHealth/5)
healths.icon_state = "health3"
else if(health >= 1*maxHealth/5)
healths.icon_state = "health4"
else if(health > 0)
healths.icon_state = "health5"
else
healths.icon_state = "health6"
//A more lightweight proc that will get called every 0.1 seconds for a more responsive light indicator
/mob/living/simple_animal/hostile/grue/proc/update_darkness_indicator(var/timer = 0)
if(client && hud_used) //Double-check since this proc can be called independently from regular_hud_updates()
if(lightparams.dark_dim_light==GRUE_DARK)
healths2.icon_state = "lightlevel_dark"
healths2.name = "nice and dark"
else if(lightparams.dark_dim_light==GRUE_DIM)
healths2.icon_state = "lightlevel_dim"
healths2.name = "adequately dim"
else if(lightparams.dark_dim_light==GRUE_LIGHT)
healths2.name = "painfully bright"
switch(timer)
if(14 to 1.#INF)
healths2.icon_state = "lightlevel_bright_warning"
if(7 to 13)
healths2.icon_state = "lightlevel_bright_danger"
if(-1.#INF to 6)
healths2.icon_state = "lightlevel_bright"
//Checks every 0.2 seconds to update the darkness level icon and count down a ticker
//Allows the grue to stay in contact with bright lights for 2 seconds before getting roasted, regardless of Life() tick
//Updates a screen indicator as well as the darkness indicator
//Starts incrementing the timer when the grue goes back into the darkness, resets if the grue got burnt
/mob/living/simple_animal/hostile/grue/proc/time_limit_in_light_loop()
var/time_limit = 2 SECONDS
var/penalty_timer = -1 //After 2 seconds, resets the speed and light damage multipliers
while((stat != DEAD) && !gcDestroyed) //Happens for as long as the grue isn't dead AND isn't just outright gone
if(!timestopped)
if(penalty_timer > 0)
if(lightparams.dark_dim_light != GRUE_LIGHT) //Only tick down the counter when the grue is not exposed to the light
penalty_timer = max(0, penalty_timer - 2)
else if(penalty_timer != -1) //if set at -1, it is disabled so that the procs don't run too many times
lightparams.speed_adjust(src)
lightparams.accum_light_expos_mult = 1
penalty_timer = -1
lightparams.ddl_update(src) //Checks the light condition
update_darkness_indicator(time_limit)
lightparams.nutri_adjust(src)
if(lightparams.dark_dim_light == GRUE_LIGHT)
if(time_limit <= 0) //Timer expired, do the damage and reset it
var/thisdmg=lightparams.get_light_damage(src)
apply_damage(thisdmg,BURN) //burn in light
if(thisdmg>(maxHealth/7))
to_chat(src, "<span class='danger'>The burning light sears your flesh!</span>")
else
to_chat(src, "<span class='warning'>The bright light scalds you!</span>")
playsound(src, 'sound/effects/grue_burn.ogg', 50, 1)
time_limit = 2 SECONDS
penalty_timer = 2 SECONDS
lightparams.speed_adjust(src) //Reduce their speed until they're back in darkness
lightparams.alem_adjust()
else //They are in the light, reduce the timer (by default has a value of 20)
time_limit = max(0, time_limit - 2)
else //Grue is in darkness, recover the timer twice as fast
time_limit = min(2 SECONDS, time_limit + 4)
sleep(2)
loop_active = FALSE
return
/mob/living/simple_animal/hostile/grue/Life()
..()
//process nutrienergy and health according to current tile brightness level
if(stat!=DEAD)
if(!loop_active) //start the loop
loop_active = TRUE
spawn()
time_limit_in_light_loop()
//apply eating-based healing before processing light-based damage or healing
if(digest)
apply_damage(digest_heal,BRUTE)
nutrienergy=min(maxnutrienergy,nutrienergy+digest_sp)
digest--
if(lightparams.dark_dim_light == GRUE_DARK)
if(!ismoulting) //moulting temporarily stops healing via darkness
apply_damage(lightparams.get_dark_heal(src),BURN) //heal in dark
if(ismoulting)
moulttimer--
if(moulttimer<=0)
complete_moult()
regular_hud_updates()
standard_damage_overlay_updates()
if((stat==CONSCIOUS) && !busy && !ismoulting && !client && !mind && !ckey) //Checks for AI
grue_ai()
//Grues already have a way to check their own health and the damage indicator doesn't mesh well with the vision.
/mob/living/simple_animal/hostile/grue/standard_damage_overlay_updates()
return
//AI stuff:
/mob/living/simple_animal/hostile/grue/proc/grue_ai()
//Moulting
if(lifestage!=GRUE_ADULT && (nutrienergy>=moultcost) && lightparams.dark_dim_light==GRUE_DARK)
start_moult()
//Eating
if(lifestage>=GRUE_JUVENILE && stance==HOSTILE_STANCE_IDLE && lightparams.dark_dim_light<GRUE_LIGHT)
var/list/feed_targets = list()
for(var/mob/living/carbon/C in range(1,get_turf(src)))
feed_targets += C
if(feed_targets.len)
handle_feed(pick(feed_targets))
//Egglaying
if(config.grue_egglaying && (lifestage==GRUE_ADULT) && (eatencharge>0) && (lightparams.dark_dim_light==GRUE_DARK))
reproduce()
//Movement
//
//Wandering behavior
if((!client||deny_client_move) && !busy && !ismoulting && !anchored && (ckey == null) && !(flags & INVULNERABLE))
if(canmove)
turns_since_move++
if(turns_since_move >= turns_per_move)
if(!(stop_automated_movement_when_pulled && pulledby))
var/list/potential_dests=list() //any potential wander destinations
var/list/respite_dests=list() //any potential wander destinations leading out of the light into dark/dim
for(var/direction in cardinal)
var/potential_dest = get_step(src, direction)
if(lightparams.dark_dim_light==GRUE_LIGHT)//always prioritize wandering to a dark/dim tile if in light
if(get_ddl(potential_dest)<GRUE_LIGHT)
respite_dests+=potential_dest
else
potential_dests+=potential_dest
else //never wander from a dark/dim tile into light
if(get_ddl(potential_dest)<GRUE_LIGHT)
potential_dests+=potential_dest
var/ourdest
if(respite_dests.len)
ourdest=pick(respite_dests)
else if(potential_dests.len)
ourdest=pick(potential_dests)
if(ourdest)
wander_move(ourdest)
turns_since_move = 0
/mob/living/simple_animal/hostile/grue/New()
..()
if(!loop_active) //start the loop
loop_active = TRUE
spawn()
time_limit_in_light_loop()
add_language(LANGUAGE_GRUE)
default_language = all_languages[LANGUAGE_GRUE]
number = rand(1, 1000)
init_language = default_language
lifestage_updates() //update the grue's sprite and stats according to the current lifestage
/mob/living/simple_animal/hostile/grue/Login()
..()
//client.images += light_source_images
/mob/living/simple_animal/hostile/grue/UnarmedAttack(atom/A)
if(isturf(A))
var/turf/T = A
for(var/atom/B in T)
if(istype(B, /obj/machinery/light))
var/obj/machinery/light/L = B
if(!L.current_bulb || L.current_bulb.status == LIGHT_BROKEN)
continue
UnarmedAttack(B)
..()
/mob/living/simple_animal/hostile/grue/unarmed_attack_mob(target)
if(isgrue(target))
playsound(src, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
visible_message("<span class='notice'>[src] nuzzles \the [target].</span>", "<span class='notice'>You nuzzle \the [target].</span>")
return
return ..()
/mob/living/simple_animal/hostile/grue/proc/get_ddl(var/turf/thisturf) //get the dark_dim_light status of a given turf
var/thisturf_brightness=10*thisturf.get_lumcount()
if(thisturf_brightness<=lightparams.bright_limit_gain)
return GRUE_DARK
else if(thisturf_brightness>lightparams.bright_limit_drain)
return GRUE_LIGHT
else
return GRUE_DIM
/mob/living/simple_animal/hostile/grue/proc/lifestage_updates() //Initialize or update lifestage-dependent stats
if(lifestage==GRUE_LARVA)
name = "grue larva"
desc = "A scurrying thing that lives in the dark. It is still a larva."
icon_state = "gruespawn_living"
icon_living = "gruespawn_living"
icon_dead = "gruespawn_dead"
base_melee_dam_up = 5 //base melee damage upper
base_melee_dam_lw = 3 //base melee damage lower
attacktext = "bites"
rescaleHealth(50)
nutrienergy=50 //starts out ready to moult
maxnutrienergy = 50
moultcost=50
lightparams.lightresist=1
environment_smash_flags = 0
attack_sound = 'sound/weapons/bite.ogg'
size = SIZE_SMALL
pass_flags = PASSTABLE
reagents.maximum_volume = 500
//Larval grue spells: moult, ventcrawl, and hide
add_spell(new /spell/aoe_turf/grue_hide, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/aoe_turf/grue_ventcrawl, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/aoe_turf/grue_moult, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
else if(lifestage==GRUE_JUVENILE)
name = "grue"
desc = "A creeping thing that lives in the dark. It is still a juvenile."
icon_state = "grueling_living"
icon_living = "grueling_living"
icon_dead = "grueling_dead"
base_melee_dam_up = 20 //base melee damage upper
base_melee_dam_lw = 15 //base melee damage lower
attacktext = "chomps"
rescaleHealth(150)
nutrienergy=0 //starts out hungry
maxnutrienergy = 500
moultcost = 100
lightparams.lightresist=0.85
environment_smash_flags = SMASH_LIGHT_STRUCTURES | SMASH_CONTAINERS | OPEN_DOOR_WEAK | OPEN_DOOR_STRONG
attack_sound = 'sound/weapons/cbar_hitbod1.ogg'
size = SIZE_BIG
pass_flags = 0
reagents.maximum_volume = 1000
//Juvenile grue spells: eat, moult, and shadow shunt
add_spell(new /spell/aoe_turf/grue_moult, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/aoe_turf/grue_blink, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/targeted/grue_eat, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
else
name = "grue"
desc = "A dangerous thing that lives in the dark."
icon_state = "grue_living"
icon_living = "grue_living"
icon_dead = "grue_dead"
attacktext = "gnashes"
rescaleHealth(250)
maxnutrienergy = 1000
moultcost=0 //not needed for adults
base_melee_dam_up = 30 //base melee damage upper
base_melee_dam_lw = 20 //base melee damage lower
melee_damage_type = BRUTE
held_items = list()
lightparams.lightresist=0.7
environment_smash_flags = SMASH_LIGHT_STRUCTURES | SMASH_CONTAINERS | OPEN_DOOR_WEAK | OPEN_DOOR_STRONG
attack_sound = 'sound/weapons/cbar_hitbod1.ogg'
size = SIZE_BIG
pass_flags = 0
reagents.maximum_volume = 1500
//Adult grue spells: eat, lay eggs, shadow shunt, and drain light
if(config.grue_egglaying)
add_spell(new /spell/aoe_turf/grue_egg, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/aoe_turf/grue_blink, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/aoe_turf/grue_drainlight/, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
add_spell(new /spell/targeted/grue_eat, "grue_spell_ready", /obj/abstract/screen/movable/spell_master/grue)
name = "[name] ([number])"
real_name = name
grue_stat_updates()
//Grue vision
/mob/living/simple_animal/hostile/grue/update_perception()
if(!client)
return
if(dark_plane)
if(master_plane)
master_plane.blend_mode = BLEND_ADD
dark_plane.alphas["grue"] = 0 // with the master_plane at BLEND_ADD, shadows appear well lit while actually well lit places appear blinding.
client.color = list(
1,0,0,0,
-1,0.2,0.2,0,
-1,0.2,0.2,0,
0,0,0,1,
0,0,0,0)
check_dark_vision()
/mob/living/simple_animal/hostile/grue/Stat()
..()
if(statpanel("Status"))
if(ismoulting)
stat(null, "Moulting progress: [round(100*(1-moulttimer/moulttime),0.1)]%")
stat(null, "Nutritive energy: [round(nutrienergy,0.1)]/[round(maxnutrienergy,0.1)]")
if(lifestage>=GRUE_JUVENILE)
stat(null, "Sentient organisms eaten: [eatencount]")
if(config.grue_egglaying && lifestage==GRUE_ADULT)
stat(null, "Reproductive energy: [eatencharge]")
/mob/living/simple_animal/hostile/grue/gruespawn
lifestage=GRUE_LARVA
/mob/living/simple_animal/hostile/grue/grueling
lifestage=GRUE_JUVENILE
//Moulting into more mature forms.
/mob/living/simple_animal/hostile/grue/proc/moult()
if(alert(src,"Would you like to moult? You will become a vulnerable and immobile chrysalis during the process.",,"Moult","Cancel") == "Moult")
if(lifestage<GRUE_ADULT)
if(nutrienergy<moultcost && (digest*digest_sp<(moultcost-nutrienergy)))
to_chat(src, "<span class='notice'>You need to feed more first.</span>")
return
if(nutrienergy<moultcost && digest)
to_chat(src, "<span class='notice'>You are still digesting.</span>")
return
else if(!isturf(loc))
to_chat(src, "<span class='notice'>You need more room to moult.</span>")
return
else if(stat==UNCONSCIOUS)
to_chat(src, "<span class='notice'>You must be awake to moult.</span>")
return
else if(busy)
to_chat(src, "<span class='notice'>You are already doing something.</span>")
return
else
start_moult()
else
to_chat(src, "<span class='notice'>You are already fully mature.</span>")
else
return
/mob/living/simple_animal/hostile/grue/proc/start_moult()
if(stat==CONSCIOUS && nutrienergy>=moultcost && !ismoulting && lifestage<GRUE_ADULT)
nutrienergy-=moultcost
visible_message("<span class='warning'>\The [src] morphs into a chrysalis...</span>","<span class='notice'>You begin moulting.</span>")
stat=UNCONSCIOUS //go unconscious while moulting
ismoulting=TRUE
moulttimer=moulttime//reset moulting timer
plane = MOB_PLANE //In case grue somehow moulted while hiding
if(lifestage==GRUE_LARVA)
lifestage=GRUE_JUVENILE
desc = "A small grue chrysalis."
name = "grue chrysalis"
icon_state = "moult1"
icon_living = "moult1"
icon_dead = "moult1"
rescaleHealth(25) //vulnerable while moulting
else if(lifestage==GRUE_JUVENILE)
lifestage=GRUE_ADULT
desc = "A grue chrysalis."
name = "grue chrysalis"
icon_state = "moult2"
icon_living = "moult2"
icon_dead = "moult2"
rescaleHealth(50) //vulnerable while moulting
//Remove spells to prepare for new lifestage spell list
var/list/current_spells = src.spell_list.Copy()
for(var/spell/S in current_spells)
src.remove_spell(S)
else
return
/mob/living/simple_animal/hostile/grue/proc/complete_moult()
if(ismoulting && stat!=DEAD)
lifestage_updates()
stat=CONSCIOUS //wake up
ismoulting=FALSE //is no longer moulting
var/hintstring=""
if(lifestage==GRUE_JUVENILE)
hintstring="a juvenile, and can eat sentient beings to gain their strength"
else if(lifestage==GRUE_ADULT)
hintstring="fully-grown[config.grue_egglaying ? ", and can lay eggs to spawn offspring" : ""]"
visible_message("<span class='warning'>The chrysalis shifts and morphs into a grue!</span>","<span class='warning'>You finish moulting! You are now [hintstring].</span>")
playsound(src, 'sound/effects/grue_moult.ogg', 50, 1)
else
return
/mob/living/simple_animal/hostile/grue/death(gibbed)
if(!gibbed)
playsound(src, 'sound/misc/grue_screech.ogg', 50, 1)
if(ismoulting)
desc="[desc] This one seems dead and lifeless."
for(var/spell/aoe_turf/grue_drainlight/S in spell_list)
S.stop_casting(null, src, TRUE)
..()
//Reproduction via egglaying.
/mob/living/simple_animal/hostile/grue/proc/reproduce()
if(lifestage==GRUE_ADULT) //must be adult
if(eatencharge<=0)
to_chat(src, "<span class='notice'>You need to feed more first.</span>")
return
else if(!isturf(loc))
to_chat(src, "<span class='notice'>You need more room to reproduce.</span>")
return
else if(stat==UNCONSCIOUS)
to_chat(src, "<span class='notice'>You must be awake to reproduce.</span>")
return
else if(busy)
to_chat(src, "<span class='notice'>You are already doing something.</span>")
return
else
handle_reproduce()
else
to_chat(src, "<span class='notice'>You haven't grown enough to reproduce yet.</span>")
/mob/living/simple_animal/hostile/grue/proc/handle_reproduce()
if(eatencharge>=1)
busy=TRUE
visible_message("<span class='warning'>\The [src] tightens up...</span>","<span class='notice'>You start to push out an egg...</span>")
if(do_after(src, src, 5 SECONDS))
visible_message("<span class='warning'>\The [src] pushes out an egg!</span>","<span class='notice'>You lay an egg.</span>")
eatencharge--
var/mob/living/simple_animal/grue_egg/E = new /mob/living/simple_animal/grue_egg(get_turf(src))
E.parent_grue=src //mark this grue as the parent of the egg
busy=FALSE
else
to_chat(src, "<span class='notice'>You need to feed more first.</span>")
return
//Procs for grabbing players.
/mob/living/simple_animal/hostile/grue/proc/request_player()
var/list/candidates=list()
for(var/mob/dead/observer/G in get_active_candidates(ROLE_GRUE, poll="Would you like to become a grue?"))
if(!G.client)
continue
if(isantagbanned(G))
continue
candidates += G
if(!candidates.len)
message_admins("Unable to find a mind for [src.name]")
return 0
shuffle(candidates)
for(var/mob/i in candidates)
if(!i || !i.client)
continue //Dont bother removing them from the list since we only grab one grue
return i
return 0
/mob/living/simple_animal/hostile/grue/proc/transfer_personality(var/client/candidate)
if(!candidate)
return
src.ckey = candidate.ckey
if(hatched && src.mind) //Assign it grue_basic objectives if its a hatched grue
var/datum/role/grue/hatchRole = new /datum/role/grue
mind.assigned_role = "Grue"
hatchRole.AssignToRole(mind,1)
hatchRole.Greet(GREET_DEFAULT)
hatchRole.ForgeObjectives(hatched)
hatchRole.AnnounceObjectives()
//Eating sentient beings.
/mob/living/simple_animal/hostile/grue/proc/handle_feed(var/mob/living/E)
visible_message("<span class='danger'>\The [src] opens its mouth wide...</span>","<span class='danger'>You open your mouth wide, preparing to eat \the [E]!</span>")
busy=TRUE
if(do_mob(src , E, eattime, eattime, 0)) //check on every tick
to_chat(src, "<span class='danger'>You have eaten \the [E]!</span>")
to_chat(E, "<span class='danger'>You have been eaten by a grue.</span>")
digest+=10 //add 10 life ticks (20 seconds) of increased healing + nutrition gain
//Transfer any reagents inside the creature to the grue
E.reagents.trans_to(src, E.reagents.total_volume)
//Upgrade the grue's stats as it feeds
if(E.mind && !isskellington(E)) //eaten creature must have a mind to power up the grue, and mustn't be pure skeletons
playsound(src, 'sound/misc/grue_growl.ogg', 50, 1)
eatencount++ //makes the grue stronger
if(mind && mind.GetRole(GRUE)) //also increment the counter for objectives
var/datum/role/grue/G = mind.GetRole(GRUE)
if(G)
G.eatencount++
eatencharge++ //can be spent on egg laying
grue_stat_updates(TRUE)
else
if(isskellington(E))
to_chat(src, "<span class='warning'>That creature was only bones, and didn't quite satisfy your hunger...</span>")
else
to_chat(src, "<span class='warning'>That creature didn't quite satisfy your hunger...</span>")
E.drop_all()
if(can_skeletonize(E))
var/mob/living/carbon/human/H = E
gibs(H.loc, H.virus2, H.dna, H.species.flesh_color, H.species.blood_color)
if(isvox(H))
H.set_species("Skeletal Vox")
else
H.set_species("Skellington")
H.regenerate_icons()
H.death(FALSE)
else //Eat the mob otherwise
E.gib()
busy=FALSE
//Determines whether the human will be gibbed or turned into a skeleton
/mob/living/simple_animal/hostile/grue/proc/can_skeletonize(var/mob/living/E)
if(ishuman(E))
var/mob/living/carbon/human/H = E
if(H.species.anatomy_flags & NO_BLOOD)
return 0
return 1
return 0
/mob/living/simple_animal/hostile/grue/proc/grue_stat_updates(var/feed_verbose = FALSE) //update stats, called by lifestage_updates() as well as handle_feed()
//health regen in darkness
lightparams.regenbonus = lightparams.base_regenbonus * (1.1 ** eatencount) //increased health regen in darkness
//melee damage, 50 damage limit
melee_damage_lower = min(50, base_melee_dam_lw + (5 * eatencount))
melee_damage_upper = min(50, base_melee_dam_up + (5 * eatencount))
//speed bonus in dark and dim conditions
lightparams.speed_m_dark_dim_light[1]=max(1/2,lightparams.base_speed_m_dark_dim_light[1]/(1.2 ** eatencount))//faster in darkness
lightparams.speed_m_dark_dim_light[2]=max(5/8,lightparams.base_speed_m_dark_dim_light[2]/(1.2 ** eatencount))//faster in dim light
//update light drain power in case the grue fed while channeling it
if(channeling_flags & GRUE_DRAINLIGHT)
drainlight_set()
if(lifestage==GRUE_ADULT)
if(eatencount>=GRUE_WALLBREAK)
environment_smash_flags |= SMASH_WALLS
if(eatencount>=GRUE_RWALLBREAK)
environment_smash_flags |= SMASH_RWALLS
if(feed_verbose)
var/wallhintstring="er"
if(lifestage==GRUE_ADULT) //juveniles that feed up to at least GRUE_WALLBREAK will still gain the ability to smash walls once they moult into adults, but they wont get the hint message
if(eatencount==GRUE_WALLBREAK)
wallhintstring=" enough to smash down most walls"
if(eatencount==GRUE_RWALLBREAK)
wallhintstring=" enough to smash down even reinforced walls"
to_chat(src, "<span class='warning'>You feel power coursing through you! You feel strong[wallhintstring]... but still hungry...</span>")
if(lifestage==GRUE_JUVENILE)
force_airlock_time=30 * (eatencount==0) //takes 3 seconds if they haven't eaten, but is instant if they have
else
force_airlock_time=0
//shadow shunt cooldown
for(var/spell/aoe_turf/grue_blink/thisspell in spell_list)
var/newcooldown = max(45 SECONDS - eatencount * 2 SECONDS, 8 SECONDS)
thisspell.charge_cooldown_max = newcooldown
thisspell.charge_counter = min(newcooldown, thisspell.charge_counter)
//Drain the light from the surrounding area.
/mob/living/simple_animal/hostile/grue/proc/drainlight(var/activating = TRUE, var/mute = FALSE)
if(activating)
if(nutrienergy)
channeling_flags |= GRUE_DRAINLIGHT
drainlight_set()
if(!mute)
to_chat(src, "<span class='notice'>You focus on draining the light from the surrounding space.</span>")
return TRUE
else
if(!mute)
to_chat(src, "<span class='notice'>You need to feed more first.</span>")
else
channeling_flags &= !GRUE_DRAINLIGHT
set_light(0)
if(nutrienergy)
if(!mute)
to_chat(src, "<span class='notice'>You stop draining light.</span>")
else
if(!mute)
to_chat(src, "<span class='warning'>You are too hungry to keep draining light...")
return FALSE
/mob/living/simple_animal/hostile/grue/proc/drainlight_set() //Set the strength of light drain.
set_light(max(10, 7 + eatencount), max(-15, -3 * eatencount - 3), GRUE_BLOOD) //Eating sentients makes the drain more powerful.
//Ventcrawling and hiding, only for gruespawn
/mob/living/simple_animal/hostile/grue/proc/ventcrawl()
if(lifestage==GRUE_LARVA)
var/pipe = start_ventcrawl()
if(pipe)
handle_ventcrawl(pipe)
else
to_chat(src, "<span class='notice'>You are too big to do that.</span>")
/mob/living/simple_animal/hostile/grue/proc/hide()
if(lifestage==GRUE_LARVA)
if(isUnconscious())
return
if(locked_to && istype(locked_to, /obj/item/critter_cage))
return
if(plane != HIDING_MOB_PLANE)
plane = HIDING_MOB_PLANE
to_chat(src, "<span class='notice'>You are now hiding.</span>")
else
plane = MOB_PLANE
to_chat(src, "<span class='notice'>You have stopped hiding.</span>")
else
to_chat(src, "<span class='notice'>You are too big to do that.</span>")
//Shadow shunt, blinks to another nearby dark location.
/mob/living/simple_animal/hostile/grue/proc/grueblink()
var/list/blinkcandidates = list()
//get all sufficiently dark spots in range
finding_blink_spots:
for(var/turf/thisfloor in orange(25))
if(thisfloor.density)
continue
if(get_ddl(thisfloor) > GRUE_DARK)
continue
for(var/atom/thiscontent in thisfloor.contents)
if(thiscontent.density)
continue finding_blink_spots
if(ismob(thiscontent))
continue finding_blink_spots
if(istable(thiscontent))
continue finding_blink_spots
blinkcandidates += thisfloor
if(blinkcandidates.len)
//check up to 50 random valid locations and pick the furthest one
var/turf/blinktarget
var/btdist = -1
var/oxy_lower_bound = min_oxy / CELL_VOLUME
for(var/bc_index in 1 to min(blinkcandidates.len, 50))
var/random_bc_index = rand(1, blinkcandidates.len)
var/turf/random_bc = blinkcandidates[random_bc_index]
var/thisdist = get_dist(random_bc, src)
//weight away from low oxygen spots
var/o2content = random_bc.air ? random_bc.air.molar_density(GAS_OXYGEN): 0
if(o2content < oxy_lower_bound)
thisdist *= o2content
else
thisdist *= oxy_lower_bound
if(thisdist > btdist)
btdist = thisdist
blinktarget = random_bc
playsound(src, 'sound/effects/grue_shadowshunt.ogg', 20, 1)
playsound(blinktarget, 'sound/effects/grue_shadowshunt.ogg', 20, 1)
to_chat(src, "<span class='notice'>You [pick("shift", "step", "slide", "glide", "push")] [pick("comfortably", "easily", "effortlessly", "readily", "gracefully")] through the [pick("darkness", "dark", "shadows", "shade", "blackness")].</span>")
new /obj/effect/gruesparkles (loc)
unlock_from() // just in case
forceMove(blinktarget)
new /obj/effect/gruesparkles (loc)
return TRUE
else
to_chat(src, "<span class='warning'>You reach into the darkness, but can't seem to find a way.</span>")
//set the remaining cooldown to one second if no valid location was found
for(var/spell/aoe_turf/grue_blink/thisspell in spell_list)
thisspell.charge_counter = thisspell.charge_cooldown_max - 1 SECONDS
return
// Dark sparkles when a grue uses shadow shunt.
/obj/effect/gruesparkles
name = "dark glimmers"
icon_state = "gruesparkles"
anchored = 1
/obj/effect/gruesparkles/New()
..()
spawn(1.5 SECONDS)
fade()
/obj/effect/gruesparkles/proc/fade()
if(!src)
return
if(alpha <= 0)
qdel(src)
return
spawn(0.5 SECONDS)
alpha -= 55
fade()