mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
basic honkbots (#81920)
## About The Pull Request this refactors honkbots into basic mobs. its mostly a faithful 1:1 refactor but i couldnt keep my hands to myselves so i gave them some new behaviors. honkbots now love playing with clowns, they will go seek out for clowns and celebrate around them. also, if the honkbot finds a banana peel or a slippery item near it, it will actively drag people onto them honkbots will now go out of theirway to mess with secbots and annoy them ## Why It's Good For The Game refactors hinkbots into basic bots and also undoes some of the silliness i did in the previous basic bot prs. i also added lazylist support to remove_thing_from_list. ## Changelog 🆑 refactor: honkbots are now basic mobs, please report any bugs add: honkbots will try to slip people on banana peels /🆑
This commit is contained in:
@@ -1186,7 +1186,7 @@
|
||||
/obj/item/storage/crayons,
|
||||
/obj/item/storage/crayons,
|
||||
/obj/item/storage/crayons,
|
||||
/mob/living/simple_animal/bot/secbot/honkbot,
|
||||
/mob/living/basic/bot/honkbot,
|
||||
/turf/open/floor/sepia,
|
||||
/area/shuttle/escape)
|
||||
"NN" = (
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on
|
||||
#define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles
|
||||
#define AI_BOT_PATH_LENGTH 150
|
||||
#define AI_BOT_PATH_LENGTH 75
|
||||
|
||||
// How far should we, by default, be looking for interesting things to de-idle?
|
||||
#define AI_DEFAULT_INTERESTING_DIST 10
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
//bitfield defines
|
||||
|
||||
///can honkbots slip people?
|
||||
#define HONKBOT_MODE_SLIP (1<<0)
|
||||
///can honkbots check IDs?
|
||||
#define HONKBOT_CHECK_IDS (1<<1)
|
||||
///can honkbots check records?
|
||||
#define HONKBOT_CHECK_RECORDS (1<<2)
|
||||
///can honkbots handcuff people?
|
||||
#define HONKBOT_HANDCUFF_TARGET (1<<3)
|
||||
|
||||
DEFINE_BITFIELD(honkbot_flags, list(
|
||||
"CAN_SLIP" = HONKBOT_MODE_SLIP,
|
||||
"CHECK_IDS" = HONKBOT_CHECK_IDS,
|
||||
"CHECK_RECORDS" = HONKBOT_CHECK_RECORDS,
|
||||
"CAN_FAKE_CUFF" = HONKBOT_HANDCUFF_TARGET,
|
||||
))
|
||||
|
||||
|
||||
// bot keys
|
||||
///The first beacon we find
|
||||
#define BB_BEACON_TARGET "beacon_target"
|
||||
@@ -73,3 +92,20 @@
|
||||
#define BB_WASH_FRUSTRATION "wash_frustration"
|
||||
///key that holds cooldown after we finish cleaning something, so we dont immediately run off to patrol
|
||||
#define BB_POST_CLEAN_COOLDOWN "post_clean_cooldown"
|
||||
|
||||
//Honkbots
|
||||
///key that holds all possible clown friends
|
||||
#define BB_CLOWNS_LIST "clowns_list"
|
||||
///key that holds the clown we play with
|
||||
#define BB_CLOWN_FRIEND "clown_friend"
|
||||
///key that holds the list of slippery items
|
||||
#define BB_SLIPPERY_ITEMS "slippery_items"
|
||||
///key that holds list of types we will attempt to slip
|
||||
#define BB_SLIP_LIST "slip_list"
|
||||
///key that holds the slippery item we will drag people too
|
||||
#define BB_SLIPPERY_TARGET "slippery_target"
|
||||
///key that holds the victim we will slip
|
||||
#define BB_SLIP_TARGET "slip_target"
|
||||
///key that holds our honk ability
|
||||
#define BB_HONK_ABILITY "honk_ability"
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/// Signal sent when a blackboard key is set to a new value
|
||||
#define COMSIG_AI_BLACKBOARD_KEY_SET(blackboard_key) "ai_blackboard_key_set_[blackboard_key]"
|
||||
|
||||
///Signal sent before a blackboard key is cleared
|
||||
#define COMSIG_AI_BLACKBOARD_KEY_PRECLEAR(blackboard_key) "ai_blackboard_key_pre_clear_[blackboard_key]"
|
||||
|
||||
/// Signal sent when a blackboard key is cleared
|
||||
#define COMSIG_AI_BLACKBOARD_KEY_CLEARED(blackboard_key) "ai_blackboard_key_clear_[blackboard_key]"
|
||||
|
||||
|
||||
@@ -1143,6 +1143,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
/// Trait applied to objects and mobs that can attack a boulder and break it down. (See /obj/item/boulder/manual_process())
|
||||
#define TRAIT_BOULDER_BREAKER "boulder_breaker"
|
||||
|
||||
/// Trait given to mobs wearing the clown mask
|
||||
#define TRAIT_PERCEIVED_AS_CLOWN "perceived_as_clown"
|
||||
/// Does this item bypass ranged armor checks?
|
||||
#define TRAIT_BYPASS_RANGED_ARMOR "bypass_ranged_armor"
|
||||
|
||||
|
||||
@@ -373,6 +373,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
"TRAIT_PARROT_PERCHED" = TRAIT_PARROT_PERCHED,
|
||||
"TRAIT_PASSTABLE" = TRAIT_PASSTABLE,
|
||||
"TRAIT_PASSWINDOW" = TRAIT_PASSWINDOW,
|
||||
"TRAIT_PERCEIVED_AS_CLOWN" = TRAIT_PERCEIVED_AS_CLOWN,
|
||||
"TRAIT_PERFECT_ATTACKER" = TRAIT_PERFECT_ATTACKER,
|
||||
"TRAIT_PERMANENTLY_MORTAL" = TRAIT_PERMANENTLY_MORTAL,
|
||||
"TRAIT_PHOTOGRAPHER" = TRAIT_PHOTOGRAPHER,
|
||||
|
||||
@@ -715,6 +715,8 @@ multiple modular subtrees with behaviors
|
||||
/datum/ai_controller/proc/clear_blackboard_key(key)
|
||||
if(isnull(blackboard[key]))
|
||||
return
|
||||
if(pawn && (SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_PRECLEAR(key))))
|
||||
return
|
||||
CLEAR_AI_DATUM_TARGET(blackboard[key], key)
|
||||
blackboard[key] = null
|
||||
if(isnull(pawn))
|
||||
@@ -755,6 +757,19 @@ multiple modular subtrees with behaviors
|
||||
CRASH("remove_thing_from_blackboard_key called with an invalid \"thing\" argument ([thing]). \
|
||||
(The passed value is not tracked in the passed list.)")
|
||||
|
||||
///removes a tracked object from a lazylist
|
||||
/datum/ai_controller/proc/remove_from_blackboard_lazylist_key(key, thing)
|
||||
var/lazylist = blackboard[key]
|
||||
if(isnull(lazylist))
|
||||
return
|
||||
for(var/key_index in lazylist)
|
||||
if(thing == key_index || lazylist[key_index] == thing)
|
||||
CLEAR_AI_DATUM_TARGET(thing, key)
|
||||
lazylist -= key_index
|
||||
break
|
||||
if(!LAZYLEN(lazylist))
|
||||
clear_blackboard_key(key)
|
||||
|
||||
/// Signal proc to go through every key and remove the datum from all keys it finds
|
||||
/datum/ai_controller/proc/sig_remove_from_blackboard(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
source.minimum_distance = controller.get_minimum_distance()
|
||||
|
||||
/datum/ai_movement/jps/bot
|
||||
max_pathing_attempts = 25
|
||||
max_pathing_attempts = 8
|
||||
maximum_length = 25
|
||||
diagonal_flags = DIAGONAL_REMOVE_ALL
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
/datum/ai_movement/jps/bot/travel_to_beacon
|
||||
maximum_length = AI_BOT_PATH_LENGTH
|
||||
max_pathing_attempts = 20
|
||||
|
||||
/datum/ai_movement/jps/modsuit
|
||||
maximum_length = MOD_AI_RANGE
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
/datum/crafting_recipe/honkbot
|
||||
name = "Honkbot"
|
||||
result = /mob/living/simple_animal/bot/secbot/honkbot
|
||||
result = /mob/living/basic/bot/honkbot
|
||||
reqs = list(
|
||||
/obj/item/storage/box/clown = 1,
|
||||
/obj/item/bodypart/arm/right/robot = 1,
|
||||
|
||||
101
code/datums/components/cuff_n_stun.dm
Normal file
101
code/datums/components/cuff_n_stun.dm
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* A component to stun and cuff targets
|
||||
*/
|
||||
/datum/component/stun_n_cuff
|
||||
/// mobs we cannot stun nor cuff
|
||||
var/list/blacklist_mobs
|
||||
///sound to play when stunning
|
||||
var/stun_sound
|
||||
///time to stun the target for
|
||||
var/stun_timer
|
||||
///time it takes for us to handcuff the target
|
||||
var/handcuff_timer
|
||||
///callback after we have stunned someone
|
||||
var/datum/callback/post_stun_callback
|
||||
///callback after we have arrested someone
|
||||
var/datum/callback/post_arrest_callback
|
||||
///time until we can stun again
|
||||
var/stun_cooldown_timer
|
||||
///type of cuffs we use
|
||||
var/handcuff_type
|
||||
///cooldown until we can stun again
|
||||
COOLDOWN_DECLARE(stun_cooldown)
|
||||
|
||||
/datum/component/stun_n_cuff/Initialize(list/blacklist_mobs = list(),
|
||||
stun_sound = 'sound/weapons/egloves.ogg',
|
||||
stun_timer = 8 SECONDS,
|
||||
handcuff_timer = 4 SECONDS,
|
||||
stun_cooldown_timer = 10 SECONDS,
|
||||
handcuff_type = /obj/item/restraints/handcuffs/cable/zipties/used,
|
||||
post_stun_callback,
|
||||
post_arrest_callback,
|
||||
)
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.blacklist_mobs = blacklist_mobs
|
||||
src.stun_sound = stun_sound
|
||||
src.stun_timer = stun_timer
|
||||
src.handcuff_timer = handcuff_timer
|
||||
src.handcuff_type = handcuff_type
|
||||
src.stun_cooldown_timer = stun_cooldown_timer
|
||||
src.post_stun_callback = post_stun_callback
|
||||
src.post_arrest_callback = post_arrest_callback
|
||||
|
||||
|
||||
/datum/component/stun_n_cuff/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_unarmed_attack))
|
||||
|
||||
/datum/component/stun_n_cuff/UnregisterFromParent()
|
||||
UnregisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)
|
||||
REMOVE_TRAIT(parent, TRAIT_MOB_BREEDER, REF(src))
|
||||
post_stun_callback = null
|
||||
post_arrest_callback = null
|
||||
|
||||
/datum/component/stun_n_cuff/proc/on_unarmed_attack(mob/living/source, atom/target)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(target == source || !iscarbon(target))
|
||||
return NONE
|
||||
|
||||
if(is_type_in_typecache(target, blacklist_mobs))
|
||||
return NONE
|
||||
|
||||
var/mob/living/carbon/living_target = target
|
||||
if(living_target.IsParalyzed())
|
||||
INVOKE_ASYNC(src, PROC_REF(cuff_target), target)
|
||||
else
|
||||
stun_target(target)
|
||||
|
||||
return COMPONENT_HOSTILE_NO_ATTACK
|
||||
|
||||
/datum/component/stun_n_cuff/proc/cuff_target(mob/living/carbon/human_target)
|
||||
if(human_target.handcuffed)
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.balloon_alert(human_target, "already cuffed!")
|
||||
return
|
||||
|
||||
playsound(parent, 'sound/weapons/cablecuff.ogg', 30, TRUE)
|
||||
human_target.visible_message(span_danger("[parent] is trying to put zipties on [human_target]!"),\
|
||||
span_danger("[parent] is trying to put zipties on you!"))
|
||||
|
||||
if(!do_after(parent, handcuff_timer, human_target))
|
||||
return
|
||||
human_target.set_handcuffed(new handcuff_type(human_target))
|
||||
human_target.update_handcuffed()
|
||||
post_arrest_callback?.Invoke(human_target)
|
||||
|
||||
/datum/component/stun_n_cuff/proc/stun_target(mob/living/carbon/human_target)
|
||||
if(!COOLDOWN_FINISHED(src, stun_cooldown))
|
||||
return
|
||||
playsound(parent, stun_sound, 50, TRUE)
|
||||
human_target.Paralyze(stun_timer)
|
||||
human_target.set_stutter(40 SECONDS)
|
||||
log_combat(parent, human_target, "honked")
|
||||
|
||||
human_target.visible_message(
|
||||
span_danger("[parent] stuns [human_target]!"), \
|
||||
span_userdanger("[parent] stuns you!"), \
|
||||
)
|
||||
COOLDOWN_START(src, stun_cooldown, stun_cooldown_timer)
|
||||
post_stun_callback?.Invoke(human_target)
|
||||
@@ -277,6 +277,7 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
|
||||
dye_color = DYE_CLOWN
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
flags_cover = MASKCOVERSEYES
|
||||
clothing_traits = list(TRAIT_PERCEIVED_AS_CLOWN)
|
||||
resistance_flags = FLAMMABLE
|
||||
actions_types = list(/datum/action/item_action/adjust)
|
||||
dog_fashion = /datum/dog_fashion/head/clown
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
if(6 to 10)
|
||||
new /obj/item/melee/skateboard/pro(src)
|
||||
if(11 to 15)
|
||||
new /mob/living/simple_animal/bot/secbot/honkbot(src)
|
||||
new /mob/living/basic/bot/honkbot(src)
|
||||
if(16 to 20)
|
||||
new /obj/item/stack/ore/diamond(src, 10)
|
||||
if(21 to 25)
|
||||
|
||||
@@ -14,32 +14,40 @@ GLOBAL_LIST_INIT(command_strings, list(
|
||||
gender = NEUTER
|
||||
mob_biotypes = MOB_ROBOTIC
|
||||
basic_mob_flags = DEL_ON_DEATH
|
||||
density = FALSE
|
||||
|
||||
icon = 'icons/mob/silicon/aibots.dmi'
|
||||
icon_state = "medibot0"
|
||||
base_icon_state = "medibot"
|
||||
|
||||
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0)
|
||||
habitable_atmos = null
|
||||
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST)
|
||||
|
||||
maximum_survivable_temperature = INFINITY
|
||||
minimum_survivable_temperature = 0
|
||||
has_unlimited_silicon_privilege = TRUE
|
||||
|
||||
sentience_type = SENTIENCE_ARTIFICIAL
|
||||
status_flags = NONE //no default canpush
|
||||
faction = list(FACTION_MINING)
|
||||
ai_controller = /datum/ai_controller/basic_controller/bot
|
||||
pass_flags = PASSFLAPS
|
||||
pass_flags = PASSFLAPS | PASSMOB
|
||||
|
||||
verb_say = "states"
|
||||
verb_ask = "queries"
|
||||
verb_exclaim = "declares"
|
||||
verb_yell = "alarms"
|
||||
|
||||
initial_language_holder = /datum/language_holder/synthetic
|
||||
bubble_icon = "machine"
|
||||
|
||||
speech_span = SPAN_ROBOT
|
||||
faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET)
|
||||
faction = list(FACTION_SILICON)
|
||||
light_system = OVERLAY_LIGHT
|
||||
light_range = 3
|
||||
light_power = 0.6
|
||||
speed = 3
|
||||
|
||||
req_one_access = list(ACCESS_ROBOTICS)
|
||||
interaction_flags_click = ALLOW_SILICON_REACH
|
||||
///The Robot arm attached to this robot - has a 50% chance to drop on death.
|
||||
@@ -101,7 +109,7 @@ GLOBAL_LIST_INIT(command_strings, list(
|
||||
/mob/living/basic/bot/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
AddElement(/datum/element/relay_attackers)
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement))
|
||||
RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked))
|
||||
RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access))
|
||||
|
||||
@@ -23,11 +23,24 @@
|
||||
BB_PREVIOUS_BEACON_TARGET,
|
||||
BB_BOT_SUMMON_TARGET,
|
||||
)
|
||||
///how many times we tried to reach the target
|
||||
var/current_pathing_attempts = 0
|
||||
///if we cant reach it after this many attempts, add it to our ignore list
|
||||
var/max_pathing_attempts = 25
|
||||
can_idle = FALSE // we want these to be running always
|
||||
can_idle = FALSE
|
||||
|
||||
/datum/targeting_strategy/basic/bot/can_attack(mob/living/living_mob, atom/the_target, vision_range)
|
||||
var/datum/ai_controller/my_controller = living_mob.ai_controller
|
||||
if(isnull(my_controller))
|
||||
return FALSE
|
||||
if(!ishuman(the_target) || LAZYACCESS(my_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], the_target))
|
||||
return FALSE
|
||||
var/mob/living/living_target = the_target
|
||||
if(isnull(living_target.mind))
|
||||
return FALSE
|
||||
if(get_turf(living_mob) == get_turf(living_target))
|
||||
return ..()
|
||||
var/list/path = get_path_to(living_mob, living_target, max_distance = 10, access = my_controller.get_access())
|
||||
if(!length(path) || QDELETED(living_mob))
|
||||
my_controller?.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, living_target, TRUE)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/TryPossessPawn(atom/new_pawn)
|
||||
. = ..()
|
||||
@@ -66,7 +79,7 @@
|
||||
set_blackboard_key(key, target)
|
||||
return TRUE
|
||||
if(!bypass_add_to_blacklist)
|
||||
set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, REF(target), TRUE)
|
||||
set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE)
|
||||
return FALSE
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/proc/can_reach_target(target, distance = 10)
|
||||
@@ -236,3 +249,30 @@
|
||||
/datum/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded, target_key)
|
||||
. = ..()
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/datum/ai_behavior/bot_search
|
||||
action_cooldown = 2 SECONDS
|
||||
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
|
||||
/datum/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, looking_for, radius = 5, pathing_distance = 10, bypass_add_blacklist = FALSE)
|
||||
if(!istype(controller))
|
||||
stack_trace("attempted to give [controller.pawn] the bot search behavior!")
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST]
|
||||
for(var/atom/potential_target as anything in oview(radius, controller.pawn))
|
||||
if(QDELETED(living_pawn))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
if(!is_type_in_typecache(potential_target, looking_for))
|
||||
continue
|
||||
if(LAZYACCESS(ignore_list, potential_target))
|
||||
continue
|
||||
if(!valid_target(controller, potential_target))
|
||||
continue
|
||||
if(controller.set_if_can_reach(target_key, potential_target, distance = pathing_distance, bypass_add_to_blacklist = bypass_add_blacklist))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
/datum/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target)
|
||||
return TRUE
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
desc = "A little cleaning robot, he looks so excited!"
|
||||
icon = 'icons/mob/silicon/aibots.dmi'
|
||||
icon_state = "cleanbot0"
|
||||
pass_flags = PASSMOB | PASSFLAPS
|
||||
density = FALSE
|
||||
anchored = FALSE
|
||||
health = 25
|
||||
maxHealth = 25
|
||||
light_color = "#99ccff"
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items,
|
||||
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_UNREACHABLE_LIST_COOLDOWN = 3 MINUTES,
|
||||
BB_SALUTE_MESSAGES = list(
|
||||
"salutes",
|
||||
@@ -77,11 +76,13 @@
|
||||
for(var/atom/found_item in found)
|
||||
if(QDELETED(controller.pawn))
|
||||
break
|
||||
if(LAZYACCESS(ignore_list, REF(found_item)))
|
||||
if(LAZYACCESS(ignore_list, found_item))
|
||||
continue
|
||||
if(get_turf(found_item) == get_turf(controller.pawn))
|
||||
return found_item
|
||||
var/list/path = get_path_to(controller.pawn, found_item, max_distance = BOT_CLEAN_PATH_LIMIT, access = controller.get_access())
|
||||
if(!length(path))
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, REF(found_item), TRUE)
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, found_item, TRUE)
|
||||
continue
|
||||
return found_item
|
||||
|
||||
@@ -104,7 +105,7 @@
|
||||
/datum/ai_behavior/find_and_set/spray_target/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST]
|
||||
for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn))
|
||||
if(LAZYACCESS(ignore_list, REF(human_target)))
|
||||
if(LAZYACCESS(ignore_list, human_target))
|
||||
continue
|
||||
if(human_target.stat != CONSCIOUS || isnull(human_target.mind))
|
||||
continue
|
||||
@@ -137,7 +138,7 @@
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(!succeeded && !isnull(target))
|
||||
controller.clear_blackboard_key(target_key)
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, REF(target), TRUE)
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE)
|
||||
return
|
||||
if(QDELETED(target) || is_type_in_typecache(target, controller.blackboard[BB_HUNTABLE_TRASH]))
|
||||
return
|
||||
@@ -212,7 +213,7 @@
|
||||
return
|
||||
if(isnull(parent.ai_controller))
|
||||
return
|
||||
if(LAZYACCESS(parent.ai_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], REF(target)))
|
||||
if(LAZYACCESS(parent.ai_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], target))
|
||||
return
|
||||
return ..()
|
||||
|
||||
|
||||
110
code/modules/mob/living/basic/bots/honkbots/honkbot.dm
Normal file
110
code/modules/mob/living/basic/bots/honkbots/honkbot.dm
Normal file
@@ -0,0 +1,110 @@
|
||||
/mob/living/basic/bot/honkbot
|
||||
name = "\improper Honkbot"
|
||||
desc = "A little robot. It looks happy with its bike horn."
|
||||
icon_state = "honkbot"
|
||||
base_icon_state = "honkbot"
|
||||
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0)
|
||||
req_access = list(ACCESS_ROBOTICS, ACCESS_THEATRE, ACCESS_JANITOR)
|
||||
radio_key = /obj/item/encryptionkey/headset_service
|
||||
ai_controller = /datum/ai_controller/basic_controller/bot/honkbot
|
||||
radio_channel = RADIO_CHANNEL_SERVICE
|
||||
bot_type = HONK_BOT
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL | BOT_MODE_ROUNDSTART_POSSESSION
|
||||
hackables = "sound control systems"
|
||||
path_image_color = "#FF69B4"
|
||||
data_hud_type = DATA_HUD_SECURITY_BASIC
|
||||
additional_access = /datum/id_trim/job/clown
|
||||
possessed_message = "You are a honkbot! Make sure the crew are having a great time!"
|
||||
///our voicelines
|
||||
var/static/list/honkbot_sounds = list(
|
||||
HONKBOT_VOICED_HONK_HAPPY = 'sound/items/bikehorn.ogg',
|
||||
HONKBOT_VOICED_HONK_SAD = 'sound/misc/sadtrombone.ogg',
|
||||
)
|
||||
///Honkbot's flags
|
||||
var/honkbot_flags = HONKBOT_CHECK_RECORDS | HONKBOT_HANDCUFF_TARGET | HONKBOT_MODE_SLIP
|
||||
|
||||
/mob/living/basic/bot/honkbot/Initialize(mapload)
|
||||
. = ..()
|
||||
var/static/list/clown_friends = typecacheof(list(
|
||||
/mob/living/carbon/human,
|
||||
/mob/living/silicon/robot,
|
||||
))
|
||||
ai_controller.set_blackboard_key(BB_CLOWNS_LIST, clown_friends)
|
||||
var/static/list/slippery_items = typecacheof(list(
|
||||
/obj/item/grown/bananapeel,
|
||||
/obj/item/soap,
|
||||
))
|
||||
ai_controller.set_blackboard_key(BB_SLIPPERY_ITEMS, slippery_items)
|
||||
|
||||
var/datum/action/cooldown/mob_cooldown/bot/honk/bike_honk = new(src)
|
||||
bike_honk.Grant(src)
|
||||
bike_honk.post_honk_callback = CALLBACK(src, PROC_REF(set_attacking_state))
|
||||
ai_controller.set_blackboard_key(BB_HONK_ABILITY, bike_honk)
|
||||
|
||||
AddComponent(/datum/component/slippery,\
|
||||
knockdown = 6 SECONDS,\
|
||||
paralyze = 3 SECONDS,\
|
||||
on_slip_callback = CALLBACK(src, PROC_REF(post_slip)),\
|
||||
can_slip_callback = CALLBACK(src, PROC_REF(pre_slip)),\
|
||||
)
|
||||
AddComponent(/datum/component/stun_n_cuff,\
|
||||
stun_sound = 'sound/items/AirHorn.ogg',\
|
||||
post_stun_callback = CALLBACK(src, PROC_REF(post_stun)),\
|
||||
post_arrest_callback = CALLBACK(src, PROC_REF(post_arrest)),\
|
||||
handcuff_type = /obj/item/restraints/handcuffs/cable/zipties/fake,\
|
||||
)
|
||||
|
||||
/mob/living/basic/bot/honkbot/generate_speak_list()
|
||||
return honkbot_sounds
|
||||
|
||||
/mob/living/basic/bot/honkbot/proc/pre_slip()
|
||||
return (prob(70) && ai_controller?.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
|
||||
|
||||
/mob/living/basic/bot/honkbot/proc/post_slip()
|
||||
INVOKE_ASYNC(src, TYPE_PROC_REF(/mob/living/basic/bot, speak), HONKBOT_VOICED_HONK_SAD)
|
||||
set_attacking_state()
|
||||
|
||||
/mob/living/basic/bot/honkbot/proc/set_attacking_state()
|
||||
icon_state = "[base_icon_state]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.2 SECONDS)
|
||||
|
||||
/mob/living/basic/bot/honkbot/proc/post_arrest(mob/living/carbon/current_target)
|
||||
playsound(src, (bot_access_flags & BOT_COVER_EMAGGED ? SFX_HONKBOT_E : 'sound/items/bikehorn.ogg'), 50, FALSE)
|
||||
icon_state = bot_access_flags & BOT_COVER_EMAGGED ? "[base_icon_state]-e" : "[base_icon_state]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 3 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE)
|
||||
|
||||
/mob/living/basic/bot/honkbot/proc/post_stun(mob/living/carbon/current_target)
|
||||
if(!istype(current_target))
|
||||
return
|
||||
|
||||
current_target.set_stutter(40 SECONDS)
|
||||
current_target.set_jitter_if_lower(100 SECONDS)
|
||||
set_attacking_state()
|
||||
if(HAS_TRAIT(current_target, TRAIT_DEAF))
|
||||
return
|
||||
|
||||
var/obj/item/organ/internal/ears/target_ears = current_target.get_organ_slot(ORGAN_SLOT_EARS)
|
||||
target_ears?.adjustEarDamage(0, 5)
|
||||
|
||||
/mob/living/basic/bot/honkbot/ui_data(mob/user)
|
||||
var/list/data = ..()
|
||||
if(!(bot_access_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user))
|
||||
data["custom_controls"]["slip_people"] = honkbot_flags & HONKBOT_MODE_SLIP
|
||||
data["custom_controls"]["fake_cuff"] = honkbot_flags & HONKBOT_HANDCUFF_TARGET
|
||||
data["custom_controls"]["check_ids"] = honkbot_flags & HONKBOT_CHECK_IDS
|
||||
data["custom_controls"]["check_records"] = honkbot_flags & HONKBOT_CHECK_RECORDS
|
||||
return data
|
||||
|
||||
/mob/living/basic/bot/honkbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(. || !isliving(ui.user) || (bot_access_flags & BOT_COVER_LOCKED) && !(ui.user.has_unlimited_silicon_privilege))
|
||||
return
|
||||
switch(action)
|
||||
if("slip_people")
|
||||
honkbot_flags ^= HONKBOT_MODE_SLIP
|
||||
if("fake_cuff")
|
||||
honkbot_flags ^= HONKBOT_HANDCUFF_TARGET
|
||||
if("check_ids")
|
||||
honkbot_flags ^= HONKBOT_CHECK_IDS
|
||||
if("check_records")
|
||||
honkbot_flags ^= HONKBOT_CHECK_RECORDS
|
||||
@@ -0,0 +1,19 @@
|
||||
/datum/action/cooldown/mob_cooldown/bot/honk
|
||||
name = "Honk"
|
||||
desc = "Spread cheer and joy all around!"
|
||||
button_icon = 'icons/obj/art/horn.dmi'
|
||||
button_icon_state = "bike_horn"
|
||||
cooldown_time = 5 SECONDS
|
||||
click_to_activate = FALSE
|
||||
///callback after we have honked
|
||||
var/datum/callback/post_honk_callback
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bot/honk/Activate()
|
||||
playsound(owner, 'sound/items/bikehorn.ogg', 50, TRUE, -1)
|
||||
post_honk_callback?.Invoke()
|
||||
StartCooldown()
|
||||
return TRUE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bot/honk/Destroy()
|
||||
. = ..()
|
||||
post_honk_callback = null
|
||||
248
code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm
Normal file
248
code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm
Normal file
@@ -0,0 +1,248 @@
|
||||
/datum/ai_controller/basic_controller/bot/honkbot
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES,
|
||||
BB_ALWAYS_IGNORE_FACTION = TRUE,
|
||||
)
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/respond_to_summon,
|
||||
/datum/ai_planning_subtree/use_mob_ability/random_honk,
|
||||
/datum/ai_planning_subtree/manage_unreachable_list,
|
||||
/datum/ai_planning_subtree/find_wanted_targets,
|
||||
/datum/ai_planning_subtree/troll_target,
|
||||
/datum/ai_planning_subtree/slip_victims,
|
||||
/datum/ai_planning_subtree/play_with_clowns,
|
||||
/datum/ai_planning_subtree/find_patrol_beacon,
|
||||
)
|
||||
reset_keys = list(
|
||||
BB_BEACON_TARGET,
|
||||
BB_PREVIOUS_BEACON_TARGET,
|
||||
BB_BOT_SUMMON_TARGET,
|
||||
)
|
||||
ai_traits = PAUSE_DURING_DO_AFTER
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/honkbot/TryPossessPawn(atom/new_pawn)
|
||||
. = ..()
|
||||
if(. & AI_CONTROLLER_INCOMPATIBLE)
|
||||
return
|
||||
RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SLIP_TARGET), PROC_REF(on_clear_target))
|
||||
RegisterSignal(new_pawn, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(on_stop_pulling))
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/honkbot/proc/on_clear_target(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/mob/living/living_pawn = pawn
|
||||
living_pawn.stop_pulling()
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/honkbot/proc/on_stop_pulling(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!blackboard_key_exists(BB_SLIP_TARGET))
|
||||
return
|
||||
|
||||
var/atom/slip_target = blackboard[BB_SLIP_TARGET]
|
||||
set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, slip_target, TRUE)
|
||||
clear_blackboard_key(BB_SLIP_TARGET)
|
||||
|
||||
/datum/ai_planning_subtree/find_wanted_targets
|
||||
|
||||
/datum/ai_planning_subtree/find_wanted_targets/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/static/list/can_arrest = typecacheof(list(/mob/living/carbon/human))
|
||||
if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
|
||||
controller.queue_behavior(/datum/ai_behavior/bot_search/wanted_targets, BB_BASIC_MOB_CURRENT_TARGET, can_arrest)
|
||||
|
||||
/datum/ai_behavior/bot_search/wanted_targets
|
||||
|
||||
/datum/ai_behavior/bot_search/wanted_targets/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target)
|
||||
if(!ishuman(my_target))
|
||||
return FALSE
|
||||
var/mob/living/carbon/human/human_target = my_target
|
||||
if(human_target.handcuffed || human_target.stat != CONSCIOUS)
|
||||
return FALSE
|
||||
if(locate(human_target) in controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST])
|
||||
return TRUE
|
||||
var/mob/living/basic/bot/honkbot/my_bot = controller.pawn
|
||||
var/honkbot_flags = my_bot.honkbot_flags
|
||||
var/assess_flags = NONE
|
||||
if(human_target.IsParalyzed() && !(honkbot_flags & HONKBOT_HANDCUFF_TARGET))
|
||||
return FALSE
|
||||
if(my_bot.bot_access_flags & BOT_COVER_EMAGGED)
|
||||
assess_flags |= JUDGE_EMAGGED
|
||||
if(honkbot_flags & HONKBOT_CHECK_IDS)
|
||||
assess_flags |= JUDGE_IDCHECK
|
||||
if(honkbot_flags & HONKBOT_CHECK_RECORDS)
|
||||
assess_flags |= JUDGE_RECORDCHECK
|
||||
return (human_target.assess_threat(assess_flags) > 0)
|
||||
|
||||
/datum/ai_planning_subtree/troll_target
|
||||
|
||||
/datum/ai_planning_subtree/troll_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
|
||||
var/mob/living/carbon/my_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
|
||||
if(QDELETED(my_target) || !istype(my_target) || my_target.handcuffed)
|
||||
controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
|
||||
return
|
||||
|
||||
var/mob/living/basic/bot/honkbot/my_bot = controller.pawn
|
||||
if(my_target.IsParalyzed() && !(my_bot.honkbot_flags & HONKBOT_HANDCUFF_TARGET))
|
||||
controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
|
||||
return
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once/honkbot, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/datum/ai_behavior/basic_melee_attack/interact_once/honkbot
|
||||
|
||||
/datum/ai_behavior/basic_melee_attack/interact_once/honkbot/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key)
|
||||
var/mob/living/carbon/human/human_target = controller.blackboard[target_key]
|
||||
if(!isnull(human_target))
|
||||
controller.remove_from_blackboard_lazylist_key(BB_BASIC_MOB_RETALIATE_LIST, human_target)
|
||||
return ..()
|
||||
|
||||
/datum/ai_planning_subtree/play_with_clowns/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
|
||||
var/mob/living/clown_target = controller.blackboard[BB_CLOWN_FRIEND]
|
||||
if(QDELETED(clown_target))
|
||||
var/list/my_list = controller.blackboard[BB_CLOWNS_LIST]
|
||||
controller.queue_behavior(/datum/ai_behavior/bot_search/clown_friends, BB_CLOWN_FRIEND, my_list)
|
||||
return
|
||||
controller.queue_behavior(/datum/ai_behavior/play_with_clown, BB_CLOWN_FRIEND)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/datum/ai_behavior/bot_search/clown_friends
|
||||
|
||||
/datum/ai_behavior/bot_search/clown_friends/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target)
|
||||
if(HAS_TRAIT(my_target, TRAIT_PERCEIVED_AS_CLOWN))
|
||||
return TRUE
|
||||
if(!istype(my_target, /mob/living/silicon/robot))
|
||||
return FALSE
|
||||
var/mob/living/silicon/robot/robot_target = my_target
|
||||
return istype(robot_target.model, /obj/item/robot_model/clown)
|
||||
|
||||
/datum/ai_behavior/play_with_clown
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
|
||||
/datum/ai_behavior/play_with_clown/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/play_with_clown/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
var/mob/living/living_target = controller.blackboard[target_key]
|
||||
if(QDELETED(living_target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
var/datum/action/honk_ability = controller.blackboard[BB_HONK_ABILITY]
|
||||
honk_ability?.Trigger()
|
||||
living_pawn.manual_emote("celebrates with [living_target]!")
|
||||
living_pawn.emote("flip")
|
||||
living_pawn.emote("beep")
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/play_with_clown/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key)
|
||||
. = ..()
|
||||
var/mob/living/living_target = controller.blackboard[target_key]
|
||||
if(QDELETED(living_target))
|
||||
return
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, living_target, TRUE)
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/datum/ai_planning_subtree/slip_victims/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(!living_pawn.has_gravity())
|
||||
return
|
||||
|
||||
var/atom/slippery_item = controller.blackboard[BB_SLIPPERY_TARGET]
|
||||
if(QDELETED(slippery_item) || !can_see(controller.pawn, slippery_item, 5))
|
||||
controller.clear_blackboard_key(BB_SLIP_TARGET)
|
||||
controller.clear_blackboard_key(BB_SLIPPERY_TARGET)
|
||||
controller.queue_behavior(/datum/ai_behavior/bot_search, BB_SLIPPERY_TARGET, controller.blackboard[BB_SLIPPERY_ITEMS])
|
||||
return
|
||||
|
||||
var/mob/living/living_target = controller.blackboard[BB_SLIP_TARGET]
|
||||
|
||||
if(QDELETED(living_target))
|
||||
var/static/list/to_slip = typecacheof(list(/mob/living/carbon/human))
|
||||
controller.queue_behavior(/datum/ai_behavior/bot_search/slip_target, BB_SLIP_TARGET, to_slip)
|
||||
return
|
||||
|
||||
if(living_pawn.pulling == living_target)
|
||||
controller.queue_behavior(/datum/ai_behavior/drag_to_slip, BB_SLIP_TARGET, BB_SLIPPERY_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/drag_target, BB_SLIP_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/datum/ai_behavior/bot_search/slip_target
|
||||
|
||||
/datum/ai_behavior/bot_search/slip_target/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target)
|
||||
return (!my_target.buckled && my_target.has_gravity())
|
||||
|
||||
/datum/ai_behavior/drag_to_slip
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
required_distance = 0
|
||||
|
||||
/datum/ai_behavior/drag_to_slip/setup(datum/ai_controller/controller, slip_target, slippery_target)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[slippery_target]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/drag_to_slip/perform(seconds_per_tick, datum/ai_controller/controller, slip_target, slippery_target)
|
||||
var/mob/living/our_pawn = controller.pawn
|
||||
var/atom/living_target = controller.blackboard[slip_target]
|
||||
if(QDELETED(living_target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
var/list/possible_dirs = GLOB.alldirs.Copy()
|
||||
possible_dirs -= get_dir(our_pawn, living_target)
|
||||
for(var/direction in possible_dirs)
|
||||
var/turf/possible_turf = get_step(our_pawn, direction)
|
||||
if(possible_turf.is_blocked_turf(source_atom = our_pawn))
|
||||
possible_dirs -= direction
|
||||
step(our_pawn, pick(possible_dirs))
|
||||
our_pawn.stop_pulling()
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/drag_to_slip/finish_action(datum/ai_controller/controller, success, slip_target, slippery_target)
|
||||
. = ..()
|
||||
if(success)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
living_pawn.emote("flip")
|
||||
var/atom/slipped_victim = controller.blackboard[slip_target]
|
||||
if(!isnull(slipped_victim))
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, slipped_victim, TRUE)
|
||||
controller.clear_blackboard_key(slip_target)
|
||||
controller.clear_blackboard_key(slippery_target)
|
||||
|
||||
/datum/ai_behavior/drag_target
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
||||
|
||||
/datum/ai_behavior/drag_target/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
var/atom/movable/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target) || target.anchored || target.pulledby)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
var/mob/living/our_mob = controller.pawn
|
||||
our_mob.start_pulling(target)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/drag_target/finish_action(datum/ai_controller/controller, succeeded, target_key)
|
||||
. = ..()
|
||||
if(!succeeded)
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/random_honk
|
||||
ability_key = BB_HONK_ABILITY
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/random_honk/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(!SPT_PROB(5, seconds_per_tick))
|
||||
return
|
||||
return ..()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
icon = 'icons/mob/silicon/aibots.dmi'
|
||||
icon_state = "hygienebot"
|
||||
base_icon_state = "hygienebot"
|
||||
pass_flags = PASSMOB | PASSFLAPS | PASSTABLE
|
||||
pass_flags = parent_type::pass_flags | PASSTABLE
|
||||
layer = MOB_UPPER_LAYER
|
||||
density = FALSE
|
||||
anchored = FALSE
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
icon = 'icons/mob/silicon/aibots.dmi'
|
||||
icon_state = "medibot0"
|
||||
base_icon_state = "medibot"
|
||||
density = FALSE
|
||||
health = 20
|
||||
maxHealth = 20
|
||||
speed = 2
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
search_range = (mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : initial(search_range)
|
||||
var/list/ignore_keys = controller.blackboard[BB_TEMPORARY_IGNORE_LIST]
|
||||
for(var/mob/living/carbon/human/treatable_target in oview(search_range, controller.pawn))
|
||||
if(LAZYACCESS(ignore_keys, REF(treatable_target)) || treatable_target.stat == DEAD)
|
||||
if(LAZYACCESS(ignore_keys, treatable_target) || treatable_target.stat == DEAD)
|
||||
continue
|
||||
if((access_flags & BOT_COVER_EMAGGED) && treatable_target.stat == CONSCIOUS)
|
||||
controller.set_if_can_reach(BB_PATIENT_TARGET, treatable_target, distance =BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1))
|
||||
@@ -107,18 +107,18 @@
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
// only clear the target if they get healed
|
||||
/datum/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/controller, succeeded, target_key, is_stationary, healed_target = FALSE)
|
||||
/datum/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/controller, succeeded, target_key, threshold, damage_type_healer, access_flags, is_stationary)
|
||||
. = ..()
|
||||
var/atom/target = controller.blackboard[target_key]
|
||||
if(!succeeded)
|
||||
|
||||
if(!isnull(target) && !is_stationary)
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, REF(target), TRUE)
|
||||
controller.set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE)
|
||||
|
||||
controller.clear_blackboard_key(target_key)
|
||||
return
|
||||
|
||||
if(QDELETED(target) || !healed_target)
|
||||
if(QDELETED(target) || !check_if_healed(target, threshold, damage_type_healer, access_flags))
|
||||
return
|
||||
|
||||
var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY]
|
||||
|
||||
@@ -323,11 +323,9 @@
|
||||
if(!can_finish_build(attacking_item, user))
|
||||
return
|
||||
to_chat(user, span_notice("You add the [attacking_item] to [src]! Honk!"))
|
||||
var/mob/living/simple_animal/bot/secbot/honkbot/new_honkbot = new(drop_location())
|
||||
var/mob/living/basic/bot/honkbot/new_honkbot = new(drop_location())
|
||||
new_honkbot.name = created_name
|
||||
new_honkbot.limiting_spam = TRUE // only long enough to hear the first ping.
|
||||
playsound(new_honkbot, 'sound/machines/ping.ogg', 50, TRUE, -1)
|
||||
new_honkbot.baton_type = attacking_item.type
|
||||
qdel(attacking_item)
|
||||
qdel(src)
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/mob/living/simple_animal/bot/secbot/honkbot
|
||||
name = "\improper Honkbot"
|
||||
desc = "A little robot. It looks happy with its bike horn."
|
||||
icon_state = "honkbot"
|
||||
light_color = "#e084f7"
|
||||
light_power = 1
|
||||
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0)
|
||||
combat_mode = FALSE
|
||||
|
||||
req_one_access = list(ACCESS_ROBOTICS, ACCESS_THEATRE)
|
||||
radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key
|
||||
radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway.
|
||||
bot_type = HONK_BOT
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL | BOT_MODE_ROUNDSTART_POSSESSION
|
||||
hackables = "sound control systems"
|
||||
path_image_color = "#FF69B4"
|
||||
data_hud_type = DATA_HUD_SECURITY_BASIC //show jobs
|
||||
|
||||
baton_type = /obj/item/bikehorn
|
||||
cuff_type = /obj/item/restraints/handcuffs/cable/zipties/fake/used
|
||||
security_mode_flags = SECBOT_CHECK_WEAPONS | SECBOT_HANDCUFF_TARGET
|
||||
possessed_message = "You are a honkbot! Make sure the crew are having a great time!"
|
||||
|
||||
automated_announcements = list(
|
||||
HONKBOT_VOICED_HONK_HAPPY = 'sound/items/bikehorn.ogg',
|
||||
HONKBOT_VOICED_HONK_SAD = 'sound/misc/sadtrombone.ogg',
|
||||
)
|
||||
|
||||
///Keeping track of how much we honk to prevent spamming it
|
||||
var/limiting_spam = FALSE
|
||||
///Sound played when HONKing someone
|
||||
var/honksound = 'sound/items/bikehorn.ogg'
|
||||
///Cooldown between honks
|
||||
var/cooldowntime = 30
|
||||
///Cooldown between ear-breaking horn sounds
|
||||
var/cooldowntimehorn = 10
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
// Doing this hurts my soul, but simplebot access reworks are for another day.
|
||||
var/datum/id_trim/job/clown_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/clown]
|
||||
//We're doing set_access instead to overwrite the sec access they get.
|
||||
access_card.set_access(clown_trim.access + clown_trim.wildcard_access)
|
||||
prev_access = access_card.access.Copy()
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/on_entered(datum/source, atom/movable/AM)
|
||||
if(prob(70)) //only a chance to slip
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/knockOver(mob/living/carbon/tripped_target)
|
||||
. = ..()
|
||||
INVOKE_ASYNC(src, TYPE_PROC_REF(/mob/living/simple_animal/bot, speak), HONKBOT_VOICED_HONK_SAD)
|
||||
icon_state = "[initial(icon_state)]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.2 SECONDS)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/threat_react(threatlevel)
|
||||
speak(HONKBOT_VOICED_HONK_HAPPY)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/bot_reset()
|
||||
..()
|
||||
limiting_spam = FALSE
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/stun_attack(mob/living/carbon/current_target, harm = FALSE) // airhorn stun
|
||||
if(limiting_spam)
|
||||
return
|
||||
|
||||
var/judgement_criteria = judgement_criteria()
|
||||
playsound(src, 'sound/items/AirHorn.ogg', 100, TRUE, -1) //HEEEEEEEEEEEENK!!
|
||||
icon_state = "[initial(icon_state)]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.2 SECONDS)
|
||||
if(!ishuman(current_target))
|
||||
current_target.Paralyze(8 SECONDS)
|
||||
current_target.set_stutter(40 SECONDS)
|
||||
addtimer(CALLBACK(src, PROC_REF(limiting_spam_false)), cooldowntime)
|
||||
return
|
||||
|
||||
current_target.set_stutter(40 SECONDS)
|
||||
var/obj/item/organ/internal/ears/target_ears = current_target.get_organ_slot(ORGAN_SLOT_EARS)
|
||||
if(target_ears && !HAS_TRAIT(current_target, TRAIT_DEAF))
|
||||
target_ears.adjustEarDamage(0, 5) //far less damage than the H.O.N.K.
|
||||
current_target.set_jitter_if_lower(100 SECONDS)
|
||||
current_target.Paralyze(6 SECONDS)
|
||||
if(client) //prevent spam from players
|
||||
limiting_spam = TRUE
|
||||
|
||||
if(bot_cover_flags & BOT_COVER_EMAGGED) // you really don't want to hit an emagged honkbot
|
||||
threatlevel = 6 // will never let you go
|
||||
else
|
||||
//HONK once, then leave
|
||||
if(ishuman(current_target))
|
||||
var/mob/living/carbon/human/human_target = current_target
|
||||
threatlevel = human_target.assess_threat(judgement_criteria)
|
||||
threatlevel -= 6
|
||||
addtimer(CALLBACK(src, PROC_REF(limiting_spam_false)), cooldowntime)
|
||||
|
||||
log_combat(src, current_target, "honked")
|
||||
|
||||
current_target.visible_message(
|
||||
span_danger("[src] honks [current_target]!"), \
|
||||
span_userdanger("[src] honks you!"), \
|
||||
)
|
||||
|
||||
target_lastloc = target.loc
|
||||
mode = BOT_PREP_ARREST
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/retaliate(mob/living/carbon/human/attacking_human)
|
||||
. = ..()
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE, -1)
|
||||
icon_state = "[initial(icon_state)]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.2 SECONDS)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
if(!limiting_spam)
|
||||
bike_horn()
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/handle_automated_action()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
if(!limiting_spam && prob(30))
|
||||
bike_horn()
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/start_handcuffing(mob/living/carbon/current_target)
|
||||
. = ..()
|
||||
if(bot_cover_flags & BOT_COVER_EMAGGED) //emagged honkbots will spam short and memorable sounds.
|
||||
if(!limiting_spam)
|
||||
playsound(src, SFX_HONKBOT_E, 50, FALSE)
|
||||
icon_state = "honkbot-e"
|
||||
else if(!limiting_spam)
|
||||
playsound(src, honksound, 50, TRUE, -1)
|
||||
icon_state = "[initial(icon_state)]-c"
|
||||
|
||||
limiting_spam = TRUE // prevent spam
|
||||
addtimer(CALLBACK(src, PROC_REF(limiting_spam_false)), cooldowntimehorn)
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 3 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE)
|
||||
|
||||
//Honkbots don't care for NAP violations
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/check_nap_violations()
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/proc/limiting_spam_false() //used for addtimer
|
||||
limiting_spam = FALSE
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/proc/bike_horn() // horn attack
|
||||
if(limiting_spam)
|
||||
return
|
||||
playsound(loc, honksound, 50, TRUE, -1)
|
||||
limiting_spam = TRUE // prevent spam
|
||||
icon_state = "[initial(icon_state)]-c"
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.2 SECONDS)
|
||||
addtimer(CALLBACK(src, PROC_REF(limiting_spam_false)), cooldowntimehorn)
|
||||
@@ -20,7 +20,6 @@
|
||||
/mob/living/simple_animal/bot/secbot/genesky,
|
||||
/mob/living/simple_animal/bot/secbot/grievous,
|
||||
/mob/living/simple_animal/bot/secbot/grievous/toy,
|
||||
/mob/living/simple_animal/bot/secbot/honkbot,
|
||||
/mob/living/simple_animal/bot/secbot/pingsky,
|
||||
/mob/living/simple_animal/bot/vibebot,
|
||||
/mob/living/simple_animal/hostile,
|
||||
|
||||
@@ -1058,6 +1058,7 @@
|
||||
#include "code\datums\components\crank_recharge.dm"
|
||||
#include "code\datums\components\crate_carrier.dm"
|
||||
#include "code\datums\components\creamed.dm"
|
||||
#include "code\datums\components\cuff_n_stun.dm"
|
||||
#include "code\datums\components\cult_ritual_item.dm"
|
||||
#include "code\datums\components\curse_of_hunger.dm"
|
||||
#include "code\datums\components\curse_of_polymorph.dm"
|
||||
@@ -4641,6 +4642,9 @@
|
||||
#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot.dm"
|
||||
#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_abilities.dm"
|
||||
#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_ai.dm"
|
||||
#include "code\modules\mob\living\basic\bots\honkbots\honkbot.dm"
|
||||
#include "code\modules\mob\living\basic\bots\honkbots\honkbot_abilities.dm"
|
||||
#include "code\modules\mob\living\basic\bots\honkbots\honkbot_ai.dm"
|
||||
#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot.dm"
|
||||
#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot_ai.dm"
|
||||
#include "code\modules\mob\living\basic\bots\medbot\medbot.dm"
|
||||
@@ -5091,7 +5095,6 @@
|
||||
#include "code\modules\mob\living\simple_animal\bot\ed209bot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\firebot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\floorbot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\honkbot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\mulebot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\secbot.dm"
|
||||
#include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm"
|
||||
|
||||
1
tools/UpdatePaths/Scripts/81920_honkbots.txt
Normal file
1
tools/UpdatePaths/Scripts/81920_honkbots.txt
Normal file
@@ -0,0 +1 @@
|
||||
/mob/living/simple_animal/bot/secbot/honkbot/@SUBTYPES : /mob/living/basic/bot/honkbot/@SUBTYPES{@OLD}
|
||||
Reference in New Issue
Block a user