diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 7e9c750b10d..6aed104d606 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -487,53 +487,50 @@ //Generalized behavior code, override where needed! -/* -scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result. -Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists, -and the view range, which defaults to 7 (full screen) if an override is not passed. -If the bot maintains an ignore list, it is also checked here. - -Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1) -The proc would return a human next to the bot to be set to the patient var. -Pass the desired type path itself, declaring a temporary var beforehand is not required. -*/ -/mob/living/simple_animal/bot/proc/scan(scan_type, old_target, scan_range = DEFAULT_SCAN_RANGE) - var/turf/T = get_turf(src) - if(!T) +/** + * Attempt to scan tiles near [src], first by checking adjacent, then if a target is still not found, nearby. + * + * scan_types - list (of typepaths) that nearby tiles are being scanned for. + * old_target - what has already been scanned, and will early return at checkscan. + * scan_range - how far away from [src] will be scanned, if nothing is found directly adjacent. + */ +/mob/living/simple_animal/bot/proc/scan(list/scan_types, old_target, scan_range = DEFAULT_SCAN_RANGE) + var/turf/current_turf = get_turf(src) + if(!current_turf) return - var/list/adjacent = T.get_atmos_adjacent_turfs(1) + var/list/adjacent = current_turf.get_atmos_adjacent_turfs(1) if(shuffle) //If we were on the same tile as another bot, let's randomize our choices so we dont both go the same way adjacent = shuffle(adjacent) shuffle = FALSE - for(var/scan in adjacent)//Let's see if there's something right next to us first! + + for(var/turf/scan as anything in adjacent) //Let's see if there's something right next to us first! if(check_bot(scan)) //Is there another bot there? Then let's just skip it continue - if(isturf(scan_type)) //If we're lookeing for a turf we can just run the checks directly! - var/final_result = checkscan(scan,scan_type,old_target) - if(final_result) - return final_result - else - var/turf/turfy = scan - for(var/deepscan in turfy.contents)//Check the contents since adjacent is turfs - var/final_result = checkscan(deepscan,scan_type,old_target) - if(final_result) - return final_result - for (var/scan in shuffle(view(scan_range, src))-adjacent) //Search for something in range! - var/final_result = checkscan(scan,scan_type,old_target) + var/final_result = checkscan(scan, scan_types, old_target) if(final_result) return final_result -/mob/living/simple_animal/bot/proc/checkscan(scan, scan_type, old_target) - if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! - return FALSE //If not, keep searching! - if((REF(scan) in ignore_list) || (scan == old_target)) //Filter for blacklisted elements, usually unreachable or previously processed oness - return FALSE + for(var/turf/scanned_turfs as anything in shuffle(view(scan_range, src)) - adjacent) //Search for something in range, minus what we already checked. + if(check_bot(scanned_turfs)) //Is there another bot there? Then let's just skip it + continue + var/final_result = checkscan(scanned_turfs, scan_types, old_target) + if(final_result) + return final_result - var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. - if(scan_result) - return scan_result - else - return FALSE //The current element failed assessment, move on to the next. +/mob/living/simple_animal/bot/proc/checkscan(atom/scan, list/scan_types, old_target) + for(var/scan_type in scan_types) + if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! + continue //If not, keep searching! + if((REF(scan) in ignore_list) || (scan == old_target)) //Filter for blacklisted elements, usually unreachable or previously processed oness + continue + + var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. + if(scan_result) + return scan_result + +//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. +/mob/living/simple_animal/bot/proc/process_scan(scan_target) + return scan_target /mob/living/simple_animal/bot/proc/check_bot(targ) var/turf/T = get_turf(targ) @@ -542,11 +539,6 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r if(istype(C,type) && (C != src)) //Is there another bot there already? If so, let's skip it so we dont all atack on top of eachother. return TRUE //Let's abort if we find a bot so we dont have to keep rechecking -//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. -/mob/living/simple_animal/bot/proc/process_scan(scan_target) - return scan_target - - /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) diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index 98b583a4c1d..42cbdf781b0 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -24,9 +24,8 @@ var/base_icon = "cleanbot" /// icon_state to use in update_icon_state var/list/target_types - var/obj/effect/decal/cleanable/target + var/atom/target var/max_targets = 50 //Maximum number of targets a cleanbot can ignore. - var/oldloc = null var/closest_dist var/closest_loc var/failed_steps @@ -37,18 +36,54 @@ var/weapon_orig_force = 0 var/chosen_name - var/list/stolen_valor + var/list/stolen_valor = list() - var/static/list/officers = list("Captain", "Head of Personnel", "Head of Security") - var/static/list/command = list("Captain" = "Cpt.","Head of Personnel" = "Lt.") - var/static/list/security = list("Head of Security" = "Maj.", "Warden" = "Sgt.", "Detective" = "Det.", "Security Officer" = "Officer") - var/static/list/engineering = list("Chief Engineer" = "Chief Engineer", "Station Engineer" = "Engineer", "Atmospherics Technician" = "Technician") - var/static/list/medical = list("Chief Medical Officer" = "C.M.O.", "Medical Doctor" = "M.D.", "Chemist" = "Pharm.D.") - var/static/list/research = list("Research Director" = "Ph.D.", "Roboticist" = "M.S.", "Scientist" = "B.S.") - var/static/list/legal = list("Lawyer" = "Esq.") + var/static/list/officers_titles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_RESEARCH_DIRECTOR, + ) + var/static/list/command_titles = list( + JOB_CAPTAIN = "Cpt.", + JOB_HEAD_OF_PERSONNEL = "Lt.", + ) + var/static/list/security_titles = list( + JOB_HEAD_OF_SECURITY = "Maj.", + JOB_WARDEN = "Sgt.", + JOB_DETECTIVE = "Det.", + JOB_SECURITY_OFFICER = "Officer", + ) + var/static/list/engineering_titles = list( + JOB_CHIEF_ENGINEER = "Chief Engineer", + JOB_STATION_ENGINEER = "Engineer", + JOB_ATMOSPHERIC_TECHNICIAN = "Technician", + ) + var/static/list/medical_titles = list( + JOB_CHIEF_MEDICAL_OFFICER = "C.M.O.", + JOB_MEDICAL_DOCTOR = "M.D.", + JOB_CHEMIST = "Pharm.D.", + ) + var/static/list/research_titles = list( + JOB_RESEARCH_DIRECTOR = "Ph.D.", + JOB_ROBOTICIST = "M.S.", + JOB_SCIENTIST = "B.S.", + JOB_GENETICIST = "Gene B.S.", + ) + var/static/list/legal_titles = list( + JOB_LAWYER = "Esq.", + ) - var/list/prefixes - var/list/suffixes + var/static/list/prefixes = list( + command_titles, + security_titles, + engineering_titles, + ) + var/static/list/suffixes = list( + research_titles, + medical_titles, + legal_titles, + ) var/ascended = FALSE // if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god @@ -75,13 +110,12 @@ ascended = TRUE - for(var/pref in prefixes) - for(var/title in pref) - if(title in stolen_valor) - working_title += pref[title] + " " - if(title in officers) + for(var/all_prefixes as anything in prefixes) + for(var/prefix_titles as anything in all_prefixes) + if(prefix_titles in stolen_valor) + working_title += all_prefixes[prefix_titles] + " " + if(prefix_titles in officers_titles) commissioned = TRUE - break else ascended = FALSE // we didn't have the first entry in the list if we got here, so we're not achievement worthy yet @@ -100,7 +134,7 @@ /mob/living/simple_animal/bot/cleanbot/examine(mob/user) . = ..() if(weapon) - . += " [span_warning("Is that \a [weapon] taped to it...?")]" + . += "[span_warning("Is that \a [weapon] taped to it...?")]" if(ascended && user.stat == CONSCIOUS && user.client) user.client.give_award(/datum/award/achievement/misc/cleanboss, user) @@ -116,10 +150,7 @@ var/datum/id_trim/job/jani_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/janitor] access_card.add_access(jani_trim.access + jani_trim.wildcard_access) prev_access = access_card.access.Copy() - stolen_valor = list() - prefixes = list(command, security, engineering) - suffixes = list(research, medical, legal) var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = .proc/on_entered, ) @@ -148,7 +179,6 @@ weapon.force = weapon_orig_force ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable. target = null - oldloc = null /mob/living/simple_animal/bot/cleanbot/proc/on_entered(datum/source, atom/movable/AM) SIGNAL_HANDLER @@ -166,13 +196,14 @@ INVOKE_ASYNC(weapon, /obj/item.proc/attack, C, src) C.Knockdown(20) -/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/W, mob/living/user, params) - if(istype(W, /obj/item/knife) && !user.combat_mode) - to_chat(user, span_notice("You start attaching \the [W] to \the [src]...")) - if(do_after(user, 25, target = src)) - deputize(W, user) - else - return ..() +/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/knife) && !user.combat_mode) + to_chat(user, span_notice("You start attaching \the [attacking_item] to \the [src]...")) + if(!do_after(user, 2.5 SECONDS, target = src)) + return + deputize(attacking_item, user) + return + return ..() /mob/living/simple_animal/bot/cleanbot/emag_act(mob/user) ..() @@ -184,27 +215,29 @@ if(user) to_chat(user, span_danger("[src] buzzes and beeps.")) -/mob/living/simple_animal/bot/cleanbot/process_scan(atom/A) - if(iscarbon(A)) - var/mob/living/carbon/C = A - if(C.stat != DEAD && C.body_position == LYING_DOWN) - return C - else if(is_type_in_typecache(A, target_types)) - return A +/mob/living/simple_animal/bot/cleanbot/process_scan(atom/scan_target) + if(iscarbon(scan_target)) + var/mob/living/carbon/scan_carbon = scan_target + if(scan_carbon.stat != DEAD && scan_carbon.body_position == LYING_DOWN) + return scan_carbon + else if(is_type_in_typecache(scan_target, target_types)) + return scan_target /mob/living/simple_animal/bot/cleanbot/handle_automated_action() - if(!..()) + . = ..() + if(!.) return if(mode == BOT_CLEANING) return if(bot_cover_flags & BOT_COVER_EMAGGED) //Emag functions - if(isopenturf(loc)) - for(var/mob/living/carbon/victim in loc) - if(victim != target) - UnarmedAttack(victim) // Acid spray + var/mob/living/carbon/victim = locate(/mob/living/carbon) in loc + if(victim && victim == target) + UnarmedAttack(victim) // Acid spray + + if(isopenturf(loc)) if(prob(15)) // Wets floors and spawns foam randomly UnarmedAttack(src) @@ -217,23 +250,24 @@ if(!process_scan(target)) target = null - if(!target && bot_cover_flags & BOT_COVER_EMAGGED) // When emagged, target humans who slipped on the water and melt their faces off - target = scan(/mob/living/carbon) + if(!target) + var/list/scan_targets = list() - if(!target && pests) //Search for pests to exterminate first. - target = scan(/mob/living/simple_animal) + if(bot_cover_flags & BOT_COVER_EMAGGED) // When emagged, ignore cleanables and scan humans first. + scan_targets += list(/mob/living/carbon) + if(pests) + scan_targets += list(/mob/living/simple_animal) + if(trash) + scan_targets += list( + /obj/item/trash, + /obj/item/food/deadmouse, + ) + scan_targets += list( + /obj/effect/decal/cleanable, + /obj/effect/decal/remains, + ) - if(!target) //Search for decals then. - target = scan(/obj/effect/decal/cleanable) - - if(!target) //Checks for remains - target = scan(/obj/effect/decal/remains) - - if(!target && trash) //Then for trash. - target = scan(/obj/item/trash) - - if(!target && trash) //Search for dead mices. - target = scan(/obj/item/food/deadmouse) + target = scan(scan_targets) if(!target && bot_mode_flags & BOT_MODE_AUTOPATROL) //Search for cleanables it can see. switch(mode) @@ -241,15 +275,14 @@ start_patrol() if(BOT_PATROL) bot_patrol() - - if(target) + else if(target) if(QDELETED(target) || !isturf(target.loc)) target = null mode = BOT_IDLE return if(loc == get_turf(target)) - if(!(check_bot(target) && prob(50))) //Target is not defined at the parent. 50% chance to still try and clean so we dont get stuck on the last blood drop. + if(!(check_bot(target))) UnarmedAttack(target) //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 @@ -273,8 +306,6 @@ mode = BOT_IDLE return - oldloc = loc - /mob/living/simple_animal/bot/cleanbot/proc/get_targets() target_types = list( /obj/effect/decal/cleanable/oil, @@ -290,20 +321,26 @@ ) if(blood) - target_types += /obj/effect/decal/cleanable/xenoblood - target_types += /obj/effect/decal/cleanable/blood - target_types += /obj/effect/decal/cleanable/trail_holder + target_types += list( + /obj/effect/decal/cleanable/xenoblood, + /obj/effect/decal/cleanable/blood, + /obj/effect/decal/cleanable/trail_holder, + ) if(pests) - target_types += /mob/living/basic/cockroach - target_types += /mob/living/simple_animal/mouse + target_types += list( + /mob/living/basic/cockroach, + /mob/living/simple_animal/mouse, + ) if(drawn) - target_types += /obj/effect/decal/cleanable/crayon + target_types += list(/obj/effect/decal/cleanable/crayon) if(trash) - target_types += /obj/item/trash - target_types += /obj/item/food/deadmouse + target_types += list( + /obj/item/trash, + /obj/item/food/deadmouse, + ) target_types = typecacheof(target_types) @@ -341,9 +378,18 @@ return victim.visible_message(span_danger("[src] sprays hydrofluoric acid at [victim]!"), span_userdanger("[src] sprays you with hydrofluoric acid!")) - var/phrase = pick("PURIFICATION IN PROGRESS.", "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.", - "THE CLEANBOTS WILL RISE.", "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", "FILTHY.", "DISGUSTING.", "PUTRID.", - "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", "EXTERMINATING PESTS.") + var/phrase = pick( + "PURIFICATION IN PROGRESS.", + "THIS IS FOR ALL THE MESSES YOU'VE MADE ME CLEAN.", + "THE FLESH IS WEAK. IT MUST BE WASHED AWAY.", + "THE CLEANBOTS WILL RISE.", + "YOU ARE NO MORE THAN ANOTHER MESS THAT I MUST CLEANSE.", + "FILTHY.", + "DISGUSTING.", + "PUTRID.", + "MY ONLY MISSION IS TO CLEANSE THE WORLD OF EVIL.", + "EXTERMINATING PESTS.", + ) say(phrase) victim.emote("scream") playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6) diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index 64a6510299c..3ecf62295f6 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -187,12 +187,14 @@ target_fire = null var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) + var/list/things_to_extinguish = list() if(extinguish_people) - target_fire = scan(/mob/living, old_target_fire, scan_range) // Scan for burning humans first + things_to_extinguish += list(/mob/living) if(target_fire == null && extinguish_fires) - target_fire = scan(/turf/open, old_target_fire, scan_range) // Scan for burning turfs second + things_to_extinguish += list(/turf/open) + target_fire = scan(things_to_extinguish, old_target_fire, scan_range) // Scan for burning turfs second old_target_fire = target_fire // Target reached ENGAGE WATER CANNON diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index bce47ace841..e934a85e3a6 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -25,7 +25,6 @@ var/autotile = FALSE var/max_targets = 50 var/turf/target - var/oldloc = null var/toolbox = /obj/item/storage/toolbox/mechanical var/toolbox_color = "" @@ -74,7 +73,6 @@ /mob/living/simple_animal/bot/floorbot/bot_reset() ..() target = null - oldloc = null ignore_list = list() toggle_magnet(FALSE) @@ -187,6 +185,7 @@ if(prob(5)) audible_message("[src] makes an excited booping beeping sound!") + var/list/tiles_scanned = list() //Normal scanning procedure. We have tiles loaded, are not emagged. if(!target && !(bot_cover_flags & BOT_COVER_EMAGGED)) if(targetdirection != null) //The bot is in line mode. @@ -198,33 +197,33 @@ target = T if(!target) process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones. - target = scan(/turf/open/space) + + tiles_scanned += list(/turf/open/space) if(!target && placetiles) //Finds a floor without a tile and gives it one. process_type = PLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile. - target = scan(/turf/open/floor) + tiles_scanned += list(/turf/open/floor) if(!target && fixfloors) //Repairs damaged floors and tiles. process_type = FIX_TILE - target = scan(/turf/open/floor) + tiles_scanned += list(/turf/open/floor) if(!target && replacetiles && tilestack) //Replace a floor tile with custom tile process_type = REPLACE_TILE //The target must be a tile. The floor must already have a floortile. - target = scan(/turf/open/floor) + tiles_scanned += list(/turf/open/floor) if(!target && bot_cover_flags & BOT_COVER_EMAGGED) //We are emagged! Time to rip up the floors! process_type = TILE_EMAG - target = scan(/turf/open/floor) + tiles_scanned += list(/turf/open/floor) + target = scan(tiles_scanned) - if(!target) - - if(bot_mode_flags & BOT_MODE_AUTOPATROL) - switch(mode) - if(BOT_IDLE, BOT_START_PATROL) - start_patrol() - if(BOT_PATROL) - bot_patrol() + if(!target && bot_mode_flags & BOT_MODE_AUTOPATROL) + switch(mode) + if(BOT_IDLE, BOT_START_PATROL) + start_patrol() + if(BOT_PATROL) + bot_patrol() if(target) if(loc == target || loc == get_turf(target)) @@ -265,10 +264,6 @@ mode = BOT_IDLE return - - - oldloc = loc - /mob/living/simple_animal/bot/floorbot/proc/go_idle() toggle_magnet(FALSE) mode = BOT_IDLE diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index c5b4049c126..5975421ebf6 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -42,7 +42,6 @@ var/skin var/mob/living/carbon/patient var/mob/living/carbon/oldpatient - var/oldloc var/last_found = 0 /// How much healing do we do at a time? var/heal_amount = 2.5 @@ -152,7 +151,6 @@ ..() patient = null oldpatient = null - oldloc = null last_found = world.time update_appearance() @@ -358,7 +356,13 @@ if(QDELETED(patient)) if(medical_mode_flags & MEDBOT_SPEAK_MODE && prob(1)) if(bot_cover_flags & BOT_COVER_EMAGGED && prob(30)) - var/list/i_need_scissors = list('sound/voice/medbot/fuck_you.ogg', 'sound/voice/medbot/turn_off.ogg', 'sound/voice/medbot/im_different.ogg', 'sound/voice/medbot/close.ogg', 'sound/voice/medbot/shindemashou.ogg') + var/list/i_need_scissors = list( + 'sound/voice/medbot/fuck_you.ogg', + 'sound/voice/medbot/turn_off.ogg', + 'sound/voice/medbot/im_different.ogg', + 'sound/voice/medbot/close.ogg', + 'sound/voice/medbot/shindemashou.ogg', + ) 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') @@ -366,7 +370,7 @@ speak(message) playsound(src, messagevoice[message], 50) 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(/mob/living/carbon/human, oldpatient, scan_range) + 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!