diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index 9a32653a12b..b70f7f9013d 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -137,6 +137,10 @@
#define COMSIG_LIVING_UNARMED_ATTACK "living_unarmed_attack"
///From base of mob/living/MobBump() (mob/living)
#define COMSIG_LIVING_MOB_BUMP "living_mob_bump"
+///From base of mob/living/Bump() (turf/closed)
+#define COMSIG_LIVING_WALL_BUMP "living_wall_bump"
+///From base of turf/closed/Exited() (turf/closed)
+#define COMSIG_LIVING_WALL_EXITED "living_wall_exited"
///From base of mob/living/ZImpactDamage() (mob/living, levels, turf/t)
#define COMSIG_LIVING_Z_IMPACT "living_z_impact"
#define NO_Z_IMPACT_DAMAGE (1<<0)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index c26fd3b41a3..f6ef0a45c80 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -148,6 +148,9 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
#define ismining(A) (istype(A, /mob/living/simple_animal/hostile/asteroid) || istype(A, /mob/living/basic/mining))
+/// constructs, which are both simple and basic for now
+#define isconstruct(A) (istype(A, /mob/living/simple_animal/hostile/construct) || istype(A, /mob/living/basic/construct))
+
//Simple animals
#define isanimal(A) (istype(A, /mob/living/simple_animal))
@@ -175,8 +178,6 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian))
-#define isconstruct(A) (istype(A, /mob/living/simple_animal/hostile/construct))
-
#define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna))
#define isclown(A) (istype(A, /mob/living/basic/clown))
diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm
index 93220a1045e..b37c61bc681 100644
--- a/code/_globalvars/phobias.dm
+++ b/code/_globalvars/phobias.dm
@@ -91,6 +91,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
"the supernatural" = typecacheof(list(
/mob/dead/observer,
/mob/living/basic/bat,
+ /mob/living/basic/construct,
/mob/living/basic/demon,
/mob/living/basic/faithless,
/mob/living/basic/ghost,
diff --git a/code/datums/components/healing_touch.dm b/code/datums/components/healing_touch.dm
index 4b953fc6289..029b0f660ef 100644
--- a/code/datums/components/healing_touch.dm
+++ b/code/datums/components/healing_touch.dm
@@ -32,6 +32,10 @@
var/action_text
/// Text to print when action completes, replaces %SOURCE% with healer and %TARGET% with healed mob
var/complete_text
+ /// Whether to print the target's remaining health after healing (for non-carbon targets only)
+ var/show_health
+ /// Color for the healing effect
+ var/heal_color
/datum/component/healing_touch/Initialize(
heal_brute = 20,
@@ -46,6 +50,8 @@
self_targetting = HEALING_TOUCH_NOT_SELF,
action_text = "%SOURCE% begins healing %TARGET%",
complete_text = "%SOURCE% finishes healing %TARGET%",
+ show_health = FALSE,
+ heal_color = COLOR_HEALING_CYAN,
)
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
@@ -62,6 +68,8 @@
src.self_targetting = self_targetting
src.action_text = action_text
src.complete_text = complete_text
+ src.show_health = show_health
+ src.heal_color = heal_color
RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_healing)) // Players
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(try_healing)) // NPCs
@@ -147,7 +155,11 @@
healer.visible_message(span_notice("[format_string(complete_text, healer, target)]"))
target.heal_overall_damage(brute = heal_brute, burn = heal_burn, stamina = heal_stamina, required_bodytype = required_bodytype)
- new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN)
+ new /obj/effect/temp_visual/heal(get_turf(target), heal_color)
+
+ if(show_health && !iscarbon(target))
+ var/formatted_string = format_string("%TARGET% now has [target.health]/[target.maxHealth] health.", healer, target)
+ healer.visible_message(span_danger(formatted_string))
/// Reformats the passed string with the replacetext keys
/datum/component/healing_touch/proc/format_string(string, atom/source, atom/target)
diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/elements/amputating_limbs.dm
index f7e3d08fcc4..7271b20a6c3 100644
--- a/code/datums/elements/amputating_limbs.dm
+++ b/code/datums/elements/amputating_limbs.dm
@@ -1,4 +1,4 @@
-/// This component will intercept bare-handed attacks by the owner on critically injured carbons and amputate random limbs instead
+/// This component will intercept bare-handed attacks by the owner on sufficiently injured carbons and amputate random limbs instead
/datum/element/amputating_limbs
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
diff --git a/code/datums/elements/structure_repair.dm b/code/datums/elements/structure_repair.dm
new file mode 100644
index 00000000000..d3b26eed815
--- /dev/null
+++ b/code/datums/elements/structure_repair.dm
@@ -0,0 +1,45 @@
+/// Intercepts attacks from mobs with this component to instead repair specified structures.
+/datum/element/structure_repair
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ /// How much to heal structures by
+ var/heal_amount
+ /// Typecache of types of structures to repair
+ var/list/structure_types_typecache
+
+/datum/element/structure_repair/Attach(
+ datum/target,
+ heal_amount = 5,
+ structure_types_typecache = typecacheof(list(/obj/structure)),
+)
+ . = ..()
+ if (!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.heal_amount = heal_amount
+ src.structure_types_typecache = structure_types_typecache
+ RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_repair))
+
+/datum/element/structure_repair/Detach(datum/source)
+ UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+ return ..()
+
+/// If the target is of a valid type, interrupt the attack chain to repair it instead
+/datum/element/structure_repair/proc/try_repair(mob/living/fixer, atom/target)
+ SIGNAL_HANDLER
+
+ if (!is_type_in_typecache(target, structure_types_typecache))
+ return
+
+ if (target.get_integrity() >= target.max_integrity)
+ target.balloon_alert(fixer, "not damaged!")
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ target.repair_damage(heal_amount)
+ fixer.Beam(target, icon_state = "sendbeam", time = 0.4 SECONDS)
+ fixer.visible_message(
+ span_danger("[fixer] repairs [target]."),
+ span_danger("You repair [target], leaving it at [round(target.get_integrity() * 100 / target.max_integrity)]% stability."),
+ )
+
+ return COMPONENT_CANCEL_ATTACK_CHAIN
diff --git a/code/datums/elements/wall_walker.dm b/code/datums/elements/wall_walker.dm
new file mode 100644
index 00000000000..92ac3318c12
--- /dev/null
+++ b/code/datums/elements/wall_walker.dm
@@ -0,0 +1,49 @@
+/// This element will allow the mob it's attached to to pass through a specified type of wall, and drag anything through it.
+/datum/element/wall_walker
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ /// What kind of walls can we pass through?
+ var/wall_type
+
+/datum/element/wall_walker/Attach(
+ datum/target,
+ wall_type = /turf/closed/wall,
+)
+ . = ..()
+ if (!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.wall_type = wall_type
+ RegisterSignal(target, COMSIG_LIVING_WALL_BUMP, PROC_REF(try_pass_wall))
+ RegisterSignal(target, COMSIG_LIVING_WALL_EXITED, PROC_REF(exit_wall))
+
+/datum/element/wall_walker/Detach(datum/source)
+ UnregisterSignal(source, list(COMSIG_LIVING_WALL_BUMP, COMSIG_LIVING_WALL_EXITED))
+ return ..()
+
+/// If the wall is of the proper type, pass into it and keep hold on whatever you're pulling
+/datum/element/wall_walker/proc/try_pass_wall(mob/living/passing_mob, turf/closed/bumped_wall)
+ if(!istype(bumped_wall, wall_type))
+ return
+
+ var/atom/movable/stored_pulling = passing_mob.pulling
+ if(stored_pulling) //force whatever you're pulling to come with you
+ stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc))
+ stored_pulling.forceMove(passing_mob.loc)
+ passing_mob.forceMove(bumped_wall)
+
+ if(stored_pulling) //don't drop them because we went into a wall
+ passing_mob.start_pulling(stored_pulling, supress_message = TRUE)
+
+/// If the wall is of the proper type, pull whatever you're pulling into it
+/datum/element/wall_walker/proc/exit_wall(mob/living/passing_mob, turf/closed/exited_wall)
+ if(!istype(exited_wall, wall_type))
+ return
+
+ var/atom/movable/stored_pulling = passing_mob.pulling
+ if(isnull(stored_pulling))
+ return
+
+ stored_pulling.setDir(get_dir(stored_pulling.loc, passing_mob.loc))
+ stored_pulling.forceMove(exited_wall)
+ passing_mob.start_pulling(stored_pulling, supress_message = TRUE)
diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm
index a26e456971d..9fbce09ace2 100644
--- a/code/game/turfs/closed/wall/misc_walls.dm
+++ b/code/game/turfs/closed/wall/misc_walls.dm
@@ -18,16 +18,6 @@
/turf/closed/wall/mineral/cult/devastate_wall()
new sheet_type(get_turf(src), sheet_amount)
-/turf/closed/wall/mineral/cult/Exited(atom/movable/gone, direction)
- . = ..()
- if(istype(gone, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with
- var/mob/living/simple_animal/hostile/construct/harvester/H = gone
- var/atom/movable/stored_pulling = H.pulling
- if(stored_pulling)
- stored_pulling.setDir(direction)
- stored_pulling.forceMove(src)
- H.start_pulling(stored_pulling, supress_message = TRUE)
-
/turf/closed/wall/mineral/cult/artificer
name = "runed stone wall"
desc = "A cold stone wall engraved with indecipherable symbols. Studying them causes your head to pound."
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 7b601fb82ac..06a063f16ea 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -363,5 +363,13 @@
/turf/closed/wall/metal_foam_base
girder_type = /obj/structure/foamedmetal
+/turf/closed/wall/Bumped(atom/movable/bumped_atom)
+ . = ..()
+ SEND_SIGNAL(bumped_atom, COMSIG_LIVING_WALL_BUMP, src)
+
+/turf/closed/wall/Exited(atom/movable/gone, direction)
+ . = ..()
+ SEND_SIGNAL(gone, COMSIG_LIVING_WALL_EXITED, src)
+
#undef MAX_DENT_DECALS
#undef LEANING_OFFSET
diff --git a/code/modules/mob/living/basic/constructs/_construct.dm b/code/modules/mob/living/basic/constructs/_construct.dm
new file mode 100644
index 00000000000..f2e55cceb86
--- /dev/null
+++ b/code/modules/mob/living/basic/constructs/_construct.dm
@@ -0,0 +1,156 @@
+/mob/living/basic/construct
+ icon = 'icons/mob/nonhuman-player/cult.dmi'
+ gender = NEUTER
+ basic_mob_flags = DEL_ON_DEATH
+ combat_mode = TRUE
+ mob_biotypes = MOB_MINERAL | MOB_SPECIAL
+ faction = list(FACTION_CULT)
+ unsuitable_atmos_damage = 0
+ minimum_survivable_temperature = 0
+ maximum_survivable_temperature = INFINITY
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ pressure_resistance = 100
+ speed = 0
+ unique_name = TRUE
+ initial_language_holder = /datum/language_holder/construct
+ death_message = "collapses in a shattered heap."
+
+ speak_emote = list("hisses")
+ response_help_continuous = "thinks better of touching"
+ response_help_simple = "think better of touching"
+ response_disarm_continuous = "flails at"
+ response_disarm_simple = "flail at"
+ response_harm_continuous = "punches"
+ response_harm_simple = "punch"
+
+ // Vivid red, cause cult theme
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 20
+
+ /// List of spells that this construct can cast
+ var/list/construct_spells = list()
+ /// Flavor text shown to players when they spawn as this construct
+ var/playstyle_string = "You are a generic construct. Your job is to not exist, and you should probably adminhelp this."
+ /// The construct's master
+ var/master = null
+ /// Whether this construct is currently seeking nar nar
+ var/seeking = FALSE
+ /// Whether this construct can repair other constructs or cult buildings. Gets the healing_touch component if so.
+ var/can_repair = FALSE
+ /// Whether this construct can repair itself. Works independently of can_repair.
+ var/can_repair_self = FALSE
+ /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue
+ var/theme = THEME_CULT
+ /// What flavor of gunk does this construct drop on death?
+ var/static/list/remains = list(/obj/item/ectoplasm/construct)
+ /// Can this construct smash walls? Gets the wall_smasher element if so.
+ var/smashes_walls = FALSE
+
+/mob/living/basic/construct/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/simple_flying)
+ if(length(remains))
+ AddElement(/datum/element/death_drops, remains)
+ if(smashes_walls)
+ AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_WALLS)
+ if(can_repair)
+ AddComponent(\
+ /datum/component/healing_touch,\
+ heal_brute = 5,\
+ heal_burn = 0,\
+ heal_time = 0,\
+ valid_targets_typecache = typecacheof(list(/mob/living/basic/construct, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/shade)),\
+ self_targetting = can_repair_self ? HEALING_TOUCH_ANYONE : HEALING_TOUCH_NOT_SELF,\
+ action_text = "%SOURCE% begins repairing %TARGET%'s dents.",\
+ complete_text = "%TARGET%'s dents are repaired.",\
+ show_health = TRUE,\
+ heal_color = COLOR_CULT_RED,\
+ )
+ var/static/list/structure_types = typecacheof(list(/obj/structure/destructible/cult))
+ AddElement(\
+ /datum/element/structure_repair,\
+ structure_types_typecache = structure_types,\
+ )
+ add_traits(list(TRAIT_HEALS_FROM_CULT_PYLONS, TRAIT_SPACEWALK), INNATE_TRAIT)
+ for(var/spell in construct_spells)
+ var/datum/action/new_spell = new spell(src)
+ new_spell.Grant(src)
+
+ var/spell_count = 1
+ for(var/datum/action/spell as anything in actions)
+ if(!(spell.type in construct_spells))
+ continue
+
+ var/pos = 2 + spell_count * 31
+ if(construct_spells.len >= 4)
+ pos -= 31 * (construct_spells.len - 4)
+ spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position
+ spell_count++
+ update_action_buttons()
+
+ if(icon_state)
+ add_overlay("glow_[icon_state]_[theme]")
+
+/mob/living/basic/construct/Login()
+ . = ..()
+ if(!. || !client)
+ return FALSE
+ to_chat(src, span_bold(playstyle_string))
+
+/mob/living/basic/construct/examine(mob/user)
+ var/text_span
+ switch(theme)
+ if(THEME_CULT)
+ text_span = "cult"
+ if(THEME_WIZARD)
+ text_span = "purple"
+ if(THEME_HOLY)
+ text_span = "blue"
+ . = list("This is [icon2html(src, user)] \a [src]!\n[desc]")
+ if(health < maxHealth)
+ if(health >= maxHealth/2)
+ . += span_warning("[p_They()] look[p_s()] slightly dented.")
+ else
+ . += span_warning(span_bold("[p_They()] look[p_s()] severely dented!"))
+ . += ""
+ return .
+
+/mob/living/basic/construct/narsie_act()
+ return
+
+/mob/living/basic/construct/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
+ return FALSE
+
+// Allows simple constructs to repair basic constructs.
+/mob/living/basic/construct/attack_animal(mob/living/simple_animal/user, list/modifiers)
+ if(!isconstruct(user))
+ if(src != user)
+ return ..()
+ return
+
+ if(src == user) //basic constructs use the healing hands component instead
+ return
+
+ var/mob/living/simple_animal/hostile/construct/doll = user
+ if(!doll.can_repair || (doll == src && !doll.can_repair_self))
+ return ..()
+ if(theme != doll.theme)
+ return ..()
+
+ if(health >= maxHealth)
+ to_chat(user, span_cult("You cannot repair [src]'s dents, as [p_they()] [p_have()] none!"))
+ return
+
+ heal_overall_damage(brute = 5)
+
+ Beam(user, icon_state = "sendbeam", time = 4)
+ user.visible_message(
+ span_danger("[user] repairs some of \the [src]'s dents."),
+ span_cult("You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health."),
+ )
+
+/// Construct ectoplasm. Largely a placeholder, since the death drop element needs a unique list.
+/obj/item/ectoplasm/construct
+ name = "blood-red ectoplasm"
+ desc = "Has a pungent metallic smell."
diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm b/code/modules/mob/living/basic/constructs/harvester.dm
similarity index 69%
rename from code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm
rename to code/modules/mob/living/basic/constructs/harvester.dm
index 8c5fc8eae37..30b30994872 100644
--- a/code/modules/mob/living/simple_animal/hostile/constructs/harvester.dm
+++ b/code/modules/mob/living/basic/constructs/harvester.dm
@@ -1,4 +1,4 @@
-/mob/living/simple_animal/hostile/construct/harvester
+/mob/living/basic/construct/harvester
name = "Harvester"
real_name = "Harvester"
desc = "A long, thin construct built to herald Nar'Sie's rise. It'll be all over soon."
@@ -24,59 +24,34 @@
can_repair = TRUE
slowed_by_drag = FALSE
-
-/mob/living/simple_animal/hostile/construct/harvester/Bump(atom/thing)
+/mob/living/basic/construct/harvester/Initialize(mapload)
. = ..()
- if(!istype(thing, /turf/closed/wall/mineral/cult) || thing == loc)
- return // we can go through cult walls
- var/atom/movable/stored_pulling = pulling
-
- if(stored_pulling)
- stored_pulling.setDir(get_dir(stored_pulling.loc, loc))
- stored_pulling.forceMove(loc)
- forceMove(thing)
-
- if(stored_pulling)
- start_pulling(stored_pulling, supress_message = TRUE) //drag anything we're pulling through the wall with us by magic
-
-/mob/living/simple_animal/hostile/construct/harvester/AttackingTarget()
- if(!iscarbon(target))
- return ..()
-
- var/mob/living/carbon/victim = target
- if(HAS_TRAIT(victim, TRAIT_NODISMEMBER))
- return ..() //ATTACK!
-
- var/list/parts = list()
- var/strong_limbs = 0
-
- for(var/obj/item/bodypart/limb as anything in victim.bodyparts)
- if(limb.body_part == HEAD || limb.body_part == CHEST)
- continue
- if(!(limb.bodypart_flags & BODYPART_UNREMOVABLE))
- parts += limb
- else
- strong_limbs++
-
- if(!LAZYLEN(parts))
- if(strong_limbs) // they have limbs we can't remove, and no parts we can, attack!
- return ..()
- victim.Paralyze(60)
- visible_message(span_danger("[src] knocks [victim] down!"))
- to_chat(src, span_cultlarge("\"Bring [victim.p_them()] to me.\""))
- return FALSE
-
- do_attack_animation(victim)
- var/obj/item/bodypart/limb = pick(parts)
- limb.dismember()
- return FALSE
-
-/mob/living/simple_animal/hostile/construct/harvester/Initialize(mapload)
- . = ..()
- var/datum/action/innate/seek_prey/seek = new()
+ AddElement(\
+ /datum/element/amputating_limbs,\
+ surgery_time = 0,\
+ surgery_verb = "slicing",\
+ minimum_stat = CONSCIOUS,\
+ )
+ AddElement(/datum/element/wall_walker, /turf/closed/wall/mineral/cult)
+ var/datum/action/innate/seek_prey/seek = new(src)
seek.Grant(src)
seek.Activate()
+/// If the attack is a limbless carbon, abort the attack, paralyze them, and get a special message from Nar'Sie.
+/mob/living/basic/construct/harvester/resolve_unarmed_attack(atom/attack_target, list/modifiers)
+ if(!iscarbon(attack_target))
+ return ..()
+ var/mob/living/carbon/carbon_target = attack_target
+
+ for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts)
+ if(limb.body_part == HEAD || limb.body_part == CHEST)
+ continue
+ return ..() //if any arms or legs exist, attack
+
+ carbon_target.Paralyze(6 SECONDS)
+ visible_message(span_danger("[src] knocks [carbon_target] down!"))
+ to_chat(src, span_cultlarge("\"Bring [carbon_target.p_them()] to me.\""))
+
/datum/action/innate/seek_master
name = "Seek your Master"
desc = "You and your master share a soul-link that informs you of their location"
@@ -89,7 +64,7 @@
/// Where is nar nar? Are we even looking?
var/tracking = FALSE
/// The construct we're attached to
- var/mob/living/simple_animal/hostile/construct/the_construct
+ var/mob/living/basic/construct/the_construct
/datum/action/innate/seek_master/Grant(mob/living/player)
the_construct = player
@@ -132,7 +107,7 @@
/datum/action/innate/seek_prey/Activate()
if(GLOB.cult_narsie == null)
return
- var/mob/living/simple_animal/hostile/construct/harvester/the_construct = owner
+ var/mob/living/basic/construct/harvester/the_construct = owner
if(the_construct.seeking)
desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!"
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 687359a6ff6..01362dfb1ba 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -464,7 +464,7 @@
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cult_ending_helper), CULT_VICTORY_MASS_CONVERSION), 120)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(ending_helper)), 270)
if(client)
- makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE)
+ makeNewConstruct(/mob/living/basic/construct/harvester, src, cultoverride = TRUE)
else
switch(rand(1, 4))
if(1)
diff --git a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm b/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm
index 23f7590dc8e..31150a4dc89 100644
--- a/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/constructs/constructs.dm
@@ -3,7 +3,7 @@
real_name = "Construct"
desc = ""
gender = NEUTER
- mob_biotypes = NONE
+ mob_biotypes = MOB_MINERAL | MOB_SPECIAL
speak_emote = list("hisses")
response_help_continuous = "thinks better of touching"
response_help_simple = "think better of touching"
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 09ce3b3c65c..a492557d4e4 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -372,7 +372,7 @@
if(!MP)
return FALSE //Sanity, this should never happen.
- if(ispath(MP, /mob/living/simple_animal/hostile/construct))
+ if(ispath(MP, /mob/living/simple_animal/hostile/construct) || ispath(MP, /mob/living/basic/construct))
return FALSE //Verbs do not appear for players.
//Good mobs!
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
index 1c83ca58e1d..d11b57bcb93 100644
--- a/code/modules/power/singularity/narsie.dm
+++ b/code/modules/power/singularity/narsie.dm
@@ -112,7 +112,7 @@
return ..()
/obj/narsie/attack_ghost(mob/user)
- makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = loc)
+ makeNewConstruct(/mob/living/basic/construct/harvester, user, cultoverride = TRUE, loc_override = loc)
/obj/narsie/process()
var/datum/component/singularity/singularity_component = singularity.resolve()
diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm
index 396bb2140aa..98e50a3add4 100644
--- a/code/modules/unit_tests/simple_animal_freeze.dm
+++ b/code/modules/unit_tests/simple_animal_freeze.dm
@@ -73,7 +73,6 @@
/mob/living/simple_animal/hostile/construct/artificer/hostile,
/mob/living/simple_animal/hostile/construct/artificer/mystic,
/mob/living/simple_animal/hostile/construct/artificer/noncult,
- /mob/living/simple_animal/hostile/construct/harvester,
/mob/living/simple_animal/hostile/construct/juggernaut,
/mob/living/simple_animal/hostile/construct/juggernaut/angelic,
/mob/living/simple_animal/hostile/construct/juggernaut/hostile,
diff --git a/tgstation.dme b/tgstation.dme
index e21567b7465..62643c5d1a1 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1449,6 +1449,7 @@
#include "code\datums\elements\squish.dm"
#include "code\datums\elements\sticker.dm"
#include "code\datums\elements\strippable.dm"
+#include "code\datums\elements\structure_repair.dm"
#include "code\datums\elements\swabbable.dm"
#include "code\datums\elements\tear_wall.dm"
#include "code\datums\elements\temporary_atom.dm"
@@ -1466,6 +1467,7 @@
#include "code\datums\elements\waddling.dm"
#include "code\datums\elements\wall_engraver.dm"
#include "code\datums\elements\wall_smasher.dm"
+#include "code\datums\elements\wall_walker.dm"
#include "code\datums\elements\weapon_description.dm"
#include "code\datums\elements\weather_listener.dm"
#include "code\datums\elements\web_walker.dm"
@@ -4446,6 +4448,8 @@
#include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm"
#include "code\modules\mob\living\basic\clown\clown.dm"
#include "code\modules\mob\living\basic\clown\clown_ai.dm"
+#include "code\modules\mob\living\basic\constructs\_construct.dm"
+#include "code\modules\mob\living\basic\constructs\harvester.dm"
#include "code\modules\mob\living\basic\farm_animals\deer.dm"
#include "code\modules\mob\living\basic\farm_animals\pig.dm"
#include "code\modules\mob\living\basic\farm_animals\pony.dm"
@@ -4822,7 +4826,6 @@
#include "code\modules\mob\living\simple_animal\hostile\zombie.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\artificer.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm"
-#include "code\modules\mob\living\simple_animal\hostile\constructs\harvester.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm"
#include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm"
#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm"
diff --git a/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt b/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt
new file mode 100644
index 00000000000..93996fd123b
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt
@@ -0,0 +1 @@
+/mob/living/simple_animal/hostile/construct/harvester : /mob/living/basic/construct/harvester{@OLD}