From 598952c1c2d3aeef73cbc37afe07e322cfc51bfb Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sun, 30 Jul 2023 03:09:06 +0200 Subject: [PATCH] [MIRROR] Refactors chameleon actions, Adds chameleon outfit saving, Adds chameleon scanner [MDB IGNORE] (#22773) * Refactors chameleon actions, Adds chameleon outfit saving, Adds chameleon scanner * Update cards_ids.dm * Update _glasses.dm * Delete chameleon.dm * Update _glasses.dm * Update _glasses.dm --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> --- .../signals_atom/signals_atom_x_act.dm | 4 +- code/datums/components/gps.dm | 5 +- code/datums/elements/empprotection.dm | 4 +- code/game/atoms.dm | 8 +- code/game/objects/items/cards_ids.dm | 39 +- code/game/objects/items/storage/holsters.dm | 18 +- .../game/objects/items/storage/uplink_kits.dm | 1 + code/modules/clothing/chameleon.dm | 1083 ----------------- .../clothing/chameleon/_chameleon_action.dm | 222 ++++ .../clothing/chameleon/_chameleon_outfit.dm | 119 ++ .../chameleon/chameleon_action_subtypes.dm | 299 +++++ .../clothing/chameleon/chameleon_drone.dm | 52 + .../clothing/chameleon/chameleon_gun.dm | 157 +++ .../clothing/chameleon/chameleon_scanner.dm | 116 ++ .../chameleon/generic_chameleon_clothing.dm | 326 +++++ code/modules/clothing/glasses/_glasses.dm | 24 +- code/modules/clothing/glasses/hud.dm | 21 +- .../library/skill_learning/skillchip.dm | 4 +- .../mob/living/carbon/carbon_update_icons.dm | 2 + .../carbon/human/species_types/ethereal.dm | 4 +- .../friendly/drone/visuals_icons.dm | 9 + code/modules/mob/mob_defines.dm | 3 - code/modules/mod/modules/modules_general.dm | 9 +- code/modules/paperwork/paperwork.dm | 31 +- tgstation.dme | 8 +- 25 files changed, 1376 insertions(+), 1192 deletions(-) delete mode 100644 code/modules/clothing/chameleon.dm create mode 100644 code/modules/clothing/chameleon/_chameleon_action.dm create mode 100644 code/modules/clothing/chameleon/_chameleon_outfit.dm create mode 100644 code/modules/clothing/chameleon/chameleon_action_subtypes.dm create mode 100644 code/modules/clothing/chameleon/chameleon_drone.dm create mode 100644 code/modules/clothing/chameleon/chameleon_gun.dm create mode 100644 code/modules/clothing/chameleon/chameleon_scanner.dm create mode 100644 code/modules/clothing/chameleon/generic_chameleon_clothing.dm diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index 33b279cc3f8..e1811a24af3 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -4,7 +4,9 @@ ///from the [EX_ACT] wrapper macro: (severity, target) #define COMSIG_ATOM_EX_ACT "atom_ex_act" -///from base of atom/emp_act(): (severity) +///from base of atom/emp_act(): (severity). return EMP protection flags +#define COMSIG_ATOM_PRE_EMP_ACT "atom_emp_act" +///from base of atom/emp_act(): (severity, protection) #define COMSIG_ATOM_EMP_ACT "atom_emp_act" ///from base of atom/fire_act(): (exposed_temperature, exposed_volume) #define COMSIG_ATOM_FIRE_ACT "atom_fire_act" diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm index b19828edd90..ceee193bf4a 100644 --- a/code/datums/components/gps.dm +++ b/code/datums/components/gps.dm @@ -66,9 +66,10 @@ GLOBAL_LIST_EMPTY(GPS_list) examine_list += span_notice("Alt-click to switch it [tracking ? "off":"on"].") ///Called on COMSIG_ATOM_EMP_ACT -/datum/component/gps/item/proc/on_emp_act(datum/source, severity) +/datum/component/gps/item/proc/on_emp_act(datum/source, severity, protection) SIGNAL_HANDLER - + if(protection & EMP_PROTECT_SELF) + return emped = TRUE var/atom/A = parent A.cut_overlay("working") diff --git a/code/datums/elements/empprotection.dm b/code/datums/elements/empprotection.dm index ab69c48988d..2955551fd6d 100644 --- a/code/datums/elements/empprotection.dm +++ b/code/datums/elements/empprotection.dm @@ -8,10 +8,10 @@ if(. == ELEMENT_INCOMPATIBLE || !isatom(target)) return ELEMENT_INCOMPATIBLE flags = _flags - RegisterSignal(target, COMSIG_ATOM_EMP_ACT, PROC_REF(getEmpFlags)) + RegisterSignal(target, COMSIG_ATOM_PRE_EMP_ACT, PROC_REF(getEmpFlags)) /datum/element/empprotection/Detach(atom/target) - UnregisterSignal(target, COMSIG_ATOM_EMP_ACT) + UnregisterSignal(target, COMSIG_ATOM_PRE_EMP_ACT) return ..() /datum/element/empprotection/proc/getEmpFlags(datum/source, severity) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index a67a2931fbe..7ca3af957a0 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -632,17 +632,19 @@ /** * React to an EMP of the given severity * - * Default behaviour is to send the [COMSIG_ATOM_EMP_ACT] signal + * Default behaviour is to send the [COMSIG_ATOM_PRE_EMP_ACT] and [COMSIG_ATOM_EMP_ACT] signal * - * If the signal does not return protection, and there are attached wires then we call + * If the pre-signal does not return protection, and there are attached wires then we call * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires * * We then return the protection value */ /atom/proc/emp_act(severity) - var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) + var/protection = SEND_SIGNAL(src, COMSIG_ATOM_PRE_EMP_ACT, severity) if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) wires.emp_pulse() + + SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity, protection) return protection // Pass the protection value collected here upwards /** diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 01657674a6a..a471046ca9c 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1313,9 +1313,11 @@ /obj/item/card/id/advanced/chameleon name = "agent card" - desc = "A highly advanced chameleon ID card. Touch this card on another ID card or player to choose which accesses to copy. Has special magnetic properties which force it to the front of wallets." + desc = "A highly advanced chameleon ID card. Touch this card on another ID card or player to choose which accesses to copy. \ + Has special magnetic properties which force it to the front of wallets." trim = /datum/id_trim/chameleon wildcard_slots = WILDCARD_LIMIT_CHAMELEON_PLUS // SKYRAT EDIT - Original WILDCARD_LIMIT_CHAMELEON + actions_types = list(/datum/action/item_action/chameleon/change/id, /datum/action/item_action/chameleon/change/id_trim) /// Have we set a custom name and job assignment, or will we use what we're given when we chameleon change? var/forged = FALSE @@ -1326,38 +1328,35 @@ /obj/item/card/id/advanced/chameleon/Initialize(mapload) . = ..() - - 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() - add_item_action(chameleon_card_action) register_item_context() /obj/item/card/id/advanced/chameleon/Destroy() theft_target = null - . = ..() + return ..() -/obj/item/card/id/advanced/chameleon/afterattack(atom/target, mob/user, proximity) +/obj/item/card/id/advanced/chameleon/afterattack(atom/target, mob/user, proximity, click_parameters) + . = ..() if(!proximity) return if(isidcard(target)) theft_target = WEAKREF(target) ui_interact(user) - return AFTERATTACK_PROCESSED_ITEM - - return ..() + return . | AFTERATTACK_PROCESSED_ITEM /obj/item/card/id/advanced/chameleon/pre_attack_secondary(atom/target, mob/living/user, params) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return . + // If we're attacking a human, we want it to be covert. We're not ATTACKING them, we're trying // to sneakily steal their accesses by swiping our agent ID card near them. As a result, we // return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN to cancel any part of the following the attack chain. if(ishuman(target)) - to_chat(user, "You covertly start to scan [target] with \the [src], hoping to pick up a wireless ID card signal...") + target.balloon_alert(user, "scanning ID card...") if(!do_after(user, 2 SECONDS, target)) - to_chat(user, "The scan was interrupted.") + target.balloon_alert(user, "interrupted!") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/mob/living/carbon/human/human_target = target @@ -1365,11 +1364,11 @@ var/list/target_id_cards = human_target.get_all_contents_type(/obj/item/card/id) if(!length(target_id_cards)) - to_chat(user, "The scan failed to locate any ID cards.") + target.balloon_alert(user, "no IDs!") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/selected_id = pick(target_id_cards) - to_chat(user, "You successfully sync your [src] with \the [selected_id].") + target.balloon_alert(user, UNLINT("IDs synced")) theft_target = WEAKREF(selected_id) ui_interact(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN @@ -1377,7 +1376,7 @@ if(isitem(target)) var/obj/item/target_item = target - to_chat(user, "You covertly start to scan [target] with your [src], hoping to pick up a wireless ID card signal...") + target.balloon_alert(user, "scanning ID card...") var/list/target_id_cards = target_item.get_all_contents_type(/obj/item/card/id) @@ -1387,16 +1386,16 @@ target_id_cards |= target_item_id if(!length(target_id_cards)) - to_chat(user, "The scan failed to locate any ID cards.") + target.balloon_alert(user, "no IDs!") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/selected_id = pick(target_id_cards) - to_chat(user, "You successfully sync your [src] with \the [selected_id].") + target.balloon_alert(user, UNLINT("IDs synced")) theft_target = WEAKREF(selected_id) ui_interact(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - return ..() + return . /obj/item/card/id/advanced/chameleon/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/game/objects/items/storage/holsters.dm b/code/game/objects/items/storage/holsters.dm index de40bcc07e6..3dc42f79604 100644 --- a/code/game/objects/items/storage/holsters.dm +++ b/code/game/objects/items/storage/holsters.dm @@ -129,7 +129,7 @@ inhand_icon_state = "syndicate_holster" worn_icon_state = "syndicate_holster" w_class = WEIGHT_CLASS_NORMAL - var/datum/action/item_action/chameleon/change/chameleon_action + actions_types = list(/datum/action/item_action/chameleon/change/belt) /obj/item/storage/belt/holster/chameleon/Initialize(mapload) . = ..() @@ -155,22 +155,6 @@ atom_storage.silent = TRUE - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/storage/belt - chameleon_action.chameleon_name = "Belt" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/storage/belt/holster/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/storage/belt/holster/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - /obj/item/storage/belt/holster/nukie name = "operative holster" desc = "A deep shoulder holster capable of holding almost any form of firearm and its ammo." diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 0694f50a72a..936bc011d89 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -504,6 +504,7 @@ new /obj/item/stamp/chameleon(src) new /obj/item/modular_computer/pda/chameleon(src) new /obj/item/gun/energy/laser/chameleon(src) + new /obj/item/chameleon_scanner(src) //5*(2*4) = 5*8 = 45, 45 damage if you hit one person with all 5 stars. //Not counting the damage it will do while embedded (2*4 = 8, at 15% chance) diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm deleted file mode 100644 index 60c7e43316f..00000000000 --- a/code/modules/clothing/chameleon.dm +++ /dev/null @@ -1,1083 +0,0 @@ -#define EMP_RANDOMISE_TIME 300 - -/datum/action/item_action/chameleon/drone/randomise - name = "Randomise Headgear" - button_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "random" - -/datum/action/item_action/chameleon/drone/randomise/Trigger(trigger_flags) - if(!IsAvailable(feedback = TRUE)) - return - - // Damn our lack of abstract interfeces - if (istype(target, /obj/item/clothing/head/chameleon/drone)) - var/obj/item/clothing/head/chameleon/drone/X = target - X.chameleon_action.random_look(owner) - if (istype(target, /obj/item/clothing/mask/chameleon/drone)) - var/obj/item/clothing/mask/chameleon/drone/Z = target - Z.chameleon_action.random_look(owner) - - return 1 - - -/datum/action/item_action/chameleon/drone/togglehatmask - name = "Toggle Headgear Mode" - button_icon = 'icons/mob/actions/actions_silicon.dmi' - -/datum/action/item_action/chameleon/drone/togglehatmask/New() - ..() - - if (istype(target, /obj/item/clothing/head/chameleon/drone)) - button_icon_state = "drone_camogear_helm" - if (istype(target, /obj/item/clothing/mask/chameleon/drone)) - button_icon_state = "drone_camogear_mask" - -/datum/action/item_action/chameleon/drone/togglehatmask/Trigger(trigger_flags) - if(!IsAvailable(feedback = TRUE)) - return - - // No point making the code more complicated if no non-drone - // is ever going to use one of these - - var/mob/living/simple_animal/drone/D - - if(isdrone(owner)) - D = owner - else - return - - // The drone unEquip() proc sets head to null after dropping - // an item, so we need to keep a reference to our old headgear - // to make sure it's deleted. - var/obj/old_headgear = target - var/obj/new_headgear - - if(istype(old_headgear, /obj/item/clothing/head/chameleon/drone)) - new_headgear = new /obj/item/clothing/mask/chameleon/drone() - else if(istype(old_headgear, /obj/item/clothing/mask/chameleon/drone)) - new_headgear = new /obj/item/clothing/head/chameleon/drone() - else - to_chat(owner, span_warning("You shouldn't be able to toggle a camogear helmetmask if you're not wearing it.")) - if(new_headgear) - // Force drop the item in the headslot, even though - // it's has TRAIT_NODROP - D.dropItemToGround(target, TRUE) - qdel(old_headgear) - // where is `ITEM_SLOT_HEAD` defined? WHO KNOWS - D.equip_to_slot(new_headgear, ITEM_SLOT_HEAD) - return 1 - - -/datum/action/chameleon_outfit - name = "Select Chameleon Outfit" - button_icon_state = "chameleon_outfit" - var/list/outfit_options //By default, this list is shared between all instances. It is not static because if it were, subtypes would not be able to have their own. If you ever want to edit it, copy it first. - -/datum/action/chameleon_outfit/New() - ..() - initialize_outfits() - -/datum/action/chameleon_outfit/proc/initialize_outfits() - var/static/list/standard_outfit_options - if(!standard_outfit_options) - standard_outfit_options = list() - for(var/path in subtypesof(/datum/outfit/job)) - var/datum/outfit/O = path - standard_outfit_options[initial(O.name)] = path - sortTim(standard_outfit_options, GLOBAL_PROC_REF(cmp_text_asc)) - outfit_options = standard_outfit_options - -/datum/action/chameleon_outfit/Trigger(trigger_flags) - return select_outfit(owner) - -/datum/action/chameleon_outfit/proc/select_outfit(mob/user) - if(!user || !IsAvailable(feedback = TRUE)) - return FALSE - var/selected = tgui_input_list(user, "Select outfit to change into", "Chameleon Outfit", outfit_options) - if(isnull(selected)) - return FALSE - if(!IsAvailable(feedback = TRUE) || QDELETED(src) || QDELETED(user)) - return FALSE - if(isnull(outfit_options[selected])) - return FALSE - var/outfit_type = outfit_options[selected] - var/datum/outfit/job/O = new outfit_type() - var/list/outfit_types = O.get_chameleon_disguise_info() - var/datum/job/job_datum = SSjob.GetJobType(O.jobtype) - - for(var/V in user.chameleon_item_actions) - var/datum/action/item_action/chameleon/change/A = V - var/done = FALSE - for(var/T in outfit_types) - for(var/name in A.chameleon_list) - if(A.chameleon_list[name] == T) - A.apply_job_data(job_datum) - A.update_look(user, T) - outfit_types -= T - done = TRUE - break - if(done) - break - - //suit hoods - if(ishuman(user)) - var/mob/living/carbon/human/H = user - //make sure they are actually wearing the suit, not just holding it, and that they have a chameleon hat - if(istype(H.wear_suit, /obj/item/clothing/suit/chameleon) && istype(H.head, /obj/item/clothing/head/chameleon)) - var/helmet_type - if(ispath(O.suit, /obj/item/clothing/suit/hooded)) - var/obj/item/clothing/suit/hooded/hooded = O.suit - helmet_type = initial(hooded.hoodtype) - if(helmet_type) - var/obj/item/clothing/head/chameleon/hat = H.head - hat.chameleon_action.update_look(user, helmet_type) - qdel(O) - return TRUE - - -/datum/action/item_action/chameleon/change - name = "Chameleon Change" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED - var/list/chameleon_blacklist = list() //This is a typecache - var/list/chameleon_list = list() - var/chameleon_type = null - var/chameleon_name = "Item" - - var/emp_timer - -/datum/action/item_action/chameleon/change/Grant(mob/M) - if(M && (owner != M)) - if(!M.chameleon_item_actions) - M.chameleon_item_actions = list(src) - var/datum/action/chameleon_outfit/O = new /datum/action/chameleon_outfit() - O.Grant(M) - else - M.chameleon_item_actions |= src - ..() - -/datum/action/item_action/chameleon/change/Remove(mob/M) - if(M && (M == owner)) - LAZYREMOVE(M.chameleon_item_actions, src) - if(!LAZYLEN(M.chameleon_item_actions)) - var/datum/action/chameleon_outfit/O = locate(/datum/action/chameleon_outfit) in M.actions - qdel(O) - ..() - -/datum/action/item_action/chameleon/change/proc/initialize_disguises() - name = "Change [chameleon_name] Appearance" - build_all_button_icons() - - chameleon_blacklist |= typecacheof(target.type) - for(var/V in typesof(chameleon_type)) - if(ispath(V) && ispath(V, /obj/item)) - var/obj/item/I = V - if(chameleon_blacklist[V] || (initial(I.item_flags) & ABSTRACT) || !initial(I.icon_state)) - continue - var/chameleon_item_name = "[initial(I.name)] ([initial(I.icon_state)])" - chameleon_list[chameleon_item_name] = I - - -/datum/action/item_action/chameleon/change/proc/select_look(mob/user) - var/obj/item/picked_item - 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))) - if(isnull(picked_name)) - return - if(isnull(chameleon_list[picked_name])) - return - picked_item = chameleon_list[picked_name] - update_look(user, picked_item) - -/datum/action/item_action/chameleon/change/proc/random_look(mob/user) - var/picked_name = pick(chameleon_list) - // If a user is provided, then this item is in use, and we - // need to update our icons and stuff - - if(user) - update_look(user, chameleon_list[picked_name]) - - // Otherwise, it's likely a random initialisation, so we - // don't have to worry - - else - update_item(chameleon_list[picked_name]) - -/datum/action/item_action/chameleon/change/proc/update_look(mob/user, obj/item/picked_item) - if(istype(target, /obj/item/gun/energy/laser/chameleon)) - var/obj/item/gun/energy/laser/chameleon/chameleon_gun = target - chameleon_gun.set_chameleon_disguise(picked_item) - if(isliving(user)) - var/mob/living/C = user - if(C.stat != CONSCIOUS) - return - - update_item(picked_item) - var/obj/item/thing = target - thing.update_slot_icon() - build_all_button_icons() - -/datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item) - var/atom/atom_target = target - atom_target.name = initial(picked_item.name) - atom_target.desc = initial(picked_item.desc) - atom_target.icon_state = initial(picked_item.icon_state) - if(isitem(atom_target)) - var/obj/item/item_target = target - item_target.worn_icon = initial(picked_item.worn_icon) - item_target.lefthand_file = initial(picked_item.lefthand_file) - item_target.righthand_file = initial(picked_item.righthand_file) - if(initial(picked_item.greyscale_colors)) - if(initial(picked_item.greyscale_config_worn)) - item_target.worn_icon = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config_worn), initial(picked_item.greyscale_colors)) - if(initial(picked_item.greyscale_config_inhand_left)) - item_target.lefthand_file = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config_inhand_left), initial(picked_item.greyscale_colors)) - if(initial(picked_item.greyscale_config_inhand_right)) - item_target.righthand_file = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config_inhand_right), initial(picked_item.greyscale_colors)) - item_target.worn_icon_state = initial(picked_item.worn_icon_state) - item_target.inhand_icon_state = initial(picked_item.inhand_icon_state) - if(isclothing(item_target) && ispath(picked_item, /obj/item/clothing)) - var/obj/item/clothing/clothing_target = item_target - var/obj/item/clothing/picked_clothing = picked_item - clothing_target.flags_cover = initial(picked_clothing.flags_cover) - if(initial(picked_item.greyscale_config) && initial(picked_item.greyscale_colors)) - atom_target.icon = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config), initial(picked_item.greyscale_colors)) - else - atom_target.icon = initial(picked_item.icon) - -/datum/action/item_action/chameleon/change/Trigger(trigger_flags) - if(!IsAvailable(feedback = TRUE)) - return - - select_look(owner) - return 1 - -/datum/action/item_action/chameleon/change/proc/emp_randomise(amount = EMP_RANDOMISE_TIME) - START_PROCESSING(SSprocessing, src) - random_look(owner) - - var/new_value = world.time + amount - if(new_value > emp_timer) - emp_timer = new_value - -/datum/action/item_action/chameleon/change/process() - if(world.time > emp_timer) - STOP_PROCESSING(SSprocessing, src) - return - random_look(owner) - -/datum/action/item_action/chameleon/change/proc/apply_job_data(datum/job/job_datum) - return - -/datum/action/item_action/chameleon/change/id/update_item(obj/item/picked_item) - ..() - var/obj/item/card/id/advanced/chameleon/agent_card = target - if(istype(agent_card)) - var/obj/item/card/id/copied_card = picked_item - - // If the outfit comes with a special trim override, we'll steal some stuff from that. - var/new_trim = initial(copied_card.trim) - - if(new_trim) - SSid_access.apply_trim_to_chameleon_card(agent_card, new_trim, TRUE) - - // If the ID card hasn't been forged, we'll check if there has been an assignment set already by any new trim. - // 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.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.update_label() - agent_card.update_icon() - -/datum/action/item_action/chameleon/change/id/apply_job_data(datum/job/job_datum) - ..() - var/obj/item/card/id/advanced/chameleon/agent_card = target - if(istype(agent_card) && istype(job_datum)) - agent_card.forged = TRUE - - // job_outfit is going to be a path. - var/datum/outfit/job/job_outfit = job_datum.outfit - if(!job_outfit) - return - - // copied_card is also going to be a path. - var/obj/item/card/id/copied_card = initial(job_outfit.id) - if(!copied_card) - return - - // If the outfit comes with a special trim override, we'll use that. Otherwise, use the card's default trim. Failing that, no trim at all. - var/new_trim = initial(job_outfit.id_trim) ? initial(job_outfit.id_trim) : initial(copied_card.trim) - - if(new_trim) - SSid_access.apply_trim_to_chameleon_card(agent_card, new_trim, FALSE) - else - agent_card.assignment = job_datum.title - - 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.update_label() - agent_card.update_icon() - -/datum/action/item_action/chameleon/change/id_trim/initialize_disguises() - name = "Change [chameleon_name] Appearance" - build_all_button_icons() - - chameleon_blacklist |= typecacheof(target.type) - for(var/trim_path in typesof(chameleon_type)) - if(ispath(trim_path) && ispath(trim_path, /datum/id_trim)) - if(chameleon_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/chameleon_item_name = "[trim.assignment] ([trim.trim_state])" - chameleon_list[chameleon_item_name] = trim_path - -/datum/action/item_action/chameleon/change/id_trim/update_item(picked_trim_path) - var/obj/item/card/id/advanced/chameleon/agent_card = target - - if(istype(agent_card)) - SSid_access.apply_trim_to_chameleon_card(agent_card, picked_trim_path, TRUE) - - agent_card.update_label() - agent_card.update_icon() - -/datum/action/item_action/chameleon/change/tablet/update_item(obj/item/picked_item) - ..() - var/obj/item/modular_computer/pda/agent_pda = target - if(istype(agent_pda)) - agent_pda.update_appearance() - -/datum/action/item_action/chameleon/change/tablet/apply_job_data(datum/job/job_datum) - ..() - var/obj/item/modular_computer/pda/agent_pda = target - if(istype(agent_pda) && istype(job_datum)) - agent_pda.saved_job = job_datum.title - - -/obj/item/clothing/under/chameleon -//starts off as black - name = "black jumpsuit" - desc = "It's a plain jumpsuit. It has a small dial on the wrist." - icon_state = "jumpsuit" - greyscale_colors = "#3f3f3f" - greyscale_config = /datum/greyscale_config/jumpsuit - greyscale_config_worn = /datum/greyscale_config/jumpsuit/worn - greyscale_config_inhand_left = /datum/greyscale_config/jumpsuit/inhand_left - greyscale_config_inhand_right = /datum/greyscale_config/jumpsuit/inhand_right - sensor_mode = SENSOR_OFF //Hey who's this guy on the Syndicate Shuttle?? - random_sensor = FALSE - resistance_flags = NONE - can_adjust = FALSE - armor_type = /datum/armor/clothing_under/chameleon - - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // SKYRAT EDIT - special_desc = "A chameleon jumpsuit employed by the Syndicate in infiltration operations." // SKYRAT EDIT - - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/clothing_under/chameleon - melee = 10 - bullet = 10 - laser = 10 - fire = 50 - acid = 50 - wound = 10 - -/obj/item/clothing/under/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/under - chameleon_action.chameleon_name = "Jumpsuit" - chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/under/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/under/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/under/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/suit/chameleon - name = "armor" - desc = "A slim armored vest that protects against most types of damage." - icon_state = "armor" - icon = 'icons/obj/clothing/suits/armor.dmi' - worn_icon = 'icons/mob/clothing/suits/armor.dmi' - inhand_icon_state = "armor" - blood_overlay_type = "armor" - resistance_flags = NONE - armor_type = /datum/armor/suit_chameleon - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // SKYRAT EDIT - special_desc = "A chameleon vest employed by the Syndicate in infiltration operations." // SKYRAT EDIT - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/suit_chameleon - melee = 10 - bullet = 10 - laser = 10 - fire = 50 - acid = 50 - -/obj/item/clothing/suit/chameleon/Initialize(mapload) - . = ..() - allowed = GLOB.security_vest_allowed //should at least act like a vest - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/suit - chameleon_action.chameleon_name = "Suit" - chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/suit/armor/abductor, /obj/item/clothing/suit/changeling), only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/suit/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/suit/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/suit/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/glasses/chameleon - name = "Optical Meson Scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting condition." - icon_state = "meson" - inhand_icon_state = "meson" - resistance_flags = NONE - armor_type = /datum/armor/glasses_chameleon - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // SKYRAT EDIT - special_desc = "Chameleon glasses employed by the Syndicate in infiltration operations." // SKYRAT EDIT - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/glasses_chameleon - melee = 10 - bullet = 10 - laser = 10 - fire = 50 - acid = 50 - -/obj/item/clothing/glasses/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/glasses/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/glasses/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/glasses/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/gloves/chameleon - desc = "These gloves provide protection against electric shock." - name = "insulated gloves" - icon_state = "yellow" - inhand_icon_state = "ygloves" - greyscale_colors = null - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A pair of chameleon gloves employed by the Syndicate in infiltration operations." // Skyrat edit - resistance_flags = NONE - armor_type = /datum/armor/gloves_chameleon - - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/gloves_chameleon - melee = 10 - bullet = 10 - laser = 10 - fire = 50 - acid = 50 - -/obj/item/clothing/gloves/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/gloves - chameleon_action.chameleon_name = "Gloves" - chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/gloves, /obj/item/clothing/gloves/color, /obj/item/clothing/gloves/changeling), only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/gloves/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/gloves/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/gloves/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/head/chameleon - name = "grey cap" - desc = "It's a baseball hat in a tasteful grey colour." - icon = 'icons/obj/clothing/head/hats.dmi' - worn_icon = 'icons/mob/clothing/head/hats.dmi' - icon_state = "greysoft" - resistance_flags = NONE - armor_type = /datum/armor/head_chameleon - - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/head_chameleon - melee = 5 - bullet = 5 - laser = 5 - fire = 50 - acid = 50 - -/obj/item/clothing/head/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/head - chameleon_action.chameleon_name = "Hat" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/head/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/head/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/head/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/head/chameleon/drone - // The camohat, I mean, holographic hat projection, is part of the - // drone itself. - armor_type = /datum/armor/none - // which means it offers no protection, it's just air and light - -/obj/item/clothing/head/chameleon/drone/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - chameleon_action.random_look() - var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.build_all_button_icons() - var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.build_all_button_icons() - -/obj/item/clothing/mask/chameleon - name = "gas mask" - desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate - icon_state = "gas_alt" - inhand_icon_state = "gas_alt" - resistance_flags = NONE - armor_type = /datum/armor/mask_chameleon - clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A chameleon mask employed by the Syndicate in infiltration operations, frequently used to mimic the voice of other crewmembers using fake id's." // Skyrat edit - - var/voice_change = 1 ///This determines if the voice changer is on or off. - - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/mask_chameleon - melee = 5 - bullet = 5 - laser = 5 - bio = 100 - fire = 50 - acid = 50 - -/obj/item/clothing/mask/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/mask - chameleon_action.chameleon_name = "Mask" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/mask/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/mask/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/mask/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/mask/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/mask/chameleon/attack_self(mob/user) - voice_change = !voice_change - to_chat(user, span_notice("The voice changer is now [voice_change ? "on" : "off"]!")) - - -/obj/item/clothing/mask/chameleon/drone - //Same as the drone chameleon hat, undroppable and no protection - armor_type = /datum/armor/none - // Can drones use the voice changer part? Let's not find out. - voice_change = 0 - -/obj/item/clothing/mask/chameleon/drone/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - chameleon_action.random_look() - var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.build_all_button_icons() - var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.build_all_button_icons() - -/obj/item/clothing/mask/chameleon/drone/attack_self(mob/user) - to_chat(user, span_notice("[src] does not have a voice changer.")) - -/obj/item/clothing/shoes/chameleon - name = "black shoes" - desc = "A pair of black shoes." - icon_state = "sneakers" - inhand_icon_state = "sneakers_back" - greyscale_colors = "#545454#ffffff" - greyscale_config = /datum/greyscale_config/sneakers - greyscale_config_worn = /datum/greyscale_config/sneakers/worn - greyscale_config_inhand_left = /datum/greyscale_config/sneakers/inhand_left - greyscale_config_inhand_right = /datum/greyscale_config/sneakers/inhand_right - resistance_flags = NONE - armor_type = /datum/armor/shoes_chameleon - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A pair of chameleon shoes employed by the Syndicate in infiltration operations." // Skyrat edit - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/shoes_chameleon - melee = 10 - bullet = 10 - laser = 10 - bio = 90 - fire = 50 - acid = 50 - -/obj/item/clothing/shoes/chameleon/Initialize(mapload) - . = ..() - - create_storage(storage_type = /datum/storage/pockets/shoes) - - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/shoes - chameleon_action.chameleon_name = "Shoes" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/shoes/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/shoes/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/clothing/shoes/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/shoes/chameleon/noslip - clothing_traits = list(TRAIT_NO_SLIP_WATER) - can_be_bloody = FALSE - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A pair of chameleon shoes with an anti-slip coating employed by the Syndicate in infiltration operations." // Skyrat edit - - -/obj/item/clothing/shoes/chameleon/noslip/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/storage/backpack/chameleon - name = "backpack" - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A chameleon backpack employed by the Syndicate in infiltration operations." // Skyrat edit - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/storage/backpack/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/storage/backpack - chameleon_action.chameleon_name = "Backpack" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/storage/backpack/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/storage/backpack/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/storage/backpack/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/storage/belt/chameleon - name = "toolbelt" - desc = "Holds tools." - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A chameleon belt employed by the Syndicate in infiltration operations." // Skyrat edit - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/storage/belt/chameleon/Initialize(mapload) - . = ..() - - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/storage/belt - chameleon_action.chameleon_name = "Belt" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - - atom_storage.silent = TRUE - -/obj/item/storage/belt/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/storage/belt/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/storage/belt/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/radio/headset/chameleon - name = "radio headset" - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A chameleon headset employed by the Syndicate in infiltration operations." // Skyrat edit - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/radio/headset/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/radio/headset - chameleon_action.chameleon_name = "Headset" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/radio/headset/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/radio/headset/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/radio/headset/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/modular_computer/pda/chameleon - name = "tablet" - var/datum/action/item_action/chameleon/change/tablet/chameleon_action - -/obj/item/modular_computer/pda/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/modular_computer/pda - chameleon_action.chameleon_name = "tablet" - chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/modular_computer/pda/heads), only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/modular_computer/pda/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/modular_computer/pda/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/modular_computer/pda/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/stamp/chameleon - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/stamp/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/stamp - chameleon_action.chameleon_name = "Stamp" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/stamp/chameleon/Destroy() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/stamp/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/clothing/neck/chameleon - name = "black tie" - desc = "A neosilk clip-on tie." - icon_state = "detective" //we use this icon_state since the other ones are all generated by GAGS. - resistance_flags = NONE - armor_type = /datum/armor/neck_chameleon - w_class = WEIGHT_CLASS_SMALL - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // Skyrat edit - special_desc = "A chameleon tie employed by the Syndicate in infiltration operations." // Skyrat edit - - -/obj/item/clothing/neck/chameleon - var/datum/action/item_action/chameleon/change/chameleon_action - -/datum/armor/neck_chameleon - fire = 50 - acid = 50 - -/obj/item/clothing/neck/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/neck - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/neck/cloak/skill_reward) - chameleon_action.chameleon_name = "Neck Accessory" - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/neck/chameleon/Destroy() - qdel(chameleon_action) - return ..() - -/obj/item/clothing/neck/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/neck/chameleon/broken/Initialize(mapload) - . = ..() - chameleon_action.emp_randomise(INFINITY) - -/obj/item/gun/energy/laser/chameleon - var/datum/action/item_action/chameleon/change/chameleon_action - - ammo_type = list(/obj/item/ammo_casing/energy/chameleon) - pin = /obj/item/firing_pin - automatic_charge_overlays = FALSE - can_select = FALSE - - /// The vars copied over to our projectile on fire. - var/list/chameleon_projectile_vars - - /// The badmin mode. Makes your projectiles act like the real deal. - var/real_hits = FALSE - -/obj/item/gun/energy/laser/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/gun - chameleon_action.chameleon_name = "Gun" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/gun/energy/minigun) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - - recharge_newshot() - set_chameleon_disguise(/obj/item/gun/energy/laser) - -/obj/item/gun/energy/laser/chameleon/Destroy() - chameleon_projectile_vars.Cut() - QDEL_NULL(chameleon_action) - return ..() - -/obj/item/gun/energy/laser/chameleon/emp_act(severity) - return - -/** - * Description: Resets the currently loaded chameleon variables, essentially resetting it to brand new. - * Arguments: [] - */ -/obj/item/gun/energy/laser/chameleon/proc/reset_chameleon_vars() - chameleon_projectile_vars = list() - - if(chambered) - chambered.firing_effect_type = initial(chambered.firing_effect_type) - - fire_sound = initial(fire_sound) - burst_size = initial(burst_size) - fire_delay = initial(fire_delay) - inhand_x_dimension = initial(inhand_x_dimension) - inhand_y_dimension = initial(inhand_y_dimension) - - QDEL_NULL(chambered.loaded_projectile) - chambered.newshot() - -/** - * Description: Sets what gun we should be mimicking. - * Arguments: [obj/item/gun/gun_to_set (the gun we're trying to mimic)] - */ -/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_gun(obj/item/gun/gun_to_set) - if(!istype(gun_to_set)) - stack_trace("[gun_to_set] is not a valid gun.") - return FALSE - - fire_sound = gun_to_set.fire_sound - burst_size = gun_to_set.burst_size - fire_delay = gun_to_set.fire_delay - inhand_x_dimension = gun_to_set.inhand_x_dimension - inhand_y_dimension = gun_to_set.inhand_y_dimension - - if(istype(gun_to_set, /obj/item/gun/ballistic)) - var/obj/item/gun/ballistic/ball_gun = gun_to_set - var/obj/item/ammo_box/ball_ammo = new ball_gun.spawn_magazine_type(gun_to_set) - qdel(ball_gun) - - if(!istype(ball_ammo) || !ball_ammo.ammo_type) - qdel(ball_ammo) - return FALSE - - var/obj/item/ammo_casing/ball_cartridge = new ball_ammo.ammo_type(gun_to_set) - set_chameleon_ammo(ball_cartridge) - - else if(istype(gun_to_set, /obj/item/gun/magic)) - var/obj/item/gun/magic/magic_gun = gun_to_set - var/obj/item/ammo_casing/magic_cartridge = new magic_gun.ammo_type(gun_to_set) - set_chameleon_ammo(magic_cartridge) - - else if(istype(gun_to_set, /obj/item/gun/energy)) - var/obj/item/gun/energy/energy_gun = gun_to_set - if(islist(energy_gun.ammo_type) && energy_gun.ammo_type.len) - var/obj/item/ammo_casing/energy_cartridge = energy_gun.ammo_type[1] - set_chameleon_ammo(energy_cartridge) - - else if(istype(gun_to_set, /obj/item/gun/syringe)) - var/obj/item/ammo_casing/syringe_cartridge = new /obj/item/ammo_casing/syringegun(src) - set_chameleon_ammo(syringe_cartridge) - - else - var/obj/item/ammo_casing/default_cartridge = new /obj/item/ammo_casing(src) - set_chameleon_ammo(default_cartridge) - -/** - * Description: Sets the ammo type our gun should have. - * Arguments: [obj/item/ammo_casing/cartridge (the ammo_casing we're trying to copy)] - */ -/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_ammo(obj/item/ammo_casing/cartridge) - if(!istype(cartridge)) - stack_trace("[cartridge] is not a valid ammo casing.") - return FALSE - - var/obj/projectile/projectile = cartridge.loaded_projectile - set_chameleon_projectile(projectile) - -/** - * Description: Sets the current projectile variables for our chameleon gun. - * Arguments: [obj/projectile/template_projectile (the projectile we're trying to copy)] - */ -/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_projectile(obj/projectile/template_projectile) - if(!istype(template_projectile)) - stack_trace("[template_projectile] is not a valid projectile.") - return FALSE - - chameleon_projectile_vars = list("name" = "practice laser", "icon" = 'icons/obj/weapons/guns/projectiles.dmi', "icon_state" = "laser") - - var/default_state = isnull(template_projectile.icon_state) ? "laser" : template_projectile.icon_state - - chameleon_projectile_vars["name"] = template_projectile.name - chameleon_projectile_vars["icon"] = template_projectile.icon - chameleon_projectile_vars["icon_state"] = default_state - chameleon_projectile_vars["speed"] = template_projectile.speed - chameleon_projectile_vars["color"] = template_projectile.color - chameleon_projectile_vars["hitsound"] = template_projectile.hitsound - chameleon_projectile_vars["impact_effect_type"] = template_projectile.impact_effect_type - chameleon_projectile_vars["range"] = template_projectile.range - chameleon_projectile_vars["suppressed"] = template_projectile.suppressed - chameleon_projectile_vars["hitsound_wall"] = template_projectile.hitsound_wall - chameleon_projectile_vars["pass_flags"] = template_projectile.pass_flags - - if(istype(chambered, /obj/item/ammo_casing/energy/chameleon)) - var/obj/item/ammo_casing/energy/chameleon/cartridge = chambered - - cartridge.loaded_projectile.name = template_projectile.name - cartridge.loaded_projectile.icon = template_projectile.icon - cartridge.loaded_projectile.icon_state = default_state - cartridge.loaded_projectile.speed = template_projectile.speed - cartridge.loaded_projectile.color = template_projectile.color - cartridge.loaded_projectile.hitsound = template_projectile.hitsound - cartridge.loaded_projectile.impact_effect_type = template_projectile.impact_effect_type - cartridge.loaded_projectile.range = template_projectile.range - cartridge.loaded_projectile.suppressed = template_projectile.suppressed - cartridge.loaded_projectile.hitsound_wall = template_projectile.hitsound_wall - cartridge.loaded_projectile.pass_flags = template_projectile.pass_flags - - cartridge.projectile_vars = chameleon_projectile_vars.Copy() - - if(real_hits) - qdel(chambered.loaded_projectile) - chambered.projectile_type = template_projectile.type - - qdel(template_projectile) - - -/** - * Description: Resets our chameleon variables, then resets the entire gun to mimic the given guntype. - * Arguments: [guntype (the gun we're copying, pathtyped to obj/item/gun)] - */ -/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_disguise(guntype) - reset_chameleon_vars() - var/obj/item/gun/new_gun = new guntype(src) - set_chameleon_gun(new_gun) - qdel(new_gun) - -#undef EMP_RANDOMISE_TIME diff --git a/code/modules/clothing/chameleon/_chameleon_action.dm b/code/modules/clothing/chameleon/_chameleon_action.dm new file mode 100644 index 00000000000..7d11355791b --- /dev/null +++ b/code/modules/clothing/chameleon/_chameleon_action.dm @@ -0,0 +1,222 @@ +/// Default duration of an EMP randomisation on a chameleon item +#define EMP_RANDOMISE_TIME 30 SECONDS + +/datum/action/item_action/chameleon + +/datum/action/item_action/chameleon/change + name = "Chameleon Change" + 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() + /// Typecache of typepaths we can turn into + VAR_FINAL/list/chameleon_typecache + /// Assoc list of item name + icon state to item typepath + /// This is passed to the list input + VAR_FINAL/list/chameleon_list + /// The prime typepath of what class of item we're allowed to pick from + var/chameleon_type + /// Used in the action button to describe what we're changing into + var/chameleon_name = "Item" + /// What chameleon is active right now? + /// Can be set in the declaration to update in init + var/active_type + /// Cooldown from when we started being EMP'd + COOLDOWN_DECLARE(emp_timer) + +/datum/action/item_action/chameleon/change/New(Target) + . = ..() + if(!isitem(target)) + stack_trace("Adding chameleon action to non-item ([target])") + qdel(src) + return + + initialize_blacklist() + initialize_disguises() + if(active_type) + if(chameleon_blacklist[active_type]) + stack_trace("[type] has an active type defined in init which is blacklisted ([active_type])") + active_type = null + else + update_look(active_type) + + RegisterSignal(target, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp)) + +/datum/action/item_action/chameleon/change/Destroy() + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/action/item_action/chameleon/change/proc/on_emp(datum/source, severity, protection) + SIGNAL_HANDLER + if(protection & EMP_PROTECT_SELF) + return + emp_randomise() + +/datum/action/item_action/chameleon/change/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + + // Whenever a mob gains their first cham change action, they need to also gain the outfit action + if(locate(/datum/action/chameleon_outfit) in grant_to.actions) + return + + var/datum/action/chameleon_outfit/outfit_action = new(owner) + outfit_action.Grant(owner) + +/datum/action/item_action/chameleon/change/Remove(mob/remove_from) + . = ..() + // Likewise when the mob loses the cham change action, if they have no others, they need to lose the outfit action + if(locate(/datum/action/item_action/chameleon/change) in remove_from.actions) + return + + var/datum/action/chameleon_outfit/outfit_action = locate() in remove_from.actions + QDEL_NULL(outfit_action) + +/datum/action/item_action/chameleon/change/proc/initialize_blacklist() + chameleon_blacklist |= typecacheof(target.type) + +/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) + + chameleon_typecache |= typecacheof(type_to_add) + for(var/obj/item/item_type as anything in chameleon_typecache) + if(chameleon_blacklist[item_type] || (initial(item_type.item_flags) & ABSTRACT) || !initial(item_type.icon_state)) + continue + var/chameleon_item_name = "[initial(item_type.name)] ([initial(item_type.icon_state)])" + chameleon_list[chameleon_item_name] = item_type + + +/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))) + if(isnull(picked_name) || isnull(chameleon_list[picked_name]) || QDELETED(src) || QDELETED(user) || QDELETED(owner) || !IsAvailable(feedback = TRUE)) + return + var/obj/item/picked_item = chameleon_list[picked_name] + update_look(picked_item) + +/datum/action/item_action/chameleon/change/proc/random_look() + var/picked_name = pick(chameleon_list) + update_look(chameleon_list[picked_name]) + +/datum/action/item_action/chameleon/change/proc/update_look(obj/item/picked_item) + var/obj/item/chameleon_item = target + + update_item(picked_item) + build_all_button_icons() + active_type = picked_item + + if(ismob(chameleon_item.loc)) + var/mob/wearer = chameleon_item.loc + wearer.update_clothing(chameleon_item.slot_flags | ITEM_SLOT_HANDS) + +/datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item) + PROTECTED_PROC(TRUE) // Call update_look, not this! + + var/atom/atom_target = target + atom_target.name = initial(picked_item.name) + atom_target.desc = initial(picked_item.desc) + atom_target.icon_state = initial(picked_item.icon_state) + + if(isitem(atom_target)) + var/obj/item/item_target = target + item_target.worn_icon = initial(picked_item.worn_icon) + item_target.lefthand_file = initial(picked_item.lefthand_file) + item_target.righthand_file = initial(picked_item.righthand_file) + + item_target.worn_icon_state = initial(picked_item.worn_icon_state) + item_target.inhand_icon_state = initial(picked_item.inhand_icon_state) + + if(initial(picked_item.greyscale_colors)) + if(initial(picked_item.greyscale_config_worn)) + item_target.worn_icon = SSgreyscale.GetColoredIconByType( + initial(picked_item.greyscale_config_worn), + initial(picked_item.greyscale_colors), + ) + if(initial(picked_item.greyscale_config_inhand_left)) + item_target.lefthand_file = SSgreyscale.GetColoredIconByType( + initial(picked_item.greyscale_config_inhand_left), + initial(picked_item.greyscale_colors), + ) + if(initial(picked_item.greyscale_config_inhand_right)) + item_target.righthand_file = SSgreyscale.GetColoredIconByType( + initial(picked_item.greyscale_config_inhand_right), + initial(picked_item.greyscale_colors), + ) + + item_target.flags_inv = initial(picked_item.flags_inv) + item_target.transparent_protection = initial(picked_item.transparent_protection) + if(isclothing(item_target) && ispath(picked_item, /obj/item/clothing)) + var/obj/item/clothing/clothing_target = item_target + var/obj/item/clothing/picked_clothing = picked_item + clothing_target.flags_cover = initial(picked_clothing.flags_cover) + + + if(initial(picked_item.greyscale_config) && initial(picked_item.greyscale_colors)) + atom_target.icon = SSgreyscale.GetColoredIconByType( + initial(picked_item.greyscale_config), + initial(picked_item.greyscale_colors), + ) + + else + atom_target.icon = initial(picked_item.icon) + +/datum/action/item_action/chameleon/change/Trigger(trigger_flags) + if(!IsAvailable(feedback = TRUE)) + return FALSE + + select_look(owner) + return TRUE + +/datum/action/item_action/chameleon/change/proc/emp_randomise(amount = EMP_RANDOMISE_TIME) + START_PROCESSING(SSprocessing, src) + random_look() + + COOLDOWN_START(src, emp_timer, amount) + +/datum/action/item_action/chameleon/change/process() + if(COOLDOWN_FINISHED(src, emp_timer)) + STOP_PROCESSING(SSprocessing, src) + return + random_look() + +/datum/action/item_action/chameleon/change/proc/apply_outfit(datum/outfit/applying_from, list/all_items_to_apply) + SHOULD_CALL_PARENT(TRUE) + + var/using_item_type + for(var/item_type in all_items_to_apply) + if(!ispath(item_type, /obj/item)) + stack_trace("Invalid item type passed to apply_outfit ([item_type])") + continue + if(chameleon_typecache[item_type]) + using_item_type = item_type + break + + if(isnull(using_item_type)) + return FALSE + + if(istype(applying_from, /datum/outfit/job)) + var/datum/outfit/job/job_outfit = applying_from + var/datum/job/job_datum = SSjob.GetJobType(job_outfit.jobtype) + apply_job_data(job_datum) + + update_look(using_item_type) + all_items_to_apply -= using_item_type + return TRUE + +/// Used when applying this cham item via a job datum (from an outfit selection) +/datum/action/item_action/chameleon/change/proc/apply_job_data(datum/job/job_datum) + return + +#undef EMP_RANDOMISE_TIME diff --git a/code/modules/clothing/chameleon/_chameleon_outfit.dm b/code/modules/clothing/chameleon/_chameleon_outfit.dm new file mode 100644 index 00000000000..df5f3056ac5 --- /dev/null +++ b/code/modules/clothing/chameleon/_chameleon_outfit.dm @@ -0,0 +1,119 @@ +/datum/action/chameleon_outfit + name = "Select Chameleon Outfit" + desc = "LMB: Select a job to update all of your chameleon items to.
\ + RMB: Save your current chameleon setup as a custom outfit." + button_icon_state = "chameleon_outfit" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED + + /// Determines when we're in use + var/applying = FALSE + /// Cached assoc list of job outfit datums by their names that we can select + /// If you intend on editing this, ensure you are copying it first + var/list/outfit_options + /// Assoc list of custom outfit names ("Custom outfit 1", "Custom outfit 2", etc) to list of all item typepaths saved in that outfit + var/list/custom_outfits + /// Cap on how many custom outfits we can save + var/max_custom_oufits = 10 + /// Used to generate the next custom outfit name, also to track which next to override if we've passed the limit + var/next_custom_outfit = 1 + +/datum/action/chameleon_outfit/New() + . = ..() + outfit_options = get_initial_outfits() + +/datum/action/chameleon_outfit/proc/get_initial_outfits() + var/static/list/standard_outfit_options + if(!standard_outfit_options) + standard_outfit_options = list() + for(var/datum/outfit/found_outfit as anything in subtypesof(/datum/outfit/job)) + standard_outfit_options[initial(found_outfit.name)] = new found_outfit() + sortTim(standard_outfit_options, GLOBAL_PROC_REF(cmp_text_asc)) + + return standard_outfit_options + +/datum/action/chameleon_outfit/Trigger(trigger_flags) + . = ..() + if(!. || applying) + return + if(usr != owner) // i guess + return + applying = TRUE + + if(trigger_flags & TRIGGER_SECONDARY_ACTION) + . = save_current_outfit(usr) + else + . = select_outfit(usr) + + applying = FALSE + return . + +/datum/action/chameleon_outfit/proc/save_current_outfit(mob/user) + var/list/saved_paths = list() + for(var/datum/action/item_action/chameleon/change/change_action in owner.actions) + if(change_action.active_type) + saved_paths |= change_action.active_type + + save_outfit(user, saved_paths) + +/datum/action/chameleon_outfit/proc/save_outfit(mob/user, list/saved_paths) + if(!length(saved_paths)) + owner.balloon_alert(user, "no outfit saved!") + return + + for(var/existing_outfit in custom_outfits) + if(custom_outfits[existing_outfit] ~= saved_paths) + owner.balloon_alert(user, "outfit already saved!") + return + + if(next_custom_outfit > max_custom_oufits) + next_custom_outfit = 1 + + LAZYSET(custom_outfits, "Custom Outfit #[next_custom_outfit]", saved_paths) + owner.balloon_alert(user, "outfit saved in slot [next_custom_outfit]") + next_custom_outfit += 1 + +/datum/action/chameleon_outfit/proc/select_outfit(mob/user) + var/list/all_options = list() + if(LAZYLEN(custom_outfits)) + all_options += "--- Custom outfits ---" + all_options += custom_outfits + all_options += "--- Job outfits ---" + all_options += outfit_options + + var/selected = tgui_input_list(user, "Select outfit to change into", "Chameleon Outfit", all_options) + if(isnull(selected) || QDELETED(src) || QDELETED(user) || QDELETED(owner) || !IsAvailable(feedback = TRUE)) + return FALSE + + var/selected_outfit = all_options[selected] + if(islist(selected_outfit)) + var/list/selected_custom_outfit = selected_outfit + var/datum/outfit/empty_outfit = new() + apply_outfit(empty_outfit, selected_custom_outfit.Copy()) + qdel(empty_outfit) + return TRUE + + if(istype(selected_outfit, /datum/outfit)) + apply_outfit(selected_outfit) + return TRUE + + return FALSE + +/** + * Applies the given outfit to all chameleon actions the owner has + * + * * outfit - what outfit to apply + * * outfit_types - optinal, list of typepaths to apply. If null, defaults to all items in the passed outfit. This list is mutated! + */ +/datum/action/chameleon_outfit/proc/apply_outfit(datum/outfit/outfit, list/outfit_types) + if(isnull(outfit_types)) + outfit_types = outfit.get_chameleon_disguise_info() + + for(var/datum/action/item_action/chameleon/change/change_action in owner.actions) + change_action.apply_outfit(outfit, outfit_types) + if(!isitem(change_action.target)) + continue + var/obj/item/cham_item = change_action.target + if(!cham_item.slot_flags) + continue + // Clothing items get a small delay to create a visual effect and also introduce some counterplay and also distinguish from lings + stoplag(0.1 SECONDS) diff --git a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm new file mode 100644 index 00000000000..db8185c43ed --- /dev/null +++ b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm @@ -0,0 +1,299 @@ +/datum/action/item_action/chameleon/change/neck + chameleon_type = /obj/item/clothing/neck + chameleon_name = "Neck Accessory" + active_type = /obj/item/clothing/neck/tie/black + +/datum/action/item_action/chameleon/change/neck/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(/obj/item/clothing/neck/cloak/skill_reward) + +/datum/action/item_action/chameleon/change/stamp + chameleon_type = /obj/item/stamp + chameleon_name = "Stamp" + +/datum/action/item_action/chameleon/change/tablet + chameleon_type = /obj/item/modular_computer/pda + chameleon_name = "Tablet" + +/datum/action/item_action/chameleon/change/tablet/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list(/obj/item/modular_computer/pda/heads), only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/tablet/update_item(obj/item/picked_item) + ..() + var/obj/item/modular_computer/pda/agent_pda = target + if(istype(agent_pda)) + agent_pda.update_appearance() + +/datum/action/item_action/chameleon/change/tablet/apply_job_data(datum/job/job_datum) + ..() + var/obj/item/modular_computer/pda/agent_pda = target + if(istype(agent_pda) && istype(job_datum)) + agent_pda.saved_job = job_datum.title + +/datum/action/item_action/chameleon/change/headset + chameleon_type = /obj/item/radio/headset + chameleon_name = "Headset" + active_type = /obj/item/radio/headset + +/datum/action/item_action/chameleon/change/belt + chameleon_type = /obj/item/storage/belt + chameleon_name = "Belt" + active_type = /obj/item/storage/belt/utility + +/datum/action/item_action/chameleon/change/backpack + chameleon_type = /obj/item/storage/backpack + chameleon_name = "Backpack" + active_type = /obj/item/storage/backpack + +/datum/action/item_action/chameleon/change/shoes + chameleon_type = /obj/item/clothing/shoes + chameleon_name = "Shoes" + active_type = /obj/item/clothing/shoes/sneakers/black + +/datum/action/item_action/chameleon/change/shoes/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(/obj/item/clothing/shoes/changeling, only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/mask + chameleon_type = /obj/item/clothing/mask + chameleon_name = "Mask" + active_type = /obj/item/clothing/mask/gas + +/datum/action/item_action/chameleon/change/mask/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(/obj/item/clothing/mask/changeling, only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/hat + chameleon_type = /obj/item/clothing/head + chameleon_name = "Hat" + active_type = /obj/item/clothing/head/soft/black + +/datum/action/item_action/chameleon/change/hat/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/gloves + chameleon_type = /obj/item/clothing/gloves + chameleon_name = "Gloves" + active_type = /obj/item/clothing/gloves/color/yellow + +/datum/action/item_action/chameleon/change/gloves/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list(/obj/item/clothing/gloves, /obj/item/clothing/gloves/color, /obj/item/clothing/gloves/changeling), only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/glasses + chameleon_type = /obj/item/clothing/glasses + chameleon_name = "Glasses" + active_type = /obj/item/clothing/glasses/meson + +/datum/action/item_action/chameleon/change/glasses/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list(/obj/item/clothing/glasses/changeling, /obj/item/clothing/glasses/hud/security/chameleon, /obj/item/clothing/glasses/thermal/syndi), only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/glasses/no_preset + active_type = null + +/datum/action/item_action/chameleon/change/suit + chameleon_type = /obj/item/clothing/suit + chameleon_name = "Suit" + active_type = /obj/item/clothing/suit/armor/vest + +/datum/action/item_action/chameleon/change/suit/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list(/obj/item/clothing/suit/armor/abductor, /obj/item/clothing/suit/changeling), only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/suit/apply_outfit(mob/user, datum/outfit/applying_from, list/all_items_to_apply) + . = ..() + if(!. || !ispath(applying_from.suit, /obj/item/clothing/suit/hooded)) + return + // If we're appling a hooded suit, and wearing a cham hat, make it a hood + var/obj/item/clothing/suit/hooded/hooded = applying_from.suit + var/datum/action/item_action/chameleon/change/hat/hood_action = locate() in user.actions + hood_action?.update_look(user, initial(hooded.hoodtype)) + +/datum/action/item_action/chameleon/change/jumpsuit + chameleon_type = /obj/item/clothing/under + chameleon_name = "Jumpsuit" + active_type = /obj/item/clothing/under/color/black + +/datum/action/item_action/chameleon/change/jumpsuit/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE) + +/datum/action/item_action/chameleon/change/id + chameleon_type = /obj/item/card/id/advanced + chameleon_name = "ID Card" + +/datum/action/item_action/chameleon/change/id/New(Target) + . = ..() + if(!istype(target, /obj/item/card/id/advanced/chameleon)) + stack_trace("Adding chameleon ID action to non-chameleon id ([target])") + qdel(src) + +/datum/action/item_action/chameleon/change/id/update_item(obj/item/picked_item) + . = ..() + var/obj/item/card/id/advanced/chameleon/agent_card = target + var/obj/item/card/id/copied_card = picked_item + + // If the outfit comes with a special trim override, we'll steal some stuff from that. + var/new_trim = initial(copied_card.trim) + + if(new_trim) + SSid_access.apply_trim_to_chameleon_card(agent_card, new_trim, TRUE) + + // If the ID card hasn't been forged, we'll check if there has been an assignment set already by any new trim. + // 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.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.update_label() + agent_card.update_appearance(UPDATE_ICON) + +/datum/action/item_action/chameleon/change/id/apply_job_data(datum/job/job_datum) + var/obj/item/card/id/advanced/chameleon/agent_card = target + agent_card.forged = TRUE + + // job_outfit is going to be a path. + var/datum/outfit/job/job_outfit = job_datum.outfit + if(isnull(job_outfit)) + return + + // copied_card is also going to be a path. + var/obj/item/card/id/copied_card = initial(job_outfit.id) + if(isnull(copied_card)) + return + + // If the outfit comes with a special trim override, we'll use that. Otherwise, use the card's default trim. Failing that, no trim at all. + var/new_trim = initial(job_outfit.id_trim) ? initial(job_outfit.id_trim) : initial(copied_card.trim) + + if(new_trim) + SSid_access.apply_trim_to_chameleon_card(agent_card, new_trim, FALSE) + else + agent_card.assignment = job_datum.title + + 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.update_label() + agent_card.update_appearance(UPDATE_ICON) + +/datum/action/item_action/chameleon/change/id_trim + chameleon_type = /datum/id_trim + chameleon_name = "ID Trim" + +/datum/action/item_action/chameleon/change/id_trim/New(Target) + . = ..() + if(!istype(target, /obj/item/card/id/advanced/chameleon)) + stack_trace("Adding chameleon ID trim action to non-chameleon id ([target])") + qdel(src) + +/datum/action/item_action/chameleon/change/id_trim/initialize_blacklist() + return + +/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() + + 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]) + 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_typecache[trim_path] = TRUE + +/datum/action/item_action/chameleon/change/id_trim/update_item(picked_trim_path) + var/obj/item/card/id/advanced/chameleon/agent_card = target + + SSid_access.apply_trim_to_chameleon_card(agent_card, picked_trim_path, TRUE) + + agent_card.update_label() + agent_card.update_appearance(UPDATE_ICON) + +/datum/action/item_action/chameleon/change/gun + chameleon_type = /obj/item/gun + chameleon_name = "Gun" + +/datum/action/item_action/chameleon/change/gun/New(Target) + . = ..() + if(!istype(target, /obj/item/gun/energy/laser/chameleon)) + stack_trace("Adding chameleon gun action to non-chameleon gun ([target])") + qdel(src) + +/datum/action/item_action/chameleon/change/gun/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(/obj/item/gun/energy/minigun) + +/datum/action/item_action/chameleon/change/gun/update_look(obj/item/picked_item) + var/obj/item/gun/energy/laser/chameleon/chameleon_gun = target + chameleon_gun.set_chameleon_disguise(picked_item) + return ..() + +/datum/action/item_action/chameleon/change/scanner + chameleon_type = /obj/item/pen + chameleon_name = "Chameleon Scanner" + active_type = /obj/item/storage/fancy/cigarettes/cigpack_robustgold + /// Other types the chameleon scanner can swap into in addition to the chameleon type + var/static/list/other_cham_types = list( + /obj/item/analyzer, + /obj/item/assembly/flash/handheld, + /obj/item/assembly/signaler, + /obj/item/autopsy_scanner, + /obj/item/camera, + /obj/item/cane, + /obj/item/detective_scanner, + /obj/item/door_remote, + /obj/item/flashlight, + /obj/item/geiger_counter, + /obj/item/healthanalyzer, + /obj/item/mining_scanner, + /obj/item/multitool, + /obj/item/plant_analyzer, + /obj/item/sensor_device, + /obj/item/storage/fancy/cigarettes, // Fanservice + /obj/item/taperecorder, + /obj/item/toy/crayon, + ) + // (Other ideas include: GPSs, PDAs, station bounced radios, holosign creators, etc. But this is good enough) + +/datum/action/item_action/chameleon/change/scanner/initialize_blacklist() + . = ..() + chameleon_blacklist |= typecacheof(list( + /obj/item/assembly/signaler/anomaly, + /obj/item/camera/siliconcam, + /obj/item/door_remote/omni, + /obj/item/flashlight/emp/debug, + /obj/item/flashlight/eyelight/adapted, + /obj/item/flashlight/flare, + /obj/item/flashlight/lamp, + /obj/item/healthanalyzer/rad_laser, + /obj/item/multitool/ai_detect, + /obj/item/multitool/cyborg, + /obj/item/multitool/drone, + /obj/item/multitool/field_debug, + /obj/item/storage/fancy/cigarettes/cigars, + )) + +/datum/action/item_action/chameleon/change/scanner/initialize_disguises() + . = ..() + for(var/other_type in other_cham_types) + add_chameleon_items(other_type) diff --git a/code/modules/clothing/chameleon/chameleon_drone.dm b/code/modules/clothing/chameleon/chameleon_drone.dm new file mode 100644 index 00000000000..83d6610d671 --- /dev/null +++ b/code/modules/clothing/chameleon/chameleon_drone.dm @@ -0,0 +1,52 @@ +/datum/action/item_action/chameleon/drone/randomise + name = "Randomise Headgear" + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "random" + +/datum/action/item_action/chameleon/drone/randomise/Trigger(trigger_flags) + if(!IsAvailable(feedback = TRUE)) + return FALSE + + for(var/datum/action/item_action/chameleon/change/to_randomize in owner.actions) + to_randomize.random_look() + return TRUE + +// Allows a drone to turn their hat into a mask +// This action's existence is very silly can be replaced with just, a hat with a chameleon action that can be both hats and masks. +/datum/action/item_action/chameleon/drone/togglehatmask + name = "Toggle Headgear Mode" + button_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "drone_camogear_helm" + +/datum/action/item_action/chameleon/drone/togglehatmask/New(Target) + if (istype(Target, /obj/item/clothing/head/chameleon/drone)) + button_icon_state = "drone_camogear_helm" + if (istype(Target, /obj/item/clothing/mask/chameleon/drone)) + button_icon_state = "drone_camogear_mask" + return ..() + +/datum/action/item_action/chameleon/drone/togglehatmask/IsAvailable(feedback) + return ..() && isdrone(owner) + +/datum/action/item_action/chameleon/drone/togglehatmask/Trigger(trigger_flags) + if(!IsAvailable(feedback = TRUE)) + return FALSE + + var/mob/living/simple_animal/drone/droney = owner + + // The drone unEquip() proc sets head to null after dropping + // an item, so we need to keep a reference to our old headgear + // to make sure it's deleted. + var/obj/old_headgear = target + var/obj/new_headgear + + if(istype(old_headgear, /obj/item/clothing/head/chameleon/drone)) + new_headgear = new /obj/item/clothing/mask/chameleon/drone(droney) + else if(istype(old_headgear, /obj/item/clothing/mask/chameleon/drone)) + new_headgear = new /obj/item/clothing/head/chameleon/drone(droney) + else + to_chat(owner, span_warning("You shouldn't be able to toggle a camogear helmetmask if you're not wearing it.")) + return FALSE + droney.dropItemToGround(target, force = TRUE) + droney.equip_to_slot_or_del(new_headgear, ITEM_SLOT_HEAD) + return TRUE diff --git a/code/modules/clothing/chameleon/chameleon_gun.dm b/code/modules/clothing/chameleon/chameleon_gun.dm new file mode 100644 index 00000000000..dafd0ba5f62 --- /dev/null +++ b/code/modules/clothing/chameleon/chameleon_gun.dm @@ -0,0 +1,157 @@ + +/obj/item/gun/energy/laser/chameleon + ammo_type = list(/obj/item/ammo_casing/energy/chameleon) + pin = /obj/item/firing_pin + automatic_charge_overlays = FALSE + can_select = FALSE + actions_types = list(/datum/action/item_action/chameleon/change/gun) + + /// The vars copied over to our projectile on fire. + var/list/chameleon_projectile_vars + + /// The badmin mode. Makes your projectiles act like the real deal. + var/real_hits = FALSE + +/obj/item/gun/energy/laser/chameleon/Initialize(mapload) + . = ..() + recharge_newshot() + AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS) + // Init order shenanigans dictate we have to do this last so we can't just use `active_type` + var/datum/action/item_action/chameleon/change/gun/gun_action = locate() in actions + gun_action?.update_look(/obj/item/gun/energy/laser) + +/** + * Description: Resets the currently loaded chameleon variables, essentially resetting it to brand new. + * Arguments: [] + */ +/obj/item/gun/energy/laser/chameleon/proc/reset_chameleon_vars() + chameleon_projectile_vars = list() + + if(chambered) + chambered.firing_effect_type = initial(chambered.firing_effect_type) + + fire_sound = initial(fire_sound) + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + inhand_x_dimension = initial(inhand_x_dimension) + inhand_y_dimension = initial(inhand_y_dimension) + + QDEL_NULL(chambered.loaded_projectile) + chambered.newshot() + +/** + * Description: Sets what gun we should be mimicking. + * Arguments: [obj/item/gun/gun_to_set (the gun we're trying to mimic)] + */ +/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_gun(obj/item/gun/gun_to_set) + if(!istype(gun_to_set)) + stack_trace("[gun_to_set] is not a valid gun.") + return FALSE + + fire_sound = gun_to_set.fire_sound + burst_size = gun_to_set.burst_size + fire_delay = gun_to_set.fire_delay + inhand_x_dimension = gun_to_set.inhand_x_dimension + inhand_y_dimension = gun_to_set.inhand_y_dimension + + if(istype(gun_to_set, /obj/item/gun/ballistic)) + var/obj/item/gun/ballistic/ball_gun = gun_to_set + var/obj/item/ammo_box/ball_ammo = new ball_gun.spawn_magazine_type(gun_to_set) + qdel(ball_gun) + + if(!istype(ball_ammo) || !ball_ammo.ammo_type) + qdel(ball_ammo) + return FALSE + + var/obj/item/ammo_casing/ball_cartridge = new ball_ammo.ammo_type(gun_to_set) + set_chameleon_ammo(ball_cartridge) + + else if(istype(gun_to_set, /obj/item/gun/magic)) + var/obj/item/gun/magic/magic_gun = gun_to_set + var/obj/item/ammo_casing/magic_cartridge = new magic_gun.ammo_type(gun_to_set) + set_chameleon_ammo(magic_cartridge) + + else if(istype(gun_to_set, /obj/item/gun/energy)) + var/obj/item/gun/energy/energy_gun = gun_to_set + if(islist(energy_gun.ammo_type) && energy_gun.ammo_type.len) + var/obj/item/ammo_casing/energy_cartridge = energy_gun.ammo_type[1] + set_chameleon_ammo(energy_cartridge) + + else if(istype(gun_to_set, /obj/item/gun/syringe)) + var/obj/item/ammo_casing/syringe_cartridge = new /obj/item/ammo_casing/syringegun(src) + set_chameleon_ammo(syringe_cartridge) + + else + var/obj/item/ammo_casing/default_cartridge = new /obj/item/ammo_casing(src) + set_chameleon_ammo(default_cartridge) + +/** + * Description: Sets the ammo type our gun should have. + * Arguments: [obj/item/ammo_casing/cartridge (the ammo_casing we're trying to copy)] + */ +/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_ammo(obj/item/ammo_casing/cartridge) + if(!istype(cartridge)) + stack_trace("[cartridge] is not a valid ammo casing.") + return FALSE + + var/obj/projectile/projectile = cartridge.loaded_projectile + set_chameleon_projectile(projectile) + +/** + * Description: Sets the current projectile variables for our chameleon gun. + * Arguments: [obj/projectile/template_projectile (the projectile we're trying to copy)] + */ +/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_projectile(obj/projectile/template_projectile) + if(!istype(template_projectile)) + stack_trace("[template_projectile] is not a valid projectile.") + return FALSE + + chameleon_projectile_vars = list("name" = "practice laser", "icon" = 'icons/obj/weapons/guns/projectiles.dmi', "icon_state" = "laser") + + var/default_state = isnull(template_projectile.icon_state) ? "laser" : template_projectile.icon_state + + chameleon_projectile_vars["name"] = template_projectile.name + chameleon_projectile_vars["icon"] = template_projectile.icon + chameleon_projectile_vars["icon_state"] = default_state + chameleon_projectile_vars["speed"] = template_projectile.speed + chameleon_projectile_vars["color"] = template_projectile.color + chameleon_projectile_vars["hitsound"] = template_projectile.hitsound + chameleon_projectile_vars["impact_effect_type"] = template_projectile.impact_effect_type + chameleon_projectile_vars["range"] = template_projectile.range + chameleon_projectile_vars["suppressed"] = template_projectile.suppressed + chameleon_projectile_vars["hitsound_wall"] = template_projectile.hitsound_wall + chameleon_projectile_vars["pass_flags"] = template_projectile.pass_flags + + if(istype(chambered, /obj/item/ammo_casing/energy/chameleon)) + var/obj/item/ammo_casing/energy/chameleon/cartridge = chambered + + cartridge.loaded_projectile.name = template_projectile.name + cartridge.loaded_projectile.icon = template_projectile.icon + cartridge.loaded_projectile.icon_state = default_state + cartridge.loaded_projectile.speed = template_projectile.speed + cartridge.loaded_projectile.color = template_projectile.color + cartridge.loaded_projectile.hitsound = template_projectile.hitsound + cartridge.loaded_projectile.impact_effect_type = template_projectile.impact_effect_type + cartridge.loaded_projectile.range = template_projectile.range + cartridge.loaded_projectile.suppressed = template_projectile.suppressed + cartridge.loaded_projectile.hitsound_wall = template_projectile.hitsound_wall + cartridge.loaded_projectile.pass_flags = template_projectile.pass_flags + + cartridge.projectile_vars = chameleon_projectile_vars.Copy() + + if(real_hits) + qdel(chambered.loaded_projectile) + chambered.projectile_type = template_projectile.type + + qdel(template_projectile) + + +/** + * Description: Resets our chameleon variables, then resets the entire gun to mimic the given guntype. + * Arguments: [guntype (the gun we're copying, pathtyped to obj/item/gun)] + */ +/obj/item/gun/energy/laser/chameleon/proc/set_chameleon_disguise(guntype) + reset_chameleon_vars() + var/obj/item/gun/new_gun = new guntype(src) + set_chameleon_gun(new_gun) + qdel(new_gun) diff --git a/code/modules/clothing/chameleon/chameleon_scanner.dm b/code/modules/clothing/chameleon/chameleon_scanner.dm new file mode 100644 index 00000000000..b6ed86e8640 --- /dev/null +++ b/code/modules/clothing/chameleon/chameleon_scanner.dm @@ -0,0 +1,116 @@ +/// Small handheld chameleon item that allows a user to mimic the outfit of another person quickly. +/obj/item/chameleon_scanner + // No name or desc by default, set up by the cham action + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_TINY + actions_types = list(/datum/action/item_action/chameleon/change/scanner) + throw_speed = 3 + /// Range that we can scan people + var/scan_range = 5 + /// Cooldown between scans. + /// Not entirely intended to be a balance knob, but rather intended to prevent accidentally scanning the same person in quick succession. + COOLDOWN_DECLARE(scan_cooldown) + +/obj/item/chameleon_scanner/Initialize(mapload) + . = ..() + register_item_context() + +/obj/item/chameleon_scanner/add_item_context( + obj/item/source, + list/context, + atom/target, + mob/living/user, +) + if(ishuman(target) && IS_TRAITOR(user)) + // probably don't want to give out the context to any old crewmember - + // even though anyone can use it, it kind of reveals the fact that it's disguised + // (though it's trivial to find that out anyway) + context[SCREENTIP_CONTEXT_LMB] = "Scan target outfit" + context[SCREENTIP_CONTEXT_RMB] = "Scan target outfit and equip" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + +/obj/item/chameleon_scanner/examine(mob/user) + . = ..() + if(!IS_TRAITOR(user)) + return + // similar to context, we don't want a bunch of text revealing "THIS IS A DISGUISED ITEM" to everyone on examine. + // despite the fact that anyone can use it, we'll only show it to traitors, everyone else just has to figure it out. + . += span_red("There's a small button on the bottom side of it. You recognize this as a hidden Chameleon Scanner 6000.") + . += span_red("Left click will stealthily scan a target up to [scan_range] meters away and upload their getup as a custom outfit for you to use.") + . += span_red("Right click will do the same, but instantly equip the outfit you obtain.") + +/obj/item/chameleon_scanner/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(scan_target(target, user)) + . |= AFTERATTACK_PROCESSED_ITEM + return . + +/obj/item/chameleon_scanner/afterattack_secondary(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return . + + var/list/scanned_outfit = scan_target(target, user) + if(length(scanned_outfit)) + var/datum/outfit/empty_outfit = new() + var/datum/action/chameleon_outfit/outfit_action = locate() in user.actions + outfit_action?.apply_outfit(empty_outfit, scanned_outfit.Copy()) + qdel(empty_outfit) + + return SECONDARY_ATTACK_CONTINUE_CHAIN // no normal afterattack + +/** + * Attempts to scan a human's outfit + * + * * scanned - the atom being scanned. + * * scanner - the mob doing the scanning + * + * Returns null or a list of paths scanned. Will not return an empty list. + */ +/obj/item/chameleon_scanner/proc/scan_target(atom/scanned, mob/scanner) + + var/mob/living/carbon/human/mob_copying + if(ishuman(scanned)) + mob_copying = scanned + else if(isturf(scanned)) + mob_copying = locate() in scanned // Aim assist + + if(isnull(mob_copying)) + return + + if(!COOLDOWN_FINISHED(src, scan_cooldown)) + balloon_alert(scanner, "not ready yet!") + return + if(get_dist(scanner, mob_copying) > scan_range) + balloon_alert(scanner, "too far away!") + return + // Very short scan timer, keep you on your toes + if(!do_after(scanner, 0.5 SECONDS, scanned)) + return + + var/list/all_scanned_items = list() + for(var/obj/item/thing in mob_copying.get_equipped_items()) + var/datum/action/item_action/chameleon/change/counter_chameleon = locate() in thing.actions + if(counter_chameleon?.active_type) + // Prevent counter spying + all_scanned_items |= counter_chameleon.active_type + else + // I guess this technically serves as a way to discover changling clothes at a distance...?? + // Not sure if that's a good thing or not. + all_scanned_items |= thing.type + + if(!length(all_scanned_items)) + scanned.balloon_alert(scanner, "nothing to scan!") + return + + playsound(src, 'sound/machines/ping.ogg', vol = 30, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE) + COOLDOWN_START(src, scan_cooldown, 5 SECONDS) + + var/datum/action/chameleon_outfit/outfit_action = locate() in scanner.actions + outfit_action?.save_outfit(scanner, all_scanned_items) + + return all_scanned_items diff --git a/code/modules/clothing/chameleon/generic_chameleon_clothing.dm b/code/modules/clothing/chameleon/generic_chameleon_clothing.dm new file mode 100644 index 00000000000..9e1339b4068 --- /dev/null +++ b/code/modules/clothing/chameleon/generic_chameleon_clothing.dm @@ -0,0 +1,326 @@ + +#define BREAK_CHAMELEON_ACTION(item) \ +do { \ + var/datum/action/item_action/chameleon/change/_action = locate() in item.actions; \ + _action?.emp_randomise(INFINITY); \ +} while(FALSE) + +// Cham jumpsuit +/datum/armor/clothing_under/chameleon + melee = 10 + bullet = 10 + laser = 10 + fire = 50 + acid = 50 + wound = 10 + +/obj/item/clothing/under/chameleon + name = "black jumpsuit" + desc = "It's a plain jumpsuit. It has a small dial on the wrist." + icon_state = "jumpsuit" + greyscale_colors = "#3f3f3f" + greyscale_config = /datum/greyscale_config/jumpsuit + greyscale_config_worn = /datum/greyscale_config/jumpsuit/worn + greyscale_config_inhand_left = /datum/greyscale_config/jumpsuit/inhand_left + greyscale_config_inhand_right = /datum/greyscale_config/jumpsuit/inhand_right + sensor_mode = SENSOR_OFF //Hey who's this guy on the Syndicate Shuttle?? + random_sensor = FALSE + resistance_flags = NONE + can_adjust = FALSE + armor_type = /datum/armor/clothing_under/chameleon + actions_types = list(/datum/action/item_action/chameleon/change/jumpsuit) + +/obj/item/clothing/under/chameleon/broken + +/obj/item/clothing/under/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham suit / armor +/datum/armor/suit_chameleon + melee = 10 + bullet = 10 + laser = 10 + fire = 50 + acid = 50 + +/obj/item/clothing/suit/chameleon + name = "armor" + desc = "A slim armored vest that protects against most types of damage." + icon_state = "armor" + icon = 'icons/obj/clothing/suits/armor.dmi' + worn_icon = 'icons/mob/clothing/suits/armor.dmi' + inhand_icon_state = "armor" + blood_overlay_type = "armor" + resistance_flags = NONE + armor_type = /datum/armor/suit_chameleon + actions_types = list(/datum/action/item_action/chameleon/change/suit) + +/obj/item/clothing/suit/chameleon/Initialize(mapload) + . = ..() + allowed = GLOB.security_vest_allowed //should at least act like a vest + +/obj/item/clothing/suit/chameleon/broken + +/obj/item/clothing/suit/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham glasses +/datum/armor/glasses_chameleon + melee = 10 + bullet = 10 + laser = 10 + fire = 50 + acid = 50 + +/obj/item/clothing/glasses/chameleon + name = "Optical Meson Scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting condition." + icon_state = "meson" + inhand_icon_state = "meson" + resistance_flags = NONE + armor_type = /datum/armor/glasses_chameleon + actions_types = list(/datum/action/item_action/chameleon/change/glasses) + +/obj/item/clothing/glasses/chameleon/broken + +/obj/item/clothing/glasses/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham gloves +/datum/armor/gloves_chameleon + melee = 10 + bullet = 10 + laser = 10 + fire = 50 + acid = 50 + +/obj/item/clothing/gloves/chameleon + desc = "These gloves provide protection against electric shock." + name = "insulated gloves" + icon_state = "yellow" + inhand_icon_state = "ygloves" + greyscale_colors = null + + resistance_flags = NONE + armor_type = /datum/armor/gloves_chameleon + actions_types = list(/datum/action/item_action/chameleon/change/gloves) + +/obj/item/clothing/gloves/chameleon/broken + +/obj/item/clothing/gloves/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham hat +/datum/armor/head_chameleon + melee = 5 + bullet = 5 + laser = 5 + fire = 50 + acid = 50 + +/obj/item/clothing/head/chameleon + name = "grey cap" + desc = "It's a baseball hat in a tasteful grey colour." + icon = 'icons/obj/clothing/head/hats.dmi' + worn_icon = 'icons/mob/clothing/head/hats.dmi' + icon_state = "greysoft" + resistance_flags = NONE + armor_type = /datum/armor/head_chameleon + actions_types = list(/datum/action/item_action/chameleon/change/hat) + +/obj/item/clothing/head/chameleon/broken + +/obj/item/clothing/head/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +/obj/item/clothing/head/chameleon/drone + actions_types = list(/datum/action/item_action/chameleon/change/hat, /datum/action/item_action/chameleon/drone/togglehatmask, /datum/action/item_action/chameleon/drone/randomise) + item_flags = DROPDEL + // The camohat, I mean, holographic hat projection, is part of the drone itself. + armor_type = /datum/armor/none + // which means it offers no protection, it's just air and light + +/obj/item/clothing/head/chameleon/drone/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + var/datum/action/item_action/chameleon/change/hat/hat = locate() in actions + hat?.random_look() + +// Cham mask, voice changer included +/datum/armor/mask_chameleon + melee = 5 + bullet = 5 + laser = 5 + bio = 100 + fire = 50 + acid = 50 + +/obj/item/clothing/mask/chameleon + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate + icon_state = "gas_alt" + inhand_icon_state = "gas_alt" + resistance_flags = NONE + armor_type = /datum/armor/mask_chameleon + clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/chameleon/change/mask) + /// Is our voice changer enabled or disabled? + var/voice_change = TRUE + +/obj/item/clothing/mask/chameleon/attack_self(mob/user) + voice_change = !voice_change + to_chat(user, span_notice("The voice changer is now [voice_change ? "on" : "off"]!")) + +/obj/item/clothing/mask/chameleon/broken + +/obj/item/clothing/mask/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +/obj/item/clothing/mask/chameleon/drone + actions_types = list(/datum/action/item_action/chameleon/change/mask, /datum/action/item_action/chameleon/drone/togglehatmask, /datum/action/item_action/chameleon/drone/randomise) + item_flags = DROPDEL + //Same as the drone chameleon hat, undroppable and no protection + armor_type = /datum/armor/none + voice_change = FALSE + +/obj/item/clothing/mask/chameleon/drone/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + var/datum/action/item_action/chameleon/change/mask/mask = locate() in actions + mask?.random_look() + +/obj/item/clothing/mask/chameleon/drone/attack_self(mob/user) + to_chat(user, span_notice("[src] does not have a voice changer.")) + +// Cham shoes, including chameleon noslips +/datum/armor/shoes_chameleon + melee = 10 + bullet = 10 + laser = 10 + bio = 90 + fire = 50 + acid = 50 + +/obj/item/clothing/shoes/chameleon + name = "black shoes" + desc = "A pair of black shoes." + icon_state = "sneakers" + inhand_icon_state = "sneakers_back" + greyscale_colors = "#545454#ffffff" + greyscale_config = /datum/greyscale_config/sneakers + greyscale_config_worn = /datum/greyscale_config/sneakers/worn + greyscale_config_inhand_left = /datum/greyscale_config/sneakers/inhand_left + greyscale_config_inhand_right = /datum/greyscale_config/sneakers/inhand_right + resistance_flags = NONE + armor_type = /datum/armor/shoes_chameleon + actions_types = list(/datum/action/item_action/chameleon/change/shoes) + +/obj/item/clothing/shoes/chameleon/Initialize(mapload) + . = ..() + create_storage(storage_type = /datum/storage/pockets/shoes) + +/obj/item/clothing/shoes/chameleon/broken + +/obj/item/clothing/shoes/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +/obj/item/clothing/shoes/chameleon/noslip + clothing_traits = list(TRAIT_NO_SLIP_WATER) + can_be_bloody = FALSE + +/obj/item/clothing/shoes/chameleon/noslip/broken + +/obj/item/clothing/shoes/chameleon/noslip/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham backpack +/obj/item/storage/backpack/chameleon + name = "backpack" + actions_types = list(/datum/action/item_action/chameleon/change/backpack) + +/obj/item/storage/backpack/chameleon/broken + +/obj/item/storage/backpack/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham belt (bonus: is silent) +/obj/item/storage/belt/chameleon + name = "toolbelt" + desc = "Holds tools." + actions_types = list(/datum/action/item_action/chameleon/change/belt) + +/obj/item/storage/belt/chameleon/Initialize(mapload) + . = ..() + atom_storage.silent = TRUE + +/obj/item/storage/belt/chameleon/broken + +/obj/item/storage/belt/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham headset +/obj/item/radio/headset/chameleon + name = "radio headset" + actions_types = list(/datum/action/item_action/chameleon/change/headset) + +/obj/item/radio/headset/chameleon/broken + +/obj/item/radio/headset/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham PDA +/obj/item/modular_computer/pda/chameleon + name = "tablet" + actions_types = list(/datum/action/item_action/chameleon/change/tablet) + +/obj/item/modular_computer/pda/chameleon/broken + +/obj/item/modular_computer/pda/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham Stamp +/obj/item/stamp/chameleon + actions_types = list(/datum/action/item_action/chameleon/change/stamp) + +/obj/item/stamp/chameleon/broken + +/obj/item/stamp/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +// Cham neck-thing +/datum/armor/neck_chameleon + fire = 50 + acid = 50 + +/obj/item/clothing/neck/chameleon + name = "black tie" + desc = "A neosilk clip-on tie." + icon_state = "detective" //we use this icon_state since the other ones are all generated by GAGS. + resistance_flags = NONE + armor_type = /datum/armor/neck_chameleon + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/chameleon/change/neck) + +/obj/item/clothing/neck/chameleon/broken + +/obj/item/clothing/neck/chameleon/broken/Initialize(mapload) + . = ..() + BREAK_CHAMELEON_ACTION(src) + +#undef BREAK_CHAMELEON_ACTION diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index acf1e23f40b..0d581b67923 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -524,28 +524,12 @@ . = ..() REMOVE_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) -/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete +/obj/item/clothing/glasses/thermal/syndi name = "chameleon thermals" desc = "A pair of thermal optic goggles with an onboard chameleon generator." - special_desc_requirement = EXAMINE_CHECK_SYNDICATE // SKYRAT EDIT - special_desc = "Chameleon thermal goggles employed by the Syndicate in infiltration operations." //SKYRAT EDIT, I don't think the regular description persists through chameleon changes. - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() + actions_types = list(/datum/action/item_action/chameleon/change/glasses/no_preset) + special_desc_requirement = EXAMINE_CHECK_SYNDICATE // SKYRAT EDIT ADDITION + special_desc = "Chameleon thermal goggles employed by the Syndicate in infiltration operations." //SKYRAT EDIT ADDITION, I don't think the regular description persists through chameleon changes. /obj/item/clothing/glasses/thermal/monocle name = "thermoncle" diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 2233e639a43..5018e4185c8 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -130,26 +130,7 @@ name = "chameleon security HUD" desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection." flash_protect = FLASH_PROTECTION_FLASH - - // Yes this code is the same as normal chameleon glasses, but we don't - // have multiple inheritance, okay? - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/hud/security/chameleon/Initialize(mapload) - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - add_item_action(chameleon_action) - -/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - + actions_types = list(/datum/action/item_action/chameleon/change/glasses/no_preset) /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch name = "eyepatch HUD" diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm index 8c97570acaa..f9fd629c1ed 100644 --- a/code/modules/library/skill_learning/skillchip.dm +++ b/code/modules/library/skill_learning/skillchip.dm @@ -42,7 +42,9 @@ var/cooldown = 5 MINUTES /// Cooldown for chip actions. COOLDOWN_DECLARE(chip_cooldown) - /// Used to determine if this is an abstract type or not. If this is meant to be an abstract type, set it to the type's path. Will be overridden by subsequent abstract parents. See /datum/action/item_action/chameleon/change/skillchip/initialize_disguises() + /// Used to determine if this is an abstract type or not. + /// If this is meant to be an abstract type, set it to the type's path. + /// Will be overridden by subsequent abstract parents. var/abstract_parent_type = /obj/item/skillchip /// Set to TRUE when the skill chip's effects are applied. Set to FALSE when they're not. var/active = FALSE diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index ba3df70da4c..104480d502f 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -31,6 +31,8 @@ update_suit_storage() if(slot_flags & (ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET)) update_pockets() + if(slot_flags & ITEM_SLOT_HANDS) + update_held_items() /// Updates features and clothing attached to a specific limb with limb-specific offsets /mob/living/carbon/proc/update_features(feature_key) diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 7a0dc44e231..5abab29cb56 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -134,8 +134,10 @@ ethereal.set_facial_haircolor(dead_color, override = TRUE, update = FALSE) ethereal.set_haircolor(dead_color, override = TRUE, update = TRUE) -/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity) +/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity, protection) SIGNAL_HANDLER + if(protection & EMP_PROTECT_SELF) + return EMPeffect = TRUE spec_updatehealth(H) to_chat(H, span_notice("You feel the light of your body leave you.")) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm index aa9fa882523..69c2ac3e8cf 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm @@ -17,6 +17,15 @@ cut_overlay(I) drone_overlays[cache_index] = null +/mob/living/simple_animal/drone/update_clothing(slot_flags) + if(slot_flags & ITEM_SLOT_HEAD) + update_worn_head() + if(slot_flags & ITEM_SLOT_MASK) + update_worn_mask() + if(slot_flags & ITEM_SLOT_HANDS) + update_held_items() + if(slot_flags & (ITEM_SLOT_HANDS|ITEM_SLOT_BACKPACK|ITEM_SLOT_DEX_STORAGE)) + update_inv_internal_storage() /mob/living/simple_animal/drone/update_held_items() remove_overlay(DRONE_HANDS_LAYER) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 98def711093..ae544889633 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -59,9 +59,6 @@ var/cached_multiplicative_actions_slowdown /// List of action hud items the user has var/list/datum/action/actions - /// A list of chameleon actions we have specifically - /// This can be unified with the actions list - var/list/datum/action/item_action/chameleon/chameleon_item_actions ///Cursor icon used when holding shift over things var/examine_cursor_icon = 'icons/effects/mouse_pointers/examine_pointer.dmi' diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 9b9274bc316..e671a299413 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -504,9 +504,7 @@ /obj/item/mod/module/dna_lock/emp_act(severity) . = ..() - if(. & EMP_PROTECT_SELF) - return - on_emp(src, severity) + on_emp(src, severity, .) /obj/item/mod/module/dna_lock/emag_act(mob/user, obj/item/card/emag/emag_card) . = ..() @@ -521,9 +519,10 @@ balloon_alert(user, "dna locked!") return FALSE -/obj/item/mod/module/dna_lock/proc/on_emp(datum/source, severity) +/obj/item/mod/module/dna_lock/proc/on_emp(datum/source, severity, protection) SIGNAL_HANDLER - + if(protection & EMP_PROTECT_SELF) + return dna = null /obj/item/mod/module/dna_lock/proc/on_emag(datum/source, mob/user, obj/item/card/emag/emag_card) diff --git a/code/modules/paperwork/paperwork.dm b/code/modules/paperwork/paperwork.dm index 767e2262cab..03f22177f9e 100644 --- a/code/modules/paperwork/paperwork.dm +++ b/code/modules/paperwork/paperwork.dm @@ -41,20 +41,25 @@ /obj/item/paperwork/attackby(obj/item/attacking_item, mob/user, params) . = ..() + if(.) + return - if(!stamped) - if(istype(attacking_item, /obj/item/stamp)) - if(istype(attacking_item, stamp_requested)) //chameleon stamp does not work, this is a CRITICAL issue - add_stamp() - to_chat(user, span_notice("You skim through the papers until you find a field reading 'STAMP HERE', and complete the paperwork.")) - else - if(istype(attacking_item, /obj/item/stamp/chameleon)) - var/obj/item/stamp/chameleon/chameleon_stamp = attacking_item - to_chat(user, span_notice("[chameleon_stamp] morphs into the appropriate stamp, which you use to complete the paperwork.")) - chameleon_stamp.chameleon_action.update_item(stamp_requested) - add_stamp() - else - to_chat(user, span_warning("You hunt through the papers for somewhere to use the [attacking_item], but can't find anything.")) + if(stamped || istype(attacking_item, /obj/item/stamp)) + return + + if(istype(attacking_item, stamp_requested)) + add_stamp() + to_chat(user, span_notice("You skim through the papers until you find a field reading 'STAMP HERE', and complete the paperwork.")) + return TRUE + var/datum/action/item_action/chameleon/change/stamp/stamp_action = locate() in attacking_item.actions + if(isnull(stamp_action)) + to_chat(user, span_warning("You hunt through the papers for somewhere to use [attacking_item], but can't find anything.")) + return TRUE + + to_chat(user, span_notice("[attacking_item] morphs into the appropriate stamp, which you use to complete the paperwork.")) + stamp_action.update_look(stamp_requested) + add_stamp() + return TRUE /obj/item/paperwork/examine_more(mob/user) . = ..() diff --git a/tgstation.dme b/tgstation.dme index 0c1d7c5e7e5..5c2a2c64224 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3299,8 +3299,14 @@ #include "code\modules\client\verbs\ping.dm" #include "code\modules\client\verbs\suicide.dm" #include "code\modules\client\verbs\who.dm" -#include "code\modules\clothing\chameleon.dm" #include "code\modules\clothing\clothing.dm" +#include "code\modules\clothing\chameleon\_chameleon_action.dm" +#include "code\modules\clothing\chameleon\_chameleon_outfit.dm" +#include "code\modules\clothing\chameleon\chameleon_action_subtypes.dm" +#include "code\modules\clothing\chameleon\chameleon_drone.dm" +#include "code\modules\clothing\chameleon\chameleon_gun.dm" +#include "code\modules\clothing\chameleon\chameleon_scanner.dm" +#include "code\modules\clothing\chameleon\generic_chameleon_clothing.dm" #include "code\modules\clothing\ears\_ears.dm" #include "code\modules\clothing\glasses\_glasses.dm" #include "code\modules\clothing\glasses\engine_goggles.dm"