Skip to content

Commit

Permalink
basic bot path huds and medbot research (#126)
Browse files Browse the repository at this point in the history
* basic bot path huds and medbot research (#80277)

this pr integrates the bot path huds to ai controllers and move loops to
allow basic bots to display their paths in the hud.
also closes #80280 and closes #80330

basic bots now can display their path on huds

:cl:
add: basic bots can now display their paths on huds
fix: medbots can research healing again
/:cl:

* Update cleanbot_ai.dm

---------

Co-authored-by: Ben10Omintrix <[email protected]>
  • Loading branch information
2 people authored and Iajret committed Dec 28, 2023
1 parent e75c5ce commit f3c798d
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 20 deletions.
2 changes: 2 additions & 0 deletions code/__DEFINES/dcs/signals/signals_moveloop.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
#define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess"
//from [/datum/move_loop/has_target/jps/recalculate_path] ():
#define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath"
///from [/datum/move_loop/has_target/jps/on_finish_pathing]
#define COMSIG_MOVELOOP_JPS_FINISHED_PATHING "moveloop_jps_finished_pathing"
10 changes: 8 additions & 2 deletions code/controllers/subsystem/movement/movement_types.dm
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@
turf/avoid,
skip_first,
subsystem,
diagonal_handling,
priority,
flags,
datum/extra_info,
Expand All @@ -343,6 +344,7 @@
simulated_only,
avoid,
skip_first,
diagonal_handling,
initial_path)

/datum/move_loop/has_target/jps
Expand All @@ -360,6 +362,8 @@
var/turf/avoid
///Should we skip the first step? This is the tile we're currently on, which breaks some things
var/skip_first
///Whether we replace diagonal movements with cardinal movements or follow through with them
var/diagonal_handling
///A list for the path we're currently following
var/list/movement_path
///Cooldown for repathing, prevents spam
Expand All @@ -373,7 +377,7 @@
. = ..()
on_finish_callbacks += CALLBACK(src, PROC_REF(on_finish_pathing))

/datum/move_loop/has_target/jps/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, list/initial_path)
/datum/move_loop/has_target/jps/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, diagonal_handling, list/initial_path)
. = ..()
if(!.)
return
Expand All @@ -384,6 +388,7 @@
src.simulated_only = simulated_only
src.avoid = avoid
src.skip_first = skip_first
src.diagonal_handling = diagonal_handling
movement_path = initial_path?.Copy()

/datum/move_loop/has_target/jps/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, initial_path)
Expand All @@ -410,14 +415,15 @@
if(!COOLDOWN_FINISHED(src, repath_cooldown))
return
COOLDOWN_START(src, repath_cooldown, repath_delay)
if(SSpathfinder.pathfind(moving, target, max_path_length, minimum_distance, access, simulated_only, avoid, skip_first, on_finish = on_finish_callbacks))
if(SSpathfinder.pathfind(moving, target, max_path_length, minimum_distance, access, simulated_only, avoid, skip_first, diagonal_handling, on_finish = on_finish_callbacks))
is_pathing = TRUE
SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_REPATH)

///Called when a path has finished being created
/datum/move_loop/has_target/jps/proc/on_finish_pathing(list/path)
movement_path = path
is_pathing = FALSE
SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, path)

/datum/move_loop/has_target/jps/move()
if(!length(movement_path))
Expand Down
15 changes: 14 additions & 1 deletion code/datums/ai/movement/ai_movement_jps.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@
/datum/ai_movement/jps
max_pathing_attempts = 20
var/maximum_length = AI_MAX_PATH_LENGTH
///how we deal with diagonal movement, whether we try to avoid them or follow through with them
var/diagonal_flags = DIAGONAL_REMOVE_CLUNKY

/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
. = ..()
var/atom/movable/moving = controller.pawn
var/delay = controller.movement_delay

var/datum/move_loop/loop = SSmove_manager.jps_move(moving,
var/datum/move_loop/has_target/jps/loop = SSmove_manager.jps_move(moving,
current_movement_target,
delay,
repath_delay = 0.5 SECONDS,
max_path_length = maximum_length,
minimum_distance = controller.get_minimum_distance(),
access = controller.get_access(),
subsystem = SSai_movement,
diagonal_handling = diagonal_flags,
extra_info = controller,
)

RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
RegisterSignal(loop, COMSIG_MOVELOOP_JPS_REPATH, PROC_REF(repath_incoming))

return loop

/datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source)
SIGNAL_HANDLER
var/datum/ai_controller/controller = source.extra_info
Expand All @@ -35,3 +40,11 @@
/datum/ai_movement/jps/bot
max_pathing_attempts = 25
maximum_length = AI_BOT_PATH_LENGTH
diagonal_flags = DIAGONAL_REMOVE_ALL

