Basic Constructs: Artificer (#79015)

## About The Pull Request

Really getting into the meat of the constructs now. Artificers have
become basic mobs.

On the whole, this was a pretty rote conversion, with no significant
gameplay changes other than the switch to using healing hands rather
than a unique heal ability. The player experience as an artificer is
more or less identical.

The _interesting_ part comes with the AI for the seldom-used "hostile"
variant. Hostile artificers, being squishy and laughably weak, are now a
dedicated "medic" role for constructs. They will perform triage, always
seeking the most wounded construct (or shade!) to give healing to. They
will not attack at all, but they _will_ flee with great speed if
attacked and not busy healing. If they are healing another construct,
they will remain even if they are beaten to death.

I've added some more AI functionality that may come in handy in the
future, and done some refactoring to keep things from getting out of
hand:
- A planning subtree for finding targets that will always select the
most heavily wounded living target that the mob can see (or rather, the
one with the least health). Useful again for medical triage, or for
making a particularly cruel mob that always attacks whoever is easiest
to kill. I plan to use this for NPC wraith constructs when I convert
them.
- Targeting datums can now check a blackboard key to see if they should
only target wounded mobs. This is particularly useful for "medic" type
mobs such as this one.
- I've refactored the "minimum stat" behavior of targeting datums to be
stored in a blackboard key. This removes the need to have unique
subtypes for each different minimum stat we might want. Which... for the
most part, weren't even used, leading to proliferation of several
completely identical targeting datums in a bunch of different files.
Hopefully this change will make things cleaner.

In addition, this PR fixes a pair of bugs from #78807 that I didn't
catch:
- Healing constructs can now actually heal shades. Turns out I forgot to
add the correct biotype.
- Healing hands, when set to print the target's remaining health, no
longer does so as a visible message.

The one thing I didn't do that I kind of wanted to is make NPC
artificers heal themselves when wounded and not busy doing something
else, but it ended up being kind of annoying to make a mob willingly
target itself. NPC artificers never had this behavior before, so I
consider it okay, but maybe I'll circle back to it later.
## Why It's Good For The Game

Another basic conversion, another 5 items off the checklist. Very little
should change in-game, though I think the new NPC AI could make for
interesting challenges in ruins or bitrunning or something.
## Changelog
🆑
refactor: Artificer constructs have been converted to the basic mob
framework. This should change very little about them, but please report
any bugs. NPC artificers are now smarter, and will focus on healing
nearby wounded constructs - if you see them, take them out first!
/🆑
This commit is contained in:
lizardqueenlexi
2023-10-20 00:03:27 -05:00
committed by GitHub
parent 47f10ca860
commit 0c17553a96
33 changed files with 137 additions and 113 deletions

View File

@@ -111,7 +111,7 @@
/turf/open/floor/cult, /turf/open/floor/cult,
/area/shuttle/escape) /area/shuttle/escape)
"w" = ( "w" = (
/mob/living/simple_animal/hostile/construct/artificer, /mob/living/basic/construct/artificer,
/turf/open/floor/cult, /turf/open/floor/cult,
/area/shuttle/escape) /area/shuttle/escape)
"x" = ( "x" = (

View File

@@ -43,6 +43,10 @@
#define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target"
///Blackboard key for a whitelist typecache of "things we can target while trying to move" ///Blackboard key for a whitelist typecache of "things we can target while trying to move"
#define BB_OBSTACLE_TARGETTING_WHITELIST "BB_targetting_whitelist" #define BB_OBSTACLE_TARGETTING_WHITELIST "BB_targetting_whitelist"
/// Key for the minimum status at which we want to target mobs (does not need to be specified if CONSCIOUS)
#define BB_TARGET_MINIMUM_STAT "BB_target_minimum_stat"
/// Flag for whether to target only wounded mobs
#define BB_TARGET_WOUNDED_ONLY "BB_target_wounded_only"
/// Blackboard key storing how long your targetting datum has held a particular target /// Blackboard key storing how long your targetting datum has held a particular target
#define BB_BASIC_MOB_HAS_TARGET_TIME "BB_basic_mob_has_target_time" #define BB_BASIC_MOB_HAS_TARGET_TIME "BB_basic_mob_has_target_time"

View File

@@ -925,6 +925,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FISH_FED_LUBE "fish_fed_lube" #define TRAIT_FISH_FED_LUBE "fish_fed_lube"
#define TRAIT_FISH_NO_HUNGER "fish_no_hunger" #define TRAIT_FISH_NO_HUNGER "fish_no_hunger"
/// Trait given to angelic constructs to let them purge cult runes
#define TRAIT_ANGELIC "angelic"
// common trait sources // common trait sources
#define TRAIT_GENERIC "generic" #define TRAIT_GENERIC "generic"
#define UNCONSCIOUS_TRAIT "unconscious" #define UNCONSCIOUS_TRAIT "unconscious"

View File

@@ -194,3 +194,7 @@
var/datum/award/award_a = SSachievements.awards[type_a] var/datum/award/award_a = SSachievements.awards[type_a]
var/datum/award/award_b = SSachievements.awards[type_b] var/datum/award/award_b = SSachievements.awards[type_b]
return award_b?.load_priority - award_a?.load_priority return award_b?.load_priority - award_a?.load_priority
/// Orders mobs by health
/proc/cmp_mob_health(mob/living/mob_a, mob/living/mob_b)
return mob_b.health - mob_a.health

View File

@@ -0,0 +1,11 @@
/// Picks targets based on which one has the lowest health
/datum/ai_behavior/find_potential_targets/most_wounded
/datum/ai_behavior/find_potential_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets)
var/list/living_targets = list()
for(var/mob/living/living_target in filtered_targets)
living_targets += filtered_targets
if(living_targets.len)
sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health))
return living_targets[1]
return ..()

