From abd366fcf30d0bc7e3521c10ad8e979232c98527 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 26 Dec 2024 01:02:32 -0500 Subject: [PATCH] Bundle o fixes and changes (#1165) * make current medbot work * forgor * bugs * fix pathfinding bug * cleanup ignore_list handling * make the timeout window smaller * add BOT_PATHING state * set_mode --- code/__DEFINES/robots.dm | 47 ++ code/__HELPERS/paths/jps_path.dm | 17 +- code/__HELPERS/paths/path.dm | 21 +- code/game/data_huds.dm | 2 +- .../items/devices/scanners/health_analyzer.dm | 5 +- code/modules/clothing/suits/armor.dm | 1 + code/modules/do_after/do_after.dm | 2 +- code/modules/mob/living/damage_procs.dm | 19 + .../living/simple_animal/bot/SuperBeepsky.dm | 4 +- .../mob/living/simple_animal/bot/bot.dm | 155 ++++--- .../mob/living/simple_animal/bot/cleanbot.dm | 17 +- .../mob/living/simple_animal/bot/ed209bot.dm | 2 +- .../mob/living/simple_animal/bot/firebot.dm | 11 +- .../mob/living/simple_animal/bot/floorbot.dm | 14 +- .../mob/living/simple_animal/bot/honkbot.dm | 2 +- .../living/simple_animal/bot/hygienebot.dm | 12 +- .../mob/living/simple_animal/bot/medbot.dm | 410 ++++++++++-------- .../mob/living/simple_animal/bot/mulebot.dm | 36 +- .../mob/living/simple_animal/bot/secbot.dm | 22 +- code/modules/mob/mob.dm | 5 +- .../chemistry/reagents/medicine_reagents.dm | 4 +- 21 files changed, 472 insertions(+), 336 deletions(-) diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index d8aabb1e17c5..d4fd44c293c0 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -152,6 +152,8 @@ DEFINE_BITFIELD(bot_cover_flags, list( #define BOT_RESPONDING "Proceeding to AI waypoint" /// Currently moving #define BOT_MOVING "Moving" +/// Currently making a path +#define BOT_PATHING "Pathing" // Unique modes // /// Secbot - At target, preparing to arrest @@ -225,3 +227,48 @@ DEFINE_BITFIELD(janitor_mode_flags, list( "CLEANBOT_CLEAN_PESTS" = CLEANBOT_CLEAN_PESTS, "CLEANBOT_CLEAN_DRAWINGS" = CLEANBOT_CLEAN_DRAWINGS, )) + +#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_SUFFER "Why are we still here? Just to suffer?" + +#define MEDIBOT_VOICED_FUCK_YOU "Fuck you." +#define MEDIBOT_VOICED_NOT_A_GAME "Turn off your computer. This is not a game." +#define MEDIBOT_VOICED_IM_DIFFERENT "I'm different!" +#define MEDIBOT_VOICED_FOURTH_WALL "Close Dreamseeker.exe now. Or else." +#define MEDIBOT_VOICED_SHINDEMASHOU "Shindemashou." + +#define MEDIBOT_VOICED_WAIT "Hey, wait..." +#define MEDIBOT_VOICED_DONT "Please don't..." +#define MEDIBOT_VOICED_TRUSTED_YOU "I trusted you..." +#define MEDIBOT_VOICED_NO_SAD "Nooo..." +#define MEDIBOT_VOICED_OH_FUCK "Oh fuck-" + +#define MEDIBOT_VOICED_FORGIVE "I forgive you." +#define MEDIBOT_VOICED_THANKS "Thank you!" +#define MEDIBOT_VOICED_GOOD_PERSON "You are a good person." +#define MEDIBOT_VOICED_BEHAVIOUR_REPORTED "Your behavior has been reported, have a nice day." + +#define MEDIBOT_VOICED_ASSISTANCE "I require assistance." +#define MEDIBOT_VOICED_PUT_BACK "Please put me back." +#define MEDIBOT_VOICED_IM_SCARED "Please, I am scared!" +#define MEDIBOT_VOICED_NEED_HELP "I don't like this, I need help!" +#define MEDIBOT_VOICED_THIS_HURTS "This hurts, my pain is real!" +#define MEDIBOT_VOICED_THE_END "Is this the end?" +#define MEDIBOT_VOICED_NOOO "Nooo!" +#define MEDIBOT_VOICED_CHICKEN "LOOK AT ME?! I am a chicken." diff --git a/code/__HELPERS/paths/jps_path.dm b/code/__HELPERS/paths/jps_path.dm index a01cd315910a..b8b531ca5a12 100644 --- a/code/__HELPERS/paths/jps_path.dm +++ b/code/__HELPERS/paths/jps_path.dm @@ -147,16 +147,17 @@ QDEL_NULL(open) var/list/path = src.path || list() - reverse_range(path) + if(length(path)) + reverse_range(path) - switch(diagonal_handling) - if(DIAGONAL_REMOVE_CLUNKY) - path = remove_clunky_diagonals(path, pass_info, simulated_only, avoid) - if(DIAGONAL_REMOVE_ALL) - path = remove_diagonals(path, pass_info, simulated_only, avoid) + switch(diagonal_handling) + if(DIAGONAL_REMOVE_CLUNKY) + path = remove_clunky_diagonals(path, pass_info, simulated_only, avoid) + if(DIAGONAL_REMOVE_ALL) + path = remove_diagonals(path, pass_info, simulated_only, avoid) - if(length(path) > 0 && skip_first) - path.Cut(1,2) + if(length(path) > 0 && skip_first) + path.Cut(1,2) hand_back(path) return ..() diff --git a/code/__HELPERS/paths/path.dm b/code/__HELPERS/paths/path.dm index cd4b644a304d..cbc617313a84 100644 --- a/code/__HELPERS/paths/path.dm +++ b/code/__HELPERS/paths/path.dm @@ -21,23 +21,24 @@ * * diagonal_handling: defines how we handle diagonal moves. see __DEFINES/path.dm */ /proc/jps_path_to(atom/movable/caller, atom/end, max_distance = 30, mintargetdist, list/access, simulated_only = TRUE, turf/exclude, skip_first=TRUE, diagonal_handling=DIAGONAL_REMOVE_CLUNKY) - var/list/path = list() + var/datum/pathfind_packet/packet = new // We're guarenteed that list will be the first list in pathfinding_finished's argset because of how callback handles the arguments list - var/datum/callback/await = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(pathfinding_finished), path) + var/datum/callback/await = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(pathfinding_finished), packet) if(!SSpathfinder.pathfind(caller, end, max_distance, mintargetdist, access, simulated_only, exclude, skip_first, diagonal_handling, await)) return list() - UNTIL(length(path)) - if(length(path) == 1 && path[1] == null || (QDELETED(caller) || QDELETED(end))) // It's trash, just hand back null to make it easy - return list() - return path + UNTIL(packet.path) + return packet.path /// Uses funny pass by reference bullshit to take the path created by pathfinding, and insert it into a return list /// We'll be able to use this return list to tell a sleeping proc to continue execution -/proc/pathfinding_finished(list/return_list, list/path) - // We use += here to ensure the list is still pointing at the same thing - return_list += path +/proc/pathfinding_finished(datum/pathfind_packet/return_packet, list/path) + return_packet.path = path || list() +/// Wrapper around the path list since we play with refs. +/datum/pathfind_packet + /// The unwound path, set when it's finished. + var/list/path /datum/pathfind /// The turf we started at @@ -97,7 +98,7 @@ * Call to return a value to whoever spawned this pathfinding work * Will fail if it's already been called */ -/datum/pathfind/proc/hand_back(value) +/datum/pathfind/proc/hand_back(list/value) set waitfor = FALSE on_finish?.Invoke(value) on_finish = null diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 2264b9aae738..03d8a5c1d9d5 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -509,7 +509,7 @@ Diagnostic HUDs! new_state = "hudpatrol" if(BOT_PREP_ARREST, BOT_ARREST, BOT_HUNT) //STOP RIGHT THERE, CRIMINAL SCUM! new_state = "hudalert" - if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_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) //Moving to target for normal bots, moving to deliver or go home for MULES. new_state = "hudmove" else new_state = "" diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index d88d6f90e927..4680e643c499 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -64,12 +64,11 @@ playsound(user, 'sound/items/healthanalyzer.ogg', 50, 1) - user.visible_message(span_notice("[user] analyzes [M]'s vitals."), \ - span_notice("You analyze [M]'s vitals.")) + user.visible_message(span_notice("[user] analyzes [M] with [src].")) switch (scanmode) if (SCANMODE_HEALTH) - healthscan(user, M, advanced, mode) + healthscan(user, M, advanced, mode, TRUE) if (SCANMODE_CHEM) chemscan(user, M) if (SCANMODE_SURGERY) diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 0aa78f864bc0..3f2dcf714570 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -2,6 +2,7 @@ fallback_colors = list(list(14, 18)) fallback_icon_state = "armor" allowed = null + clothing_flags = parent_type::clothing_flags | THICKMATERIAL body_parts_covered = CHEST cold_protection = CHEST|GROIN min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT diff --git a/code/modules/do_after/do_after.dm b/code/modules/do_after/do_after.dm index e956ec5733d3..77cf768edad0 100644 --- a/code/modules/do_after/do_after.dm +++ b/code/modules/do_after/do_after.dm @@ -12,7 +12,7 @@ * * max_interact_count: The action will automatically fail if they are already performing this many or more actions with the given interaction_key. * * display: An atom or image to display over the user's head. Only works with DO_PUBLIC flag. */ -/proc/do_after(atom/movable/user, atom/target, time, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1, image/display) +/proc/do_after(atom/movable/user, atom/target, time = 0, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1, image/display) if(!user) return FALSE diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 571c2bd0ac23..fd73bf2f0cd3 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -92,6 +92,25 @@ final_mod *= new_mod return final_mod +/** + * Simply a wrapper for calling mob adjustXLoss() procs to heal a certain damage type, + * when you don't know what damage type you're healing exactly. + */ +/mob/living/proc/heal_damage_type(heal_amount = 0, damagetype = BRUTE) + heal_amount = abs(heal_amount) * -1 + + switch(damagetype) + if(BRUTE) + return adjustBruteLoss(heal_amount) + if(BURN) + return adjustFireLoss(heal_amount) + if(TOX) + return adjustToxLoss(heal_amount) + if(OXY) + return adjustOxyLoss(heal_amount) + if(STAMINA) + stamina.adjust(-heal_amount) + ///like [apply_damage][/mob/living/proc/apply_damage] except it always uses the damage procs /mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) switch(damagetype) diff --git a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm index e7ec27ce0a48..040530547375 100644 --- a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm +++ b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm @@ -66,7 +66,7 @@ SSmove_manager.stop_looping(src) look_for_perp() // see if any criminals are in range if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) if(BOT_HUNT) // hunting for perp update_appearance() playsound(src,'sound/effects/beepskyspinsabre.ogg',100,TRUE,-1) @@ -123,7 +123,7 @@ visible_message(span_warning("[src] ignites his energy swords!")) icon_state = "grievous-c" visible_message("[src] points at [C.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) break else diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index d5b26bbe718a..a71da7e358d6 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -111,7 +111,59 @@ var/path_image_icon_state = "path_indicator" var/path_image_color = "#FFFFFF" var/reset_access_timer_id - var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list + + COOLDOWN_DECLARE(ignore_list_cleanup_cd) + +/mob/living/simple_animal/bot/Initialize(mapload) + . = ..() + GLOB.bots_list += src + + path_hud = new /datum/atom_hud/data/bot_path() + for(var/hud in path_hud.hud_icons) // You get to see your own path + set_hud_image_active(hud, exclusive_hud = path_hud) + + // Give bots a fancy new ID card that can hold any access. + access_card = new /obj/item/card/id/advanced/simple_bot(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.set_access(list(ACCESS_ROBOTICS)) + internal_radio = new /obj/item/radio(src) + if(radio_key) + internal_radio.keyslot = new radio_key + internal_radio.subspace_transmission = TRUE + internal_radio.canhear_range = -1 // anything greater will have the bot broadcast the channel as if it were saying it out loud. + internal_radio.recalculateChannels() + + //Adds bot to the diagnostic HUD system + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_atom_to_hud(src) + diag_hud_set_bothealth() + diag_hud_set_botstat() + diag_hud_set_botmode() + + //If a bot has its own HUD (for player bots), provide it. + if(!isnull(data_hud_type)) + var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] + datahud.show_to(src) + if(path_hud) + path_hud.add_atom_to_hud(src) + path_hud.show_to(src) + + +/mob/living/simple_animal/bot/Destroy() + if(path_hud) + QDEL_NULL(path_hud) + path_hud = null + GLOB.bots_list -= src + if(paicard) + ejectpai() + QDEL_NULL(internal_radio) + QDEL_NULL(access_card) + ignore_list = null + return ..() + +/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. @@ -163,53 +215,6 @@ return TRUE return FALSE -/mob/living/simple_animal/bot/Initialize(mapload) - . = ..() - GLOB.bots_list += src - - path_hud = new /datum/atom_hud/data/bot_path() - for(var/hud in path_hud.hud_icons) // You get to see your own path - set_hud_image_active(hud, exclusive_hud = path_hud) - - // Give bots a fancy new ID card that can hold any access. - access_card = new /obj/item/card/id/advanced/simple_bot(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.set_access(list(ACCESS_ROBOTICS)) - internal_radio = new /obj/item/radio(src) - if(radio_key) - internal_radio.keyslot = new radio_key - internal_radio.subspace_transmission = TRUE - internal_radio.canhear_range = -1 // anything greater will have the bot broadcast the channel as if it were saying it out loud. - internal_radio.recalculateChannels() - - //Adds bot to the diagnostic HUD system - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_atom_to_hud(src) - diag_hud_set_bothealth() - diag_hud_set_botstat() - diag_hud_set_botmode() - - //If a bot has its own HUD (for player bots), provide it. - if(!isnull(data_hud_type)) - var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] - datahud.show_to(src) - if(path_hud) - path_hud.add_atom_to_hud(src) - path_hud.show_to(src) - - -/mob/living/simple_animal/bot/Destroy() - if(path_hud) - QDEL_NULL(path_hud) - path_hud = null - GLOB.bots_list -= src - if(paicard) - ejectpai() - QDEL_NULL(internal_radio) - QDEL_NULL(access_card) - return ..() - /mob/living/simple_animal/bot/proc/check_access(mob/living/user, obj/item/card/id) if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access. return TRUE @@ -301,14 +306,9 @@ /mob/living/simple_animal/bot/handle_automated_action() //Master process which handles code common across most bots. diag_hud_set_botmode() - if (ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk - for(var/ref in ignore_list) - var/atom/referredatom = locate(ref) - if (!referredatom || !istype(referredatom) || QDELETED(referredatom)) - ignore_list -= ref - ignorelistcleanuptimer = 1 - else - ignorelistcleanuptimer++ + if (COOLDOWN_FINISHED(src, ignore_list_cleanup_cd)) + clear_ignore_list() + COOLDOWN_START(src, ignore_list_cleanup_cd, 20 SECONDS) if(!(bot_mode_flags & BOT_MODE_ON) || client) return FALSE @@ -327,6 +327,8 @@ if(BOT_SUMMON) //Called to a location summon_step() return FALSE + if(BOT_PATHING) + return FALSE return TRUE //Successful completion. Used to prevent child process() continuing if this one is ended early. @@ -554,12 +556,26 @@ GLOBAL_LIST_EMPTY(scan_typecaches) return TRUE return FALSE -/mob/living/simple_animal/bot/proc/add_to_ignore(subject) - if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. - ignore_list += REF(subject) - else //If the list is full, insert newest, delete oldest. - ignore_list.Cut(1,2) - ignore_list += REF(subject) +/// Add an atom to our list of ignored objects. +/mob/living/simple_animal/bot/proc/add_to_ignore(atom/subject) + if(isnull(subject)) + return + + if(ignore_list.len < 50) + remove_ignored_atom(ignore_list[1]) + + ignore_list += subject + RegisterSignal(subject, COMSIG_PARENT_QDELETING, PROC_REF(remove_ignored_atom)) + +/mob/living/simple_animal/bot/proc/remove_ignored_atom(datum/source) + SIGNAL_HANDLER + + UnregisterSignal(source, COMSIG_PARENT_QDELETING) + ignore_list -= source + +/mob/living/simple_animal/bot/proc/clear_ignore_list() + for(var/atom/A as anything in ignore_list) + remove_ignored_atom(A) /* Movement proc for stepping a bot through a path generated through A-star. @@ -626,7 +642,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(message) to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.")) pathset = TRUE - mode = BOT_RESPONDING + set_mode(BOT_RESPONDING) tries = 0 else if(message) @@ -655,7 +671,8 @@ Pass a positive integer as an argument to override a bot's default speed. pathset = FALSE access_card.set_access(prev_access) tries = 0 - mode = BOT_IDLE + set_mode(BOT_IDLE) + frustration = 0 ignore_list = list() diag_hud_set_botstat() diag_hud_set_botmode() @@ -685,7 +702,7 @@ Pass a positive integer as an argument to override a bot's default speed. return if(!(bot_mode_flags & BOT_MODE_AUTOPATROL)) //A bot not set to patrol should not be patrolling. - mode = BOT_IDLE + set_mode(BOT_IDLE) return if(patrol_target) // has patrol target @@ -701,7 +718,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(!path.len) patrol_target = null return - mode = BOT_PATROL + set_mode(BOT_PATROL) // perform a single patrol step /mob/living/simple_animal/bot/proc/patrol_step() @@ -726,7 +743,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() @@ -744,7 +761,7 @@ Pass a positive integer as an argument to override a bot's default speed. destination = next_destination else bot_mode_flags &= ~BOT_MODE_AUTOPATROL - mode = BOT_IDLE + set_mode(BOT_IDLE) speak("Disengaging patrol mode.") /mob/living/simple_animal/bot/proc/get_next_patrol_target() @@ -793,7 +810,7 @@ Pass a positive integer as an argument to override a bot's default speed. summon_target = get_turf(user) if(user_access.len != 0) access_card.set_access(user_access + prev_access) //Adds the user's access, if any. - mode = BOT_SUMMON + set_mode(BOT_SUMMON) speak("Responding.", radio_channel) if("ejectpai") diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index ccde75f075e3..63de613dca3e 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -246,28 +246,29 @@ else if(target) if(QDELETED(target) || !isturf(target.loc)) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return if(get_dist(src, target) <= 1) UnarmedAttack(target, proximity_flag = TRUE) //Rather than check at every step of the way, let's check before we do an action, so we can rescan before the other bot. if(QDELETED(target)) //We done here. target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return - if(target && path.len == 0 && (get_dist(src,target) > 1) && mode != BOT_MOVING) - mode = BOT_MOVING + if(target && path.len == 0 && (get_dist(src,target) > 1)) + set_mode(BOT_PATHING) path = jps_path_to(src, target, max_distance=30, mintargetdist=1, access = access_card?.GetAccess()) + set_mode(BOT_MOVING) if(length(path) == 0) add_to_ignore(target) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) if(path.len > 0 && target) if(!bot_move(path[path.len])) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return /mob/living/simple_animal/bot/cleanbot/proc/get_targets() @@ -325,14 +326,14 @@ return . = ..() if(ismopable(attack_target) || istype(attack_target, /obj/effect/decal/cleanable/blood)) - mode = BOT_CLEANING + set_mode(BOT_CLEANING) update_icon_state() var/turf/T = get_turf(attack_target) if(do_after(src, T, 1 SECOND)) T.wash(CLEAN_SCRUB) visible_message(span_notice("[src] cleans [T].")) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) update_icon_state() else if(isitem(attack_target) || istype(attack_target, /obj/effect/decal)) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 5facdc1ac168..1d506bb7567c 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -109,7 +109,7 @@ var/mob/to_arrest = pick(targets) if(to_arrest) target = to_arrest - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/secbot/ed209/RangedAttack(atom/A) if(!(bot_mode_flags & BOT_MODE_ON)) diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index 9ccba3009cd2..24873fb26153 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -99,7 +99,7 @@ /mob/living/simple_animal/bot/firebot/proc/soft_reset() path = list() target_fire = null - mode = BOT_IDLE + set_mode(BOT_IDLE) last_found = world.time update_appearance() @@ -168,7 +168,7 @@ if(IsStun() || IsParalyzed()) old_target_fire = target_fire target_fire = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return if(prob(1) && target_fire == null) @@ -219,16 +219,17 @@ // Target ran away else if(target_fire && path.len && (get_dist(target_fire,path[path.len]) > 2)) path = list() - mode = BOT_IDLE + set_mode(BOT_IDLE) last_found = world.time else if(target_fire && stationary_mode) soft_reset() return - if(target_fire && (get_dist(src, target_fire) > 2) && mode != BOT_MOVING) - mode = BOT_MOVING + if(target_fire && (get_dist(src, target_fire) > 2)) + set_mode(BOT_PATHING) path = jps_path_to(src, target_fire, max_distance=30, mintargetdist=1, access = access_card?.GetAccess()) + set_mode(BOT_MOVING) if(!path.len) soft_reset() diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 35faef3b15e3..9808a5fe8ed8 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -236,7 +236,7 @@ else if(bot_cover_flags & BOT_COVER_EMAGGED && isfloorturf(target)) var/turf/open/floor/F = target toggle_magnet() - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) if(isplatingturf(F)) F.TryScrapeToLattice() else @@ -255,16 +255,16 @@ if(!bot_move(target)) add_to_ignore(target) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return else if( !bot_move(target) ) target = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return /mob/living/simple_animal/bot/floorbot/proc/go_idle() toggle_magnet(FALSE) - mode = BOT_IDLE + set_mode(BOT_IDLE) target = null /mob/living/simple_animal/bot/floorbot/proc/is_hull_breach(turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas. @@ -324,7 +324,7 @@ if(isspaceturf(target_turf)) //If we are fixing an area not part of pure space, it is toggle_magnet() visible_message(span_notice("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ")) - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) if(do_after(src, target_turf, 50) && mode == BOT_REPAIRING) if(autotile) //Build the floor and include a tile. if(replacetiles && tilestack) @@ -345,14 +345,14 @@ if(F.broken || F.burnt || isplatingturf(F)) toggle_magnet() - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) visible_message(span_notice("[src] begins [(F.broken || F.burnt) ? "repairing the floor" : "placing a floor tile"].")) if(do_after(src, F, 50) && mode == BOT_REPAIRING) success = TRUE else if(replacetiles && tilestack && F.type != tilestack.turf_type) toggle_magnet() - mode = BOT_REPAIRING + set_mode(BOT_REPAIRING) visible_message(span_notice("[src] begins replacing the floor tiles.")) if(do_after(src, target_turf, 50) && mode == BOT_REPAIRING && tilestack) success = TRUE diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 4fe46c3e2adb..799653b0f2a3 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -93,7 +93,7 @@ ) target_lastloc = target.loc - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) /mob/living/simple_animal/bot/secbot/honkbot/retaliate(mob/living/carbon/human/attacking_human) . = ..() diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm index 9038c49ffc5c..7ccd256818a3 100644 --- a/code/modules/mob/living/simple_animal/bot/hygienebot.dm +++ b/code/modules/mob/living/simple_animal/bot/hygienebot.dm @@ -80,7 +80,7 @@ /mob/living/simple_animal/bot/hygienebot/turn_off() ..() - mode = BOT_IDLE + set_mode(BOT_IDLE) /mob/living/simple_animal/bot/hygienebot/bot_reset() ..() @@ -108,7 +108,7 @@ SSmove_manager.stop_looping(src) look_for_lowhygiene() // see if any disgusting fucks are in range if(!mode && bot_mode_flags & BOT_MODE_AUTOPATROL) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) if(BOT_HUNT) // hunting for stinkman if(bot_cover_flags & BOT_COVER_EMAGGED) //lol fuck em up @@ -130,7 +130,7 @@ speak("Well about fucking time you degenerate.", "Fucking finally.", "Thank god, you finally stopped.") playsound(loc, 'sound/effects/hygienebot_angry.ogg', 60, 1) mad = FALSE - mode = BOT_SHOWERSTANCE + set_mode(BOT_SHOWERSTANCE) else stop_washing() var/olddist = get_dist(src, target) @@ -168,7 +168,7 @@ bot_patrol() /mob/living/simple_animal/bot/hygienebot/proc/back_to_idle() - mode = BOT_IDLE + set_mode(BOT_IDLE) SSmove_manager.stop_looping(src) target = null frustration = 0 @@ -178,7 +178,7 @@ /mob/living/simple_animal/bot/hygienebot/proc/back_to_hunt() frustration = 0 - mode = BOT_HUNT + set_mode(BOT_HUNT) stop_washing() INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) @@ -192,7 +192,7 @@ speak("Unhygienic client found. Please stand still so I can clean you.") playsound(loc, 'sound/effects/hygienebot_happy.ogg', 60, 1) visible_message("[src] points at [H.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) break else diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index eec739383d35..2a404a4ad542 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -34,6 +34,62 @@ hackables = "health processor circuits" path_image_color = "#DDDDFF" + var/list/idle_phrases = list( + MEDIBOT_VOICED_MASK_ON = 'sound/voice/medbot/radar.ogg', + MEDIBOT_VOICED_ALWAYS_A_CATCH = 'sound/voice/medbot/catch.ogg', + MEDIBOT_VOICED_PLASTIC_SURGEON = 'sound/voice/medbot/surgeon.ogg', + MEDIBOT_VOICED_LIKE_FLIES = 'sound/voice/medbot/flies.ogg', + MEDIBOT_VOICED_DELICIOUS = 'sound/voice/medbot/delicious.ogg', + MEDIBOT_VOICED_SUFFER = 'sound/voice/medbot/why.ogg' + ) + + var/list/finish_healing_phrases = list( + MEDIBOT_VOICED_ALL_PATCHED_UP = 'sound/voice/medbot/patchedup.ogg', + MEDIBOT_VOICED_APPLE_A_DAY = 'sound/voice/medbot/apple.ogg', + MEDIBOT_VOICED_FEEL_BETTER = 'sound/voice/medbot/feelbetter.ogg', + ) + + var/list/located_patient_phrases = list( + MEDIBOT_VOICED_HOLD_ON = 'sound/voice/medbot/coming.ogg', + MEDIBOT_VOICED_WANT_TO_HELP = 'sound/voice/medbot/help.ogg', + MEDIBOT_VOICED_YOU_ARE_INJURED = 'sound/voice/medbot/injured.ogg' + ) + + var/list/patient_died_phrases = list( + MEDIBOT_VOICED_STAY_WITH_ME = 'sound/voice/medbot/no.ogg', + MEDIBOT_VOICED_LIVE = 'sound/voice/medbot/live.ogg', + MEDIBOT_VOICED_NEVER_LOST = 'sound/voice/medbot/lost.ogg' + ) + + var/list/pre_tip_phrases = list( + MEDIBOT_VOICED_WAIT = 'sound/voice/medbot/hey_wait.ogg', + MEDIBOT_VOICED_DONT = 'sound/voice/medbot/please_dont.ogg', + MEDIBOT_VOICED_TRUSTED_YOU = 'sound/voice/medbot/i_trusted_you.ogg', + MEDIBOT_VOICED_NO_SAD = 'sound/voice/medbot/nooo.ogg', + MEDIBOT_VOICED_OH_FUCK = 'sound/voice/medbot/oh_fuck.ogg', + ) + + var/list/untip_phrases = list( + MEDIBOT_VOICED_FORGIVE = 'sound/voice/medbot/forgive.ogg', + MEDIBOT_VOICED_THANKS = 'sound/voice/medbot/thank_you.ogg', + MEDIBOT_VOICED_GOOD_PERSON = 'sound/voice/medbot/youre_good.ogg', + MEDIBOT_VOICED_FUCK_YOU = 'sound/voice/medbot/fuck_you.ogg', + MEDIBOT_VOICED_BEHAVIOUR_REPORTED = 'sound/voice/medbot/reported.ogg' + ) + + var/list/panic_phrases = list( + MEDIBOT_VOICED_ASSISTANCE = 'sound/voice/medbot/i_require_asst.ogg', + MEDIBOT_VOICED_PUT_BACK = 'sound/voice/medbot/please_put_me_back.ogg', + MEDIBOT_VOICED_IM_SCARED = 'sound/voice/medbot/please_im_scared.ogg', + MEDIBOT_VOICED_NEED_HELP = 'sound/voice/medbot/dont_like.ogg', + MEDIBOT_VOICED_THIS_HURTS = 'sound/voice/medbot/pain_is_real.ogg', + MEDIBOT_VOICED_THE_END = 'sound/voice/medbot/is_this_the_end.ogg', + MEDIBOT_VOICED_NOOO = 'sound/voice/medbot/nooo.ogg' + ) + + /// Compiled list of all the phrase lists. + var/list/all_phrases + /// drop determining variable var/healthanalyzer = /obj/item/healthanalyzer /// drop determining variable @@ -41,15 +97,32 @@ ///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox" var/skin var/mob/living/carbon/patient - var/mob/living/carbon/oldpatient + /// Weakref to the last patient. The last patient is ignored for a bit when finding a new target. + var/datum/weakref/previous_patient + /// Timestamp of the last time we found a patient. var/last_found = 0 - /// How much healing do we do at a time? - var/heal_amount = 2.5 + /// Bots must wait this long before considering the previous patient a valid target. + var/repeat_patient_cooldown = 20 SECONDS + /// Start healing when they have this much damage in a category var/heal_threshold = 10 - /// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types + /// How long it takes to inject. + var/heal_time = 2 SECONDS + /// How many units of reagent to inject. + var/injection_amount = 15 + + /// What damage type does this bot support. Set to "all" for it to heal any of the 4 base damage types var/damagetype_healer = BRUTE + /// Reagents to use for each healing type. + var/list/healing_reagents = list( + BRUTE = /datum/reagent/medicine/tricordrazine, + BURN = /datum/reagent/medicine/tricordrazine, + TOX = /datum/reagent/medicine/tricordrazine, + OXY = /datum/reagent/medicine/tricordrazine, + "emag" = /datum/reagent/toxin/chloralhydrate, + ) + ///Flags Medbots use to decide how they should be acting. var/medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_SPEAK_MODE // Selections: MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE @@ -79,7 +152,8 @@ desc = "International Medibot of mystery." skin = "bezerk" damagetype_healer = "all" - heal_amount = 10 + injection_amount = 20 + heal_time = 1 SECOND /mob/living/simple_animal/bot/medbot/derelict name = "\improper Old Medibot" @@ -88,7 +162,8 @@ damagetype_healer = "all" medical_mode_flags = MEDBOT_SPEAK_MODE heal_threshold = 0 - heal_amount = 5 + injection_amount = 5 + heal_time = 5 SECONDS /mob/living/simple_animal/bot/medbot/examine(mob/user) . = ..() @@ -136,6 +211,8 @@ skin = new_skin update_appearance() + all_phrases = idle_phrases + located_patient_phrases + finish_healing_phrases + patient_died_phrases + pre_tip_phrases + untip_phrases + panic_phrases + AddComponent(/datum/component/tippable, \ tip_time = 3 SECONDS, \ untip_time = 3 SECONDS, \ @@ -147,15 +224,18 @@ /mob/living/simple_animal/bot/medbot/bot_reset() ..() patient = null - oldpatient = null + previous_patient = null last_found = world.time + tending = FALSE update_appearance() /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 + tending = FALSE + frustration = 0 update_appearance() /mob/living/simple_animal/bot/medbot/attack_paw(mob/user, list/modifiers) @@ -212,23 +292,20 @@ z_flick("medibot_spark", src) playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) if(user) - oldpatient = user + previous_patient = WEAKREF(user) /mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) if(H.stat == DEAD) return null - if((H == oldpatient) && (world.time < last_found + 200)) + if(IS_WEAKREF_OF(H, previous_patient) && (world.time < last_found + repeat_patient_cooldown)) return null - if(!assess_patient(H)) + if(!is_viable_patient(H)) return null last_found = world.time if(COOLDOWN_FINISHED(src, last_newpatient_speak)) COOLDOWN_START(src, last_newpatient_speak, MEDBOT_NEW_PATIENTSPEAK_DELAY) - var/list/messagevoice = list("Hey, [H.name]! Hold on, I'm coming." = 'sound/voice/medbot/coming.ogg',"Wait [H.name]! I want to help!" = 'sound/voice/medbot/help.ogg',"[H.name], you appear to be injured!" = 'sound/voice/medbot/injured.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 50, FALSE) + medbot_phrase(pick(located_patient_phrases), H) return H /* @@ -241,16 +318,7 @@ return COOLDOWN_START(src, last_tipping_action_voice, MEDBOT_FREAKOUT_DELAY) // message for tipping happens when we start interacting, message for righting comes after finishing - var/static/list/messagevoice = list( - "Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg', - "Please don't..." = 'sound/voice/medbot/please_dont.ogg', - "I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', - "Nooo..." = 'sound/voice/medbot/nooo.ogg', - "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg', - ) - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 70, FALSE) + medbot_phrase(pick(pre_tip_phrases), user) /* * Proc used in a callback for after this medibot is tipped by the tippable component. @@ -258,7 +326,7 @@ * user - the mob who tipped us over */ /mob/living/simple_animal/bot/medbot/proc/after_tip_over(mob/user) - mode = BOT_TIPPED + set_mode(BOT_TIPPED) tipper_name = user.name playsound(src, 'sound/machines/warning-buzzer.ogg', 50) @@ -268,50 +336,49 @@ * user - the mob who righted us. Can be null. */ /mob/living/simple_animal/bot/medbot/proc/after_righted(mob/user) - var/list/messagevoice + var/phrase if(user) if(user.name == tipper_name) - messagevoice = list("I forgive you." = 'sound/voice/medbot/forgive.ogg') + phrase = MEDIBOT_VOICED_FORGIVE else - messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg') + phrase = pick(MEDIBOT_VOICED_THANKS, MEDIBOT_VOICED_GOOD_PERSON) else - messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg') + phrase = pick(MEDIBOT_VOICED_FUCK_YOU, MEDIBOT_VOICED_BEHAVIOUR_REPORTED) + tipper_name = null if(COOLDOWN_FINISHED(src, last_tipping_action_voice)) COOLDOWN_START(src, last_tipping_action_voice, MEDBOT_FREAKOUT_DELAY) - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 70) + medbot_phrase(phrase, user) + tipped_status = MEDBOT_PANIC_NONE - mode = BOT_IDLE + set_mode(BOT_IDLE) /// if someone tipped us over, check whether we should ask for help or just right ourselves eventually /mob/living/simple_animal/bot/medbot/proc/handle_panic() tipped_status++ - var/list/messagevoice + var/phrase switch(tipped_status) if(MEDBOT_PANIC_LOW) - messagevoice = list("I require assistance." = 'sound/voice/medbot/i_require_asst.ogg') + phrase = MEDIBOT_VOICED_ASSISTANCE if(MEDBOT_PANIC_MED) - messagevoice = list("Please put me back." = 'sound/voice/medbot/please_put_me_back.ogg') + phrase = MEDIBOT_VOICED_PUT_BACK if(MEDBOT_PANIC_HIGH) - messagevoice = list("Please, I am scared!" = 'sound/voice/medbot/please_im_scared.ogg') + phrase = MEDIBOT_VOICED_IM_SCARED if(MEDBOT_PANIC_FUCK) - messagevoice = list("I don't like this, I need help!" = 'sound/voice/medbot/dont_like.ogg', "This hurts, my pain is real!" = 'sound/voice/medbot/pain_is_real.ogg') + phrase = pick(MEDIBOT_VOICED_NEED_HELP, MEDIBOT_VOICED_THIS_HURTS) if(MEDBOT_PANIC_ENDING) - messagevoice = list("Is this the end?" = 'sound/voice/medbot/is_this_the_end.ogg', "Nooo!" = 'sound/voice/medbot/nooo.ogg') + phrase = pick(MEDIBOT_VOICED_NOOO, MEDIBOT_VOICED_THE_END) if(MEDBOT_PANIC_END) speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel) if(prob(tipped_status)) do_jitter_animation(tipped_status * 0.1) - if(messagevoice) - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 70) + if(phrase) + medbot_phrase(phrase) + else if(prob(tipped_status * 0.2)) playsound(src, 'sound/machines/warning-buzzer.ogg', 30, extrarange=-2) @@ -328,14 +395,15 @@ return if(IsStun() || IsParalyzed()) - oldpatient = patient + previous_patient = WEAKREF(patient) patient = null - mode = BOT_IDLE + set_mode(BOT_IDLE) return - if(frustration > 8) - oldpatient = patient + if(frustration > 5) + previous_patient = WEAKREF(patient) soft_reset() + medbot_phrase(MEDIBOT_VOICED_FUCK_YOU) if(QDELETED(patient)) if(medical_mode_flags & MEDBOT_SPEAK_MODE && prob(1)) @@ -349,48 +417,44 @@ ) playsound(src, pick(i_need_scissors), 70) else - var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg', "Why are we still here? Just to suffer?" = 'sound/voice/medbot/why.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 50) + medbot_phrase(pick(idle_phrases)) + var/scan_range = (medical_mode_flags & MEDBOT_STATIONARY_MODE ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. - patient = scan(list(/mob/living/carbon/human), oldpatient, scan_range) - oldpatient = patient - - if(patient && (get_dist(src,patient) <= 1) && !tending) //Patient is next to us, begin treatment! - if(mode != BOT_HEALING) - mode = BOT_HEALING - update_appearance() - frustration = 0 - medicate_patient(patient) - return + patient = scan(list(/mob/living/carbon/human), scan_range = scan_range) + //previous_patient = WEAKREF(patient) + + if(patient) + if((get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! + if(mode != BOT_HEALING) + update_appearance() + frustration = 0 + try_medicate_patient(patient) + return - //Patient has moved away from us! - else if(patient && path.len && (get_dist(patient,path[path.len]) > 2)) - path = list() - mode = BOT_IDLE - last_found = world.time + else if(medical_mode_flags & MEDBOT_STATIONARY_MODE) //Since we cannot move in this mode, ignore the patient and wait for another. + soft_reset() + return - else if(medical_mode_flags & MEDBOT_STATIONARY_MODE && patient) //Since we cannot move in this mode, ignore the patient and wait for another. - soft_reset() - return + //Patient has moved away from us! + else if(!path.len || (path.len && (get_dist(patient,path[path.len]) > 2))) + path = list() + set_mode(BOT_IDLE) + last_found = world.time - if(patient && path.len == 0 && (get_dist(src,patient) > 1) && mode != BOT_MOVING) - mode = BOT_MOVING - path = jps_path_to(src, patient, max_distance=30, access = access_card?.GetAccess()) - if(!path.len) //try to get closer if you can't reach the patient directly + if(path.len == 0 && (get_dist(src,patient) > 1)) + set_mode(BOT_PATHING) path = jps_path_to(src, patient, max_distance=30, mintargetdist=1, access = access_card?.GetAccess()) + set_mode(BOT_MOVING) if(!path.len) //Do not chase a patient we cannot reach. + add_to_ignore(patient) soft_reset() - if(path.len > 0 && patient) - if(!bot_move(path[path.len])) - oldpatient = patient - soft_reset() - return - - if(path.len > 8 && patient) - frustration++ + if(path.len > 0) + frustration++ + if(!bot_move(path[path.len])) + previous_patient = WEAKREF(patient) + soft_reset() + return if(bot_mode_flags & BOT_MODE_AUTOPATROL && !(medical_mode_flags & MEDBOT_STATIONARY_MODE) && !patient) switch(mode) @@ -399,66 +463,60 @@ if(BOT_PATROL) bot_patrol() -/mob/living/simple_animal/bot/medbot/proc/assess_patient(mob/living/carbon/C) - . = FALSE +/// Returns a reagent to inject, if we can treat the mob. +/mob/living/simple_animal/bot/medbot/proc/is_viable_patient(mob/living/carbon/C, declare_crit = TRUE) //Time to see if they need medical help! - if(medical_mode_flags & MEDBOT_STATIONARY_MODE && !Adjacent(C)) //YOU come to ME, BRO - return FALSE + if((medical_mode_flags & MEDBOT_STATIONARY_MODE) && !Adjacent(C)) //YOU come to ME, BRO + return if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - return FALSE //welp too late for them! + return //welp too late for them! if(!(loc == C.loc) && !(isturf(C.loc) && isturf(loc))) - return FALSE + return if(C.suiciding) - return FALSE //Kevorkian school of robotic medical assistants. + return //Kevorkian school of robotic medical assistants. if(bot_cover_flags & BOT_COVER_EMAGGED) //Everyone needs our medicine. (Our medicine is toxins) - return TRUE + return if(HAS_TRAIT(C, TRAIT_MEDIBOTCOMINGTHROUGH) && !HAS_TRAIT_FROM(C, TRAIT_MEDIBOTCOMINGTHROUGH, tag)) //the early medbot gets the worm (or in this case the patient) - return FALSE - - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) - var/obj/item/clothing/CS = H.wear_suit - var/obj/item/clothing/CH = H.head - if (CS.clothing_flags & CH.clothing_flags & THICKMATERIAL) - return FALSE // Skip over them if they have no exposed flesh. + return - if(medical_mode_flags & MEDBOT_DECLARE_CRIT && C.health <= 0) //Critical condition! Call for help! + //Critical condition! Call for help! + if(declare_crit && (medical_mode_flags & MEDBOT_DECLARE_CRIT) && C.undergoing_cardiac_arrest()) declare(C) + if(!C.try_inject(src, zone_selected || BODY_ZONE_CHEST)) + return + + if(bot_cover_flags & BOT_COVER_EMAGGED) + return healing_reagents["emag"] + //They're injured enough for it! - var/list/treat_me_for = list() - if(C.getBruteLoss() > heal_threshold) - treat_me_for += BRUTE + var/heals_all = damagetype_healer == "all" - if(C.getOxyLoss() > (5 + heal_threshold)) - treat_me_for += OXY + if((heals_all || (damagetype_healer == BRUTE)) && C.getBruteLoss() > heal_threshold && !C.bloodstream.has_reagent(healing_reagents[BRUTE])) + return healing_reagents[BRUTE] - if(C.getFireLoss() > heal_threshold) - treat_me_for += BURN + if((heals_all || (damagetype_healer == BURN)) && C.getFireLoss() > heal_threshold && !C.bloodstream.has_reagent(healing_reagents[BURN])) + return healing_reagents[BURN] - if(C.getToxLoss() > heal_threshold) - treat_me_for += TOX + if((heals_all || (damagetype_healer == TOX)) && C.getToxLoss() > heal_threshold && !C.bloodstream.has_reagent(healing_reagents[TOX])) + return healing_reagents[TOX] - if(damagetype_healer in treat_me_for) - return TRUE - if(damagetype_healer == "all" && treat_me_for.len) - return TRUE + if((heals_all || (damagetype_healer == OXY)) && C.getOxyLoss() > (5 + heal_threshold) && !C.bloodstream.has_reagent(healing_reagents[OXY])) + return healing_reagents[OXY] /mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A, proximity_flag, list/modifiers) if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) return - if(iscarbon(A) && !tending) + if(iscarbon(A) && mode != BOT_HEALING) var/mob/living/carbon/C = A patient = C - mode = BOT_HEALING update_appearance() - medicate_patient(C) + try_medicate_patient(C) update_appearance() return ..() @@ -468,91 +526,70 @@ if(!is_blind()) chemscan(src, A) -/mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) +/// Attempt to inject a patient with the goods. +/mob/living/simple_animal/bot/medbot/proc/try_medicate_patient(mob/living/carbon/C) if(!(bot_mode_flags & BOT_MODE_ON)) return if(!istype(C)) - oldpatient = patient + previous_patient = WEAKREF(patient) soft_reset() return if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - var/list/messagevoice = list("No! Stay with me!" = 'sound/voice/medbot/no.ogg',"Live, damnit! LIVE!" = 'sound/voice/medbot/live.ogg',"I...I've never lost a patient before. Not today, I mean." = 'sound/voice/medbot/lost.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 50) - oldpatient = patient + medbot_phrase(pick(patient_died_phrases), C) + previous_patient = WEAKREF(patient) soft_reset() return - tending = TRUE - while(tending) - var/treatment_method - var/list/potential_methods = list() - - if(C.getBruteLoss() > heal_threshold) - potential_methods += BRUTE - - if(C.getFireLoss() > heal_threshold) - potential_methods += BURN + if(!is_viable_patient(C, declare_crit = FALSE)) + previous_patient = WEAKREF(patient) + soft_reset() + return - if(C.getOxyLoss() > (5 + heal_threshold)) - potential_methods += OXY + set_mode(BOT_HEALING) - if(C.getToxLoss() > heal_threshold) - potential_methods += TOX + visible_message(span_warning("[src] is trying to inject [C].")) - for(var/i in potential_methods) - if(i != damagetype_healer) - continue - treatment_method = i + var/datum/callback/medicate_check = CALLBACK(src, PROC_REF(medicate_callback), C) + while(TRUE) + if(!patient) + break - if(damagetype_healer == "all" && potential_methods.len) - treatment_method = pick(potential_methods) + var/reagent_type = is_viable_patient(C, declare_crit = FALSE) - if(!treatment_method && !(bot_cover_flags & BOT_COVER_EMAGGED)) //If they don't need any of that they're probably cured! + if(!reagent_type) //If they don't need any of that they're probably cured! if(C.maxHealth - C.get_organic_health() < heal_threshold) to_chat(src, span_notice("[C] is healthy! Your programming prevents you from tending the wounds of anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 5] for oxygen damage.)")) - var/list/messagevoice = list("All patched up!" = 'sound/voice/medbot/patchedup.ogg',"An apple a day keeps me away." = 'sound/voice/medbot/apple.ogg',"Feel better soon!" = 'sound/voice/medbot/feelbetter.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 50) + visible_message(span_notice("[src] retracts it's syringe arm.")) + medbot_phrase(pick(finish_healing_phrases), C) bot_reset() - tending = FALSE - else if(patient) - C.visible_message(span_danger("[src] is trying to tend the wounds of [patient]!"), \ - span_userdanger("[src] is trying to tend your wounds!")) - - if(do_after(src, patient, 20)) //Slightly faster than default tend wounds, but does less HPS - if((get_dist(src, patient) <= 1) && (bot_mode_flags & BOT_MODE_ON) && assess_patient(patient)) - var/healies = heal_amount - var/obj/item/storage/medkit/medkit = medkit_type - if(treatment_method == BRUTE && initial(medkit.damagetype_healed) == BRUTE) //specialized brute gets a bit of bonus, as a snack. - healies *= 1.1 - if(bot_cover_flags & BOT_COVER_EMAGGED) - patient.reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 5) - patient.apply_damage((healies * 1), treatment_method, spread_damage = TRUE) - log_combat(src, patient, "pretended to tend wounds on", "internal tools", "([uppertext(treatment_method)]) (EMAGGED)") - else - patient.apply_damage((healies * -1), treatment_method) //don't need to check treatment_method since we know by this point that they were actually damaged. - log_combat(src, patient, "tended the wounds of", "internal tools", "([uppertext(treatment_method)])") - C.visible_message(span_notice("[src] tends the wounds of [patient]!"), \ - "[span_green("[src] tends your wounds!")]") - ADD_TRAIT(patient,TRAIT_MEDIBOTCOMINGTHROUGH,tag) - addtimer(TRAIT_CALLBACK_REMOVE(patient, TRAIT_MEDIBOTCOMINGTHROUGH, tag), (30 SECONDS)) - else - tending = FALSE - else - tending = FALSE + return - update_appearance() - if(!tending) - visible_message("[src] places its tools back into itself.") - soft_reset() - else - tending = FALSE + ADD_TRAIT(patient,TRAIT_MEDIBOTCOMINGTHROUGH, tag) + addtimer(TRAIT_CALLBACK_REMOVE(patient, TRAIT_MEDIBOTCOMINGTHROUGH, tag), (30 SECONDS), TIMER_UNIQUE|TIMER_OVERRIDE) + + visible_message(span_warning("[src] is trying to inject [patient].")) + + if(!do_after(src, patient, heal_time, DO_PUBLIC, extra_checks = medicate_check, display = image('icons/obj/syringe.dmi', "syringe_0"))) //Slightly faster than default tend wounds, but does less HPS + break + + var/datum/reagent/R = SSreagents.chemical_reagents_list[reagent_type] + R.expose_mob(patient, injection_amount, methods = INJECT) + patient.bloodstream.add_reagent(R.type, injection_amount) + + log_combat(src, patient, "injected", "internal tools", "([injection_amount]u [reagent_type][bot_cover_flags & BOT_COVER_EMAGGED ? " (EMAGGED)" : ""])") + + visible_message(span_notice("[src] injects [patient].")) + + visible_message(span_notice("[src] retracts it's syringe arm.")) + soft_reset() + +/mob/living/simple_animal/bot/medbot/proc/medicate_callback(mob/living/carbon/target) + if((get_dist(src, patient) > 1) || !(bot_mode_flags & BOT_MODE_ON) || !is_viable_patient(patient, declare_crit = FALSE)) + return FALSE + return TRUE /mob/living/simple_animal/bot/medbot/explode() var/atom/Tsec = drop_location() @@ -569,8 +606,17 @@ if(!COOLDOWN_FINISHED(src, last_patient_message)) return COOLDOWN_START(src, last_patient_message, MEDBOT_PATIENTSPEAK_DELAY) - var/area/location = get_area(src) - speak("Medical emergency! [crit_patient || "A patient"] is in critical condition at [location]!", radio_channel) + + var/area/location = get_area(crit_patient) + speak("Medical emergency! [crit_patient] is in critical condition at [location]!", radio_channel) + +/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_NEW_PATIENTSPEAK_DELAY #undef MEDBOT_PATIENTSPEAK_DELAY diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 5e3412601f47..dec7c07d6327 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -249,7 +249,7 @@ switch(mode) if(BOT_IDLE, BOT_DELIVER, BOT_GO_HOME) data["modeStatus"] = "good" - if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV) + if(BOT_BLOCKED, BOT_NAV, BOT_WAIT_FOR_NAV, BOT_PATHING) data["modeStatus"] = "average" if(BOT_NO_ROUTE) data["modeStatus"] = "bad" @@ -409,7 +409,7 @@ return load = AM - mode = BOT_IDLE + set_mode(BOT_IDLE) update_appearance() ///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it. @@ -438,7 +438,7 @@ update_appearance() return - mode = BOT_IDLE + set_mode(BOT_IDLE) var/atom/movable/cached_load = load //cache the load since unbuckling mobs clears the var. @@ -529,55 +529,55 @@ blockcount = 0 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 @@ -596,9 +596,9 @@ if(!(bot_mode_flags & BOT_MODE_ON)) return if(destination == home_destination) - mode = BOT_GO_HOME + set_mode(BOT_GO_HOME) else - mode = BOT_DELIVER + set_mode(BOT_DELIVER) get_nav() // starts bot moving to home @@ -610,7 +610,7 @@ /mob/living/simple_animal/bot/mulebot/proc/do_start_home() set_destination(home_destination) - mode = BOT_BLOCKED + set_mode(BOT_BLOCKED) // called when bot reaches current target /mob/living/simple_animal/bot/mulebot/proc/at_target() @@ -651,7 +651,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 @@ -828,7 +828,7 @@ return load = AM - mode = BOT_IDLE + set_mode(BOT_IDLE) update_appearance() /mob/living/simple_animal/bot/mulebot/paranormal/update_overlays() diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index e3ffc5661092..1b38041206fb 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -125,7 +125,7 @@ /mob/living/simple_animal/bot/secbot/turn_off() ..() - mode = BOT_IDLE + set_mode(BOT_IDLE) /mob/living/simple_animal/bot/secbot/bot_reset() ..() @@ -183,7 +183,7 @@ threatlevel += 6 if(threatlevel >= 4) target = attacking_human - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/secbot/proc/judgement_criteria() var/final = FALSE @@ -276,7 +276,7 @@ ..() /mob/living/simple_animal/bot/secbot/proc/start_handcuffing(mob/living/carbon/current_target) - mode = BOT_ARREST + set_mode(BOT_ARREST) playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) current_target.visible_message(span_danger("[src] is trying to put zipties on [current_target]!"),\ span_userdanger("[src] is trying to put zipties on you!")) @@ -320,7 +320,7 @@ span_userdanger("[src] stuns you!")) target_lastloc = target.loc - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) /mob/living/simple_animal/bot/secbot/handle_automated_action() . = ..() @@ -333,7 +333,7 @@ SSmove_manager.stop_looping(src) look_for_perp() // see if any criminals are in range if((mode == BOT_IDLE) && bot_mode_flags & BOT_MODE_AUTOPATROL) // didn't start hunting during look_for_perp, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode + set_mode(BOT_START_PATROL) if(BOT_HUNT) // hunting for perp // if can't reach perp for long enough, go idle @@ -378,7 +378,7 @@ if(BOT_ARREST) if(!target) set_anchored(FALSE) - mode = BOT_IDLE + set_mode(BOT_IDLE) last_found = world.time frustration = 0 return @@ -391,7 +391,7 @@ back_to_hunt() return else //Try arresting again if the target escapes. - mode = BOT_PREP_ARREST + set_mode(BOT_PREP_ARREST) set_anchored(FALSE) if(BOT_START_PATROL) @@ -404,7 +404,7 @@ /mob/living/simple_animal/bot/secbot/proc/back_to_idle() set_anchored(FALSE) - mode = BOT_IDLE + set_mode(BOT_IDLE) target = null last_found = world.time frustration = 0 @@ -413,7 +413,7 @@ /mob/living/simple_animal/bot/secbot/proc/back_to_hunt() set_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 @@ -447,7 +447,7 @@ playsound(src, pick('sound/voice/beepsky/criminal.ogg', 'sound/voice/beepsky/justice.ogg', 'sound/voice/beepsky/freeze.ogg'), 50, FALSE) visible_message("[src] points at [nearby_carbons.name]!") - mode = BOT_HUNT + set_mode(BOT_HUNT) INVOKE_ASYNC(src, PROC_REF(handle_automated_action)) break @@ -498,7 +498,7 @@ . = ..() if(!isalien(target)) target = user - mode = BOT_HUNT + set_mode(BOT_HUNT) /mob/living/simple_animal/bot/secbot/proc/on_entered(datum/source, atom/movable/AM) SIGNAL_HANDLER diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 3f89815a8f0d..e50ceb906d98 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -201,7 +201,10 @@ /// Update the pixel_y value of all huds attached to us. /atom/proc/update_hud_images_height() var/new_pixel_y = get_hud_pixel_y() - for(var/hud in hud_possible) + for(var/hud in hud_list) + var/image/I = hud_list[hud] + if(!isimage(I) || I.override) + continue set_hud_image_vars(hud, new_pixel_y = new_pixel_y, pixel_y_only = TRUE) /** diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 898cb4fca592..323c084505d5 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -219,9 +219,9 @@ value = 6 /datum/reagent/medicine/tricordrazine/affect_blood(mob/living/carbon/C, removed) - var/heal = 1 + ((clamp(round(current_cycle % 10), 0, 3))) * removed + var/heal = (1 + clamp(floor(current_cycle / 25), 0, 5)) * removed C.heal_overall_damage(heal, heal, updating_health = FALSE) - C.adjustToxLoss(-heal * removed, FALSE) + C.adjustToxLoss(-heal, FALSE) return TRUE /datum/reagent/medicine/tricordrazine/godblood