Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
04323a7
mimic component started
Jan 18, 2026
3940bc1
Add skeleton for base mimic ability and tying to mimicry datum.
Jan 19, 2026
fae9037
Merge branch 'Monkestation:master' into mimic
Coll6 Jan 19, 2026
dfbcd11
Base item mimicing added, mimic goes invisible and has basic click an…
Jan 19, 2026
fbabcb4
attempt to rewrite and add proper GC
Jan 24, 2026
466fe8f
add 3 second cool down after item is deleted, qdel signal on mimicked…
Jan 24, 2026
58b918a
Using undense trait and readded invisibility readding tracking.
Jan 25, 2026
4bf020e
Moved mob traits and applied via list, Register and unregister procs …
Jan 28, 2026
274d99d
Merge branch 'Monkestation:master' into mimic
Coll6 Jan 29, 2026
aea7b10
mimic is not a component
JohnFulpWillard Feb 1, 2026
c0931e4
Merge pull request #30 from JohnFulpWillard/mimic-no-component
Coll6 Feb 1, 2026
a4aca3a
Stubbed in damage reflection, atom integrity adjustment for weight cl…
Feb 3, 2026
074631b
Spoof resistance flags component, remove mimicry proc, spawned disk d…
Feb 28, 2026
c7db1b5
Change to drop location. Prevent mimic while vent crawling.
Feb 28, 2026
51d67b1
Merge branch 'master' into mimic
Coll6 Feb 28, 2026
057e708
oops. Linters thank you.
Feb 28, 2026
4ac4391
Why don't these things complain on compile UGH
Feb 28, 2026
8d4160f
Add preattack check for mimic_disguise and for atom_storage base_item…
Mar 5, 2026
47dcf89
Not really sure where or how to put this. Assuming this will just be …
Mar 5, 2026
c86960a
Merge branch 'master' into mimic
Coll6 Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/__DEFINES/sight.dm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
// INVISIBILITY PRIORITIES