View File

@@ -0,0 +1,6 @@
/// Selects the most wounded potential target that we can see
/datum/ai_planning_subtree/simple_find_wounded_target
/datum/ai_planning_subtree/simple_find_wounded_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
controller.queue_behavior(/datum/ai_behavior/find_potential_targets/most_wounded, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)

View File

@@ -15,10 +15,12 @@
/datum/targetting_datum/basic /datum/targetting_datum/basic
/// When we do our basic faction check, do we look for exact faction matches? /// When we do our basic faction check, do we look for exact faction matches?
var/check_factions_exactly = FALSE var/check_factions_exactly = FALSE
/// Minimum status to attack living beings /// Whether we care for seeing the target or not
var/stat_attack = CONSCIOUS
///Whether we care for seeing the target or not
var/ignore_sight = FALSE var/ignore_sight = FALSE
/// Blackboard key containing the minimum stat of a living mob to target
var/minimum_stat_key = BB_TARGET_MINIMUM_STAT
/// If this blackboard key is TRUE, makes us only target wounded mobs
var/target_wounded_key
/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) /datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range)
var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller
@@ -54,7 +56,9 @@
var/mob/living/living_target = the_target var/mob/living/living_target = the_target
if(faction_check(our_controller, living_mob, living_target)) if(faction_check(our_controller, living_mob, living_target))
return FALSE return FALSE
if(living_target.stat > stat_attack) if(living_target.stat > our_controller.blackboard[minimum_stat_key])
return FALSE
if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth)
return FALSE return FALSE
return TRUE return TRUE
@@ -121,8 +125,8 @@
find_smaller = FALSE find_smaller = FALSE
inclusive = FALSE inclusive = FALSE
/datum/targetting_datum/basic/attack_until_dead /// Makes the mob only attack their own faction. Useful mostly if their attacks do something helpful (e.g. healing touch).
stat_attack = HARD_CRIT /datum/targetting_datum/basic/same_faction
/datum/targetting_datum/basic/attack_even_if_dead /datum/targetting_datum/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target)
stat_attack = DEAD return !..() // inverts logic to ONLY target mobs that share a faction

View File

