From ec4defe5983f6fe22b9c4eba0469f81fe21e46ad Mon Sep 17 00:00:00 2001 From: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:55:09 -0500 Subject: [PATCH] Basic Constructs: parent type + Harvester (#78807) ## About The Pull Request I kind of hate cult as a whole, but I like these little guys. Let's basic-ize them. This PR begins the process with the harbinger of the Red Harvest, the Harvester! Their actual capabilities have been changed very little, except that most of their unique properties have been moved to components and elements. The basic parent type of constructs has also been set up to make the next bunch of conversions easier. - Constructs capable of repair now receive the healing hands component. Healing hands has been extended, to allow the healing particles to come in custom colors, and to allow it to print the target's health if the target is not a carbon. - Repairing constructs also receive a new element: Structure repair is a lighter-weight variant on healing hands that allows repairing clicked-on atoms of specified types. - Constructs capable of damaging walls, meanwhile, receive the wall smasher element. Harvesters in specific have two special elements: - The existing "amputating limbs" element, making them instantly rip a limb off of any carbon they attack. As before, if they attempt this on a carbon with no arms or legs, the harvester will hear Nar'Sie's call to bring the victim to her. - A new "wall walker" element, allowing them to walk through walls of specified type (cult walls for harvesters) and allowing them to drag any atom through as well. Other than laying the groundwork, there's not much else here. I started with Harvesters specifically because they are only ever player-controlled, which makes things easy. I'm not completely happy with the use of healing hands here - it gets the job done, but currently loses a bit of the previous flavor (a healing beam as a visual; printing the target's health in cult span). I may extend it further to allow this behavior. I've included an UpdatePaths script, even if these things shouldn't be mapped, just in case something fucky is going on on a downstream. You never know. ## Why It's Good For The Game Constructs, currently, occupy _19_ spots on the simple animal list. This is something close to 10% of all the remaining ones. Also, like everything to do with cult, construct code is janky, old, and desperately in need of updating. This is the first step. ## Changelog :cl: refactor: Harvester constructs have been updated to the basic mob framework. This should have very little impact on their behavior, but please report any issues. /:cl: --------- Co-authored-by: san7890 --- .../signals/signals_mob/signals_mob_living.dm | 4 + code/__DEFINES/is_helpers.dm | 5 +- code/_globalvars/phobias.dm | 1 + code/datums/components/healing_touch.dm | 14 +- code/datums/elements/amputating_limbs.dm | 2 +- code/datums/elements/structure_repair.dm | 45 +++++ code/datums/elements/wall_walker.dm | 49 ++++++ code/game/turfs/closed/wall/misc_walls.dm | 10 -- code/game/turfs/closed/walls.dm | 8 + .../mob/living/basic/constructs/_construct.dm | 156 ++++++++++++++++++ .../hostile => basic}/constructs/harvester.dm | 79 +++------ code/modules/mob/living/living_defense.dm | 2 +- .../hostile/constructs/constructs.dm | 2 +- code/modules/mob/transform_procs.dm | 2 +- code/modules/power/singularity/narsie.dm | 2 +- .../unit_tests/simple_animal_freeze.dm | 1 - tgstation.dme | 5 +- ...07_simple_to_basic_construct_harvester.txt | 1 + 18 files changed, 316 insertions(+), 72 deletions(-) create mode 100644 code/datums/elements/structure_repair.dm create mode 100644 code/datums/elements/wall_walker.dm create mode 100644 code/modules/mob/living/basic/constructs/_construct.dm rename code/modules/mob/living/{simple_animal/hostile => basic}/constructs/harvester.dm (69%) create mode 100644 tools/UpdatePaths/Scripts/78807_simple_to_basic_construct_harvester.txt 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 57d3505fcdb..38302feaba5 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 aa3743a44b4..f31fd60b131 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -452,7 +452,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 99dd421452c..be2cccbcde4 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 a0e6337e034..50bb2e1a678 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 4562f074b5a..f98ec173ce3 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1363,6 +1363,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" @@ -1380,6 +1381,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" @@ -4360,6 +4362,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" @@ -4736,7 +4740,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}