Fixes some AI bugs.

This commit is contained in:
Neerti
2019-01-23 11:54:01 -05:00
committed by Novacat
parent 1e5b351f43
commit 519890613f
4 changed files with 330 additions and 322 deletions

View File

@@ -1,266 +1,269 @@
// Specialized AI for slime simplemobs.
// Unlike the parent AI code, this will probably break a lot of things if you put it on something that isn't /mob/living/simple_mob/slime/xenobio
/datum/ai_holder/simple_mob/xenobio_slime
hostile = TRUE
cooperative = TRUE
firing_lanes = TRUE
var/rabid = FALSE // Will attack regardless of discipline.
var/discipline = 0 // Beating slimes makes them less likely to lash out. In theory.
var/resentment = 0 // 'Unjustified' beatings make this go up, and makes it more likely for abused slimes to go rabid.
var/obedience = 0 // Conversely, 'justified' beatings make this go up, and makes discipline decay slower, potentially making it not decay at all.
var/always_stun = FALSE // If true, the slime will elect to attempt to permastun the target.
var/last_discipline_decay = null // Last world.time discipline was reduced from decay.
var/discipline_decay_time = 5 SECONDS // Earliest that one discipline can decay.
/datum/ai_holder/simple_mob/xenobio_slime/sapphire
always_stun = TRUE // They know that stuns are godly.
intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death.
/datum/ai_holder/simple_mob/xenobio_slime/light_pink
discipline = 10
obedience = 10
/datum/ai_holder/simple_mob/xenobio_slime/passive/New() // For Kendrick.
..()
pacify()
/datum/ai_holder/simple_mob/xenobio_slime/New()
..()
ASSERT(istype(holder, /mob/living/simple_mob/slime/xenobio))
// Checks if disciplining the slime would be 'justified' right now.
/datum/ai_holder/simple_mob/xenobio_slime/proc/is_justified_to_discipline()
if(rabid)
return TRUE
if(target)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(istype(H.species, /datum/species/monkey))
return FALSE // Attacking monkeys is okay.
return TRUE // Otherwise attacking other things is bad.
return FALSE // Not attacking anything.
/datum/ai_holder/simple_mob/xenobio_slime/proc/can_command(mob/living/commander)
if(rabid)
return FALSE
if(!hostile)
return SLIME_COMMAND_OBEY
// if(commander in friends)
// return SLIME_COMMAND_FRIEND
if(holder.IIsAlly(commander))
return SLIME_COMMAND_FACTION
if(discipline > resentment && obedience >= 5)
return SLIME_COMMAND_OBEY
return FALSE
/datum/ai_holder/simple_mob/xenobio_slime/proc/adjust_discipline(amount, silent)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(amount > 0)
if(rabid)
return
var/justified = is_justified_to_discipline()
lost_target() // Stop attacking.
if(justified)
obedience++
if(!silent)
holder.say(pick("Fine...", "Okay...", "Sorry...", "I yield...", "Mercy..."))
else
if(prob(resentment * 20))
enrage()
holder.say(pick("Evil...", "Kill...", "Tyrant..."))
else
if(!silent)
holder.say(pick("Why...?", "I don't understand...?", "Cruel...", "Stop...", "Nooo..."))
resentment++ // Done after check so first time will never enrage.
discipline = between(0, discipline + amount, 10)
my_slime.update_mood()
// This slime always enrages if disciplined.
/datum/ai_holder/simple_mob/xenobio_slime/red/adjust_discipline(amount, silent)
if(amount > 0 && !rabid)
holder.say("Grrr...")
holder.add_modifier(/datum/modifier/berserk, 30 SECONDS)
enrage()
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_strategical()
discipline_decay()
// Handles decay of discipline.
/datum/ai_holder/simple_mob/xenobio_slime/proc/discipline_decay()
if(discipline > 0 && last_discipline_decay + discipline_decay_time < world.time)
if(!prob(75 + (obedience * 5)))
adjust_discipline(-1)
last_discipline_decay = world.time
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_tactic()
evolve_and_reproduce()
// Hit the correct verbs to keep the slime species going.
/datum/ai_holder/simple_mob/xenobio_slime/proc/evolve_and_reproduce()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.amount_grown >= 10)
// Press the correct verb when we can.
if(my_slime.is_adult)
my_slime.reproduce() // Splits into four new baby slimes.
else
my_slime.evolve() // Turns our holder into an adult slime.
// Called when pushed too far (or a red slime core was used).
/datum/ai_holder/simple_mob/xenobio_slime/proc/enrage()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.harmless)
return
rabid = TRUE
my_slime.update_mood()
my_slime.visible_message(span("danger", "\The [src] enrages!"))
// Called when using a pacification agent (or it's Kendrick being initalized).
/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify()
lost_target() // So it stops trying to kill them.
rabid = FALSE
hostile = FALSE
retaliate = FALSE
cooperative = FALSE
// The holder's attack changes based on intent. This lets the AI choose what effect is desired.
/datum/ai_holder/simple_mob/xenobio_slime/pre_melee_attack(atom/A)
if(istype(A, /mob/living))
var/mob/living/L = A
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if( (!L.lying && prob(30 + (my_slime.power_charge * 7) ) || (!L.lying && always_stun) ))
my_slime.a_intent = I_DISARM // Stun them first.
else if(my_slime.can_consume(L) && L.lying)
my_slime.a_intent = I_GRAB // Then eat them.
else
my_slime.a_intent = I_HURT // Otherwise robust them.
/datum/ai_holder/simple_mob/xenobio_slime/closest_distance(atom/movable/AM)
if(istype(AM, /mob/living))
var/mob/living/L = AM
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(istype(H.species, /datum/species/monkey))
return 1 // Otherwise ranged slimes will eat a lot less often.
if(L.stat >= UNCONSCIOUS)
return 1 // Melee (eat) the target if dead/dying, don't shoot it.
return ..()
/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM)
. = ..()
if(.) // Do some additional checks because we have Special Code(tm).
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too.
return TRUE // Monkeys are always food (sorry Pun Pun).
else if(H.species && H.species.name == SPECIES_PROMETHEAN)
return FALSE // Prometheans are always our friends.
if(discipline && !rabid)
return FALSE // We're a good slime.
// Commands, reactions, etc
/datum/ai_holder/simple_mob/xenobio_slime/on_hear_say(mob/living/speaker, message)
ai_log("xenobio_slime/on_hear_say([speaker], [message]) : Entered.", AI_LOG_DEBUG)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if((findtext(message, num2text(my_slime.number)) || findtext(message, my_slime.name) || findtext(message, "slimes"))) // Talking to us.
// First, make sure it's actually a player saying something and not an AI, or else we risk infinite loops.
if(!speaker.client)
return
// Are all slimes being referred to?
// var/mass_order = FALSE
// if(findtext(message, "slimes"))
// mass_order = TRUE
// Say hello back.
if(findtext(message, "hello") || findtext(message, "hi") || findtext(message, "greetings"))
delayed_say(pick("Hello...", "Hi..."), speaker)
// Follow request.
if(findtext(message, "follow") || findtext(message, "come with me"))
if(!can_command(speaker))
delayed_say(pick("No...", "I won't follow..."), speaker)
return
delayed_say("Yes... I follow \the [speaker]...", speaker)
set_follow(speaker)
// Squish request.
if(findtext(message , "squish"))
if(!can_command(speaker))
delayed_say("No...", speaker)
return
spawn(rand(1 SECOND, 2 SECONDS))
if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime.
return
my_slime.squish()
// Stop request.
if(findtext(message, "stop") || findtext(message, "halt") || findtext(message, "cease"))
if(my_slime.victim) // We're being asked to stop eatting someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE)
my_slime.stop_consumption()
if(target) // We're being asked to stop chasing someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified.
lost_target()
if(leader) // We're being asked to stop following someone.
if(can_command(speaker) == SLIME_COMMAND_FRIEND || leader == speaker)
delayed_say("Yes... I'll stop...", speaker)
lose_follow()
else
delayed_say("No... I'll keep following \the [leader]...", speaker)
/* // Commented out since its mostly useless now due to slimes refusing to attack if it would make them naughty.
// Murder request
if(findtext(message, "harm") || findtext(message, "attack") || findtext(message, "kill") || findtext(message, "murder") || findtext(message, "eat") || findtext(message, "consume") || findtext(message, "absorb"))
if(can_command(speaker) < SLIME_COMMAND_FACTION)
delayed_say("No...", speaker)
return
for(var/mob/living/L in view(7, my_slime) - list(my_slime, speaker))
if(L == src)
continue // Don't target ourselves.
var/list/valid_names = splittext(L.name, " ") // Should output list("John", "Doe") as an example.
for(var/line in valid_names) // Check each part of someone's name.
if(findtext(message, lowertext(line))) // If part of someone's name is in the command, the slime targets them if allowed to.
if(!(mass_order && line == "slime")) //don't think random other slimes are target
if(can_attack(L))
delayed_say("Okay... I attack \the [L]...", speaker)
give_target(L)
return
else
delayed_say("No... I won't attack \the [L].", speaker)
return
// If we're here, it couldn't find anyone with that name.
delayed_say("No... I don't know who to attack...", speaker)
*/
ai_log("xenobio_slime/on_hear_say() : Exited.", AI_LOG_DEBUG)
/datum/ai_holder/simple_mob/xenobio_slime/can_violently_breakthrough()
if(discipline && !rabid) // Good slimes don't shatter the windows because their buddy in an adjacent cell decided to piss off Slimesky.
return FALSE
// Specialized AI for slime simplemobs.
// Unlike the parent AI code, this will probably break a lot of things if you put it on something that isn't /mob/living/simple_mob/slime/xenobio
/datum/ai_holder/simple_mob/xenobio_slime
hostile = TRUE
cooperative = TRUE
firing_lanes = TRUE
mauling = TRUE // They need it to get the most out of monkeys.
var/rabid = FALSE // Will attack regardless of discipline.
var/discipline = 0 // Beating slimes makes them less likely to lash out. In theory.
var/resentment = 0 // 'Unjustified' beatings make this go up, and makes it more likely for abused slimes to go rabid.
var/obedience = 0 // Conversely, 'justified' beatings make this go up, and makes discipline decay slower, potentially making it not decay at all.
var/always_stun = FALSE // If true, the slime will elect to attempt to permastun the target.
var/last_discipline_decay = null // Last world.time discipline was reduced from decay.
var/discipline_decay_time = 5 SECONDS // Earliest that one discipline can decay.
/datum/ai_holder/simple_mob/xenobio_slime/sapphire
always_stun = TRUE // They know that stuns are godly.
intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death.
/datum/ai_holder/simple_mob/xenobio_slime/light_pink
discipline = 10
obedience = 10
/datum/ai_holder/simple_mob/xenobio_slime/passive/New() // For Kendrick.
..()
pacify()
/datum/ai_holder/simple_mob/xenobio_slime/New()
..()
ASSERT(istype(holder, /mob/living/simple_mob/slime/xenobio))
// Checks if disciplining the slime would be 'justified' right now.
/datum/ai_holder/simple_mob/xenobio_slime/proc/is_justified_to_discipline()
if(!can_act())
return FALSE // The slime considers it abuse if they get stunned while already stunned.
if(rabid)
return TRUE
if(target && can_attack(target))
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(istype(H.species, /datum/species/monkey))
return FALSE // Attacking monkeys is okay.
return TRUE // Otherwise attacking other things is bad.
return FALSE // Not attacking anything.
/datum/ai_holder/simple_mob/xenobio_slime/proc/can_command(mob/living/commander)
if(rabid)
return FALSE
if(!hostile)
return SLIME_COMMAND_OBEY
// if(commander in friends)
// return SLIME_COMMAND_FRIEND
if(holder.IIsAlly(commander))
return SLIME_COMMAND_FACTION
if(discipline > resentment && obedience >= 5)
return SLIME_COMMAND_OBEY
return FALSE
/datum/ai_holder/simple_mob/xenobio_slime/proc/adjust_discipline(amount, silent)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(amount > 0)
if(rabid)
return
var/justified = is_justified_to_discipline()
lost_target() // Stop attacking.
if(justified)
obedience++
if(!silent)
holder.say(pick("Fine...", "Okay...", "Sorry...", "I yield...", "Mercy..."))
else
if(prob(resentment * 20))
enrage()
holder.say(pick("Evil...", "Kill...", "Tyrant..."))
else
if(!silent)
holder.say(pick("Why...?", "I don't understand...?", "Cruel...", "Stop...", "Nooo..."))
resentment++ // Done after check so first time will never enrage.
discipline = between(0, discipline + amount, 10)
my_slime.update_mood()
// This slime always enrages if disciplined.
/datum/ai_holder/simple_mob/xenobio_slime/red/adjust_discipline(amount, silent)
if(amount > 0 && !rabid)
holder.say("Grrr...")
holder.add_modifier(/datum/modifier/berserk, 30 SECONDS)
enrage()
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_strategical()
discipline_decay()
// Handles decay of discipline.
/datum/ai_holder/simple_mob/xenobio_slime/proc/discipline_decay()
if(discipline > 0 && last_discipline_decay + discipline_decay_time < world.time)
if(!prob(75 + (obedience * 5)))
adjust_discipline(-1)
last_discipline_decay = world.time
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_tactic()
evolve_and_reproduce()
// Hit the correct verbs to keep the slime species going.
/datum/ai_holder/simple_mob/xenobio_slime/proc/evolve_and_reproduce()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.amount_grown >= 10)
// Press the correct verb when we can.
if(my_slime.is_adult)
my_slime.reproduce() // Splits into four new baby slimes.
else
my_slime.evolve() // Turns our holder into an adult slime.
// Called when pushed too far (or a red slime core was used).
/datum/ai_holder/simple_mob/xenobio_slime/proc/enrage()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.harmless)
return
rabid = TRUE
my_slime.update_mood()
my_slime.visible_message(span("danger", "\The [src] enrages!"))
// Called when using a pacification agent (or it's Kendrick being initalized).
/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify()
lost_target() // So it stops trying to kill them.
rabid = FALSE
hostile = FALSE
retaliate = FALSE
cooperative = FALSE
// The holder's attack changes based on intent. This lets the AI choose what effect is desired.
/datum/ai_holder/simple_mob/xenobio_slime/pre_melee_attack(atom/A)
if(istype(A, /mob/living))
var/mob/living/L = A
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if( (!L.lying && prob(30 + (my_slime.power_charge * 7) ) || (!L.lying && always_stun) ))
my_slime.a_intent = I_DISARM // Stun them first.
else if(my_slime.can_consume(L) && L.lying)
my_slime.a_intent = I_GRAB // Then eat them.
else
my_slime.a_intent = I_HURT // Otherwise robust them.
/datum/ai_holder/simple_mob/xenobio_slime/closest_distance(atom/movable/AM)
if(istype(AM, /mob/living))
var/mob/living/L = AM
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(istype(H.species, /datum/species/monkey))
return 1 // Otherwise ranged slimes will eat a lot less often.
if(L.stat >= UNCONSCIOUS)
return 1 // Melee (eat) the target if dead/dying, don't shoot it.
return ..()
/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM)
. = ..()
if(.) // Do some additional checks because we have Special Code(tm).
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too.
return TRUE // Monkeys are always food (sorry Pun Pun).
else if(H.species && H.species.name == SPECIES_PROMETHEAN)
return FALSE // Prometheans are always our friends.
if(discipline && !rabid)
return FALSE // We're a good slime.
// Commands, reactions, etc
/datum/ai_holder/simple_mob/xenobio_slime/on_hear_say(mob/living/speaker, message)
ai_log("xenobio_slime/on_hear_say([speaker], [message]) : Entered.", AI_LOG_DEBUG)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if((findtext(message, num2text(my_slime.number)) || findtext(message, my_slime.name) || findtext(message, "slimes"))) // Talking to us.
// First, make sure it's actually a player saying something and not an AI, or else we risk infinite loops.
if(!speaker.client)
return
// Are all slimes being referred to?
// var/mass_order = FALSE
// if(findtext(message, "slimes"))
// mass_order = TRUE
// Say hello back.
if(findtext(message, "hello") || findtext(message, "hi") || findtext(message, "greetings"))
delayed_say(pick("Hello...", "Hi..."), speaker)
// Follow request.
if(findtext(message, "follow") || findtext(message, "come with me"))
if(!can_command(speaker))
delayed_say(pick("No...", "I won't follow..."), speaker)
return
delayed_say("Yes... I follow \the [speaker]...", speaker)
set_follow(speaker)
// Squish request.
if(findtext(message , "squish"))
if(!can_command(speaker))
delayed_say("No...", speaker)
return
spawn(rand(1 SECOND, 2 SECONDS))
if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime.
return
my_slime.squish()
// Stop request.
if(findtext(message, "stop") || findtext(message, "halt") || findtext(message, "cease"))
if(my_slime.victim) // We're being asked to stop eatting someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE)
my_slime.stop_consumption()
if(target) // We're being asked to stop chasing someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified.
lost_target()
if(leader) // We're being asked to stop following someone.
if(can_command(speaker) == SLIME_COMMAND_FRIEND || leader == speaker)
delayed_say("Yes... I'll stop...", speaker)
lose_follow()
else
delayed_say("No... I'll keep following \the [leader]...", speaker)
/* // Commented out since its mostly useless now due to slimes refusing to attack if it would make them naughty.
// Murder request
if(findtext(message, "harm") || findtext(message, "attack") || findtext(message, "kill") || findtext(message, "murder") || findtext(message, "eat") || findtext(message, "consume") || findtext(message, "absorb"))
if(can_command(speaker) < SLIME_COMMAND_FACTION)
delayed_say("No...", speaker)
return
for(var/mob/living/L in view(7, my_slime) - list(my_slime, speaker))
if(L == src)
continue // Don't target ourselves.
var/list/valid_names = splittext(L.name, " ") // Should output list("John", "Doe") as an example.
for(var/line in valid_names) // Check each part of someone's name.
if(findtext(message, lowertext(line))) // If part of someone's name is in the command, the slime targets them if allowed to.
if(!(mass_order && line == "slime")) //don't think random other slimes are target
if(can_attack(L))
delayed_say("Okay... I attack \the [L]...", speaker)
give_target(L)
return
else
delayed_say("No... I won't attack \the [L].", speaker)
return
// If we're here, it couldn't find anyone with that name.
delayed_say("No... I don't know who to attack...", speaker)
*/
ai_log("xenobio_slime/on_hear_say() : Exited.", AI_LOG_DEBUG)
/datum/ai_holder/simple_mob/xenobio_slime/can_violently_breakthrough()
if(discipline && !rabid) // Good slimes don't shatter the windows because their buddy in an adjacent cell decided to piss off Slimesky.
return FALSE
return ..()

