Files
Bubberstation/code/modules/mob/living/basic/bots/_bots.dm
Ben10Omintrix 55c41fb9ad basic honkbots (#81920)
## About The Pull Request
this refactors honkbots into basic mobs. its mostly a faithful 1:1
refactor but i couldnt keep my hands to myselves so i gave them some new
behaviors.

honkbots now love playing with clowns, they will go seek out for clowns
and celebrate around them. also, if the honkbot finds a banana peel or a
slippery item near it, it will actively drag people onto them

honkbots will now go out of theirway to mess with secbots and annoy them

## Why It's Good For The Game
refactors hinkbots into basic bots and also undoes some of the silliness
i did in the previous basic bot prs. i also added lazylist support to
remove_thing_from_list.

## Changelog
🆑
refactor: honkbots are now basic mobs, please report any bugs
add: honkbots will try to slip people on banana peels
/🆑
2024-06-05 10:17:34 -04:00

821 lines
28 KiB
Plaintext

GLOBAL_LIST_INIT(command_strings, list(
"patroloff" = "STOP PATROL",
"patrolon" = "START PATROL",
"stop" = "STOP",
"go" = "GO",
"home" = "RETURN HOME",
))
#define SENTIENT_BOT_RESET_TIMER 45 SECONDS
/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
density = FALSE
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 = null
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
ai_controller = /datum/ai_controller/basic_controller/bot
pass_flags = PASSFLAPS | PASSMOB
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_SILICON)
light_system = OVERLAY_LIGHT
light_range = 3
light_power = 0.6
speed = 3
req_one_access = list(ACCESS_ROBOTICS)
interaction_flags_click = ALLOW_SILICON_REACH
///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_COVER_MAINTS_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED
var/bot_access_flags = BOT_COVER_LOCKED
///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 = COLOR_WHITE
///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/ai_retaliate)
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/allowed(mob/living/user)
if(!(bot_access_flags & BOT_COVER_LOCKED)) // Unlocked.
return TRUE
return ..()
/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_COVER_LOCKED) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again.
bot_access_flags &= ~BOT_COVER_LOCKED
balloon_alert(user, "cover unlocked")
return TRUE
if((bot_access_flags & BOT_COVER_LOCKED) || !(bot_access_flags & BOT_COVER_MAINTS_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_COVER_LOCKED
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_COVER_MAINTS_OPEN ? "open" : "closed"].")
. += span_info("You can use a <b>screwdriver</b> to [bot_access_flags & BOT_COVER_MAINTS_OPEN ? "close" : "open"] it.")
if(bot_access_flags & BOT_COVER_MAINTS_OPEN)
. += span_notice("Its control panel is [bot_access_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].")
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_COVER_LOCKED ? "un" : ""]lock its control panel.")
if(isnull(paicard))
return
. += span_notice("It has a pAI device installed.")
if(!(bot_access_flags & BOT_COVER_MAINTS_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/click_alt(mob/user)
unlock_with_id(user)
return CLICK_ACTION_SUCCESS
/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_COVER_MAINTS_OPEN)
balloon_alert(user, "access panel must be closed!")
return
if(!allowed(user))
balloon_alert(user, "no access")
return
bot_access_flags ^= BOT_COVER_LOCKED
to_chat(user, span_notice("Controls are now [bot_access_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"]."))
return TRUE
/mob/living/basic/bot/screwdriver_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_SUCCESS
if(bot_access_flags & BOT_COVER_LOCKED)
to_chat(user, span_warning("The maintenance panel is locked!"))
return
tool.play_tool_sound(src)
bot_access_flags ^= BOT_COVER_MAINTS_OPEN
to_chat(user, span_notice("The maintenance panel is now [bot_access_flags & BOT_COVER_MAINTS_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_COVER_MAINTS_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_COVER_MAINTS_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)
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"] = HAS_SILICON_ACCESS(user)
data["custom_controls"] = list()
data["emagged"] = bot_access_flags & BOT_COVER_EMAGGED
data["has_access"] = allowed(user)
data["locked"] = (bot_access_flags & BOT_COVER_LOCKED)
data["settings"] = list()
if(!(bot_access_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(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_COVER_MAINTS_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(!allowed(the_user))
balloon_alert(the_user, "access denied!")
return
if(action == "lock")
bot_access_flags ^= BOT_COVER_LOCKED
switch(action)
if("power")
if(bot_mode_flags & BOT_MODE_ON)
turn_off()
else
turn_on()
if("maintenance")
bot_access_flags ^= BOT_COVER_MAINTS_OPEN
if("patrol")
bot_mode_flags ^= BOT_MODE_AUTOPATROL
bot_reset()
if("airplane")
bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED
if("hack")
if(!HAS_SILICON_ACCESS(the_user))
return
if(!(bot_access_flags & BOT_COVER_EMAGGED))
bot_access_flags |= (BOT_COVER_LOCKED|BOT_COVER_EMAGGED|BOT_COVER_HACKED)
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(!HAS_SILICON_ACCESS(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_MAINTS_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(!allowed(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
return (door_attempt.check_access(access_card) ? ACCESS_ALLOWED : 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)
if(client) //if we're sentient, we reset ourselves after a short period
addtimer(CALLBACK(src, PROC_REF(bot_reset)), SENTIENT_BOT_RESET_TIMER)
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
#undef SENTIENT_BOT_RESET_TIMER