Files
Yogstation/code/modules/mob/living/simple_animal/bot/bot.dm
vuonojenmustaturska ddd4409133 Adds an EMP protection component, allowing ANYTHING to be protected from EMP (#37671)
The component is initialized with any combination of three flags, EMP_PROTECT_SELF protects against effects that target the object itself, EMP_PROTECT_WIRES protects against wires being messed with similar to the NO_EMP_WIRES_1 flag which this PR removes, EMP_PROTECT_CONTENTS protects against things that are inside the object like organs, internal "non-virtual" power cells and the like

This enables (but doesn't introduce) new mechanics such as adding EMP shielding at runtime to any atom, or taking it away.
2018-05-22 21:35:29 +01:00

1021 lines
37 KiB
Plaintext

// AI (i.e. game AI, not the AI player) controlled bots
/mob/living/simple_animal/bot
icon = 'icons/mob/aibots.dmi'
layer = MOB_LAYER
gender = NEUTER
mob_biotypes = list(MOB_ROBOTIC)
light_range = 3
stop_automated_movement = 1
wander = 0
healable = 0
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
maxbodytemp = INFINITY
minbodytemp = 0
has_unlimited_silicon_privilege = 1
sentience_type = SENTIENCE_ARTIFICIAL
status_flags = NONE //no default canpush
verb_say = "states"
verb_ask = "queries"
verb_exclaim = "declares"
verb_yell = "alarms"
initial_language_holder = /datum/language_holder/synthetic
bubble_icon = "machine"
faction = list("neutral", "silicon" , "turret")
var/obj/machinery/bot_core/bot_core = null
var/bot_core_type = /obj/machinery/bot_core
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_height = 0
var/obj/item/paicard/paicard // Inserted pai card.
var/allow_pai = 1 // Are we even allowed to insert a pai card.
var/bot_name
var/list/player_access = list() //Additonal access the bots gets when player controlled
var/emagged = FALSE
var/list/prev_access = list()
var/on = TRUE
var/open = FALSE//Maint panel
var/locked = TRUE
var/hacked = FALSE //Used to differentiate between being hacked by silicons and emagged by humans.
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, associated with the path image, if there is one.
var/pathset = 0
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 = 0 //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/radio_key = null //which channels can the bot listen to
var/radio_channel = "Common" //The bot's default radio channel
var/auto_patrol = 0// 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/shuffle = FALSE // If we should shuffle our adjacency checking
var/blockcount = 0 //number of times retried a blocked path
var/awaiting_beacon = 0 // count of pticks awaiting a beacon response
var/nearest_beacon // the nearest beacon's tag
var/turf/nearest_beacon_loc // the nearest beacon's location
var/beacon_freq = FREQ_NAV_BEACON
var/model = "" //The type of bot it is.
var/bot_type = 0 //The type of bot it is, for radio control.
var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC //The type of data HUD the bot uses. Diagnostic by default.
//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", \
"Proceeding to AI waypoint", "Navigating to Delivery Location", "Navigating to Home", \
"Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination")
var/datum/atom_hud/data/bot_path/path_hud = new /datum/atom_hud/data/bot_path()
var/path_image_icon = 'icons/mob/aibots.dmi'
var/path_image_icon_state = "path_indicator"
var/path_image_color = "#FFFFFF"
var/reset_access_timer_id
var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list
var/robot_arm = /obj/item/bodypart/r_arm/robot
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views
/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(paicard)
return "<b>pAI Controlled</b>"
else
return "<b>Autonomous</b>"
else if(!on)
return "<span class='bad'>Inactive</span>"
else if(!mode)
return "<span class='good'>Idle</span>"
else
return "<span class='average'>[mode_name[mode]]</span>"
/mob/living/simple_animal/bot/proc/turn_on()
if(stat)
return FALSE
on = TRUE
canmove = TRUE
set_light(initial(light_range))
update_icon()
diag_hud_set_botstat()
return TRUE
/mob/living/simple_animal/bot/proc/turn_off()
on = FALSE
canmove = FALSE
set_light(0)
bot_reset() //Resets an AI's call, should it exist.
update_icon()
/mob/living/simple_animal/bot/Initialize()
. = ..()
GLOB.bots_list += src
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.
access_card.access += ACCESS_ROBOTICS
set_custom_texts()
Radio = new/obj/item/radio(src)
if(radio_key)
Radio.keyslot = new radio_key
Radio.subspace_transmission = TRUE
Radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud.
Radio.recalculateChannels()
bot_core = new bot_core_type(src)
//Adds bot to the diagnostic HUD system
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
diag_hud_set_bothealth()
diag_hud_set_botstat()
diag_hud_set_botmode()
//If a bot has its own HUD (for player bots), provide it.
if(data_hud_type)
var/datum/atom_hud/datahud = GLOB.huds[data_hud_type]
datahud.add_hud_to(src)
if(path_hud)
path_hud.add_to_hud(src)
path_hud.add_hud_to(src)
/mob/living/simple_animal/bot/update_canmove()
. = ..()
if(!on)
. = 0
canmove = .
/mob/living/simple_animal/bot/Destroy()
if(path_hud)
QDEL_NULL(path_hud)
path_hud = null
GLOB.bots_list -= src
if(paicard)
ejectpai()
qdel(Radio)
qdel(access_card)
qdel(bot_core)
return ..()
/mob/living/simple_animal/bot/bee_friendly()
return TRUE
/mob/living/simple_animal/bot/death(gibbed)
explode()
..()
/mob/living/simple_animal/bot/proc/explode()
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.
locked = FALSE
emagged = 1
to_chat(user, "<span class='notice'>You bypass [src]'s controls.</span>")
return
if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
emagged = 2
remote_disabled = 1 //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.
to_chat(src, "<span class='userdanger'>(#$*#$^^( OVERRIDE DETECTED</span>")
add_logs(user, src, "emagged")
return
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>")
/mob/living/simple_animal/bot/examine(mob/user)
..()
if(health < maxHealth)
if(health > maxHealth/3)
to_chat(user, "[src]'s parts look loose.")
else
to_chat(user, "[src]'s parts look very loose!")
else
to_chat(user, "[src] is in pristine condition.")
/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
if(amount>0 && prob(10))
new /obj/effect/decal/cleanable/oil(loc)
. = ..()
/mob/living/simple_animal/bot/updatehealth()
..()
diag_hud_set_bothealth()
/mob/living/simple_animal/bot/med_hud_set_health()
return //we use a different hud
/mob/living/simple_animal/bot/med_hud_set_status()
return //we use a different hud
/mob/living/simple_animal/bot/handle_automated_action() //Master process which handles code common across most bots.
diag_hud_set_botmode()
if (ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk
for(var/ref in ignore_list)
var/atom/referredatom = locate(ref)
if (!referredatom || !istype(referredatom) || QDELETED(referredatom))
ignore_list -= ref
ignorelistcleanuptimer = 1
else
ignorelistcleanuptimer++
if(!on || client)
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.
call_mode()
return
if(BOT_SUMMON) //Called by PDA
bot_summon()
return
return TRUE //Successful completion. Used to prevent child process() continuing if this one is ended early.
/mob/living/simple_animal/bot/attack_hand(mob/living/carbon/human/H)
if(H.a_intent == INTENT_HELP)
interact(H)
else
return ..()
/mob/living/simple_animal/bot/attack_ai(mob/user)
if(!topic_denied(user))
interact(user)
else
to_chat(user, "<span class='warning'>[src]'s interface is not responding!</span>")
/mob/living/simple_animal/bot/interact(mob/user)
show_controls(user)
/mob/living/simple_animal/bot/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/screwdriver))
if(!locked)
open = !open
to_chat(user, "<span class='notice'>The maintenance panel is now [open ? "opened" : "closed"].</span>")
else
to_chat(user, "<span class='warning'>The maintenance panel is locked.</span>")
else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))
if(bot_core.allowed(user) && !open && !emagged)
locked = !locked
to_chat(user, "Controls are now [locked ? "locked." : "unlocked."]")
else
if(emagged)
to_chat(user, "<span class='danger'>ERROR</span>")
if(open)
to_chat(user, "<span class='warning'>Please close the access panel before locking it.</span>")
else
to_chat(user, "<span class='warning'>Access denied.</span>")
else if(istype(W, /obj/item/paicard))
insertpai(user, W)
else if(istype(W, /obj/item/hemostat) && paicard)
if(open)
to_chat(user, "<span class='warning'>Close the access panel before manipulating the personality slot!</span>")
else
to_chat(user, "<span class='notice'>You attempt to pull [paicard] free...</span>")
if(do_after(user, 30, target = src))
if (paicard)
user.visible_message("<span class='notice'>[user] uses [W] to pull [paicard] out of [bot_name]!</span>","<span class='notice'>You pull [paicard] out of [bot_name] with [W].</span>")
ejectpai(user)
else
user.changeNext_move(CLICK_CD_MELEE)
if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM)
if(health >= maxHealth)
to_chat(user, "<span class='warning'>[src] does not need a repair!</span>")
return
if(!open)
to_chat(user, "<span class='warning'>Unable to repair with the maintenance panel closed!</span>")
return
if(W.use_tool(src, user, 0, volume=40))
adjustHealth(-10)
user.visible_message("[user] repairs [src]!","<span class='notice'>You repair [src].</span>")
else
if(W.force) //if force is non-zero
do_sparks(5, TRUE, src)
..()
/mob/living/simple_animal/bot/bullet_act(obj/item/projectile/Proj)
if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN))
if(prob(75) && Proj.damage > 0)
do_sparks(5, TRUE, src)
return ..()
/mob/living/simple_animal/bot/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
var/was_on = on
stat |= EMPED
new /obj/effect/temp_visual/emp(loc)
if(paicard)
paicard.emp_act(severity)
src.visible_message("[paicard] is flies out of [bot_name]!","<span class='warning'>You are forcefully ejected from [bot_name]!</span>")
ejectpai(0)
if(on)
turn_off()
spawn(severity*300)
stat &= ~EMPED
if(was_on)
turn_on()
/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.
if((!on) || (!message))
return
if(channel && Radio.channels[channel])// Use radio if we have channel key
Radio.talk_into(src, message, channel, get_spans(), get_default_language())
else
say(message)
/mob/living/simple_animal/bot/get_spans()
return ..() | SPAN_ROBOT
/mob/living/simple_animal/bot/radio(message, message_mode, list/spans, language)
. = ..()
if(. != 0)
return
switch(message_mode)
if(MODE_HEADSET)
Radio.talk_into(src, message, , spans, language)
return REDUCE_RANGE
if(MODE_DEPARTMENT)
Radio.talk_into(src, message, message_mode, spans, language)
return REDUCE_RANGE
if(message_mode in GLOB.radiochannels)
Radio.talk_into(src, message, message_mode, spans, language)
return REDUCE_RANGE
/mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone)
var/dropped_item = new drop_item(dropzone)
drop_item = null
if(istype(dropped_item, /obj/item/stock_parts/cell))
var/obj/item/stock_parts/cell/dropped_cell = dropped_item
dropped_cell.charge = 0
dropped_cell.update_icon()
else if(istype(dropped_item, /obj/item/storage))
var/obj/item/storage/S = dropped_item
S.contents = list()
else if(istype(dropped_item, /obj/item/gun/energy))
var/obj/item/gun/energy/dropped_gun = dropped_item
dropped_gun.cell.charge = 0
dropped_gun.update_icon()
//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)
return
var/list/adjacent = T.GetAtmosAdjacentTurfs(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!
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)
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
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.
return
/mob/living/simple_animal/bot/proc/check_bot(targ)
var/turf/T = get_turf(targ)
if(T)
for(var/C in T.contents)
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)
else //If the list is full, insert newest, delete oldest.
ignore_list.Cut(1,2)
ignore_list += REF(subject)
/*
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 || path.len == 0) //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[path.len]) //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.
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.
if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES)
for(var/step_number = 0, step_number < step_count,step_number++)
spawn(BOT_STEP_DELAY*step_number)
bot_step(dest)
else
return FALSE
return TRUE
/mob/living/simple_animal/bot/proc/bot_step(dest) //Step,increase tries if failed
if(!path)
return FALSE
if(path.len > 1)
step_towards(src, path[1])
if(get_turf(src) == path[1]) //Successful move
increment_path()
tries = 0
else
tries++
return FALSE
else if(path.len == 1)
step_to(src, dest)
set_path(null)
return TRUE
/mob/living/simple_animal/bot/proc/check_bot_access()
if(mode != BOT_SUMMON && mode != BOT_RESPONDING)
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.
//For giving the bot temporary all-access.
var/obj/item/card/id/all_access = new /obj/item/card/id
var/datum/job/captain/All = new/datum/job/captain
all_access.access = All.get_access()
set_path(get_path_to(src, waypoint, /turf/proc/Distance_cardinal, 0, 200, id=all_access))
calling_ai = caller //Link the AI to the bot!
ai_waypoint = waypoint
if(path && path.len) //Ensures that a valid path is calculated!
var/end_area = get_area_name(waypoint)
if(!on)
turn_on() //Saves the AI the hassle of having to activate a bot manually.
access_card = all_access //Give the bot all-access while under the AI's command.
if(client)
reset_access_timer_id = addtimer(CALLBACK (src, .proc/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 [icon2html(calling_ai, src)] <b>[caller]</b>. Proceed to <b>[end_area]</b>.</span><br>[path.len-1] meters to destination. You have been granted additional door access for 60 seconds.</span>")
if(message)
to_chat(calling_ai, "<span class='notice'>[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.</span>")
pathset = 1
mode = BOT_RESPONDING
tries = 0
else
if(message)
to_chat(calling_ai, "<span class='danger'>Failed to calculate a valid route. Ensure destination is clear of obstructions and within range.</span>")
calling_ai = null
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.
var/success = bot_move(ai_waypoint, 3)
if(!success)
if(calling_ai)
to_chat(calling_ai, "[icon2html(src, calling_ai)] [get_turf(src) == ai_waypoint ? "<span class='notice'>[src] successfully arrived to waypoint.</span>" : "<span class='danger'>[src] failed to reach waypoint.</span>"]")
calling_ai = null
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.
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)
summon_target = null
pathset = 0
access_card.access = prev_access
tries = 0
mode = BOT_IDLE
diag_hud_set_botstat()
diag_hud_set_botmode()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Patrol and summon code!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mob/living/simple_animal/bot/proc/bot_patrol()
patrol_step()
spawn(5)
if(mode == BOT_PATROL)
patrol_step()
return
/mob/living/simple_animal/bot/proc/start_patrol()
if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol.
auto_patrol = 0
tries = 0
speak("Unable to start patrol.")
return
if(!auto_patrol) //A bot not set to patrol should not be patrolling.
mode = BOT_IDLE
return
if(patrol_target) // has patrol target
spawn(0)
calc_path() // Find a route to it
if(path.len == 0)
patrol_target = null
return
mode = BOT_PATROL
else // no patrol target, so need a new one
speak("Engaging patrol mode.")
find_patrol_target()
tries++
return
// 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(!get_next_patrol_target())
find_patrol_target() //If it fails, look for the nearest one instead.
return
else if(path.len > 0 && 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
spawn(2)
calc_path()
if(path.len == 0)
find_patrol_target()
tries = 0
else // no path, so calculate new one
mode = BOT_START_PATROL
// finds the nearest beacon to self
/mob/living/simple_animal/bot/proc/find_patrol_target()
nearest_beacon = null
new_destination = null
find_nearest_beacon()
if(nearest_beacon)
patrol_target = nearest_beacon_loc
destination = next_destination
else
auto_patrol = 0
mode = BOT_IDLE
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.
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 TRUE
/mob/living/simple_animal/bot/proc/find_nearest_beacon()
for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"])
var/dist = get_dist(src, NB)
if(nearest_beacon) //Loop though the beacon net to find the true closest beacon.
//Ignore the beacon if were are located on it.
if(dist>1 && dist<get_dist(src,nearest_beacon_loc))
nearest_beacon = NB.location
nearest_beacon_loc = NB.loc
next_destination = NB.codes["next_patrol"]
else
continue
else if(dist > 1) //Begin the search, save this one for comparison on the next loop.
nearest_beacon = NB.location
nearest_beacon_loc = NB.loc
patrol_target = nearest_beacon_loc
destination = nearest_beacon
//PDA control. Some bots, especially MULEs, may have more parameters.
/mob/living/simple_animal/bot/proc/bot_control(command, mob/user, turf/user_turf, list/user_access = list())
if(!on || emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
return TRUE //ACCESS DENIED
if(client)
bot_control_message(command,user,user_turf,user_access)
// process control input
switch(command)
if("patroloff")
bot_reset() //HOLD IT!!
auto_patrol = 0
if("patrolon")
auto_patrol = 1
if("summon")
bot_reset()
summon_target = user_turf
if(user_access.len != 0)
access_card.access = user_access + prev_access //Adds the user's access, if any.
mode = BOT_SUMMON
speak("Responding.", radio_channel)
calc_summon_path()
if("ejectpai")
ejectpairemote(user)
return
//
/mob/living/simple_animal/bot/proc/bot_control_message(command,user,user_turf,user_access)
switch(command)
if("patroloff")
to_chat(src, "<span class='warning big'>STOP PATROL</span>")
if("patrolon")
to_chat(src, "<span class='warning big'>START PATROL</span>")
if("summon")
var/area/a = get_area(user_turf)
to_chat(src, "<span class='warning big'>PRIORITY ALERT:[user] in [a.name]!</span>")
if("stop")
to_chat(src, "<span class='warning big'>STOP!</span>")
if("go")
to_chat(src, "<span class='warning big'>GO!</span>")
if("home")
to_chat(src, "<span class='warning big'>RETURN HOME!</span>")
if("ejectpai")
return
else
to_chat(src, "<span class='warning'>Unidentified control sequence recieved:[command]</span>")
/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
/mob/living/simple_animal/bot/proc/calc_path(turf/avoid)
check_bot_access()
set_path(get_path_to(src, patrol_target, /turf/proc/Distance_cardinal, 0, 120, id=access_card, exclude=avoid))
/mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid)
check_bot_access()
spawn()
set_path(get_path_to(src, summon_target, /turf/proc/Distance_cardinal, 0, 150, id=access_card, exclude=avoid))
if(!path.len) //Cannot reach target. Give up and announce the issue.
speak("Summon command failed, destination unreachable.",radio_channel)
bot_reset()
/mob/living/simple_animal/bot/proc/summon_step()
if(client) // In use by player, don't actually move.
return
if(loc == summon_target) // Arrived to summon location.
bot_reset()
return
else if(path.len > 0 && summon_target) //Proper path acquired!
if(path[1] == loc)
increment_path()
return
var/moved = bot_move(summon_target, 3) // Move attempt
if(!moved)
spawn(2)
calc_summon_path()
tries = 0
else // no path, so calculate new one
calc_summon_path()
/mob/living/simple_animal/bot/Collide(M as mob|obj) //Leave no door unopened!
. = ..()
if((istype(M, /obj/machinery/door/airlock) || istype(M, /obj/machinery/door/window)) && (!isnull(access_card)))
var/obj/machinery/door/D = M
if(D.check_access(access_card))
D.open()
frustration = 0
/mob/living/simple_animal/bot/proc/show_controls(mob/M)
users |= M
var/dat = ""
dat = get_controls(M)
var/datum/browser/popup = new(M,window_id,window_name,350,600)
popup.set_content(dat)
popup.open(use_onclose = 0)
onclose(M,window_id,ref=src)
return
/mob/living/simple_animal/bot/proc/update_controls()
for(var/mob/M in users)
show_controls(M)
/mob/living/simple_animal/bot/proc/get_controls(mob/M)
return "PROTOBOT - NOT FOR USE"
/mob/living/simple_animal/bot/Topic(href, href_list)
//No ..() to prevent strip panel showing up - Todo: make that saner
if(href_list["close"])// HUE HUE
if(usr in users)
users.Remove(usr)
return TRUE
if(topic_denied(usr))
to_chat(usr, "<span class='warning'>[src]'s interface is not responding!</span>")
return TRUE
add_fingerprint(usr)
if((href_list["power"]) && (bot_core.allowed(usr) || !locked))
if(on)
turn_off()
else
turn_on()
switch(href_list["operation"])
if("patrol")
auto_patrol = !auto_patrol
bot_reset()
if("remote")
remote_disabled = !remote_disabled
if("hack")
if(emagged != 2)
emagged = 2
hacked = TRUE
locked = TRUE
to_chat(usr, "<span class='warning'>[text_hack]</span>")
bot_reset()
else if(!hacked)
to_chat(usr, "<span class='boldannounce'>[text_dehack_fail]</span>")
else
emagged = FALSE
hacked = FALSE
to_chat(usr, "<span class='notice'>[text_dehack]</span>")
bot_reset()
if("ejectpai")
if(paicard && (!locked || issilicon(usr) || IsAdminGhost(usr)))
to_chat(usr, "<span class='notice'>You eject [paicard] from [bot_name]</span>")
ejectpai(usr)
update_controls()
/mob/living/simple_animal/bot/proc/update_icon()
icon_state = "[initial(icon_state)][on]"
// Machinery to simplify topic and access calls
/obj/machinery/bot_core
use_power = NO_POWER_USE
var/mob/living/simple_animal/bot/owner = null
/obj/machinery/bot_core/Initialize()
. = ..()
owner = loc
if(!istype(owner))
return INITIALIZE_HINT_QDEL
/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.canUseTopic(src))
return TRUE
// 0 for access, 1 for denied.
if(emagged == 2) //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) && !IsAdminGhost(user)) //Bot is hacked, so only silicons and admins are allowed access.
return TRUE
return FALSE
/mob/living/simple_animal/bot/proc/hack(mob/user)
var/hack
if(issilicon(user) || IsAdminGhost(user)) //Allows silicons or admins to toggle the emag status of a bot.
hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]<BR>"
hack += "Harm Prevention Safety System: <A href='?src=[REF(src)];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.
hack += "Remote network control radio: <A href='?src=[REF(src)];operation=remote'>[remote_disabled ? "Disconnected" : "Connected"]</A><BR>"
return hack
/mob/living/simple_animal/bot/proc/showpai(mob/user)
var/eject = ""
if((!locked || issilicon(usr) || IsAdminGhost(usr)))
if(paicard || allow_pai)
eject += "Personality card status: "
if(paicard)
if(client)
eject += "<A href='?src=[REF(src)];operation=ejectpai'>Active</A>"
else
eject += "<A href='?src=[REF(src)];operation=ejectpai'>Inactive</A>"
else if(!allow_pai || key)
eject += "Unavailable"
else
eject += "Not inserted"
eject += "<BR>"
eject += "<BR>"
return eject
/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/paicard/card)
if(paicard)
to_chat(user, "<span class='warning'>A [paicard] is already inserted!</span>")
else if(allow_pai && !key)
if(!locked && !open)
if(card.pai && card.pai.mind)
if(!user.transferItemToLoc(card, src))
return
paicard = card
user.visible_message("[user] inserts [card] into [src]!","<span class='notice'>You insert [card] into [src].</span>")
paicard.pai.mind.transfer_to(src)
to_chat(src, "<span class='notice'>You sense your form change as you are uploaded into [src].</span>")
bot_name = name
name = paicard.pai.name
faction = user.faction.Copy()
language_holder = paicard.pai.language_holder.copy(src)
add_logs(user, paicard.pai, "uploaded to [bot_name],")
return TRUE
else
to_chat(user, "<span class='warning'>[card] is inactive.</span>")
else
to_chat(user, "<span class='warning'>The personality slot is locked.</span>")
else
to_chat(user, "<span class='warning'>[src] is not compatible with [card]</span>")
/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = 1)
if(paicard)
if(mind && paicard.pai)
mind.transfer_to(paicard.pai)
else if(paicard.pai)
paicard.pai.key = key
else
ghostize(0) // The pAI card that just got ejected was dead.
key = null
paicard.forceMove(loc)
if(user)
add_logs(user, paicard.pai, "ejected from [src.bot_name],")
else
add_logs(src, paicard.pai, "ejected")
if(announce)
to_chat(paicard.pai, "<span class='notice'>You feel your control fade as [paicard] ejects from [bot_name].</span>")
paicard = null
name = bot_name
faction = initial(faction)
/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user)
if(bot_core.allowed(user) && paicard)
speak("Ejecting personality chip.", radio_channel)
ejectpai(user)
/mob/living/simple_animal/bot/Login()
. = ..()
access_card.access += player_access
diag_hud_set_botmode()
/mob/living/simple_animal/bot/Logout()
. = ..()
bot_reset()
/mob/living/simple_animal/bot/revive(full_heal = 0, admin_revive = 0)
if(..())
update_icon()
. = 1
/mob/living/simple_animal/bot/ghost()
if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet.
..()
if(paicard && (!client || stat == DEAD))
ejectpai(0)
/mob/living/simple_animal/bot/sentience_act()
faction -= "silicon"
/mob/living/simple_animal/bot/proc/set_path(list/newpath)
path = newpath ? newpath : list()
if(!path_hud)
return
var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED])
if(path_hud)
path_huds_watching_me += path_hud
for(var/V in path_huds_watching_me)
var/datum/atom_hud/H = V
H.remove_from_hud(src)
var/list/path_images = hud_list[DIAG_PATH_HUD]
QDEL_LIST(path_images)
if(newpath)
for(var/i in 1 to newpath.len)
var/turf/T = newpath[i]
if(T == loc) //don't bother putting an image if it's where we already exist.
continue
var/direction = NORTH
if(i > 1)
var/turf/prevT = path[i - 1]
var/image/prevI = path[prevT]
direction = get_dir(prevT, T)
if(i > 2)
var/turf/prevprevT = path[i - 2]
var/prevDir = get_dir(prevprevT, prevT)
var/mixDir = direction|prevDir
if(mixDir in GLOB.diagonals)
prevI.dir = mixDir
if(prevDir & (NORTH|SOUTH))
var/matrix/ntransform = matrix()
ntransform.Turn(90)
if((mixDir == NORTHWEST) || (mixDir == SOUTHEAST))
ntransform.Scale(-1, 1)
else
ntransform.Scale(1, -1)
prevI.transform = ntransform
var/mutable_appearance/MA = new /mutable_appearance()
MA.icon = path_image_icon
MA.icon_state = path_image_icon_state
MA.layer = ABOVE_OPEN_TURF_LAYER
MA.plane = 0
MA.appearance_flags = RESET_COLOR|RESET_TRANSFORM
MA.color = path_image_color
MA.dir = direction
var/image/I = image(loc = T)
I.appearance = MA
path[T] = I
path_images += I
for(var/V in path_huds_watching_me)
var/datum/atom_hud/H = V
H.add_to_hud(src)
/mob/living/simple_animal/bot/proc/increment_path()
if(!path || !path.len)
return
var/image/I = path[path[1]]
if(I)
I.icon_state = null
path.Cut(1, 2)