@@ -159,7 +159,7 @@
if(show_health && !iscarbon(target)) if(show_health && !iscarbon(target))
var/formatted_string = format_string("%TARGET% now has <b>[target.health]/[target.maxHealth] health.</b>", healer, target) var/formatted_string = format_string("%TARGET% now has <b>[target.health]/[target.maxHealth] health.</b>", healer, target)
healer.visible_message(span_danger(formatted_string)) to_chat(healer, span_danger(formatted_string))
/// Reformats the passed string with the replacetext keys /// Reformats the passed string with the replacetext keys
/datum/component/healing_touch/proc/format_string(string, atom/source, atom/target) /datum/component/healing_touch/proc/format_string(string, atom/source, atom/target)

View File

@@ -185,11 +185,10 @@
UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED) UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED)
/datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller) /datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller)
var/datum/targetting_datum/basic/targetting = controller.blackboard[BB_TARGETTING_DATUM]
var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET]
if(QDELETED(victim)) if(QDELETED(victim))
return return
if(victim.stat > targetting.stat_attack) if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT])
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
return return
controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETTING_DATUM) controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETTING_DATUM)

View File

@@ -112,7 +112,7 @@ Runes can either be invoked by one's self or with many different cultists. Each
/obj/effect/rune/attack_animal(mob/living/simple_animal/user, list/modifiers) /obj/effect/rune/attack_animal(mob/living/simple_animal/user, list/modifiers)
if(isshade(user) || isconstruct(user)) if(isshade(user) || isconstruct(user))
if(istype(user, /mob/living/simple_animal/hostile/construct/wraith/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/juggernaut/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/artificer/angelic)) if(HAS_TRAIT(user, TRAIT_ANGELIC))
to_chat(user, span_warning("You purge the rune!")) to_chat(user, span_warning("You purge the rune!"))
qdel(src) qdel(src)
else if(construct_invoke || !IS_CULTIST(user)) //if you're not a cult construct we want the normal fail message else if(construct_invoke || !IS_CULTIST(user)) //if you're not a cult construct we want the normal fail message

View File

@@ -485,15 +485,15 @@
makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/noncult, target, creator, cultoverride, loc_override) makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/noncult, target, creator, cultoverride, loc_override)
if(CONSTRUCT_ARTIFICER) if(CONSTRUCT_ARTIFICER)
if(IS_CULTIST(creator)) if(IS_CULTIST(creator))
makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc makeNewConstruct(/mob/living/basic/construct/artificer, target, creator, cultoverride, loc_override) // ignore themes, the actual giving of cult info is in the makeNewConstruct proc
return return
switch(theme) switch(theme)
if(THEME_WIZARD) if(THEME_WIZARD)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/mystic, target, creator, cultoverride, loc_override) makeNewConstruct(/mob/living/basic/construct/artificer/mystic, target, creator, cultoverride, loc_override)
if(THEME_HOLY) if(THEME_HOLY)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/angelic, target, creator, cultoverride, loc_override) makeNewConstruct(/mob/living/basic/construct/artificer/angelic, target, creator, cultoverride, loc_override)
if(THEME_CULT) if(THEME_CULT)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/artificer/noncult, target, creator, cultoverride, loc_override) makeNewConstruct(/mob/living/basic/construct/artificer/noncult, target, creator, cultoverride, loc_override)
/proc/makeNewConstruct(mob/living/simple_animal/hostile/construct/ctype, mob/target, mob/stoner = null, cultoverride = FALSE, loc_override = null) /proc/makeNewConstruct(mob/living/simple_animal/hostile/construct/ctype, mob/target, mob/stoner = null, cultoverride = FALSE, loc_override = null)
if(QDELETED(target)) if(QDELETED(target))

View File

