mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-25 09:01:40 +00:00
* 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>
832 lines
29 KiB
Plaintext
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
|