Files
Bubberstation/code/modules/modular_computers/computers/item/pda.dm
T
SkyratBot d865f6fa9a [MIRROR] pAIs control a PDA they're inserted into [MDB IGNORE] (#23519)
* pAIs control a PDA they're inserted into (#78103)

## About The Pull Request

This PR lets pAIs inserted into a PDA to control the PDA of their owner.
They can see their own pAI configurations but cannot edit any of it.
They also can't eject themselves from it.
This means they can receive and send PDA messages as their owner, acting
like a personal secretary

This also adds support for multiple people using a PDA and its UI
updating for all of them and PDA messages being received and responded
to from multiple people under the same PDA
It also removes pAI camera in favor of using siliconcamera, which is the
same thing; this just cuts down on some copy paste.

I also reverted PDA's ringer being off preventing messages from being
sent to your chat, silent mode was meant to prevent only the ringing
sound.

## Why It's Good For The Game

pAIs can now do a little bit more to help their owners on a personal
level, and adds support for more stuff like this in the future (an idea
I had was being able to hack into PDAs in the same way the CE can hack
into APCs remotely)

This is a re-PR of https://github.com/tgstation/tgstation/pull/76445 but
it's a little better this time and does not remove the PDA Messenger app
from pAIs.

## Changelog

🆑
fix: PDAs being on silent no longer prevents PDAs from being sent to
your chat, again.
add: pAIs inserted into a PDA can now control the PDA, and will receive
PDA messages sent to it (and can respond under the PDA's name).
/🆑

* pAIs control a PDA they're inserted into

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
2023-09-04 20:04:07 -04:00

412 lines
14 KiB
Plaintext

/obj/item/modular_computer/pda
name = "pda"
icon = 'icons/obj/modular_pda.dmi'
icon_state = "pda"
worn_icon_state = "nothing"
base_icon_state = "tablet"
greyscale_config = /datum/greyscale_config/tablet
greyscale_colors = "#999875#a92323"
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
inhand_icon_state = "electronic"
steel_sheet_cost = 2
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass=SMALL_MATERIAL_AMOUNT, /datum/material/plastic=SMALL_MATERIAL_AMOUNT)
interaction_flags_atom = INTERACT_ATOM_ALLOW_USER_LOCATION | INTERACT_ATOM_IGNORE_MOBILITY
icon_state_menu = "menu"
max_capacity = 64
allow_chunky = TRUE
hardware_flag = PROGRAM_TABLET
max_idle_programs = 2
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
has_light = TRUE //LED flashlight!
comp_light_luminosity = 2.3 //this is what old PDAs were set to
looping_sound = FALSE
///The item currently inserted into the PDA, starts with a pen.
var/obj/item/inserted_item = /obj/item/pen
///Whether the PDA should have 'pda_programs' apps installed on Initialize.
var/has_pda_programs = TRUE
///Static list of default PDA apps to install on Initialize.
var/static/list/datum/computer_file/pda_programs = list(
/datum/computer_file/program/messenger,
/datum/computer_file/program/nt_pay,
/datum/computer_file/program/notepad,
// SKYRAT EDIT ADDITION START
/datum/computer_file/program/crew_manifest, // Adds crew manifest to all base tablets
// SKRAT EDIT ADDITION END
)
///List of items that can be stored in a PDA
var/static/list/contained_item = list(
/obj/item/pen,
/obj/item/toy/crayon,
/obj/item/lipstick,
/obj/item/flashlight/pen,
/obj/item/reagent_containers/hypospray/medipen,
/obj/item/clothing/mask/cigarette,
)
/obj/item/modular_computer/pda/Initialize(mapload)
. = ..()
if(inserted_item)
inserted_item = new inserted_item(src)
/obj/item/modular_computer/pda/Destroy()
if(istype(inserted_item))
QDEL_NULL(inserted_item)
return ..()
/obj/item/modular_computer/pda/install_default_programs()
var/list/apps_to_download = list()
if(has_pda_programs)
apps_to_download += default_programs + pda_programs
apps_to_download += starting_programs
for(var/programs as anything in apps_to_download)
var/datum/computer_file/program/program_type = new programs
store_file(program_type)
/obj/item/modular_computer/pda/update_overlays()
. = ..()
if(computer_id_slot)
. += mutable_appearance(initial(icon), "id_overlay")
if(light_on)
. += mutable_appearance(initial(icon), "light_overlay")
if(inserted_pai)
. += mutable_appearance(initial(icon), "pai_inserted")
/obj/item/modular_computer/pda/attack_ai(mob/user)
to_chat(user, span_notice("It doesn't feel right to snoop around like that..."))
return // we don't want ais or cyborgs using a private role tablet
/obj/item/modular_computer/pda/interact(mob/user)
. = ..()
if(HAS_TRAIT(src, TRAIT_PDA_MESSAGE_MENU_RIGGED))
explode(user, from_message_menu = TRUE)
/obj/item/modular_computer/pda/attack_self(mob/user)
// bypass literacy checks to access syndicate uplink
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
if(hidden_uplink?.owner && HAS_TRAIT(user, TRAIT_ILLITERATE))
if(hidden_uplink.owner != user.key)
return ..()
hidden_uplink.locked = FALSE
hidden_uplink.interact(null, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
return ..()
/obj/item/modular_computer/pda/pre_attack(atom/target, mob/living/user, params)
if(!inserted_disk || !ismachinery(target))
return ..()
var/obj/machinery/target_machine = target
if(!target_machine.panel_open && !istype(target, /obj/machinery/computer))
return ..()
if(!istype(inserted_disk, /obj/item/computer_disk/virus/clown))
return ..()
var/obj/item/computer_disk/virus/clown/installed_cartridge = inserted_disk
if(!installed_cartridge.charges)
to_chat(user, span_notice("Out of virus charges."))
return ..()
to_chat(user, span_notice("You upload the virus to [target]!"))
var/sig_list = list(COMSIG_ATOM_ATTACK_HAND)
if(istype(target,/obj/machinery/door/airlock))
sig_list = list(COMSIG_AIRLOCK_OPEN, COMSIG_AIRLOCK_CLOSE)
installed_cartridge.charges--
target.AddComponent(
/datum/component/sound_player, \
uses = rand(15,20), \
signal_list = sig_list, \
)
return TRUE
/obj/item/modular_computer/pda/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(inserted_item)
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Remove [inserted_item]"
. = CONTEXTUAL_SCREENTIP_SET
else if(istype(held_item) && is_type_in_list(held_item, contained_item))
context[SCREENTIP_CONTEXT_LMB] = "Insert [held_item]"
. = CONTEXTUAL_SCREENTIP_SET
return . || NONE
/obj/item/modular_computer/pda/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(!is_type_in_list(attacking_item, contained_item))
return
if(attacking_item.w_class >= WEIGHT_CLASS_SMALL) // Anything equal to or larger than small won't work
user.balloon_alert(user, "too big!")
return
if(inserted_item)
balloon_alert(user, "no room!")
return
if(!user.transferItemToLoc(attacking_item, src))
return
balloon_alert(user, "inserted [attacking_item]")
inserted_item = attacking_item
playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
/obj/item/modular_computer/pda/AltClick(mob/user)
. = ..()
if(.)
return
remove_pen(user)
/obj/item/modular_computer/pda/CtrlClick(mob/user)
. = ..()
if(.)
return
remove_pen(user)
///Finds how hard it is to send a virus to this tablet, checking all programs downloaded.
/obj/item/modular_computer/pda/proc/get_detomatix_difficulty()
var/detomatix_difficulty
for(var/datum/computer_file/program/downloaded_apps in stored_files)
detomatix_difficulty += downloaded_apps.detomatix_resistance
return detomatix_difficulty
/obj/item/modular_computer/pda/proc/remove_pen(mob/user)
if(issilicon(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) //TK doesn't work even with this removed but here for readability
return
if(inserted_item)
balloon_alert(user, "removed [inserted_item]")
user.put_in_hands(inserted_item)
inserted_item = null
update_appearance()
playsound(src, 'sound/machines/pda_button2.ogg', 50, TRUE)
/obj/item/modular_computer/pda/proc/explode(mob/target, mob/bomber, from_message_menu = FALSE)
var/turf/current_turf = get_turf(src)
if(from_message_menu)
log_bomber(null, null, target, "'s tablet exploded as [target.p_they()] tried to open their tablet message menu because of a recent tablet bomb.")
else
log_bomber(bomber, "successfully tablet-bombed", target, "as [target.p_they()] tried to reply to a rigged tablet message [bomber && !is_special_character(bomber) ? "(SENT BY NON-ANTAG)" : ""]")
if (ismob(loc))
var/mob/loc_mob = loc
loc_mob.show_message(
msg = span_userdanger("Your [src] explodes!"),
type = MSG_VISUAL,
alt_msg = span_warning("You hear a loud *pop*!"),
alt_type = MSG_AUDIBLE,
)
else
visible_message(span_danger("[src] explodes!"), span_warning("You hear a loud *pop*!"))
target.client?.give_award(/datum/award/achievement/misc/clickbait, target)
if(current_turf)
current_turf.hotspot_expose(700,125)
if(istype(inserted_disk, /obj/item/computer_disk/virus/detomatix))
explosion(src, devastation_range = -1, heavy_impact_range = 1, light_impact_range = 3, flash_range = 4)
else
explosion(src, devastation_range = -1, heavy_impact_range = -1, light_impact_range = 2, flash_range = 3)
qdel(src)
/**
* A simple helper proc that applies the client's ringtone prefs to the tablet's messenger app,
* if it has one.
*
* Arguments:
* * owner_client - The client whose prefs we'll use to set the ringtone of this PDA.
*/
/obj/item/modular_computer/pda/proc/update_pda_prefs(client/owner_client)
if(!owner_client)
return
var/new_ringtone = owner_client.prefs.read_preference(/datum/preference/text/pda_ringtone)
if(new_ringtone && (new_ringtone != MESSENGER_RINGTONE_DEFAULT))
update_ringtone(new_ringtone)
var/new_theme = owner_client.prefs.read_preference(/datum/preference/choiced/pda_theme)
if(new_theme)
device_theme = GLOB.pda_name_to_theme[new_theme]
/// A simple proc to set the ringtone from a pda.
/obj/item/modular_computer/pda/proc/update_ringtone(new_ringtone)
if(!istext(new_ringtone))
return
var/datum/computer_file/program/messenger/messenger_app = locate() in stored_files
if(messenger_app)
messenger_app.ringtone = new_ringtone
/**
* Nuclear PDA
*
* PDA that doesn't come with the default apps but has Fission360
* Resistant to emags, these are given to nukies for disk pinpointer stuff.
*/
/obj/item/modular_computer/pda/nukeops
name = "nuclear pda"
device_theme = PDA_THEME_SYNDICATE
comp_light_luminosity = 6.3 //matching a flashlight
light_color = COLOR_RED
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#a80001#5C070F#000000"
long_ranged = TRUE
starting_programs = list(
/datum/computer_file/program/radar/fission360,
)
/obj/item/modular_computer/pda/nukeops/Initialize(mapload)
. = ..()
emag_act(forced = TRUE)
var/datum/computer_file/program/messenger/msg = locate() in stored_files
if(msg)
msg.invisible = TRUE
/**
* Silicon PDA
*
* PDAs that are built-in to Silicons and should not exist at any point without being inside of one.
*/
/obj/item/modular_computer/pda/silicon
name = "modular interface"
icon_state = "tablet-silicon"
base_icon_state = "tablet-silicon"
greyscale_config = null
greyscale_colors = null
has_light = FALSE //tablet light button actually enables/disables the borg lamp
comp_light_luminosity = 0
inserted_item = null
has_pda_programs = FALSE
starting_programs = list(
/datum/computer_file/program/messenger,
)
///Ref to the RoboTact app. Important enough to borgs to deserve a ref.
var/datum/computer_file/program/robotact/robotact
///IC log that borgs can view in their personal management app
var/list/borglog = list()
///Ref to the silicon we're installed in. Set by the silicon itself during its creation.
var/mob/living/silicon/silicon_owner
/obj/item/modular_computer/pda/silicon/cyborg
starting_programs = list(
/datum/computer_file/program/filemanager,
/datum/computer_file/program/robotact,
/datum/computer_file/program/crew_manifest, // SKYRAT EDIT ADDITION - Manifests for borgs
/datum/computer_file/program/messenger, // SKYRAT EDIT ADDITION - Messenger for borgs
)
/obj/item/modular_computer/pda/silicon/Initialize(mapload)
. = ..()
vis_flags |= VIS_INHERIT_ID
silicon_owner = loc
if(!istype(silicon_owner))
silicon_owner = null
stack_trace("[type] initialized outside of a silicon, deleting.")
return INITIALIZE_HINT_QDEL
/obj/item/modular_computer/pda/silicon/Destroy()
silicon_owner = null
return ..()
/obj/item/modular_computer/pda/silicon/turn_on(mob/user, open_ui = FALSE)
if(silicon_owner?.stat != DEAD)
return ..()
return FALSE
/obj/item/modular_computer/pda/silicon/get_ntnet_status()
//No borg found
if(!silicon_owner)
return FALSE
// no AIs/pAIs
var/mob/living/silicon/robot/cyborg_check = silicon_owner
if(!istype(cyborg_check))
return ..()
//lockdown restricts borg networking
if(cyborg_check.lockcharge)
return FALSE
//borg cell dying restricts borg networking
if(!cyborg_check.cell || cyborg_check.cell.charge == 0)
return FALSE
return ..()
/**
* Returns a ref to the RoboTact app, creating the app if need be.
*
* The RoboTact app is important for borgs, and so should always be available.
* This proc will look for it in the tablet's robotact var, then check the
* hard drive if the robotact var is unset, and finally attempt to create a new
* copy if the hard drive does not contain the app. If the hard drive rejects
* the new copy (such as due to lack of space), the proc will crash with an error.
* RoboTact is supposed to be undeletable, so these will create runtime messages.
*/
/obj/item/modular_computer/pda/silicon/proc/get_robotact()
if(robotact)
return robotact
robotact = find_file_by_name("robotact")
if(robotact)
return robotact
stack_trace("Cyborg [silicon_owner] ( [silicon_owner.type] ) was somehow missing their self-manage app in their tablet. A new copy has been created.")
robotact = new(src)
if(store_file(robotact))
return robotact
qdel(robotact)
robotact = null
CRASH("Cyborg [silicon_owner]'s tablet hard drive rejected recieving a new copy of the self-manage app. To fix, check the hard drive's space remaining. Please make a bug report about this.")
//Makes the light settings reflect the borg's headlamp settings
/obj/item/modular_computer/pda/silicon/cyborg/ui_data(mob/user)
. = ..()
.["has_light"] = TRUE
if(iscyborg(silicon_owner))
var/mob/living/silicon/robot/robo = silicon_owner
.["light_on"] = robo.lamp_enabled
.["comp_light_color"] = robo.lamp_color
//Makes the flashlight button affect the borg rather than the tablet
/obj/item/modular_computer/pda/silicon/toggle_flashlight()
if(!silicon_owner || QDELETED(silicon_owner))
return FALSE
if(iscyborg(silicon_owner))
var/mob/living/silicon/robot/robo = silicon_owner
robo.toggle_headlamp()
return TRUE
//Makes the flashlight color setting affect the borg rather than the tablet
/obj/item/modular_computer/pda/silicon/set_flashlight_color(color)
if(!silicon_owner || QDELETED(silicon_owner) || !color)
return FALSE
if(iscyborg(silicon_owner))
var/mob/living/silicon/robot/robo = silicon_owner
robo.lamp_color = color
robo.toggle_headlamp(FALSE, TRUE)
return TRUE
/obj/item/modular_computer/pda/silicon/ui_state(mob/user)
return GLOB.reverse_contained_state
/obj/item/modular_computer/pda/silicon/cyborg/syndicate
icon_state = "tablet-silicon-syndicate"
device_theme = PDA_THEME_SYNDICATE
/obj/item/modular_computer/pda/silicon/cyborg/syndicate/Initialize(mapload)
. = ..()
if(iscyborg(silicon_owner))
var/mob/living/silicon/robot/robo = silicon_owner
robo.lamp_color = COLOR_RED //Syndicate likes it red