@@ -23,7 +23,7 @@
max_wizard_trigger_potency = 7 max_wizard_trigger_potency = 7
/datum/round_event/portal_storm/portal_storm_narsie /datum/round_event/portal_storm/portal_storm_narsie
boss_types = list(/mob/living/simple_animal/hostile/construct/artificer/hostile = 6) boss_types = list(/mob/living/basic/construct/artificer/hostile = 6)
hostile_types = list(/mob/living/simple_animal/hostile/construct/juggernaut/hostile = 8,\ hostile_types = list(/mob/living/simple_animal/hostile/construct/juggernaut/hostile = 8,\
/mob/living/simple_animal/hostile/construct/wraith/hostile = 6) /mob/living/simple_animal/hostile/construct/wraith/hostile = 6)

View File

@@ -4,7 +4,8 @@
*/ */
/datum/ai_controller/basic_controller/blobbernaut /datum/ai_controller/basic_controller/blobbernaut
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/jps ai_movement = /datum/ai_movement/jps
@@ -20,7 +21,8 @@
*/ */
/datum/ai_controller/basic_controller/blob_zombie /datum/ai_controller/basic_controller/blob_zombie
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/jps ai_movement = /datum/ai_movement/jps
@@ -37,7 +39,8 @@
*/ */
/datum/ai_controller/basic_controller/blob_spore /datum/ai_controller/basic_controller/blob_spore
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/jps ai_movement = /datum/ai_movement/jps

View File

@@ -14,6 +14,7 @@
/datum/ai_controller/basic_controller/clown/murder /datum/ai_controller/basic_controller/clown/murder
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_BASIC_MOB_SPEAK_LINES = null, BB_BASIC_MOB_SPEAK_LINES = null,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )

View File

@@ -61,6 +61,7 @@
heal_burn = 0,\ heal_burn = 0,\
heal_time = 0,\ heal_time = 0,\
valid_targets_typecache = typecacheof(list(/mob/living/basic/construct, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/shade)),\ valid_targets_typecache = typecacheof(list(/mob/living/basic/construct, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/shade)),\
valid_biotypes = MOB_MINERAL | MOB_SPIRIT,\
self_targetting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\ self_targetting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\
action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\ action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\
complete_text = "%TARGET%'s dents are repaired.",\ complete_text = "%TARGET%'s dents are repaired.",\

View File

