Yet another Bot refactor. 2.0 (#23900)

* test 1

claim_job
comment spaces

start

* yay, its BROKE

* Update bot.dm

* alcohol is the only escape

* static

* it works

* spacing

* cleanbot list cleanup

* comments

* space AREA

This is what insanity looks like.

Every bot PR breaks something, without fail.

fixed replace tiles? broke hull breach
fixed hullbreach? broke cleanbots
fixed hullbreach? well also broke it

* Comment Sorting

* comments 2/2

* comments 3/?

* comments 4/?

* autodoc 1/?

* maketile doesnt drop

* maketile update

* floor - snake_case + if else

* Floor - comments

* floor - snake_case 2/?

* cleanbot turf reservation comment

* floorbot ignore list

* comments

* var/atom/A

* target check. null runtime

* tile clean comment removal

* again

* reviews. final_result removal

* comment block

* avoid bot TRUE

* comments ?/?

I lost track of how manny comment commits there are

* autodocs

* autodoc

* autodocs

* ignore_job -= target.UID() additions

* anchored

Noticed that, if a tile is in space, they will dance around it as they do not anchor. Now they do

* runtime fix

* ignore_job edits. clean after 30

* typo

* fix before replace, DUH

* ignore job clearing

* Time Dilation 106% no more

* eat tile fix, oops

* replace plating

* comment shift

* lets try again

* mode !

* process_type = null + to_chat cleanup + ignore_job bot_reset

* remove extra ignore_job

* target.loc

* TRUE

* ignore job and revert loc

* move speed and such

* comment with proc

* God I hate myself

* TRUE / FALSE

* comments :)

* else if

* pesky comment

* mods are asleep, rename the vars

* small edits

* Make ignore_job culling based on amount of bots alive. 1 job per bot

* Revert move speed changes. Bots move slower than security on cere

* Emag fixes oof

* ++

* + 1?

* ++

* comment

* dropped metal amount

* paranthesis

* no need for absolute

* Thanks VSC conflict resolver for duplicating this here

* Emag is a bool now, self remind comment removed

* Documentation

* move speed null nulled

* replace_tile lag / freeze fix + removed extra job stuff

