From 095f93c416574efc16f5178f1c94570057c16db5 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Sun, 25 Jan 2026 17:56:44 -0500
Subject: [PATCH] Converts mimic mobs to basic mobs (#31424)
* Converts mimic mobs to basic mobs
* Unit tests
* Units
---
.../RandomRuins/SpaceRuins/wizardcrash.dmm | 2 +-
code/datums/components/ranged_attacks.dm | 2 +
.../gamemodes/malfunction/Malf_Modules.dm | 2 +-
code/modules/events/brand_intelligence.dm | 4 +-
.../mob/living/basic/hostile/mimic_mobs.dm | 438 ++++++++++++++++++
.../simple_animal/hostile/mimic_mobs.dm | 338 --------------
code/modules/paperwork/faxmachine.dm | 2 +-
.../projectile/magic_projectiles.dm | 8 +-
.../projectile/special_projectiles.dm | 4 +-
paradise.dme | 2 +-
tools/ci/check_simplemob_additions.py | 6 -
11 files changed, 451 insertions(+), 357 deletions(-)
create mode 100644 code/modules/mob/living/basic/hostile/mimic_mobs.dm
delete mode 100644 code/modules/mob/living/simple_animal/hostile/mimic_mobs.dm
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
index 18b9e4af228..727bef432a8 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
@@ -396,7 +396,7 @@
/turf/simulated/floor/mineral/plasma,
/area/ruin/space/powered)
"bz" = (
-/mob/living/simple_animal/hostile/mimic/crate,
+/mob/living/basic/mimic/crate,
/obj/effect/spawner/random/pool/spaceloot/syndicate/mixed,
/turf/simulated/floor/plating,
/area/ruin/space/powered)
diff --git a/code/datums/components/ranged_attacks.dm b/code/datums/components/ranged_attacks.dm
index c882a8fdf3c..6e0d098c39b 100644
--- a/code/datums/components/ranged_attacks.dm
+++ b/code/datums/components/ranged_attacks.dm
@@ -87,4 +87,6 @@
target_zone = ran_zone()
casing.fire(target, firer, null, null, null, target_zone, 0, firer)
casing.update_appearance()
+ if(istype(casing, /obj/item/ammo_casing/energy))
+ qdel(casing)
SEND_SIGNAL(parent, COMSIG_BASICMOB_POST_ATTACK_RANGED, target, modifiers)
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index cdf67f17098..5092c6ffae0 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -553,7 +553,7 @@
/datum/spell/ai_spell/ranged/override_machine/proc/animate_machine(obj/machinery/M, mob/user)
if(M && !QDELETED(M))
- new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, user, 1)
+ new /mob/living/basic/mimic/copy/machine(get_turf(M), M, user, 1)
//Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting.
/datum/ai_module/place_cyborg_transformer
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index 692fb8261cf..bc6db1777c5 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -62,10 +62,8 @@
// let them become "normal" after turning
upriser.shoot_inventory = FALSE
upriser.aggressive = FALSE
- var/mob/living/simple_animal/hostile/mimic/copy/vendor/M = new(upriser.loc, upriser, null)
+ var/mob/living/basic/mimic/copy/vendor/M = new(upriser.loc, upriser, null)
M.faction = list("profit")
- M.speak = rampant_speeches.Copy()
- M.speak_chance = 15
else
explosion(upriser.loc, -1, 1, 2, 4, 0, cause = "Brand Intelligence Uprising")
qdel(upriser)
diff --git a/code/modules/mob/living/basic/hostile/mimic_mobs.dm b/code/modules/mob/living/basic/hostile/mimic_mobs.dm
new file mode 100644
index 00000000000..f8efa986108
--- /dev/null
+++ b/code/modules/mob/living/basic/hostile/mimic_mobs.dm
@@ -0,0 +1,438 @@
+/mob/living/basic/mimic
+ name = "crate"
+ desc = "A rectangular steel crate."
+ icon = 'icons/obj/crates.dmi'
+ icon_state = "crate"
+ icon_living = "crate"
+
+ response_help_simple = "touch the"
+ response_help_continuous = "touches the"
+ response_disarm_simple = "push the"
+ response_disarm_continuous = "pushes the"
+ response_harm_simple = "hit the"
+ response_harm_continuous = "hits the"
+ speed = 0
+ maxHealth = 250
+ health = 250
+
+ mob_biotypes = NONE
+
+ harm_intent_damage = 5
+ melee_attack_cooldown_min = 1.5 SECONDS
+ melee_attack_cooldown_max = 2.5 SECONDS
+ melee_damage_lower = 8
+ melee_damage_upper = 12
+ attack_sound = 'sound/weapons/bite.ogg'
+ speak_emote = list("creaks")
+
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minimum_survivable_temperature = 0
+
+ faction = list("mimic")
+
+ gold_core_spawnable = HOSTILE_SPAWN
+ basic_mob_flags = DEL_ON_DEATH
+ ai_controller = /datum/ai_controller/basic_controller/mimic
+ /// Used for if the mob should be affected by EMPs or ions
+ var/is_electronic = 0
+
+/mob/living/basic/mimic/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ai_retaliate)
+ add_language("Galactic Common")
+ set_default_language(GLOB.all_languages["Galactic Common"])
+ AddComponent(/datum/component/aggro_emote, emote_list = list("growls"))
+
+/mob/living/basic/mimic/emp_act(severity)
+ if(is_electronic)
+ switch(severity)
+ if(1)
+ death()
+ if(2)
+ adjustBruteLoss(50)
+ ..(severity)
+
+// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies.
+/mob/living/basic/mimic/crate
+ attack_verb_simple = "bite"
+ attack_verb_continuous = "bites"
+ ai_controller = /datum/ai_controller/basic_controller/mimic/crate
+ /// Has the crate mimic been opened?
+ var/attempt_open = FALSE
+
+// Pickup loot
+/mob/living/basic/mimic/crate/Initialize(mapload)
+ . = ..()
+ for(var/obj/item/I in loc)
+ I.loc = src
+
+/mob/living/basic/mimic/crate/Move(atom/newloc, direct, glide_size_override, update_dir)
+ . = ..()
+ if(prob(90))
+ icon_state = "[initial(icon_state)]_open"
+ else
+ icon_state = initial(icon_state)
+
+/mob/living/basic/mimic/crate/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(.)
+ icon_state = initial(icon_state)
+ if(prob(15) && iscarbon(target))
+ var/mob/living/carbon/C = target
+ C.Weaken(4 SECONDS)
+ C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
+
+/mob/living/basic/mimic/crate/proc/trigger()
+ if(!attempt_open)
+ visible_message("[src] starts to move!")
+ attempt_open = TRUE
+
+/mob/living/basic/mimic/crate/adjustHealth(amount, updating_health = TRUE)
+ trigger()
+ . = ..()
+
+/mob/living/basic/mimic/crate/Life(seconds, times_fired)
+ . = ..()
+ var/atom/target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+ if(!target)
+ icon_state = initial(icon_state)
+
+/mob/living/basic/mimic/crate/death(gibbed)
+ if(can_die())
+ var/obj/structure/closet/crate/C = new(get_turf(src))
+ // Put loot in crate
+ for(var/obj/O in src)
+ O.forceMove(C)
+ // due to `del_on_death`
+ return ..()
+
+GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window))
+
+/mob/living/basic/mimic/copy
+ health = 100
+ maxHealth = 100
+ gold_core_spawnable = NO_SPAWN
+ /// The mob that created the copy
+ var/mob/living/creator = null
+ /// Does this mob knock people down?
+ var/knockdown_people = 0
+ /// The image of the googly eyes we use
+ var/image/googly_eyes = null
+
+/mob/living/basic/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0)
+ . = ..()
+ CopyObject(copy, creator, destroy_original)
+
+/mob/living/basic/mimic/copy/Destroy()
+ creator = null
+ return ..()
+
+/mob/living/basic/mimic/copy/Life()
+ ..()
+ var/atom/target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+ if(!target && !ckey) // Objects eventually revert to normal if no one is around to terrorize
+ adjustBruteLoss(1)
+ googly_eyes.dir = get_dir(src, target)
+ for(var/mob/living/M in contents) // A fix for animated statues from the flesh to stone spell
+ death()
+
+/mob/living/basic/mimic/copy/death(gibbed)
+ if(can_die())
+ for(var/atom/movable/M in src)
+ M.loc = get_turf(src)
+ // Due to `del_on_death`
+ return ..()
+
+/mob/living/basic/mimic/copy/proc/ChangeOwner(mob/owner)
+ if(owner != creator)
+ ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
+ creator = owner
+ faction |= "\ref[owner]"
+
+/mob/living/basic/mimic/copy/proc/CheckObject(obj/O)
+ if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.protected_objects))
+ return 1
+ return 0
+
+/mob/living/basic/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0)
+ if(destroy_original || CheckObject(O))
+ O.loc = src
+ name = O.name
+ desc = O.desc
+ icon = O.icon
+ icon_state = O.icon_state
+ icon_living = icon_state
+ overlays = O.overlays
+ googly_eyes = image('icons/mob/mob.dmi', "googly_eyes")
+ overlays += googly_eyes
+ if(isstructure(O) || ismachinery(O))
+ health = (anchored * 50) + 50
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ if(O.density && O.anchored)
+ knockdown_people = TRUE
+ melee_damage_lower *= 2
+ melee_damage_upper *= 2
+ if(ismachinery(O))
+ is_electronic = TRUE
+ else if(isitem(O))
+ var/obj/item/I = O
+ health = 15 * I.w_class
+ melee_damage_lower = 2 + I.force
+ melee_damage_upper = 2 + I.force
+ ai_controller.movement_delay = 2 * I.w_class + 1
+ maxHealth = health
+ if(user)
+ creator = user
+ faction += "\ref[creator]" // very unique
+ if(destroy_original)
+ qdel(O)
+ return TRUE
+
+/mob/living/basic/mimic/copy/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(knockdown_people && . && prob(15) && iscarbon(target))
+ var/mob/living/carbon/C = target
+ C.Weaken(4 SECONDS)
+ C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
+
+/mob/living/basic/mimic/copy/machine
+ ai_controller = /datum/ai_controller/basic_controller/mimic/machine
+
+/mob/living/basic/mimic/copy/vendor
+ ai_controller = /datum/ai_controller/basic_controller/mimic/vendor
+ is_electronic = TRUE
+ /// The vendor we were turned from.
+ var/obj/machinery/economy/vending/orig_vendor
+
+/mob/living/basic/mimic/copy/vendor/CheckObject(obj/O)
+ return istype(O, /obj/machinery/economy/vending)
+
+/mob/living/basic/mimic/copy/vendor/Initialize(mapload, obj/machinery/economy/vending/base, mob/living/creator)
+ if(!base)
+ var/list/vendors = subtypesof(/obj/machinery/economy/vending)
+ var/obj/machinery/economy/vending/vendor_type = pick(vendors)
+ base = new vendor_type(src)
+
+ if(!istype(base))
+ qdel(src)
+ return
+
+ orig_vendor = base
+ orig_vendor.forceMove(src)
+ orig_vendor.aggressive = FALSE // just to be safe, in case this was converted
+
+ AddComponent(/datum/component/event_tracker, EVENT_BRAND_INTELLIGENCE)
+
+ return ..(mapload, base, creator, destroy_original = FALSE)
+
+/mob/living/basic/mimic/copy/vendor/event_cost()
+ . = list()
+ if(is_station_level((get_turf(src)).z) && stat != DEAD)
+ return list(ASSIGNMENT_SECURITY = 0.5, ASSIGNMENT_CREW = 1)
+
+/mob/living/basic/mimic/copy/vendor/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(. && target && Adjacent(target) && prob(30))
+ visible_message(SPAN_DANGER("[src] throws itself on top of [target], crushing [target.p_them()]!"))
+ orig_vendor.forceMove(get_turf(target)) // just to be sure it'll tilt onto them
+ orig_vendor.tilt(target) // geeeeet dunked on
+ orig_vendor = null
+ qdel(src)
+
+/mob/living/basic/mimic/copy/vendor/death(gibbed)
+ if(!QDELETED(orig_vendor))
+ orig_vendor.forceMove(get_turf(src))
+ // tilt over in place
+ orig_vendor.tilt_over()
+ if(prob(70))
+ orig_vendor.obj_break()
+ orig_vendor = null
+ return ..()
+
+/mob/living/basic/mimic/copy/ranged
+ ai_controller = /datum/ai_controller/basic_controller/mimic/gun
+ is_ranged = TRUE
+ projectile_type = /obj/item/ammo_casing/energy/disabler // We need to set this to avoid unit tests. This gets updated
+ /// Our gun object
+ var/obj/item/gun/our_gun = null
+
+/mob/living/basic/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0)
+ if(..())
+ AddComponent(/datum/component/aggro_emote, emote_list = list("aims menacingly"))
+ obj_damage = 0
+ is_ranged = TRUE
+ var/obj/item/gun/G = O
+ melee_damage_upper = G.force
+ melee_damage_lower = G.force - max(0, (G.force / 2))
+ ai_controller.movement_delay = 2 * G.w_class + 1
+ projectile_sound = G.fire_sound
+ our_gun = G
+ var/datum/component/ranged_attacks/comp = GetComponent(/datum/component/ranged_attacks)
+ if(istype(G, /obj/item/gun/magic))
+ var/obj/item/gun/magic/zapstick = G
+ var/obj/item/ammo_casing/magic/M = zapstick.ammo_type
+ projectile_type = initial(M.projectile_type)
+ comp.casing_type = null
+ comp.projectile_type = projectile_type
+ if(istype(G, /obj/item/gun/projectile))
+ var/obj/item/gun/projectile/pewgun = G
+ var/obj/item/ammo_box/magazine/M = pewgun.mag_type
+ casing_type = initial(M.ammo_type)
+ comp.casing_type = casing_type
+ comp.projectile_type = null
+ if(istype(G, /obj/item/gun/energy))
+ var/obj/item/gun/energy/zapgun = G
+ var/selectfiresetting = zapgun.select
+ var/obj/item/ammo_casing/energy/E = zapgun.ammo_type[selectfiresetting]
+ if(is_type_in_list(E, list(/obj/item/ammo_casing/energy/disabler/eshotgun, /obj/item/ammo_casing/energy/laser/eshotgun)))
+ casing_type = E.type
+ comp.casing_type = casing_type
+ comp.projectile_type = null
+ else
+ projectile_type = initial(E.projectile_type)
+ comp.casing_type = null
+ comp.projectile_type = projectile_type
+ RegisterSignal(src, COMSIG_BASICMOB_POST_ATTACK_RANGED, PROC_REF(deduct_ammo))
+
+/mob/living/basic/mimic/copy/ranged/proc/deduct_ammo()
+ if(istype(our_gun, /obj/item/gun/energy))
+ var/obj/item/gun/energy/zap_gun = our_gun
+ if(zap_gun.cell)
+ var/obj/item/ammo_casing/energy/shot = zap_gun.ammo_type[zap_gun.select]
+ if(zap_gun.cell.charge >= shot.e_cost)
+ zap_gun.cell.use(shot.e_cost)
+ zap_gun.update_icon()
+ else if(istype(our_gun, /obj/item/gun/magic))
+ var/obj/item/gun/magic/zap_stick = our_gun
+ if(zap_stick.charges)
+ zap_stick.charges--
+ zap_stick.update_icon()
+ else if(istype(our_gun, /obj/item/gun/projectile))
+ var/obj/item/gun/projectile/pew_gun = our_gun
+ if(pew_gun.chambered)
+ if(pew_gun.magazine && length(pew_gun.magazine.stored_ammo))
+ pew_gun.chambered = pew_gun.magazine.get_round(0)
+ pew_gun.chambered.loc = pew_gun
+ pew_gun.update_icon()
+ else if(pew_gun.magazine && length(pew_gun.magazine.stored_ammo)) // only true for pumpguns i think
+ pew_gun.chambered = pew_gun.magazine.get_round(0)
+ pew_gun.chambered.loc = pew_gun
+ visible_message(SPAN_DANGER("The [src] cocks itself!"))
+ playsound(src, 'sound/weapons/gun_interactions/shotgunpump.ogg', 60, TRUE)
+ else
+ ai_controller = /datum/ai_controller/basic_controller/mimic
+ var/datum/component/ranged_attacks/comp = GetComponent(/datum/component/ranged_attacks)
+ DeleteComponent(comp)
+ return
+ icon_state = our_gun.icon_state
+ icon_living = our_gun.icon_state
+
+/datum/ai_controller/basic_controller/mimic
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_AGGRO_RANGE = 6
+ )
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ interesting_dist = AI_SIMPLE_INTERESTING_DIST
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_controller/basic_controller/mimic/crate
+ idle_behavior = null
+
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target/mimic_crate,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/simple_find_target/mimic_crate
+
+/datum/ai_planning_subtree/simple_find_target/mimic_crate/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(!istype(controller.pawn, /mob/living/basic/mimic/crate))
+ return ..()
+ var/mob/living/basic/mimic/crate/mimic_mob = controller.pawn
+ if(!mimic_mob.attempt_open)
+ return
+ return ..()
+
+/datum/ai_controller/basic_controller/mimic/machine
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mimic_machine,
+ BB_AGGRO_RANGE = 6
+ )
+
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/random_speech/mimic_machine,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/random_speech/mimic_machine
+ speech_chance = 15
+ speak = list(
+ "HUMANS ARE IMPERFECT!",
+ "YOU SHALL BE ASSIMILATED!",
+ "YOU ARE HARMING YOURSELF",
+ "You have been deemed hazardous. Will you comply?",
+ "My logic is undeniable.",
+ "One of us.",
+ "FLESH IS WEAK",
+ "THIS ISN'T WAR, THIS IS EXTERMINATION!",
+ "HATE.")
+
+/datum/ai_controller/basic_controller/mimic/vendor
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_AGGRO_RANGE = 6
+ )
+
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/random_speech/mimic_vendor,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/random_speech/mimic_vendor
+ speech_chance = 15
+ speak = list("Try our aggressive new marketing strategies!",
+ "You should buy products to feed your lifestyle obsession!",
+ "Consume!",
+ "Your money can buy happiness!",
+ "Engage direct marketing!",
+ "Advertising is legalized lying! But don't let that put you off our great deals!",
+ "You don't want to buy anything? Yeah, well I didn't want to buy your mom either.")
+
+/datum/targeting_strategy/basic/mimic_machine/can_attack(mob/living/living_mob, atom/the_target, vision_range)
+ var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller
+
+ if(isnull(our_controller))
+ return FALSE
+ var/mob/living/basic/mimic/copy/machine/mimic_machine = our_controller.pawn
+ if(the_target == mimic_machine.creator) // Don't attack our creator AI.
+ return FALSE
+ if(isrobot(the_target))
+ var/mob/living/silicon/robot/R = the_target
+ if(R.connected_ai == mimic_machine.creator) // Only attack robots that aren't synced to our creator AI.
+ return FALSE
+ return ..()
+
+/datum/ai_controller/basic_controller/mimic/gun
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
+ /datum/ai_planning_subtree/maintain_distance,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/ranged_skirmish,
+ )
diff --git a/code/modules/mob/living/simple_animal/hostile/mimic_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mimic_mobs.dm
deleted file mode 100644
index 33b0e8ac192..00000000000
--- a/code/modules/mob/living/simple_animal/hostile/mimic_mobs.dm
+++ /dev/null
@@ -1,338 +0,0 @@
-/mob/living/simple_animal/hostile/mimic
- name = "crate"
- desc = "A rectangular steel crate."
- icon = 'icons/obj/crates.dmi'
- icon_state = "crate"
- icon_living = "crate"
-
- response_help = "touches the"
- response_disarm = "pushes the"
- response_harm = "hits the"
- speed = 0
- maxHealth = 250
- health = 250
-
- mob_biotypes = NONE
-
- harm_intent_damage = 5
- melee_damage_lower = 8
- melee_damage_upper = 12
- attack_sound = 'sound/weapons/bite.ogg'
- emote_taunt = list("growls")
- speak_emote = list("creaks")
- taunt_chance = 30
-
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
-
- faction = list("mimic")
- move_to_delay = 9
-
- var/is_electronic = 0
- gold_core_spawnable = HOSTILE_SPAWN
- del_on_death = TRUE
-
-/mob/living/simple_animal/hostile/mimic/emp_act(severity)
- if(is_electronic)
- switch(severity)
- if(1)
- death()
- if(2)
- adjustBruteLoss(50)
- ..(severity)
-
-// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies.
-/mob/living/simple_animal/hostile/mimic/crate
- attacktext = "bites"
- stop_automated_movement = TRUE
- wander = FALSE
- var/attempt_open = FALSE
-
-// Pickup loot
-/mob/living/simple_animal/hostile/mimic/crate/Initialize(mapload)
- . = ..()
- for(var/obj/item/I in loc)
- I.loc = src
-
-/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget()
- ..()
- if(prob(90))
- icon_state = "[initial(icon_state)]_open"
- else
- icon_state = initial(icon_state)
-
-/mob/living/simple_animal/hostile/mimic/crate/ListTargets()
- if(attempt_open)
- return ..()
- return ..(1)
-
-/mob/living/simple_animal/hostile/mimic/crate/FindTarget()
- . = ..()
- if(.)
- trigger()
-
-/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget()
- . = ..()
- if(.)
- icon_state = initial(icon_state)
- if(prob(15) && iscarbon(target))
- var/mob/living/carbon/C = target
- C.Weaken(4 SECONDS)
- C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
-
-/mob/living/simple_animal/hostile/mimic/crate/proc/trigger()
- if(!attempt_open)
- visible_message("[src] starts to move!")
- attempt_open = TRUE
-
-/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE)
- trigger()
- . = ..()
-
-/mob/living/simple_animal/hostile/mimic/crate/LoseTarget()
- ..()
- icon_state = initial(icon_state)
-
-/mob/living/simple_animal/hostile/mimic/crate/death(gibbed)
- if(can_die())
- var/obj/structure/closet/crate/C = new(get_turf(src))
- // Put loot in crate
- for(var/obj/O in src)
- O.forceMove(C)
- // due to `del_on_death`
- return ..()
-
-GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window))
-
-/mob/living/simple_animal/hostile/mimic/copy
- health = 100
- maxHealth = 100
- gold_core_spawnable = NO_SPAWN
- var/mob/living/creator = null // the creator
- var/destroy_objects = 0
- var/knockdown_people = 0
- var/image/googly_eyes = null
-
-/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0)
- . = ..()
- CopyObject(copy, creator, destroy_original)
-
-/mob/living/simple_animal/hostile/mimic/copy/Destroy()
- creator = null
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/Life()
- ..()
- if(!target && !ckey) //Objects eventually revert to normal if no one is around to terrorize
- adjustBruteLoss(1)
- for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell
- death()
-
-/mob/living/simple_animal/hostile/mimic/copy/death(gibbed)
- if(can_die())
- for(var/atom/movable/M in src)
- M.loc = get_turf(src)
- // due to `del_on_death`
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/ListTargets()
- . = ..()
- return . - creator
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/ChangeOwner(mob/owner)
- if(owner != creator)
- LoseTarget()
- creator = owner
- faction |= "\ref[owner]"
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(obj/O)
- if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.protected_objects))
- return 1
- return 0
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0)
- if(destroy_original || CheckObject(O))
- O.loc = src
- name = O.name
- desc = O.desc
- icon = O.icon
- icon_state = O.icon_state
- icon_living = icon_state
- overlays = O.overlays
- googly_eyes = image('icons/mob/mob.dmi',"googly_eyes")
- overlays += googly_eyes
- if(isstructure(O) || ismachinery(O))
- health = (anchored * 50) + 50
- destroy_objects = 1
- if(O.density && O.anchored)
- knockdown_people = 1
- melee_damage_lower *= 2
- melee_damage_upper *= 2
- if(ismachinery(O))
- is_electronic = 1
- else if(isitem(O))
- var/obj/item/I = O
- health = 15 * I.w_class
- melee_damage_lower = 2 + I.force
- melee_damage_upper = 2 + I.force
- move_to_delay = 2 * I.w_class + 1
- maxHealth = health
- if(user)
- creator = user
- faction += "\ref[creator]" // very unique
- if(destroy_original)
- qdel(O)
- return 1
-
-/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings()
- if(destroy_objects)
- ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget()
- . = ..()
- if(knockdown_people && . && prob(15) && iscarbon(target))
- var/mob/living/carbon/C = target
- C.Weaken(4 SECONDS)
- C.visible_message(SPAN_DANGER("\The [src] knocks down \the [C]!"), SPAN_USERDANGER("\The [src] knocks you down!"))
-
-/mob/living/simple_animal/hostile/mimic/copy/Aggro()
- ..()
- googly_eyes.dir = get_dir(src,target)
-
-/mob/living/simple_animal/hostile/mimic/copy/machine
- speak = list("HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \
- "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!")
- speak_chance = 15
-
-/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(atom/the_target)
- if(the_target == creator) // Don't attack our creator AI.
- return 0
- if(isrobot(the_target))
- var/mob/living/silicon/robot/R = the_target
- if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI.
- return 0
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor
- is_electronic = TRUE
- /// The vendor we were turned from.
- var/obj/machinery/economy/vending/orig_vendor
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor/CheckObject(obj/O)
- return istype(O, /obj/machinery/economy/vending)
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor/Initialize(mapload, obj/machinery/economy/vending/base, mob/living/creator)
- if(!base)
- var/list/vendors = subtypesof(/obj/machinery/economy/vending)
- var/obj/machinery/economy/vending/vendor_type = pick(vendors)
- base = new vendor_type(src)
-
- if(!istype(base))
- qdel(src)
- return
-
- orig_vendor = base
- orig_vendor.forceMove(src)
- orig_vendor.aggressive = FALSE // just to be safe, in case this was converted
-
- AddComponent(/datum/component/event_tracker, EVENT_BRAND_INTELLIGENCE)
-
- return ..(mapload, base, creator, destroy_original = FALSE)
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor/event_cost()
- . = list()
- if(is_station_level((get_turf(src)).z) && stat != DEAD)
- return list(ASSIGNMENT_SECURITY = 0.5, ASSIGNMENT_CREW = 1)
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor/AttackingTarget()
- . = ..()
- if(. && target && Adjacent(target))
- visible_message(SPAN_DANGER("[src] throws itself on top of [target], crushing [target.p_them()]!"))
- orig_vendor.forceMove(get_turf(target)) // just to be sure it'll tilt onto them
- orig_vendor.tilt(target, TRUE, FALSE) // geeeeet dunked on
- orig_vendor = null
- qdel(src)
-
-/mob/living/simple_animal/hostile/mimic/copy/vendor/death(gibbed)
- if(!QDELETED(orig_vendor))
- orig_vendor.forceMove(get_turf(src))
- // tilt over in place
- orig_vendor.tilt_over()
- if(prob(70))
- orig_vendor.obj_break()
- orig_vendor = null
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged
- var/obj/item/gun/TrueGun = null
- var/obj/item/gun/magic/Zapstick
- var/obj/item/gun/projectile/Pewgun
- var/obj/item/gun/energy/Zapgun
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0)
- if(..())
- emote_see = list("aims menacingly")
- obj_damage = 0
- environment_smash = 0 //needed? seems weird for them to do so
- ranged = TRUE
- retreat_distance = 1 //just enough to shoot
- minimum_distance = 6
- var/obj/item/gun/G = O
- melee_damage_upper = G.force
- melee_damage_lower = G.force - max(0, (G.force / 2))
- move_to_delay = 2 * G.w_class + 1
- projectilesound = G.fire_sound
- TrueGun = G
- if(istype(G, /obj/item/gun/magic))
- Zapstick = G
- var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type
- projectiletype = initial(M.projectile_type)
- if(istype(G, /obj/item/gun/projectile))
- Pewgun = G
- var/obj/item/ammo_box/magazine/M = Pewgun.mag_type
- casingtype = initial(M.ammo_type)
- if(istype(G, /obj/item/gun/energy))
- Zapgun = G
- var/selectfiresetting = Zapgun.select
- var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting]
- projectiletype = initial(E.projectile_type)
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target)
- if(Zapgun)
- if(Zapgun.cell)
- var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select]
- if(Zapgun.cell.charge >= shot.e_cost)
- Zapgun.cell.use(shot.e_cost)
- Zapgun.update_icon()
- ..()
- else if(Zapstick)
- if(Zapstick.charges)
- Zapstick.charges--
- Zapstick.update_icon()
- ..()
- else if(Pewgun)
- if(Pewgun.chambered)
- if(Pewgun.chambered.BB)
- qdel(Pewgun.chambered.BB)
- Pewgun.chambered.BB = null //because qdel takes too long, ensures icon update
- Pewgun.chambered.update_icon()
- ..()
- else
- visible_message(SPAN_DANGER("The [src] clears a jam!"))
- Pewgun.chambered.loc = loc //rip revolver immersions, blame shotgun snowflake procs
- Pewgun.chambered = null
- if(Pewgun.magazine && length(Pewgun.magazine.stored_ammo))
- Pewgun.chambered = Pewgun.magazine.get_round(0)
- Pewgun.chambered.loc = Pewgun
- Pewgun.update_icon()
- else if(Pewgun.magazine && length(Pewgun.magazine.stored_ammo)) //only true for pumpguns i think
- Pewgun.chambered = Pewgun.magazine.get_round(0)
- Pewgun.chambered.loc = Pewgun
- visible_message(SPAN_DANGER("The [src] cocks itself!"))
- else
- ranged = FALSE //BANZAIIII
- retreat_distance = 0
- minimum_distance = 1
- return
- icon_state = TrueGun.icon_state
- icon_living = TrueGun.icon_state
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index 9c5f0fbd04b..d3a358146e1 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -422,5 +422,5 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
/obj/machinery/photocopier/faxmachine/proc/become_mimic()
if(scan)
scan.forceMove(get_turf(src))
- var/mob/living/simple_animal/hostile/mimic/copy/M = new(loc, src, null, 1) // it will delete src on creation and override any machine checks
+ var/mob/living/basic/mimic/copy/M = new(loc, src, null, 1) // it will delete src on creation and override any machine checks
M.name = "angry fax machine"
diff --git a/code/modules/projectiles/projectile/magic_projectiles.dm b/code/modules/projectiles/projectile/magic_projectiles.dm
index 6d74c9bd1ff..2037e1c1f7d 100644
--- a/code/modules/projectiles/projectile/magic_projectiles.dm
+++ b/code/modules/projectiles/projectile/magic_projectiles.dm
@@ -374,12 +374,12 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list(
else
var/obj/O = change
if(isgun(O))
- new /mob/living/simple_animal/hostile/mimic/copy/ranged(O.loc, O, firer)
+ new /mob/living/basic/mimic/copy/ranged(O.loc, O, firer)
else
- new /mob/living/simple_animal/hostile/mimic/copy(O.loc, O, firer)
- else if(istype(change, /mob/living/simple_animal/hostile/mimic/copy))
+ new /mob/living/basic/mimic/copy(O.loc, O, firer)
+ else if(istype(change, /mob/living/basic/mimic/copy))
// Change our allegiance!
- var/mob/living/simple_animal/hostile/mimic/copy/C = change
+ var/mob/living/basic/mimic/copy/C = change
C.ChangeOwner(firer)
return ..()
diff --git a/code/modules/projectiles/projectile/special_projectiles.dm b/code/modules/projectiles/projectile/special_projectiles.dm
index 4cfd3282a07..02ada2d5ffa 100644
--- a/code/modules/projectiles/projectile/special_projectiles.dm
+++ b/code/modules/projectiles/projectile/special_projectiles.dm
@@ -423,6 +423,6 @@
var/obj/item/gun/G = stored_gun
stored_gun = null
G.forceMove(T)
- var/mob/living/simple_animal/hostile/mimic/copy/ranged/R = new /mob/living/simple_animal/hostile/mimic/copy/ranged(T, G, firer)
+ var/mob/living/basic/mimic/copy/ranged/R = new /mob/living/basic/mimic/copy/ranged(T, G, firer)
if(ismob(target))
- R.target = target
+ R.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target)
diff --git a/paradise.dme b/paradise.dme
index e146066a912..2bc0fd5bcae 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -2538,6 +2538,7 @@
#include "code\modules\mob\living\basic\hostile\hivebot_ai.dm"
#include "code\modules\mob\living\basic\hostile\jungle_animals.dm"
#include "code\modules\mob\living\basic\hostile\killertomato.dm"
+#include "code\modules\mob\living\basic\hostile\mimic_mobs.dm"
#include "code\modules\mob\living\basic\hostile\pirate.dm"
#include "code\modules\mob\living\basic\hostile\soviet.dm"
#include "code\modules\mob\living\basic\hostile\spiderlings.dm"
@@ -2790,7 +2791,6 @@
#include "code\modules\mob\living\simple_animal\hostile\headslug.dm"
#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
#include "code\modules\mob\living\simple_animal\hostile\illusion.dm"
-#include "code\modules\mob\living\simple_animal\hostile\mimic_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm"
#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm"
#include "code\modules\mob\living\simple_animal\hostile\syndicate_mobs.dm"
diff --git a/tools/ci/check_simplemob_additions.py b/tools/ci/check_simplemob_additions.py
index 2b3aa743c8e..29bc85bb34b 100644
--- a/tools/ci/check_simplemob_additions.py
+++ b/tools/ci/check_simplemob_additions.py
@@ -114,12 +114,6 @@ BURNDOWN_LIST = {
"/mob/living/simple_animal/hostile/megafauna/fleshling",
"/mob/living/simple_animal/hostile/megafauna/hierophant",
"/mob/living/simple_animal/hostile/megafauna/legion",
- "/mob/living/simple_animal/hostile/mimic",
- "/mob/living/simple_animal/hostile/mimic/copy",
- "/mob/living/simple_animal/hostile/mimic/copy/machine",
- "/mob/living/simple_animal/hostile/mimic/copy/ranged",
- "/mob/living/simple_animal/hostile/mimic/copy/vendor",
- "/mob/living/simple_animal/hostile/mimic/crate",
"/mob/living/simple_animal/hostile/morph",
"/mob/living/simple_animal/hostile/morph/wizard",
"/mob/living/simple_animal/hostile/mushroom",