@@ -1,4 +1,4 @@
/mob/living/simple_animal/hostile/construct/artificer /mob/living/basic/construct/artificer
name = "Artificer" name = "Artificer"
real_name = "Artificer" real_name = "Artificer"
desc = "A bulbous construct dedicated to building and maintaining the Cult of Nar'Sie's armies." desc = "A bulbous construct dedicated to building and maintaining the Cult of Nar'Sie's armies."
@@ -8,15 +8,11 @@
health = 50 health = 50
response_harm_continuous = "viciously beats" response_harm_continuous = "viciously beats"
response_harm_simple = "viciously beat" response_harm_simple = "viciously beat"
harm_intent_damage = 5
obj_damage = 60 obj_damage = 60
melee_damage_lower = 5 melee_damage_lower = 5
melee_damage_upper = 5 melee_damage_upper = 5
retreat_distance = 10
minimum_distance = 10 //AI artificers will flee like fuck
attack_verb_continuous = "rams" attack_verb_continuous = "rams"
attack_verb_simple = "ram" attack_verb_simple = "ram"
environment_smash = ENVIRONMENT_SMASH_WALLS
attack_sound = 'sound/weapons/punch2.ogg' attack_sound = 'sound/weapons/punch2.ogg'
construct_spells = list( construct_spells = list(
/datum/action/cooldown/spell/conjure/cult_floor, /datum/action/cooldown/spell/conjure/cult_floor,
@@ -33,70 +29,38 @@
can_repair = TRUE can_repair = TRUE
can_repair_self = TRUE can_repair_self = TRUE
smashes_walls = TRUE
///The health HUD applied to this mob. ///The health HUD applied to this mob.
var/health_hud = DATA_HUD_MEDICAL_ADVANCED var/health_hud = DATA_HUD_MEDICAL_ADVANCED
/mob/living/simple_animal/hostile/construct/artificer/Initialize(mapload) /mob/living/basic/construct/artificer/Initialize(mapload)
. = ..() . = ..()
AddElement(/datum/element/ai_retaliate)
var/datum/atom_hud/datahud = GLOB.huds[health_hud] var/datum/atom_hud/datahud = GLOB.huds[health_hud]
datahud.show_to(src) datahud.show_to(src)
/mob/living/simple_animal/hostile/construct/artificer/Found(atom/thing) //what have we found here? /// Hostile NPC version. Heals nearby constructs and cult structures, avoids targets that aren't extremely hurt.
if(!isconstruct(thing)) //is it a construct? /mob/living/basic/construct/artificer/hostile
return FALSE ai_controller = /datum/ai_controller/basic_controller/artificer
var/mob/living/simple_animal/hostile/construct/cultie = thing smashes_walls = FALSE
if(cultie.health < cultie.maxHealth) //is it hurt? let's go heal it if it is
return TRUE
/mob/living/simple_animal/hostile/construct/artificer/CanAttack(atom/the_target) // Alternate artificer themes
if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it /mob/living/basic/construct/artificer/angelic
return FALSE
if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible
return TRUE //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs
return FALSE
/mob/living/simple_animal/hostile/construct/artificer/MoveToTarget(list/possible_targets)
..()
if(!isliving(target))
return
var/mob/living/victim = target
if(isconstruct(victim) && victim.health >= victim.maxHealth) //is this target an unhurt construct? stop trying to heal it
LoseTarget()
return
if(victim.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you
retreat_distance = null
minimum_distance = 1
/mob/living/simple_animal/hostile/construct/artificer/Aggro()
..()
if(isconstruct(target)) //oh the target is a construct no need to flee
retreat_distance = null
minimum_distance = 1
/mob/living/simple_animal/hostile/construct/artificer/LoseAggro()
..()
retreat_distance = initial(retreat_distance)
minimum_distance = initial(minimum_distance)
/mob/living/simple_animal/hostile/construct/artificer/hostile //actually hostile, will move around, hit things, heal other constructs
AIStatus = AI_ON
environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP
/////////////////////////////Artificer-alts/////////////////////////
/mob/living/simple_animal/hostile/construct/artificer/angelic
desc = "A bulbous construct dedicated to building and maintaining holy armies." desc = "A bulbous construct dedicated to building and maintaining holy armies."
theme = THEME_HOLY theme = THEME_HOLY
loot = list(/obj/item/ectoplasm/angelic)
construct_spells = list( construct_spells = list(
/datum/action/cooldown/spell/conjure/soulstone/purified, /datum/action/cooldown/spell/conjure/soulstone/purified,
/datum/action/cooldown/spell/conjure/construct/lesser, /datum/action/cooldown/spell/conjure/construct/lesser,
/datum/action/cooldown/spell/aoe/magic_missile/lesser, /datum/action/cooldown/spell/aoe/magic_missile/lesser,
/datum/action/innate/cult/create_rune/revive, /datum/action/innate/cult/create_rune/revive,
) )
/mob/living/simple_animal/hostile/construct/artificer/mystic
/mob/living/basic/construct/artificer/angelic/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT)
/mob/living/basic/construct/artificer/mystic
theme = THEME_WIZARD theme = THEME_WIZARD
loot = list(/obj/item/ectoplasm/mystic)
construct_spells = list( construct_spells = list(
/datum/action/cooldown/spell/conjure/cult_floor, /datum/action/cooldown/spell/conjure/cult_floor,
/datum/action/cooldown/spell/conjure/cult_wall, /datum/action/cooldown/spell/conjure/cult_wall,
@@ -106,7 +70,7 @@
/datum/action/innate/cult/create_rune/revive, /datum/action/innate/cult/create_rune/revive,
) )
/mob/living/simple_animal/hostile/construct/artificer/noncult /mob/living/basic/construct/artificer/noncult
construct_spells = list( construct_spells = list(
/datum/action/cooldown/spell/conjure/cult_floor, /datum/action/cooldown/spell/conjure/cult_floor,
/datum/action/cooldown/spell/conjure/cult_wall, /datum/action/cooldown/spell/conjure/cult_wall,

View File

@@ -0,0 +1,25 @@
/// Artificers
/datum/ai_controller/basic_controller/artificer
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/same_faction/construct,
BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_WOUNDED_ONLY = TRUE,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_wounded_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/target_retaliate/to_flee,
/datum/ai_planning_subtree/flee_target/from_flee_key,
)
/// Targetting datum that will only allow mobs that constructs can heal.
/datum/targetting_datum/basic/same_faction/construct
target_wounded_key = BB_TARGET_WOUNDED_ONLY
/datum/targetting_datum/basic/same_faction/construct/can_attack(mob/living/living_mob, atom/the_target, vision_range, check_faction = TRUE)
if(isconstruct(the_target) || istype(the_target, /mob/living/simple_animal/shade))
return ..()
return FALSE

View File

@@ -1,7 +1,8 @@
/// Pretty basic, just click people to death. Also hunt and eat bananas. /// Pretty basic, just click people to death. Also hunt and eat bananas.
/datum/ai_controller/basic_controller/gorilla /datum/ai_controller/basic_controller/gorilla
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/gorilla, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
BB_EMOTE_KEY = "ooga", BB_EMOTE_KEY = "ooga",
BB_EMOTE_CHANCE = 40, BB_EMOTE_CHANCE = 40,
) )
@@ -18,9 +19,6 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/basic_melee_attack_subtree,
) )
/datum/targetting_datum/basic/allow_items/gorilla
stat_attack = UNCONSCIOUS
/datum/ai_planning_subtree/attack_obstacle_in_path/gorilla /datum/ai_planning_subtree/attack_obstacle_in_path/gorilla
attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla

