/* Cards * Contains: * DATA CARD * ID CARD * FINGERPRINT CARD HOLDER * FINGERPRINT CARD */ /* * DATA CARDS - Used for the IC data card reader */ /obj/item/card name = "card" desc = "Does card things." icon = 'icons/obj/card.dmi' w_class = WEIGHT_CLASS_TINY var/list/files = list() /obj/item/card/suicide_act(mob/living/carbon/user) user.visible_message("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") return BRUTELOSS /obj/item/card/data name = "data card" desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." icon_state = "data_1" obj_flags = UNIQUE_RENAME var/function = "storage" var/data = "null" var/special = null inhand_icon_state = "card-id" lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' var/detail_color = COLOR_ASSEMBLY_ORANGE /obj/item/card/data/Initialize() .=..() update_appearance() /obj/item/card/data/update_overlays() . = ..() if(detail_color == COLOR_FLOORTILE_GRAY) return var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") detail_overlay.color = detail_color . += detail_overlay /obj/item/card/data/full_color desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." icon_state = "data_2" /obj/item/card/data/disk desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." icon_state = "data_3" /* * ID CARDS */ /// "Retro" ID card that renders itself as the icon state with no overlays. /obj/item/card/id name = "retro identification card" desc = "A card used to provide ID and determine access across the station." icon_state = "card_grey" worn_icon_state = "card_retro" inhand_icon_state = "card-id" lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' slot_flags = ITEM_SLOT_ID armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 100) resistance_flags = FIRE_PROOF | ACID_PROOF /// How many magical mining Disney Dollars this card has for spending at the mining equipment vendors. var/mining_points = 0 /// The name registered on the card (for example: Dr Bryan See) var/registered_name = null /// Linked bank account. var/datum/bank_account/registered_account /// Linked paystand. var/obj/machinery/paystand/my_store /// Registered owner's age. var/registered_age = 13 /// The job name registered on the card (for example: Assistant). var/assignment /// Trim datum associated with the card. Controls which job icon is displayed on the card and which accesses do not require wildcards. var/datum/id_trim/trim /// Access levels held by this card. var/list/access = list() /// List of wildcard slot names as keys with lists of wildcard data as values. var/list/wildcard_slots = list() /obj/item/card/id/Initialize(mapload) . = ..() // Applying the trim updates the label and icon, so don't do this twice. if(ispath(trim)) SSid_access.apply_trim_to_card(src, trim) else update_label() update_icon() RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, .proc/update_in_wallet) /obj/item/card/id/Destroy() if (registered_account) registered_account.bank_cards -= src if (my_store && my_store.my_card == src) my_store.my_card = null return ..() /obj/item/card/id/get_id_examine_strings(mob/user) . = ..() . += list("[icon2html(get_icon_source(), user, extra_classes = "bigicon")]") /// Simple helper proc. Returns the source of the icon for this card. Advanced cards can override this to return their icon that has been cached due to using overlays. /obj/item/card/id/proc/get_icon_source() return src /** * Helper proc, checks whether the ID card can hold any given set of wildcards. * * Returns TRUE if the card can hold the wildcards, FALSE otherwise. * Arguments: * * wildcard_list - List of accesses to check. * * try_wildcard - If not null, will attempt to add wildcards for this wildcard specifically and will return FALSE if the card cannot hold all wildcards in this slot. */ /obj/item/card/id/proc/can_add_wildcards(list/wildcard_list, try_wildcard = null) if(!length(wildcard_list)) return TRUE var/list/new_wildcard_limits = list() for(var/flag_name in wildcard_slots) if(try_wildcard && !(flag_name == try_wildcard)) continue var/list/wildcard_info = wildcard_slots[flag_name] new_wildcard_limits[flag_name] = wildcard_info["limit"] - length(wildcard_info["usage"]) if(!length(new_wildcard_limits)) return FALSE var/wildcard_allocated for(var/wildcard in wildcard_list) var/wildcard_flag = SSid_access.get_access_flag(wildcard) wildcard_allocated = FALSE for(var/flag_name in new_wildcard_limits) var/limit_flags = SSid_access.wildcard_flags_by_wildcard[flag_name] if(!(wildcard_flag & limit_flags)) continue // Negative limits mean infinite slots. Positive limits mean limited slots still available. 0 slots means no slots. if(new_wildcard_limits[flag_name] == 0) continue new_wildcard_limits[flag_name]-- wildcard_allocated = TRUE break if(!wildcard_allocated) return FALSE return TRUE /** * Attempts to add the given wildcards to the ID card. * * Arguments: * * wildcard_list - List of accesses to add. * * try_wildcard - If not null, will attempt to add all wildcards to this wildcard slot only. * * mode - The method to use when adding wildcards. See define for ERROR_ON_FAIL */ /obj/item/card/id/proc/add_wildcards(list/wildcard_list, try_wildcard = null, mode = ERROR_ON_FAIL) var/wildcard_allocated // Iterate through each wildcard in our list. Get its access flag. Then iterate over wildcard slots and try to fit it in. for(var/wildcard in wildcard_list) var/wildcard_flag = SSid_access.get_access_flag(wildcard) wildcard_allocated = FALSE for(var/flag_name in wildcard_slots) if(flag_name == WILDCARD_NAME_FORCED) continue if(try_wildcard && !(flag_name == try_wildcard)) continue var/limit_flags = SSid_access.wildcard_flags_by_wildcard[flag_name] if(!(wildcard_flag & limit_flags)) continue var/list/wildcard_info = wildcard_slots[flag_name] var/wildcard_limit = wildcard_info["limit"] var/list/wildcard_usage = wildcard_info["usage"] var/wildcard_count = wildcard_limit - length(wildcard_usage) // Negative limits mean infinite slots. Positive limits mean limited slots still available. 0 slots means no slots. if(wildcard_count == 0) continue wildcard_usage |= wildcard access |= wildcard wildcard_allocated = TRUE break // Fallback for if we couldn't allocate the wildcard for some reason. if(!wildcard_allocated) if(mode == ERROR_ON_FAIL) CRASH("Wildcard ([wildcard]) could not be added to [src].") if(mode == TRY_ADD_ALL) continue // If the card has no info for historic forced wildcards, create the list. if(!wildcard_slots[WILDCARD_NAME_FORCED]) wildcard_slots[WILDCARD_NAME_FORCED] = list(limit = 0, usage = list()) var/list/wildcard_info = wildcard_slots[WILDCARD_NAME_FORCED] var/list/wildcard_usage = wildcard_info["usage"] wildcard_usage |= wildcard access |= wildcard wildcard_info["limit"] = length(wildcard_usage) /** * Removes wildcards from the ID card. * * Arguments: * * wildcard_list - List of accesses to remove. */ /obj/item/card/id/proc/remove_wildcards(list/wildcard_list) var/wildcard_removed // Iterate through each wildcard in our list. Get its access flag. Then iterate over wildcard slots and try to remove it. for(var/wildcard in wildcard_list) wildcard_removed = FALSE for(var/flag_name in wildcard_slots) if(flag_name == WILDCARD_NAME_FORCED) continue var/list/wildcard_info = wildcard_slots[flag_name] var/wildcard_usage = wildcard_info["usage"] if(!(wildcard in wildcard_usage)) continue wildcard_usage -= wildcard access -= wildcard wildcard_removed = TRUE break // Fallback to see if this was a force-added wildcard. if(!wildcard_removed) // If the card has no info for historic forced wildcards, that's an error state. if(!wildcard_slots[WILDCARD_NAME_FORCED]) stack_trace("Wildcard ([wildcard]) could not be removed from [src]. This card has no forced wildcard data and the wildcard is not in this card's wildcard lists.") var/list/wildcard_info = wildcard_slots[WILDCARD_NAME_FORCED] var/wildcard_usage = wildcard_info["usage"] if(!(wildcard in wildcard_usage)) stack_trace("Wildcard ([wildcard]) could not be removed from [src]. This access is not a wildcard on this card.") wildcard_usage -= wildcard access -= wildcard wildcard_info["limit"] = length(wildcard_usage) if(!wildcard_info["limit"]) wildcard_slots -= WILDCARD_NAME_FORCED /** * Attempts to add the given accesses to the ID card as non-wildcards. * * Depending on the mode, may add accesses as wildcards or error if it can't add them as non-wildcards. * Arguments: * * add_accesses - List of accesses to check. * * try_wildcard - If not null, will attempt to add all accesses that require wildcard slots to this wildcard slot only. * * mode - The method to use when adding accesses. See define for ERROR_ON_FAIL */ /obj/item/card/id/proc/add_access(list/add_accesses, try_wildcard = null, mode = ERROR_ON_FAIL) var/list/wildcard_access = list() var/list/normal_access = list() build_access_lists(add_accesses, normal_access, wildcard_access) // Check if we can add the wildcards. if(mode == ERROR_ON_FAIL) if(!can_add_wildcards(wildcard_access, try_wildcard)) CRASH("Cannot add wildcards from \[[add_accesses.Join(",")]\] to [src]") // All clear to add the accesses. access |= normal_access if(mode != TRY_ADD_ALL_NO_WILDCARD) add_wildcards(wildcard_access, try_wildcard, mode = mode) return TRUE /** * Removes the given accesses from the ID Card. * * Will remove the wildcards if the accesses given are on the card as wildcard accesses. * Arguments: * * rem_accesses - List of accesses to remove. */ /obj/item/card/id/proc/remove_access(list/rem_accesses) var/list/wildcard_access = list() var/list/normal_access = list() build_access_lists(rem_accesses, normal_access, wildcard_access) access -= normal_access remove_wildcards(wildcard_access) /** * Attempts to set the card's accesses to the given accesses, clearing all accesses not in the given list. * * Depending on the mode, may add accesses as wildcards or error if it can't add them as non-wildcards. * Arguments: * * new_access_list - List of all accesses that this card should hold exclusively. * * mode - The method to use when setting accesses. See define for ERROR_ON_FAIL */ /obj/item/card/id/proc/set_access(list/new_access_list, mode = ERROR_ON_FAIL) var/list/wildcard_access = list() var/list/normal_access = list() build_access_lists(new_access_list, normal_access, wildcard_access) // Check if we can add the wildcards. if(mode == ERROR_ON_FAIL) if(!can_add_wildcards(wildcard_access)) CRASH("Cannot add wildcards from \[[new_access_list.Join(",")]\] to [src]") clear_access() access = normal_access.Copy() if(mode != TRY_ADD_ALL_NO_WILDCARD) add_wildcards(wildcard_access, mode = mode) return TRUE /// Clears all accesses from the ID card - both wildcard and normal. /obj/item/card/id/proc/clear_access() // Go through the wildcards and reset them. for(var/flag_name in wildcard_slots) var/list/wildcard_info = wildcard_slots[flag_name] var/list/wildcard_usage = wildcard_info["usage"] wildcard_usage.Cut() // Hard reset access access.Cut() /** * Helper proc. Creates access lists for the access procs. * * Takes the accesses list and compares it with the trim. Any basic accesses that match the trim are * added to basic_access_list and the rest are added to wildcard_access_list. * This proc directly modifies the lists passed in as args. It expects these lists to be instantiated. * There is no return value. * Arguments: * * accesses - List of accesses you want to stort into basic_access_list and wildcard_access_list. Should not be null. * * basic_access_list - Mandatory argument. The proc modifies the list passed in this argument and adds accesses the trim supports to it. * * wildcard_access_list - Mandatory argument. The proc modifies the list passed in this argument and adds accesses the trim does not support to it. */ /obj/item/card/id/proc/build_access_lists(list/accesses, list/basic_access_list, list/wildcard_access_list) if(!length(accesses) || isnull(basic_access_list) || isnull(wildcard_access_list)) CRASH("Invalid parameters passed to build_access_lists") var/list/trim_accesses = trim?.access // Populate the lists. for(var/new_access in accesses) if(new_access in trim_accesses) basic_access_list |= new_access continue wildcard_access_list |= new_access /obj/item/card/id/attack_self(mob/user) if(Adjacent(user)) var/minor if(registered_name && registered_age && registered_age < AGE_MINOR) minor = " [registered_age]" user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].") add_fingerprint(user) /obj/item/card/id/vv_edit_var(var_name, var_value) . = ..() if(.) switch(var_name) if(NAMEOF(src, assignment), NAMEOF(src, registered_name), NAMEOF(src, registered_age)) update_label() update_icon() if(NAMEOF(src, trim)) if(ispath(trim)) SSid_access.apply_trim_to_card(src, trim) /obj/item/card/id/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/holochip)) insert_money(W, user) return else if(istype(W, /obj/item/stack/spacecash)) insert_money(W, user, TRUE) return else if(istype(W, /obj/item/coin)) insert_money(W, user, TRUE) return else if(istype(W, /obj/item/storage/bag/money)) var/obj/item/storage/bag/money/money_bag = W var/list/money_contained = money_bag.contents var/money_added = mass_insert_money(money_contained, user) if (money_added) to_chat(user, "You stuff the contents into the card! They disappear in a puff of bluespace smoke, adding [money_added] worth of credits to the linked account.") return else return ..() /** * Insert credits or coins into the ID card and add their value to the associated bank account. * * Arguments: * money - The item to attempt to convert to credits and insert into the card. * user - The user inserting the item. * physical_currency - Boolean, whether this is a physical currency such as a coin and not a holochip. */ /obj/item/card/id/proc/insert_money(obj/item/money, mob/user, physical_currency) if(!registered_account) to_chat(user, "[src] doesn't have a linked account to deposit [money] into!") return var/cash_money = money.get_item_credit_value() if(!cash_money) to_chat(user, "[money] doesn't seem to be worth anything!") return registered_account.adjust_money(cash_money) SSblackbox.record_feedback("amount", "credits_inserted", cash_money) log_econ("[cash_money] credits were inserted into [src] owned by [src.registered_name]") if(physical_currency) to_chat(user, "You stuff [money] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") else to_chat(user, "You insert [money] into [src], adding [cash_money] credits to the linked account.") to_chat(user, "The linked account now reports a balance of [registered_account.account_balance] cr.") qdel(money) /** * Insert multiple money or money-equivalent items at once. * * Arguments: * money - List of items to attempt to convert to credits and insert into the card. * user - The user inserting the items. */ /obj/item/card/id/proc/mass_insert_money(list/money, mob/user) if(!registered_account) to_chat(user, "[src] doesn't have a linked account to deposit into!") return FALSE if (!money || !money.len) return FALSE var/total = 0 for (var/obj/item/physical_money in money) total += physical_money.get_item_credit_value() CHECK_TICK registered_account.adjust_money(total) SSblackbox.record_feedback("amount", "credits_inserted", total) log_econ("[total] credits were inserted into [src] owned by [src.registered_name]") QDEL_LIST(money) return total /// Helper proc. Can the user alt-click the ID? /obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) if(!isliving(user)) return if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return return TRUE /// Attempts to set a new bank account on the ID card. /obj/item/card/id/proc/set_new_account(mob/living/user) . = FALSE var/datum/bank_account/old_account = registered_account var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num | null if (isnull(new_bank_id)) return if(!alt_click_can_use_id(user)) return if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) to_chat(user, "The account ID number needs to be between 111111 and 999999.") return if (registered_account && registered_account.account_id == new_bank_id) to_chat(user, "The account ID was already assigned to this card.") return var/datum/bank_account/B = SSeconomy.bank_accounts_by_id["[new_bank_id]"] if(B) if (old_account) old_account.bank_cards -= src B.bank_cards += src registered_account = B to_chat(user, "The provided account has been linked to this ID card.") return TRUE to_chat(user, "The account ID number provided is invalid.") return /obj/item/card/id/AltClick(mob/living/user) if(!alt_click_can_use_id(user)) return if(!registered_account) set_new_account(user) return if (registered_account.being_dumped) registered_account.bank_card_talk("内部服务器错误", TRUE) return var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num|null, 1) if(!amount_to_remove || amount_to_remove < 0) return if(!alt_click_can_use_id(user)) return if(registered_account.adjust_money(-amount_to_remove)) var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) user.put_in_hands(holochip) to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") SSblackbox.record_feedback("amount", "credits_removed", amount_to_remove) log_econ("[amount_to_remove] credits were removed from [src] owned by [src.registered_name]") return else var/difference = amount_to_remove - registered_account.account_balance registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) /obj/item/card/id/examine(mob/user) . = ..() if(registered_account) . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." . += "There's more information below, you can look again to take a closer look..." /obj/item/card/id/examine_more(mob/user) var/list/msg = list("You examine [src] closer, and note the following...") if(registered_age) msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]" if(mining_points) msg += "There's [mining_points] mining equipment redemption point\s loaded onto this card." if(registered_account) msg += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." if(registered_account.account_job) var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) if(D) msg += "The [D.account_holder] reports a balance of [D.account_balance] cr." msg += "Alt-Click the ID to pull money from the linked account in the form of holochips." msg += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." if(registered_account.civilian_bounty) msg += "There is an active civilian bounty." msg += "[registered_account.bounty_text()]" msg += "Quantity: [registered_account.bounty_num()]" msg += "Reward: [registered_account.bounty_value()]" if(registered_account.account_holder == user.real_name) msg += "If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number." else msg += "There is no registered account linked to this card. Alt-Click to add one." return msg /obj/item/card/id/GetAccess() return access /obj/item/card/id/GetID() return src /obj/item/card/id/RemoveID() return src /// Called on COMSIG_ATOM_UPDATED_ICON. Updates the visuals of the wallet this card is in. /obj/item/card/id/proc/update_in_wallet() SIGNAL_HANDLER if(istype(loc, /obj/item/storage/wallet)) var/obj/item/storage/wallet/powergaming = loc if(powergaming.front_id == src) powergaming.update_label() powergaming.update_appearance() /// Updates the name based on the card's vars and state. /obj/item/card/id/proc/update_label() var/blank = !registered_name name = "[blank ? initial(name) : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" /obj/item/card/id/away name = "\proper a perfectly generic identification card" desc = "A perfectly generic identification card. Looks like it could use some flavor." trim = /datum/id_trim/away icon_state = "retro" registered_age = null /obj/item/card/id/away/hotel name = "Staff ID" desc = "A staff ID used to access the hotel's doors." trim = /datum/id_trim/away/hotel /obj/item/card/id/away/hotel/securty name = "Officer ID" trim = /datum/id_trim/away/hotel/security /obj/item/card/id/away/old name = "\proper a perfectly generic identification card" desc = "A perfectly generic identification card. Looks like it could use some flavor." /obj/item/card/id/away/old/sec name = "Charlie Station Security Officer's ID card" desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"." trim = /datum/id_trim/away/old/sec /obj/item/card/id/away/old/sci name = "Charlie Station Scientist's ID card" desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"." trim = /datum/id_trim/away/old/sci /obj/item/card/id/away/old/eng name = "Charlie Station Engineer's ID card" desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"." trim = /datum/id_trim/away/old/eng /obj/item/card/id/away/old/apc name = "APC Access ID" desc = "A special ID card that allows access to APC terminals." trim = /datum/id_trim/away/old/apc /obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin name = "bunker access ID" /obj/item/card/id/departmental_budget name = "departmental card (ERROR)" desc = "Provides access to the departmental budget." icon_state = "budgetcard" var/department_ID = ACCOUNT_CIV var/department_name = ACCOUNT_CIV_NAME registered_age = null /obj/item/card/id/departmental_budget/Initialize() . = ..() var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID) if(B) registered_account = B if(!B.bank_cards.Find(src)) B.bank_cards += src name = "departmental card ([department_name])" desc = "Provides access to the [department_name]." SSeconomy.dep_cards += src /obj/item/card/id/departmental_budget/Destroy() SSeconomy.dep_cards -= src return ..() /obj/item/card/id/departmental_budget/update_label() return /obj/item/card/id/departmental_budget/car department_ID = ACCOUNT_CAR department_name = ACCOUNT_CAR_NAME icon_state = "car_budget" //saving up for a new tesla /obj/item/card/id/departmental_budget/AltClick(mob/living/user) registered_account.bank_card_talk("Withdrawing is not compatible with this card design.", TRUE) //prevents the vault bank machine being useless and putting money from the budget to your card to go over personal crates /obj/item/card/id/advanced name = "identification card" desc = "A card used to provide ID and determine access across the station. Has an integrated digital display and advanced microchips." icon_state = "card_grey" worn_icon_state = "card_grey" wildcard_slots = WILDCARD_LIMIT_GREY /// An overlay icon state for when the card is assigned to a name. Usually manifests itself as a little scribble to the right of the job icon. var/assigned_icon_state = "assigned" /// Cached icon that has been built for this card. var/icon/cached_flat_icon /// If this is set, will manually override the icon file for the trim. Intended for admins to VV edit and chameleon ID cards. var/trim_icon_override /// If this is set, will manually override the icon state for the trim. Intended for admins to VV edit and chameleon ID cards. var/trim_state_override /obj/item/card/id/advanced/get_icon_source() return get_cached_flat_icon() /// If no cached_flat_icon exists, this proc creates it. This proc then returns the cached_flat_icon. /obj/item/card/id/advanced/proc/get_cached_flat_icon() if(!cached_flat_icon) cached_flat_icon = getFlatIcon(src) return cached_flat_icon /obj/item/card/id/advanced/get_examine_string(mob/user, thats = FALSE) return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat /obj/item/card/id/advanced/update_overlays() . = ..() cached_flat_icon = null if(registered_name && registered_name != "Captain") . += mutable_appearance(icon, assigned_icon_state) var/trim_icon_file = trim_icon_override ? trim_icon_override : trim?.trim_icon var/trim_icon_state = trim_state_override ? trim_state_override : trim?.trim_state if(!trim_icon_file || !trim_icon_state) return . += mutable_appearance(trim_icon_file, trim_icon_state) /obj/item/card/id/advanced/silver name = "silver identification card" desc = "A silver card which shows honour and dedication." icon_state = "card_silver" worn_icon_state = "card_silver" inhand_icon_state = "silver_id" wildcard_slots = WILDCARD_LIMIT_SILVER /datum/id_trim/maint_reaper access = list(ACCESS_MAINT_TUNNELS) trim_state = "trim_janitor" assignment = "Reaper" /obj/item/card/id/advanced/silver/reaper name = "Thirteen's ID Card (Reaper)" trim = /datum/id_trim/maint_reaper registered_name = "Thirteen" /obj/item/card/id/advanced/gold name = "gold identification card" desc = "A golden card which shows power and might." icon_state = "card_gold" worn_icon_state = "card_gold" inhand_icon_state = "gold_id" wildcard_slots = WILDCARD_LIMIT_GOLD /obj/item/card/id/advanced/gold/captains_spare name = "captain's spare ID" desc = "The spare ID of the High Lord himself." 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 ..() /obj/item/card/id/advanced/centcom name = "\improper CentCom ID" desc = "An ID straight from Central Command." icon_state = "card_centcom" worn_icon_state = "card_centcom" assigned_icon_state = "assigned_centcom" registered_name = "Central Command" registered_age = null trim = /datum/id_trim/centcom wildcard_slots = WILDCARD_LIMIT_CENTCOM /obj/item/card/id/advanced/centcom/ert name = "\improper CentCom ID" desc = "An ERT ID card." registered_age = null registered_name = "Emergency Response Intern" trim = /datum/id_trim/centcom/ert /obj/item/card/id/advanced/centcom/ert registered_name = "Emergency Response Team Commander" trim = /datum/id_trim/centcom/ert/commander /obj/item/card/id/advanced/centcom/ert/security registered_name = "Security Response Officer" trim = /datum/id_trim/centcom/ert/security /obj/item/card/id/advanced/centcom/ert/engineer registered_name = "Engineering Response Officer" trim = /datum/id_trim/centcom/ert/engineer /obj/item/card/id/advanced/centcom/ert/medical registered_name = "Medical Response Officer" trim = /datum/id_trim/centcom/ert/medical /obj/item/card/id/advanced/centcom/ert/chaplain registered_name = "Religious Response Officer" trim = /datum/id_trim/centcom/ert/chaplain /obj/item/card/id/advanced/centcom/ert/janitor registered_name = "Janitorial Response Officer" trim = /datum/id_trim/centcom/ert/janitor /obj/item/card/id/advanced/centcom/ert/clown registered_name = "Entertainment Response Officer" trim = /datum/id_trim/centcom/ert/clown /obj/item/card/id/advanced/black name = "black identification card" desc = "This card is telling you one thing and one thing alone. The person holding this card is an utter badass." icon_state = "card_black" worn_icon_state = "card_black" assigned_icon_state = "assigned_syndicate" wildcard_slots = WILDCARD_LIMIT_GOLD /obj/item/card/id/advanced/black/deathsquad name = "\improper Death Squad ID" desc = "A Death Squad ID card." registered_name = "Death Commando" trim = /datum/id_trim/centcom/deathsquad wildcard_slots = WILDCARD_LIMIT_DEATHSQUAD /obj/item/card/id/advanced/black/syndicate_command name = "syndicate ID card" desc = "An ID straight from the Syndicate." registered_name = "Syndicate" registered_age = null trim = /datum/id_trim/syndicom wildcard_slots = WILDCARD_LIMIT_SYNDICATE /obj/item/card/id/advanced/black/syndicate_command/crew_id name = "syndicate ID card" desc = "An ID straight from the Syndicate." registered_name = "Syndicate" trim = /datum/id_trim/syndicom/crew /obj/item/card/id/advanced/black/syndicate_command/captain_id name = "syndicate captain ID card" desc = "An ID straight from the Syndicate." registered_name = "Syndicate" trim = /datum/id_trim/syndicom/captain /obj/item/card/id/advanced/debug name = "\improper Debug ID" desc = "A debug ID card. Has ALL the all access, you really shouldn't have this." icon_state = "card_centcom" worn_icon_state = "card_centcom" assigned_icon_state = "assigned_centcom" trim = /datum/id_trim/admin wildcard_slots = WILDCARD_LIMIT_ADMIN /obj/item/card/id/advanced/debug/Initialize() . = ..() registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) /obj/item/card/id/advanced/prisoner name = "prisoner ID card" desc = "You are a number, you are not a free man." icon_state = "card_prisoner" worn_icon_state = "card_prisoner" inhand_icon_state = "orange-id" lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' registered_name = "Scum" registered_age = null trim = /datum/id_trim/job/prisoner wildcard_slots = WILDCARD_LIMIT_PRISONER /// Number of gulag points required to earn freedom. var/goal = 0 /// Number of gulag points earned. var/points = 0 // SKYRAT EDIT: Start - Genpop IDs access = list(ACCESS_ENTER_GENPOP) var/sentence = 0 //When world.time is greater than this number, the card will have its ACCESS_ENTER_GENPOP access replaced with ACCESS_LEAVE_GENPOP the next time it's checked, unless this value is 0/null var/crime= "\[REDACTED\]" /obj/item/card/id/advanced/prisoner/GetAccess() if((sentence && world.time >= sentence) || (goal && points >= goal)) access = list(ACCESS_LEAVE_GENPOP) return ..() /obj/item/card/id/advanced/prisoner/examine(mob/user) . = ..() if(sentence && world.time < sentence) to_chat(user, "You're currently serving a sentence for [crime]. [DisplayTimeText(sentence - world.time)] left.") else if(goal) to_chat(user, "You have accumulated [points] out of the [goal] points you need for freedom.") else if(!sentence) to_chat(user, "You are currently serving a permanent sentence for [crime].") else to_chat(user, "Your sentence is up! You're free!") /obj/item/card/id/advanced/prisoner/process() if(!sentence) STOP_PROCESSING(SSobj, src) return if(world.time >= sentence) if(prob(90)) playsound(loc, 'sound/machines/ping.ogg', 50, 1) if(isliving(loc)) to_chat(loc, "[src] buzzes: You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.") else playsound(loc, 'modular_skyrat/modules/mapping/code/quest_succeeded.ogg', 50, 1) if(isliving(loc)) to_chat(loc, "[src]Quest Completed! Serve your prison sentence. You may now leave the prison through the turnstiles and return this ID to the locker to retrieve your belongings.") STOP_PROCESSING(SSobj, src) return // SKYRAT EDIT: End - Genpop IDs /obj/item/card/id/advanced/prisoner/attack_self(mob/user) to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") /obj/item/card/id/advanced/prisoner/one name = "Prisoner #13-001" registered_name = "Prisoner #13-001" trim = /datum/id_trim/job/prisoner/one /obj/item/card/id/advanced/prisoner/two name = "Prisoner #13-002" registered_name = "Prisoner #13-002" trim = /datum/id_trim/job/prisoner/two /obj/item/card/id/advanced/prisoner/three name = "Prisoner #13-003" registered_name = "Prisoner #13-003" trim = /datum/id_trim/job/prisoner/three /obj/item/card/id/advanced/prisoner/four name = "Prisoner #13-004" registered_name = "Prisoner #13-004" trim = /datum/id_trim/job/prisoner/four /obj/item/card/id/advanced/prisoner/five name = "Prisoner #13-005" registered_name = "Prisoner #13-005" trim = /datum/id_trim/job/prisoner/five /obj/item/card/id/advanced/prisoner/six name = "Prisoner #13-006" registered_name = "Prisoner #13-006" trim = /datum/id_trim/job/prisoner/six /obj/item/card/id/advanced/prisoner/seven name = "Prisoner #13-007" registered_name = "Prisoner #13-007" trim = /datum/id_trim/job/prisoner/seven /obj/item/card/id/advanced/mining name = "mining ID" trim = /datum/id_trim/job/shaft_miner/spare /obj/item/card/id/advanced/highlander name = "highlander ID" registered_name = "Highlander" desc = "There can be only one!" icon_state = "card_black" worn_icon_state = "card_black" assigned_icon_state = "assigned_syndicate" trim = /datum/id_trim/highlander wildcard_slots = WILDCARD_LIMIT_ADMIN /obj/item/card/id/advanced/chameleon name = "agent card" desc = "A highly advanced chameleon ID card. Touch this card on another ID card to choose which accesses to copy." wildcard_slots = WILDCARD_LIMIT_CHAMELEON /// Have we set a custom name and job assignment, or will we use what we're given when we chameleon change? var/forged = FALSE /// Anti-metagaming protections. If TRUE, anyone can change the ID card's details. If FALSE, only syndicate agents can. var/anyone = FALSE /// Weak ref to the ID card we're currently attempting to steal access from. var/datum/weakref/theft_target /obj/item/card/id/advanced/chameleon/Initialize() . = ..() var/datum/action/item_action/chameleon/change/id/chameleon_card_action = new(src) chameleon_card_action.chameleon_type = /obj/item/card/id/advanced chameleon_card_action.chameleon_name = "ID Card" chameleon_card_action.initialize_disguises() /obj/item/card/id/advanced/chameleon/Destroy() theft_target = null . = ..() /obj/item/card/id/advanced/chameleon/afterattack(obj/item/O, mob/user, proximity) if(!proximity) return if(istype(O, /obj/item/card/id)) theft_target = WEAKREF(O) ui_interact(user) return return ..() /obj/item/card/id/advanced/chameleon/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChameleonCard", name) ui.open() /obj/item/card/id/advanced/chameleon/ui_static_data(mob/user) var/list/data = list() data["wildcardFlags"] = SSid_access.wildcard_flags_by_wildcard data["accessFlagNames"] = SSid_access.access_flag_string_by_flag data["accessFlags"] = SSid_access.flags_by_access return data /obj/item/card/id/advanced/chameleon/ui_data(mob/user) var/list/data = list() data["showBasic"] = FALSE var/list/regions = list() var/obj/item/card/id/target_card = theft_target.resolve() if(target_card) var/list/tgui_region_data = SSid_access.all_region_access_tgui for(var/region in SSid_access.station_regions) regions += tgui_region_data[region] data["accesses"] = regions data["ourAccess"] = access data["ourTrimAccess"] = trim ? trim.access : list() data["theftAccess"] = target_card.access.Copy() data["wildcardSlots"] = wildcard_slots data["selectedList"] = access data["trimAccess"] = list() return data /obj/item/card/id/advanced/chameleon/ui_act(action, list/params) . = ..() if(.) return var/obj/item/card/id/target_card = theft_target?.resolve() if(QDELETED(target_card)) to_chat(usr, "The ID card you were attempting to scan is no longer in range.") target_card = null return TRUE // Wireless ID theft! var/turf/our_turf = get_turf(src) var/turf/target_turf = get_turf(target_card) if(!our_turf.Adjacent(target_turf)) to_chat(usr, "The ID card you were attempting to scan is no longer in range.") target_card = null return TRUE switch(action) if("mod_access") var/access_type = params["access_target"] var/try_wildcard = params["access_wildcard"] if(access_type in access) remove_access(list(access_type)) LOG_ID_ACCESS_CHANGE(usr, src, "removed [SSid_access.get_access_desc(access_type)]") return TRUE if(!(access_type in target_card.access)) to_chat(usr, "ID error: ID card rejected your attempted access modification.") LOG_ID_ACCESS_CHANGE(usr, src, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]") return TRUE if(!can_add_wildcards(list(access_type), try_wildcard)) to_chat(usr, "ID error: ID card rejected your attempted access modification.") LOG_ID_ACCESS_CHANGE(usr, src, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]") return TRUE if(!add_access(list(access_type), try_wildcard)) to_chat(usr, "ID error: ID card rejected your attempted access modification.") LOG_ID_ACCESS_CHANGE(usr, src, "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(usr)] just added [SSid_access.get_access_desc(access_type)] to an ID card [ADMIN_VV(src)] [(registered_name) ? "belonging to [registered_name]." : "with no registered name."]") LOG_ID_ACCESS_CHANGE(usr, src, "added [SSid_access.get_access_desc(access_type)]") return TRUE /obj/item/card/id/advanced/chameleon/attack_self(mob/user) if(isliving(user) && user.mind) var/popup_input = alert(user, "Choose Action", "Agent ID", "Show", "Forge/Reset", "Change Account ID") if(user.incapacitated()) return if(!user.is_holding(src)) return if(popup_input == "Forge/Reset") if(!forged) var/input_name = stripped_input(user, "What name would you like to put on this card? Leave blank to randomise.", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name), MAX_NAME_LEN) input_name = sanitize_name(input_name) if(!input_name) // Invalid/blank names give a randomly generated one. if(user.gender == MALE) input_name = "[pick(GLOB.first_names_male)] [pick(GLOB.last_names)]" else if(user.gender == FEMALE) input_name = "[pick(GLOB.first_names_female)] [pick(GLOB.last_names)]" else input_name = "[pick(GLOB.first_names)] [pick(GLOB.last_names)]" registered_name = input_name var/change_trim = alert(user, "Adjust the appearance of your card's trim?", "Modify Trim", "Yes", "No") if(change_trim == "Yes") var/list/blacklist = typecacheof(type) + typecacheof(/obj/item/card/id/advanced/simple_bot) var/list/trim_list = list() for(var/trim_path in typesof(/datum/id_trim)) if(blacklist[trim_path]) continue 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 var/selected_trim_path selected_trim_path = input("Select trim to apply to your card.\nNote: This will not grant any trim accesses.", "Forge Trim", selected_trim_path) as null|anything in sortList(trim_list, /proc/cmp_typepaths_asc) if(selected_trim_path) SSid_access.apply_trim_to_chameleon_card(src, trim_list[selected_trim_path]) var/target_occupation = stripped_input(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_MESSAGE_LEN) if(target_occupation) assignment = target_occupation var/new_age = input(user, "Choose the ID's age:\n([AGE_MIN]-[AGE_MAX])", "Agent card age") as num|null if(new_age) registered_age = max(round(text2num(new_age)), 0) update_label() update_icon() forged = TRUE to_chat(user, "You successfully forge the ID card.") log_game("[key_name(user)] has forged \the [initial(name)] with name \"[registered_name]\", occupation \"[assignment]\" and trim \"[trim?.assignment]\".") if(!registered_account) if(ishuman(user)) var/mob/living/carbon/human/accountowner = user var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[accountowner.account_id]"] if(account) account.bank_cards += src registered_account = account to_chat(user, "Your account number has been automatically assigned.") return if(forged) registered_name = initial(registered_name) assignment = initial(assignment) SSid_access.remove_trim_from_card(src) log_game("[key_name(user)] has reset \the [initial(name)] named \"[src]\" to default.") update_label() update_icon() forged = FALSE to_chat(user, "You successfully reset the ID card.") return if (popup_input == "Change Account ID") set_new_account(user) return return ..() /// A special variant of the classic chameleon ID card which accepts all access. /obj/item/card/id/advanced/chameleon/black icon_state = "card_black" worn_icon_state = "card_black" assigned_icon_state = "assigned_syndicate" wildcard_slots = WILDCARD_LIMIT_GOLD /obj/item/card/id/advanced/engioutpost registered_name = "George 'Plastic' Miller" desc = "A card used to provide ID and determine access across the station. There's blood dripping from the corner. Ew." trim = /datum/id_trim/engioutpost registered_age = 47 /obj/item/card/id/advanced/simple_bot name = "simple bot ID card" desc = "An internal ID card used by the station's non-sentient bots. You should report this to a coder if you're holding it." wildcard_slots = WILDCARD_LIMIT_ADMIN