Files
Bubberstation/code/datums/ai/basic_mobs/admin_ai_templates.dm

452 lines
17 KiB
Plaintext

/// Used to set up a basic AI controller on a mob for admin ease of use
/datum/admin_ai_template
/// What do admins see when selecting this option?
var/name = ""
/// What AI controller do we apply?
var/controller_type
/// Should we be active even if the target has an active client?
var/override_client
/// Do we apply the hostile faction?
var/make_hostile
/// How likely is it that we move when not busy?
var/idle_chance
/// When do we stop targeting mobs?
var/minimum_stat
/// Actually perform the process
/datum/admin_ai_template/proc/apply(mob/living/target, client/user)
if (QDELETED(target) || !isliving(target))
to_chat(user, span_warning("Invalid target for AI controller."))
return
if (gather_information(target, user))
apply_controller(target, user)
/// Set up any stored variables before we actually apply the controller
/datum/admin_ai_template/proc/gather_information(mob/living/target, client/user)
override_client = tgui_alert(user, "Would you like this controller to be active even while the mob has a client controlling it?", "Override Client?", list("Yes", "No"))
if (isnull(override_client))
return FALSE
override_client = override_client == "Yes"
idle_chance = tgui_input_number(user, "How likely (% chance per second) should this mob be to move to another tile when it's not doing anything else?", "Walk Chance", max_value = 100, min_value = 0)
if (isnull(idle_chance))
return FALSE
if (isnull(make_hostile))
make_hostile = tgui_alert(user, "Do you want to override this mob's faction with the hostile faction?", "Override Faction?", list("Yes", "No"))
if (isnull(make_hostile))
return FALSE
make_hostile = make_hostile == "Yes"
if (isnull(minimum_stat))
var/static/list/stat_types = list(
"Conscious" = CONSCIOUS,
"Soft Crit" = SOFT_CRIT,
"Unconscious" = UNCONSCIOUS,
"Hard Crit" = HARD_CRIT,
"Dead (will probably get stuck punching a corpse forever)" = DEAD,
)
var/selected_stat = tgui_input_list(user, "Attack targets at the maximum health level of...?", "Persistence Level", stat_types, "Soft Crit")
if (isnull(selected_stat))
return FALSE
minimum_stat = stat_types[selected_stat]
return TRUE
/datum/admin_ai_template/proc/apply_controller(mob/living/target, client/user)
if (QDELETED(target))
to_chat(user, span_warning("Target stopped existing while you were answering prompts :("))
return
QDEL_NULL(target.ai_controller)
target.ai_controller = new controller_type(target)
if (make_hostile)
target.set_faction(list(FACTION_HOSTILE))
target.set_allies(list(REF(target)))
var/datum/ai_controller/controller = target.ai_controller
controller.set_blackboard_key(BB_BASIC_MOB_IDLE_WALK_CHANCE, idle_chance)
controller.set_blackboard_key(BB_TARGET_MINIMUM_STAT, minimum_stat)
if (override_client)
controller.continue_processing_when_client = TRUE
controller.reset_ai_status()
/// Walks at a guy and attacks
/datum/admin_ai_template/hostile
name = "Hostile Melee"
controller_type = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles
/// Walks away from a guy and attacks
/datum/admin_ai_template/hostile_ranged
name = "Hostile Ranged"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ranged
/// When should we retreat?
var/min_range
/// When should we advance?
var/max_range
/// What projectile do we fire?
var/projectile_type
/// What's the time between shots?
var/fire_cooldown
/// How many projectiles per shot?
var/burst_shots
/// What's the delay between projectiles in a burst?
var/burst_interval
/// What sound do we make?
var/projectile_sound
/datum/admin_ai_template/hostile_ranged/gather_information(mob/living/target, client/user)
. = ..()
if (!.)
return FALSE
if (!setup_ranged_attacks(target, user))
return FALSE
return decide_min_max_range(target, user)
/// Give target a gun
/datum/admin_ai_template/hostile_ranged/proc/setup_ranged_attacks(mob/living/target, client/user)
if (target.GetComponent(/datum/component/ranged_attacks))
return TRUE
var/static/list/all_projectiles = subtypesof(/obj/projectile)
// These don't really browsable user-friendly names because there's a lot of duplicates, sorry admins
projectile_type = tgui_input_list(user, "What projectile should we fire?", "Select ammo", all_projectiles)
if (isnull(projectile_type))
return FALSE
fire_cooldown = tgui_input_number(user, "How many seconds between shots?", "Fire Rate", round_value = FALSE, max_value = 10, min_value = 0.2, default = 1)
if (isnull(fire_cooldown))
return FALSE
fire_cooldown = fire_cooldown SECONDS
burst_shots = tgui_input_number(user, "How many shots to fire per burst?", "Burst Count", max_value = 100, min_value = 1, default = 1)
if (isnull(burst_shots))
return FALSE
if (burst_shots > 1)
burst_interval = tgui_input_number(user, "How many seconds delay between burst shots?", "Burst Rate", round_value = FALSE, max_value = 2, min_value = 0.1, default = 0.2)
if (isnull(burst_interval))
return FALSE
burst_interval = burst_interval SECONDS
var/pick_sound = tgui_alert(user, "Select a firing sound effect?", "Select Sound", list("Yes", "No"))
if (isnull(pick_sound))
return FALSE
if (pick_sound == "Yes")
projectile_sound = input("", "Select fire sound",) as null|sound
return TRUE
/// Decide our movement details
/datum/admin_ai_template/hostile_ranged/proc/decide_min_max_range(mob/living/target, client/user)
min_range = tgui_input_number(user, "How far should this mob try to stay away from its target?", "Min Distance", max_value = 9, min_value = 0, default = 2)
if (isnull(min_range))
return FALSE
max_range = tgui_input_number(user, "How close should this mob try to stay to its target?", "Max Distance", max_value = 9, min_value = 1, default = 6)
if (isnull(max_range))
return FALSE
return TRUE
/datum/admin_ai_template/hostile_ranged/apply_controller(mob/living/target, client/user)
. = ..()
var/datum/ai_controller/controller = target.ai_controller
controller.set_blackboard_key(BB_RANGED_SKIRMISH_MIN_DISTANCE, min_range)
controller.set_blackboard_key(BB_RANGED_SKIRMISH_MAX_DISTANCE, max_range)
if (!projectile_type)
return
target.AddComponent(\
/datum/component/ranged_attacks,\
cooldown_time = fire_cooldown,\
projectile_type = projectile_type,\
projectile_sound = projectile_sound,\
burst_shots = burst_shots,\
burst_intervals = burst_interval,\
)
if (fire_cooldown <= 1 SECONDS)
target.AddComponent(/datum/component/ranged_mob_full_auto)
/// Walks at a guy while shooting and attacks
/datum/admin_ai_template/hostile_ranged/and_melee
name = "Hostile Ranged/Melee"
controller_type = /datum/ai_controller/basic_controller/simple/simple_skirmisher
/datum/admin_ai_template/hostile_ranged/and_melee/decide_min_max_range(mob/living/target, client/user)
return TRUE
/// Maintain distance from a guy and use an ability on cooldown
/datum/admin_ai_template/ability
name = "Hostile Ability User"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ability
/// What is our ability?
var/ability_type
/// When should we retreat?
var/min_range
/// When should we advance?
var/max_range
/datum/admin_ai_template/ability/gather_information(mob/living/target, client/user)
. = ..()
if (!.)
return FALSE
// We'll limit it to mob actions because they're mostly set up for random mobs already, and spells take some extra finagling for wizard clothing etc
var/static/list/all_mob_actions = sort_list(subtypesof(/datum/action/cooldown/mob_cooldown), GLOBAL_PROC_REF(cmp_typepaths_asc))
var/static/list/actions_by_name = list()
if (!length(actions_by_name))
for (var/datum/action/cooldown/mob_cooldown as anything in all_mob_actions)
actions_by_name["[initial(mob_cooldown.name)] ([mob_cooldown])"] = mob_cooldown
ability_type = tgui_input_list(user, "Which ability should it use?", "Select Ability", actions_by_name)
if (isnull(ability_type))
return FALSE
ability_type = actions_by_name[ability_type]
return decide_min_max_range(target, user)
/// Decide our movement details, some copy/paste here unfortunately
/datum/admin_ai_template/ability/proc/decide_min_max_range(mob/living/target, client/user)
min_range = tgui_input_number(user, "How far should this mob try to stay away from its target?", "Min Distance", max_value = 9, min_value = 0, default = 2)
if (isnull(min_range))
return FALSE
max_range = tgui_input_number(user, "How close should this mob try to stay to its target?", "Max Distance", max_value = 9, min_value = 1, default = 6)
if (isnull(max_range))
return FALSE
return TRUE
/datum/admin_ai_template/ability/apply_controller(mob/living/target, client/user)
. = ..()
var/datum/action/cooldown/ability = locate(ability_type) in target.actions
if (isnull(ability))
ability = new ability_type(target)
ability.Grant(target)
var/datum/ai_controller/controller = target.ai_controller
controller.set_blackboard_key(BB_TARGETED_ACTION, ability)
controller.set_blackboard_key(BB_RANGED_SKIRMISH_MIN_DISTANCE, min_range)
controller.set_blackboard_key(BB_RANGED_SKIRMISH_MAX_DISTANCE, max_range)
/// Walks at a guy and uses an ability on that guy
/datum/admin_ai_template/ability/melee
name = "Hostile Ability User (Melee Attacks)"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ability_melee
/datum/admin_ai_template/ability/melee/decide_min_max_range(mob/living/target, client/user)
return TRUE
/// Stays away from a guy and uses an ability on that guy
/datum/admin_ai_template/hostile_ranged/ability
name = "Hostile Ability User (Ranged Attacks)"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ability_ranged
/// What is our ability?
var/ability_type
/datum/admin_ai_template/hostile_ranged/ability/gather_information(mob/living/target, client/user)
. = ..()
if (!.)
return FALSE
// Sadly gotta copy/paste this here too
var/static/list/all_mob_actions = sort_list(subtypesof(/datum/action/cooldown/mob_cooldown), GLOBAL_PROC_REF(cmp_typepaths_asc))
var/static/list/actions_by_name = list()
if (!length(actions_by_name))
for (var/datum/action/cooldown/mob_cooldown as anything in all_mob_actions)
actions_by_name["[initial(mob_cooldown.name)] ([mob_cooldown])"] = mob_cooldown
ability_type = tgui_input_list(user, "Which ability should it use?", "Select Ability", actions_by_name)
if (isnull(ability_type))
return FALSE
ability_type = actions_by_name[ability_type]
return TRUE
/datum/admin_ai_template/hostile_ranged/ability/apply_controller(mob/living/target, client/user)
. = ..()
var/datum/action/cooldown/ability = locate(ability_type) in target.actions
if (isnull(ability))
ability = new ability_type(target)
ability.Grant(target)
var/datum/ai_controller/controller = target.ai_controller
controller.set_blackboard_key(BB_TARGETED_ACTION, ability)
/// Chill unless you throw hands
/datum/admin_ai_template/retaliate
name = "Passive But Fights Back (Melee)"
controller_type = /datum/ai_controller/basic_controller/simple/simple_retaliate
make_hostile = FALSE
/datum/admin_ai_template/retaliate/apply_controller(mob/living/target, client/user)
. = ..()
if (!HAS_TRAIT_FROM(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, /datum/element/ai_retaliate)) // Not really what this is for but it should work
target.AddElement(/datum/element/ai_retaliate)
/// Shoots anyone who attacks them
/datum/admin_ai_template/hostile_ranged/ability/retaliate
name = "Passive But Fights Back (Ranged Attacks)"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ranged_retaliate
make_hostile = FALSE
/datum/admin_ai_template/hostile_ranged/ability/retaliate/apply_controller(mob/living/target, client/user)
. = ..()
if (!HAS_TRAIT_FROM(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, /datum/element/ai_retaliate)) // Not really what this is for but it should work
target.AddElement(/datum/element/ai_retaliate)
/// Uses their signature move on anyone who attacks them
/datum/admin_ai_template/ability/retaliate
name = "Passive But Fights Back (Ability)"
controller_type = /datum/ai_controller/basic_controller/simple/simple_ability_retaliate
make_hostile = FALSE
/datum/admin_ai_template/ability/retaliate/apply_controller(mob/living/target, client/user)
. = ..()
if (!HAS_TRAIT_FROM(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, /datum/element/ai_retaliate)) // Not really what this is for but it should work
target.AddElement(/datum/element/ai_retaliate)
/// Who knows what this guy will do, he's a loose cannon
/datum/admin_ai_template/grumpy
name = "Gets Mad Unpredictably"
controller_type = /datum/ai_controller/basic_controller/simple/simple_capricious
make_hostile = FALSE
/// Chance per second to get pissed off
var/flipout_chance
/// Chance per second to stop being pissed off
var/calm_down_chance
/datum/admin_ai_template/grumpy/gather_information(mob/living/target, client/user)
. = ..()
if (!.)
return FALSE
flipout_chance = tgui_input_number(user, "What's the % chance per second we'll get mad for no reason?", "Tantrum Chance", round_value = FALSE, max_value = 100, min_value = 0, default = 0.5)
if (isnull(flipout_chance))
return FALSE
calm_down_chance = tgui_input_number(user, "What's the % chance per second we'll stop being mad?", "Zen Chance", round_value = FALSE, max_value = 100, min_value = 0, default = 10)
if (isnull(calm_down_chance))
return FALSE
return TRUE
/datum/admin_ai_template/grumpy/apply_controller(mob/living/target, client/user)
. = ..()
var/datum/ai_controller/controller = target.ai_controller
controller.set_blackboard_key(BB_RANDOM_AGGRO_CHANCE, flipout_chance)
controller.set_blackboard_key(BB_RANDOM_DEAGGRO_CHANCE, calm_down_chance)
if (!HAS_TRAIT_FROM(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, /datum/element/ai_retaliate)) // Not really what this is for but it should work
target.AddElement(/datum/element/ai_retaliate)
/// Coward
/datum/admin_ai_template/fearful
name = "Runs Away"
minimum_stat = CONSCIOUS
make_hostile = FALSE
controller_type = /datum/ai_controller/basic_controller/simple/simple_fearful
/// Doesn't like violence
/datum/admin_ai_template/skittish
name = "Runs Away From Attackers"
minimum_stat = CONSCIOUS
make_hostile = FALSE
controller_type = /datum/ai_controller/basic_controller/simple/simple_skittish
/datum/admin_ai_template/skittish/apply_controller(mob/living/target, client/user)
. = ..()
if (!HAS_TRAIT_FROM(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, /datum/element/ai_retaliate)) // Not really what this is for but it should work
target.AddElement(/datum/element/ai_retaliate)
/// You gottit boss
/datum/admin_ai_template/goon
name = "Obeys Commands"
controller_type = /datum/ai_controller/basic_controller/simple/simple_goon
/// Who is really in charge here?
var/mob/living/da_boss
/datum/admin_ai_template/goon/gather_information(mob/living/target, client/user)
. = ..()
if (!.)
return FALSE
var/find_a_mob = tgui_alert(user, "Make this mob a minion of a mob in your tile? (If you don't do this you will need to use the befriend proc)", "Set Master?", list("Yes", "No"))
if (isnull(override_client))
return FALSE
find_a_mob = find_a_mob == "Yes"
if (!find_a_mob)
return TRUE
return grab_mob(target, user)
/// Find a mob to make the boss
/datum/admin_ai_template/goon/proc/grab_mob(mob/living/target, client/user)
var/list/mobs_in_my_tile = list()
for (var/mob/living/dude in (range(0, user.mob) - target))
mobs_in_my_tile[dude.real_name] = dude
if (length(mobs_in_my_tile))
var/picked = tgui_input_list(user, "Select new master.", "Set Master", mobs_in_my_tile + "Try Again", "Try Again")
if (isnull(picked))
return FALSE
if (picked == "Try Again")
return grab_mob(target, user)
da_boss = mobs_in_my_tile[picked]
return TRUE
var/find_a_mob = tgui_alert(user, "No applicable mobs found. Try again?", "Try Again?", list("Yes", "No"))
if (isnull(find_a_mob))
return FALSE
find_a_mob = find_a_mob == "Yes"
if (!find_a_mob)
return TRUE
return grab_mob(target, user)
/datum/admin_ai_template/goon/apply_controller(mob/living/target, client/user)
. = ..()
// There's not really much point making this customisable at the moment
var/static/list/pet_commands = list(
/datum/pet_command/idle,
/datum/pet_command/move,
/datum/pet_command/attack,
/datum/pet_command/follow/start_active,
/datum/pet_command/protect_owner,
)
target.AddComponent(/datum/component/obeys_commands, pet_commands)
if (isnull(da_boss))
return
target.befriend(da_boss)
/// Whatever it was doing before we fucked with it (mostly, can't do this with total confidence)
/datum/admin_ai_template/reset
name = "Reset"
/datum/admin_ai_template/reset/gather_information(mob/living/target, client/user)
return TRUE
/datum/admin_ai_template/reset/apply_controller(mob/living/target, client/user)
QDEL_NULL(target.ai_controller)
var/controller_type = initial(target.ai_controller)
target.ai_controller = new controller_type(src)
/// Like I'm doing nothing at all, nothing at all
/datum/admin_ai_template/clear
name = "None"
/datum/admin_ai_template/clear/gather_information(mob/living/target, client/user)
return TRUE
/datum/admin_ai_template/clear/apply_controller(mob/living/target, client/user)
QDEL_NULL(target.ai_controller)