[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>
This commit is contained in:
SkyratBot
2023-07-29 21:09:06 -04:00
committed by GitHub
co-authored by MrMelbert Bloop
parent 7b2447ea08
commit 598952c1c2
25 changed files with 1376 additions and 1192 deletions
@@ -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"
+3 -2
View File
@@ -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")
+2 -2
View File
@@ -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)
+5 -3
View File
@@ -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
/**
+19 -20
View File
@@ -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, "<span class='notice'>You covertly start to scan [target] with \the [src], hoping to pick up a wireless ID card signal...</span>")
target.balloon_alert(user, "scanning ID card...")
if(!do_after(user, 2 SECONDS, target))
to_chat(user, "<span class='notice'>The scan was interrupted.</span>")
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, "<span class='notice'>The scan failed to locate any ID cards.</span>")
target.balloon_alert(user, "no IDs!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
var/selected_id = pick(target_id_cards)
to_chat(user, "<span class='notice'>You successfully sync your [src] with \the [selected_id].</span>")
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, "<span class='notice'>You covertly start to scan [target] with your [src], hoping to pick up a wireless ID card signal...</span>")
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, "<span class='notice'>The scan failed to locate any ID cards.</span>")
target.balloon_alert(user, "no IDs!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
var/selected_id = pick(target_id_cards)
to_chat(user, "<span class='notice'>You successfully sync your [src] with \the [selected_id].</span>")
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)
+1 -17
View File
@@ -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."
@@ -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)
File diff suppressed because it is too large Load Diff
@@ -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
@@ -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.<br>\
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)
@@ -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)
@@ -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
@@ -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)
@@ -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 <i>Chameleon Scanner 6000</i>.")
. += span_red("<b>Left click</b> 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("<b>Right click</b> 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
@@ -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
+4 -20
View File
@@ -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"
+1 -20
View File
@@ -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"
@@ -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
@@ -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)
@@ -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."))
@@ -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)
-3
View File
@@ -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'
+4 -5
View File
@@ -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)
+18 -13
View File
@@ -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)
. = ..()
+7 -1
View File
@@ -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"