View File

@@ -70,8 +70,10 @@
on_engagement(target)
if(firing_lanes && !test_projectile_safety(target))
// Nudge them a bit, maybe they can shoot next time.
step_rand(holder)
holder.face_atom(target)
var/turf/T = get_step(holder, pick(cardinal))
if(T)
holder.IMove(T) // IMove() will respect movement cooldown.
holder.face_atom(target)
ai_log("engage_target() : Could not safely fire at target. Exiting.", AI_LOG_DEBUG)
return
@@ -130,6 +132,9 @@
// Used to make sure projectiles will probably hit the target and not the wall or a friend.
/datum/ai_holder/proc/test_projectile_safety(atom/movable/AM)
if(holder.Adjacent(AM)) // If they're right next to us then lets just say yes. check_trajectory() tends to spaz out otherwise.
return TRUE
var/mob/living/L = check_trajectory(AM, holder) // This isn't always reliable but its better than the previous method.
// world << "Checked trajectory, would hit [L]."

View File

@@ -105,7 +105,7 @@
if(isliving(the_target))
var/mob/living/L = the_target
if(ishuman(L) || issilicon(L))
if(!L.client) // SSD players get a pass
if(L.key && !L.client) // SSD players get a pass
return FALSE
if(L.stat)
if(L.stat == DEAD) // Leave dead things alone