Files
Paradise/code/modules/mob/living/basic/minebots/minebot_ai.dm
2025-08-06 05:22:26 +00:00

312 lines
13 KiB
Plaintext

/datum/ai_controller/basic_controller/minebot
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/always_check_factions,
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
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(),
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.",
),
BB_OWNER_FRIENDLY_FIRE_APOLOGIES = list(
"Sorry.",
"My fault.",
"Oops.",
),
)
ai_movement = /datum/ai_movement/jps
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/befriend_miners,
/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/flee_target/minebot_gibtonite,
/datum/ai_planning_subtree/locate_dead_humans,
)
ai_traits = AI_FLAG_PAUSE_DURING_DO_AFTER
/datum/ai_planning_subtree/befriend_miners/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
if(!controller.blackboard_key_exists(BB_MINER_FRIEND))
return
controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_MINER_FRIEND)
/// Find dead humans and report their location on the radio
/datum/ai_planning_subtree/locate_dead_humans/select_behaviors(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)
// For now, we only look for friends, and we only befriend the person
// who created us, so we're not screaming on comms about every single
// dead player that we find
if(!controller.blackboard_key_exists(BB_FRIENDS_LIST))
return null
for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn))
if(!(target in controller.blackboard[BB_FRIENDS_LIST]))
continue
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/radio/radio = locate(/obj/item/radio) in living_pawn.contents
if(!radio)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/message = message_to_multilingual("Miner in need of help at coordinates: [target_turf.x], [target_turf.y], [target_turf.z]!")
radio.talk_into(living_pawn, message, "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/select_behaviors(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.a_intent != INTENT_HARM)
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/select_behaviors(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, BB_BASIC_MOB_CURRENT_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
/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/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
if(!controller.blackboard[BB_AUTOMATED_MINING])
return
if(controller.blackboard_key_exists(BB_MINEBOT_GIBTONITE_RUN))
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
var/old_intent = living_pawn.a_intent
if(living_pawn.a_intent != INTENT_HARM)
living_pawn.a_intent = INTENT_HARM
living_pawn.face_atom(target)
var/turf/simulated/mineral/mineral_target = target
if(istype(mineral_target) && istype(mineral_target.ore, /datum/ore/gibtonite))
// Duplicate signals can occur when a minebot previously tried to mine a
// turf and failed (e.g. something else was hit by the kpa projectile)
RegisterSignal(mineral_target, COMSIG_MINE_EXPOSE_GIBTONITE, PROC_REF(on_mine_expose_gibtonite), override = TRUE)
living_pawn.RangedAttack(target)
living_pawn.a_intent = old_intent
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/minebot_mine_turf/proc/on_mine_expose_gibtonite(datum/source, mob/living/instigator)
SIGNAL_HANDLER // COMSIG_MINE_EXPOSE_GIBTONITE
UnregisterSignal(source, COMSIG_MINE_EXPOSE_GIBTONITE)
instigator.emote("me", EMOTE_VISIBLE, "yelps!")
instigator.ai_controller.set_blackboard_key(BB_MINEBOT_GIBTONITE_RUN, source)
/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 as anything in turfs_in_path)
if(turf.is_blocked_turf(exclude_mobs = TRUE))
controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target)
return TRUE
for(var/mob/living/potential_friend in turf)
if(source == potential_friend)
continue
if(controller.blackboard_key_exists(BB_FRIENDS_LIST) && (potential_friend in controller.blackboard[BB_FRIENDS_LIST]))
return TRUE
if(source.faction_check_mob(potential_friend))
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
hunt_range = 4
/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
// If we're not on automated mining mode, don't pick up ore
// This is so we don't constantly pick up ore we were just told to drop
// while in another mode
if(!controller.blackboard[BB_AUTOMATED_MINING])
return
return ..()
/datum/ai_planning_subtree/flee_target/minebot_gibtonite
target_key = BB_MINEBOT_GIBTONITE_RUN
/// 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!"
speech_commands = list("mine")
/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."
/// the ability we will use
var/ability_key
/datum/pet_command/minebot_ability/execute_action(datum/ai_controller/controller)
var/datum/action/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")
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")
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.a_intent != INTENT_HARM)
living_pawn.a_intent = INTENT_HARM
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.a_intent = INTENT_HARM
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)