Skip to content

Commit

Permalink
[MIRROR] Basic Constructs: Artificer [MDB IGNORE] (#24456)
Browse files Browse the repository at this point in the history
* Basic Constructs: Artificer (#79015)

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.

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.
:cl:
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!
/:cl:

* Basic Constructs: Artificer

* Modular

* Modular paths

* Modular paths

---------

Co-authored-by: lizardqueenlexi <[email protected]>
Co-authored-by: Giz <[email protected]>
  • Loading branch information
3 people authored and FFMirrorBot committed Oct 20, 2023
1 parent dc68835 commit 9c1a8e8
Show file tree
Hide file tree
Showing 37 changed files with 163 additions and 136 deletions.
10 changes: 5 additions & 5 deletions _maps/RandomRuins/SpaceRuins/medieval1.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
/turf/open/floor/wood/parquet,
/area/ruin/space)
"eo" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
dir = 1
},
/turf/open/floor/stone,
/area/ruin/space)
"gv" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile,
/mob/living/basic/construct/artificer/hostile,
/turf/open/space/basic,
/area/ruin/space)
"hi" = (
Expand All @@ -37,7 +37,7 @@
/turf/open/space/basic,
/area/ruin/space)
"jy" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
dir = 4
},
/turf/open/misc/asteroid/basalt/wasteland,
Expand Down Expand Up @@ -92,7 +92,7 @@
/turf/open/floor/stone,
/area/ruin/space)
"tj" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
dir = 4
},
/turf/open/space/basic,
Expand Down Expand Up @@ -276,7 +276,7 @@
/turf/open/floor/wood/parquet,
/area/ruin/space)
"XM" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
dir = 8
},
/turf/open/space/basic,
Expand Down
14 changes: 7 additions & 7 deletions _maps/RandomRuins/SpaceRuins/skyrat/port_tarkon/defcon2.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,7 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/machinery/light/dim/directional/east,
/obj/effect/turf_decal/delivery/blue,
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
health = 150;
maxHealth = 150
},
Expand Down Expand Up @@ -1263,7 +1263,7 @@
/turf/open/misc/asteroid/airless,
/area/solars/tarkon)
"he" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile,
/mob/living/basic/construct/artificer/hostile,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/iron,
/area/ruin/space/has_grav/port_tarkon/porthall)
Expand Down Expand Up @@ -3940,7 +3940,7 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/effect/decal/cleanable/glass,
/obj/effect/decal/cleanable/blood,
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
health = 200;
maxHealth = 200;
name = "Third eye of the veil"
Expand Down Expand Up @@ -4125,7 +4125,7 @@
/turf/open/floor/engine/co2,
/area/ruin/space/has_grav/port_tarkon/atmos)
"xo" = (
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
health = 150;
maxHealth = 150
},
Expand Down Expand Up @@ -4698,7 +4698,7 @@
/area/ruin/space/has_grav/port_tarkon/starboardhall)
"AI" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
health = 150;
maxHealth = 150
},
Expand Down Expand Up @@ -8632,7 +8632,7 @@
"Wq" = (
/obj/structure/cable,
/obj/effect/decal/cleanable/blood,
/mob/living/simple_animal/hostile/construct/artificer/hostile,
/mob/living/basic/construct/artificer/hostile,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/iron,
/area/ruin/space/has_grav/port_tarkon/cargo)
Expand Down Expand Up @@ -8772,7 +8772,7 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
/mob/living/simple_animal/hostile/construct/artificer/hostile{
/mob/living/basic/construct/artificer/hostile{
health = 150;
maxHealth = 150
},
Expand Down
2 changes: 1 addition & 1 deletion _maps/shuttles/emergency_narnar.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
/turf/open/floor/cult,
/area/shuttle/escape)
"w" = (
/mob/living/simple_animal/hostile/construct/artificer,
/mob/living/basic/construct/artificer,
/turf/open/floor/cult,
/area/shuttle/escape)
"x" = (
Expand Down
4 changes: 4 additions & 0 deletions code/__DEFINES/ai/ai_blackboard.dm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#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"
#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
#define BB_BASIC_MOB_HAS_TARGET_TIME "BB_basic_mob_has_target_time"
Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,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_NO_HUNGER "fish_no_hunger"

/// Trait given to angelic constructs to let them purge cult runes
#define TRAIT_ANGELIC "angelic"

// common trait sources
#define TRAIT_GENERIC "generic"
#define UNCONSCIOUS_TRAIT "unconscious"
Expand Down
4 changes: 4 additions & 0 deletions code/__HELPERS/cmp.dm
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@
var/datum/award/award_a = SSachievements.awards[type_a]
var/datum/award/award_b = SSachievements.awards[type_b]
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
11 changes: 11 additions & 0 deletions code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targetting.dm
Original file line number Diff line number Diff line change
@@ -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 ..()
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
/datum/targetting_datum/basic
/// When we do our basic faction check, do we look for exact faction matches?
var/check_factions_exactly = FALSE
/// Minimum status to attack living beings
var/stat_attack = CONSCIOUS
///Whether we care for seeing the target or not
/// Whether we care for seeing the target or not
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)
var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller
Expand Down Expand Up @@ -54,7 +56,9 @@
var/mob/living/living_target = the_target
if(faction_check(our_controller, living_mob, living_target))
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 TRUE
Expand Down Expand Up @@ -121,8 +125,8 @@
find_smaller = FALSE
inclusive = FALSE

