Files
Bubberstation/code/modules/modular_computers/file_system/programs/card.dm
Gandalf 9361376345 PDA update (Messenger works while dead, Microwave works, etc). (#80069) [REMIRROR] (#25829)
* PDA update (Messenger works while dead, Microwave works, etc). (#80069)

This is an update that touches many more things all at once (compared to
my other PRs) meant to make PDAs in general feel more consistent and not
take away from one of the experiences we want to encourage: interaction
between players.

1. Replaced all checks of a 'pda' with a 'modular pc'. This means
technically (though not done in-game currently) other modpcs can hold an
uplink, and microwaves can charge laptops.
2. Speaking of microwave, they now don't break and require
deconstruction if the cell is removed mid-charge.
3. When a Mod PC is out of power, it will now allow the Messenger to
work (which now also doesn't consume any additional power), if the app
exists on the PC. Here's a video demonstration

https://github.com/tgstation/tgstation/assets/53777086/7ae12f81-a271-49b8-95fa-2ba54d2e2d1f

4. Flashlights can't be turned on while the cell is dead
5. I replaced a bunch of program vars with ``program_flags`` and renamed
``usage_flags`` to ``can_run_on_flags``.
6. Added a debug modPC that has every app installed by default. Mafia
had some issues in the past that were unknown because Mafia wasn't
preinstalled with any tablet so was never in create & destroy nor in any
other unit test. This was just an easy solution I had, but PDAs should
get more in-depth unit tests in the future for running apps n stuff- I
just wanted to make sure no other apps were broken/harddeling.

Currently when a PDA dies, its only use is to reply to PDA messages sent
to you, since you can still reply to them. Instead of just fixing it and
telling players to cope, I thought it would be nice to allow PDA
Messenger to still work, as it is a vital app.
You can call it some emergency power mode or whatever, I don't really
mind the reason behind why it is this way.

When I made cells used more on PDAs, my main goal was to encourage
upgrading your PDA and/or limiting how many apps you use at once, I did
not want this to hit on players who use it as a form of interaction.
This is the best of both worlds, I think.

The rest of the changes is just for modularity, if some downstream wants
to add tablets, phone computers, or whatever the hell else, they can
still get just as far as PDAs should be able to get to, hopefully.

🆑
add: PDAs with a dead power cell are now limited to using their
Messenger app.
fix: Microwaves now stop charging PDAs if the cell was removed
mid-charge.
fix: Microwaves can now charge laptops.
fix: PDA Flashlights can't be turned on while the PDA is dead.
fix: You can now hold a laptop up to a camera (if it has a notekeeper
app installed) like PDAs already could.
/🆑

---------

Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com>

* ok

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com>
2023-12-24 23:20:11 +00:00

315 lines
12 KiB
Plaintext

/datum/computer_file/program/card_mod
filename = "plexagonidwriter"
filedesc = "Plexagon Access Management"
downloader_category = PROGRAM_CATEGORY_EQUIPMENT
program_open_overlay = "id"
extended_desc = "Program for programming employee ID cards to access parts of the station."
download_access = list(ACCESS_COMMAND)
size = 8
tgui_id = "NtosCard"
program_icon = "id-card"
/// If TRUE, this program only modifies Centcom accesses.
var/is_centcom = FALSE
/// If TRUE, this program is authenticated with limited departmental access.
var/minor = FALSE
/// The name/assignment combo of the ID card used to authenticate.
var/authenticated_card
/// The name of the registered user, related to `authenticated_card`.
var/authenticated_user
/// The regions this program has access to based on the authenticated ID.
var/list/region_access = list()
/// The list of accesses this program is verified to change based on the authenticated ID. Used for state checking against player input.
var/list/valid_access = list()
/// List of job templates that can be applied to ID cards from this program.
var/list/job_templates = list()
/// Which departments this program has access to. See region defines.
var/target_dept
/**
* Authenticates the program based on the specific ID card.
*
* If the card has ACCESS_CHANGE_IDs, it authenticates with all options.
* Otherwise, it authenticates depending on SSid_access.sub_department_managers_tgui
* compared to the access on the supplied ID card.
* Arguments:
* * user - Program's user.
* * auth_card - The ID card to attempt to authenticate under.
*/
/datum/computer_file/program/card_mod/proc/authenticate(mob/user, obj/item/card/id/auth_card)
if(!auth_card)
return
region_access.Cut()
valid_access.Cut()
job_templates.Cut()
// If the program isn't locked to a specific department or is_centcom and we have ACCESS_CHANGE_IDS in our auth card, we're not minor.
if((!target_dept || is_centcom) && (ACCESS_CHANGE_IDS in auth_card.access))
minor = FALSE
authenticated_card = "[auth_card.name]"
authenticated_user = auth_card.registered_name ? auth_card.registered_name : "Unknown"
job_templates = is_centcom ? SSid_access.centcom_job_templates.Copy() : SSid_access.station_job_templates.Copy()
valid_access = is_centcom ? SSid_access.get_region_access_list(list(REGION_CENTCOM)) : SSid_access.get_region_access_list(list(REGION_ALL_STATION))
update_static_data(user)
return TRUE
// Otherwise, we're minor and now we have to build a list of restricted departments we can change access for.
var/list/managers = SSid_access.sub_department_managers_tgui
for(var/access_as_text in managers)
var/list/info = managers[access_as_text]
var/access = access_as_text
if((access in auth_card.access) && ((target_dept in info["regions"]) || !target_dept))
region_access |= info["regions"]
job_templates |= info["templates"]
if(length(region_access))
minor = TRUE
valid_access |= SSid_access.get_region_access_list(region_access)
authenticated_card = "[auth_card.name] \[LIMITED ACCESS\]"
update_static_data(user)
return TRUE
return FALSE
/datum/computer_file/program/card_mod/on_start(mob/living/user)
. = ..()
if(!.)
return FALSE
computer.crew_manifest_update = TRUE
/datum/computer_file/program/card_mod/kill_program(mob/user)
computer.crew_manifest_update = FALSE
var/obj/item/card/id/inserted_auth_card = computer.computer_id_slot
if(inserted_auth_card)
GLOB.manifest.modify(inserted_auth_card.registered_name, inserted_auth_card.assignment, inserted_auth_card.get_trim_assignment())
return ..()
/datum/computer_file/program/card_mod/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
var/mob/user = usr
var/obj/item/card/id/inserted_auth_card = computer.computer_id_slot
switch(action)
// Log in.
if("PRG_authenticate")
if(!computer || !inserted_auth_card)
playsound(computer, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return TRUE
if(authenticate(user, inserted_auth_card))
playsound(computer, 'sound/machines/terminal_on.ogg', 50, FALSE)
return TRUE
// Log out.
if("PRG_logout")
authenticated_card = null
authenticated_user = null
playsound(computer, 'sound/machines/terminal_off.ogg', 50, FALSE)
return TRUE
// Print a report.
if("PRG_print")
if(!computer)
return TRUE
if(!authenticated_card)
return TRUE
var/contents = {"<h4>Access Report</h4>
<u>Prepared By:</u> [authenticated_user]<br>
<u>For:</u> [inserted_auth_card.registered_name ? inserted_auth_card.registered_name : "Unregistered"]<br>
<hr>
<u>Assignment:</u> [inserted_auth_card.assignment]<br>
<u>Access:</u><br>
"}
var/list/known_access_rights = SSid_access.get_region_access_list(list(REGION_ALL_STATION))
for(var/A in inserted_auth_card.access)
if(A in known_access_rights)
contents += " [SSid_access.get_access_desc(A)]"
if(!computer.print_text(contents, "access report - [inserted_auth_card.registered_name ? inserted_auth_card.registered_name : "Unregistered"]"))
to_chat(usr, span_notice("Printer is out of paper."))
return TRUE
else
playsound(computer, 'sound/machines/terminal_on.ogg', 50, FALSE)
computer.visible_message(span_notice("\The [computer] prints out a paper."))
return TRUE
if("PRG_eject_id")
if(inserted_auth_card)
return computer.RemoveID(usr)
else
var/obj/item/I = user.get_active_held_item()
if(isidcard(I))
return computer.InsertID(I, user)
return TRUE
// Used to fire someone. Wipes all access from their card and modifies their assignment.
if("PRG_terminate")
if(!computer || !authenticated_card)
return TRUE
if(minor)
if(!(inserted_auth_card.trim?.type in job_templates))
to_chat(usr, span_notice("Software error: You do not have the necessary permissions to demote this card."))
return TRUE
// Set the new assignment then remove the trim.
inserted_auth_card.assignment = is_centcom ? "Fired" : "Demoted"
SSid_access.remove_trim_from_card(inserted_auth_card)
playsound(computer, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return TRUE
// Change ID card assigned name.
if("PRG_edit")
if(!computer || !authenticated_card || !inserted_auth_card)
return TRUE
var/old_name = inserted_auth_card.registered_name
// Sanitize the name first. We're not using the full sanitize_name proc as ID cards can have a wider variety of things on them that
// would not pass as a formal character name, but would still be valid on an ID card created by a player.
var/new_name = sanitize(params["name"])
if(!new_name)
inserted_auth_card.registered_name = null
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
inserted_auth_card.update_label()
// We had a name before and now we have no name, so this will unassign the card and we update the icon.
if(old_name)
inserted_auth_card.update_icon()
return TRUE
// However, we are going to reject bad names overall including names with invalid characters in them, while allowing numbers.
new_name = reject_bad_name(new_name, allow_numbers = TRUE)
if(!new_name)
to_chat(usr, span_notice("Software error: The ID card rejected the new name as it contains prohibited characters."))
return TRUE
inserted_auth_card.registered_name = new_name
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
inserted_auth_card.update_label()
// Card wasn't assigned before and now it is, so update the icon accordingly.
if(!old_name)
inserted_auth_card.update_icon()
return TRUE
// Change age
if("PRG_age")
if(!computer || !authenticated_card || !inserted_auth_card)
return TRUE
var/new_age = params["id_age"]
if(!isnum(new_age))
stack_trace("[key_name(usr)] ([usr]) attempted to set invalid age \[[new_age]\] to [inserted_auth_card]")
return TRUE
inserted_auth_card.registered_age = new_age
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
return TRUE
// Change assignment
if("PRG_assign")
if(!computer || !authenticated_card || !inserted_auth_card)
return TRUE
var/new_asignment = trim(sanitize(params["assignment"]), MAX_NAME_LEN)
inserted_auth_card.assignment = new_asignment
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
inserted_auth_card.update_label()
return TRUE
// Add/remove access.
if("PRG_access")
if(!computer || !authenticated_card || !inserted_auth_card)
return TRUE
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
var/access_type = params["access_target"]
var/try_wildcard = params["access_wildcard"]
if(!(access_type in valid_access))
stack_trace("[key_name(usr)] ([usr]) attempted to add invalid access \[[access_type]\] to [inserted_auth_card]")
return TRUE
if(access_type in inserted_auth_card.access)
inserted_auth_card.remove_access(list(access_type))
LOG_ID_ACCESS_CHANGE(user, inserted_auth_card, "removed [SSid_access.get_access_desc(access_type)]")
return TRUE
if(!inserted_auth_card.add_access(list(access_type), try_wildcard))
to_chat(usr, span_notice("ID error: ID card rejected your attempted access modification."))
LOG_ID_ACCESS_CHANGE(user, inserted_auth_card, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]")
return TRUE
if(access_type in ACCESS_ALERT_ADMINS)
message_admins("[ADMIN_LOOKUPFLW(user)] just added [SSid_access.get_access_desc(access_type)] to an ID card [ADMIN_VV(inserted_auth_card)] [(inserted_auth_card.registered_name) ? "belonging to [inserted_auth_card.registered_name]." : "with no registered name."]")
LOG_ID_ACCESS_CHANGE(user, inserted_auth_card, "added [SSid_access.get_access_desc(access_type)]")
return TRUE
// Apply template to ID card.
if("PRG_template")
if(!computer || !authenticated_card || !inserted_auth_card)
return TRUE
playsound(computer, SFX_TERMINAL_TYPE, 50, FALSE)
var/template_name = params["name"]
if(!template_name)
return TRUE
for(var/trim_path in job_templates)
var/datum/id_trim/trim = SSid_access.trim_singletons_by_path[trim_path]
if(trim.assignment != template_name)
continue
SSid_access.add_trim_access_to_card(inserted_auth_card, trim_path)
return TRUE
stack_trace("[key_name(usr)] ([usr]) attempted to apply invalid template \[[template_name]\] to [inserted_auth_card]")
return TRUE
/datum/computer_file/program/card_mod/ui_static_data(mob/user)
var/list/data = list()
data["station_name"] = station_name()
data["centcom_access"] = is_centcom
data["minor"] = target_dept || minor ? TRUE : FALSE
var/list/regions = list()
var/list/tgui_region_data = SSid_access.all_region_access_tgui
if(is_centcom)
regions += tgui_region_data[REGION_CENTCOM]
else
for(var/region in SSid_access.station_regions)
if((minor || target_dept) && !(region in region_access))
continue
regions += tgui_region_data[region]
data["regions"] = regions
data["accessFlags"] = SSid_access.flags_by_access
data["wildcardFlags"] = SSid_access.wildcard_flags_by_wildcard
data["accessFlagNames"] = SSid_access.access_flag_string_by_flag
data["showBasic"] = TRUE
data["templates"] = job_templates
return data
/datum/computer_file/program/card_mod/ui_data(mob/user)
var/list/data = list()
var/obj/item/card/id/inserted_id = computer.computer_id_slot
data["authIDName"] = inserted_id ? inserted_id.name : "-----"
data["authenticatedUser"] = authenticated_card
data["has_id"] = !!inserted_id
data["id_name"] = inserted_id ? inserted_id.name : "-----"
if(inserted_id)
data["id_rank"] = inserted_id.assignment ? inserted_id.assignment : "Unassigned"
data["id_owner"] = inserted_id.registered_name ? inserted_id.registered_name : "-----"
data["access_on_card"] = inserted_id.access
data["wildcardSlots"] = inserted_id.wildcard_slots
data["id_age"] = inserted_id.registered_age
if(inserted_id.trim)
var/datum/id_trim/card_trim = inserted_id.trim
data["hasTrim"] = TRUE
data["trimAssignment"] = card_trim.assignment ? card_trim.assignment : ""
data["trimAccess"] = card_trim.access ? card_trim.access : list()
else
data["hasTrim"] = FALSE
data["trimAssignment"] = ""
data["trimAccess"] = list()
return data