631 lines
18 KiB
Plaintext
631 lines
18 KiB
Plaintext
|
|
/mob/living/simple_animal/slime
|
|
var/AIproc = 0 // determines if the AI loop is activated
|
|
var/Atkcool = 0 // attack cooldown
|
|
var/Tempstun = 0 // temporary temperature stuns
|
|
var/Discipline = 0 // if a slime has been hit with a freeze gun, or wrestled/attacked off a human, they become disciplined and don't attack anymore for a while
|
|
var/SStun = 0 // stun variable
|
|
|
|
typing_indicator_state = /obj/effect/overlay/typing_indicator/slime
|
|
|
|
/mob/living/simple_animal/slime/BiologicalLife(delta_time, times_fired)
|
|
if(!(. = ..()))
|
|
return
|
|
if(buckled)
|
|
handle_feeding()
|
|
if(!stat) // Slimes in stasis don't lose nutrition, don't change mood and don't respond to speech
|
|
handle_nutrition()
|
|
handle_targets()
|
|
if (!ckey)
|
|
handle_mood()
|
|
handle_speech()
|
|
|
|
// Unlike most of the simple animals, slimes support UNCONSCIOUS
|
|
/mob/living/simple_animal/slime/update_stat()
|
|
if(stat == UNCONSCIOUS && health > 0)
|
|
return
|
|
..()
|
|
|
|
/mob/living/simple_animal/slime/proc/AIprocess() // the master AI process
|
|
|
|
if(AIproc || stat || client)
|
|
return
|
|
|
|
var/hungry = 0
|
|
if (nutrition < get_starve_nutrition())
|
|
hungry = 2
|
|
else if (nutrition < get_grow_nutrition() && prob(25) || nutrition < get_hunger_nutrition())
|
|
hungry = 1
|
|
|
|
AIproc = 1
|
|
|
|
while(AIproc && stat != DEAD && (attacked || hungry || rabid || buckled))
|
|
if(!CHECK_MOBILITY(src, MOBILITY_MOVE)) //also covers buckling. Not sure why buckled is in the while condition if we're going to immediately break, honestly
|
|
break
|
|
|
|
if(!Target || client)
|
|
break
|
|
|
|
if(Target.health <= -70 || Target.stat == DEAD)
|
|
Target = null
|
|
AIproc = 0
|
|
break
|
|
|
|
if(Target)
|
|
if(locate(/mob/living/simple_animal/slime) in Target.buckled_mobs)
|
|
Target = null
|
|
AIproc = 0
|
|
break
|
|
if(!AIproc)
|
|
break
|
|
|
|
if(Target in view(1,src))
|
|
if(!CanFeedon(Target)) //If they're not able to be fed upon, ignore them.
|
|
if(!Atkcool)
|
|
Atkcool = 1
|
|
spawn(45)
|
|
Atkcool = 0
|
|
|
|
if(Target.Adjacent(src))
|
|
Target.attack_slime(src)
|
|
break
|
|
if(!Target.lying && prob(80))
|
|
|
|
if(Target.client && Target.health >= 20)
|
|
if(!Atkcool)
|
|
Atkcool = 1
|
|
spawn(45)
|
|
Atkcool = 0
|
|
|
|
if(Target.Adjacent(src))
|
|
Target.attack_slime(src)
|
|
|
|
else
|
|
if(!Atkcool && Target.Adjacent(src))
|
|
Feedon(Target)
|
|
|
|
else
|
|
if(!Atkcool && Target.Adjacent(src))
|
|
Feedon(Target)
|
|
|
|
else if(Target in view(7, src))
|
|
if(!Target.Adjacent(src))
|
|
// Bug of the month candidate: slimes were attempting to move to target only if it was directly next to them, which caused them to target things, but not approach them
|
|
step_to(src, Target)
|
|
else
|
|
Target = null
|
|
AIproc = 0
|
|
break
|
|
|
|
var/sleeptime = movement_delay()
|
|
if(sleeptime <= 0)
|
|
sleeptime = 1
|
|
|
|
sleep(sleeptime + 2) // this is about as fast as a player slime can go
|
|
|
|
AIproc = 0
|
|
|
|
/mob/living/simple_animal/slime/handle_environment(datum/gas_mixture/environment)
|
|
if(!environment)
|
|
return
|
|
|
|
var/loc_temp = get_temperature(environment)
|
|
|
|
adjust_bodytemperature(adjust_body_temperature(bodytemperature, loc_temp, 1))
|
|
|
|
//Account for massive pressure differences
|
|
|
|
if(bodytemperature < (T0C + 5)) // start calculating temperature damage etc
|
|
if(bodytemperature <= (T0C - 40)) // stun temperature
|
|
Tempstun = 1
|
|
|
|
if(bodytemperature <= (T0C - 50)) // hurt temperature
|
|
if(bodytemperature <= 50) // sqrting negative numbers is bad
|
|
adjustBruteLoss(200)
|
|
else
|
|
adjustBruteLoss(round(sqrt(bodytemperature)) * 2)
|
|
|
|
else
|
|
Tempstun = 0
|
|
|
|
if(stat != DEAD)
|
|
var/bz_percentage = environment.total_moles() ? (environment.get_moles(GAS_BZ) / environment.total_moles()) : 0
|
|
var/stasis = (bz_percentage >= 0.05 && bodytemperature < (T0C + 100)) || force_stasis
|
|
|
|
if(stat == CONSCIOUS && stasis)
|
|
to_chat(src, "<span class='danger'>Nerve gas in the air has put you in stasis!</span>")
|
|
set_stat(UNCONSCIOUS)
|
|
powerlevel = 0
|
|
rabid = 0
|
|
update_mobility()
|
|
regenerate_icons()
|
|
else if(stat == UNCONSCIOUS && !stasis)
|
|
to_chat(src, "<span class='notice'>You wake up from the stasis.</span>")
|
|
set_stat(CONSCIOUS)
|
|
update_mobility()
|
|
regenerate_icons()
|
|
|
|
updatehealth()
|
|
|
|
|
|
return //TODO: DEFERRED
|
|
|
|
/mob/living/simple_animal/slime/proc/adjust_body_temperature(current, loc_temp, boost)
|
|
var/temperature = current
|
|
var/difference = abs(current-loc_temp) //get difference
|
|
var/increments// = difference/10 //find how many increments apart they are
|
|
if(difference > 50)
|
|
increments = difference/5
|
|
else
|
|
increments = difference/10
|
|
var/change = increments*boost // Get the amount to change by (x per increment)
|
|
var/temp_change
|
|
if(current < loc_temp)
|
|
temperature = min(loc_temp, temperature+change)
|
|
else if(current > loc_temp)
|
|
temperature = max(loc_temp, temperature-change)
|
|
temp_change = (temperature - current)
|
|
return temp_change
|
|
|
|
/mob/living/simple_animal/slime/handle_status_effects()
|
|
..()
|
|
if(prob(30) && !stat)
|
|
adjustBruteLoss(-1)
|
|
|
|
/mob/living/simple_animal/slime/proc/handle_feeding()
|
|
if(!ismob(buckled))
|
|
return
|
|
var/mob/M = buckled
|
|
|
|
if(stat)
|
|
Feedstop(silent = TRUE)
|
|
|
|
if(M.stat == DEAD) // our victim died
|
|
if(!client)
|
|
if(!rabid && !attacked)
|
|
var/mob/living/carbon/their_attacker = M.getLAssailant()
|
|
if(their_attacker != M)
|
|
if(prob(50))
|
|
if(!(their_attacker in Friends))
|
|
Friends[their_attacker] = 1
|
|
else
|
|
++Friends[their_attacker]
|
|
else
|
|
to_chat(src, "<i>This subject does not have a strong enough life energy anymore...</i>")
|
|
|
|
if(M.client && ishuman(M))
|
|
if(prob(85))
|
|
rabid = 1 //we go rabid after finishing to feed on a human with a client.
|
|
|
|
Feedstop()
|
|
return
|
|
|
|
if(iscarbon(M))
|
|
var/mob/living/carbon/C = M
|
|
C.adjustCloneLoss(rand(2,4))
|
|
C.adjustToxLoss(rand(1,2))
|
|
|
|
if(prob(10) && C.client)
|
|
to_chat(C, "<span class='userdanger'>[pick("You can feel your body becoming weak!", \
|
|
"You feel like you're about to die!", \
|
|
"You feel every part of your body screaming in agony!", \
|
|
"A low, rolling pain passes through your body!", \
|
|
"Your body feels as if it's falling apart!", \
|
|
"You feel extremely weak!", \
|
|
"A sharp, deep pain bathes every inch of your body!")]</span>")
|
|
|
|
else if(isanimal(M))
|
|
var/mob/living/simple_animal/SA = M
|
|
|
|
var/totaldamage = 0 //total damage done to this unfortunate animal
|
|
totaldamage += SA.adjustCloneLoss(rand(2,4))
|
|
totaldamage += SA.adjustToxLoss(rand(1,2))
|
|
|
|
if(totaldamage <= 0) //if we did no(or negative!) damage to it, stop
|
|
Feedstop(0, 0)
|
|
return
|
|
|
|
else
|
|
Feedstop(0, 0)
|
|
return
|
|
|
|
adjust_nutrition((rand(7, 15) * CONFIG_GET(number/damage_multiplier)), get_max_nutrition(), TRUE)
|
|
|
|
//Heal yourself.
|
|
adjustBruteLoss(-3)
|
|
|
|
/mob/living/simple_animal/slime/proc/handle_nutrition()
|
|
|
|
if(docile) //God as my witness, I will never go hungry again
|
|
nutrition = 700
|
|
return
|
|
|
|
if(prob(15))
|
|
adjust_nutrition(-1 - is_adult)
|
|
|
|
if(nutrition <= 0 && prob(75))
|
|
adjustBruteLoss(rand(0,5))
|
|
|
|
else if (nutrition >= get_grow_nutrition() && amount_grown < SLIME_EVOLUTION_THRESHOLD)
|
|
adjust_nutrition(-20)
|
|
amount_grown++
|
|
update_action_buttons_icon()
|
|
|
|
if(amount_grown >= SLIME_EVOLUTION_THRESHOLD && !buckled && !Target && !ckey)
|
|
if(is_adult)
|
|
Reproduce()
|
|
else
|
|
Evolve()
|
|
|
|
/mob/living/simple_animal/slime/adjust_nutrition(change, max = INFINITY, slime_check = FALSE)
|
|
. = ..()
|
|
if(!slime_check)
|
|
return
|
|
if(nutrition >= get_grow_nutrition())
|
|
if(powerlevel<10)
|
|
if(prob(30-powerlevel*2))
|
|
powerlevel++
|
|
else if(nutrition >= get_hunger_nutrition() + 100) //can't get power levels unless you're a bit above hunger level.
|
|
if(powerlevel<5)
|
|
if(prob(25-powerlevel*5))
|
|
powerlevel++
|
|
|
|
/mob/living/simple_animal/slime/proc/handle_targets()
|
|
update_mobility()
|
|
|
|
if(attacked > 50)
|
|
attacked = 50
|
|
|
|
if(attacked > 0)
|
|
attacked--
|
|
|
|
if(Discipline > 0)
|
|
|
|
if(Discipline >= 5 && rabid)
|
|
if(prob(60))
|
|
rabid = 0
|
|
|
|
if(prob(10))
|
|
Discipline--
|
|
|
|
if(!client)
|
|
if(!CHECK_MOBILITY(src, MOBILITY_MOVE))
|
|
return
|
|
|
|
if(buckled)
|
|
return // if it's eating someone already, continue eating!
|
|
|
|
if(Target)
|
|
--target_patience
|
|
if (target_patience <= 0 || SStun > world.time || Discipline || attacked || docile) // Tired of chasing or something draws out attention
|
|
target_patience = 0
|
|
Target = null
|
|
|
|
if(AIproc && SStun > world.time)
|
|
return
|
|
|
|
var/hungry = 0 // determines if the slime is hungry
|
|
|
|
if (nutrition < get_starve_nutrition())
|
|
hungry = 2
|
|
else if (nutrition < get_grow_nutrition() && prob(25) || nutrition < get_hunger_nutrition())
|
|
hungry = 1
|
|
|
|
if(hungry == 2 && !client) // if a slime is starving, it starts losing its friends
|
|
if(Friends.len > 0 && prob(1))
|
|
var/mob/nofriend = pick(Friends)
|
|
--Friends[nofriend]
|
|
|
|
if(!Target)
|
|
if(will_hunt() && hungry || attacked || rabid) // Only add to the list if we need to
|
|
var/list/targets = list()
|
|
|
|
for(var/mob/living/L in view(7,src))
|
|
|
|
if(isslime(L) || L.stat == DEAD) // Ignore other slimes and dead mobs
|
|
continue
|
|
|
|
if(L in Friends) // No eating friends!
|
|
continue
|
|
|
|
var/ally = FALSE
|
|
for(var/F in faction)
|
|
if(F == "neutral") //slimes are neutral so other mobs not target them, but they can target neutral mobs
|
|
continue
|
|
if(F in L.faction)
|
|
ally = TRUE
|
|
break
|
|
if(ally)
|
|
continue
|
|
|
|
if(issilicon(L) && (rabid || attacked)) // They can't eat silicons, but they can glomp them in defence
|
|
targets += L // Possible target found!
|
|
|
|
if(ishuman(L)) //Ignore slime(wo)men
|
|
var/mob/living/carbon/human/H = L
|
|
if(src.type in H.dna.species.ignored_by)
|
|
continue
|
|
|
|
if(locate(/mob/living/simple_animal/slime) in L.buckled_mobs) // Only one slime can latch on at a time.
|
|
continue
|
|
|
|
targets += L // Possible target found!
|
|
|
|
if(targets.len > 0)
|
|
if(attacked || rabid || hungry == 2)
|
|
Target = targets[1] // I am attacked and am fighting back or so hungry I don't even care
|
|
else
|
|
for(var/mob/living/carbon/C in targets)
|
|
if(!Discipline && prob(5))
|
|
if(ishuman(C) || isalienadult(C))
|
|
Target = C
|
|
break
|
|
|
|
if(islarva(C) || ismonkey(C))
|
|
Target = C
|
|
break
|
|
|
|
if (Target)
|
|
target_patience = rand(5,7)
|
|
if (is_adult)
|
|
target_patience += 3
|
|
|
|
if(!Target) // If we have no target, we are wandering or following orders
|
|
if (Leader)
|
|
if(holding_still)
|
|
holding_still = max(holding_still - 1, 0)
|
|
else if(CHECK_MOBILITY(src, MOBILITY_MOVE) && isturf(loc))
|
|
step_to(src, Leader)
|
|
|
|
else if(hungry)
|
|
if (holding_still)
|
|
holding_still = max(holding_still - hungry, 0)
|
|
else if(CHECK_MOBILITY(src, MOBILITY_MOVE) && isturf(loc) && prob(50))
|
|
step(src, pick(GLOB.cardinals))
|
|
|
|
else
|
|
if(holding_still)
|
|
holding_still = max(holding_still - 1, 0)
|
|
else if (docile && pulledby)
|
|
holding_still = 10
|
|
else if(CHECK_MOBILITY(src, MOBILITY_MOVE) && isturf(loc) && prob(33))
|
|
step(src, pick(GLOB.cardinals))
|
|
else if(!AIproc)
|
|
INVOKE_ASYNC(src, PROC_REF(AIprocess))
|
|
|
|
/mob/living/simple_animal/slime/handle_automated_movement()
|
|
return //slime random movement is currently handled in handle_targets()
|
|
|
|
/mob/living/simple_animal/slime/handle_automated_speech()
|
|
return //slime random speech is currently handled in handle_speech()
|
|
|
|
/mob/living/simple_animal/slime/proc/handle_mood()
|
|
var/newmood = ""
|
|
if (rabid || attacked)
|
|
newmood = "angry"
|
|
else if (docile)
|
|
newmood = ":3"
|
|
else if (Target)
|
|
newmood = "mischievous"
|
|
|
|
if (!newmood)
|
|
if (Discipline && prob(25))
|
|
newmood = "pout"
|
|
else if (prob(1))
|
|
newmood = pick("sad", ":3", "pout")
|
|
|
|
if ((mood == "sad" || mood == ":3" || mood == "pout") && !newmood)
|
|
if(prob(75))
|
|
newmood = mood
|
|
|
|
if (newmood != mood) // This is so we don't redraw them every time
|
|
mood = newmood
|
|
regenerate_icons()
|
|
|
|
/mob/living/simple_animal/slime/proc/handle_speech()
|
|
//Speech understanding starts here
|
|
var/to_say
|
|
if (speech_buffer.len > 0)
|
|
var/who = speech_buffer[1] // Who said it?
|
|
var/phrase = speech_buffer[2] // What did they say?
|
|
if ((findtext(phrase, num2text(number)) || findtext(phrase, "slimes"))) // Talking to us
|
|
if (findtext(phrase, "hello") || findtext(phrase, "hi"))
|
|
to_say = pick("Hello...", "Hi...")
|
|
else if (findtext(phrase, "follow"))
|
|
if (Leader)
|
|
if (Leader == who) // Already following him
|
|
to_say = pick("Yes...", "Lead...", "Follow...")
|
|
else if (Friends[who] > Friends[Leader]) // VIVA
|
|
Leader = who
|
|
to_say = "Yes... I follow [who]..."
|
|
else
|
|
to_say = "No... I follow [Leader]..."
|
|
else
|
|
if (Friends[who] >= SLIME_FRIENDSHIP_FOLLOW)
|
|
Leader = who
|
|
to_say = "I follow..."
|
|
else // Not friendly enough
|
|
to_say = pick("No...", "I no follow...")
|
|
else if (findtext(phrase, "stop"))
|
|
if (buckled) // We are asked to stop feeding
|
|
if (Friends[who] >= SLIME_FRIENDSHIP_STOPEAT)
|
|
Feedstop()
|
|
Target = null
|
|
if (Friends[who] < SLIME_FRIENDSHIP_STOPEAT_NOANGRY)
|
|
--Friends[who]
|
|
to_say = "Grrr..." // I'm angry but I do it
|
|
else
|
|
to_say = "Fine..."
|
|
else if (Target) // We are asked to stop chasing
|
|
if (Friends[who] >= SLIME_FRIENDSHIP_STOPCHASE)
|
|
Target = null
|
|
if (Friends[who] < SLIME_FRIENDSHIP_STOPCHASE_NOANGRY)
|
|
--Friends[who]
|
|
to_say = "Grrr..." // I'm angry but I do it
|
|
else
|
|
to_say = "Fine..."
|
|
else if (Leader) // We are asked to stop following
|
|
if (Leader == who)
|
|
to_say = "Yes... I stay..."
|
|
Leader = null
|
|
else
|
|
if (Friends[who] > Friends[Leader])
|
|
Leader = null
|
|
to_say = "Yes... I stop..."
|
|
else
|
|
to_say = "No... keep follow..."
|
|
else if (findtext(phrase, "stay"))
|
|
if (Leader)
|
|
if (Leader == who)
|
|
holding_still = Friends[who] * 10
|
|
to_say = "Yes... stay..."
|
|
else if (Friends[who] > Friends[Leader])
|
|
holding_still = (Friends[who] - Friends[Leader]) * 10
|
|
to_say = "Yes... stay..."
|
|
else
|
|
to_say = "No... keep follow..."
|
|
else
|
|
if (Friends[who] >= SLIME_FRIENDSHIP_STAY)
|
|
holding_still = Friends[who] * 10
|
|
to_say = "Yes... stay..."
|
|
else
|
|
to_say = "No... won't stay..."
|
|
else if (findtext(phrase, "attack"))
|
|
if (rabid && prob(20))
|
|
Target = who
|
|
AIprocess() //Wake up the slime's Target AI, needed otherwise this doesn't work
|
|
to_say = "ATTACK!?!?"
|
|
else if (Friends[who] >= SLIME_FRIENDSHIP_ATTACK)
|
|
for (var/mob/living/L in view(7,src)-list(src,who))
|
|
if (findtext(phrase, lowertext(L.name)))
|
|
if (isslime(L))
|
|
to_say = "NO... [L] slime friend"
|
|
--Friends[who] //Don't ask a slime to attack its friend
|
|
else if(!Friends[L] || Friends[L] < 1)
|
|
Target = L
|
|
AIprocess()//Wake up the slime's Target AI, needed otherwise this doesn't work
|
|
to_say = "Ok... I attack [Target]"
|
|
else
|
|
to_say = "No... like [L] ..."
|
|
--Friends[who] //Don't ask a slime to attack its friend
|
|
break
|
|
else
|
|
to_say = "No... no listen"
|
|
|
|
speech_buffer = list()
|
|
|
|
//Speech starts here
|
|
if (to_say)
|
|
say (to_say)
|
|
else if(prob(1))
|
|
emote(pick("bounce","sway","light","vibrate","jiggle"))
|
|
else
|
|
var/t = 10
|
|
var/slimes_near = 0
|
|
var/dead_slimes = 0
|
|
var/friends_near = list()
|
|
for (var/mob/living/L in view(7,src))
|
|
if(isslime(L) && L != src)
|
|
++slimes_near
|
|
if (L.stat == DEAD)
|
|
++dead_slimes
|
|
if (L in Friends)
|
|
t += 20
|
|
friends_near += L
|
|
if (nutrition < get_hunger_nutrition())
|
|
t += 10
|
|
if (nutrition < get_starve_nutrition())
|
|
t += 10
|
|
if (prob(2) && prob(t))
|
|
var/phrases = list()
|
|
if (Target)
|
|
phrases += "[Target]... look yummy..."
|
|
if (nutrition < get_starve_nutrition())
|
|
phrases += "So... hungry..."
|
|
phrases += "Very... hungry..."
|
|
phrases += "Need... food..."
|
|
phrases += "Must... eat..."
|
|
else if (nutrition < get_hunger_nutrition())
|
|
phrases += "Hungry..."
|
|
phrases += "Where food?"
|
|
phrases += "I want to eat..."
|
|
phrases += "Rawr..."
|
|
phrases += "Blop..."
|
|
phrases += "Blorble..."
|
|
if (rabid || attacked)
|
|
phrases += "Hrr..."
|
|
phrases += "Nhuu..."
|
|
phrases += "Unn..."
|
|
if (mood == ":3")
|
|
phrases += "Purr..."
|
|
if (attacked)
|
|
phrases += "Grrr..."
|
|
if (bodytemperature < T0C)
|
|
phrases += "Cold..."
|
|
if (bodytemperature < T0C - 30)
|
|
phrases += "So... cold..."
|
|
phrases += "Very... cold..."
|
|
if (bodytemperature < T0C - 50)
|
|
phrases += "..."
|
|
phrases += "C... c..."
|
|
if (buckled)
|
|
phrases += "Nom..."
|
|
phrases += "Yummy..."
|
|
if (powerlevel > 3)
|
|
phrases += "Bzzz..."
|
|
if (powerlevel > 5)
|
|
phrases += "Zap..."
|
|
if (powerlevel > 8)
|
|
phrases += "Zap... Bzz..."
|
|
if (mood == "sad")
|
|
phrases += "Bored..."
|
|
if (slimes_near)
|
|
phrases += "Slime friend..."
|
|
if (slimes_near > 1)
|
|
phrases += "Slime friends..."
|
|
if (dead_slimes)
|
|
phrases += "What happened?"
|
|
if (!slimes_near)
|
|
phrases += "Lonely..."
|
|
for (var/M in friends_near)
|
|
phrases += "[M]... friend..."
|
|
if (nutrition < get_hunger_nutrition())
|
|
phrases += "[M]... feed me..."
|
|
if(!stat)
|
|
say (pick(phrases))
|
|
|
|
/mob/living/simple_animal/slime/proc/get_max_nutrition() // Can't go above it
|
|
if (is_adult)
|
|
return 1200
|
|
else
|
|
return 1000
|
|
|
|
/mob/living/simple_animal/slime/proc/get_grow_nutrition() // Above it we grow, below it we can eat
|
|
if (is_adult)
|
|
return 1000
|
|
else
|
|
return 800
|
|
|
|
/mob/living/simple_animal/slime/proc/get_hunger_nutrition() // Below it we will always eat
|
|
if (is_adult)
|
|
return 600
|
|
else
|
|
return 500
|
|
|
|
/mob/living/simple_animal/slime/proc/get_starve_nutrition() // Below it we will eat before everything else
|
|
if(is_adult)
|
|
return 300
|
|
else
|
|
return 200
|
|
|
|
/mob/living/simple_animal/slime/proc/will_hunt(hunger = -1) // Check for being stopped from feeding and chasing
|
|
if (docile)
|
|
return FALSE
|
|
if (hunger == 2 || rabid || attacked)
|
|
return TRUE
|
|
if (Leader)
|
|
return FALSE
|
|
if (holding_still)
|
|
return FALSE
|
|
return TRUE
|