Files
Bubberstation/code/modules/mob/living/basic/bots/_bots.dm
SkyratBot 24afc641c5 [MIRROR] hygeienbots basic bots [MDB IGNORE] (#25923)
* hygeienbots basic bots (#80435)

## About The Pull Request
turns hygenic bots into basic bots. also now PAIs and people can play as
hygeinebots. and they can wear hats

## Why It's Good For The Game
transforms hyginebots into basic bots. their old AI used to handle all
the logic. i moved some of the logic to the mob itself so players can
also clean (or burn) things. also this pr will add pathing limits to
bots, in the case the jps movement thinks it can reach something, but
actually cant, in which case the bot will give up the chase

## Changelog
🆑
refactor: hygeinebots are now basic bots. please report all the bugs
fix: fixes hygenebots not being able to patrol
add: hygeinebots can now be controlled by Players
/🆑

* hygeienbots basic bots

---------

Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
2023-12-31 14:45:33 +00:00

832 lines
29 KiB
Plaintext

GLOBAL_LIST_INIT(command_strings, list(
"patroloff" = "STOP PATROL",
"patrolon" = "START PATROL",
"stop" = "STOP",
"go" = "GO",
"home" = "RETURN HOME",
))
/mob/living/basic/bot
icon = 'icons/mob/silicon/aibots.dmi'
layer = MOB_LAYER
gender = NEUTER
mob_biotypes = MOB_ROBOTIC
basic_mob_flags = DEL_ON_DEATH
icon = 'icons/mob/silicon/aibots.dmi'
icon_state = "medibot0"
base_icon_state = "medibot"
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0)
habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST)
maximum_survivable_temperature = INFINITY
minimum_survivable_temperature = 0
has_unlimited_silicon_privilege = TRUE
sentience_type = SENTIENCE_ARTIFICIAL
status_flags = NONE //no default canpush
faction = list(FACTION_MINING)
ai_controller = /datum/ai_controller/basic_controller/bot
pass_flags = PASSFLAPS
verb_say = "states"
verb_ask = "queries"
verb_exclaim = "declares"
verb_yell = "alarms"
initial_language_holder = /datum/language_holder/synthetic
bubble_icon = "machine"
speech_span = SPAN_ROBOT
faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET)
light_system = MOVABLE_LIGHT
light_range = 3
light_power = 0.9
speed = 3
///Access required to access this Bot's maintenance protocols
var/maints_access_required = list(ACCESS_ROBOTICS)
///The Robot arm attached to this robot - has a 50% chance to drop on death.
var/robot_arm = /obj/item/bodypart/arm/right/robot
///The inserted (if any) pAI in this bot.
var/obj/item/pai_card/paicard
///The type of bot it is, for radio control.
var/bot_type = NONE
///All initial access this bot started with.
var/list/initial_access = list()
///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_MAINTS_PANEL_OPEN | BOT_CONTROL_PANEL_OPEN | BOT_COVER_EMAGGED | BOT_COVER_HACKED
var/bot_access_flags = NONE
///Small name of what the bot gets messed with when getting hacked/emagged.
var/hackables = "system circuits"
///Standardizes the vars that indicate the bot is busy with its function.
var/mode = BOT_IDLE
///Links a bot to the AI calling it.
var/datum/weakref/calling_ai_ref
///The bot's radio, for speaking to people.
var/obj/item/radio/internal_radio
///which channels can the bot listen to
var/radio_key = null
///The bot's default radio channel
var/radio_channel = RADIO_CHANNEL_COMMON
///our access card
var/obj/item/card/id/access_card
///The trim type that will grant additional acces
var/datum/id_trim/additional_access
///file the path icon is stored in
var/path_image_icon = 'icons/mob/silicon/aibots.dmi'
///state of the path icon
var/path_image_icon_state = "path_indicator"
///what color this path icon will use
var/path_image_color = "#FFFFFF"
///list of all layed path icons
var/list/current_pathed_turfs = list()
///The type of data HUD the bot uses. Diagnostic by default.
var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC
/// If true we will allow ghosts to control this mob
var/can_be_possessed = FALSE
/// Message to display upon possession
var/possessed_message = "You're a generic bot. How did one of these even get made?"
/// Action we use to say voice lines out loud, also we just pass anything we try to say through here just in case it plays a voice line
var/datum/action/cooldown/bot_announcement/pa_system
/// Type of bot_announcement ability we want
var/announcement_type
///list of traits we apply and remove when turning on/off
var/static/list/on_toggle_traits = list(
TRAIT_INCAPACITATED,
TRAIT_IMMOBILIZED,
TRAIT_HANDS_BLOCKED,
)
/// If true we will offer this
COOLDOWN_DECLARE(offer_ghosts_cooldown)
/mob/living/basic/bot/Initialize(mapload)
. = ..()
AddElement(/datum/element/relay_attackers)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement))
RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked))
RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access))
ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT)
GLOB.bots_list += src
// Give bots a fancy new ID card that can hold any access.
access_card = new /obj/item/card/id/advanced/simple_bot(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.set_access(list(ACCESS_ROBOTICS))
provide_additional_access()
internal_radio = new /obj/item/radio(src)
if(radio_key)
internal_radio.keyslot = new radio_key
internal_radio.subspace_transmission = TRUE
internal_radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud.
internal_radio.recalculateChannels()
//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(!isnull(data_hud_type))
var/datum/atom_hud/datahud = GLOB.huds[data_hud_type]
datahud.show_to(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_BOTS_GLITCHED))
randomize_language_if_on_station()
if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT) && (bot_mode_flags & BOT_MODE_ROUNDSTART_POSSESSION))
enable_possession(mapload = mapload)
pa_system = (isnull(announcement_type)) ? new(src, automated_announcements = generate_speak_list()) : new announcement_type(src, automated_announcements = generate_speak_list())
pa_system.Grant(src)
ai_controller.set_blackboard_key(BB_ANNOUNCE_ABILITY, pa_system)
ai_controller.set_blackboard_key(BB_RADIO_CHANNEL, radio_channel)
update_appearance()
/mob/living/basic/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.
return span_bold("[paicard ? "pAI Controlled" : "Autonomous"]")
if(!(bot_mode_flags & BOT_MODE_ON))
return span_bad("Inactive")
return span_average("[mode]")
/**
* Returns a status string about the bot's current status, if it's moving, manually controlled, or idle.
*/
/mob/living/basic/bot/proc/get_mode_ui()
if(client)
return paicard ? "pAI Controlled" : "Autonomous"
if(!(bot_mode_flags & BOT_MODE_ON))
return "Inactive"
return "[mode]"
/**
* Returns a string of flavor text for emagged bots as defined by policy.
*/
/mob/living/basic/bot/proc/get_emagged_message()
return get_policy(ROLE_EMAGGED_BOT) || "You are a malfunctioning bot! Disrupt everyone and cause chaos!"
/mob/living/basic/bot/proc/turn_on()
if(stat == DEAD)
return FALSE
bot_mode_flags |= BOT_MODE_ON
remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), POWER_LACK_TRAIT)
set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE)
update_appearance()
balloon_alert(src, "turned on")
diag_hud_set_botstat()
return TRUE
/mob/living/basic/bot/proc/turn_off()
bot_mode_flags &= ~BOT_MODE_ON
add_traits(on_toggle_traits, POWER_LACK_TRAIT)
set_light_on(bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE)
bot_reset() //Resets an AI's call, should it exist.
balloon_alert(src, "turned off")
update_appearance()
/mob/living/basic/bot/Destroy()
GLOB.bots_list -= src
calling_ai_ref = null
clear_path_hud()
QDEL_NULL(paicard)
QDEL_NULL(pa_system)
QDEL_NULL(internal_radio)
QDEL_NULL(access_card)
return ..()
/// Allows this bot to be controlled by a ghost, who will become its mind
/mob/living/basic/bot/proc/enable_possession(user, mapload = FALSE)
if (paicard)
balloon_alert(user, "already sapient!")
return
can_be_possessed = TRUE
var/can_announce = !mapload && COOLDOWN_FINISHED(src, offer_ghosts_cooldown)
AddComponent(
/datum/component/ghost_direct_control, \
ban_type = ROLE_BOT, \
poll_candidates = can_announce, \
poll_ignore_key = POLL_IGNORE_BOTS, \
assumed_control_message = (bot_access_flags & BOT_COVER_EMAGGED) ? get_emagged_message() : possessed_message, \
extra_control_checks = CALLBACK(src, PROC_REF(check_possession)), \
after_assumed_control = CALLBACK(src, PROC_REF(post_possession)), \
)
if (can_announce)
COOLDOWN_START(src, offer_ghosts_cooldown, 30 SECONDS)
/// Disables this bot from being possessed by ghosts
/mob/living/basic/bot/proc/disable_possession(mob/user)
can_be_possessed = FALSE
if(isnull(key))
return
if (user)
log_combat(user, src, "ejected from [initial(src.name)] control.")
to_chat(src, span_warning("You feel yourself fade as your personality matrix is reset!"))
ghostize(can_reenter_corpse = FALSE)
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
speak("Personality matrix reset!")
key = null
/// Returns true if this mob can be controlled
/mob/living/basic/bot/proc/check_possession(mob/potential_possessor)
if (!can_be_possessed)
to_chat(potential_possessor, span_warning("The bot's personality download has been disabled!"))
return can_be_possessed
/// Fired after something takes control of this mob
/mob/living/basic/bot/proc/post_possession()
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
speak("New personality installed successfully!")
rename(src)
/// Allows renaming the bot to something else
/mob/living/basic/bot/proc/rename(mob/user)
var/new_name = sanitize_name(
reject_bad_text(tgui_input_text(
user = user,
message = "This machine is designated [real_name]. Would you like to update its registration?",
title = "Name change",
default = real_name,
max_length = MAX_NAME_LEN,
)),
allow_numbers = TRUE,
)
if (isnull(new_name) || QDELETED(src))
return
if (key && user != src)
var/accepted = tgui_alert(
src,
message = "Do you wish to be renamed to [new_name]?",
title = "Name change",
buttons = list("Yes", "No"),
)
if (accepted != "Yes" || QDELETED(src))
return
fully_replace_character_name(real_name, new_name)
/mob/living/basic/bot/proc/check_access(mob/living/user, obj/item/card/id)
if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access.
return TRUE
if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI).
return FALSE
if(!length(maints_access_required)) // No requirements to access it.
return TRUE
if(bot_access_flags & BOT_CONTROL_PANEL_OPEN) // Unlocked.
return TRUE
var/obj/item/card/id/used_id = id || user.get_idcard(TRUE)
if(!used_id || !used_id.access)
return FALSE
for(var/requested_access in maints_access_required)
if(requested_access in used_id.access)
return TRUE
return FALSE
/mob/living/basic/bot/bee_friendly()
return TRUE
/mob/living/basic/bot/death(gibbed)
if(paicard)
ejectpai()
explode()
return ..()
/mob/living/basic/bot/proc/explode()
visible_message(span_boldnotice("[src] blows apart!"))
do_sparks(3, TRUE, src)
var/atom/location_destroyed = drop_location()
if(prob(50))
drop_part(robot_arm, location_destroyed)
/mob/living/basic/bot/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN)) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
bot_access_flags |= BOT_CONTROL_PANEL_OPEN
balloon_alert(user, "cover unlocked")
return TRUE
if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN) || !(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging.
balloon_alert(user, "open maintenance panel first!")
return FALSE
bot_access_flags |= BOT_COVER_EMAGGED
bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN
bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED //Manually emagging the bot also locks the AI from controlling it.
bot_reset()
turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP.
to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED"))
to_chat(src, span_boldnotice(get_emagged_message()))
if(user)
log_combat(user, src, "emagged")
return TRUE
/mob/living/basic/bot/examine(mob/user)
. = ..()
if(health < maxHealth)
if(health > (maxHealth * 0.3))
. += "[src]'s parts look loose."
else
. += "[src]'s parts look very loose!"
else
. += "[src] is in pristine condition."
. += span_notice("Its maintenance panel is [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "open" : "closed"].")
. += span_info("You can use a <b>screwdriver</b> to [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "close" : "open"] it.")
if(bot_access_flags & BOT_MAINTS_PANEL_OPEN)
. += span_notice("Its control panel is [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"].")
if(!(bot_access_flags & BOT_COVER_EMAGGED) && (issilicon(user) || user.Adjacent(src)))
. += span_info("Alt-click [issilicon(user) ? "" : "or use your ID on "]it to [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "" : "un"]lock its control panel.")
if(isnull(paicard))
return
. += span_notice("It has a pAI device installed.")
if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN))
. += span_info("You can use a <b>hemostat</b> to remove it.")
/mob/living/basic/bot/updatehealth()
. = ..()
diag_hud_set_bothealth()
/mob/living/basic/bot/med_hud_set_health()
return //we use a different hud
/mob/living/basic/bot/med_hud_set_status()
return //we use a different hud
/mob/living/basic/bot/attack_hand(mob/living/carbon/human/user, list/modifiers)
if(!user.combat_mode)
ui_interact(user)
return
return ..()
/mob/living/basic/bot/attack_ai(mob/user)
if(!topic_denied(user))
ui_interact(user)
return
to_chat(user, span_warning("[src]'s interface is not responding!"))
/mob/living/basic/bot/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SimpleBot", name)
ui.open()
/mob/living/basic/bot/AltClick(mob/user)
. = ..()
if(!can_interact(user))
return
if(!user.can_perform_action(src, ALLOW_SILICON_REACH))
return
unlock_with_id(user)
/mob/living/basic/bot/proc/unlock_with_id(mob/living/user)
if(bot_access_flags & BOT_COVER_EMAGGED)
balloon_alert(user, "error!")
return
if(bot_access_flags & BOT_MAINTS_PANEL_OPEN)
balloon_alert(user, "access panel must be closed!")
return
if(!check_access(user))
balloon_alert(user, "no access")
return
bot_access_flags ^= BOT_CONTROL_PANEL_OPEN
to_chat(user, span_notice("Controls are now [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"]."))
return TRUE
/mob/living/basic/bot/screwdriver_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_SUCCESS
if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN))
to_chat(user, span_warning("The maintenance panel is locked!"))
return
tool.play_tool_sound(src)
bot_access_flags ^= BOT_MAINTS_PANEL_OPEN
to_chat(user, span_notice("The maintenance panel is now [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "opened" : "closed"]."))
/mob/living/basic/bot/welder_act(mob/living/user, obj/item/tool)
user.changeNext_move(CLICK_CD_MELEE)
if(user.combat_mode)
return FALSE
. = ITEM_INTERACT_SUCCESS
if(health >= maxHealth)
user.balloon_alert(user, "no repairs needed!")
return
if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN))
user.balloon_alert(user, "maintenance panel closed!")
return
if(!tool.use_tool(src, user, 0 SECONDS, volume=40))
return
heal_overall_damage(10)
user.visible_message(span_notice("[user] repairs [src]!"),span_notice("You repair [src]."))
/mob/living/basic/bot/attackby(obj/item/attacking_item, mob/living/user, params)
if(attacking_item.GetID())
unlock_with_id(user)
return
if(istype(attacking_item, /obj/item/pai_card))
insertpai(user, attacking_item)
return
if(attacking_item.tool_behaviour != TOOL_HEMOSTAT || !paicard)
return ..()
if(bot_access_flags & BOT_MAINTS_PANEL_OPEN)
balloon_alert(user, "open the access panel!")
return
balloon_alert(user, "removing pAI...")
if(!do_after(user, 3 SECONDS, target = src) || !paicard)
return
user.visible_message(span_notice("[user] uses [attacking_item] to pull [paicard] out of [initial(src.name)]!"), \
span_notice("You pull [paicard] out of [initial(src.name)] with [attacking_item]."))
ejectpai(user)
/mob/living/basic/bot/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
if(damage_done > 0 && attacking_item.damtype != STAMINA && stat != DEAD)
do_sparks(5, TRUE, src)
. = TRUE
return ..() || .
/mob/living/basic/bot/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
. = ..()
if(prob(25) || . != BULLET_ACT_HIT)
return
if(hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN)
return
if(!hitting_projectile.is_hostile_projectile() || hitting_projectile.damage <= 0)
return
do_sparks(5, TRUE, src)
/mob/living/basic/bot/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
new /obj/effect/temp_visual/emp(loc)
if(paicard)
paicard.emp_act(severity)
src.visible_message(span_notice("[paicard] flies out of [initial(src.name)]!"), span_warning("You are forcefully ejected from [initial(src.name)]!"))
ejectpai()
if (QDELETED(src))
return
if(bot_mode_flags & BOT_MODE_ON)
turn_off()
else
addtimer(CALLBACK(src, PROC_REF(turn_on)), severity * 30 SECONDS)
if(!prob(70/severity) || !length(GLOB.uncommon_roundstart_languages))
return
remove_all_languages(source = LANGUAGE_EMP)
grant_random_uncommon_language(source = LANGUAGE_EMP)
/**
* Pass a message to have the bot say() it, passing through our announcement action to potentially also play a sound.
* Optionally pass a frequency to say it on the radio.
*/
/mob/living/basic/bot/proc/speak(message, channel)
if(!message)
return
pa_system.announce(message, channel)
/mob/living/basic/bot/radio(message, list/message_mods = list(), list/spans, language)
. = ..()
if(.)
return
if(message_mods[MODE_HEADSET])
internal_radio.talk_into(src, message, , spans, language, message_mods)
return REDUCE_RANGE
if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT)
internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return REDUCE_RANGE
if(message_mods[RADIO_EXTENSION] in GLOB.radiochannels)
internal_radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return REDUCE_RANGE
/mob/living/basic/bot/proc/drop_part(obj/item/drop_item, dropzone)
var/obj/item/item_to_drop
if(ispath(drop_item))
item_to_drop = new drop_item(dropzone)
else
item_to_drop = drop_item
item_to_drop.forceMove(dropzone)
if(istype(item_to_drop, /obj/item/stock_parts/cell))
var/obj/item/stock_parts/cell/dropped_cell = item_to_drop
dropped_cell.charge = 0
dropped_cell.update_appearance()
return
if(istype(item_to_drop, /obj/item/storage))
item_to_drop.contents = list()
return
if(!istype(item_to_drop, /obj/item/gun/energy))
return
var/obj/item/gun/energy/dropped_gun = item_to_drop
dropped_gun.cell.charge = 0
dropped_gun.update_appearance()
/mob/living/basic/bot/proc/bot_reset(bypass_ai_reset = FALSE)
SEND_SIGNAL(src, COMSIG_BOT_RESET)
if(length(initial_access))
access_card.set_access(initial_access)
diag_hud_set_botstat()
diag_hud_set_botmode()
clear_path_hud()
if(bypass_ai_reset || isnull(calling_ai_ref))
return
var/mob/living/ai_caller = calling_ai_ref.resolve()
if(isnull(ai_caller))
return
to_chat(ai_caller, span_danger("Call command to a bot has been reset."))
calling_ai_ref = null
//PDA control. Some bots, especially MULEs, may have more parameters.
/mob/living/basic/bot/proc/bot_control(command, mob/user, list/user_access = list())
if(!(bot_mode_flags & BOT_MODE_ON) || bot_access_flags & BOT_COVER_EMAGGED || !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
return TRUE //ACCESS DENIED
if(client && command != "ejectpai")
bot_control_message(command, user)
// process control input
switch(command)
if("patroloff")
bot_reset() //HOLD IT!! //OBJECTION!!
bot_mode_flags &= ~BOT_MODE_AUTOPATROL
if("patrolon")
bot_mode_flags |= BOT_MODE_AUTOPATROL
if("summon")
summon_bot(user, user_access = user_access)
if("ejectpai")
eject_pai_remote(user)
/mob/living/basic/bot/proc/bot_control_message(command, user)
if(command == "summon")
return "PRIORITY ALERT:[user] in [get_area_name(user)]!"
return GLOB.command_strings[command] || "Unidentified control sequence received:[command]"
/mob/living/basic/bot/ui_data(mob/user)
var/list/data = list()
data["can_hack"] = (issilicon(user) || isAdminGhostAI(user))
data["custom_controls"] = list()
data["emagged"] = bot_access_flags & BOT_COVER_EMAGGED
data["has_access"] = check_access(user)
data["locked"] = !(bot_access_flags & BOT_CONTROL_PANEL_OPEN)
data["settings"] = list()
if((bot_access_flags & BOT_CONTROL_PANEL_OPEN) || issilicon(user) || isAdminGhostAI(user))
data["settings"]["pai_inserted"] = !isnull(paicard)
data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT
data["settings"]["possession_enabled"] = can_be_possessed
data["settings"]["airplane_mode"] = !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED)
data["settings"]["maintenance_lock"] = !(bot_access_flags & BOT_MAINTS_PANEL_OPEN)
data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON
data["settings"]["patrol_station"] = bot_mode_flags & BOT_MODE_AUTOPATROL
return data
// Actions received from TGUI
/mob/living/basic/bot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/mob/the_user = ui.user
if(!check_access(the_user))
balloon_alert(the_user, "access denied!")
return
if(action == "lock")
bot_access_flags ^= BOT_CONTROL_PANEL_OPEN
switch(action)
if("power")
if(bot_mode_flags & BOT_MODE_ON)
turn_off()
else
turn_on()
if("maintenance")
bot_access_flags ^= BOT_MAINTS_PANEL_OPEN
if("patrol")
bot_mode_flags ^= BOT_MODE_AUTOPATROL
bot_reset()
if("airplane")
bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED
if("hack")
if(!(issilicon(the_user) || isAdminGhostAI(the_user)))
return
if(!(bot_access_flags & BOT_COVER_EMAGGED))
bot_access_flags |= (BOT_COVER_EMAGGED|BOT_COVER_HACKED)
bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN
to_chat(the_user, span_warning("You overload [src]'s [hackables]."))
message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(the_user)] in [ADMIN_VERBOSEJMP(the_user)]")
the_user.log_message("disabled safety lock of [the_user]", LOG_GAME)
bot_reset()
to_chat(src, span_userdanger("(#$*#$^^( OVERRIDE DETECTED"))
to_chat(src, span_boldnotice(get_emagged_message()))
return
if(!(bot_access_flags & BOT_COVER_HACKED))
to_chat(the_user, span_boldannounce("You fail to repair [src]'s [hackables]."))
return
bot_access_flags &= ~(BOT_COVER_EMAGGED|BOT_COVER_HACKED)
to_chat(the_user, span_notice("You reset the [src]'s [hackables]."))
the_user.log_message("re-enabled safety lock of [src]", LOG_GAME)
bot_reset()
to_chat(src, span_userdanger("Software restored to standard."))
to_chat(src, span_boldnotice(possessed_message))
if("eject_pai")
if(!paicard)
return
to_chat(the_user, span_notice("You eject [paicard] from [initial(src.name)]."))
ejectpai(the_user)
if("toggle_personality")
if (can_be_possessed)
disable_possession(the_user)
else
enable_possession(the_user)
if("rename")
rename(the_user)
/mob/living/basic/bot/update_icon_state()
icon_state = "[isnull(base_icon_state) ? initial(icon_state) : base_icon_state][bot_mode_flags & BOT_MODE_ON]"
return ..()
/// Access check proc for bot topics! Remember to place in a bot's individual Topic if desired.
/mob/living/basic/bot/proc/topic_denied(mob/user)
if(!user.can_perform_action(src, ALLOW_SILICON_REACH))
return TRUE
// 0 for access, 1 for denied.
if(!(bot_access_flags & BOT_COVER_EMAGGED)) //An emagged bot cannot be controlled by humans, silicons can if one hacked it.
return FALSE
if(!(bot_access_flags & BOT_COVER_HACKED)) //Manually emagged by a human - access denied to all.
return TRUE
if(!issilicon(user) && !isAdminGhostAI(user)) //Bot is hacked, so only silicons and admins are allowed access.
return TRUE
return FALSE
/// Places a pAI in control of this mob
/mob/living/basic/bot/proc/insertpai(mob/user, obj/item/pai_card/card)
if(paicard)
balloon_alert(user, "slot occupied!")
return
if(key)
balloon_alert(user, "personality already present!")
return
if(!(bot_access_flags & BOT_COVER_OPEN))
balloon_alert(user, "slot inaccessible!")
return
if(!(bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT))
balloon_alert(user, "incompatible firmware!")
return
if(isnull(card.pai?.mind))
balloon_alert(user, "pAI is inactive!")
return
if(!user.transferItemToLoc(card, src))
return
paicard = card
disable_possession()
paicard.pai.fold_in()
copy_languages(paicard.pai, source_override = LANGUAGE_PAI)
set_active_language(paicard.pai.get_selected_language())
user.visible_message(span_notice("[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]."))
name = paicard.pai.name
faction = user.faction.Copy()
log_combat(user, paicard.pai, "uploaded to [initial(src.name)],")
return TRUE
/mob/living/basic/bot/ghost()
if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet.
return ..()
if(paicard && (!client || stat == DEAD))
ejectpai()
/// Ejects a pAI from this bot
/mob/living/basic/bot/proc/ejectpai(mob/user = null, announce = TRUE)
if(isnull(paicard))
return
if(paicard.pai)
if(isnull(mind))
mind.transfer_to(paicard.pai)
else
paicard.pai.key = key
else
ghostize(FALSE) // The pAI card that just got ejected was dead.
key = null
paicard.forceMove(drop_location())
var/to_log = user ? user : src
log_combat(to_log, paicard.pai, "ejected [user ? "from [initial(name)]" : ""].")
if(announce)
to_chat(paicard.pai, span_notice("You feel your control fade as [paicard] ejects from [initial(name)]."))
paicard = null
name = initial(name)
faction = initial(faction)
remove_all_languages(source = LANGUAGE_PAI)
get_selected_language()
/// Ejects the pAI remotely.
/mob/living/basic/bot/proc/eject_pai_remote(mob/user)
if(!check_access(user) || !paicard)
return
speak("Ejecting personality chip.", radio_channel)
ejectpai(user)
/mob/living/basic/bot/Login()
. = ..()
if(!. || isnull(client))
return FALSE
REMOVE_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT)
speed = 2
diag_hud_set_botmode()
clear_path_hud()
/mob/living/basic/bot/Logout()
. = ..()
bot_reset()
speed = initial(speed)
ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT)
/mob/living/basic/bot/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
. = ..()
if(!.)
return
update_appearance()
/mob/living/basic/bot/rust_heretic_act()
adjustBruteLoss(400)
/mob/living/basic/bot/proc/attempt_access(mob/bot, obj/door_attempt)
SIGNAL_HANDLER
if(door_attempt.check_access(access_card))
return ACCESS_ALLOWED
return ACCESS_DISALLOWED
/mob/living/basic/bot/proc/generate_speak_list()
return null
/mob/living/basic/bot/proc/provide_additional_access()
var/datum/id_trim/additional_trim = SSid_access.trim_singletons_by_path[additional_access]
if(isnull(additional_trim))
return
access_card.add_access(additional_trim.access + additional_trim.wildcard_access)
initial_access = access_card.access.Copy()
/mob/living/basic/bot/proc/summon_bot(atom/caller, turf/turf_destination, user_access = list(), grant_all_access = FALSE)
if(isAI(caller) && !set_ai_caller(caller))
return FALSE
bot_reset(bypass_ai_reset = isAI(caller))
var/turf/destination = turf_destination ? turf_destination : get_turf(caller)
ai_controller?.set_blackboard_key(BB_BOT_SUMMON_TARGET, destination)
var/list/access_to_grant = grant_all_access ? REGION_ACCESS_ALL_STATION : user_access + initial_access
access_card.set_access(access_to_grant)
speak("Responding.", radio_channel)
update_bot_mode(new_mode = BOT_SUMMON)
return TRUE
/mob/living/basic/bot/proc/set_ai_caller(mob/living/caller)
var/atom/calling_ai = calling_ai_ref?.resolve()
if(!isnull(calling_ai) && calling_ai != src)
return FALSE
calling_ai_ref = WEAKREF(caller)
return TRUE
/mob/living/basic/bot/proc/update_bot_mode(new_mode, update_hud = TRUE)
mode = new_mode
update_appearance()
if(update_hud)
diag_hud_set_botmode()
/mob/living/basic/bot/proc/after_attacked(datum/source, atom/attacker, attack_flags)
SIGNAL_HANDLER
if(attack_flags & ATTACKER_DAMAGING_ATTACK)
do_sparks(number = 5, cardinal_only = TRUE, source = src)
/mob/living/basic/bot/spawn_gibs(drop_bitflags = NONE)
new /obj/effect/gibspawner/robot(drop_location(), src)
/mob/living/basic/bot/proc/on_bot_movement(atom/movable/source, atom/oldloc, dir, forced)
return