/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
var/datum/move_loop/loop = ..()
var/atom/our_pawn = controller.pawn
if(isnull(our_pawn))
return
our_pawn.RegisterSignal(loop, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, TYPE_PROC_REF(/mob/living/basic/bot, generate_bot_path))
22 changes: 17 additions & 5 deletions code/modules/mob/living/basic/bots/_bots.dm
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ GLOBAL_LIST_INIT(command_strings, list(
var/obj/item/card/id/access_card
///The trim type that will grant additional acces
var/datum/id_trim/additional_access
///file the path icon is stored in
var/path_image_icon = 'icons/mob/silicon/aibots.dmi'
///state of the path icon
var/path_image_icon_state = "path_indicator"
///what color this path icon will use
var/path_image_color = "#FFFFFF"
///list of all layed path icons
var/list/current_pathed_turfs = list()

///The type of data HUD the bot uses. Diagnostic by default.
var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC
Expand All @@ -91,8 +99,9 @@ GLOBAL_LIST_INIT(command_strings, list(

/mob/living/basic/bot/Initialize(mapload)
. = ..()
AddElement(/datum/element/relay_attackers)

AddElement(/datum/element/relay_attackers)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement))
RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked))
RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access))
ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT)
Expand Down Expand Up @@ -185,6 +194,7 @@ GLOBAL_LIST_INIT(command_strings, list(
/mob/living/basic/bot/Destroy()
GLOB.bots_list -= src
calling_ai_ref = null
clear_path_hud()
QDEL_NULL(paicard)
QDEL_NULL(pa_system)
QDEL_NULL(internal_radio)
Expand Down Expand Up @@ -261,10 +271,10 @@ GLOBAL_LIST_INIT(command_strings, list(
fully_replace_character_name(real_name, new_name)

/mob/living/basic/bot/proc/check_access(mob/living/user, obj/item/card/id)
if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI).
return FALSE
if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access.
return TRUE
if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI).
return FALSE
if(!length(maints_access_required)) // No requirements to access it.
return TRUE
if(bot_access_flags & BOT_CONTROL_PANEL_OPEN) // Unlocked.
Expand Down Expand Up @@ -540,6 +550,7 @@ GLOBAL_LIST_INIT(command_strings, list(
access_card.set_access(initial_access)
diag_hud_set_botstat()
diag_hud_set_botmode()
clear_path_hud()
if(bypass_ai_reset || isnull(calling_ai_ref))
return
var/mob/living/ai_caller = calling_ai_ref.resolve()
Expand Down Expand Up @@ -593,9 +604,9 @@ GLOBAL_LIST_INIT(command_strings, list(
// Actions received from TGUI
/mob/living/basic/bot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(. || !isliving(ui.user))
if(.)
return
var/mob/living/the_user = ui.user
var/mob/the_user = ui.user
if(!check_access(the_user))
balloon_alert(the_user, "access denied!")
return
Expand Down Expand Up @@ -747,6 +758,7 @@ GLOBAL_LIST_INIT(command_strings, list(
speed = 2

diag_hud_set_botmode()
clear_path_hud()

/mob/living/basic/bot/Logout()
. = ..()
Expand Down
78 changes: 78 additions & 0 deletions code/modules/mob/living/basic/bots/bot_hud.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,81 @@
holder.icon_state = "hudmove"
else
holder.icon_state = ""

///proc that handles drawing and transforming the bot's path onto diagnostic huds
/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source)
SIGNAL_HANDLER

UnregisterSignal(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING)

if(isnull(ai_controller))
return

clear_path_hud()

var/list/path_images = active_hud_list[DIAG_PATH_HUD]
QDEL_LIST(path_images)

var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED])

var/atom/move_target = ai_controller.current_movement_target
if(move_target != ai_controller.blackboard[BB_BEACON_TARGET])
return

var/list/our_path = source.movement_path
if(!length(our_path))
return

for(var/datum/atom_hud/hud as anything in path_huds_watching_me)
hud.remove_atom_from_hud(src)

for(var/index in 1 to our_path.len)
if(index == 1 || index == our_path.len)
continue
var/turf/current_turf = our_path[index]
var/turf/previous_turf = our_path[index - 1]

var/turf/next_turf = our_path[index + 1]
var/next_direction = get_dir(previous_turf, next_turf)
var/previous_direction = get_dir(current_turf, previous_turf)

var/image/path_display = image(icon = path_image_icon, loc = current_turf, icon_state = path_image_icon_state, layer = GAME_PLANE, dir = next_direction)

if((ISDIAGONALDIR(next_direction) && (previous_direction & (NORTH|SOUTH))))
var/turn_value = (next_direction == SOUTHWEST || next_direction == NORTHEAST) ? 90 : -90
path_display.transform = path_display.transform.Turn(turn_value)
path_display.transform = path_display.transform.Scale(1, -1)

path_display.color = path_image_color
path_images += path_display
current_pathed_turfs[current_turf] = path_display

for(var/datum/atom_hud/hud as anything in path_huds_watching_me)
hud.add_atom_to_hud(src)

///proc that handles moving along the bot's drawn path
/mob/living/basic/bot/proc/handle_loop_movement(atom/movable/source)
SIGNAL_HANDLER

if(client || !length(current_pathed_turfs) || isnull(ai_controller))
return

var/atom/move_target = ai_controller.current_movement_target

if(move_target != ai_controller.blackboard[BB_BEACON_TARGET])
clear_path_hud()

var/turf/our_turf = get_turf(src)
var/image/target_image = current_pathed_turfs[our_turf]
if(target_image)
animate(target_image, alpha = 0, time = 0.3 SECONDS)
current_pathed_turfs -= our_turf
return

///proc that handles deleting the bot's drawn path when needed
/mob/living/basic/bot/proc/clear_path_hud()
for(var/turf/index as anything in current_pathed_turfs)
var/image/our_image = current_pathed_turfs[index]
animate(our_image, alpha = 0, time = 0.3 SECONDS)
current_pathed_turfs -= index

1 change: 1 addition & 0 deletions code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
additional_access = /datum/id_trim/job/janitor
possessed_message = "You are a cleanbot! Clean the station to the best of your ability!"
ai_controller = /datum/ai_controller/basic_controller/bot/cleanbot
path_image_color = "#993299"
///the bucket used to build us.
var/obj/item/reagent_containers/cup/bucket/build_bucket
///Flags indicating what kind of cleanables we should scan for to set as our target to clean.
Expand Down
21 changes: 13 additions & 8 deletions code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
/datum/ai_planning_subtree/acid_spray,
/datum/ai_planning_subtree/use_mob_ability/foam_area,
/datum/ai_planning_subtree/salute_authority,
/datum/ai_planning_subtree/find_patrol_beacon,
/datum/ai_planning_subtree/find_patrol_beacon/cleanbot,
)
reset_keys = list(
BB_ACTIVE_PET_COMMAND,
Expand All @@ -37,6 +37,7 @@
BB_CLEANABLE_DRAWINGS = CLEANBOT_CLEAN_DRAWINGS,
BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH,
)
ai_traits = PAUSE_DURING_DO_AFTER

/datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
var/mob/living/basic/bot/bot_pawn = controller.pawn
Expand All @@ -53,23 +54,20 @@
/datum/ai_planning_subtree/cleaning_subtree

/datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick)
var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn
// NOVA EDIT ADDITION START - TODO - Remove when cleanbot AI runtimes are fixed
if(QDELETED(bot_pawn))
if(QDELETED(controller.pawn))
return SUBTREE_RETURN_FINISH_PLANNING
// NOVA EDIT ADDITION END

if(LAZYLEN(bot_pawn.do_afters))
return SUBTREE_RETURN_FINISH_PLANNING

if(controller.reachable_key(BB_CLEAN_TARGET, BOT_CLEAN_PATH_LIMIT))
controller.clear_blackboard_key(BB_BEACON_TARGET)
controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING

var/list/final_hunt_list = list()

final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS]
var/list/flag_list = controller.clean_flags
var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn
for(var/list_key in flag_list)
if(!(bot_pawn.janitor_mode_flags & flag_list[list_key]))
continue
Expand All @@ -78,7 +76,7 @@
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list)

/datum/ai_behavior/find_and_set/in_list/clean_targets
action_cooldown = 2 SECONDS
action_cooldown = 1 SECONDS

/datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/controller, locate_paths, search_range)
var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths)
Expand Down Expand Up @@ -205,6 +203,13 @@
return human_target
return null