* clarification about which list is being used in comment
This commit is contained in:
Deniz
2024-03-06 15:05:39 +00:00
committed by GitHub
parent 8f11c1dfd3
commit ce7c7f9885
3 changed files with 265 additions and 202 deletions
+196 -156
View File
@@ -15,7 +15,7 @@
minbodytemp = 0
has_unlimited_silicon_privilege = TRUE
sentience_type = SENTIENCE_ARTIFICIAL
status_flags = 0 //no default canpush
status_flags = 0 // No default canpush
can_strip = FALSE
speak_emote = list("states")
@@ -23,66 +23,104 @@
bubble_icon = "machine"
faction = list("neutral", "silicon")
var/list/users = list() //for dialog updates
var/list/users = list() // For dialog updates
var/window_id = "bot_control"
var/window_name = "Protobot 1.0" //Popup title
var/window_width = 0 //0 for default size
var/window_name = "Protobot 1.0" // Popup title
var/window_width = 0 // 0 for default size
var/window_height = 0
var/obj/item/paicard/paicard // Inserted pai card.
var/allow_pai = TRUE // Are we even allowed to insert a pai card.
var/obj/item/paicard/paicard // Inserted pai card.
var/allow_pai = TRUE // Are we even allowed to insert a pai card.
var/bot_name
var/disabling_timer_id = null
var/list/player_access = list()
var/emagged = FALSE
var/obj/item/card/id/access_card // the ID card that the bot "holds"
/// The ID card that the bot "holds"
var/obj/item/card/id/access_card
var/list/prev_access = list()
var/on = TRUE
var/open = FALSE //Maint panel
/// Maint panel
var/open = FALSE
var/locked = TRUE
var/hacked = FALSE //Used to differentiate between being hacked by silicons and emagged by humans.
/// Used to differentiate between being hacked by silicons and emagged by humans.
var/hacked = FALSE
/// Is currently hijacked by a pulse demon?
var/hijacked = FALSE
var/text_hack = "" //Custom text returned to a silicon upon hacking a bot.
var/text_dehack = "" //Text shown when resetting a bots hacked status to normal.
var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset.
var/declare_message = "" //What the bot will display to the HUD user.
var/frustration = 0 //Used by some bots for tracking failures to reach their target.
var/base_speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick.
var/turf/ai_waypoint //The end point of a bot's path, or the target location.
var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint
/// Custom text returned to a silicon upon hacking a bot.
var/text_hack = ""
/// Text shown when resetting a bots hacked status to normal.
var/text_dehack = ""
/// Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset.
var/text_dehack_fail = ""
/// What the bot will display to the HUD user.
var/declare_message = ""
/// Used by some bots for tracking failures to reach their target.
var/frustration = 0
/// The speed at which the bot moves, or the number of times it moves per process() tick.
var/base_speed = 2
/// The end point of a bot's path, or the target location.
var/turf/ai_waypoint
/// List of turfs through which a bot 'steps' to reach the waypoint
var/list/path = list()
var/pathset = FALSE
var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore.
var/mode = BOT_IDLE //Standardizes the vars that indicate the bot is busy with its function.
var/tries = 0 //Number of times the bot tried and failed to move.
var/remote_disabled = FALSE //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras.
var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it.
var/obj/item/radio/Radio //The bot's radio, for speaking to people.
var/list/radio_config = null //which channels can the bot listen to
var/radio_channel = "Common" //The bot's default radio channel
var/auto_patrol = FALSE // set to make bot automatically patrol
var/turf/patrol_target // this is turf to navigate to (location of beacon)
var/turf/summon_target // The turf of a user summoning a bot.
var/new_destination // pending new destination (waiting for beacon response)
var/destination // destination description tag
var/next_destination // the next destination in the patrol route
var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list
/// List of unreachable targets for an ignore-list enabled bot to ignore.
var/list/ignore_list = list()
/// List of jobs claimed by bot
var/static/list/ignore_job = list()
/// Standardizes the vars that indicate the bot is busy with its function.
var/mode = BOT_IDLE
/// Number of times the bot tried and failed to move.
var/tries = 0
/// If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras.
var/remote_disabled = FALSE
/// Links a bot to the AI calling it.
var/mob/living/silicon/ai/calling_ai
/// The bot's radio, for speaking to people.
var/obj/item/radio/Radio
/// Which channels can the bot listen to
var/list/radio_config = null
/// The bot's default radio channel
var/radio_channel = "Common"
/// Set to make bot automatically patrol
var/auto_patrol = FALSE
/// This is turf to navigate to (location of beacon)
var/turf/patrol_target
/// The turf of a user summoning a bot.
var/turf/summon_target
/// Pending new destination (waiting for beacon response)
var/new_destination
/// Destination description tag
var/destination
/// The next destination in the patrol route
var/next_destination
/// This ticks up every automated action, at 300 we clean the ignore list
var/ignore_list_cleanup_timer = 1
var/robot_arm = /obj/item/robot_parts/r_arm
var/blockcount = 0 //number of times retried a blocked path
var/awaiting_beacon = 0 // count of pticks awaiting a beacon response
/// Number of times retried a blocked path
var/blockcount = 0
/// Count of pticks awaiting a beacon response
var/awaiting_beacon = 0
var/nearest_beacon // the nearest beacon's tag
var/turf/nearest_beacon_loc // the nearest beacon's location
/// The nearest beacon's tag
var/nearest_beacon
/// The nearest beacon's location
var/turf/nearest_beacon_loc
var/model = "" //The type of bot it is.
///The type of bot it is.
var/model = ""
/// Bot Purpose under Show Laws
var/bot_purpose = "improve the station to the best of your ability"
var/control_freq = BOT_FREQ // bot control frequency
var/bot_filter // The radio filter the bot uses to identify itself on the network.
var/bot_type = 0 //The type of bot it is, for radio control.
/// Bot control frequency
var/control_freq = BOT_FREQ
/// The radio filter the bot uses to identify itself on the network.
var/bot_filter
/// The type of bot it is, for radio control.
var/bot_type = 0
/// The type of data HUD the bot uses. Diagnostic by default.
var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC
//This holds text for what the bot is mode doing, reported on the remote bot control interface.
// This holds text for what the bot is mode doing, reported on the remote bot control interface.
var/list/mode_name = list("In Pursuit","Preparing to Arrest", "Arresting", \
"Beginning Patrol", "Patrolling", "Summoned by PDA", \
"Cleaning", "Repairing", "Proceeding to work site", "Healing", \
@@ -94,11 +132,11 @@
/// List of access values you can have to access the bot. Consider this as req_one_access
var/list/req_access = list()
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD)//Diagnostic HUD views
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD) // Diagnostic HUD views
/// storing last chased target known location
/// Storing last chased target known location
var/turf/last_target_location
/// will be true if we lost target we were chasing
/// Will be true if we lost target we were chasing
var/lost_target = FALSE
/obj/item/radio/headset/bot
@@ -110,7 +148,7 @@
if(istype(B))
if(!B.radio_config)
B.radio_config = list("AI Private" = 1)
if(!(B.radio_channel in B.radio_config)) // put it first so it's the :h channel
if(!(B.radio_channel in B.radio_config)) // Put it first so it's the :h channel
B.radio_config.Insert(1, "[B.radio_channel]")
B.radio_config["[B.radio_channel]"] = 1
config(B.radio_config)
@@ -146,7 +184,7 @@
frustration++
/mob/living/simple_animal/bot/proc/get_mode()
if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player.
if(client) // Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player.
if(paicard)
return "<b>pAI Controlled</b>"
else
@@ -164,20 +202,20 @@
if(disabling_timer_id)
return FALSE
if(stat)
return 0
return FALSE
on = TRUE
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, "depowered")
set_light(initial(light_range))
update_icon(UPDATE_ICON_STATE | UPDATE_OVERLAYS)
update_controls()
diag_hud_set_botstat()
return 1
return TRUE
/mob/living/simple_animal/bot/proc/turn_off()
on = FALSE
ADD_TRAIT(src, TRAIT_IMMOBILIZED, "depowered")
set_light(0)
bot_reset() //Resets an AI's call, should it exist.
bot_reset() // Resets an AI's call, should it exist.
update_icon(UPDATE_ICON_STATE | UPDATE_OVERLAYS)
update_controls()
@@ -187,7 +225,7 @@
icon_living = icon_state
icon_dead = icon_state
access_card = new /obj/item/card/id(src)
//This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first.
// This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first.
access_card.access += ACCESS_ROBOTICS
set_custom_texts()
Radio = new/obj/item/radio/headset/bot(src)
@@ -210,10 +248,10 @@
/mob/living/simple_animal/bot/med_hud_set_health()
return //we use a different hud
return // We use a different hud
/mob/living/simple_animal/bot/med_hud_set_status()
return //we use a different hud
return // We use a different hud
/mob/living/simple_animal/bot/Destroy()
@@ -250,21 +288,23 @@
qdel(src)
/mob/living/simple_animal/bot/emag_act(mob/user)
if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
if(locked) // First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
locked = FALSE
to_chat(user, "<span class='notice'>You bypass [src]'s controls.</span>")
return TRUE
if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
emagged = TRUE
remote_disabled = TRUE //Manually emagging the bot locks out the AI built in panel.
locked = TRUE //Access denied forever!
remote_disabled = TRUE // Manually emagging the bot locks out the AI built in panel.
locked = TRUE // Access denied forever!
bot_reset()
turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
turn_on() // The bot automatically turns on when emagged, unless recently hit with EMP.
to_chat(src, "<span class='userdanger'>(#$*#$^^( OVERRIDE DETECTED</span>")
show_laws()
if(user)
add_attack_logs(user, src, "Emagged")
return TRUE
else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet.
to_chat(user, "<span class='warning'>You need to open maintenance panel first!</span>")
@@ -290,14 +330,14 @@
/mob/living/simple_animal/bot/handle_automated_action()
diag_hud_set_botmode()
if(ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk
if(++ignore_list_cleanup_timer == 300) // Every 300 actions, clean up the ignore list from old junk
for(var/uid in ignore_list)
var/atom/referredatom = locateUID(uid)
if(!referredatom || QDELETED(referredatom))
var/atom/referred_atom = locateUID(uid)
if(!referred_atom || QDELETED(referred_atom))
ignore_list -= uid
ignorelistcleanuptimer = 1
ignore_list_cleanup_timer = 0
else
ignorelistcleanuptimer++
ignore_list_cleanup_timer++
if(!on)
return
@@ -305,14 +345,14 @@
if(hijacked)
return
switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command.
if(BOT_RESPONDING) //Called by the AI.
switch(mode) // High-priority overrides are processed first. Bots can do nothing else while under direct command.
if(BOT_RESPONDING) // Called by the AI.
call_mode()
return
if(BOT_SUMMON) //Called by PDA
if(BOT_SUMMON) // Called by PDA
bot_summon()
return
return 1 //Successful completion. Used to prevent child process() continuing if this one is ended early.
return TRUE // Successful completion. Used to prevent child process() continuing if this one is ended early.
/mob/living/simple_animal/bot/attack_alien(mob/living/carbon/alien/user)
user.changeNext_move(CLICK_CD_MELEE)
@@ -407,7 +447,7 @@
return ..()
if(locked)
to_chat(user, "<span class='warning'>The maintenance panel is locked.</span>")
return TRUE // must be true or we attempt to stab the bot
return TRUE // Must be true or we attempt to stab the bot
open = !open
I.play_tool_sound(src)
@@ -418,7 +458,7 @@
/mob/living/simple_animal/bot/welder_act(mob/user, obj/item/I)
if(user.a_intent != INTENT_HELP)
return
if(user == src) //No self-repair dummy
if(user == src) // No self-repair dummy
return
. = TRUE
if(health >= maxHealth)
@@ -467,7 +507,7 @@
/mob/living/simple_animal/bot/proc/disable(time)
if(disabling_timer_id)
deltimer(disabling_timer_id) // if we already have disabling timer, lets replace it with new one
deltimer(disabling_timer_id) // If we already have disabling timer, lets replace it with new one
if(on)
turn_off()
disabling_timer_id = addtimer(CALLBACK(src, PROC_REF(enable)), time, TIMER_STOPPABLE)
@@ -486,12 +526,12 @@
set_custom_texts()
return TRUE
/mob/living/simple_animal/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally.
/mob/living/simple_animal/bot/proc/set_custom_texts() // Superclass for setting hack texts. Appears only if a set is not given to a bot locally.
text_hack = "You hack [name]."
text_dehack = "You reset [name]."
text_dehack_fail = "You fail to reset [name]."
/mob/living/simple_animal/bot/proc/speak(message, channel) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio.
/mob/living/simple_animal/bot/proc/speak(message, channel) // Pass a message to have the bot say() it. Pass a frequency to say it on the radio.
if((!on) || (!message))
return
if(channel)
@@ -500,74 +540,73 @@
say(message)
return
//Generalized behavior code, override where needed!
// 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.
If the bot has avoid_bot, which inserts its own path, it will ignore turfs with the same bot type
If avoid_bot = TRUE, it will claim it's target. List of bots that use it: Cleanbot and Floorbot
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(atom/scan_type, atom/old_target, scan_range = DEFAULT_SCAN_RANGE, avoid_bot)
var/final_result
for(var/scan in view(scan_range, src)) //Search for something in range!
var/atom/A = scan
if(!istype(A, scan_type)) //Check that the thing we found is the type we want!
continue //If not, keep searching!
if((A.UID() in ignore_list) || (A == old_target)) //Filter for blacklisted elements, usually unreachable or previously processed oness
for(var/atom/A in view(scan_range, src)) // Search for something in range!
if(!istype(A, scan_type)) // Check that the thing we found is the type we want!
continue // If not, keep searching!
if((A.UID() in ignore_list) || (A == old_target)) // Filter for blacklisted elements, usually unreachable or previously processed oness
continue
if(turf_has_bot(avoid_bot, get_turf(A))) //Ignores targets that already have a bot of the same type on it, meant for cleanbot and floorbot seperation
var/scan_result = process_scan(A) // Some bots may require additional processing when a result is selected.
if(!scan_result)
continue // The current element failed assessment, move on to the next.
if(assign_bot(A, avoid_bot)) // Is the target claimed by a bot
continue
var/scan_result = process_scan(A) //Some bots may require additional processing when a result is selected.
if(scan_result)
final_result = scan_result
else
continue //The current element failed assessment, move on to the next.
return final_result
return scan_result
/mob/living/simple_animal/bot/proc/turf_has_bot(avoid_bot, turf/turf_to_search)
if(!avoid_bot)
/mob/living/simple_animal/bot/proc/assign_bot(atom/A, avoid_bot)
if(!avoid_bot) // Only bots with avoid bot should be considered
return FALSE
for(var/bot in turf_to_search)
if(istype(bot, avoid_bot))
return TRUE
return FALSE
if(A.UID() in ignore_job) // Is this target already claimed by a bot
return TRUE
claim_job(A) // Claim the job so other bots don't come
//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/claim_job(atom/A) // Adds the target to ignore_job list
var/ignore_amount = length(GLOB.bots_list) // 1 claim per bot
if(length(ignore_job) >= ++ignore_amount) // Something went wrong, cull the herd
ignore_job.Cut()
ignore_job |= A.UID()
// 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(atom/scan_target)
return scan_target
/mob/living/simple_animal/bot/proc/add_to_ignore(atom/A)
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 |= A.UID()
else //If the list is full, insert newest, delete oldest.
ignore_list.Cut(1, 2)
ignore_list |= A.UID()
if(length(ignore_list) >= 50) // This will help keep track of them, so the bot is always trying to reach a blocked spot.
ignore_list.Cut(1, 2) // If the list is full, insert newest, delete oldest.
ignore_list |= A.UID()
/*
Movement proc for stepping a bot through a path generated through A-star.
Pass a positive integer as an argument to override a bot's default speed.
*/
/mob/living/simple_animal/bot/proc/bot_move(dest, move_speed)
if(!dest || !path || !length(path)) //A-star failed or a path/destination was not set.
if(!dest || !path || !length(path)) // A-star failed or a path/destination was not set.
set_path(null)
return FALSE
dest = get_turf(dest) //We must always compare turfs, so get the turf of the dest var if dest was originally something else.
var/turf/last_node = get_turf(path[length(path)]) //This is the turf at the end of the path, it should be equal to dest.
if(get_turf(src) == dest) //We have arrived, no need to move again.
dest = get_turf(dest) // We must always compare turfs, so get the turf of the dest var if dest was originally something else.
var/turf/last_node = get_turf(path[length(path)]) // This is the turf at the end of the path, it should be equal to dest.
if(get_turf(src) == dest) // We have arrived, no need to move again.
return TRUE
else if(dest != last_node) //The path should lead us to our given destination. If this is not true, we must stop.
else if(dest != last_node) // The path should lead us to our given destination. If this is not true, we must stop.
set_path(null)
return FALSE
var/step_count = move_speed ? move_speed : base_speed //If a value is passed into move_speed, use that instead of the default speed var.
var/step_count = move_speed ? move_speed : base_speed // If a value is passed into move_speed, use that instead of the default speed var.
if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES)
for(var/step_number in 1 to step_count)
@@ -578,7 +617,7 @@ Pass a positive integer as an argument to override a bot's default speed.
return TRUE
/mob/living/simple_animal/bot/proc/bot_step() //Step,increase tries if failed
/mob/living/simple_animal/bot/proc/bot_step() // Step,increase tries if failed
if(!length(path))
return FALSE
@@ -596,19 +635,19 @@ Pass a positive integer as an argument to override a bot's default speed.
access_card.access = prev_access
/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message=TRUE)
bot_reset() //Reset a bot before setting it to call mode.
bot_reset() // Reset a bot before setting it to call mode.
var/area/end_area = get_area(waypoint)
var/datum/job/captain/All = new/datum/job/captain
access_card.access = All.get_access() // Give the bot temporary all access
set_path(get_path_to(src, waypoint, 200, id = access_card))
calling_ai = caller //Link the AI to the bot!
calling_ai = caller // Link the AI to the bot!
ai_waypoint = waypoint
if(path && length(path)) //Ensures that a valid path is calculated!
if(path && length(path)) // Ensures that a valid path is calculated!
if(!on)
turn_on() //Saves the AI the hassle of having to activate a bot manually.
turn_on() // Saves the AI the hassle of having to activate a bot manually.
if(client)
reset_access_timer_id = addtimer(CALLBACK(src, PROC_REF(bot_reset)), 600, TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time
to_chat(src, "<span class='notice'><span class='big'>Priority waypoint set by [calling_ai] <b>[caller]</b>. Proceed to <b>[end_area.name]</b>.</span><br>[length(path)-1] meters to destination. You have been granted additional door access for 60 seconds.</span>")
@@ -624,8 +663,8 @@ Pass a positive integer as an argument to override a bot's default speed.
access_card.access = prev_access // Don't forget to reset it
set_path(null)
/mob/living/simple_animal/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc.
//Handles the bot's movement during a call.
/mob/living/simple_animal/bot/proc/call_mode() // Handles preparing a bot for a call, as well as calling the move proc.
// Handles the bot's movement during a call.
var/success = bot_move(ai_waypoint, 3)
if(!success)
if(calling_ai)
@@ -634,13 +673,14 @@ Pass a positive integer as an argument to override a bot's default speed.
bot_reset()
/mob/living/simple_animal/bot/proc/bot_reset()
if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot.
if(calling_ai) // Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot.
to_chat(calling_ai, "<span class='danger'>Call command to a bot has been reset.</span>")
calling_ai = null
if(reset_access_timer_id)
deltimer(reset_access_timer_id)
reset_access_timer_id = null
set_path(null)
ignore_job.Cut()
last_target_location = null
lost_target = FALSE
summon_target = null
@@ -668,21 +708,21 @@ Pass a positive integer as an argument to override a bot's default speed.
/mob/living/simple_animal/bot/proc/start_patrol()
set_path(null)
if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol.
if(tries >= BOT_STEP_MAX_RETRIES) // Bot is trapped, so stop trying to patrol.
auto_patrol = FALSE
tries = 0
speak("Unable to start patrol.")
return
if(!auto_patrol) //A bot not set to patrol should not be patrolling.
if(!auto_patrol) // A bot not set to patrol should not be patrolling.
mode = BOT_IDLE
return
if(patrol_target) // has patrol target
INVOKE_ASYNC(src, PROC_REF(target_patrol))
else // no patrol target, so need a new one
else // No patrol target, so need a new one
speak("Engaging patrol mode.")
find_patrol_target()
tries++
@@ -694,30 +734,30 @@ Pass a positive integer as an argument to override a bot's default speed.
return
mode = BOT_PATROL
// perform a single patrol step
// Perform a single patrol step
/mob/living/simple_animal/bot/proc/patrol_step()
if(client) // In use by player, don't actually move.
return
if(loc == patrol_target) // reached target
//Find the next beacon matching the target.
if(loc == patrol_target) // Reached target
// Find the next beacon matching the target.
if(!get_next_patrol_target())
find_patrol_target() //If it fails, look for the nearest one instead.
find_patrol_target() // If it fails, look for the nearest one instead.
return
else if(length(path) && patrol_target) // valid path
else if(length(path) && patrol_target) // Valid path
if(path[1] == loc)
increment_path()
return
var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move
if(!moved) //Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times
var/moved = bot_move(patrol_target) // step_towards(src, next) // attempt to move
if(!moved) // Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times
addtimer(CALLBACK(src, PROC_REF(patrol_step_not_moved)), 2)
else // no path, so calculate new one
else // No path, so calculate new one
mode = BOT_START_PATROL
/mob/living/simple_animal/bot/proc/patrol_step_not_moved()
@@ -726,7 +766,7 @@ Pass a positive integer as an argument to override a bot's default speed.
find_patrol_target()
tries = 0
// finds the nearest beacon to self
// Finds the nearest beacon to self
/mob/living/simple_animal/bot/proc/find_patrol_target()
nearest_beacon = null
new_destination = null
@@ -740,13 +780,13 @@ Pass a positive integer as an argument to override a bot's default speed.
speak("Disengaging patrol mode.")
/mob/living/simple_animal/bot/proc/get_next_patrol_target()
// search the beacon list for the next target in the list.
// Search the beacon list for the next target in the list.
for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"])
if(NB.location == next_destination) //Does the Beacon location text match the destination?
destination = new_destination //We now know the name of where we want to go.
patrol_target = NB.loc //Get its location and set it as the target.
next_destination = NB.codes["next_patrol"] //Also get the name of the next beacon in line.
return 1
if(NB.location == next_destination) // Does the Beacon location text match the destination?
destination = new_destination // We now know the name of where we want to go.
patrol_target = NB.loc // Get its location and set it as the target.
next_destination = NB.codes["next_patrol"] // Also get the name of the next beacon in line.
return TRUE
/mob/living/simple_animal/bot/proc/find_nearest_beacon()
for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"])
@@ -786,17 +826,17 @@ Pass a positive integer as an argument to override a bot's default speed.
if(!on)
return FALSE
// check to see if we are the commanded bot
if(emagged || remote_disabled || hijacked) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
// Check to see if we are the commanded bot
if(emagged || remote_disabled || hijacked) // Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
return FALSE
if(client)
bot_control_message(command, user, params["target"] ? params["target"] : "Unknown")
// process control input
// Process control input
switch(command)
if("stop")
bot_reset() //HOLD IT!!
bot_reset() // HOLD IT!!
auto_patrol = FALSE
if("go")
@@ -808,7 +848,7 @@ Pass a positive integer as an argument to override a bot's default speed.
summon_target = params["target"] // Location of the user
if(length(user_access))
access_card.access = user_access + prev_access //Adds the user's access, if any.
access_card.access = user_access + prev_access // Adds the user's access, if any.
mode = BOT_SUMMON
calc_summon_path()
@@ -817,11 +857,11 @@ Pass a positive integer as an argument to override a bot's default speed.
return TRUE
/mob/living/simple_animal/bot/proc/bot_summon() // summoned to PDA
/mob/living/simple_animal/bot/proc/bot_summon() // Summoned to PDA
summon_step()
// calculates a path to the current destination
// given an optional turf to avoid
// Calculates a path to the current destination
// Given an optional turf to avoid
/mob/living/simple_animal/bot/proc/calc_path(turf/avoid)
check_bot_access()
set_path(get_path_to(src, patrol_target, 120, id=access_card, exclude=avoid))
@@ -830,7 +870,7 @@ Pass a positive integer as an argument to override a bot's default speed.
set waitfor = FALSE
check_bot_access()
set_path(get_path_to(src, summon_target, 150, id=access_card, exclude=avoid))
if(!length(path)) //Cannot reach target. Give up and announce the issue.
if(!length(path)) // Cannot reach target. Give up and announce the issue.
speak("Summon command failed, destination unreachable.",radio_channel)
bot_reset()
@@ -854,7 +894,7 @@ Pass a positive integer as an argument to override a bot's default speed.
addtimer(CALLBACK(src, PROC_REF(try_calc_path)), 2)
else // no path, so calculate new one
else // No path, so calculate new one
calc_summon_path()
/mob/living/simple_animal/bot/proc/try_calc_path()
@@ -886,22 +926,22 @@ Pass a positive integer as an argument to override a bot's default speed.
return "PROTOBOT - NOT FOR USE"
/mob/living/simple_animal/bot/proc/allowed(mob/M)
var/acc = M.get_access() //see mob.dm
var/acc = M.get_access() // See mob.dm
if(acc == IGNORE_ACCESS || M.can_admin_interact())
return TRUE //Mob ignores access
return TRUE // Mob ignores access
return has_access(list(), req_access, acc)
/mob/living/simple_animal/bot/Topic(href, href_list)
if(href_list["close"])// HUE HUE
if(href_list["close"]) // HUE HUE
if(usr in users)
users.Remove(usr)
return 1
return TRUE
if(topic_denied(usr))
to_chat(usr, "<span class='warning'>[src]'s interface is not responding!</span>")
return 1
return TRUE
add_fingerprint(usr)
if((href_list["power"]) && (allowed(usr) || !locked || usr.can_admin_interact()))
@@ -927,7 +967,7 @@ Pass a positive integer as an argument to override a bot's default speed.
/mob/living/simple_animal/bot/proc/canhack(mob/M)
return ((issilicon(M) && (!emagged || hacked)) || M.can_admin_interact())
/mob/living/simple_animal/bot/proc/handle_hacking(mob/M) // refactored out of Topic/ to allow re-use by TGUIs
/mob/living/simple_animal/bot/proc/handle_hacking(mob/M) // Refactored out of Topic/ to allow re-use by TGUIs
if(!canhack(M))
return
if(!emagged)
@@ -951,15 +991,15 @@ Pass a positive integer as an argument to override a bot's default speed.
/mob/living/simple_animal/bot/update_icon_state()
icon_state = "[initial(icon_state)][on]"
/mob/living/simple_animal/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired.
/mob/living/simple_animal/bot/proc/topic_denied(mob/user) // Access check proc for bot topics! Remember to place in a bot's individual Topic if desired.
if(user.can_admin_interact())
return FALSE
if(user.incapacitated() || !(issilicon(user) || in_range(src, user)))
return TRUE
if(emagged) //An emagged bot cannot be controlled by humans, silicons can if one hacked it.
if(!hacked) //Manually emagged by a human - access denied to all.
if(emagged) // An emagged bot cannot be controlled by humans, silicons can if one hacked it.
if(!hacked) // Manually emagged by a human - access denied to all.
return TRUE
else if(!(issilicon(user) || ispulsedemon(user))) //Bot is hacked, so only silicons are allowed access.
else if(!(issilicon(user) || ispulsedemon(user))) // Bot is hacked, so only silicons are allowed access.
return TRUE
if(hijacked && !ispulsedemon(user))
return FALSE
@@ -969,10 +1009,10 @@ Pass a positive integer as an argument to override a bot's default speed.
/mob/living/simple_animal/bot/proc/hack(mob/user)
var/hack
if(issilicon(user) || user.can_admin_interact()) //Allows silicons or admins to toggle the emag status of a bot.
if(issilicon(user) || user.can_admin_interact()) // Allows silicons or admins to toggle the emag status of a bot.
hack += "[emagged ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]<BR>"
hack += "Harm Prevention Safety System: <A href='?src=[UID()];operation=hack'>[emagged ? "<span class='bad'>DANGER</span>" : "Engaged"]</A><BR>"
else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control.
else if(!locked) // Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control.
hack += "Remote network control radio: <A href='?src=[UID()];operation=remote'>[remote_disabled ? "Disconnected" : "Connected"]</A><BR>"
return hack
@@ -1095,7 +1135,7 @@ Pass a positive integer as an argument to override a bot's default speed.
used_radios += Radio
/mob/living/simple_animal/bot/is_mechanical()
return 1
return TRUE
/mob/living/simple_animal/bot/proc/set_path(list/newpath)
path = newpath ? newpath : list()
@@ -1112,13 +1152,13 @@ Pass a positive integer as an argument to override a bot's default speed.
// Common data shared among all the bots, used by BotStatus.js
/mob/living/simple_animal/bot/ui_data(mob/user)
var/list/data = list()
data["locked"] = locked // controls, locked or not
data["noaccess"] = topic_denied(user) // does the current user have access? admins, silicons etc can still access bots with locked controls
data["locked"] = locked // Controls, locked or not
data["noaccess"] = topic_denied(user) // Does the current user have access? admins, silicons etc can still access bots with locked controls
data["maintpanel"] = open
data["on"] = on
data["autopatrol"] = auto_patrol
data["painame"] = paicard ? paicard.pai.name : null
data["canhack"] = canhack(user)
data["emagged"] = emagged // this is an int, NOT a boolean
data["emagged"] = emagged
data["remote_disabled"] = remote_disabled
return data
@@ -124,7 +124,7 @@
audible_message("[src] makes an excited beeping booping sound!")
if(!target) //Search for cleanables it can see.
target = scan(/obj/effect/decal/cleanable, avoid_bot = /mob/living/simple_animal/bot/cleanbot)
target = scan(/obj/effect/decal/cleanable, avoid_bot = TRUE)
if(!target && auto_patrol) //Search for cleanables it can see.
if(mode == BOT_IDLE || mode == BOT_START_PATROL)
@@ -143,12 +143,14 @@
//Try to produce a path to the target, and ignore airlocks to which it has access.
path = get_path_to(src, target, 30, id=access_card)
if(!bot_move(target))
ignore_job -= target.UID()
add_to_ignore(target)
target = null
path = list()
return
mode = BOT_MOVING
else if(!bot_move(target))
ignore_job -= target.UID()
target = null
mode = BOT_IDLE
return
@@ -164,6 +166,7 @@
/mob/living/simple_animal/bot/cleanbot/proc/do_clean(obj/effect/decal/cleanable/target)
if(mode == BOT_CLEANING)
ignore_job -= target.UID()
QDEL_NULL(target)
anchored = FALSE
mode = BOT_IDLE
@@ -1,4 +1,4 @@
//Floorbot
// Floorbot
/mob/living/simple_animal/bot/floorbot
name = "\improper Floorbot"
desc = "A little floor repairing robot, he looks so excited!"
@@ -167,37 +167,37 @@
if(prob(5))
audible_message("[src] makes an excited booping beeping sound!")
//Normal scanning procedure. We have tiles loaded, are not emagged.
// Normal scanning procedure. We have tiles loaded, are not emagged.
if(!target && !emagged && amount)
if(!target)
process_type = HULL_BREACH //Ensures the floorbot does not try to "fix" space areas or shuttle docking zones.
target = scan(/turf/space, avoid_bot = /mob/living/simple_animal/bot/floorbot)
process_type = HULL_BREACH // Ensures the floorbot does not try to "fix" space areas or shuttle docking zones.
target = scan(/turf/space, avoid_bot = TRUE)
if(!target && replace_tiles) //Finds a floor without a tile and gives it one.
process_type = REPLACE_TILE //The target must be the floor and not a tile. The floor must not already have a floortile.
target = scan(/turf/simulated/floor, avoid_bot = /mob/living/simple_animal/bot/floorbot)
if(!target && fix_floor) //Repairs damaged floors and tiles.
if(!target && fix_floor) // Repairs damaged floors and tiles.
process_type = FIX_TILE
target = scan(/turf/simulated/floor, avoid_bot = /mob/living/simple_animal/bot/floorbot)
target = scan(/turf/simulated/floor, avoid_bot = TRUE)
if(!target && emagged) //We are emagged! Time to rip up the floors!
if(!target && replace_tiles) // Finds a floor without a tile and gives it one.
process_type = REPLACE_TILE // The target must be the floor and not a tile. The floor must not already have a floortile.
target = scan(/turf/simulated/floor/plating, avoid_bot = TRUE)
if(!target && emagged) // We are emagged! Time to rip up the floors!
process_type = TILE_EMAG
target = scan(/turf/simulated/floor, avoid_bot = /mob/living/simple_animal/bot/floorbot)
target = scan(/turf/simulated/floor, avoid_bot = TRUE)
if(amount < MAX_AMOUNT && !target) //Out of tiles! We must refill!
if(eat_tiles) //Configured to find and consume floortiles!
if(amount < MAX_AMOUNT && !target) // Out of tiles! We must refill!
if(!target && eat_tiles) // Configured to find and consume floortiles!
process_type = null
target = scan(/obj/item/stack/tile/plasteel)
if(!target && make_tiles) //We did not manage to find any floor tiles! Scan for metal stacks and make our own!
if(!target && make_tiles) // We did not manage to find any floor tiles! Scan for metal stacks and make our own!
process_type = null
target = scan(/obj/item/stack/sheet/metal)
if(!target && nag_on_empty) //Floorbot is empty and cannot acquire more tiles, nag the engineers for more!
if(!target && nag_on_empty) // Floorbot is empty and cannot acquire more tiles, nag the engineers for more!
nag()
if(!target)
if(auto_patrol)
@@ -211,10 +211,10 @@
if(loc == target || loc == target.loc)
if(istype(target, /obj/item/stack/tile/plasteel))
start_eattile(target)
start_eat_tile(target)
if(istype(target, /obj/item/stack/sheet/metal))
start_maketile(target)
start_make_tile(target)
if(isturf(target) && !emagged)
repair(target)
@@ -232,7 +232,7 @@
path = list()
return
if(!length(path))
if(!length(path)) // No path, need a new one
if(!isturf(target))
var/turf/TL = get_turf(target)
path = get_path_to(src, TL, 30, id = access_card, simulated_only = 0)
@@ -241,10 +241,12 @@
if(!bot_move(target))
add_to_ignore(target)
ignore_job -= target.UID()
target = null
mode = BOT_IDLE
return
else if(!bot_move(target))
ignore_job -= target.UID()
target = null
mode = BOT_IDLE
return
@@ -255,45 +257,50 @@
amount ++
anchored = FALSE
mode = BOT_IDLE
ignore_job -= target.UID()
target = null
/mob/living/simple_animal/bot/floorbot/proc/nag() //Annoy everyone on the channel to refill us!
/mob/living/simple_animal/bot/floorbot/proc/nag() // Annoy everyone on the channel to refill us!
if(!nagged)
speak("Requesting refill [MAX_AMOUNT - amount] at <b>[get_area(src)]</b>!", radio_channel)
nagged = TRUE
/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.
/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.
return !istype(get_area(t), /area/space)
//Floorbots, having several functions, need sort out special conditions here.
// Floorbots, having several functions, need sort out special conditions here.
/mob/living/simple_animal/bot/floorbot/process_scan(atom/scan_target)
var/result
var/turf/simulated/floor/F
switch(process_type)
if(HULL_BREACH) //The most common job, patching breaches in the station's hull.
if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space.
if(HULL_BREACH) // The most common job, patching breaches in the station's hull.
if(is_hull_breach(scan_target)) // Ensure that the targeted space turf is actually part of the station, and not random space.
result = scan_target
anchored = TRUE //Prevent the floorbot being blown off-course while trying to reach a hull breach.
anchored = TRUE // Prevent the floorbot being blown off-course while trying to reach a hull breach.
if(REPLACE_TILE)
F = scan_target
if(istype(F, /turf/simulated/floor/plating)) //The floor must not already have a tile.
if(istype(F, /turf/simulated/floor/plating)) // The floor must not already have a tile.
if(locate(/obj/structure/window) in get_turf(F)) // Targeting plating under window
add_to_ignore(scan_target)
return FALSE
result = F
if(FIX_TILE) //Selects only damaged floors.
if(FIX_TILE) // Selects only damaged floors.
F = scan_target
if(istype(F) && (F.broken || F.burnt))
result = F
if(TILE_EMAG) //Emag mode! Rip up the floor and cause breaches to space!
if(TILE_EMAG) // Emag mode! Rip up the floor and cause breaches to space!
F = scan_target
if(!istype(F, /turf/simulated/floor/plating))
result = F
else //If no special processing is needed, simply return the result.
else // If no special processing is needed, simply return the result.
result = scan_target
return result
/mob/living/simple_animal/bot/floorbot/proc/repair(turf/target_turf)
if(isspaceturf(target_turf))
//Must be a hull breach to continue.
// Must be a hull breach to continue.
if(!is_hull_breach(target_turf))
ignore_job -= target.UID()
target = null
return
@@ -302,12 +309,13 @@
if(amount <= 0)
mode = BOT_IDLE
ignore_job -= target.UID()
target = null
return
anchored = TRUE
if(isspaceturf(target_turf)) //If we are fixing an area not part of pure space, it is
if(isspaceturf(target_turf)) // If we are fixing an area not part of pure space, it is
visible_message("<span class='notice'>[src] begins to repair the hole.</span>")
mode = BOT_REPAIRING
update_icon(UPDATE_OVERLAYS)
@@ -326,6 +334,8 @@
if(mode != BOT_REPAIRING)
return
ignore_job -= target_turf.UID() // If called after the tile fix, turf changes and the UID with it
if(autotile || replace_tiles)
if(process_type != HULL_BREACH)
F.break_tile_to_plating()
@@ -339,15 +349,16 @@
anchored = FALSE
target = null
/mob/living/simple_animal/bot/floorbot/proc/start_eattile(obj/item/stack/tile/plasteel/T)
/mob/living/simple_animal/bot/floorbot/proc/start_eat_tile(obj/item/stack/tile/plasteel/T)
if(!istype(T, /obj/item/stack/tile/plasteel))
return
anchored = TRUE
visible_message("<span class='notice'>[src] begins to collect tiles.</span>")
mode = BOT_EAT_TILE
update_icon(UPDATE_OVERLAYS)
addtimer(CALLBACK(src, PROC_REF(do_eattile), T), 2 SECONDS)
addtimer(CALLBACK(src, PROC_REF(do_eat_tile), T), 2 SECONDS)
/mob/living/simple_animal/bot/floorbot/proc/do_eattile(obj/item/stack/tile/plasteel/T)
/mob/living/simple_animal/bot/floorbot/proc/do_eat_tile(obj/item/stack/tile/plasteel/T)
if(isnull(T))
target = null
mode = BOT_IDLE
@@ -359,30 +370,39 @@
else
amount += T.amount
qdel(T)
anchored = FALSE
target = null
mode = BOT_IDLE
update_icon(UPDATE_OVERLAYS)
/mob/living/simple_animal/bot/floorbot/proc/start_maketile(obj/item/stack/sheet/metal/M)
/mob/living/simple_animal/bot/floorbot/proc/start_make_tile(obj/item/stack/sheet/metal/M)
if(!istype(M, /obj/item/stack/sheet/metal))
return
anchored = TRUE
visible_message("<span class='notice'>[src] begins to create tiles.</span>")
mode = BOT_MAKE_TILE
update_icon(UPDATE_OVERLAYS)
addtimer(CALLBACK(src, PROC_REF(do_maketile), M), 2 SECONDS)
addtimer(CALLBACK(src, PROC_REF(do_make_tile), M), 2 SECONDS)
/mob/living/simple_animal/bot/floorbot/proc/do_maketile(obj/item/stack/sheet/metal/M)
/mob/living/simple_animal/bot/floorbot/proc/do_make_tile(obj/item/stack/sheet/metal/M)
if(isnull(M))
target = null
mode = BOT_IDLE
return
var/obj/item/stack/tile/plasteel/T = new /obj/item/stack/tile/plasteel
T.amount = 4
T.forceMove(M.loc)
if((amount + 4) > MAX_AMOUNT) // 1 metal = 4 tiles, hence + 4
var/missing_amount = MAX_AMOUNT - amount
var/extra = amount + 4 - MAX_AMOUNT
amount += missing_amount
new /obj/item/stack/tile/plasteel(get_turf(src), extra)
else
amount += 4
if(M.amount > 1)
M.amount--
M.amount --
else
qdel(M)
anchored = FALSE
target = null
mode = BOT_IDLE
update_icon(UPDATE_OVERLAYS)
@@ -408,7 +428,7 @@
if(prob(50))
drop_part(robot_arm, Tsec)
while(amount)//Dumps the tiles into the appropriate sized stacks
while(amount)// Dumps the tiles into the appropriate sized stacks
if(amount >= 16)
var/obj/item/stack/tile/plasteel/T = new (Tsec)
T.amount = 16
@@ -425,9 +445,9 @@
if(isturf(A))
repair(A)
else if(istype(A,/obj/item/stack/tile/plasteel))
start_eattile(A)
start_eat_tile(A)
else if(istype(A,/obj/item/stack/sheet/metal))
start_maketile(A)
start_make_tile(A)
else
..()