mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 05:30:05 +01:00
PAI Holochassis are now leashed to an area around their card (#76763)
## About The Pull Request This change restricts PAI holograms to an area around their assigned PAI card. If you leave this area, you are teleported back to the card's location (but not automatically put back into the card). https://www.youtube.com/watch?v=L2ThEVa4nx8 This setting can be configured from the PAI menu, it's set pretty short in the video because otherwise it wouldn't teleport when I threw the card and I like doing that.  To accomodate this I set up a component to deal with a reasonably common problem I have had, "what if I want to track the movement of something in a box in a bag in someone's inventory". Please tell me if the solution I came up with is stupid and we already have a better one that I forgot about. Also now you can put pAIs inside bots again, by popular request. ## Why It's Good For The Game Personal AIs are intended to be personal assistants to their owner. rather than fully independent entities which can pick up their own card and leave as soon as they spawn. As "aimless wanderer" players can now possess station bots, pAIs can be limited to an area around the bearer of their card. Because the holoform now doesn't contain the card, this also means that a PAI cannot run off and then be impossible to retrieve. You are always in control of where it can go. Also it's funny to throw the card and watch the chicken get teleported to it. ## Changelog 🆑 add: Personal AI holograms are now limited to an area around their PAI card. The size of this are can be configured via the PAI card. add: pAI cards can now be placed inside bots in order to grant them control of the bot. /🆑
This commit is contained in:
@@ -109,3 +109,6 @@
|
||||
#define COMSIG_MOVABLE_MESSAGE_GET_NAME_PART "movable_message_get_name_part"
|
||||
///The index of the name part
|
||||
#define NAME_PART_INDEX 1
|
||||
|
||||
///from /datum/component/track_hierarchical_movement/on_moved() if atom or any of its containers has moved: (atom/old_loc, dir, forced, list/old_locs)
|
||||
#define COMSIG_MOVABLE_OR_CONTAINER_MOVED "movable_or_container_moved"
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
/// The amount of time between spamming for pAI candidates
|
||||
#define PAI_SPAM_TIME (40 SECONDS)
|
||||
|
||||
/// Maximum distance you can set the holoform leash
|
||||
#define HOLOFORM_MAX_RANGE 9
|
||||
/// Minimum distance you can set the holoform leash
|
||||
#define HOLOFORM_MIN_RANGE 3
|
||||
|
||||
/// UI action to toggle huds
|
||||
#define PAI_TOGGLE_MEDICAL_HUD 0
|
||||
#define PAI_TOGGLE_SECURITY_HUD 1
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
///The Bot is currently allowed to be remote controlled by Silicon.
|
||||
#define BOT_MODE_REMOTE_ENABLED (1<<2)
|
||||
///The Bot is allowed to have a ghost placed in control of it.
|
||||
#define BOT_MODE_GHOST_CONTROLLABLE (1<<3)
|
||||
#define BOT_MODE_CAN_BE_SAPIENT (1<<3)
|
||||
|
||||
//Bot cover defines indicating the Bot's status
|
||||
///The Bot's cover is open and can be modified/emagged by anyone.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Component which outputs a signal if the attached atom or any of its containers moves
|
||||
*/
|
||||
/datum/component/track_hierarchical_movement
|
||||
/// List of things we're currently listening out for movement from
|
||||
var/list/containers
|
||||
|
||||
/datum/component/track_hierarchical_movement/Initialize()
|
||||
. = ..()
|
||||
if (!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
/datum/component/track_hierarchical_movement/Destroy(force, silent)
|
||||
LAZYCLEARLIST(containers)
|
||||
return ..()
|
||||
|
||||
/datum/component/track_hierarchical_movement/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
|
||||
rebuild_hierarchy()
|
||||
|
||||
/datum/component/track_hierarchical_movement/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
|
||||
for (var/container in containers)
|
||||
UnregisterSignal(container, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/// Something in our hierarchy moved, send signal then rebuild hierarchy
|
||||
/datum/component/track_hierarchical_movement/proc/on_moved(datum/source, old_loc, dir, forced)
|
||||
SIGNAL_HANDLER
|
||||
SEND_SIGNAL(parent, COMSIG_MOVABLE_OR_CONTAINER_MOVED, old_loc, dir, forced)
|
||||
rebuild_hierarchy()
|
||||
|
||||
/// Listen for the movement signal on every parent which isn't the floor or the void
|
||||
/datum/component/track_hierarchical_movement/proc/rebuild_hierarchy()
|
||||
for (var/container in containers)
|
||||
UnregisterSignal(container, COMSIG_MOVABLE_MOVED)
|
||||
LAZYCLEARLIST(containers)
|
||||
var/atom/checked = parent
|
||||
while (!isnull(checked.loc) && !isturf(checked.loc))
|
||||
RegisterSignal(checked.loc, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
|
||||
checked = checked.loc
|
||||
LAZYADD(containers, checked)
|
||||
@@ -37,6 +37,8 @@
|
||||
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
|
||||
|
||||
@@ -45,8 +47,8 @@
|
||||
///All initial access this bot started with.
|
||||
var/list/prev_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_GHOST_CONTROLLABLE
|
||||
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
///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
|
||||
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
|
||||
|
||||
///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED
|
||||
var/bot_cover_flags = BOT_COVER_LOCKED
|
||||
@@ -113,7 +115,7 @@
|
||||
|
||||
/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.
|
||||
return "<b>Autonomous</b>"
|
||||
return paicard ? "<b>pAI Controlled</b>" : "<b>Autonomous</b>"
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "<span class='bad'>Inactive</span>"
|
||||
return "<span class='average'>[mode]</span>"
|
||||
@@ -123,7 +125,7 @@
|
||||
*/
|
||||
/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 "Autonomous"
|
||||
return paicard ? "pAI Controlled" : "Autonomous"
|
||||
if(!(bot_mode_flags & BOT_MODE_ON))
|
||||
return "Inactive"
|
||||
return "[mode]"
|
||||
@@ -190,10 +192,12 @@
|
||||
if(HAS_TRAIT(SSstation, STATION_TRAIT_BOTS_GLITCHED))
|
||||
randomize_language_if_on_station()
|
||||
|
||||
if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE))
|
||||
enable_possession(mapload)
|
||||
if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT))
|
||||
enable_possession(mapload = mapload)
|
||||
|
||||
/mob/living/simple_animal/bot/Destroy()
|
||||
if(paicard)
|
||||
ejectpai()
|
||||
GLOB.bots_list -= src
|
||||
QDEL_NULL(personality_download)
|
||||
QDEL_NULL(internal_radio)
|
||||
@@ -202,7 +206,10 @@
|
||||
return ..()
|
||||
|
||||
/// Allows this bot to be controlled by a ghost, who will become its mind
|
||||
/mob/living/simple_animal/bot/proc/enable_possession(mapload = FALSE)
|
||||
/mob/living/simple_animal/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)
|
||||
personality_download = AddComponent(\
|
||||
@@ -221,14 +228,15 @@
|
||||
/mob/living/simple_animal/bot/proc/disable_possession(mob/user)
|
||||
can_be_possessed = FALSE
|
||||
QDEL_NULL(personality_download)
|
||||
if (mind)
|
||||
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)
|
||||
say("Personally matrix reset!", forced = "bot")
|
||||
key = null
|
||||
if (!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)
|
||||
say("Personality matrix reset!", forced = "bot")
|
||||
key = null
|
||||
|
||||
/// Returns true if this mob can be controlled
|
||||
/mob/living/simple_animal/bot/proc/check_possession(mob/potential_possessor)
|
||||
@@ -334,6 +342,10 @@
|
||||
var/is_sillycone = issilicon(user)
|
||||
if(!(bot_cover_flags & BOT_COVER_EMAGGED) && (is_sillycone || user.Adjacent(src)))
|
||||
. += span_info("Alt-click [is_sillycone ? "" : "or use your ID on "]it to [bot_cover_flags & BOT_COVER_LOCKED ? "un" : ""]lock its control panel.")
|
||||
if(paicard)
|
||||
. += span_notice("It has a pAI device installed.")
|
||||
if(!(bot_cover_flags & BOT_COVER_OPEN))
|
||||
. += span_info("You can use a <b>hemostat</b> to remove it.")
|
||||
|
||||
/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
if(amount > 0 && prob(10))
|
||||
@@ -453,6 +465,19 @@
|
||||
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)
|
||||
if(bot_cover_flags & BOT_COVER_OPEN)
|
||||
balloon_alert(user, "open the access panel!")
|
||||
else
|
||||
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)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/attacked_by(obj/item/I, mob/living/user)
|
||||
@@ -474,6 +499,10 @@
|
||||
var/was_on = bot_mode_flags & BOT_MODE_ON ? TRUE : FALSE
|
||||
stat |= EMPED
|
||||
new /obj/effect/temp_visual/emp(loc)
|
||||
if(paicard)
|
||||
paicard.emp_act(severity)
|
||||
src.visible_message(span_notice("[paicard] is flies out of [initial(src.name)]!"), span_warning("You are forcefully ejected from [initial(src.name)]!"))
|
||||
ejectpai()
|
||||
|
||||
if(prob(70/severity))
|
||||
set_active_language(get_random_spoken_language())
|
||||
@@ -842,6 +871,8 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
access_card.set_access(user_access + prev_access) //Adds the user's access, if any.
|
||||
mode = BOT_SUMMON
|
||||
speak("Responding.", radio_channel)
|
||||
if("ejectpai")
|
||||
ejectpairemote(user)
|
||||
return
|
||||
|
||||
|
||||
@@ -925,7 +956,8 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
data["locked"] = bot_cover_flags & BOT_COVER_LOCKED
|
||||
data["settings"] = list()
|
||||
if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user))
|
||||
data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE
|
||||
data["settings"]["pai_inserted"] = !!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_cover_flags & BOT_COVER_OPEN)
|
||||
@@ -967,18 +999,24 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]")
|
||||
usr.log_message("disabled safety lock of [src]", LOG_GAME)
|
||||
bot_reset()
|
||||
else if(!(bot_cover_flags & BOT_COVER_HACKED))
|
||||
return
|
||||
if(!(bot_cover_flags & BOT_COVER_HACKED))
|
||||
to_chat(usr, span_boldannounce("You fail to repair [src]'s [hackables]."))
|
||||
else
|
||||
bot_cover_flags &= ~(BOT_COVER_EMAGGED|BOT_COVER_HACKED)
|
||||
to_chat(usr, span_notice("You reset the [src]'s [hackables]."))
|
||||
usr.log_message("re-enabled safety lock of [src]", LOG_GAME)
|
||||
bot_reset()
|
||||
return
|
||||
bot_cover_flags &= ~(BOT_COVER_EMAGGED|BOT_COVER_HACKED)
|
||||
to_chat(usr, span_notice("You reset the [src]'s [hackables]."))
|
||||
usr.log_message("re-enabled safety lock of [src]", LOG_GAME)
|
||||
bot_reset()
|
||||
if("eject_pai")
|
||||
if(!paicard)
|
||||
return
|
||||
to_chat(usr, span_notice("You eject [paicard] from [initial(src.name)]."))
|
||||
ejectpai(usr)
|
||||
if("toggle_personality")
|
||||
if (can_be_possessed)
|
||||
disable_possession(usr)
|
||||
else
|
||||
enable_possession()
|
||||
enable_possession(usr)
|
||||
if("rename")
|
||||
rename(usr)
|
||||
|
||||
@@ -986,7 +1024,8 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
icon_state = "[isnull(base_icon_state) ? initial(icon_state) : base_icon_state][get_bot_flag(bot_mode_flags, BOT_MODE_ON)]"
|
||||
return ..()
|
||||
|
||||
/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.
|
||||
/// Access check proc for bot topics! Remember to place in a bot's individual Topic if desired.
|
||||
/mob/living/simple_animal/bot/proc/topic_denied(mob/user)
|
||||
if(!user.can_perform_action(src, ALLOW_SILICON_REACH))
|
||||
return TRUE
|
||||
// 0 for access, 1 for denied.
|
||||
@@ -997,6 +1036,72 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/// Places a pAI in control of this mob
|
||||
/mob/living/simple_animal/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_cover_flags & BOT_COVER_LOCKED || !(bot_cover_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(!card.pai || !card.pai.mind)
|
||||
balloon_alert(user, "pAI is inactive!")
|
||||
return
|
||||
if(!user.transferItemToLoc(card, src))
|
||||
return
|
||||
paicard = card
|
||||
disable_possession()
|
||||
if(paicard.pai.holoform)
|
||||
paicard.pai.fold_in()
|
||||
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/simple_animal/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/simple_animal/bot/proc/ejectpai(mob/user = null, announce = TRUE)
|
||||
if(!paicard)
|
||||
return
|
||||
if(mind && paicard.pai)
|
||||
mind.transfer_to(paicard.pai)
|
||||
else if(paicard.pai)
|
||||
paicard.pai.key = key
|
||||
else
|
||||
ghostize(FALSE) // The pAI card that just got ejected was dead.
|
||||
key = null
|
||||
paicard.forceMove(loc)
|
||||
if(user)
|
||||
log_combat(user, paicard.pai, "ejected from [initial(src.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 [initial(src.name)]."))
|
||||
paicard = null
|
||||
name = initial(src.name)
|
||||
faction = initial(faction)
|
||||
|
||||
/// Ejects the pAI remotely.
|
||||
/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user)
|
||||
if(!check_access(user) || !paicard)
|
||||
return
|
||||
speak("Ejecting personality chip.", radio_channel)
|
||||
ejectpai(user)
|
||||
|
||||
/mob/living/simple_animal/bot/Login()
|
||||
. = ..()
|
||||
if(!. || !client)
|
||||
@@ -1014,7 +1119,6 @@ Pass a positive integer as an argument to override a bot's default speed.
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
update_appearance()
|
||||
|
||||
/mob/living/simple_animal/bot/sentience_act()
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/autopatrol
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/medbay
|
||||
name = "Scrubs, MD"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key
|
||||
radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway.
|
||||
bot_type = HONK_BOT
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE | BOT_MODE_AUTOPATROL
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL
|
||||
hackables = "sound control systems"
|
||||
path_image_color = "#FF69B4"
|
||||
data_hud_type = DATA_HUD_SECURITY_BASIC //show jobs
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR)
|
||||
radio_key = /obj/item/encryptionkey/headset_service
|
||||
radio_channel = RADIO_CHANNEL_SERVICE //Service
|
||||
bot_mode_flags = ~BOT_MODE_GHOST_CONTROLLABLE
|
||||
bot_mode_flags = ~BOT_MODE_CAN_BE_SAPIENT
|
||||
bot_type = HYGIENE_BOT
|
||||
hackables = "cleaning service protocols"
|
||||
path_image_color = "#993299"
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
COOLDOWN_DECLARE(last_tipping_action_voice)
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/autopatrol
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_GHOST_CONTROLLABLE
|
||||
bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/stationary
|
||||
medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE
|
||||
|
||||
@@ -263,8 +263,9 @@
|
||||
data["autoPickup"] = auto_pickup
|
||||
data["reportDelivery"] = report_delivery
|
||||
data["id"] = id
|
||||
data["allow_possession"] = bot_mode_flags & BOT_MODE_GHOST_CONTROLLABLE
|
||||
data["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT
|
||||
data["possession_enabled"] = can_be_possessed
|
||||
data["pai_inserted"] = !!paicard
|
||||
return data
|
||||
|
||||
/mob/living/simple_animal/bot/mulebot/ui_act(action, params)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security
|
||||
radio_channel = RADIO_CHANNEL_SECURITY //Security channel
|
||||
bot_type = SEC_BOT
|
||||
bot_mode_flags = ~BOT_MODE_GHOST_CONTROLLABLE
|
||||
bot_mode_flags = ~BOT_MODE_CAN_BE_SAPIENT
|
||||
data_hud_type = DATA_HUD_SECURITY_ADVANCED
|
||||
hackables = "target identification systems"
|
||||
path_image_color = "#FF0000"
|
||||
@@ -65,13 +65,13 @@
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/ofitser
|
||||
name = "Prison Ofitser"
|
||||
desc = "Powered by the tears and sweat of laborers."
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_CAN_BE_SAPIENT|BOT_MODE_AUTOPATROL)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/armsky
|
||||
name = "Sergeant-At-Armsky"
|
||||
desc = "It's Sergeant-At-Armsky! He's a disgruntled assistant to the warden that would probably shoot you if he had hands."
|
||||
health = 45
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_CAN_BE_SAPIENT|BOT_MODE_AUTOPATROL)
|
||||
security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_IDS | SECBOT_CHECK_RECORDS
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/jr
|
||||
@@ -87,7 +87,7 @@
|
||||
name = "Officer Pingsky"
|
||||
desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment."
|
||||
radio_channel = RADIO_CHANNEL_AI_PRIVATE
|
||||
bot_mode_flags = ~(BOT_MODE_GHOST_CONTROLLABLE|BOT_MODE_AUTOPATROL)
|
||||
bot_mode_flags = ~(BOT_MODE_CAN_BE_SAPIENT|BOT_MODE_AUTOPATROL)
|
||||
security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_IDS | SECBOT_CHECK_RECORDS
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/genesky
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
|
||||
/obj/item/pai_card/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/track_hierarchical_movement)
|
||||
update_appearance()
|
||||
SSpai.pai_card_list += src
|
||||
|
||||
@@ -95,6 +96,11 @@
|
||||
return UI_INTERACTIVE
|
||||
return ..()
|
||||
|
||||
/obj/item/pai_card/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
.["range_max"] = HOLOFORM_MAX_RANGE
|
||||
.["range_min"] = HOLOFORM_MIN_RANGE
|
||||
|
||||
/obj/item/pai_card/ui_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = list()
|
||||
@@ -110,6 +116,7 @@
|
||||
name = pai.name,
|
||||
transmit = pai.can_transmit,
|
||||
receive = pai.can_receive,
|
||||
range = pai.leashed_distance,
|
||||
)
|
||||
return data
|
||||
|
||||
@@ -146,6 +153,12 @@
|
||||
if("toggle_radio")
|
||||
pai.toggle_radio(params["option"])
|
||||
return TRUE
|
||||
if("increase_range")
|
||||
pai.increment_range(1)
|
||||
return TRUE
|
||||
if("decrease_range")
|
||||
pai.increment_range(-1)
|
||||
return TRUE
|
||||
if("wipe_pai")
|
||||
pai.wipe_pai(usr)
|
||||
ui.close()
|
||||
|
||||
+36
-2
@@ -37,6 +37,8 @@
|
||||
var/can_transmit = TRUE
|
||||
/// The card we inhabit
|
||||
var/obj/item/pai_card/card
|
||||
/// The maximum distance we can travel away from our pai card
|
||||
var/leashed_distance = 5
|
||||
/// The current chasis that will appear when in holoform
|
||||
var/chassis = "repairbot"
|
||||
/// Toggles whether the pAI can hold encryption keys or not
|
||||
@@ -225,14 +227,28 @@
|
||||
var/newcardloc = pai_card
|
||||
pai_card = new(newcardloc)
|
||||
pai_card.set_personality(src)
|
||||
forceMove(pai_card)
|
||||
card = pai_card
|
||||
forceMove(pai_card)
|
||||
addtimer(VARSET_WEAK_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_INIT_TIME)
|
||||
if(!holoform)
|
||||
add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), PAI_FOLDED)
|
||||
desc = "A pAI hard-light holographics emitter. This one appears in the form of a [chassis]."
|
||||
update_appearance(UPDATE_DESC)
|
||||
|
||||
RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed))
|
||||
RegisterSignal(card, COMSIG_MOVABLE_OR_CONTAINER_MOVED, PROC_REF(check_distance))
|
||||
|
||||
/mob/living/silicon/pai/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
||||
. = ..()
|
||||
check_distance()
|
||||
|
||||
/// Checks if we're in range of our pai card
|
||||
/mob/living/silicon/pai/proc/check_distance()
|
||||
SIGNAL_HANDLER
|
||||
if (get_dist(get_turf(card), get_turf(src)) <= leashed_distance)
|
||||
return
|
||||
to_chat(src, span_warning("You moved out of range of your holotransmitter!"))
|
||||
new /obj/effect/temp_visual/guardian/phase/out(loc)
|
||||
forceMove(get_turf(card))
|
||||
|
||||
/mob/living/silicon/pai/make_laws()
|
||||
laws = new /datum/ai_laws/pai()
|
||||
@@ -259,6 +275,15 @@
|
||||
update_stat()
|
||||
SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE)
|
||||
|
||||
/mob/living/silicon/pai/update_desc(updates)
|
||||
desc = "A hard-light holographic avatar representing a pAI. This one appears in the form of a [chassis]."
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/update_icon_state()
|
||||
icon_state = resting ? "[chassis]_rest" : "[chassis]"
|
||||
held_state = "[chassis]"
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Resolves the weakref of the pai's master.
|
||||
* If the master has been deleted, calls reset_software().
|
||||
@@ -434,3 +459,12 @@
|
||||
for(var/mob/living/cultist as anything in invokers)
|
||||
to_chat(cultist, span_cultitalic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices..."))
|
||||
return STOP_SACRIFICE
|
||||
|
||||
/// Updates the distance we can be from our pai card
|
||||
/mob/living/silicon/pai/proc/increment_range(increment_amount)
|
||||
var/new_distance = leashed_distance + increment_amount
|
||||
if (new_distance < HOLOFORM_MIN_RANGE || new_distance > HOLOFORM_MAX_RANGE)
|
||||
return
|
||||
leashed_distance = new_distance
|
||||
if (increment_amount < 0)
|
||||
check_distance()
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
|
||||
/mob/living/silicon/pai/update_resting()
|
||||
. = ..()
|
||||
if(resting)
|
||||
icon_state = "[chassis]_rest"
|
||||
else
|
||||
icon_state = "[chassis]"
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
if(loc != card)
|
||||
visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]"))
|
||||
|
||||
@@ -77,11 +74,11 @@
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN)
|
||||
else
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_OVERLOAD_COOLDOWN)
|
||||
icon_state = "[chassis]"
|
||||
set_resting(FALSE, silent = TRUE, instant = TRUE)
|
||||
if(!holoform)
|
||||
. = fold_out(force)
|
||||
return FALSE
|
||||
visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!"))
|
||||
visible_message(span_notice("[src] dematerialises!"))
|
||||
stop_pulling()
|
||||
if(ispickedupmob(loc))
|
||||
var/obj/item/clothing/head/mob_holder/mob_head = loc
|
||||
@@ -89,8 +86,8 @@
|
||||
if(client)
|
||||
client.perspective = EYE_PERSPECTIVE
|
||||
client.set_eye(card)
|
||||
var/turf/target = drop_location()
|
||||
card.forceMove(target)
|
||||
if (isturf(loc))
|
||||
new /obj/effect/temp_visual/guardian/phase/out(loc)
|
||||
forceMove(card)
|
||||
add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), PAI_FOLDED)
|
||||
set_density(FALSE)
|
||||
@@ -129,20 +126,14 @@
|
||||
var/obj/item/modular_computer/pc = card.loc
|
||||
pc.inserted_pai = null
|
||||
pc.visible_message(span_notice("[src] ejects itself from [pc]!"))
|
||||
if(isliving(card.loc))
|
||||
var/mob/living/living_holder = card.loc
|
||||
if(!living_holder.temporarilyRemoveItemFromInventory(card))
|
||||
balloon_alert(src, "unable to expand")
|
||||
return FALSE
|
||||
card.forceMove(get_turf(pc))
|
||||
forceMove(get_turf(card))
|
||||
card.forceMove(src)
|
||||
if(client)
|
||||
client.perspective = EYE_PERSPECTIVE
|
||||
client.set_eye(src)
|
||||
set_light_on(FALSE)
|
||||
icon_state = "[chassis]"
|
||||
held_state = "[chassis]"
|
||||
visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!"))
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
visible_message(span_boldnotice("[src] appears in a flash of light!"))
|
||||
holoform = TRUE
|
||||
return TRUE
|
||||
|
||||
@@ -157,9 +148,7 @@
|
||||
if(!choice)
|
||||
return FALSE
|
||||
chassis = choice
|
||||
icon_state = "[chassis]"
|
||||
held_state = "[chassis]"
|
||||
desc = "A pAI mobile hard-light holographics emitter. This one appears in the form of a [chassis]."
|
||||
update_appearance(UPDATE_DESC | UPDATE_ICON_STATE)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
|
||||
@@ -1047,6 +1047,7 @@
|
||||
#include "code\datums\components\tippable.dm"
|
||||
#include "code\datums\components\toggle_attached_clothing.dm"
|
||||
#include "code\datums\components\toggle_suit.dm"
|
||||
#include "code\datums\components\track_hierarchical_movement.dm"
|
||||
#include "code\datums\components\transforming.dm"
|
||||
#include "code\datums\components\trapdoor.dm"
|
||||
#include "code\datums\components\twohanded.dm"
|
||||
|
||||
@@ -20,6 +20,7 @@ export const Mule = (props, context) => {
|
||||
id,
|
||||
allow_possession,
|
||||
possession_enabled,
|
||||
pai_inserted,
|
||||
destinations = [],
|
||||
} = data;
|
||||
const locked = data.locked && !data.siliconUser;
|
||||
@@ -74,13 +75,22 @@ export const Mule = (props, context) => {
|
||||
<Section
|
||||
title="Controls"
|
||||
buttons={
|
||||
!!load && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Unload"
|
||||
onClick={() => act('unload')}
|
||||
/>
|
||||
)
|
||||
<>
|
||||
{!!load && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Unload"
|
||||
onClick={() => act('unload')}
|
||||
/>
|
||||
)}
|
||||
{!!pai_inserted && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject PAI"
|
||||
onClick={() => act('eject_pai')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="ID">
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Window } from '../layouts';
|
||||
type Data = {
|
||||
candidates: ReadonlyArray<Candidate>;
|
||||
pai: Pai;
|
||||
range_max: number;
|
||||
range_min: number;
|
||||
};
|
||||
|
||||
type Candidate = Readonly<{
|
||||
@@ -25,6 +27,7 @@ type Pai = {
|
||||
name: string;
|
||||
transmit: BooleanLike;
|
||||
receive: BooleanLike;
|
||||
range: number;
|
||||
};
|
||||
|
||||
export const PaiCard = (props, context) => {
|
||||
@@ -140,7 +143,19 @@ const CandidateDisplay = (
|
||||
const PaiOptions = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
pai: { can_holo, dna, emagged, laws, master, name, transmit, receive },
|
||||
range_max,
|
||||
range_min,
|
||||
pai: {
|
||||
can_holo,
|
||||
dna,
|
||||
emagged,
|
||||
laws,
|
||||
master,
|
||||
name,
|
||||
transmit,
|
||||
receive,
|
||||
range,
|
||||
},
|
||||
} = data;
|
||||
const suppliedLaws = laws[0] ? decodeHtmlEntities(laws[0]) : 'None';
|
||||
|
||||
@@ -170,6 +185,25 @@ const PaiOptions = (props, context) => {
|
||||
Toggle
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Holoform Range">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="fa-circle-minus"
|
||||
onClick={() => act('decrease_range')}
|
||||
disabled={range === range_min}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.5}>{range}</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="fa-circle-plus"
|
||||
onClick={() => act('increase_range')}
|
||||
disabled={range === range_max}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Transmit">
|
||||
<Button
|
||||
icon={transmit ? 'toggle-on' : 'toggle-off'}
|
||||
|
||||
@@ -20,6 +20,7 @@ type Settings = {
|
||||
allow_possession: number;
|
||||
possession_enabled: number;
|
||||
has_personality: number;
|
||||
pai_inserted: boolean;
|
||||
};
|
||||
|
||||
type Controls = {
|
||||
@@ -57,10 +58,12 @@ export const SimpleBot = (props, context) => {
|
||||
const TabDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
const { can_hack, has_access, locked } = data;
|
||||
const { allow_possession } = data.settings;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!can_hack && <HackButton />}
|
||||
{!!allow_possession && <PaiButton />}
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="fa-poll-h"
|
||||
@@ -103,6 +106,33 @@ const HackButton = (props, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
/** Creates a button indicating PAI status and offers the eject action */
|
||||
const PaiButton = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
const { pai_inserted } = data.settings;
|
||||
|
||||
if (!pai_inserted) {
|
||||
return (
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="robot"
|
||||
tooltip={multiline`Insert an active PAI card to control this device.`}>
|
||||
No PAI Inserted
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
disabled={!pai_inserted}
|
||||
icon="eject"
|
||||
onClick={() => act('eject_pai')}
|
||||
tooltip={multiline`Ejects the current PAI.`}>
|
||||
Eject PAI
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** Displays the bot's standard settings: Power, patrol, etc. */
|
||||
const SettingsDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<SimpleBotContext>(context);
|
||||
|
||||
Reference in New Issue
Block a user