View File

@@ -78,7 +78,8 @@
/datum/ai_controller/basic_controller/star_gazer /datum/ai_controller/basic_controller/star_gazer
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/star_gazer(), BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/basic/not_friends/attack_closed_turfs, BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/basic/not_friends/attack_closed_turfs,
) )
@@ -92,9 +93,6 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/basic_melee_attack_subtree,
) )
/datum/targetting_datum/basic/star_gazer
stat_attack = HARD_CRIT
/datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer /datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer
attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer

View File

@@ -1,7 +1,8 @@
#define ENRAGE_ADDITION 25 #define ENRAGE_ADDITION 25
/datum/ai_controller/basic_controller/ice_whelp /datum/ai_controller/basic_controller/ice_whelp
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
BB_WHELP_ENRAGED = 0, BB_WHELP_ENRAGED = 0,
) )

View File

@@ -3,7 +3,8 @@
*/ */
/datum/ai_controller/basic_controller/brimdemon /datum/ai_controller/basic_controller/brimdemon
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/brimdemon, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_traits = PAUSE_DURING_DO_AFTER ai_traits = PAUSE_DURING_DO_AFTER
@@ -16,9 +17,6 @@
/datum/ai_planning_subtree/targeted_mob_ability/brimbeam, /datum/ai_planning_subtree/targeted_mob_ability/brimbeam,
) )
/datum/targetting_datum/basic/brimdemon
stat_attack = HARD_CRIT
/datum/ai_planning_subtree/move_to_cardinal/brimdemon /datum/ai_planning_subtree/move_to_cardinal/brimdemon
move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon

View File

@@ -3,7 +3,8 @@
/datum/ai_controller/basic_controller/goliath /datum/ai_controller/basic_controller/goliath
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/basic_avoidance ai_movement = /datum/ai_movement/basic_avoidance
@@ -19,9 +20,6 @@
/datum/ai_planning_subtree/goliath_dig, /datum/ai_planning_subtree/goliath_dig,
) )
/datum/targetting_datum/basic/allow_items/goliath
stat_attack = HARD_CRIT
/datum/ai_planning_subtree/basic_melee_attack_subtree/goliath /datum/ai_planning_subtree/basic_melee_attack_subtree/goliath
operational_datums = list(/datum/component/ai_target_timer) operational_datums = list(/datum/component/ai_target_timer)
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath

