Files
SmArtKarandGitHub 119c1e9ccc Adds a priority targeting framework, and priority targeting to mining mobs (#95720)
## About The Pull Request

Introduces a new targeting priority strategy system for basicmob AIs,
which allows controllers to decide which mob to prioritize over others.

Mining mobs will now focus on the NODE drone unless hit, and will pursue
the attacker for 25 seconds before dropping the aggro. They also get
increased aggro if you've attacked other mobs in their view recently,
and after a few hits will have enough aggro to swap to you from the NODE
drone. Ashwalkers get a reduction in aggro because they live there.
Legion broods and brimdemons will immediately target anyone who attacks
their allies rather than waiting for multiple hits. Broods also now
inherit their parent's targets and retaliation/reinforcements lists.


https://github.com/user-attachments/assets/6baaba8a-8b3c-4b2f-ae8b-842f0b1f2b6d

#### This is a bounty for ArcaneMusic

## Why It's Good For The Game

Makes vent defense mob behavior more predictable and easier for players
to manipulate, allowing them to draw aggro from the NODE drone should
make vents more engaging and less of an AI rng fest

## Changelog
🆑
add: Mining mobs now use priority when choosing their target,
prioritizing NODE drones over miners who haven't attacked them or their
allies
/🆑
2026-05-08 20:40:01 +01:00

324 lines
12 KiB
Plaintext

/**
* ## Space Carp
*
* A migratory dwarf fortress reference who swim through space and sometimes bump into the space station.
* Can be created in dehydrated form by traitors, and are also summoned through rifts by space dragons.
*
* Begin regenerating their health after a short time without taking any damage, and will try to run away to do this if they get hurt.
* Lethally attracted to loose plastic.
*
* Tameable by feeding them meat, and can follow basic instructions. Rideable.
* Owned as a pet both by the HoS (sometimes) and also the Nuclear Operatives.
*/
/mob/living/basic/carp
name = "space carp"
desc = "A ferocious, fang-bearing creature that resembles a fish."
icon = 'icons/mob/simple/carp.dmi'
icon_state = "base"
icon_living = "base"
icon_dead = "base_dead"
icon_gib = "carp_gib"
gold_core_spawnable = HOSTILE_SPAWN
mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_AQUATIC
health = 25
maxHealth = 25
max_stamina = 120
pressure_resistance = 200
combat_mode = TRUE
obj_damage = 50
melee_damage_lower = 20
melee_damage_upper = 20
attack_sound = 'sound/items/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
melee_attack_cooldown = 1.5 SECONDS
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "gently pushes aside"
response_disarm_simple = "gently push aside"
faction = list(FACTION_CARP)
butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 1)
greyscale_config = /datum/greyscale_config/carp
ai_controller = /datum/ai_controller/basic_controller/carp
initial_language_holder = /datum/language_holder/carp
habitable_atmos = null
minimum_survivable_temperature = 0
maximum_survivable_temperature = 1500
/// If true we will run away from attackers even at full health
var/cowardly = FALSE
/// Cytology cells you can swab from this creature
var/cell_line = CELL_LINE_TABLE_CARP
/// What colour is our 'healing' outline?
var/regenerate_colour = COLOR_PALE_GREEN
/// Ability which lets carp teleport around
var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/teleport
/// Information to apply when treating this carp as a vehicle
var/ridable_data = /datum/component/riding/creature/carp
/// Commands you can give this carp once it is tamed, not static because subtypes can modify it
var/tamed_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
/datum/pet_command/attack,
)
/// Carp want to eat raw meat
var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet)
/// Carp want to eat delicious six pack plastic rings
var/static/list/desired_trash = list(/obj/item/storage/cans)
/// Structures that AI carp are willing to attack. This prevents them from deconstructing supermatter cooling equipment.
var/static/list/allowed_obstacle_targets = typecacheof(list(
/obj/structure/closet,
/obj/machinery/door,
/obj/structure/door_assembly,
/obj/structure/filingcabinet,
/obj/structure/frame,
/obj/structure/grille,
/obj/structure/plasticflaps,
/obj/structure/rack,
/obj/structure/reagent_dispensers, // Carp can have a little welding fuel, as a treat
/obj/structure/table,
/obj/machinery/vending,
/obj/structure/window,
))
/datum/emote/carp
abstract_type = /datum/emote/carp
mob_type_allowed_typecache = /mob/living/basic/carp
mob_type_blacklist_typecache = list()
/datum/emote/carp/bloop
key = "bloop"
key_third_person = "bloops"
message = "bloops!"
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
/mob/living/basic/carp/Initialize(mapload, mob/tamer)
ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) //Need to set before init cause if we init in hyperspace we get dragged before the trait can be added
. = ..()
apply_colour()
add_traits(list(TRAIT_HEALS_FROM_CARP_RIFTS, TRAIT_SPACEWALK), INNATE_TRAIT)
if (cell_line)
AddElement(/datum/element/swabable, cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddElement(/datum/element/simple_flying)
if (!cowardly)
AddElement(/datum/element/ai_flee_while_injured)
setup_eating()
AddComponent(/datum/component/speechmod, replacements = strings("crustacean_replacement.json", "crustacean"))
AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("gnashes")))
AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour)
AddComponent(/datum/component/profound_fisher)
if (tamer)
tamed(tamer, feedback = FALSE)
befriend(tamer)
else
var/static/list/food_types = list(/obj/item/food/meat)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 10, bonus_tame_chance = 5)
teleport = new(src)
teleport.Grant(src)
ai_controller.set_blackboard_key(BB_CARP_RIFT, teleport)
ai_controller.set_blackboard_key(BB_OBSTACLE_TARGETING_WHITELIST, allowed_obstacle_targets)
/mob/living/basic/carp/Destroy()
QDEL_NULL(teleport)
return ..()
/// Tell the elements and the blackboard what food we want to eat
/mob/living/basic/carp/proc/setup_eating()
AddElement(/datum/element/basic_eating, food_types = desired_food)
AddElement(/datum/element/basic_eating, heal_amt = 0, damage_amount = 10, damage_type = BRUTE, food_types = desired_trash) // We are killing our planet
var/list/foods_list = desired_food + desired_trash
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(foods_list))
/// Set a random colour on the carp, override to do something else
/mob/living/basic/carp/proc/apply_colour()
if (!greyscale_config)
return
set_greyscale(colors = list(pick_weight(GLOB.carp_colors)))
/// Called when another mob has forged a bond of friendship with this one, passed the taming mob as 'tamer'
/mob/living/basic/carp/tamed(mob/living/tamer, atom/food, feedback = TRUE)
. = ..()
AddElement(/datum/element/ridable, ridable_data)
AddComponent(/datum/component/obeys_commands, tamed_commands)
if (!feedback)
return
spin(spintime = 10, speed = 1)
visible_message("[src] spins in a circle as it seems to bond with [tamer].")
/// Teleport when you right click away from you
/mob/living/basic/carp/ranged_secondary_attack(atom/atom_target, modifiers)
teleport.Trigger(target = atom_target)
/// Gives the carp a list of weakrefs of destinations to try and travel between when it has nothing better to do
/mob/living/basic/carp/proc/migrate_to(list/datum/weakref/migration_points)
ai_controller.can_idle = FALSE
ai_controller.set_ai_status(AI_STATUS_ON) // We need htem to actually walk to the station
var/list/actual_points = list()
for(var/datum/weakref/point_ref as anything in migration_points)
var/turf/point_resolved = point_ref.resolve()
if(QDELETED(point_resolved))
return // invalid list, we can't migrate to this
actual_points += point_resolved
ai_controller.set_blackboard_key(BB_CARP_MIGRATION_PATH, actual_points)
/mob/living/basic/carp/death(gibbed)
. = ..()
REMOVE_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
/mob/living/basic/carp/revive(full_heal_flags, excess_healing, force_grab_ghost)
. = ..()
ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
/**
* Holographic carp from the holodeck
*/
/mob/living/basic/carp/holographic
icon_state = "base_friend"
icon_living = "holocarp"
gold_core_spawnable = NO_SPAWN
greyscale_config = NONE
basic_mob_flags = DEL_ON_DEATH
cell_line = NONE
regenerate_colour = "#ffffff"
/mob/living/basic/carp/holographic/Initialize(mapload, mob/tamer)
. = ..()
AddComponent(/datum/component/holographic_nature)
/// Holocarp don't eat food
/mob/living/basic/carp/holographic/setup_eating()
return FALSE
/**
* Pet carp, abstract carp which just holds some shared properties.
*/
/mob/living/basic/carp/pet
speak_emote = list("squeaks")
gold_core_spawnable = NO_SPAWN
gender = FEMALE // Both current existing pet carp are female but you can remove this if someone else gets a male one?
ai_controller = /datum/ai_controller/basic_controller/carp/pet
initial_language_holder = /datum/language_holder/carp/hear_common
/mob/living/basic/carp/pet/Initialize(mapload)
. = ..()
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/pet_bonus, "bloop")
/**
* Lia - Sometimes the pet of the Head of Security.
* Has a lot more health than a normal carp because she's meant to be a mildly more threatening pet to have to assassinate than an aging corgi.
*/
/mob/living/basic/carp/pet/lia
name = "Lia"
real_name = "Lia"
desc = "A failed experiment of Nanotrasen to create weaponised carp technology. This less than intimidating carp now serves as the Head of Security's pet."
faction = list(FACTION_NEUTRAL)
maxHealth = 200
health = 200
icon_state = "magicarp"
icon_dead = "magicarp_dead"
icon_gib = "magicarp_gib"
icon_living = "magicarp"
greyscale_config = NONE
/// Boosted chance for Cayenne to be silver
#define RARE_CAYENNE_CHANCE 10
/**
* Cayenne - Loyal member of the nuclear operatives.
* Spawns in the nuke op shuttle, can be made sapient if they want to do that for some reason.
* Is very talented and also capable of holding the nuclear disk.
*/
/mob/living/basic/carp/pet/cayenne
name = "Cayenne"
real_name = "Cayenne"
desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot."
faction = list(ROLE_SYNDICATE)
/// Overlay to apply to display the disk
var/mutable_appearance/disk_overlay
/// Overlay to apply over the disk so it looks like cayenne is holding it
var/mutable_appearance/mouth_overlay
/mob/living/basic/carp/pet/cayenne/Initialize(mapload)
. = ..()
var/datum/callback/got_disk = CALLBACK(src, PROC_REF(got_disk))
var/datum/callback/display_disk = CALLBACK(src, PROC_REF(display_disk))
AddComponent(/datum/component/nuclear_bomb_operator, got_disk, display_disk)
/mob/living/basic/carp/pet/cayenne/apply_colour()
if (prob(RARE_CAYENNE_CHANCE))
set_greyscale(colors = list(COLOR_CARP_SILVER))
else
return ..()
/// She did it! Treats for Cayenne!
/mob/living/basic/carp/pet/cayenne/proc/got_disk(obj/item/disk/nuclear/disky)
if (disky.fake) // Never mind she didn't do it
return
client.give_award(/datum/award/achievement/misc/cayenne_disk, src)
/// Adds an overlay to show the disk on Cayenne
/mob/living/basic/carp/pet/cayenne/proc/display_disk(list/new_overlays)
if (!mouth_overlay)
mouth_overlay = mutable_appearance(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/carp/disk_mouth, greyscale_colors), "disk_mouth")
new_overlays += mouth_overlay
if (!disk_overlay)
disk_overlay = mutable_appearance('icons/mob/simple/carp.dmi', "disk_overlay")
new_overlays += disk_overlay
/mob/living/basic/carp/advanced
health = 40
maxHealth = 40
obj_damage = 15
#undef RARE_CAYENNE_CHANCE
///Carp-parasite from carpellosis disease
/mob/living/basic/carp/ella
name = "Ella"
real_name = "Ella"
desc = "It came out of someone."
gold_core_spawnable = NO_SPAWN
/mob/living/basic/carp/ella/Initialize(mapload)
. = ..()
death() // It comes into the world dead when the disease is cured
///Wild carp that just vibe ya know
/mob/living/basic/carp/passive
name = "false carp"
desc = "A close relative of the space carp which is entirely toothless and feeds by stealing its cousin's leftovers."
icon_state = "base_friend"
icon_living = "base_friend"
icon_dead = "base_friend_dead"
greyscale_config = /datum/greyscale_config/carp_friend
attack_verb_continuous = "suckers"
attack_verb_simple = "suck"
melee_damage_lower = 0
melee_damage_upper = 0
cowardly = TRUE
ai_controller = /datum/ai_controller/basic_controller/carp/passive
gold_core_spawnable = FRIENDLY_SPAWN
/mob/living/basic/carp/passive/Initialize(mapload)
. = ..()
AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(on_attacked)))
AddElement(/datum/element/pet_bonus, "bloop")
ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT)
/// If someone slaps one of the school, scatter
/mob/living/basic/carp/passive/proc/on_attacked(mob/living/attacker)
for(var/mob/living/basic/carp/passive/schoolmate in oview(src, 9))
schoolmate.ai_controller?.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker, world.time)