mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-06-03 13:13:21 +01:00
b4989ba05b
* Unit Tests for AI Planning Subtrees not having required element/component (#77539)
## About The Pull Request
Hey there,
I've personally fallen for this stupid thing twice (in #77503 and #75627
(d3575161ca)), so I decided to spend a few
hours to crack out a unit test to ensure that I (and no one else) falls
for this stupid thing again.
Let me know if there's a smarter way to code something like this, but I
couldn't figure out a better way to accomodate the current framework and
be as agnostic to certain oddities as possible.
## Why It's Good For The Game
Catches stuff like this:
```txt
[2023-08-11 21:10:04.019] FAILURE #1: The mob Garden Gnome does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #2: The mob the morph does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #3: The mob the guard spiderling (946) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #4: The mob the ambush spiderling (255) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #5: The mob the scout spiderling (375) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #6: The mob the flesh spiderling (337) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #7: The mob the hunter spiderling (869) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #8: The mob the nurse spiderling (629) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #9: The mob the tangle spiderling (19) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #10: The mob the broodmother spiderling (855) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #11: The mob the viper spiderling (519) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #12: The mob the tarantula spiderling (963) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
- FAILURE #13: The mob the spiderling (100) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_ELEMENT, but has a planning subtree (/datum/ai_planning_subtree/target_retaliate/to_flee) that requires it! at code/modules/unit_tests/ensure_subtree_element.dm:45
```
(ignore the part about gnomes and morphs, this was an earlier version of
the unit test. everything else was relevant and is fixed)
## Changelog
🆑
fix: Growing spiders will now retaliate against you like they were
always meant to.
/🆑
* Unit Tests for AI Planning Subtrees not having required element/component
---------
Co-authored-by: san7890 <the@san7890.com>
71 lines
3.4 KiB
Plaintext
71 lines
3.4 KiB
Plaintext
/// Sets the BB target to a mob which you can see and who has recently attacked you
|
|
/datum/ai_planning_subtree/target_retaliate
|
|
operational_datums = list(/datum/element/ai_retaliate, /datum/component/ai_retaliate_advanced)
|
|
/// Blackboard key which tells us how to select valid targets
|
|
var/targetting_datum_key = BB_TARGETTING_DATUM
|
|
/// Blackboard key in which to store selected target
|
|
var/target_key = BB_BASIC_MOB_CURRENT_TARGET
|
|
/// Blackboard key in which to store selected target's hiding place
|
|
var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION
|
|
|
|
/datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
. = ..()
|
|
controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, target_key, targetting_datum_key, hiding_place_key)
|
|
|
|
/// Places a mob which you can see and who has recently attacked you into some 'run away from this' AI keys
|
|
/// Can use a different targetting datum than you use to select attack targets
|
|
/// Not required if fleeing is the only target behaviour or uses the same target datum
|
|
/datum/ai_planning_subtree/target_retaliate/to_flee
|
|
targetting_datum_key = BB_FLEE_TARGETTING_DATUM
|
|
target_key = BB_BASIC_MOB_FLEE_TARGET
|
|
hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION
|
|
|
|
/**
|
|
* Picks a target from a provided list of atoms who have been pissing you off
|
|
* You will probably need /datum/element/ai_retaliate to take advantage of this unless you're populating the blackboard yourself
|
|
*/
|
|
/datum/ai_behavior/target_from_retaliate_list
|
|
action_cooldown = 2 SECONDS
|
|
/// How far can we see stuff?
|
|
var/vision_range = 9
|
|
|
|
/datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targetting_datum_key, hiding_location_key)
|
|
. = ..()
|
|
var/mob/living/living_mob = controller.pawn
|
|
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
|
|
if(!targetting_datum)
|
|
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
|
|
|
|
var/list/enemies_list = controller.blackboard[shitlist_key]
|
|
if (!length(enemies_list))
|
|
finish_action(controller, succeeded = FALSE)
|
|
return
|
|
|
|
if (controller.blackboard[target_key] in enemies_list) // Don't bother changing
|
|
finish_action(controller, succeeded = FALSE)
|
|
return
|
|
|
|
var/atom/new_target = pick_final_target(controller, enemies_list)
|
|
controller.set_blackboard_key(target_key, new_target)
|
|
|
|
var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, new_target)
|
|
|
|
if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
|
|
controller.set_blackboard_key(hiding_location_key, potential_hiding_location)
|
|
|
|
finish_action(controller, succeeded = TRUE)
|
|
|
|
/// Returns true if this target is valid for attacking based on current conditions
|
|
/datum/ai_behavior/target_from_retaliate_list/proc/can_attack_target(mob/living/living_mob, atom/target, datum/targetting_datum/targetting_datum)
|
|
if (!target)
|
|
return FALSE
|
|
if (target == living_mob)
|
|
return FALSE
|
|
if (!can_see(living_mob, target, vision_range))
|
|
return FALSE
|
|
return targetting_datum.can_attack(living_mob, target)
|
|
|
|
/// Returns the desired final target from the filtered list of enemies
|
|
/datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list)
|
|
return pick(enemies_list)
|