Addresses Ater's things.

This commit is contained in:
Neerti
2018-11-28 03:44:13 -05:00
parent 869405a16d
commit 0cf536b7de
26 changed files with 282 additions and 392 deletions
@@ -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
+1 -2
View File
@@ -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()
+1 -24
View File
@@ -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"