diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 07c8778c535..2d02cdb2d31 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1186,6 +1186,10 @@ /// If this is set, will manually override the trim shown for SecHUDs. Intended for admins to VV edit and chameleon ID cards. var/sechud_icon_state_override = null + /// A name (eg "Captain") that is "inherent" to this ID card + /// If the assigned name matches the inherent name it does not apply the label + var/inherent_assigned_name + /obj/item/card/id/advanced/Initialize(mapload) . = ..() RegisterSignal(src, COMSIG_ITEM_EQUIPPED, PROC_REF(update_intern_status)) @@ -1288,6 +1292,13 @@ is_intern = FALSE update_label() +/obj/item/card/id/advanced/update_label() + if(inherent_assigned_name && registered_name == inherent_assigned_name) + name = "[initial(name)][(!assignment || assignment == inherent_assigned_name) ? "" : " ([assignment])"]" + return + + return ..() + /obj/item/card/id/advanced/update_overlays() . = ..() @@ -1390,13 +1401,7 @@ registered_name = "Captain" trim = /datum/id_trim/job/captain registered_age = null - -/obj/item/card/id/advanced/gold/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze - if(registered_name == "Captain") - name = "[initial(name)][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" - update_appearance(UPDATE_ICON) - else - ..() + inherent_assigned_name = "Captain" /obj/item/card/id/advanced/centcom name = "\improper CentCom ID" @@ -1491,14 +1496,7 @@ desc = "The spare ID of the Dark Lord himself." registered_name = "Captain" registered_age = null - -/obj/item/card/id/advanced/black/syndicate_command/captain_id/syndie_spare/update_label() - if(registered_name == "Captain") - name = "[initial(name)][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" - update_appearance(UPDATE_ICON) - return - - return ..() + inherent_assigned_name = "Captain" /obj/item/card/id/advanced/debug name = "\improper Debug ID" @@ -1952,22 +1950,6 @@ else input_name = "[pick(GLOB.first_names)] [pick(GLOB.last_names)]" - var/change_trim = tgui_alert(user, "Adjust the appearance of your card's trim?", "Modify Trim", list("Yes", "No")) - if(!after_input_check(user)) - return TRUE - var/selected_trim_path - var/static/list/trim_list - if(change_trim == "Yes") - trim_list = list() - for(var/trim_path in typesof(/datum/id_trim)) - var/datum/id_trim/trim = SSid_access.trim_singletons_by_path[trim_path] - if(trim && trim.trim_state && trim.assignment) - var/fake_trim_name = "[trim.assignment] ([trim.trim_state])" - trim_list[fake_trim_name] = trim_path - selected_trim_path = tgui_input_list(user, "Select trim to apply to your card.\nNote: This will not grant any trim accesses.", "Forge Trim", sort_list(trim_list, GLOBAL_PROC_REF(cmp_typepaths_asc))) - if(!after_input_check(user)) - return TRUE - var/target_occupation = tgui_input_text(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels.", "Agent card job assignment", assignment ? assignment : "Assistant", max_length = MAX_NAME_LEN) if(!after_input_check(user)) return TRUE @@ -1984,8 +1966,6 @@ return registered_name = input_name - if(selected_trim_path) - SSid_access.apply_trim_override(src, trim_list[selected_trim_path]) if(target_occupation) assignment = sanitize(target_occupation) if(new_age) @@ -1999,16 +1979,10 @@ to_chat(user, span_notice("You successfully forge the ID card.")) user.log_message("forged \the [initial(name)] with name \"[registered_name]\", occupation \"[assignment]\" and trim \"[trim?.assignment]\".", LOG_GAME) - if(!ishuman(user)) + if(!ishuman(user) || registered_account) return var/mob/living/carbon/human/owner = user - if (!selected_trim_path) // Ensure that even without a trim update, we update user's sechud - owner.update_ID_card() - - if (registered_account) - return - var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[owner.account_id]"] if(account) set_account(account) diff --git a/code/modules/clothing/chameleon/_chameleon_action.dm b/code/modules/clothing/chameleon/_chameleon_action.dm index 88ca8e51fe8..fd05f481bad 100644 --- a/code/modules/clothing/chameleon/_chameleon_action.dm +++ b/code/modules/clothing/chameleon/_chameleon_action.dm @@ -8,7 +8,7 @@ check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED /// Typecache of all item types we explicitly cannot pick /// Note that abstract items are already excluded - VAR_FINAL/list/chameleon_blacklist = list() + VAR_FINAL/list/chameleon_blacklist /// Typecache of typepaths we can turn into VAR_FINAL/list/chameleon_typecache /// Assoc list of item name + icon state to item typepath @@ -31,10 +31,18 @@ qdel(src) return + name = "Change [chameleon_name] Appearance" + build_all_button_icons() + + LAZYINITLIST(chameleon_blacklist) + LAZYINITLIST(chameleon_typecache) + LAZYINITLIST(chameleon_list) + initialize_blacklist() initialize_disguises() + if(active_type) - if(chameleon_blacklist[active_type]) + if(is_type_in_typecache(active_type, chameleon_blacklist)) stack_trace("[type] has an active type defined in init which is blacklisted ([active_type])") active_type = null else @@ -73,31 +81,40 @@ var/datum/action/chameleon_outfit/outfit_action = locate() in remove_from.actions QDEL_NULL(outfit_action) +/// Basic initialization of the chameleon items we cannot pick from /datum/action/item_action/chameleon/change/proc/initialize_blacklist() chameleon_blacklist |= typecacheof(target.type) +/// Basic initialization of the chameleon items we can pick from /datum/action/item_action/chameleon/change/proc/initialize_disguises() - name = "Change [chameleon_name] Appearance" - build_all_button_icons() - - LAZYINITLIST(chameleon_typecache) - LAZYINITLIST(chameleon_list) - if(!ispath(chameleon_type, /obj/item)) stack_trace("Non-item chameleon type defined on [type] ([chameleon_type])") return add_chameleon_items(chameleon_type) -/datum/action/item_action/chameleon/change/proc/add_chameleon_items(type_to_add) +/// Used for formatting a typepath into something human readable for selection +/datum/action/item_action/chameleon/change/proc/format_readable_name(datum/format_type) + if(ispath(format_type, /obj/item)) + var/obj/item/format_item = format_type + return "[format_item::name] ([replacetext(format_item::post_init_icon_state || format_item::icon_state, "_", " ")])" - chameleon_typecache |= typecacheof(type_to_add) - for(var/obj/item/item_type as anything in chameleon_typecache) - if(chameleon_blacklist[item_type] || (item_type::item_flags & ABSTRACT) || item_type == item_type::abstract_type || !item_type::icon_state) + return "[format_type]" + +/** + * Adds items of the given type or types to the chameleon selection list + * + * * type_or_types_to_add: A single typepath or a list of typepaths to add to the chameleon selection + * * only_root: If TRUE, only add the literal type or types passed, not their children + * * ignore_root: If TRUE, only add children of the type or types passed, not the literal types + */ +/datum/action/item_action/chameleon/change/proc/add_chameleon_items(type_or_types_to_add, only_root = FALSE, ignore_root = FALSE) + var/list/new_items = typecacheof(type_or_types_to_add, only_root_path = only_root, ignore_root_path = ignore_root) + for(var/obj/item/item_type as anything in new_items - chameleon_typecache) + if(is_type_in_typecache(item_type, chameleon_blacklist) || (item_type::item_flags & ABSTRACT) || item_type == item_type::abstract_type || !item_type::icon_state) continue - var/chameleon_item_name = "[item_type::name] ([item_type::post_init_icon_state || item_type::icon_state])" - chameleon_list[chameleon_item_name] = item_type - + chameleon_list[format_readable_name(item_type)] = item_type + chameleon_typecache |= new_items /datum/action/item_action/chameleon/change/proc/select_look(mob/user) var/picked_name = tgui_input_list(user, "Select [chameleon_name] to change into", "Chameleon Settings", sort_list(chameleon_list, GLOBAL_PROC_REF(cmp_typepaths_asc))) diff --git a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm index 956b0892e33..6f86cb48798 100644 --- a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm +++ b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm @@ -136,6 +136,45 @@ stack_trace("Adding chameleon ID action to non-chameleon id ([target])") qdel(src) +/datum/action/item_action/chameleon/change/id/format_readable_name(datum/format_type) + if(ispath(format_type, /obj/item/card/id/advanced)) + var/obj/item/card/id/advanced/format_card = format_type + var/list/basesplit_state = splittext(format_card::post_init_icon_state || format_card::icon_state, "_") + if(basesplit_state[1] == "card") // kind of evil, but so is making icon state user visible + basesplit_state.Cut(1, 2) + + var/list/assignedsplit_state = splittext(format_card::assigned_icon_state, "_") + if(assignedsplit_state[1] == "assigned") // kind of evil, but so is making icon state user visible + assignedsplit_state.Cut(1, 2) + + var/final_state_string = "[jointext(basesplit_state, " ")], [jointext(assignedsplit_state, " ") || "unassigned"]" + + return "[format_card::name] ([final_state_string])" + + return ..() + +/datum/action/item_action/chameleon/change/id/initialize_disguises() + // basic id types + add_chameleon_items(list( + /obj/item/card/id/advanced, + /obj/item/card/id/advanced/black, + /obj/item/card/id/advanced/bountyhunter, + /obj/item/card/id/advanced/centcom, + /obj/item/card/id/advanced/gold, + /obj/item/card/id/advanced/platinum, + /obj/item/card/id/advanced/prisoner, + /obj/item/card/id/advanced/rainbow, + /obj/item/card/id/advanced/robotic, + /obj/item/card/id/advanced/silver, + ), only_root = TRUE) + + // all black cards (they're all syndie cards, which are IC anyways) + add_chameleon_items(/obj/item/card/id/advanced/black) + // all centcom cards (they should all be IC anyways) + add_chameleon_items(/obj/item/card/id/advanced/centcom) + // explicitly include the captain's spare id, even though it could be manually constructed from the gold id, for ease + add_chameleon_items(/obj/item/card/id/advanced/gold/captains_spare, only_root = TRUE) + /datum/action/item_action/chameleon/change/id/update_item(obj/item/picked_item) . = ..() var/obj/item/card/id/advanced/chameleon/agent_card = target @@ -151,15 +190,14 @@ // If there has not, we set the assignment to the copied card's default as well as copying over the the // default registered name from the copied card. if(!agent_card.forged) - if(!agent_card.assignment) - agent_card.assignment = initial(copied_card.assignment) - + agent_card.assignment ||= initial(copied_card.assignment) agent_card.registered_name = initial(copied_card.registered_name) agent_card.icon_state = initial(copied_card.icon_state) if(ispath(copied_card, /obj/item/card/id/advanced)) var/obj/item/card/id/advanced/copied_advanced_card = copied_card agent_card.assigned_icon_state = initial(copied_advanced_card.assigned_icon_state) + agent_card.inherent_assigned_name = initial(copied_advanced_card.inherent_assigned_name) agent_card.update_label() agent_card.update_appearance(UPDATE_ICON) @@ -204,26 +242,40 @@ stack_trace("Adding chameleon ID trim action to non-chameleon id ([target])") qdel(src) +/datum/action/item_action/chameleon/change/id_trim/format_readable_name(datum/format_type) + if(istype(format_type, /datum/id_trim)) + var/datum/id_trim/trim = format_type + var/list/trimstate_split = splittext(trim.trim_state, "_") + if(trimstate_split[1] == "trim") // kind of evil, but so is making icon state user visible + trimstate_split.Cut(1, 2) + + return "[trim.assignment] ([jointext(trimstate_split, " ")])" + + return ..() + /datum/action/item_action/chameleon/change/id_trim/initialize_blacklist() - return + chameleon_blacklist |= typecacheof(/datum/id_trim/centcom/corpse) /datum/action/item_action/chameleon/change/id_trim/initialize_disguises() - // Little bit of copypasta but we only use trim datums rather than item paths - name = "Change [chameleon_name] Appearance" - build_all_button_icons() + var/list/chameleon_whitelist = typecacheof(list( + /datum/id_trim/battlecruiser, + /datum/id_trim/bit_avatar, + /datum/id_trim/bounty_hunter, + /datum/id_trim/centcom, + /datum/id_trim/job, + /datum/id_trim/pirate, + /datum/id_trim/syndicom, + /datum/id_trim/technician_id, + )) - LAZYINITLIST(chameleon_typecache) - LAZYINITLIST(chameleon_list) - - for(var/datum/id_trim/trim_path as anything in typesof(/datum/id_trim)) - if(chameleon_blacklist[trim_path]) + for(var/trim_path in chameleon_whitelist) + if(is_type_in_typecache(trim_path, chameleon_blacklist)) continue var/datum/id_trim/trim = SSid_access.trim_singletons_by_path[trim_path] if(trim && trim.trim_state && trim.assignment) - var/chameleon_item_name = "[trim.assignment] ([trim.trim_state])" - chameleon_list[chameleon_item_name] = trim_path + chameleon_list[format_readable_name(trim)] = trim_path chameleon_typecache[trim_path] = TRUE /datum/action/item_action/chameleon/change/id_trim/update_item(picked_trim_path) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 97db5a18b7a..01a901dd28f 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -196,6 +196,7 @@ #include "hydroponics_self_mutations.dm" #include "hydroponics_validate_genes.dm" #include "id_access.dm" +#include "id_card.dm" #include "inhands.dm" #include "interaction_door.dm" #include "interaction_silicon.dm" diff --git a/code/modules/unit_tests/id_card.dm b/code/modules/unit_tests/id_card.dm new file mode 100644 index 00000000000..b0658b55899 --- /dev/null +++ b/code/modules/unit_tests/id_card.dm @@ -0,0 +1,6 @@ +/// Ensures the captain's spare id keeps the name "captain's spare id", and doesn't get changed by id label +/datum/unit_test/spare_id_name + +/datum/unit_test/spare_id_name/Run() + var/obj/item/card/id/advanced/gold/captains_spare/card = EASY_ALLOCATE() + TEST_ASSERT_EQUAL(card.name, initial(card.name), "Captain's spare ID card should not change its name by default.")