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:
Ben10Omintrix
2024-06-05 17:17:34 +03:00
committed by GitHub
parent b7225d8486
commit 55c41fb9ad
27 changed files with 618 additions and 191 deletions

View File

@@ -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" = (

View File

@@ -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

View File

@@ -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"

View File

@@ -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]"

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View 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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -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"

View File

@@ -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 ..()

View 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

View File

@@ -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

View 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 ..()

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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"

View File

@@ -0,0 +1 @@
/mob/living/simple_animal/bot/secbot/honkbot/@SUBTYPES : /mob/living/basic/bot/honkbot/@SUBTYPES{@OLD}