diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index a98585c1de..4a858d5cce 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -84,7 +84,7 @@
cover = 25
/obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart")
- if(!damage < STRUCTURE_MIN_DAMAGE_THRESHOLD)
+ if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD)
return 0
user.do_attack_animation(src)
visible_message("[user] [attack_message] the [src]!")
diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm
index 3a68afb5f7..bd6adfe32f 100644
--- a/code/game/turfs/simulated/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall_attacks.dm
@@ -120,7 +120,7 @@
radiate()
user.setClickCooldown(user.get_attack_speed())
var/rotting = (locate(/obj/effect/overlay/wallrot) in src)
- if(!damage < STRUCTURE_MIN_DAMAGE_THRESHOLD * 2)
+ if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD * 2)
try_touch(user, rotting)
return
diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
index ad362edb71..f2d1f05e13 100644
--- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
+++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
@@ -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("\The [holder] starts to follow \the [L].")
- 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
diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm
index ae462d5b7f..4a8fbdff69 100644
--- a/code/modules/ai/ai_holder_combat.dm
+++ b/code/modules/ai/ai_holder_combat.dm
@@ -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.
diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm
index fa576b6a8f..93267d19f4 100644
--- a/code/modules/ai/ai_holder_communication.dm
+++ b/code/modules/ai/ai_holder_communication.dm
@@ -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()
diff --git a/code/modules/ai/ai_holder_debug.dm b/code/modules/ai/ai_holder_debug.dm
index 2c8f0bc1fa..beee998af8 100644
--- a/code/modules/ai/ai_holder_debug.dm
+++ b/code/modules/ai/ai_holder_debug.dm
@@ -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)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index cd80358b31..3c9d68f68a 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -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
diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm
index d9b0d3be42..af1b2774b3 100644
--- a/code/modules/mob/living/simple_mob/defense.dm
+++ b/code/modules/mob/living/simple_mob/defense.dm
@@ -69,7 +69,7 @@
var/datum/gender/T = gender_datums[src.get_visible_gender()]
to_chat(user, "\The [src] is dead, medical items won't bring [T.him] back to life.") // 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 ..()
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm
index 63e91a1e62..c3d1f60731 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm
@@ -32,4 +32,14 @@
/obj/item/projectile/beam/stun/electric_spider
name = "stun beam"
- agony = 20
\ No newline at end of file
+ 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.
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
index f039ba6bad..f3eb1cf81e 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
@@ -98,3 +98,68 @@
// visible_message("\The [src] seizes \the [victim] aggressively!")
// 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.
+*/
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm
index 5dd460e165..62934195dd 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm
@@ -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"
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm
index ecac2d5add..bedd95e3ce 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm
@@ -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"
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm
index 9ef4f0de94..3b7f279653 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm
@@ -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]!"))
\ No newline at end of file
+ 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("\The [holder] starts to follow \the [L].")
+ set_follow(L, rand(20 SECONDS, 40 SECONDS))
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm
index cb25725b07..fe43c856f9 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm
@@ -45,4 +45,10 @@
maxHealth = 1 LASERS_TO_KILL
health = 1 LASERS_TO_KILL
melee_damage_lower = 8
- melee_damage_upper = 8
\ No newline at end of file
+ melee_damage_upper = 8
+
+/datum/ai_holder/simple_mob/hivebot
+ pointblank = TRUE
+ conserve_ammo = TRUE
+ firing_lanes = TRUE
+ can_flee = FALSE // Fearless dumb machines.
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm
index ca5712b9e2..360686d591 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm
@@ -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."))
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
index 6a5c5095ec..246b0fa1c6 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
@@ -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.
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm
index a29616a9bc..c20d623c53 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm
@@ -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)
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm
index d8349dffd3..153bc9c478 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm
@@ -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
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm
index 2fdffae0b1..0e897599ac 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm
@@ -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 ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
index 4b6fd4cfc3..8986cbcbe3 100644
--- a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
@@ -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.
\
You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.
\
- 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"
diff --git a/html/changelog.html b/html/changelog.html
index f553d99325..e926e5bfd2 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -100,7 +100,6 @@