From 55c41fb9ad9c74e47d9ab1eae31146412edfe624 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:17:34 +0300 Subject: [PATCH] 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 :cl: refactor: honkbots are now basic mobs, please report any bugs add: honkbots will try to slip people on banana peels /:cl: --- _maps/shuttles/emergency_casino.dmm | 2 +- code/__DEFINES/ai/ai.dm | 2 +- code/__DEFINES/ai/bot_keys.dm | 36 +++ .../dcs/signals/signals_mob/signals_mob_ai.dm | 3 + code/__DEFINES/traits/declarations.dm | 2 + code/_globalvars/traits/_traits.dm | 1 + code/datums/ai/_ai_controller.dm | 15 ++ code/datums/ai/movement/ai_movement_jps.dm | 3 +- code/datums/components/crafting/robot.dm | 2 +- code/datums/components/cuff_n_stun.dm | 101 +++++++ code/modules/clothing/masks/gasmask.dm | 1 + code/modules/mining/abandoned_crates.dm | 2 +- code/modules/mob/living/basic/bots/_bots.dm | 16 +- code/modules/mob/living/basic/bots/bot_ai.dm | 52 +++- .../living/basic/bots/cleanbot/cleanbot.dm | 3 - .../living/basic/bots/cleanbot/cleanbot_ai.dm | 13 +- .../mob/living/basic/bots/honkbots/honkbot.dm | 110 ++++++++ .../basic/bots/honkbots/honkbot_abilities.dm | 19 ++ .../living/basic/bots/honkbots/honkbot_ai.dm | 248 ++++++++++++++++++ .../basic/bots/hygienebot/hygienebot.dm | 2 +- .../mob/living/basic/bots/medbot/medbot.dm | 1 - .../mob/living/basic/bots/medbot/medbot_ai.dm | 8 +- .../living/simple_animal/bot/construction.dm | 4 +- .../mob/living/simple_animal/bot/honkbot.dm | 156 ----------- .../unit_tests/simple_animal_freeze.dm | 1 - tgstation.dme | 5 +- tools/UpdatePaths/Scripts/81920_honkbots.txt | 1 + 27 files changed, 618 insertions(+), 191 deletions(-) create mode 100644 code/datums/components/cuff_n_stun.dm create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot.dm create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot_abilities.dm create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm delete mode 100644 code/modules/mob/living/simple_animal/bot/honkbot.dm create mode 100644 tools/UpdatePaths/Scripts/81920_honkbots.txt diff --git a/_maps/shuttles/emergency_casino.dmm b/_maps/shuttles/emergency_casino.dmm index 55985a02e69..a3280acfeb7 100644 --- a/_maps/shuttles/emergency_casino.dmm +++ b/_maps/shuttles/emergency_casino.dmm @@ -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" = ( diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 2f567be2d57..37ee5c077e2 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -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 diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 8a46d156114..61189861141 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -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" + diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm index 6cca71839a9..026247acf57 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -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]" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 254dee79055..580b6ef2264 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -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" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 2fba6d117c6..10b50f03e9e 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -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, diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 3753b077a51..58e9746cbf0 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -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 diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index d6f37c2a956..b4c4fe1a28a 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -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 diff --git a/code/datums/components/crafting/robot.dm b/code/datums/components/crafting/robot.dm index 326c58d50c4..11a5887bf91 100644 --- a/code/datums/components/crafting/robot.dm +++ b/code/datums/components/crafting/robot.dm @@ -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, diff --git a/code/datums/components/cuff_n_stun.dm b/code/datums/components/cuff_n_stun.dm new file mode 100644 index 00000000000..d238a81f06a --- /dev/null +++ b/code/datums/components/cuff_n_stun.dm @@ -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) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index e6f3d7294c9..807e7955688 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -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 diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm index 9a0d5b01de0..fa15060d282 100644 --- a/code/modules/mining/abandoned_crates.dm +++ b/code/modules/mining/abandoned_crates.dm @@ -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) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index d98369294e0..6febd66d270 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -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)) diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index 1c30f83b2e6..93d53d6e521 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -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 diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 7cfe7132e15..cd30dd4057d 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -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" diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index a58a97c7274..1fbaa6db2a9 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -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 ..() diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm new file mode 100644 index 00000000000..7f869995c31 --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm @@ -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 diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_abilities.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot_abilities.dm new file mode 100644 index 00000000000..4db55668dce --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_abilities.dm @@ -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 diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm new file mode 100644 index 00000000000..f8d4f55150d --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm @@ -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 ..() + diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm index 8def16692bf..4dbd78dcac1 100644 --- a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm @@ -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 diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index fcf252c0178..945dd5c709f 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -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 diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 0a4520ad17b..f0b2f089cb6 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -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] diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index 1bef64de8a6..7d4565b46f4 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -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) diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm deleted file mode 100644 index 71199583317..00000000000 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ /dev/null @@ -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) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 761ee79216d..7db63d6b48d 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -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, diff --git a/tgstation.dme b/tgstation.dme index 21b26d0db77..803e8fadfaa 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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" diff --git a/tools/UpdatePaths/Scripts/81920_honkbots.txt b/tools/UpdatePaths/Scripts/81920_honkbots.txt new file mode 100644 index 00000000000..5ece41054f9 --- /dev/null +++ b/tools/UpdatePaths/Scripts/81920_honkbots.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/bot/secbot/honkbot/@SUBTYPES : /mob/living/basic/bot/honkbot/@SUBTYPES{@OLD} \ No newline at end of file