mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-09 23:27:56 +01:00
452 lines
17 KiB
Plaintext
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)
|