mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-13 00:55:20 +01:00
31d4eb04f1
## About The Pull Request Two main things. Multiple instances of use of a proc in AI controllers seemingly assuming the default behaviour will work for them, but what ends up happening is `search_tactic` gets redefined and redefined with no defaut search range parameter, so nothing ends up passed to the `search_tactic` child procs, so they all call `oview` with `null` and this... somehow doesn't runtime? Has behaviour that works some of the time??? I hate this fucking language. Anyway. Stoat steal items behaviour was completely broken and apparently was not tested once since it was merged in. I've made the corrections I can, but I haven't figured out why stoat AI never enters idle, so we have a behaviour that leads to the stoat running up to an item, grabbing it, and then just staying there, unmoving. I've sunk too many hours into this, I'm just going to call it fixed and let someone else figure out what exciting additions there need to be to a behaviour that was never functional in the first place. ## Why It's Good For The Game i don't know man i just want the pain to stop okay, generally speaking, when people write AI behaviours, they want those AI behaviours to do something and not just silently fail for six months or longer ## Changelog 🆑 fix: Stoats have a chance to try and grab items like they always should have. /🆑
364 lines
16 KiB
Plaintext
364 lines
16 KiB
Plaintext
/datum/ai_controller/basic_controller/minebot
|
|
blackboard = list(
|
|
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
|
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
|
|
BB_BASIC_MOB_FLEE_DISTANCE = 3,
|
|
BB_MINIMUM_SHOOTING_DISTANCE = 3,
|
|
BB_MINEBOT_PLANT_MINES = TRUE,
|
|
BB_MINEBOT_REPAIR_DRONE = TRUE,
|
|
BB_MINEBOT_AUTO_DEFEND = TRUE,
|
|
BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite),
|
|
BB_AUTOMATED_MINING = FALSE,
|
|
BB_OWNER_SELF_HARM_RESPONSES = list(
|
|
"Please stop hurting yourself.",
|
|
"There is no need to do that.",
|
|
"Your actions are illogical.",
|
|
"Please make better choices.",
|
|
"Remember, you have beaten your worst days before."
|
|
)
|
|
)
|
|
|
|
ai_movement = /datum/ai_movement/basic_avoidance
|
|
idle_behavior = /datum/idle_behavior/idle_random_walk
|
|
planning_subtrees = list(
|
|
/datum/ai_planning_subtree/simple_find_target,
|
|
/datum/ai_planning_subtree/launch_missiles,
|
|
/datum/ai_planning_subtree/pet_planning,
|
|
/datum/ai_planning_subtree/befriend_miners,
|
|
/datum/ai_planning_subtree/defend_node,
|
|
/datum/ai_planning_subtree/minebot_maintain_distance,
|
|
/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot,
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot,
|
|
/datum/ai_planning_subtree/minebot_mining,
|
|
/datum/ai_planning_subtree/locate_dead_humans,
|
|
)
|
|
|
|
/datum/ai_planning_subtree/launch_missiles
|
|
|
|
/datum/ai_planning_subtree/launch_missiles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/datum/action/cooldown/missile_ability = controller.blackboard[BB_MINEBOT_MISSILE_ABILITY]
|
|
if(!missile_ability?.IsAvailable())
|
|
return
|
|
if(!controller.blackboard_key_exists(BB_MINEBOT_MISSILE_TARGET))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/clear_bombing_zone, BB_MINEBOT_MISSILE_TARGET, /obj/effect/temp_visual/minebot_target, 7)
|
|
return
|
|
controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, BB_MINEBOT_MISSILE_ABILITY, BB_MINEBOT_MISSILE_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/ai_behavior/find_and_set/clear_bombing_zone
|
|
|
|
/datum/ai_behavior/find_and_set/clear_bombing_zone/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
for(var/obj/effect/temp_visual/minebot_target/target in oview(search_range, controller.pawn))
|
|
if(isclosedturf(get_turf(target)))
|
|
continue
|
|
return target
|
|
return null
|
|
|
|
/datum/ai_planning_subtree/befriend_miners/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
if(!controller.blackboard_key_exists(BB_MINER_FRIEND))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/miner_to_befriend, BB_MINER_FRIEND)
|
|
return
|
|
controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_MINER_FRIEND)
|
|
|
|
/datum/ai_behavior/find_and_set/miner_to_befriend
|
|
|
|
/datum/ai_behavior/find_and_set/miner_to_befriend/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn))
|
|
if(HAS_TRAIT(target, TRAIT_ROCK_STONER))
|
|
return target
|
|
return null
|
|
|
|
/datum/ai_planning_subtree/defend_node/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/mob/living/target = controller.blackboard[BB_DRONE_DEFEND]
|
|
if(QDELETED(target))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_DRONE_DEFEND, /mob/living/basic/node_drone)
|
|
return
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(!living_pawn.faction.Find(REF(target)))
|
|
controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_DRONE_DEFEND)
|
|
return
|
|
if(target.health < (target.maxHealth * 0.75) && controller.blackboard[BB_MINEBOT_REPAIR_DRONE])
|
|
controller.queue_behavior(/datum/ai_behavior/repair_drone, BB_DRONE_DEFEND)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/ai_behavior/repair_drone
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
|
|
|
/datum/ai_behavior/repair_drone/setup(datum/ai_controller/controller, target_key)
|
|
. = ..()
|
|
var/turf/target = controller.blackboard[target_key]
|
|
if(isnull(target))
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/repair_drone/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
|
var/atom/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
var/mob/living/living_pawn = controller.pawn
|
|
living_pawn.say("REPAIRING [target]!")
|
|
controller.ai_interact(target = target)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
/datum/ai_behavior/repair_drone/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
if(!success)
|
|
controller.clear_blackboard_key(target_key)
|
|
|
|
///find dead humans and report their location on the radio
|
|
/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER))
|
|
controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human)
|
|
|
|
/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn))
|
|
if(target.stat >= UNCONSCIOUS && target.mind)
|
|
return target
|
|
return null
|
|
|
|
/datum/ai_behavior/send_sos_message
|
|
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
|
action_cooldown = 2 MINUTES
|
|
|
|
/datum/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
|
var/mob/living/carbon/target = controller.blackboard[target_key]
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(QDELETED(target) || is_station_level(target.z))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
var/turf/target_turf = get_turf(target)
|
|
var/obj/item/implant/radio/radio_implant = locate(/obj/item/implant/radio) in living_pawn.contents
|
|
if(!radio_implant)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
var/message = "ALERT, [target] in need of help at coordinates: [target_turf.x], [target_turf.y], [target_turf.z]!"
|
|
radio_implant.radio.talk_into(living_pawn, message, RADIO_CHANNEL_SUPPLY)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
/datum/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
controller.clear_blackboard_key(target_key)
|
|
|
|
///operational datums is null because we dont use a ranged component, we use a gun in our contents
|
|
/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot
|
|
operational_datums = null
|
|
ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/minebot
|
|
|
|
/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
|
|
if(QDELETED(target))
|
|
return
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(!living_pawn.combat_mode) //we are not on attack mode
|
|
return
|
|
controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/ai_planning_subtree/minebot_maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
|
|
if(QDELETED(target))
|
|
return
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(get_dist(living_pawn, target) <= controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE])
|
|
controller.queue_behavior(/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot, BB_BASIC_MOB_CURRENT_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot
|
|
|
|
/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key)
|
|
if(!controller.blackboard[BB_MINEBOT_PLANT_MINES])
|
|
return ..()
|
|
var/datum/action/cooldown/mine_ability = controller.blackboard[BB_MINEBOT_LANDMINE_ABILITY]
|
|
mine_ability?.Trigger()
|
|
return ..()
|
|
|
|
/datum/ai_behavior/basic_ranged_attack/minebot
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
|
avoid_friendly_fire = TRUE
|
|
///if our target is closer than this distance, finish action
|
|
var/minimum_distance = 3
|
|
|
|
/datum/ai_behavior/basic_ranged_attack/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
|
|
. = ..()
|
|
minimum_distance = controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] ? controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] : initial(minimum_distance)
|
|
var/atom/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(get_dist(living_pawn, target) <= minimum_distance)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
///mine walls if we are on automated mining mode
|
|
/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
if(!controller.blackboard[BB_AUTOMATED_MINING])
|
|
return
|
|
if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF))
|
|
controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF)
|
|
|
|
/datum/ai_behavior/find_mineral_wall/minebot
|
|
|
|
/datum/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall)
|
|
var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS]
|
|
var/turf/previous_unreachable_wall = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL]
|
|
if(is_type_in_list(target_wall, forbidden_turfs) || target_wall == previous_unreachable_wall)
|
|
return FALSE
|
|
controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL)
|
|
return ..()
|
|
|
|
/datum/ai_behavior/minebot_mine_turf
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
|
required_distance = 2
|
|
action_cooldown = 3 SECONDS
|
|
|
|
/datum/ai_behavior/minebot_mine_turf/setup(datum/ai_controller/controller, target_key)
|
|
. = ..()
|
|
var/turf/target = controller.blackboard[target_key]
|
|
if(isnull(target))
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
|
var/mob/living/basic/living_pawn = controller.pawn
|
|
var/turf/target = controller.blackboard[target_key]
|
|
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
if(check_obstacles_in_path(controller, target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
if(!living_pawn.combat_mode)
|
|
living_pawn.set_combat_mode(TRUE)
|
|
|
|
living_pawn.RangedAttack(target)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
/datum/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target)
|
|
var/mob/living/source = controller.pawn
|
|
var/list/turfs_in_path = get_line(source, target) - target
|
|
for(var/turf/turf in turfs_in_path)
|
|
if(turf.is_blocked_turf(ignore_atoms = list(source)))
|
|
controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/datum/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
controller.clear_blackboard_key(target_key)
|
|
|
|
///store ores in our body
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot
|
|
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot
|
|
hunt_chance = 100
|
|
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING]
|
|
var/mob/living/living_pawn = controller.pawn
|
|
|
|
if(!automated_mining && living_pawn.combat_mode) //are we not on automated mining or collect mode?
|
|
return
|
|
|
|
return ..()
|
|
|
|
/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot
|
|
hunt_cooldown = 2 SECONDS
|
|
|
|
/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot/target_caught(mob/living/hunter, obj/item/stack/ore/hunted)
|
|
if(hunter.combat_mode)
|
|
hunter.set_combat_mode(FALSE)
|
|
return ..()
|
|
|
|
///pet commands
|
|
/datum/pet_command/free/minebot
|
|
|
|
/datum/pet_command/free/minebot/execute_action(datum/ai_controller/controller)
|
|
controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE)
|
|
return ..()
|
|
|
|
/datum/pet_command/automate_mining
|
|
command_name = "Automate mining"
|
|
command_desc = "Make your minebot automatically mine!"
|
|
radial_icon_state = "mine"
|
|
speech_commands = list("mine")
|
|
callout_type = /datum/callout_option/mine
|
|
|
|
/datum/pet_command/automate_mining/valid_callout_target(mob/living/speaker, datum/callout_option/callout, atom/target)
|
|
return ismineralturf(target)
|
|
|
|
/datum/pet_command/automate_mining/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to start mining!"
|
|
|
|
/datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller)
|
|
controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
|
|
/datum/pet_command/minebot_ability
|
|
command_name = "Minebot ability"
|
|
command_desc = "Make your minebot use one of its abilities."
|
|
radial_icon = 'icons/mob/actions/actions_mecha.dmi'
|
|
///the ability we will use
|
|
var/ability_key
|
|
|
|
/datum/pet_command/minebot_ability/execute_action(datum/ai_controller/controller)
|
|
var/datum/action/cooldown/ability = controller.blackboard[ability_key]
|
|
if(!ability?.IsAvailable())
|
|
return
|
|
controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/minebot_ability/light
|
|
command_name = "Toggle lights"
|
|
command_desc = "Make your minebot toggle its lights."
|
|
speech_commands = list("light")
|
|
radial_icon_state = "mech_lights_off"
|
|
ability_key = BB_MINEBOT_LIGHT_ABILITY
|
|
|
|
/datum/pet_command/minebot_ability/light/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to toggle its lights!"
|
|
|
|
/datum/pet_command/minebot_ability/dump
|
|
command_name = "Dump ore"
|
|
command_desc = "Make your minebot dump all its ore!"
|
|
speech_commands = list("dump", "ore")
|
|
radial_icon_state = "mech_eject"
|
|
ability_key = BB_MINEBOT_DUMP_ABILITY
|
|
|
|
/datum/pet_command/minebot_ability/dump/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to dump its ore!"
|
|
|
|
/datum/pet_command/attack/minebot
|
|
attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot
|
|
|
|
/datum/pet_command/attack/minebot/execute_action(datum/ai_controller/controller)
|
|
controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE)
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(!living_pawn.combat_mode)
|
|
living_pawn.set_combat_mode(TRUE)
|
|
return ..()
|
|
|
|
/datum/pet_command/idle/minebot
|
|
|
|
/datum/pet_command/idle/minebot/execute_action(datum/ai_controller/controller)
|
|
controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE)
|
|
return ..()
|
|
|
|
/datum/pet_command/protect_owner/minebot
|
|
|
|
/datum/pet_command/protect_owner/minebot/set_command_target(mob/living/parent, atom/target)
|
|
if(!parent.ai_controller.blackboard[BB_MINEBOT_AUTO_DEFEND])
|
|
return FALSE
|
|
if(!parent.ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && !QDELETED(target)) //we are already dealing with something,
|
|
parent.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target)
|
|
return TRUE
|
|
|
|
/datum/pet_command/protect_owner/minebot/execute_action(datum/ai_controller/controller)
|
|
if(controller.blackboard[BB_MINEBOT_AUTO_DEFEND])
|
|
var/mob/living/living_pawn = controller.pawn
|
|
living_pawn.set_combat_mode(TRUE)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
|
|
|