new space ruin, the biological research outpost (#79149)

## About The Pull Request

![2023-10-21 18 02
39](https://github.com/tgstation/tgstation/assets/70376633/5829e939-3b04-465f-a186-095ceb360bba)

adds this ruin to space ruin pool
this is a shady (as NT always is) bioresearch outpost that got fucked up
by an experiment
this has like some puzzle aspect to it since you gotta find keycards and
shit and press buttons to unlock shield gates
this ends with you fighting a heart which if you defeat, destroys the
blockade that prevents you from entering the outpost vault

also you can no longer literally just cut indestructible grilles or
unanchor indestructible windows

### new puzzle elements or something idk
variant of pressure plate that you cannot remove and it sends a puzzle
signal
cooler red puzzle doors that look very foreboding or something idk
theyre for this ruin
also puzzle blockades, which are indestructible dense objects that are
destroyed if they receive a puzzle signal
and also buttons and keycard pads for puzzles


https://github.com/tgstation/tgstation/assets/70376633/c98807ec-1e7b-49c4-a757-cdbb76a1b566



https://github.com/tgstation/tgstation/assets/70376633/9d5d9dd1-5868-44e6-a978-5ea57b30c298

stuff that throws electric shocks in a pattern, ignores insuls and only
knocks down, and no you cannot just run past


https://github.com/tgstation/tgstation/assets/70376633/5772917c-a963-48a4-a743-b0f610801d25

### enemies
living floor, it can only attack stuff on top of it and it attacks until
the victim is dead
it is invincible to all but a crowbar, and it cannot move, and it
remains hidden until a victim is in range


https://github.com/tgstation/tgstation/assets/70376633/aa1d54f6-b259-4e58-9d44-e393d2131acf

living flesh, it can replace your limbs with itself
the conditions for that are; the limb must have 20 or more brute, victim
must be alive and dismemberable, the limb may not be torso or head, or
the limb may not be living flesh
alternatively it can replace a missing limb
these are all checked with every attack
they have 20 hp
the limbs in question will sometimes act up, while passively draining
nutrition, arms will randomly start pulling nearby stuff, legs may step
randomly
limbs when detached, turn into mobs and reactivate AI 2 seconds later.
if the host is shocked, all living flesh limbs will detach, or if the
host dies they will also do that


https://github.com/tgstation/tgstation/assets/70376633/765cc99e-c800-4efb-aabe-d68817bbd7ae



## Why It's Good For The Game

ruin variety is cool i think
also the other things i added should be useful for other mappers for
bitrunning or whatever

also bug bad for that one fix
## Changelog
🆑
add: living floor, living flesh, and other stuff for the bioresearch
outpost ruin
add: bioresearch outpost ruin
fix: you may not defeat indestructible grilles and windows with mere
tools
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
This commit is contained in:
jimmyl
2023-10-31 08:15:42 +13:00
committed by GitHub
co-authored by Jacquerel
parent 98b05d96d2
commit d31c21ff1b
28 changed files with 5121 additions and 15 deletions
@@ -0,0 +1,168 @@
/datum/ai_controller/basic_controller/living_limb_flesh
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
)
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree
)
/mob/living/basic/living_limb_flesh
name = "living flesh"
desc = "A vaguely leg or arm shaped flesh abomination. It pulses, like a heart."
icon = 'icons/mob/simple/animal.dmi'
icon_state = "limb"
icon_living = "limb"
mob_size = MOB_SIZE_SMALL
basic_mob_flags = DEL_ON_DEATH
faction = list(FACTION_HOSTILE)
melee_damage_lower = 10
melee_damage_upper = 10
health = 20
maxHealth = 20
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
attack_verb_continuous = "tries desperately to attach to"
attack_verb_simple = "try to attach to"
mob_biotypes = MOB_ORGANIC | MOB_SPECIAL
ai_controller = /datum/ai_controller/basic_controller/living_limb_flesh
/// the meat bodypart we are currently inside, used to like drain nutrition and dismember and shit
var/obj/item/bodypart/current_bodypart
/mob/living/basic/living_limb_flesh/Initialize(mapload, obj/item/bodypart/limb)
. = ..()
AddComponent(/datum/component/swarming, max_x = 8, max_y = 8)
AddElement(/datum/element/death_drops, string_list(list(/obj/effect/gibspawner/generic)))
if(!isnull(limb))
register_to_limb(limb)
/mob/living/basic/living_limb_flesh/Destroy(force)
. = ..()
QDEL_NULL(current_bodypart)
/mob/living/basic/living_limb_flesh/Life(seconds_per_tick = SSMOBS_DT, times_fired)
. = ..()
if(stat == DEAD)
return
if(isnull(current_bodypart) || isnull(current_bodypart.owner))
return
var/mob/living/carbon/human/victim = current_bodypart.owner
if(prob(SPT_PROB(3, SSMOBS_DT)))
to_chat(victim, span_warning("The thing posing as your limb makes you feel funny...")) //warn em
//firstly as a sideeffect we drain nutrition from our host
victim.adjust_nutrition(-1.5)
if(!prob(SPT_PROB(1.5, SSMOBS_DT)))
return
if(istype(current_bodypart, /obj/item/bodypart/arm))
var/list/candidates = list()
for(var/atom/movable/movable in orange(victim, 1))
if(movable.anchored)
continue
if(movable == victim)
continue
if(!victim.CanReach(movable))
continue
candidates += movable
var/atom/movable/candidate = pick(candidates)
if(isnull(candidate))
return
victim.start_pulling(candidate, supress_message = TRUE)
victim.visible_message(span_warning("[victim][victim.p_s()] [current_bodypart] instinctually starts feeling [candidate]!"))
return
if(HAS_TRAIT(victim, TRAIT_IMMOBILIZED))
return
step(victim, pick(GLOB.cardinals))
to_chat(victim, span_warning("Your [current_bodypart] moves on its own!"))
/mob/living/basic/living_limb_flesh/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown)
. = ..()
if (!ishuman(target) || target.stat == DEAD || HAS_TRAIT(target, TRAIT_NODISMEMBER))
return
var/list/zone_candidates = target.get_missing_limbs()
for(var/obj/item/bodypart/bodypart in target.bodyparts)
if(bodypart.body_zone == BODY_ZONE_HEAD || bodypart.body_zone == BODY_ZONE_CHEST)
continue
if(HAS_TRAIT(bodypart, TRAIT_IGNORED_BY_LIVING_FLESH))
continue
if(bodypart.bodypart_flags & BODYPART_UNREMOVABLE)
continue
if(bodypart.brute_dam < 20)
continue
zone_candidates += bodypart.body_zone
if(!length(zone_candidates))
return
var/target_zone = pick(zone_candidates)
var/obj/item/bodypart/target_part = target.get_bodypart(target_zone)
if(isnull(target_part))
target.emote("scream") // dismember already makes them scream so only do this if we aren't doing that
else
target_part.dismember()
var/part_type
switch(target_zone)
if(BODY_ZONE_L_ARM)
part_type = /obj/item/bodypart/arm/left/flesh
if(BODY_ZONE_R_ARM)
part_type = /obj/item/bodypart/arm/right/flesh
if(BODY_ZONE_L_LEG)
part_type = /obj/item/bodypart/leg/left/flesh
if(BODY_ZONE_R_LEG)
part_type = /obj/item/bodypart/leg/right/flesh
target.visible_message(span_danger("[src] [target_part ? "tears off and attaches itself" : "attaches itself"] to where [target][target.p_s()] limb used to be!"))
current_bodypart = new part_type(TRUE) //dont_spawn_flesh, we cant use named arguments here
current_bodypart.replace_limb(target, TRUE)
forceMove(current_bodypart)
register_to_limb(current_bodypart)
/mob/living/basic/living_limb_flesh/proc/register_to_limb(obj/item/bodypart/part)
ai_controller.set_ai_status(AI_STATUS_OFF)
RegisterSignal(part, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_lost))
RegisterSignal(part.owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died))
RegisterSignal(part.owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(owner_shocked)) //detach if we are shocked, not beneficial for the host but hey its a sideeffect
/mob/living/basic/living_limb_flesh/proc/owner_shocked(datum/source, shock_damage, source, siemens_coeff, flags)
SIGNAL_HANDLER
if(shock_damage < 10)
return
var/mob/living/carbon/human/part_owner = current_bodypart.owner
if(!detach_self())
return
var/turf/our_location = get_turf(src)
our_location.visible_message(span_warning("[part_owner][part_owner.p_s()] [current_bodypart] begins to convulse wildly!"))
/mob/living/basic/living_limb_flesh/proc/owner_died(datum/source, gibbed)
SIGNAL_HANDLER
if(gibbed)
return
addtimer(CALLBACK(src, PROC_REF(detach_self)), 1 SECONDS) //we need new hosts, dead people suck!
/mob/living/basic/living_limb_flesh/proc/detach_self()
if(isnull(current_bodypart))
return FALSE
current_bodypart.dismember()
return TRUE//on_limb_lost should be called after that
/mob/living/basic/living_limb_flesh/proc/on_limb_lost(atom/movable/source, mob/living/carbon/old_owner, dismembered)
SIGNAL_HANDLER
UnregisterSignal(source, COMSIG_BODYPART_REMOVED)
UnregisterSignal(old_owner, COMSIG_LIVING_ELECTROCUTE_ACT)
UnregisterSignal(old_owner, COMSIG_LIVING_DEATH)
addtimer(CALLBACK(src, PROC_REF(wake_up), source), 2 SECONDS)
/mob/living/basic/living_limb_flesh/proc/wake_up(atom/limb)
ai_controller.set_ai_status(AI_STATUS_ON)
forceMove(limb.drop_location())
current_bodypart = null
qdel(limb)
visible_message(span_warning("[src] begins flailing around!"))
Shake(6, 6, 0.5 SECONDS)
@@ -0,0 +1,96 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top/SelectBehaviors(datum/ai_controller/controller, delta_time)
var/mob/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(!target || QDELETED(target))
return
if(target.loc != controller.pawn.loc)
return
return ..()
/datum/ai_controller/basic_controller/living_floor
max_target_distance = 2
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
)
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top
)
/mob/living/basic/living_floor
name = "floor"
desc = "The floor you walk on. It looks near-impervious to damage."
icon = 'icons/turf/floors.dmi'
icon_state = "floor"
icon_living = "floor"
mob_size = MOB_SIZE_HUGE
mob_biotypes = MOB_SPECIAL
status_flags = GODMODE //nothing but crowbars may kill us
death_message = ""
unsuitable_atmos_damage = 0
minimum_survivable_temperature = 0
maximum_survivable_temperature = INFINITY
basic_mob_flags = DEL_ON_DEATH
move_resist = INFINITY
density = FALSE
combat_mode = TRUE
layer = TURF_LAYER
plane = FLOOR_PLANE
faction = list(FACTION_HOSTILE)
melee_damage_lower = 20
melee_damage_upper = 40 //pranked.....
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
ai_controller = /datum/ai_controller/basic_controller/living_floor
melee_attack_cooldown = 0.5 SECONDS // get real
var/icon_aggro = "floor-hostile"
var/desc_aggro = "This flooring is alive and filled with teeth, better not step on that. Being covered in plating, it is immune to damage. Seems vulnerable to prying though."
/mob/living/basic/living_floor/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_IMMOBILIZED, INNATE_TRAIT)
var/static/list/connections = list(COMSIG_ATOM_ENTERED = PROC_REF(look_aggro), COMSIG_ATOM_EXITED = PROC_REF(look_deaggro))
AddComponent(/datum/component/connect_range, tracked = src, connections = connections, range = 1, works_in_containers = FALSE)
/mob/living/basic/living_floor/proc/look_aggro(datum/source, mob/living/victim)
SIGNAL_HANDLER
if(!istype(victim) || istype(victim, /mob/living/basic/living_floor) || victim.stat == DEAD)
return
if(victim.loc == loc) //guaranteed bite
var/datum/targetting_datum/basic/targetting = ai_controller.blackboard[BB_TARGETTING_DATUM]
if(targetting.can_attack(src, victim))
melee_attack(victim)
icon_state = icon_aggro
desc = desc_aggro
/mob/living/basic/living_floor/proc/look_deaggro(datum/source, mob/living/victim)
SIGNAL_HANDLER
if(!istype(victim) && !istype(victim, /mob/living/basic/living_floor))
return
icon_state = initial(icon_state)
desc = initial(desc_aggro)
/mob/living/basic/living_floor/med_hud_set_health()
return
/mob/living/basic/living_floor/med_hud_set_status()
return
/mob/living/basic/living_floor/attackby(obj/item/weapon, mob/user, params)
if(weapon.tool_behaviour != TOOL_CROWBAR)
return ..()
balloon_alert(user, "prying...")
playsound(src, 'sound/items/crowbar.ogg', 45, TRUE)
if(!do_after(user, 5 SECONDS, src))
return
new /obj/effect/gibspawner/generic(loc)
qdel(src)
/mob/living/basic/living_floor/white
icon_state = "white"
icon_living = "white"
icon_aggro = "whitefloor-hostile"