/datum/ai_planning_subtree/find_patrol_beacon/cleanbot

/datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
if(controller.blackboard_key_exists(BB_CLEAN_TARGET))
return
return ..()

/datum/pet_command/point_targeting/clean
command_name = "Clean"
command_desc = "Command a cleanbot to clean the mess."
Expand Down
5 changes: 4 additions & 1 deletion code/modules/mob/living/basic/bots/medbot/medbot.dm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

additional_access = /datum/id_trim/job/paramedic
announcement_type = /datum/action/cooldown/bot_announcement/medbot
path_image_color = "#d9d9f4"

///anouncements when we find a target to heal
var/static/list/wait_announcements = list(
Expand Down Expand Up @@ -146,8 +147,10 @@
)

RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))

if(!HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) || !mapload || !is_station_level(z))
return
return INITIALIZE_HINT_LATELOAD

skin = "advanced"
update_appearance(UPDATE_OVERLAYS)
damage_type_healer = HEAL_ALL_DAMAGE
Expand Down
6 changes: 3 additions & 3 deletions code/modules/mob/living/basic/bots/medbot/medbot_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@
. = ..()
var/mob/living/living_target = controller.blackboard[target_key]
if(QDELETED(living_target))
finish_action(controller, FALSE)
finish_action(controller, FALSE, target_key)
return
var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY]
if(QDELETED(announcement))
finish_action(controller, FALSE)
finish_action(controller, FALSE, target_key)
return
var/text_to_announce = "Medical emergency! [living_target] is in critical condition at [get_area(living_target)]!"
announcement.announce(text_to_announce, controller.blackboard[BB_RADIO_CHANNEL])
finish_action(controller, TRUE)
finish_action(controller, TRUE, target_key)

/datum/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
Expand Down

0 comments on commit f3c798d

Please sign in to comment.