Refactors how bots scan for stuff (#66681)

Refactors how bots scan for stuff and also improves Cleanbots' code.

This started off as a Cleanbot refactor but I got too-deep into how it scans for stuff, so I decided to instead focus my PR on that first since it's something I want to tread carefully with.

removes oldloc var from Cleanbot, Medibot and Floorbot, because it was all unused.

I didn't even notice how bad this was before, because Secbots use their own snowflake thing instead of scanning like all the other bots do, which sounds like a good future PR.
This commit is contained in:
John Willard
2022-05-08 10:36:40 -07:00
committed by GitHub
parent 3caee59d05
commit 6d76199a85
5 changed files with 179 additions and 140 deletions
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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!