#define INVISIBILITY_PRIORITY_ADMIN 100
#define INVISIBILITY_PRIORITY_ABSTRACT 21 //for abstract objects/mobs (e.g. mimic while mimicing), things that are not really there.
#define INVISIBILITY_PRIORITY_TURRET_COVER 20
#define INVISIBILITY_PRIORITY_BASIC_ANTI_INVISIBILITY 10
#define INVISIBILITY_PRIORITY_NONE 0
Expand Down
26 changes: 21 additions & 5 deletions code/__HELPERS/duplicating.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars, list(
"area",
"atmos_adjacent_turfs",
"bodyparts",
"buckled_mobs",
"ckey",
"client_mobs_in_contents",
"_listen_lookup",
"computer_id",
"contents",
"cooldowns",
"currently_z_moving",
"_datum_components",
"external_organs",
"external_organs_slot",
"force_moving",
"grab_state",
"group",
"hand_bodyparts",
"held_items",
Expand All @@ -31,18 +35,29 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars, list(
"locs",
"managed_overlays",
"managed_vis_overlays",
"moving_diagonally",
"moving_from_pull",
"move_packet",
"overlays",
"overlays_standing",
"orbit_target",
"orbiters",
"orbiting",
"parent",
"parent_type",
"pixloc",
"power_supply",
"pulledby",
"pulling",
"quirks",
"reagents",
"spatial_grid_key",
"_signal_procs",
"stat",
"status_effects",
"_status_traits",
"tag",
"thrownby",
"tgui_shared_states",
"type",
"update_on_z",
Expand All @@ -55,17 +70,17 @@ GLOBAL_PROTECT(duplicate_forbidden_vars)
/**
* # duplicate_object
*
* Makes a copy of an item and transfers most vars over, barring GLOB.duplicate_forbidden_vars
* Makes a copy of a movable atom and transfers most vars over, barring GLOB.duplicate_forbidden_vars
* Args:
* original - Atom being duplicated
* original - Movable atom being duplicated
* spawning_location - Turf where the duplicated atom will be spawned at.
*/
/proc/duplicate_object(atom/original, turf/spawning_location)
/proc/duplicate_object(atom/movable/original, turf/spawning_location)
RETURN_TYPE(original.type)
if(!original)
if(!original || !istype(original))
return

var/atom/made_copy = new original.type(spawning_location)
var/atom/movable/made_copy = new original.type(spawning_location)

for(var/atom_vars in original.vars - GLOB.duplicate_forbidden_vars)
if(islist(original.vars[atom_vars]))
Expand Down Expand Up @@ -94,4 +109,5 @@ GLOBAL_PROTECT(duplicate_forbidden_vars)
for(var/datum/quirk/original_quirks as anything in original_living.quirks)
copied_living.add_quirk(original_quirks.type)

made_copy.forceMove(spawning_location)
return made_copy
2 changes: 2 additions & 0 deletions code/_onclick/item_attack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
/obj/item/proc/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers) //do stuff before attackby!
if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, target, user, modifiers, attack_modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
return TRUE
if(target.GetComponent(/datum/component/mimic_disguise) && !(user?.istate & (ISTATE_HARM)))
return TRUE // Mimicked objects should not be attacked when not trying to harm them.
return FALSE //return TRUE to avoid calling attackby after this proc does stuff

/**
Expand Down
35 changes: 35 additions & 0 deletions code/datums/components/mimic_disguise.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#define MIMIC_SPOOF_RESIST_MASK ( \
LAVA_PROOF | \
FIRE_PROOF | \
UNACIDABLE | \
ACID_PROOF | \
INDESTRUCTIBLE \
)
/datum/component/mimic_disguise
dupe_mode = COMPONENT_DUPE_UNIQUE
var/spoofed_flags = 0

/datum/component/mimic_disguise/Initialize()
. = ..()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
var/obj/item/disguise_item = parent
var/to_spoof = disguise_item.resistance_flags & MIMIC_SPOOF_RESIST_MASK

if(to_spoof)
spoofed_flags = to_spoof
disguise_item.resistance_flags &= ~to_spoof

if(!(disguise_item.obj_flags & CAN_BE_HIT)) // Mimics are attackable.
disguise_item.obj_flags |= CAN_BE_HIT

/datum/component/mimic_disguise/Destroy(force)
if(QDELETED(parent))
return ..()
var/obj/item/disguise_item = parent
if(spoofed_flags)
disguise_item.resistance_flags |= spoofed_flags
if(!(initial(disguise_item.obj_flags) & CAN_BE_HIT))
disguise_item.obj_flags &= ~CAN_BE_HIT
return ..()
#undef MIMIC_SPOOF_RESIST_MASK
194 changes: 194 additions & 0 deletions code/datums/components/perfect_mimicry.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#define INTEGRITY_PER_WCLASS 10

/mob/living/proc/grant_mimicry()
var/datum/action/cooldown/mimic_ability/mimic_object/action = new(src)
action.Grant(src)

/mob/living/proc/remove_mimicry()
for(var/datum/action/cooldown/mimic_ability/mimic_object/action in src.actions)
action.Remove(src)

/*
* Mimicry actions
*/
/datum/action/cooldown/mimic_ability
name = "Base Mimic Ability"
desc = "You should not be seeing this. This is an error alert developers."
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED

var/cooldown_after_use = 3 SECONDS // Cooldown after mimicry ends

/datum/action/cooldown/mimic_ability/mimic_object
name = "Mimic Object"
desc = "Take on the appearance and behavior of a nearby object. Use again to reveal yourself."

click_to_activate = TRUE
cooldown_time = 1 SECOND
ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'

var/static/list/allowed_objects = list() // typecache of allowed objects to mimic
var/static/list/banned_objects = list(/obj/item/folder/biscuit, /obj/item/modular_computer, /obj/item/card, \
/obj/item/holochip, /obj/item/stack
) // typecache of banned objects that should absolutely not be mimicked
var/list/applied_mob_traits = list(TRAIT_HANDS_BLOCKED, TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED, TRAIT_NOBREATH)

COOLDOWN_DECLARE(move_cooldown)
var/obj/mimicked_object
var/obj/fake_storage

/datum/action/cooldown/mimic_ability/mimic_object/proc/block_abilities()
SIGNAL_HANDLER

// Item adjustments for specific cases.
/datum/action/cooldown/mimic_ability/mimic_object/proc/handle_mimic_target(obj/item/target_item)
if(!isitem(target_item))
return
var/obj/item/new_item
if(istype(target_item, /obj/item/disk/nuclear)) // Can mimic disk but it is fake and can be destroyed.
var/obj/item/disk/nuclear/fake/nuclear = new(owner.drop_location())
var/datum/component/stationloving/stationcomp = nuclear.GetComponent(/datum/component/stationloving)
if(stationcomp)
stationcomp.allow_item_destruction = TRUE
new_item = nuclear
if(!istype(new_item))
new_item = duplicate_object(target_item, owner.drop_location())
new_item.remove_filter(HOVER_OUTLINE_FILTER)
new_item.item_flags &= ~(IN_INVENTORY | IN_STORAGE) // Prevent hover outline when mimicking inventory items.
new_item.AddComponent(/datum/component/mimic_disguise)
if(new_item.uses_integrity) // Mimicked items can break easier
var/weight_multiplier = max(1, new_item.w_class)
var/adjusted_integrity = 5 + (weight_multiplier * INTEGRITY_PER_WCLASS)
new_item.modify_max_integrity(clamp(adjusted_integrity, 10, 60))

return new_item

/datum/action/cooldown/mimic_ability/mimic_object/proc/reflect_damage(datum/source, damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
SIGNAL_HANDLER
if(!damage_amount)
return
if(isliving(owner))
var/mob/living/living_owner = owner
living_owner.apply_damage(damage_amount, damage_type, null, 0, FALSE, TRUE, 0, 0, NONE, attack_dir)

/datum/action/cooldown/mimic_ability/mimic_object/proc/handle_speech(datum/source, list/speech_args)
SIGNAL_HANDLER

if(QDELETED(mimicked_object)) // Return early and prevent speech. Object cleanup should be happening.
speech_args[SPEECH_MESSAGE] = ""
return

mimicked_object.say(speech_args[SPEECH_MESSAGE], speech_args[SPEECH_BUBBLE_TYPE], \
speech_args[SPEECH_SPANS], speech_args[SPEECH_SANITIZE], speech_args[SPEECH_LANGUAGE], \
speech_args[SPEECH_IGNORE_SPAM], speech_args[SPEECH_FORCED], speech_args[SPEECH_FILTERPROOF], \
speech_args[SPEECH_RANGE], speech_args[SPEECH_SAYMODE])

speech_args[SPEECH_MESSAGE] = ""

/datum/action/cooldown/mimic_ability/mimic_object/PreActivate(atom/mimic_target)
if(!isnull(mimicked_object))
return ..()
if(mimic_target == owner)
to_chat(owner, span_notice("You cannot mimic yourself."))
return FALSE
if(get_dist(owner, mimic_target) > 3)
to_chat(owner, span_notice("[mimic_target.name] is too far away."))
return FALSE
if(!is_allowed_object(mimic_target))
to_chat(owner, span_notice("[mimic_target.name] is too complex to mimic."))
return FALSE
if(owner.movement_type & VENTCRAWLING)
to_chat(owner, span_notice("You cannot mimic objects while ventcrawling."))
return FALSE
return ..()

/datum/action/cooldown/mimic_ability/mimic_object/proc/is_allowed_object(obj/item/target_item)
if(!isitem(target_item))
return FALSE
if(!target_item.uses_integrity)
return FALSE
if(length(banned_objects) && is_type_in_list(target_item, banned_objects))
return FALSE
if(length(allowed_objects) && !is_type_in_list(target_item, allowed_objects))
return FALSE
return TRUE

/datum/action/cooldown/mimic_ability/mimic_object/Activate(atom/mimic_target)
if(!isnull(mimicked_object))
stop_mimicry()
click_to_activate = TRUE
StartCooldown(cooldown_after_use)
return TRUE
if(start_mimicry(mimic_target))
click_to_activate = FALSE
StartCooldown()
return TRUE
return FALSE

/datum/action/cooldown/mimic_ability/mimic_object/proc/start_mimicry(obj/mimic_item)
mimicked_object = handle_mimic_target(mimic_item)
if(isnull(mimicked_object))
return
RegisterSignal(mimicked_object, COMSIG_ATOM_RELAYMOVE, PROC_REF(on_user_move))
RegisterSignal(mimicked_object, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(reflect_damage))
RegisterSignal(mimicked_object, COMSIG_QDELETING, PROC_REF(on_object_qdel))
RegisterSignal(owner, COMSIG_MOB_SAY, PROC_REF(handle_speech))
owner.forceMove(mimicked_object)
mimicked_object.buckle_mob(owner)
if(length(applied_mob_traits))
owner.add_traits(applied_mob_traits, REF(src))
if(mimicked_object.atom_storage)
fake_storage = new(src)
fake_storage.clone_storage(mimicked_object.atom_storage)
mimicked_object.atom_storage.set_real_location(fake_storage)
return mimicked_object

/datum/action/cooldown/mimic_ability/mimic_object/proc/stop_mimicry()
owner.forceMove(mimicked_object.drop_location())
UnregisterSignal(owner, COMSIG_MOB_SAY)
if(mimicked_object.atom_storage)
mimicked_object.atom_storage.remove_all(mimicked_object.drop_location())
if(length(applied_mob_traits))
owner.remove_traits(applied_mob_traits, REF(src))
if(fake_storage)
QDEL_NULL(fake_storage)
if(QDELETED(mimicked_object))
mimicked_object = null
else
QDEL_NULL(mimicked_object)

///The user can move while inside of the item.
/datum/action/cooldown/mimic_ability/mimic_object/proc/on_user_move(obj/moved_source, mob/user_moving, direction)
SIGNAL_HANDLER

if(!COOLDOWN_FINISHED(src, move_cooldown))
return COMSIG_BLOCK_RELAYMOVE
var/turf/next = get_step(moved_source, direction)
var/turf/current = get_turf(moved_source)
if(!istype(next) || !istype(current))
return COMSIG_BLOCK_RELAYMOVE
if(next.density)
return COMSIG_BLOCK_RELAYMOVE
if(!isturf(moved_source.loc))
return COMSIG_BLOCK_RELAYMOVE

step(moved_source, direction)
var/last_move_diagonal = ((direction & (direction - 1)) && (moved_source.loc == next))
COOLDOWN_START(src, move_cooldown, ((last_move_diagonal ? 1 : 0.5)) SECOND)

if(QDELETED(src))
return COMSIG_BLOCK_RELAYMOVE
return TRUE

///Called when the host of the mob is qdeleted.
/datum/action/cooldown/mimic_ability/mimic_object/proc/on_object_qdel(datum/source, force)
SIGNAL_HANDLER
stop_mimicry()
if(QDELETED(owner))
return
for(var/datum/action/cooldown/mimic_ability/mimic_abilities in owner.actions)
mimic_abilities.click_to_activate = TRUE
mimic_abilities.StartCooldown(mimic_abilities.cooldown_after_use)

/datum/action/cooldown/mimic_ability/throw_self

#undef INTEGRITY_PER_WCLASS
2 changes: 2 additions & 0 deletions code/game/atoms/atom_tool_acts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
// We have to manually handle storage in item_interaction because storage is blocking in 99% of interactions, which stifles a lot
// Yeah it sucks not being able to signalize this, but the other option is to have a second signal here just for storage which is also not great
if(atom_storage)
if(src.GetComponent(/datum/component/mimic_disguise) && user?.istate & (ISTATE_HARM))
return NONE // Mimicked storages should be attackable when hit with a tool.
if(is_left_clicking)
if(atom_storage.insert_on_attack)
return atom_storage.item_interact_insert(user, tool)
Expand Down
13 changes: 8 additions & 5 deletions code/game/objects/items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -447,16 +447,19 @@

. += "[gender == PLURAL ? "They are" : "It is"] a [weight_class_to_text(w_class)] item."

if(resistance_flags & INDESTRUCTIBLE)
// Check for mimic disguise component
var/datum/component/mimic_disguise/fake_item = GetComponent(/datum/component/mimic_disguise)
var/flags_to_check = resistance_flags | (fake_item ? fake_item.spoofed_flags : 0)
if(flags_to_check & INDESTRUCTIBLE)
. += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!"
else
if(resistance_flags & LAVA_PROOF)
if(flags_to_check & LAVA_PROOF)
. += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!"
if(resistance_flags & (ACID_PROOF | UNACIDABLE))
if(flags_to_check & (ACID_PROOF | UNACIDABLE))
. += "[src] looks pretty robust! It'd probably be able to withstand acid!"
if(resistance_flags & FREEZE_PROOF)
if(flags_to_check & FREEZE_PROOF)
. += "[src] is made of cold-resistant materials."
if(resistance_flags & FIRE_PROOF)
if(flags_to_check & FIRE_PROOF)
. += "[src] is made of fire-retardant materials."
return

Expand Down
2 changes: 2 additions & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@
#include "code\datums\components\manual_blinking.dm"
#include "code\datums\components\manual_breathing.dm"
#include "code\datums\components\material_bane.dm"
#include "code\datums\components\mimic_disguise.dm"
#include "code\datums\components\mind_linker.dm"
#include "code\datums\components\mirage_border.dm"
#include "code\datums\components\mirv.dm"
Expand All @@ -1287,6 +1288,7 @@
#include "code\datums\components\parry.dm"
#include "code\datums\components\payment.dm"
#include "code\datums\components\pellet_cloud.dm"
#include "code\datums\components\perfect_mimicry.dm"
#include "code\datums\components\phylactery.dm"
#include "code\datums\components\pinata.dm"
#include "code\datums\components\plundering_attacks.dm"
Expand Down
Loading