View File

@@ -1,7 +1,8 @@
/// Keep away and launch skulls at every opportunity, prioritising injured allies /// Keep away and launch skulls at every opportunity, prioritising injured allies
/datum/ai_controller/basic_controller/legion /datum/ai_controller/basic_controller/legion
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead/legion, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/legion,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
BB_AGGRO_RANGE = 5, // Unobservant BB_AGGRO_RANGE = 5, // Unobservant
BB_BASIC_MOB_FLEE_DISTANCE = 6, BB_BASIC_MOB_FLEE_DISTANCE = 6,
) )
@@ -18,7 +19,8 @@
/// Chase and attack whatever we are targetting, if it's friendly we will heal them /// Chase and attack whatever we are targetting, if it's friendly we will heal them
/datum/ai_controller/basic_controller/legion_brood /datum/ai_controller/basic_controller/legion_brood
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead/legion, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/legion,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/basic_avoidance ai_movement = /datum/ai_movement/basic_avoidance
@@ -29,9 +31,9 @@
) )
/// Target nearby friendlies if they are hurt (and are not themselves Legions) /// Target nearby friendlies if they are hurt (and are not themselves Legions)
/datum/targetting_datum/basic/attack_until_dead/legion /datum/targetting_datum/basic/legion
/datum/targetting_datum/basic/attack_until_dead/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) /datum/targetting_datum/basic/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)) if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly))
return FALSE return FALSE
if (istype(the_target, living_mob.type)) if (istype(the_target, living_mob.type))

View File

@@ -1,6 +1,7 @@
/datum/ai_controller/basic_controller/lobstrosity /datum/ai_controller/basic_controller/lobstrosity
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/lobster, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
BB_LOBSTROSITY_EXPLOIT_TRAITS = list(TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT), BB_LOBSTROSITY_EXPLOIT_TRAITS = list(TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT),
BB_LOBSTROSITY_FINGER_LUST = 0 BB_LOBSTROSITY_FINGER_LUST = 0
) )
@@ -18,9 +19,6 @@
/datum/ai_planning_subtree/find_fingers, /datum/ai_planning_subtree/find_fingers,
) )
/datum/targetting_datum/basic/lobster
stat_attack = HARD_CRIT
/datum/ai_planning_subtree/basic_melee_attack_subtree/lobster /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster

View File

@@ -55,7 +55,8 @@
/datum/ai_controller/basic_controller/faithless /datum/ai_controller/basic_controller/faithless
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/faithless(), BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights
) )
@@ -69,6 +70,3 @@
/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures,
/datum/ai_planning_subtree/random_speech/faithless, /datum/ai_planning_subtree/random_speech/faithless,
) )
/datum/targetting_datum/basic/faithless
stat_attack = UNCONSCIOUS

View File

@@ -58,6 +58,7 @@
/datum/ai_controller/basic_controller/mushroom /datum/ai_controller/basic_controller/mushroom
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mushroom, BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mushroom,
BB_TARGET_MINIMUM_STAT = DEAD,
) )
ai_movement = /datum/ai_movement/basic_avoidance ai_movement = /datum/ai_movement/basic_avoidance
@@ -70,7 +71,6 @@
/datum/targetting_datum/basic/mushroom /datum/targetting_datum/basic/mushroom
stat_attack = DEAD
///we only attacked another mushrooms ///we only attacked another mushrooms
/datum/targetting_datum/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) /datum/targetting_datum/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)

View File

@@ -1,6 +1,7 @@
/datum/ai_controller/basic_controller/syndicate /datum/ai_controller/basic_controller/syndicate
blackboard = list( blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
) )
ai_movement = /datum/ai_movement/basic_avoidance ai_movement = /datum/ai_movement/basic_avoidance

View File

