diff --git a/code/__DEFINES/bots.dm b/code/__DEFINES/bots.dm index ff3712ed9c0..9e77ad7779c 100644 --- a/code/__DEFINES/bots.dm +++ b/code/__DEFINES/bots.dm @@ -26,6 +26,7 @@ #define BOT_NO_ROUTE 17 // no destination beacon found (or no route) #define BOT_MAKE_TILE 18 // converting metal into tiles (floorbots) #define BOT_EAT_TILE 19 // adding said tiles to inventory (floorbots) +#define BOT_PATHING 20 // Actively pathfinding. //Bot types #define SEC_BOT "Security" // Secutritrons (Beepsky) and ED-209s @@ -42,3 +43,24 @@ #define SENTIENCE_OTHER 3 #define SENTIENCE_MINEBOT 4 #define SENTIENCE_BOSS 5 + +// Medbot voice keys +#define MEDIBOT_VOICED_HOLD_ON "Hey, %TARGET%! Hold on, I'm coming." +#define MEDIBOT_VOICED_WANT_TO_HELP "Wait, %TARGET%! I want to help!" +#define MEDIBOT_VOICED_YOU_ARE_INJURED "%TARGET%, you appear to be injured!" + +#define MEDIBOT_VOICED_ALL_PATCHED_UP "All patched up!" +#define MEDIBOT_VOICED_APPLE_A_DAY "An apple a day keeps me away." +#define MEDIBOT_VOICED_FEEL_BETTER "Feel better soon!" + +#define MEDIBOT_VOICED_STAY_WITH_ME "No! Stay with me!" +#define MEDIBOT_VOICED_LIVE "Live, damnit! LIVE!" +#define MEDIBOT_VOICED_NEVER_LOST "I...I've never lost a patient before. Not today, I mean." + +#define MEDIBOT_VOICED_DELICIOUS "Delicious!" +#define MEDIBOT_VOICED_PLASTIC_SURGEON "I knew it, I should've been a plastic surgeon." +#define MEDIBOT_VOICED_MASK_ON "Radar, put a mask on!" +#define MEDIBOT_VOICED_ALWAYS_A_CATCH "There's always a catch, and I'm the best there is." +#define MEDIBOT_VOICED_LIKE_FLIES "What kind of medbay is this? Everyone's dropping like flies." + +#define MEDIBOT_VOICED_FUCK_YOU "Fuck you." diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 83704a83585..79931aced4b 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -407,7 +407,7 @@ holder.icon_state = "hudpatrol" if(BOT_PREP_ARREST, BOT_ARREST, BOT_HUNT, BOT_BLOCKED, BOT_NO_ROUTE) //STOP RIGHT THERE, CRIMINAL SCUM! holder.icon_state = "hudalert" - if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV, BOT_WAIT_FOR_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES. + if(BOT_MOVING, BOT_PATHING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV, BOT_WAIT_FOR_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES. holder.icon_state = "hudmove" else holder.icon_state = "" diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 18541c78ab8..dfe5e10570b 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -143,6 +143,58 @@ /// Will be true if we lost target we were chasing var/lost_target = FALSE + /// Cooldown for when to cleanup the ignore list. + COOLDOWN_DECLARE(ignore_list_cleanup_cd) + +/mob/living/simple_animal/bot/Initialize(mapload) + . = ..() + GLOB.bots_list += src + icon_living = icon_state + icon_dead = icon_state + access_card = new /obj/item/card/id(src) + // This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. + access_card.access += ACCESS_ROBOTICS + set_custom_texts() + Radio = new/obj/item/radio/headset/bot(src) + Radio.follow_target = src + add_language("Galactic Common", 1) + add_language("Sol Common", 1) + add_language("Tradeband", 1) + add_language("Gutter", 1) + add_language("Trinary", 1) + default_language = GLOB.all_languages["Galactic Common"] + + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + permanent_huds |= diag_hud + + diag_hud_set_bothealth() + diag_hud_set_botstat() + diag_hud_set_botmode() + + REMOVE_TRAIT(src, TRAIT_CAN_STRIP, TRAIT_GENERIC) + +/mob/living/simple_animal/bot/Destroy() + if(paicard) + ejectpai() + set_path(null) + + var/datum/atom_hud/data_hud = GLOB.huds[data_hud_type] + if(data_hud) + data_hud.remove_hud_from(src) + + GLOB.bots_list -= src + QDEL_NULL(path) + QDEL_NULL(Radio) + QDEL_NULL(access_card) + + if(reset_access_timer_id) + deltimer(reset_access_timer_id) + reset_access_timer_id = null + + ignore_list = null + return ..() /obj/item/radio/headset/bot requires_tcomms = FALSE @@ -188,6 +240,10 @@ bot_move(last_target_location, move_speed = 6) frustration++ +/// Setter for mode, so it's all tracable to one place. +/mob/living/simple_animal/bot/proc/set_mode(new_mode) + mode = new_mode + /mob/living/simple_animal/bot/proc/get_mode() if(client) // Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. if(paicard) @@ -224,62 +280,12 @@ update_icon() update_controls() -/mob/living/simple_animal/bot/Initialize(mapload) - . = ..() - GLOB.bots_list += src - icon_living = icon_state - icon_dead = icon_state - access_card = new /obj/item/card/id(src) - // This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. - access_card.access += ACCESS_ROBOTICS - set_custom_texts() - Radio = new/obj/item/radio/headset/bot(src) - Radio.follow_target = src - add_language("Galactic Common", 1) - add_language("Sol Common", 1) - add_language("Tradeband", 1) - add_language("Gutter", 1) - add_language("Trinary", 1) - default_language = GLOB.all_languages["Galactic Common"] - - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - permanent_huds |= diag_hud - - diag_hud_set_bothealth() - diag_hud_set_botstat() - diag_hud_set_botmode() - - REMOVE_TRAIT(src, TRAIT_CAN_STRIP, TRAIT_GENERIC) - /mob/living/simple_animal/bot/med_hud_set_health() return // We use a different hud /mob/living/simple_animal/bot/med_hud_set_status() return // We use a different hud - -/mob/living/simple_animal/bot/Destroy() - if(paicard) - ejectpai() - set_path(null) - - var/datum/atom_hud/data_hud = GLOB.huds[data_hud_type] - if(data_hud) - data_hud.remove_hud_from(src) - - GLOB.bots_list -= src - QDEL_NULL(path) - QDEL_NULL(Radio) - QDEL_NULL(access_card) - - if(reset_access_timer_id) - deltimer(reset_access_timer_id) - reset_access_timer_id = null - - return ..() - /mob/living/simple_animal/bot/mob_negates_gravity() return anchored @@ -333,17 +339,35 @@ ..(reason) diag_hud_set_bothealth() +/// Add an atom to our list of ignored objects. +/mob/living/simple_animal/bot/proc/add_to_ignore(atom/subject) + if(isnull(subject) || (subject in ignore_list)) + return + + if(length(ignore_list) >= 50) + remove_ignored_atom(ignore_list[1]) + + ignore_list += subject + RegisterSignal(subject, COMSIG_PARENT_QDELETING, PROC_REF(remove_ignored_atom)) + +/// Remove an atom from the ignore list. +/mob/living/simple_animal/bot/proc/remove_ignored_atom(datum/source) + SIGNAL_HANDLER + + UnregisterSignal(source, COMSIG_PARENT_QDELETING) + ignore_list -= source + +/// Wipe the ignore list. +/mob/living/simple_animal/bot/proc/clear_ignore_list() + for(var/atom/A as anything in ignore_list) + remove_ignored_atom(A) + /mob/living/simple_animal/bot/handle_automated_action() diag_hud_set_botmode() - if(++ignore_list_cleanup_timer == 300) // Every 300 actions, clean up the ignore list from old junk - for(var/uid in ignore_list) - var/atom/referred_atom = locateUID(uid) - if(!referred_atom || QDELETED(referred_atom)) - ignore_list -= uid - ignore_list_cleanup_timer = 0 - else - ignore_list_cleanup_timer++ + if(COOLDOWN_FINISHED(src, ignore_list_cleanup_cd)) + clear_ignore_list() + COOLDOWN_START(src, ignore_list_cleanup_cd, 20 SECONDS) if(!on) return @@ -358,6 +382,8 @@ if(BOT_SUMMON) // Called by PDA bot_summon() return + if(BOT_PATHING) + return FALSE return TRUE // Successful completion. Used to prevent child process() continuing if this one is ended early. /mob/living/simple_animal/bot/attack_alien(mob/living/carbon/alien/user) @@ -574,7 +600,7 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r for(var/atom/A in view(scan_range, src)) // Search for something in range! if(!istype(A, scan_type)) // Check that the thing we found is the type we want! continue // If not, keep searching! - if((A.UID() in ignore_list) || (A == old_target)) // Filter for blacklisted elements, usually unreachable or previously processed oness + if((A in ignore_list) || (A == old_target)) // Filter for blacklisted elements, usually unreachable or previously processed ones continue var/scan_result = process_scan(A) // Some bots may require additional processing when a result is selected. if(!scan_result) @@ -600,12 +626,6 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r /mob/living/simple_animal/bot/proc/process_scan(atom/scan_target) return scan_target - -/mob/living/simple_animal/bot/proc/add_to_ignore(atom/A) - if(length(ignore_list) >= 50) // This will help keep track of them, so the bot is always trying to reach a blocked spot. - ignore_list.Cut(1, 2) // If the list is full, insert newest, delete oldest. - ignore_list |= A.UID() - /* Movement proc for stepping a bot through a path generated through A-star. Pass a positive integer as an argument to override a bot's default speed. @@ -671,7 +691,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(message) to_chat(calling_ai, "[bicon(src)] [name] called to [end_area.name]. [length(path)-1] meters to destination.") pathset = TRUE - mode = BOT_RESPONDING + set_mode(BOT_RESPONDING) tries = 0 else if(message) @@ -706,7 +726,7 @@ Pass a positive integer as an argument to override a bot's default speed. pathset = FALSE access_card.access = prev_access tries = 0 - mode = BOT_IDLE + set_mode(BOT_IDLE) diag_hud_set_botstat() diag_hud_set_botmode() @@ -734,7 +754,7 @@ Pass a positive integer as an argument to override a bot's default speed. return if(!auto_patrol) // A bot not set to patrol should not be patrolling. - mode = BOT_IDLE + set_mode(BOT_IDLE) return @@ -750,7 +770,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(!length(path)) patrol_target = null return - mode = BOT_PATROL + set_mode(BOT_PATROL) // Perform a single patrol step @@ -776,7 +796,7 @@ Pass a positive integer as an argument to override a bot's default speed. addtimer(CALLBACK(src, PROC_REF(patrol_step_not_moved)), 2) else // No path, so calculate new one - mode = BOT_START_PATROL + set_mode(BOT_START_PATROL) /mob/living/simple_animal/bot/proc/patrol_step_not_moved() calc_path() @@ -794,7 +814,7 @@ Pass a positive integer as an argument to override a bot's default speed. destination = next_destination else auto_patrol = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) speak("Disengaging patrol mode.") /mob/living/simple_animal/bot/proc/get_next_patrol_target() @@ -868,7 +888,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(length(user_access)) access_card.access = user_access + prev_access // Adds the user's access, if any. - mode = BOT_SUMMON + set_mode(BOT_SUMMON) calc_summon_path() speak("Responding.", radio_channel) diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index f9ed81612f1..697b726d359 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -77,7 +77,7 @@ /mob/living/simple_animal/bot/cleanbot/bot_reset() ..() - ignore_list.Cut() //Allows the bot to clean targets it previously ignored due to being unreachable. + clear_ignore_list() target = null oldloc = null area_locked = null @@ -155,8 +155,8 @@ target = null if(target) - if(!path || !length(path)) //No path, need a new one - //Try to produce a path to the target, and ignore airlocks to which it has access. + if(!length(path)) //No path, need a new one + set_mode(BOT_PATHING) path = get_path_to(src, target, 30, access = access_card.access) if(!bot_move(target)) ignore_job -= target.UID() @@ -164,11 +164,12 @@ target = null path = list() return - mode = BOT_MOVING + set_mode(BOT_MOVING) + else if(!bot_move(target)) ignore_job -= target.UID() target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return oldloc = loc @@ -184,7 +185,7 @@ /mob/living/simple_animal/bot/cleanbot/proc/start_clean(obj/effect/decal/cleanable/target) anchored = TRUE visible_message("[src] begins to clean up [target]") - mode = BOT_CLEANING + set_mode(BOT_CLEANING) update_icon(UPDATE_OVERLAYS) addtimer(CALLBACK(src, PROC_REF(do_clean), target), 5 SECONDS) @@ -193,7 +194,7 @@ ignore_job -= target.UID() QDEL_NULL(target) anchored = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) update_icon(UPDATE_OVERLAYS) /mob/living/simple_animal/bot/cleanbot/explode() diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index e3a63cf9dd9..a92dc4b75ec 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -85,7 +85,7 @@ /mob/living/simple_animal/bot/ed209/turn_on() . = ..() icon_state = "[lasercolor]ed209[on]" - mode = BOT_IDLE + set_mode(BOT_IDLE) /mob/living/simple_animal/bot/ed209/turn_off() ..() @@ -177,7 +177,7 @@ threatlevel += 6 if(threatlevel >= 4) target = H - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/ed209/attack_hand(mob/living/carbon/human/H) if(H.a_intent == INTENT_HARM) @@ -266,7 +266,7 @@ if(find_new_target()) return // see if any criminals are in range if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) // switch to patrol mode if(BOT_HUNT) // hunting for perp // if can't reach perp for long enough, go idle @@ -287,11 +287,11 @@ if(Adjacent(target) && isturf(target.loc) && !baton_delayed) // if right next to perp stun_attack(target) if(!lasercolor) - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) anchored = TRUE target_lastloc = target.loc return - mode = BOT_HUNT + set_mode(BOT_HUNT) target = null target_lastloc = null return @@ -330,7 +330,7 @@ back_to_hunt() return - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) anchored = FALSE if(BOT_START_PATROL) @@ -348,7 +348,7 @@ /mob/living/simple_animal/bot/ed209/proc/back_to_idle() anchored = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) target = null last_found = world.time frustration = 0 @@ -357,7 +357,7 @@ /mob/living/simple_animal/bot/ed209/proc/back_to_hunt() anchored = FALSE frustration = 0 - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) // look for a criminal in view of the bot @@ -384,7 +384,7 @@ speak("Level [threatlevel] infraction alert!") playsound(loc, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/edplaceholder.ogg'), 50, FALSE) visible_message("[src] points at [C.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) return TRUE return FALSE @@ -482,7 +482,7 @@ ..() if(!isalien(target)) target = user - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/ed209/emp_act(severity) @@ -519,7 +519,7 @@ var/mob/toarrest = pick(targets) if(toarrest) target = toarrest - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/ed209/proc/unset_disabled() disabled = FALSE @@ -571,7 +571,7 @@ speak("[no_handcuffs ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) /mob/living/simple_animal/bot/ed209/proc/cuff(mob/living/carbon/C) - mode = BOT_ARREST + set_mode(BOT_ARREST) playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) C.visible_message("[src] is trying to put zipties on [C]!",\ "[src] is trying to put zipties on you!") diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 20b1ec93929..f4c1ae6edec 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -60,7 +60,7 @@ ..() target = null oldloc = null - ignore_list.Cut() + clear_ignore_list() nagged = 0 anchored = FALSE update_icon() @@ -222,7 +222,7 @@ if(emagged && isfloorturf(target)) var/turf/simulated/floor/F = target anchored = TRUE - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) if(prob(90)) F.break_tile_to_plating() else @@ -243,12 +243,12 @@ add_to_ignore(target) ignore_job -= target.UID() target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return else if(!bot_move(target)) ignore_job -= target.UID() target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return oldloc = loc @@ -256,7 +256,7 @@ /mob/living/simple_animal/bot/floorbot/proc/inc_amount_callback() amount ++ anchored = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) ignore_job -= target.UID() target = null @@ -308,7 +308,7 @@ return if(amount <= 0) - mode = BOT_IDLE + set_mode(BOT_IDLE) ignore_job -= target.UID() target = null return @@ -317,13 +317,13 @@ if(isspaceturf(target_turf)) // If we are fixing an area not part of pure space, it is visible_message("[src] begins to repair the hole.") - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) update_icon(UPDATE_OVERLAYS) addtimer(CALLBACK(src, PROC_REF(make_bridge_plating), target_turf), 5 SECONDS) else var/turf/simulated/floor/F = target_turf - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) update_icon(UPDATE_OVERLAYS) visible_message("[src] begins repairing the floor.") addtimer(CALLBACK(src, PROC_REF(make_bridge_plating), F), 5 SECONDS) @@ -343,7 +343,7 @@ else target_turf.ChangeTurf(/turf/simulated/floor/plating) - mode = BOT_IDLE + set_mode(BOT_IDLE) amount-- update_icon(UPDATE_OVERLAYS) anchored = FALSE @@ -354,14 +354,14 @@ return anchored = TRUE visible_message("[src] begins to collect tiles.") - mode = BOT_EAT_TILE + set_mode(BOT_EAT_TILE) update_icon(UPDATE_OVERLAYS) addtimer(CALLBACK(src, PROC_REF(do_eat_tile), T), 2 SECONDS) /mob/living/simple_animal/bot/floorbot/proc/do_eat_tile(obj/item/stack/tile/plasteel/T) if(isnull(T)) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return if((amount + T.amount) > MAX_AMOUNT) var/i = MAX_AMOUNT - amount @@ -372,7 +372,7 @@ qdel(T) anchored = FALSE target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) update_icon(UPDATE_OVERLAYS) /mob/living/simple_animal/bot/floorbot/proc/start_make_tile(obj/item/stack/sheet/metal/M) @@ -380,14 +380,14 @@ return anchored = TRUE visible_message("[src] begins to create tiles.") - mode = BOT_MAKE_TILE + set_mode(BOT_MAKE_TILE) update_icon(UPDATE_OVERLAYS) addtimer(CALLBACK(src, PROC_REF(do_make_tile), M), 2 SECONDS) /mob/living/simple_animal/bot/floorbot/proc/do_make_tile(obj/item/stack/sheet/metal/M) if(isnull(M)) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return if((amount + 4) > MAX_AMOUNT) // 1 metal = 4 tiles, hence + 4 @@ -404,7 +404,7 @@ qdel(M) anchored = FALSE target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) update_icon(UPDATE_OVERLAYS) /mob/living/simple_animal/bot/floorbot/update_icon_state() diff --git a/code/modules/mob/living/simple_animal/bot/griefsky.dm b/code/modules/mob/living/simple_animal/bot/griefsky.dm index 380840b7e1c..515bd7eb59b 100644 --- a/code/modules/mob/living/simple_animal/bot/griefsky.dm +++ b/code/modules/mob/living/simple_animal/bot/griefsky.dm @@ -115,7 +115,7 @@ if(find_new_target()) return // see if any criminals are in range if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) // switch to patrol mode if(BOT_HUNT) // hunting for perp icon_state = spin_icon playsound(loc,'sound/effects/spinsabre.ogg',50, TRUE,-1) @@ -176,7 +176,7 @@ visible_message("[src] ignites his energy swords!") icon_state = "griefsky-c" visible_message("[src] points at [C.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) return TRUE return FALSE diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index c3476bc8ff6..53d9435b9b7 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -108,7 +108,7 @@ /mob/living/simple_animal/bot/honkbot/proc/retaliate(mob/living/carbon/human/H) threatlevel = 6 target = H - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/honkbot/attack_hand(mob/living/carbon/human/H) if(H.a_intent == INTENT_HARM) @@ -209,20 +209,20 @@ addtimer(VARSET_CALLBACK(src, spam_flag, FALSE), cooldowntimehorn) /mob/living/simple_animal/bot/honkbot/proc/cuff_callback(mob/living/carbon/C) - mode = BOT_ARREST + set_mode(BOT_ARREST) sleep(1 SECONDS) playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) C.visible_message("[src] is trying to put zipties on [C]!", "[src] is trying to put zipties on you!") if(!do_after(src, 6 SECONDS, target = C) || !on) - mode = BOT_IDLE + set_mode(BOT_IDLE) return if(!C.handcuffed) C.handcuffed = new /obj/item/restraints/handcuffs/twimsts(C) C.update_handcuffed() C.SetDeaf(0) playsound(loc, pick('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/bcreep.ogg'), 50, FALSE) - mode = BOT_IDLE + set_mode(BOT_IDLE) /mob/living/simple_animal/bot/honkbot/proc/stun_attack(mob/living/carbon/C) // airhorn stun if(spam_flag) @@ -270,7 +270,7 @@ if(find_new_target()) return if(!mode && auto_patrol) - mode = BOT_START_PATROL + set_mode(BOT_START_PATROL) if(BOT_HUNT) // if can't reach perp for long enough, go idle if(frustration >= 5) //gives up easier than beepsky @@ -308,7 +308,7 @@ /mob/living/simple_animal/bot/honkbot/proc/back_to_idle() anchored = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) target = null last_found = world.time frustration = 0 @@ -317,7 +317,7 @@ /mob/living/simple_animal/bot/honkbot/proc/back_to_hunt() anchored = FALSE frustration = 0 - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) // responds quickly /mob/living/simple_animal/bot/honkbot/proc/find_new_target() @@ -352,7 +352,7 @@ else speak("Honk!") visible_message("[src] starts chasing [C.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) return TRUE return FALSE @@ -375,7 +375,7 @@ ..() if(!isalien(target)) target = user - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/honkbot/proc/on_atom_entered(datum/source, atom/movable/entered) if(ismob(entered) && on) //only if its online diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index 91d39972eeb..2124643b007 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -26,10 +26,45 @@ window_name = "Automatic Medical Unit v1.1" data_hud_type = DATA_HUD_MEDICAL_ADVANCED + var/list/idle_phrases = list( + MEDIBOT_VOICED_MASK_ON = 'sound/voice/mradar.ogg', + MEDIBOT_VOICED_ALWAYS_A_CATCH = 'sound/voice/mcatch.ogg', + MEDIBOT_VOICED_PLASTIC_SURGEON = 'sound/voice/msurgeon.ogg', + MEDIBOT_VOICED_LIKE_FLIES = 'sound/voice/mflies.ogg', + MEDIBOT_VOICED_DELICIOUS = 'sound/voice/mdelicious.ogg', + ) + + var/list/finish_healing_phrases = list( + MEDIBOT_VOICED_ALL_PATCHED_UP = 'sound/voice/mpatchedup.ogg', + MEDIBOT_VOICED_APPLE_A_DAY = 'sound/voice/mapple.ogg', + MEDIBOT_VOICED_FEEL_BETTER = 'sound/voice/mfeelbetter.ogg', + ) + + var/list/located_patient_phrases = list( + MEDIBOT_VOICED_HOLD_ON = 'sound/voice/mcoming.ogg', + MEDIBOT_VOICED_WANT_TO_HELP = 'sound/voice/mhelp.ogg', + MEDIBOT_VOICED_YOU_ARE_INJURED = 'sound/voice/minjured.ogg', + ) + + var/list/patient_died_phrases = list( + MEDIBOT_VOICED_STAY_WITH_ME = 'sound/voice/mno.ogg', + MEDIBOT_VOICED_LIVE = 'sound/voice/mlive.ogg', + MEDIBOT_VOICED_NEVER_LOST = 'sound/voice/mlost.ogg', + ) + + var/list/frustration_phrases = list( + MEDIBOT_VOICED_FUCK_YOU = 'sound/voice/mfuck_you.ogg', + ) + + /// The above lists joined into one, for one-place lookups + var/list/all_phrases + var/obj/item/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents. var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits. var/mob/living/carbon/patient = null - var/mob/living/carbon/oldpatient = null + /// UID of the previous patient. Used to avoid running selection checks on them if we failed to heal them. + var/previous_patient + var/oldloc = null var/last_found = 0 var/last_warning = 0 @@ -129,6 +164,8 @@ prev_access = access_card.access qdel(J) + all_phrases = idle_phrases + located_patient_phrases + finish_healing_phrases + patient_died_phrases + frustration_phrases + if(new_skin) skin = new_skin update_icon() @@ -136,17 +173,19 @@ /mob/living/simple_animal/bot/medbot/bot_reset() ..() patient = null - oldpatient = null + previous_patient = null oldloc = null last_found = world.time declare_cooldown = FALSE + frustration = 0 update_icon() /mob/living/simple_animal/bot/medbot/proc/soft_reset() //Allows the medibot to still actively perform its medical duties without being completely halted as a hard reset does. path = list() patient = null - mode = BOT_IDLE + set_mode(BOT_IDLE) last_found = world.time + frustration = 0 update_icon() /mob/living/simple_animal/bot/medbot/set_custom_texts() @@ -268,7 +307,7 @@ audible_message("[src] buzzes oddly!") flick("medibot_spark", src) if(user) - oldpatient = user + previous_patient = user.UID() /mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) if(buckled) @@ -280,16 +319,13 @@ if(H.stat == DEAD) return - if((H == oldpatient) && (world.time < last_found + 200)) + if((H.UID() == previous_patient) && (world.time < last_found + 200)) return if(assess_patient(H)) last_found = world.time - if((last_newpatient_speak + 300) < world.time) //Don't spam these messages! - var/list/messagevoice = list("Hey, [H.name]! Hold on, I'm coming." = 'sound/voice/mcoming.ogg', "Wait [H.name]! I want to help!" = 'sound/voice/mhelp.ogg', "[H.name], you appear to be injured!" = 'sound/voice/minjured.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, FALSE) + if((last_newpatient_speak + 30 SECONDS) < world.time) //Don't spam these messages! + medbot_phrase(pick(located_patient_phrases), H) last_newpatient_speak = world.time return H else @@ -299,57 +335,57 @@ if(!..()) return - if(mode == BOT_HEALING) - return + switch(mode) + if(BOT_HEALING) + return - if(frustration > 8) - oldpatient = patient + if(frustration > 5) + previous_patient = patient.UID() soft_reset() + medbot_phrase(MEDIBOT_VOICED_FUCK_YOU) if(!patient) if(!shut_up && prob(1)) - var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/mradar.ogg', "There's always a catch, and I'm the best there is." = 'sound/voice/mcatch.ogg', "I knew it, I should've been a plastic surgeon." = 'sound/voice/msurgeon.ogg', "What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/mflies.ogg', "Delicious!" = 'sound/voice/mdelicious.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, FALSE) + medbot_phrase(pick(idle_phrases)) + var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. - patient = scan(/mob/living/carbon/human, oldpatient, scan_range) - oldpatient = patient + patient = scan(/mob/living/carbon/human, scan_range = scan_range) - if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! - if(mode != BOT_HEALING) - mode = BOT_HEALING - update_icon() - frustration = 0 - medicate_patient(patient) - return + if(patient) + if((get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! + if(mode != BOT_HEALING) + set_mode(BOT_HEALING) + update_icon() + medicate_patient(patient) + return - //Patient has moved away from us! - else if(patient && length(path) && (get_dist(patient,path[length(path)]) > 2)) - path = list() - mode = BOT_IDLE - last_found = world.time - - else if(stationary_mode && patient) //Since we cannot move in this mode, ignore the patient and wait for another. - soft_reset() - return - - if(patient && !length(path) && (get_dist(src,patient) > 1)) - path = get_path_to(src, patient, 30, access = access_card.access) - mode = BOT_MOVING - if(!length(path)) //try to get closer if you can't reach the patient directly - path = get_path_to(src, patient, 30, 1, access = access_card.access) - if(!length(path)) //Do not chase a patient we cannot reach. - soft_reset() - - if(length(path) && patient) - if(!bot_move(path[length(path)])) - oldpatient = patient + else if(stationary_mode) //Since we cannot move in this mode, ignore the patient and wait for another. soft_reset() - return + return + + //Patient has moved away from us! + else if(!length(path) || (length(path) && (get_dist(patient, path[length(path)]) > 2))) + path = list() + set_mode(BOT_IDLE) + last_found = world.time + + if(!length(path) && (get_dist(src,patient) > 1)) + set_mode(BOT_PATHING) + path = get_path_to(src, patient, 30, access = access_card.access) + set_mode(BOT_MOVING) + if(!length(path)) //try to get closer if you can't reach the patient directly + path = get_path_to(src, patient, 30, 1, access = access_card.access) + if(!length(path)) //Do not chase a patient we cannot reach. + add_to_ignore(patient) + soft_reset() + + if(length(path)) + frustration++ + if(!bot_move(path[length(path)])) + previous_patient = patient.UID() + soft_reset() + return - if(length(path) > 8 && patient) - frustration++ if(auto_patrol && !stationary_mode && !patient) if(mode == BOT_IDLE || mode == BOT_START_PATROL) @@ -358,7 +394,6 @@ if(mode == BOT_PATROL) bot_patrol() - return /mob/living/simple_animal/bot/medbot/proc/assess_beaker_injection(mob/living/carbon/C) //If we have and are using a medicine beaker, return any reagent the patient is missing @@ -431,7 +466,7 @@ if(iscarbon(A)) var/mob/living/carbon/C = A patient = C - mode = BOT_HEALING + set_mode(BOT_HEALING) update_icon() medicate_patient(C) update_icon() @@ -444,20 +479,19 @@ chemscan(src, A) /mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) + set waitfor = FALSE + if(!on) return if(!istype(C)) - oldpatient = patient + previous_patient = patient.UID() soft_reset() return if(C.stat == DEAD || HAS_TRAIT(C, TRAIT_FAKEDEATH)) - var/list/messagevoice = list("No! Stay with me!" = 'sound/voice/mno.ogg', "Live, damnit! LIVE!" = 'sound/voice/mlive.ogg', "I...I've never lost a patient before. Not today, I mean." = 'sound/voice/mlost.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, FALSE) - oldpatient = patient + medbot_phrase(pick(patient_died_phrases), C) + previous_patient = patient.UID() soft_reset() return @@ -471,38 +505,39 @@ reagent_id = select_medication(C, beaker_injection) if(!reagent_id) //If they don't need any of that they're probably cured! - var/list/messagevoice = list("All patched up!" = 'sound/voice/mpatchedup.ogg', "An apple a day keeps me away." = 'sound/voice/mapple.ogg', "Feel better soon!" = 'sound/voice/mfeelbetter.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, FALSE) + medbot_phrase(pick(finish_healing_phrases), C) bot_reset() return - else - if(!emagged && !hijacked && check_overdose(patient, reagent_id, injection_amount)) - soft_reset() - return - C.visible_message("[src] is trying to inject [patient]!", \ - "[src] is trying to inject you!") - addtimer(CALLBACK(src, PROC_REF(do_inject), C, !isnull(beaker_injection), reagent_id), 3 SECONDS) + if(!emagged && !hijacked && check_overdose(patient, reagent_id, injection_amount)) + soft_reset() return -/mob/living/simple_animal/bot/medbot/proc/do_inject(mob/living/carbon/C, inject_beaker, reagent_id) - if((get_dist(src, patient) <= 1) && on && assess_patient(patient)) - if(inject_beaker) - if(use_beaker && reagent_glass?.reagents.total_volume) - var/fraction = min(injection_amount/reagent_glass.reagents.total_volume, 1) - reagent_glass.reagents.reaction(patient, REAGENT_INGEST, fraction) - reagent_glass.reagents.trans_to(patient, injection_amount) //Inject from beaker instead. - else - patient.reagents.add_reagent(reagent_id, injection_amount) + C.visible_message( + "[src] is trying to inject [patient]!", + "[src] is trying to inject you!" + ) - C.visible_message("[src] injects [patient] with its syringe!", "[src] injects you with its syringe!") - else + if(!do_after(src, 3 SECONDS, target = C) || !on || (get_dist(src, patient) > 1) || !assess_patient(patient)) + soft_reset() visible_message("[src] retracts its syringe.") + return - update_icon() - soft_reset() + if(!isnull(beaker_injection)) + if(use_beaker && reagent_glass?.reagents.total_volume) + var/fraction = min(injection_amount/reagent_glass.reagents.total_volume, 1) + reagent_glass.reagents.reaction(patient, REAGENT_INGEST, fraction) + reagent_glass.reagents.trans_to(patient, injection_amount) //Inject from beaker instead. + else + patient.reagents.add_reagent(reagent_id, injection_amount) + + C.visible_message( + "[src] injects [patient] with its syringe!", + "[src] injects you with its syringe!" + ) + + // Don't soft reset here, we already have a patient, only soft reset if we fail to heal them. + set_mode(BOT_IDLE) /mob/living/simple_animal/bot/medbot/proc/check_overdose(mob/living/carbon/patient,reagent_id,injection_amount) var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] @@ -568,6 +603,14 @@ spawn(200) //Twenty seconds declare_cooldown = FALSE +/// Given a proper medbot phrase key, say the line with any replacements, and play its sound. +/mob/living/simple_animal/bot/medbot/proc/medbot_phrase(phrase, mob/target) + var/sound_path = all_phrases[phrase] + if(target) + phrase = replacetext(phrase, "%TARGET%", "[target]") + + speak(phrase) + playsound(src, sound_path, 75, FALSE) #undef MEDBOT_MIN_HEALING_THRESHOLD #undef MEDBOT_MAX_HEALING_THRESHOLD diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 48ef878ac71..bf6f273569c 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -432,7 +432,7 @@ AM.forceMove(src) load = AM - mode = BOT_IDLE + set_mode(BOT_IDLE) update_icon() /mob/living/simple_animal/bot/mulebot/proc/load_mob(mob/living/M) @@ -461,7 +461,7 @@ if(!load) return - mode = BOT_IDLE + set_mode(BOT_IDLE) unbuckle_all_mobs() @@ -556,55 +556,55 @@ increment_path() path -= loc if(destination == home_destination) - mode = BOT_GO_HOME + set_mode(BOT_GO_HOME) else - mode = BOT_DELIVER + set_mode(BOT_DELIVER) else // failed to move blockcount++ - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) if(blockcount == 3) buzz(ANNOYED) if(blockcount > 10) // attempt 10 times before recomputing // find new path excluding blocked turf buzz(SIGH) - mode = BOT_WAIT_FOR_NAV + set_mode(BOT_WAIT_FOR_NAV) blockcount = 0 addtimer(CALLBACK(src, PROC_REF(process_blocked), next), 2 SECONDS) return return else buzz(ANNOYED) - mode = BOT_NAV + set_mode(BOT_NAV) return else - mode = BOT_NAV + set_mode(BOT_NAV) return if(BOT_NAV) // calculate new path - mode = BOT_WAIT_FOR_NAV + set_mode(BOT_WAIT_FOR_NAV) INVOKE_ASYNC(src, PROC_REF(process_nav)) /mob/living/simple_animal/bot/mulebot/proc/process_blocked(turf/next) calc_path(avoid=next) if(length(path)) buzz(DELIGHT) - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) /mob/living/simple_animal/bot/mulebot/proc/process_nav() calc_path() if(length(path)) blockcount = 0 - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) buzz(DELIGHT) else buzz(SIGH) - mode = BOT_NO_ROUTE + set_mode(BOT_NO_ROUTE) // calculates a path to the current destination // given an optional turf to avoid @@ -624,9 +624,9 @@ if(!on) return if(destination == home_destination) - mode = BOT_GO_HOME + set_mode(BOT_GO_HOME) else - mode = BOT_DELIVER + set_mode(BOT_DELIVER) update_icon() get_nav() @@ -639,7 +639,7 @@ /mob/living/simple_animal/bot/mulebot/proc/do_start_home() set_destination(home_destination) - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) update_icon() // called when bot reaches current target @@ -685,7 +685,7 @@ if(auto_return && home_destination && destination != home_destination) // auto return set and not at home already start_home() - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) else bot_reset() // otherwise go idle diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 87b2aed467e..f47a7fa6b50 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -189,7 +189,7 @@ threatlevel += 6 if(threatlevel >= 4) target = H - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/secbot/attack_hand(mob/living/carbon/human/H) if(H.a_intent == INTENT_HARM || H.a_intent == INTENT_DISARM) @@ -245,7 +245,7 @@ /mob/living/simple_animal/bot/secbot/proc/cuff(mob/living/carbon/C) - mode = BOT_ARREST + set_mode(BOT_ARREST) playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) C.visible_message("[src] is trying to put zipties on [C]!",\ "[src] is trying to put zipties on you!") @@ -320,7 +320,7 @@ if(find_new_target()) // see if any criminals are in range return if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) // switch to patrol mode if(BOT_HUNT) // hunting for perp // if can't reach perp for long enough, go idle @@ -341,7 +341,7 @@ if(Adjacent(target) && isturf(target.loc) && !baton_delayed) // if right next to perp stun_attack(target) - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) anchored = TRUE target_lastloc = target.loc return @@ -380,7 +380,7 @@ back_to_hunt() return //Try arresting again if the target escapes. - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) anchored = FALSE if(BOT_START_PATROL) @@ -397,7 +397,7 @@ /mob/living/simple_animal/bot/secbot/proc/back_to_idle() anchored = FALSE - mode = BOT_IDLE + set_mode(BOT_IDLE) target = null last_found = world.time frustration = 0 @@ -406,7 +406,7 @@ /mob/living/simple_animal/bot/secbot/proc/back_to_hunt() anchored = FALSE frustration = 0 - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) // look for a criminal in view of the bot @@ -429,7 +429,7 @@ speak("Level [threatlevel] infraction alert!") playsound(loc, pick('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg'), 50, FALSE) visible_message("[src] points at [C.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) return TRUE return FALSE @@ -460,7 +460,7 @@ ..() if(!isalien(target)) target = user - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/secbot/proc/on_atom_entered(datum/source, atom/movable/entered) if(ismob(entered) && target) diff --git a/code/modules/mob/living/simple_animal/bot/syndicate_bots.dm b/code/modules/mob/living/simple_animal/bot/syndicate_bots.dm index ca2bdaa2045..6b09e40452b 100644 --- a/code/modules/mob/living/simple_animal/bot/syndicate_bots.dm +++ b/code/modules/mob/living/simple_animal/bot/syndicate_bots.dm @@ -67,7 +67,7 @@ if(!H) return target = H - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/ed209/syndicate/emag_act(mob/user) to_chat(user, "[src] has no card reader slot!") @@ -94,7 +94,7 @@ if(find_new_target()) return if(!mode && auto_patrol) - mode = BOT_START_PATROL + set_mode(BOT_START_PATROL) if(BOT_HUNT) if(frustration >= 8) @@ -140,7 +140,7 @@ continue target = M oldtarget_name = M.name - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) return TRUE return FALSE diff --git a/sound/voice/mfuck_you.ogg b/sound/voice/mfuck_you.ogg new file mode 100644 index 00000000000..5eacff615fe Binary files /dev/null and b/sound/voice/mfuck_you.ogg differ