Files
Yogstation/code/modules/mob/living/simple_animal/bot/bot.dm
Byemoh d93428fe4b [MDB IGNORE] [PRE-ALPHA] MiniStation (#22568)
* Create MiniStation new.dmm

* ministation

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* ministayion

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* actual changes oops

* Update MiniStation.dmm

* Update MiniStation.dmm

* miniaturestation

* Update maps.txt

* more fixes

* fixes broken path

* more fixes

* Update MiniStation.dmm

* Update MiniStation.dmm

* filters

* robotics changes

* fixes pipes

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* spawns + job overrides

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* more work

* Update MiniStation.dmm

* misc stuff

* maint stuff

* uopdates

* fixes bad area and chair

* tool storage + fixes

* mining APC

* cargo + hooks up some disposals

* more disposals + launcher

* lights

* more fixes

* Update MiniStation.dmm

* Update MiniStation.dmm

* more fixes

* sink

* remove sign

* expand janitor room

* Update MiniStation.dmm

* ai stuff + more firealarms

* meeting room, etc

* fixes wire

* Update MiniStation.dmm

* fixes

* Update MiniStation.dmm

* yep!

* atmos area

* fixes some map errors

* bookbinder

* robo shutters

* stair and chem fix

* fixes bad decals

* honesly no clue what's different

* disposals + pets

* Update MiniStation.dmm

* asteroid area

* more fixes

* medbay mixup

* choom button

* wire toxin room to grid

* remove this

* more fixes

* Update ai_whale.dmm

* more cameras

* Update networking_machines.dm

* update freezer

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* new service

* hawk + tuah + backrooms

* special charters

* remove that lol

* move him a little

* Update MiniStation.dmm

* yeah

* Update MiniStation.dmm

* psych office + more fixes + robo stuff

* thermomachines

* Update MiniStation.dmm

* maints + APCs

* all this stuff

* cameras

* air alarms

* RD office and the like

* Update MiniStation.dmm

* fixes these

* glass floors

* Update MiniStation.dmm

* more firelocks

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* extra pipe moment

* Update MiniStation.dmm

* looking up and down/fixes

* neat

* wire

* Update MiniStation.dmm

* fixes some issues

* asdad

* better SM stuff

* amon gus

* fixing falling

* Update MiniStation.dmm

* Update MiniStation.dmm

* fixes this

* fixes

* Update MiniStation.dmm

* remove stairs

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* fixes some ai stuff

* Update MiniStation.dmm

* adds some missing bells

* maint

* more maint stuff

* ai upload and goodbye meeting room

* fixes multiz pda stuff

* fixes locker

* ministation fixes

* requested changes

* shadows

* removes shadows

* adds medhuds

* no smoking

* move up/down

* correct medhuds

* brig stuff

* cameras

* fixes

* more lights

* more fixes

* fix one turf

* idk randomstuff

* more lights

* navigation nation (thanks plant)

* fixes + better flooring

* jamie changes

* myriad changes

* some more stuff

* server room fix
2024-10-19 00:13:31 +01:00

1062 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 = MOB_ROBOTIC
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 = BUBBLE_MACHINE
speech_span = SPAN_ROBOT
faction = list("neutral", "silicon" , "turret")
light_system = MOVABLE_LIGHT
light_range = 3
light_power = 0.9
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 = 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
var/commissioned = FALSE // Will other (noncommissioned) bots salute this bot?
COOLDOWN_DECLARE(next_salute_check)
var/salute_delay = 60 SECONDS
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_bad("Inactive")
else if(!mode)
return span_good("Idle")
else
return span_average("[mode_name[mode]]")
/**
* Returns a status string about the bot's current status, if it's moving, manually controlled, or idle.
*/
/mob/living/simple_animal/bot/proc/get_mode_ui()
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.
return paicard ? "pAI Controlled" : "Autonomous"
else if(!on)
return "Inactive"
else if(!mode)
return "Idle"
else
return "[mode_name[mode]]"
/mob/living/simple_animal/bot/proc/turn_on()
if(stat)
return FALSE
on = TRUE
update_mobility()
set_light_on(on)
update_appearance(UPDATE_ICON)
diag_hud_set_botstat()
return TRUE
/mob/living/simple_animal/bot/proc/turn_off()
on = FALSE
update_mobility()
set_light_on(on)
bot_reset() //Resets an AI's call, should it exist.
update_appearance(UPDATE_ICON)
/mob/living/simple_animal/bot/Initialize(mapload)
. = ..()
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_ROBO_CONTROL
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_atom_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.show_to(src)
if(path_hud)
path_hud.add_atom_to_hud(src)
path_hud.show_to(src)
/mob/living/simple_animal/bot/update_mobility()
. = ..()
if(!on)
mobility_flags = NONE
/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, obj/item/card/emag/emag_card)
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_notice("You bypass [src]'s controls."))
return TRUE
if(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_userdanger("(#$*#$^^( OVERRIDE DETECTED"))
log_combat(user, src, "emagged")
return TRUE
// Bot is unlocked, but the maint panel has not been opened with a screwdriver yet.
to_chat(user, span_warning("You need to open maintenance panel first!"))
return FALSE
/mob/living/simple_animal/bot/examine(mob/user)
. = ..()
if(health < maxHealth)
if(health > maxHealth/3)
. += "[src]'s parts look loose."
else
. += "[src]'s parts look very loose!"
else
. += "[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
if(commissioned && COOLDOWN_FINISHED(src, next_salute_check))
COOLDOWN_START(src, next_salute_check, salute_delay)
for(var/mob/living/simple_animal/bot/B in view(5, src))
if(!B.commissioned && B.on)
visible_message("<b>[B]</b> performs an elaborate salute for [src]!")
break
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, modifiers)
if(!(H.combat_mode || (modifiers && modifiers[RIGHT_CLICK])))
interact(H)
else
return ..()
/mob/living/simple_animal/bot/attack_ai(mob/user)
if(!topic_denied(user))
interact(user)
else
to_chat(user, span_warning("[src]'s interface is not responding!"))
/mob/living/simple_animal/bot/interact(mob/user)
show_controls(user)
/mob/living/simple_animal/bot/attackby(obj/item/W, mob/living/user, params)
if(W.tool_behaviour == TOOL_SCREWDRIVER)
if(!locked)
open = !open
to_chat(user, span_notice("The maintenance panel is now [open ? "opened" : "closed"]."))
else
to_chat(user, span_warning("The maintenance panel is locked."))
else if(W.GetID())
togglelock(user)
else if(istype(W, /obj/item/paicard))
insertpai(user, W)
else if(istype(W, /obj/item/hemostat) && paicard)
if(open)
to_chat(user, span_warning("Close the access panel before manipulating the personality slot!"))
else
to_chat(user, span_notice("You attempt to pull [paicard] free..."))
if(do_after(user, 3 SECONDS, src))
if (paicard)
user.visible_message(span_notice("[user] uses [W] to pull [paicard] out of [bot_name]!"),span_notice("You pull [paicard] out of [bot_name] with [W]."))
ejectpai(user)
else
user.changeNext_move(CLICK_CD_MELEE)
if(W.tool_behaviour == TOOL_WELDER && !user.combat_mode)
if(health >= maxHealth)
to_chat(user, span_warning("[src] does not need a repair!"))
return
if(!open)
to_chat(user, span_warning("Unable to repair with the maintenance panel closed!"))
return
if(W.use_tool(src, user, 0, volume=40))
adjustHealth(-10)
user.visible_message("[user] repairs [src]!",span_notice("You repair [src]."))
else
if(W.force) //if force is non-zero
do_sparks(5, TRUE, src)
..()
/mob/living/simple_animal/bot/proc/togglelock(mob/user)
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_danger("ERROR"))
if(open)
to_chat(user, span_warning("Please close the access panel before locking it."))
else
to_chat(user, span_warning("Access denied."))
/mob/living/simple_animal/bot/AltClick(mob/user)
if(Adjacent(user))
togglelock(user)
/mob/living/simple_animal/bot/bullet_act(obj/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_warning("You are forcefully ejected from [bot_name]!"))
ejectpai(0)
if(on)
turn_off()
spawn(30 * severity)
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)
else
say(message)
/mob/living/simple_animal/bot/radio(message, list/message_mods = list(), list/spans, language)
. = ..()
if(. != 0)
return
if(message_mods[MODE_HEADSET])
Radio.talk_into(src, message, , spans, language, message_mods)
return REDUCE_RANGE
else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT)
Radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return REDUCE_RANGE
else if(message_mods[RADIO_EXTENSION] in GLOB.radiochannels)
Radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return REDUCE_RANGE
/mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone)
var/obj/item/dropped_item
if(ispath(drop_item))
dropped_item = new drop_item(dropzone)
else
dropped_item = drop_item
dropped_item.forceMove(dropzone)
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_appearance(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_appearance(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.
/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_REF(bot_reset)), 600, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time
to_chat(src, span_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."))
if(message)
to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination."))
pathset = 1
mode = BOT_RESPONDING
tries = 0
else
if(message)
to_chat(calling_ai, span_danger("Failed to calculate a valid route. Ensure destination is clear of obstructions and within range."))
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_notice("[src] successfully arrived to waypoint.") : span_danger("[src] failed to reach waypoint.")]")
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_danger("Call command to a bot has been reset."))
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.
var/list/connected_levels = SSmapping.get_connected_levels(z)
for(var/level in connected_levels)
for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[level]"])
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()
var/list/connected_levels = SSmapping.get_connected_levels(z)
for(var/level in connected_levels)
for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[level]"])
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, 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)
// 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 = get_turf(user)
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)
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")
to_chat(src, "<span class='warning big'>PRIORITY ALERT:[user] in [get_area_name(user)]!</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_warning("Unidentified control sequence received:[command]"))
/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/Bump(atom/A) //Leave no door unopened!
. = ..()
if((istype(A, /obj/machinery/door/airlock) || istype(A, /obj/machinery/door/window)) && (!isnull(access_card)))
var/obj/machinery/door/D = A
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_warning("[src]'s interface is not responding!"))
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")
if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
return TRUE
auto_patrol = !auto_patrol
bot_reset()
if("remote")
remote_disabled = !remote_disabled
if("hack")
if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked))
return TRUE
if(emagged != 2)
emagged = 2
hacked = TRUE
locked = TRUE
to_chat(usr, span_warning("[text_hack]"))
bot_reset()
else if(!hacked)
to_chat(usr, span_boldannounce("[text_dehack_fail]"))
else
emagged = FALSE
hacked = FALSE
to_chat(usr, span_notice("[text_dehack]"))
bot_reset()
if("ejectpai")
if(paicard && (!locked || issilicon(usr) || IsAdminGhost(usr)))
to_chat(usr, span_notice("You eject [paicard] from [bot_name]"))
ejectpai(usr)
update_controls()
/mob/living/simple_animal/bot/update_icon_state()
. = ..()
icon_state = "[initial(icon_state)][on]"
// Machinery to simplify topic and access calls
/obj/machinery/bot_core
use_power = NO_POWER_USE
anchored = FALSE
var/mob/living/simple_animal/bot/owner = null
/obj/machinery/bot_core/Initialize(mapload)
. = ..()
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, !issilicon(user)))
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_bad("DANGER") : "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_warning("A [paicard] is already inserted!"))
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_notice("You insert [card] into [src]."))
paicard.pai.mind.transfer_to(src)
to_chat(src, span_notice("You sense your form change as you are uploaded into [src]."))
bot_name = name
name = paicard.pai.name
faction = user.faction.Copy()
log_combat(user, paicard.pai, "uploaded to [bot_name],")
return TRUE
else
to_chat(user, span_warning("[card] is inactive."))
else
to_chat(user, span_warning("The personality slot is locked."))
else
to_chat(user, span_warning("[src] is not compatible with [card]"))
/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)
log_combat(user, paicard.pai, "ejected from [src.bot_name],")
else
log_combat(src, paicard.pai, "ejected")
if(announce)
to_chat(paicard.pai, span_notice("You feel your control fade as [paicard] ejects from [bot_name]."))
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_appearance(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_atom_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_atom_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)
/mob/living/simple_animal/bot/rust_heretic_act()
adjustBruteLoss(400)