@@ -480,7 +480,7 @@
if(2) if(2)
new /mob/living/simple_animal/hostile/construct/wraith/hostile(get_turf(src)) new /mob/living/simple_animal/hostile/construct/wraith/hostile(get_turf(src))
if(3) if(3)
new /mob/living/simple_animal/hostile/construct/artificer/hostile(get_turf(src)) new /mob/living/basic/construct/artificer/hostile(get_turf(src))
if(4) if(4)
new /mob/living/simple_animal/hostile/construct/proteon/hostile(get_turf(src)) new /mob/living/simple_animal/hostile/construct/proteon/hostile(get_turf(src))
spawn_dust() spawn_dust()

View File

@@ -68,6 +68,10 @@
theme = THEME_HOLY theme = THEME_HOLY
loot = list(/obj/item/ectoplasm/angelic) loot = list(/obj/item/ectoplasm/angelic)
/mob/living/simple_animal/hostile/construct/juggernaut/angelic/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT)
/mob/living/simple_animal/hostile/construct/juggernaut/mystic /mob/living/simple_animal/hostile/construct/juggernaut/mystic
theme = THEME_WIZARD theme = THEME_WIZARD
loot = list(/obj/item/ectoplasm/mystic) loot = list(/obj/item/ectoplasm/mystic)

View File

@@ -67,6 +67,10 @@
) )
loot = list(/obj/item/ectoplasm/angelic) loot = list(/obj/item/ectoplasm/angelic)
/mob/living/simple_animal/hostile/construct/wraith/angelic/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_ANGELIC, INNATE_TRAIT)
/mob/living/simple_animal/hostile/construct/wraith/mystic /mob/living/simple_animal/hostile/construct/wraith/mystic
theme = THEME_WIZARD theme = THEME_WIZARD
construct_spells = list( construct_spells = list(

View File

@@ -67,11 +67,6 @@
/mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser,
/mob/living/simple_animal/hostile/asteroid/wolf, /mob/living/simple_animal/hostile/asteroid/wolf,
/mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/construct,
/mob/living/simple_animal/hostile/construct/artificer,
/mob/living/simple_animal/hostile/construct/artificer/angelic,
/mob/living/simple_animal/hostile/construct/artificer/hostile,
/mob/living/simple_animal/hostile/construct/artificer/mystic,
/mob/living/simple_animal/hostile/construct/artificer/noncult,
/mob/living/simple_animal/hostile/construct/juggernaut, /mob/living/simple_animal/hostile/construct/juggernaut,
/mob/living/simple_animal/hostile/construct/juggernaut/angelic, /mob/living/simple_animal/hostile/construct/juggernaut/angelic,
/mob/living/simple_animal/hostile/construct/juggernaut/hostile, /mob/living/simple_animal/hostile/construct/juggernaut/hostile,

View File

@@ -825,6 +825,7 @@
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\wounded_targetting.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm"
@@ -844,6 +845,7 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_wounded_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm"
@@ -4372,6 +4374,8 @@
#include "code\modules\mob\living\basic\clown\clown.dm" #include "code\modules\mob\living\basic\clown\clown.dm"
#include "code\modules\mob\living\basic\clown\clown_ai.dm" #include "code\modules\mob\living\basic\clown\clown_ai.dm"
#include "code\modules\mob\living\basic\constructs\_construct.dm" #include "code\modules\mob\living\basic\constructs\_construct.dm"
#include "code\modules\mob\living\basic\constructs\artificer.dm"
#include "code\modules\mob\living\basic\constructs\construct_ai.dm"
#include "code\modules\mob\living\basic\constructs\harvester.dm" #include "code\modules\mob\living\basic\constructs\harvester.dm"
#include "code\modules\mob\living\basic\farm_animals\deer.dm" #include "code\modules\mob\living\basic\farm_animals\deer.dm"
#include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm"
@@ -4758,7 +4762,6 @@
#include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm"
#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\wizard.dm"
#include "code\modules\mob\living\simple_animal\hostile\zombie.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\artificer.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm" #include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm"

View File

@@ -0,0 +1 @@
/mob/living/simple_animal/hostile/construct/artificer/@SUBTYPES : /mob/living/basic/construct/artificer/@SUBTYPES{@OLD}