/datum/targetting_datum/basic/attack_until_dead
stat_attack = HARD_CRIT
/// Makes the mob only attack their own faction. Useful mostly if their attacks do something helpful (e.g. healing touch).
/datum/targetting_datum/basic/same_faction

/datum/targetting_datum/basic/attack_even_if_dead
stat_attack = DEAD
/datum/targetting_datum/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target)
return !..() // inverts logic to ONLY target mobs that share a faction
2 changes: 1 addition & 1 deletion code/datums/components/healing_touch.dm
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@

if(show_health && !iscarbon(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
/datum/component/healing_touch/proc/format_string(string, atom/source, atom/target)
Expand Down
3 changes: 1 addition & 2 deletions code/datums/components/pet_commands/pet_commands_basic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,10 @@
UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED)

/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]
if(QDELETED(victim))
return
if(victim.stat > targetting.stat_attack)
if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT])
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
return
controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETTING_DATUM)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/cult/runes.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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!"))
qdel(src)
else if(construct_invoke || !IS_CULTIST(user)) //if you're not a cult construct we want the normal fail message
Expand Down
8 changes: 4 additions & 4 deletions code/modules/antagonists/wizard/equipment/soulstone.dm
Original file line number Diff line number Diff line change
Expand Up @@ -490,15 +490,15 @@
makeNewConstruct(/mob/living/simple_animal/hostile/construct/wraith/noncult, target, creator, cultoverride, loc_override)
if(CONSTRUCT_ARTIFICER)
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
switch(theme)
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)
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)
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)
if(QDELETED(target))
Expand Down
2 changes: 1 addition & 1 deletion code/modules/events/portal_storm.dm
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
max_wizard_trigger_potency = 7

/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,\
/mob/living/simple_animal/hostile/construct/wraith/hostile = 6)

Expand Down
9 changes: 6 additions & 3 deletions code/modules/mob/living/basic/blob_minions/blob_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*/
/datum/ai_controller/basic_controller/blobbernaut
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
Expand All @@ -20,7 +21,8 @@
*/
/datum/ai_controller/basic_controller/blob_zombie
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
Expand All @@ -37,7 +39,8 @@
*/
/datum/ai_controller/basic_controller/blob_spore
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
Expand Down
3 changes: 2 additions & 1 deletion code/modules/mob/living/basic/clown/clown_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

/datum/ai_controller/basic_controller/clown/murder
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_TARGET_MINIMUM_STAT = HARD_CRIT,
)
1 change: 1 addition & 0 deletions code/modules/mob/living/basic/constructs/_construct.dm
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
heal_burn = 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_biotypes = MOB_MINERAL | MOB_SPIRIT,\
self_targetting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\
action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\
complete_text = "%TARGET%'s dents are repaired.",\
Expand Down
Loading

0 comments on commit 9c1a8e8

Please sign in to comment.