mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-08-23 05:06:50 +01:00
Addresses Ater's things.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// AIs for simple mobs.
|
||||
// Base AIs for simple mobs.
|
||||
// Mob-specific AIs are in their mob's file.
|
||||
|
||||
/datum/ai_holder/simple_mob
|
||||
hostile = TRUE // The majority of simplemobs are hostile.
|
||||
@@ -9,24 +10,12 @@
|
||||
wander = TRUE
|
||||
base_wander_delay = 4
|
||||
|
||||
// For animals.
|
||||
// For non-hostile animals, and pets like Ian and Runtime.
|
||||
/datum/ai_holder/simple_mob/passive
|
||||
hostile = FALSE
|
||||
can_flee = TRUE
|
||||
violent_breakthrough = FALSE
|
||||
|
||||
// For parrots like Poly.
|
||||
// They modify their say_list datum based on what their mob hears.
|
||||
/datum/ai_holder/simple_mob/passive/parrot
|
||||
speak_chance = 2
|
||||
base_wander_delay = 8
|
||||
|
||||
/datum/ai_holder/simple_mob/passive/parrot/on_hear_say(mob/living/speaker, message)
|
||||
if(holder.stat || !holder.say_list || !message || speaker == holder)
|
||||
return
|
||||
var/datum/say_list/S = holder.say_list
|
||||
S.speak |= message
|
||||
|
||||
// Won't wander away, ideal for event-spawned mobs like carp or drones.
|
||||
/datum/ai_holder/simple_mob/event
|
||||
wander = FALSE
|
||||
@@ -45,21 +34,6 @@
|
||||
/datum/ai_holder/simple_mob/inert/astar
|
||||
use_astar = TRUE
|
||||
|
||||
// Can't attack but calls for help. Used by the monitor and spotter wards.
|
||||
// Special attacks are not blocked since they might be used for things besides attacking, and can be conditional.
|
||||
/datum/ai_holder/simple_mob/monitor
|
||||
hostile = TRUE // Required to call for help.
|
||||
cooperative = TRUE
|
||||
stand_ground = TRUE // So it doesn't run up to the thing it sees.
|
||||
wander = FALSE
|
||||
can_flee = FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/melee_attack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/ranged_attack(atom/A)
|
||||
return FALSE
|
||||
|
||||
// Ranged mobs.
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged
|
||||
@@ -107,15 +81,10 @@
|
||||
holder.IMove(get_step_towards(holder, A))
|
||||
holder.face_atom(A)
|
||||
|
||||
// The electric spider's AI.
|
||||
/datum/ai_holder/simple_mob/ranged/electric_spider
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/electric_spider/max_range(atom/movable/AM)
|
||||
if(isliving(AM))
|
||||
var/mob/living/L = AM
|
||||
if(L.incapacitated(INCAPACITATION_DISABLED) || L.stat == UNCONSCIOUS) // If our target is stunned, go in for the kill.
|
||||
return 1
|
||||
return ..() // Do ranged if possible otherwise.
|
||||
// Yakkity saxes while firing at you.
|
||||
/datum/ai_holder/hostile/ranged/robust/on_engagement(atom/movable/AM)
|
||||
step_rand(holder)
|
||||
holder.face_atom(AM)
|
||||
|
||||
// Switches intents based on specific criteria.
|
||||
// Used for special mobs who do different things based on intents (and aren't slimes).
|
||||
@@ -123,84 +92,6 @@
|
||||
/datum/ai_holder/simple_mob/intentional
|
||||
|
||||
|
||||
// The Advanced Dark Gygax's AI.
|
||||
// The mob has three special attacks, based on the current intent.
|
||||
// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax
|
||||
conserve_ammo = TRUE // Might help avoid 'I shoot the wall forever' cheese.
|
||||
var/closest_desired_distance = 1 // Otherwise run up to them to be able to potentially shock or punch them.
|
||||
|
||||
var/electric_defense_radius = 3 // How big to assume electric defense's area is.
|
||||
var/microsingulo_radius = 3 // Same but for microsingulo pull.
|
||||
var/rocket_explosive_radius = 2 // Explosion radius for the rockets.
|
||||
|
||||
var/electric_defense_threshold = 2 // How many non-targeted people are needed in close proximity before electric defense is viable.
|
||||
var/microsingulo_threshold = 2 // Similar to above, but uses an area around the target.
|
||||
|
||||
// Used to control the mob's positioning based on which special attack it has done.
|
||||
// Note that the intent will not change again until the next special attack is about to happen.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A)
|
||||
// Make the AI backpeddle if using an AoE special attack.
|
||||
var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles.
|
||||
if(holder.a_intent in risky_intents)
|
||||
var/closest_distance = 1
|
||||
switch(holder.a_intent) // Plus one just in case.
|
||||
if(I_HURT)
|
||||
closest_distance = rocket_explosive_radius + 1
|
||||
if(I_GRAB)
|
||||
closest_distance = microsingulo_radius + 1
|
||||
|
||||
if(get_dist(holder, A) <= closest_distance)
|
||||
holder.IMove(get_step_away(holder, A, closest_distance))
|
||||
|
||||
// Otherwise get up close and personal.
|
||||
else if(get_dist(holder, A) > closest_desired_distance)
|
||||
holder.IMove(get_step_towards(holder, A))
|
||||
|
||||
// Changes the mob's intent, which controls which special attack is used.
|
||||
// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/target = A
|
||||
|
||||
// If we're surrounded, Electric Defense will quickly fix that.
|
||||
var/tally = 0
|
||||
var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets.
|
||||
for(var/atom/movable/AM in potential_targets)
|
||||
if(get_dist(holder, AM) > electric_defense_radius)
|
||||
continue
|
||||
if(!can_attack(AM))
|
||||
continue
|
||||
tally++
|
||||
|
||||
// Should we shock them?
|
||||
if(tally >= electric_defense_threshold || get_dist(target, holder) <= electric_defense_radius)
|
||||
holder.a_intent = I_DISARM
|
||||
return
|
||||
|
||||
// Otherwise they're a fair distance away and we're not getting mobbed up close.
|
||||
// See if we should use missiles or microsingulo.
|
||||
tally = 0 // Let's recycle the var.
|
||||
for(var/atom/movable/AM in potential_targets)
|
||||
if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder.
|
||||
continue
|
||||
if(!can_attack(AM))
|
||||
continue
|
||||
if(AM.anchored) // Microsingulo doesn't do anything to anchored things.
|
||||
tally--
|
||||
else
|
||||
tally++
|
||||
|
||||
// Lots of people means minisingulo would be more useful.
|
||||
if(tally >= microsingulo_threshold)
|
||||
holder.a_intent = I_GRAB
|
||||
else // Otherwise use rockets.
|
||||
holder.a_intent = I_HURT
|
||||
|
||||
else
|
||||
holder.a_intent = I_HURT // Fire rockets if it's an obj/turf.
|
||||
|
||||
|
||||
// These try to avoid collateral damage.
|
||||
/datum/ai_holder/simple_mob/restrained
|
||||
violent_breakthrough = FALSE
|
||||
@@ -218,58 +109,7 @@
|
||||
holder.IMove(get_step(holder, pick(alldirs)))
|
||||
holder.face_atom(A)
|
||||
|
||||
// The AI for hooligan crabs. Follows people for awhile.
|
||||
/datum/ai_holder/simple_mob/melee/hooligan
|
||||
hostile = FALSE
|
||||
retaliate = TRUE
|
||||
returns_home = TRUE
|
||||
max_home_distance = 12
|
||||
var/random_follow = TRUE // Turn off if you want to bus with crabs.
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/hooligan/handle_stance_strategical()
|
||||
..()
|
||||
if(random_follow && stance == STANCE_IDLE && !leader)
|
||||
if(prob(10))
|
||||
for(var/mob/living/L in hearers(holder))
|
||||
if(!istype(L, holder)) // Don't follow other hooligan crabs.
|
||||
holder.visible_message("<span class='notice'>\The [holder] starts to follow \the [L].</span>")
|
||||
set_follow(L, rand(20 SECONDS, 40 SECONDS))
|
||||
|
||||
|
||||
// The AI for nurse spiders. Wraps things in webs by 'attacking' them.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider
|
||||
wander = TRUE
|
||||
base_wander_delay = 8
|
||||
cooperative = FALSE // So we don't ask our spider friends to attack things we're webbing. This might also make them stay at the base if their friends find tasty explorers.
|
||||
|
||||
// Get us unachored objects as an option as well.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets()
|
||||
. = ..()
|
||||
|
||||
var/static/alternative_targets = typecacheof(list(/obj/item, /obj/structure))
|
||||
|
||||
for(var/AT in typecache_filter_list(range(vision_range, holder), alternative_targets))
|
||||
var/obj/O = AT
|
||||
if(can_see(holder, O, vision_range) && !O.anchored)
|
||||
. += O
|
||||
|
||||
// Select an obj if no mobs are around.
|
||||
/datum/ai_holder/melee/nurse_spider/pick_target(list/targets)
|
||||
var/mobs_only = locate(/mob/living) in targets // If a mob is in the list of targets, then ignore objects.
|
||||
if(mobs_only)
|
||||
for(var/A in targets)
|
||||
if(!isliving(A))
|
||||
targets -= A
|
||||
|
||||
return ..(targets)
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/can_attack(atom/movable/the_target)
|
||||
. = ..()
|
||||
if(!.) // Parent returned FALSE.
|
||||
if(istype(the_target, /obj))
|
||||
var/obj/O = the_target
|
||||
if(!O.anchored)
|
||||
return TRUE
|
||||
|
||||
// This AI hits something, then runs away for awhile.
|
||||
// It will (almost) always flee if they are uncloaked, AND their target is not stunned.
|
||||
@@ -279,84 +119,13 @@
|
||||
// Used for the 'running' part of hit and run.
|
||||
/datum/ai_holder/simple_mob/melee/hit_and_run/special_flee_check()
|
||||
if(!holder.is_cloaked())
|
||||
if(target && isliving(target))
|
||||
if(isliving(target))
|
||||
var/mob/living/L = target
|
||||
return !L.incapacitated(INCAPACITATION_DISABLED) // Don't flee if our target is stunned in some form, even if uncloaked. This is so the mob keeps attacking a stunned opponent.
|
||||
return TRUE // We're out in the open, uncloaked, and our target isn't stunned, so lets flee.
|
||||
return FALSE
|
||||
|
||||
|
||||
// This AI isolates people it stuns with its 'leap' attack, by dragging them away.
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider
|
||||
|
||||
/*
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider/post_special_attack(mob/living/L)
|
||||
drag_away(L)
|
||||
|
||||
// Called after a successful leap.
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider/proc/drag_away(mob/living/L)
|
||||
world << "Doing drag_away attack on [L]"
|
||||
if(!istype(L))
|
||||
world << "Invalid type."
|
||||
return FALSE
|
||||
|
||||
// If they didn't get stunned, then don't bother.
|
||||
if(!L.incapacitated(INCAPACITATION_DISABLED))
|
||||
world << "Not incapcitated."
|
||||
return FALSE
|
||||
|
||||
// Grab them.
|
||||
if(!holder.start_pulling(L))
|
||||
world << "Failed to pull."
|
||||
return FALSE
|
||||
|
||||
holder.visible_message(span("danger","\The [holder] starts to drag \the [L] away!"))
|
||||
|
||||
var/list/allies = list()
|
||||
var/list/enemies = list()
|
||||
for(var/mob/living/thing in hearers(vision_range, holder))
|
||||
if(thing == holder || thing == L) // Don't count ourselves or the thing we just started pulling.
|
||||
continue
|
||||
if(holder.IIsAlly(thing))
|
||||
allies += thing
|
||||
else
|
||||
enemies += thing
|
||||
|
||||
// First priority: Move our victim to our friends.
|
||||
if(allies.len)
|
||||
world << "Going to move to ally"
|
||||
give_destination(get_turf(pick(allies)), min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
|
||||
// Second priority: Move our victim away from their friends.
|
||||
// There's a chance of it derping and pulling towards enemies if there's more than two people.
|
||||
// Preventing that will likely be both a lot of effort for developers and the CPU.
|
||||
else if(enemies.len)
|
||||
world << "Going to move away from enemies"
|
||||
var/mob/living/hostile = pick(enemies)
|
||||
var/turf/move_to = get_turf(hostile)
|
||||
for(var/i = 1 to vision_range) // Move them this many steps away from their friend.
|
||||
move_to = get_step_away(move_to, L, 7)
|
||||
if(move_to)
|
||||
give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
|
||||
// Third priority: Move our victim SOMEWHERE away from where they were.
|
||||
else
|
||||
world << "Going to move away randomly"
|
||||
var/turf/move_to = get_turf(L)
|
||||
move_to = get_step(move_to, pick(cardinal))
|
||||
for(var/i = 1 to vision_range) // Move them this many steps away from where they were before.
|
||||
move_to = get_step_away(move_to, L, 7)
|
||||
if(move_to)
|
||||
give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
*/
|
||||
|
||||
/datum/ai_holder/simple_mob/hivebot
|
||||
pointblank = TRUE
|
||||
conserve_ammo = TRUE
|
||||
firing_lanes = TRUE
|
||||
can_flee = FALSE // Fearless dumb machines.
|
||||
|
||||
// Simple mobs that aren't hostile, but will fight back.
|
||||
/datum/ai_holder/simple_mob/retaliate
|
||||
hostile = FALSE
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
/datum/ai_holder
|
||||
var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall.
|
||||
var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true.
|
||||
|
||||
var/shoot_range = 5 // How close the mob needs to be to attempt to shoot at the enemy, if the mob is capable of ranged attacks.
|
||||
var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack.
|
||||
|
||||
var/can_breakthrough = TRUE // If false, the AI will not try to open a path to its goal, like opening doors.
|
||||
var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true.
|
||||
|
||||
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
var/last_threaten_time = null // Ditto but only for threats.
|
||||
|
||||
var/speak_chance = 0 // Probability that the mob talks (this is 'X in 200' chance since even 1/100 is pretty noisy)
|
||||
var/reacts = 0 // Reacts to some things being said.
|
||||
|
||||
|
||||
/datum/ai_holder/proc/should_threaten()
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
|
||||
var/debug_ai = AI_LOG_OFF // The level of debugging information to display to people who can see log_debug().
|
||||
|
||||
/*
|
||||
#define AI_LOG_OFF 0 // Don't show anything.
|
||||
#define AI_LOG_ERROR 1 // Show logs of things likely causing the mob to not be functioning correctly.
|
||||
#define AI_LOG_WARNING 2 // Show less serious but still helpful to know issues that might be causing things to work incorrectly.
|
||||
#define AI_LOG_INFO 3 // Important regular events, like selecting a target or switching stances.
|
||||
#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution.
|
||||
#define AI_LOG_TRACE 5 // Even more detailed than the last.
|
||||
*/
|
||||
/datum/ai_holder/New()
|
||||
..()
|
||||
path_overlay = new(path_icon,path_icon_state)
|
||||
@@ -85,19 +77,7 @@
|
||||
last_turf_display = TRUE
|
||||
debug_ai = AI_LOG_INFO
|
||||
|
||||
/datum/ai_holder/test
|
||||
hostile = TRUE
|
||||
use_astar = TRUE
|
||||
|
||||
/datum/ai_holder/hostile/ranged
|
||||
// ranged = TRUE
|
||||
cooperative = TRUE
|
||||
firing_lanes = TRUE
|
||||
conserve_ammo = TRUE
|
||||
threaten = TRUE
|
||||
wander = TRUE
|
||||
|
||||
/datum/ai_holder/hostile/ranged/debug
|
||||
/datum/ai_holder/hostile/debug
|
||||
wander = FALSE
|
||||
conserve_ammo = FALSE
|
||||
intelligence_level = AI_SMART
|
||||
@@ -107,6 +87,3 @@
|
||||
last_turf_display = TRUE
|
||||
debug_ai = AI_LOG_INFO
|
||||
|
||||
/datum/ai_holder/hostile/ranged/robust/on_engagement(atom/movable/AM)
|
||||
step_rand(holder)
|
||||
holder.face_atom(AM)
|
||||
@@ -43,15 +43,14 @@
|
||||
cloaker.deactivate()
|
||||
|
||||
/mob/living/carbon/human/is_cloaked()
|
||||
if(mind && mind.changeling) // Ling camo.
|
||||
return mind.changeling.cloaked
|
||||
if(mind && mind.changeling && mind.changeling.cloaked) // Ling camo.
|
||||
return TRUE
|
||||
else if(istype(back, /obj/item/weapon/rig)) //Ninja cloak
|
||||
var/obj/item/weapon/rig/suit = back
|
||||
for(var/obj/item/rig_module/stealth_field/cloaker in suit.installed_modules)
|
||||
if(cloaker.active)
|
||||
return TRUE
|
||||
else
|
||||
return ..()
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/get_ear_protection()
|
||||
var/sum = 0
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
var/datum/gender/T = gender_datums[src.get_visible_gender()]
|
||||
to_chat(user, "<span class='notice'>\The [src] is dead, medical items won't bring [T.him] back to life.</span>") // the gender lookup is somewhat overkill, but it functions identically to the obsolete gender macros and future-proofs this code
|
||||
if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead.
|
||||
if(istype(O, /obj/item/weapon/material/knife) || istype(O, /obj/item/weapon/material/knife/butch))
|
||||
if(istype(O, /obj/item/weapon/material/knife))
|
||||
harvest(user)
|
||||
|
||||
return ..()
|
||||
|
||||
@@ -32,4 +32,14 @@
|
||||
|
||||
/obj/item/projectile/beam/stun/electric_spider
|
||||
name = "stun beam"
|
||||
agony = 20
|
||||
agony = 20
|
||||
|
||||
// The electric spider's AI.
|
||||
/datum/ai_holder/simple_mob/ranged/electric_spider
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/electric_spider/max_range(atom/movable/AM)
|
||||
if(isliving(AM))
|
||||
var/mob/living/L = AM
|
||||
if(L.incapacitated(INCAPACITATION_DISABLED) || L.stat == UNCONSCIOUS) // If our target is stunned, go in for the kill.
|
||||
return 1
|
||||
return ..() // Do ranged if possible otherwise.
|
||||
@@ -98,3 +98,68 @@
|
||||
// visible_message("<span class='warning'>\The [src] seizes \the [victim] aggressively!</span>")
|
||||
// do_attack_animation(victim)
|
||||
|
||||
|
||||
// This AI would've isolated people it stuns with its 'leap' attack, by dragging them away.
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider
|
||||
|
||||
/*
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider/post_special_attack(mob/living/L)
|
||||
drag_away(L)
|
||||
|
||||
// Called after a successful leap.
|
||||
/datum/ai_holder/simple_mob/melee/hunter_spider/proc/drag_away(mob/living/L)
|
||||
world << "Doing drag_away attack on [L]"
|
||||
if(!istype(L))
|
||||
world << "Invalid type."
|
||||
return FALSE
|
||||
|
||||
// If they didn't get stunned, then don't bother.
|
||||
if(!L.incapacitated(INCAPACITATION_DISABLED))
|
||||
world << "Not incapcitated."
|
||||
return FALSE
|
||||
|
||||
// Grab them.
|
||||
if(!holder.start_pulling(L))
|
||||
world << "Failed to pull."
|
||||
return FALSE
|
||||
|
||||
holder.visible_message(span("danger","\The [holder] starts to drag \the [L] away!"))
|
||||
|
||||
var/list/allies = list()
|
||||
var/list/enemies = list()
|
||||
for(var/mob/living/thing in hearers(vision_range, holder))
|
||||
if(thing == holder || thing == L) // Don't count ourselves or the thing we just started pulling.
|
||||
continue
|
||||
if(holder.IIsAlly(thing))
|
||||
allies += thing
|
||||
else
|
||||
enemies += thing
|
||||
|
||||
// First priority: Move our victim to our friends.
|
||||
if(allies.len)
|
||||
world << "Going to move to ally"
|
||||
give_destination(get_turf(pick(allies)), min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
|
||||
// Second priority: Move our victim away from their friends.
|
||||
// There's a chance of it derping and pulling towards enemies if there's more than two people.
|
||||
// Preventing that will likely be both a lot of effort for developers and the CPU.
|
||||
else if(enemies.len)
|
||||
world << "Going to move away from enemies"
|
||||
var/mob/living/hostile = pick(enemies)
|
||||
var/turf/move_to = get_turf(hostile)
|
||||
for(var/i = 1 to vision_range) // Move them this many steps away from their friend.
|
||||
move_to = get_step_away(move_to, L, 7)
|
||||
if(move_to)
|
||||
give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
|
||||
// Third priority: Move our victim SOMEWHERE away from where they were.
|
||||
else
|
||||
world << "Going to move away randomly"
|
||||
var/turf/move_to = get_turf(L)
|
||||
move_to = get_step(move_to, pick(cardinal))
|
||||
for(var/i = 1 to vision_range) // Move them this many steps away from where they were before.
|
||||
move_to = get_step_away(move_to, L, 7)
|
||||
if(move_to)
|
||||
give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance.
|
||||
*/
|
||||
|
||||
@@ -191,4 +191,40 @@
|
||||
desc = "Furry and beige, it makes you shudder to look at it. This one has brilliant green eyes and a tiny nurse hat."
|
||||
icon_state = "nursemed"
|
||||
icon_living = "nursemed"
|
||||
icon_dead = "nursemed_dead"
|
||||
icon_dead = "nursemed_dead"
|
||||
|
||||
|
||||
// The AI for nurse spiders. Wraps things in webs by 'attacking' them.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider
|
||||
wander = TRUE
|
||||
base_wander_delay = 8
|
||||
cooperative = FALSE // So we don't ask our spider friends to attack things we're webbing. This might also make them stay at the base if their friends find tasty explorers.
|
||||
|
||||
// Get us unachored objects as an option as well.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets()
|
||||
. = ..()
|
||||
|
||||
var/static/alternative_targets = typecacheof(list(/obj/item, /obj/structure))
|
||||
|
||||
for(var/AT in typecache_filter_list(range(vision_range, holder), alternative_targets))
|
||||
var/obj/O = AT
|
||||
if(can_see(holder, O, vision_range) && !O.anchored)
|
||||
. += O
|
||||
|
||||
// Select an obj if no mobs are around.
|
||||
/datum/ai_holder/melee/nurse_spider/pick_target(list/targets)
|
||||
var/mobs_only = locate(/mob/living) in targets // If a mob is in the list of targets, then ignore objects.
|
||||
if(mobs_only)
|
||||
for(var/A in targets)
|
||||
if(!isliving(A))
|
||||
targets -= A
|
||||
|
||||
return ..(targets)
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/can_attack(atom/movable/the_target)
|
||||
. = ..()
|
||||
if(!.) // Parent returned FALSE.
|
||||
if(istype(the_target, /obj))
|
||||
var/obj/O = the_target
|
||||
if(!O.anchored)
|
||||
return TRUE
|
||||
@@ -11,6 +11,7 @@
|
||||
// If set to a type, on initialize it will be instantiated into that type.
|
||||
var/obj/item/device/radio/headset/my_headset = null
|
||||
|
||||
// Say list
|
||||
/datum/say_list/bird/poly
|
||||
speak = list(
|
||||
"Poly wanna cracker!",
|
||||
@@ -236,4 +237,16 @@
|
||||
icon_state = "mtoo"
|
||||
icon_rest = "mtoo-held"
|
||||
icon_dead = "mtoo-dead"
|
||||
tt_desc = "E Lophochroa leadbeateri"
|
||||
tt_desc = "E Lophochroa leadbeateri"
|
||||
|
||||
|
||||
// AI
|
||||
/datum/ai_holder/simple_mob/passive/parrot
|
||||
speak_chance = 2
|
||||
base_wander_delay = 8
|
||||
|
||||
/datum/ai_holder/simple_mob/passive/parrot/on_hear_say(mob/living/speaker, message)
|
||||
if(holder.stat || !holder.say_list || !message || speaker == holder)
|
||||
return
|
||||
var/datum/say_list/S = holder.say_list
|
||||
S.speak |= message
|
||||
@@ -82,4 +82,21 @@
|
||||
L.throw_at(get_edge_target_turf(L, throwdir), 5, 1, src)
|
||||
visible_message(span("danger", "\The [src] hurls \the [L] away!"))
|
||||
else
|
||||
visible_message(span("danger", "\The [src] crushes \the [L]!"))
|
||||
visible_message(span("danger", "\The [src] crushes \the [L]!"))
|
||||
|
||||
// The AI for hooligan crabs. Follows people for awhile.
|
||||
/datum/ai_holder/simple_mob/melee/hooligan
|
||||
hostile = FALSE
|
||||
retaliate = TRUE
|
||||
returns_home = TRUE
|
||||
max_home_distance = 12
|
||||
var/random_follow = TRUE // Turn off if you want to bus with crabs.
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/hooligan/handle_stance_strategical()
|
||||
..()
|
||||
if(random_follow && stance == STANCE_IDLE && !leader)
|
||||
if(prob(10))
|
||||
for(var/mob/living/L in hearers(holder))
|
||||
if(!istype(L, holder)) // Don't follow other hooligan crabs.
|
||||
holder.visible_message("<span class='notice'>\The [holder] starts to follow \the [L].</span>")
|
||||
set_follow(L, rand(20 SECONDS, 40 SECONDS))
|
||||
@@ -45,4 +45,10 @@
|
||||
maxHealth = 1 LASERS_TO_KILL
|
||||
health = 1 LASERS_TO_KILL
|
||||
melee_damage_lower = 8
|
||||
melee_damage_upper = 8
|
||||
melee_damage_upper = 8
|
||||
|
||||
/datum/ai_holder/simple_mob/hivebot
|
||||
pointblank = TRUE
|
||||
conserve_ammo = TRUE
|
||||
firing_lanes = TRUE
|
||||
can_flee = FALSE // Fearless dumb machines.
|
||||
@@ -23,7 +23,7 @@
|
||||
for(var/mob/living/L in range(4, src))
|
||||
if(L == src)
|
||||
continue // Don't buff ourselves.
|
||||
if(L.faction == src.faction && L.isSynthetic()) // Don't buff enemies.
|
||||
if(IIsAlly(L) && L.isSynthetic()) // Don't buff enemies.
|
||||
L.add_modifier(/datum/modifier/aura/hivebot_commander_buff, null, src)
|
||||
|
||||
// Modifier added to friendly hivebots nearby.
|
||||
@@ -78,7 +78,7 @@
|
||||
for(var/mob/living/simple_mob/SM in hearers(resupply_range, src))
|
||||
if(SM == src)
|
||||
continue // We don't use charges buuuuut in case that changes in the future...
|
||||
if(SM.faction == src.faction) // Don't resupply enemies.
|
||||
if(IIsAlly(SM)) // Don't resupply enemies.
|
||||
if(!isnull(SM.special_attack_charges) && SM.special_attack_charges < initial(SM.special_attack_charges))
|
||||
SM.special_attack_charges += 1
|
||||
to_chat(SM, span("notice", "\The [src] has resupplied you, and you can use your special ability one additional time."))
|
||||
|
||||
@@ -178,3 +178,84 @@
|
||||
/obj/effect/temporary_effect/pulse/microsingulo/on_pulse()
|
||||
for(var/atom/A in range(pull_radius, src))
|
||||
A.singularity_pull(src, pull_strength)
|
||||
|
||||
|
||||
// The Advanced Dark Gygax's AI.
|
||||
// The mob has three special attacks, based on the current intent.
|
||||
// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax
|
||||
conserve_ammo = TRUE // Might help avoid 'I shoot the wall forever' cheese.
|
||||
var/closest_desired_distance = 1 // Otherwise run up to them to be able to potentially shock or punch them.
|
||||
|
||||
var/electric_defense_radius = 3 // How big to assume electric defense's area is.
|
||||
var/microsingulo_radius = 3 // Same but for microsingulo pull.
|
||||
var/rocket_explosive_radius = 2 // Explosion radius for the rockets.
|
||||
|
||||
var/electric_defense_threshold = 2 // How many non-targeted people are needed in close proximity before electric defense is viable.
|
||||
var/microsingulo_threshold = 2 // Similar to above, but uses an area around the target.
|
||||
|
||||
// Used to control the mob's positioning based on which special attack it has done.
|
||||
// Note that the intent will not change again until the next special attack is about to happen.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A)
|
||||
// Make the AI backpeddle if using an AoE special attack.
|
||||
var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles.
|
||||
if(holder.a_intent in risky_intents)
|
||||
var/closest_distance = 1
|
||||
switch(holder.a_intent) // Plus one just in case.
|
||||
if(I_HURT)
|
||||
closest_distance = rocket_explosive_radius + 1
|
||||
if(I_GRAB)
|
||||
closest_distance = microsingulo_radius + 1
|
||||
|
||||
if(get_dist(holder, A) <= closest_distance)
|
||||
holder.IMove(get_step_away(holder, A, closest_distance))
|
||||
|
||||
// Otherwise get up close and personal.
|
||||
else if(get_dist(holder, A) > closest_desired_distance)
|
||||
holder.IMove(get_step_towards(holder, A))
|
||||
|
||||
// Changes the mob's intent, which controls which special attack is used.
|
||||
// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/target = A
|
||||
|
||||
// If we're surrounded, Electric Defense will quickly fix that.
|
||||
var/tally = 0
|
||||
var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets.
|
||||
for(var/atom/movable/AM in potential_targets)
|
||||
if(get_dist(holder, AM) > electric_defense_radius)
|
||||
continue
|
||||
if(!can_attack(AM))
|
||||
continue
|
||||
tally++
|
||||
|
||||
// Should we shock them?
|
||||
if(tally >= electric_defense_threshold || get_dist(target, holder) <= electric_defense_radius)
|
||||
holder.a_intent = I_DISARM
|
||||
return
|
||||
|
||||
// Otherwise they're a fair distance away and we're not getting mobbed up close.
|
||||
// See if we should use missiles or microsingulo.
|
||||
tally = 0 // Let's recycle the var.
|
||||
for(var/atom/movable/AM in potential_targets)
|
||||
if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder.
|
||||
continue
|
||||
if(!can_attack(AM))
|
||||
continue
|
||||
if(AM.anchored) // Microsingulo doesn't do anything to anchored things.
|
||||
tally--
|
||||
else
|
||||
tally++
|
||||
|
||||
// Lots of people means minisingulo would be more useful.
|
||||
if(tally >= microsingulo_threshold)
|
||||
holder.a_intent = I_GRAB
|
||||
else // Otherwise use rockets.
|
||||
holder.a_intent = I_HURT
|
||||
|
||||
else
|
||||
if(get_dist(holder, A) >= rocket_explosive_radius + 1)
|
||||
holder.a_intent = I_HURT // Fire rockets if it's an obj/turf.
|
||||
else
|
||||
holder.a_intent = I_DISARM // Electricity might not work but it's safe up close.
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
// So players can toggle it too.
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/durand/verb/toggle_defense_mode()
|
||||
set name = "Toggle Defense Mode"
|
||||
set desc = "Toggles a special mode which makes you unable to move but become more resilient."
|
||||
set desc = "Toggles a special mode which makes you immobile and much more resilient."
|
||||
set category = "Abilities"
|
||||
|
||||
set_defense_mode(!defense_mode)
|
||||
|
||||
@@ -50,25 +50,17 @@
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/proc/detect_mobs()
|
||||
var/last_seen_mobs_len = seen_mobs.len
|
||||
var/list/things_in_sight = view(view_range, src)
|
||||
var/list/mobs_nearby = hearers(view_range, src)
|
||||
var/list/newly_seen_mobs = list()
|
||||
for(var/mob/living/L in things_in_sight)
|
||||
for(var/mob/living/L in mobs_nearby)
|
||||
if(L == src) // Don't detect ourselves.
|
||||
continue
|
||||
|
||||
if(L.stat) // Dead mobs aren't concerning.
|
||||
continue
|
||||
|
||||
if(owner)
|
||||
if(L == owner) // Don't yell at our owner for seeing them.
|
||||
continue
|
||||
|
||||
if(L.IIsAlly(owner)) // Don't yell if they're friendly to our owner.
|
||||
continue
|
||||
|
||||
else
|
||||
if(src.IIsAlly(L))
|
||||
continue
|
||||
if(src.IIsAlly(L))
|
||||
continue
|
||||
|
||||
// Decloak them .
|
||||
if(L.is_cloaked())
|
||||
@@ -84,15 +76,31 @@
|
||||
if(!(L in seen_mobs))
|
||||
seen_mobs += L
|
||||
newly_seen_mobs += L
|
||||
if(owner)
|
||||
to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] detected [english_list(newly_seen_mobs)]."))
|
||||
|
||||
if(newly_seen_mobs.len && owner) // Yell at our owner if someone new shows up.
|
||||
to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] detected [english_list(newly_seen_mobs)]."))
|
||||
|
||||
// Now get rid of old mobs that left vision.
|
||||
for(var/thing in seen_mobs)
|
||||
if(!(thing in things_in_sight))
|
||||
if(!(thing in mobs_nearby))
|
||||
seen_mobs -= thing
|
||||
|
||||
// Check if we need to update icon.
|
||||
if(seen_mobs.len != last_seen_mobs_len)
|
||||
update_icon()
|
||||
last_seen_mobs_len = seen_mobs.len
|
||||
|
||||
|
||||
// Can't attack but calls for help. Used by the monitor and spotter wards.
|
||||
// Special attacks are not blocked since they might be used for things besides attacking, and can be conditional.
|
||||
/datum/ai_holder/simple_mob/monitor
|
||||
hostile = TRUE // Required to call for help.
|
||||
cooperative = TRUE
|
||||
stand_ground = TRUE // So it doesn't run up to the thing it sees.
|
||||
wander = FALSE
|
||||
can_flee = FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/melee_attack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/ranged_attack(atom/A)
|
||||
return FALSE
|
||||
@@ -34,4 +34,9 @@
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/Destroy()
|
||||
owner = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/IIsAlly(mob/living/L)
|
||||
if(owner == L)
|
||||
return TRUE
|
||||
return ..()
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
player_msg = "You can fire an icicle projectile every two seconds. It hits hard, and armor has a hard time resisting it.<br>\
|
||||
You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.<br>\
|
||||
Unprotected enemies are also Chilled, making them slower, less evasive, and to suffer disabling effects for longer."
|
||||
Unprotected enemies are also Chilled, making them slower and less evasive, and disabling effects last longer."
|
||||
|
||||
/obj/item/projectile/icicle
|
||||
name = "icicle"
|
||||
|
||||
Reference in New Issue
Block a user