diff --git a/aurorastation.dme b/aurorastation.dme
index 042fdaff08c..242250b75e7 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -118,6 +118,7 @@
#include "code\_onclick\hud\gun_mode.dm"
#include "code\_onclick\hud\hud.dm"
#include "code\_onclick\hud\human.dm"
+#include "code\_onclick\hud\morph.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\nymph_hud.dm"
#include "code\_onclick\hud\other_mobs.dm"
@@ -1032,9 +1033,6 @@
#include "code\game\objects\structures\windoor_assembly.dm"
#include "code\game\objects\structures\window.dm"
#include "code\game\objects\structures\window_spawner.dm"
-#include "code\game\objects\structures\alien\alien.dm"
-#include "code\game\objects\structures\alien\node.dm"
-#include "code\game\objects\structures\alien\resin.dm"
#include "code\game\objects\structures\crates_lockers\closets.dm"
#include "code\game\objects\structures\crates_lockers\crates.dm"
#include "code\game\objects\structures\crates_lockers\largecrate.dm"
@@ -1063,7 +1061,10 @@
#include "code\game\objects\structures\crates_lockers\closets\secure\secure_closets.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\security.dm"
#include "code\game\objects\structures\crates_lockers\crates\gear_loadout.dm"
-#include "code\game\objects\structures\stool_bed_chair_nest\alien_nests.dm"
+#include "code\game\objects\structures\gore\core.dm"
+#include "code\game\objects\structures\gore\nest.dm"
+#include "code\game\objects\structures\gore\tendrils.dm"
+#include "code\game\objects\structures\gore\wall.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\bed.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\chairs.dm"
#include "code\game\objects\structures\stool_bed_chair_nest\stools.dm"
@@ -2771,6 +2772,8 @@
#include "code\modules\spell_system\spells\spell_list\self\conjure\construct_spells\cultify_area.dm"
#include "code\modules\spell_system\spells\spell_list\self\conjure\construct_spells\summon_pylon.dm"
#include "code\modules\spell_system\spells\spell_list\self\conjure\construct_spells\summon_soulstone.dm"
+#include "code\modules\spell_system\spells\spell_list\self\conjure\morph_spells\create_nest.dm"
+#include "code\modules\spell_system\spells\spell_list\self\conjure\morph_spells\create_node.dm"
#include "code\modules\spell_system\spells\spell_list\self\equip\holy_relic.dm"
#include "code\modules\spell_system\spells\spell_list\self\equip\seed.dm"
#include "code\modules\spell_system\spells\spell_list\self\equip\shield.dm"
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 3b88ac7c255..264a2513a87 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -37,6 +37,8 @@
#define LEFT 1
#define RIGHT 2
+#define FIST_ATTACK_ANIMATION -1
+
// Pulse levels, very simplified.
#define PULSE_NONE 0 // So !M.pulse checks would be possible.
#define PULSE_SLOW 1 // <60 bpm
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 3af4962d32f..c31e8e27e0a 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -52,6 +52,7 @@
#define ui_dropbutton "EAST-4:22,SOUTH:5"
#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
+#define ui_morph_resist "EAST-2:26,SOUTH:5"
#define ui_acti "EAST-2:26,SOUTH:5"
#define ui_movi "EAST-3:24,SOUTH:5"
#define ui_zonesel "EAST-1:28,SOUTH:5"
diff --git a/code/_onclick/hud/morph.dm b/code/_onclick/hud/morph.dm
new file mode 100644
index 00000000000..b83ccedc5ce
--- /dev/null
+++ b/code/_onclick/hud/morph.dm
@@ -0,0 +1,30 @@
+/mob/living/simple_animal/hostile/morph/instantiate_hud(var/datum/hud/HUD)
+ HUD.morph_hud()
+
+/datum/hud/proc/morph_hud()
+ var/obj/screen/mov_intent = new /obj/screen/movement_intent()
+ mov_intent.set_dir(SOUTHWEST)
+ mov_intent.icon = 'icons/mob/screen/morph.dmi'
+ mov_intent.icon_state = (mymob.m_intent == "run" ? "running" : "walking")
+ move_intent = mov_intent
+
+ mymob.healths = new /obj/screen()
+ mymob.healths.name = "health"
+ mymob.healths.icon = 'icons/mob/screen/morph.dmi'
+ mymob.healths.icon_state = "health0"
+ mymob.healths.screen_loc = ui_alien_health
+
+ mymob.zone_sel = new /obj/screen/zone_sel()
+ mymob.zone_sel.icon = 'icons/mob/screen/morph.dmi'
+ mymob.zone_sel.cut_overlays()
+ mymob.zone_sel.add_overlay(image('icons/mob/zone_sel.dmi', "[mymob.zone_sel.selecting]"))
+
+ var/obj/screen/resist = new /obj/screen()
+ resist.name = "resist"
+ resist.icon = 'icons/mob/screen/morph.dmi'
+ resist.icon_state = "act_resist"
+ resist.screen_loc = ui_morph_resist
+ hotkeybuttons += resist
+
+ mymob.client.screen = null
+ mymob.client.screen += list(mov_intent, mymob.healths, mymob.zone_sel, resist)
\ No newline at end of file
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index f52653cb44b..f56f8114eeb 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -451,8 +451,15 @@
usr.m_intent = "walk"
if("walk")
usr.m_intent = "run"
-
- update_move_icon(usr)
+ else if(istype(usr, /mob/living/simple_animal/hostile/morph))
+ var/mob/living/simple_animal/hostile/morph/M = usr
+ switch(usr.m_intent)
+ if("run")
+ usr.m_intent = "walk"
+ if("walk")
+ usr.m_intent = "run"
+ M.update_speed()
+ update_move_icon(usr)
// Hand slots are special to handle the handcuffs overlay
/obj/screen/inventory/hand
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index 16a13b8c191..f12efa31123 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -20,6 +20,9 @@
var/drytime
var/dries = TRUE
+/obj/effect/decal/cleanable/blood/no_dry
+ dries = FALSE
+
/obj/effect/decal/cleanable/blood/reveal_blood()
if(!fluorescent)
fluorescent = 1
diff --git a/code/game/objects/structures/alien/alien.dm b/code/game/objects/structures/alien/alien.dm
deleted file mode 100644
index b911b997c0a..00000000000
--- a/code/game/objects/structures/alien/alien.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-/obj/structure/alien
- name = "alien thing"
- desc = "There's something alien about this."
- icon = 'icons/mob/npc/alien.dmi'
- layer = 4
- var/health = 50
-
-/obj/structure/alien/proc/healthcheck()
- if(health <=0)
- density = 0
- qdel(src)
- return
-
-/obj/structure/alien/bullet_act(var/obj/item/projectile/Proj)
- health -= Proj.damage
- ..()
- healthcheck()
- return
-
-/obj/structure/alien/ex_act(severity)
- switch(severity)
- if(1.0)
- health-=50
- if(2.0)
- health-=50
- if(3.0)
- if (prob(50))
- health-=50
- else
- health-=25
- healthcheck()
- return
-
-/obj/structure/alien/hitby(AM as mob|obj)
- ..()
- visible_message("\The [src] was hit by \the [AM].")
- var/tforce = 0
- if(ismob(AM))
- tforce = 10
- else
- tforce = AM:throwforce
- playsound(loc, 'sound/effects/attackblob.ogg', 100, 1)
- health = max(0, health - tforce)
- healthcheck()
- ..()
- return
-
-/obj/structure/alien/attack_generic()
- attack_hand(usr)
-
-/obj/structure/alien/attackby(var/obj/item/W, var/mob/user)
- health = max(0, health - W.force)
- playsound(loc, 'sound/effects/attackblob.ogg', 100, 1)
- healthcheck()
- ..()
- return
-
-/obj/structure/alien/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
- if(air_group) return 0
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return !opacity
- return !density
\ No newline at end of file
diff --git a/code/game/objects/structures/alien/node.dm b/code/game/objects/structures/alien/node.dm
deleted file mode 100644
index 49357f94c43..00000000000
--- a/code/game/objects/structures/alien/node.dm
+++ /dev/null
@@ -1,105 +0,0 @@
-#define NODERANGE 3
-
-/obj/structure/alien/weeds
- name = "weeds"
- desc = "Weird purple weeds."
- icon_state = "weeds"
- anchored = 1
- density = 0
- health = 15
- var/obj/structure/alien/weeds/node/linked_node = null
- var/list/dirs_left
-
-/obj/structure/alien/weeds/node
- icon_state = "weednode"
- name = "purple sac"
- desc = "Weird purple octopus-like thing."
- light_range = NODERANGE
- var/node_range = NODERANGE
-
-/obj/structure/alien/weeds/Destroy()
- STOP_PROCESSING(SSprocessing, src)
- return ..()
-
-/obj/structure/alien/weeds/Initialize(pos, node)
- . = ..()
- if(istype(loc, /turf/space))
- return INITIALIZE_HINT_QDEL
- linked_node = node
- if(icon_state == "weeds")icon_state = pick("weeds", "weeds1", "weeds2")
-
- START_PROCESSING(SSprocessing, src)
-
- dirs_left = cardinal.Copy()
-
-/obj/structure/alien/weeds/process()
- var/turf/U = get_turf(src)
-
- if (istype(U, /turf/space))
- qdel(src)
- return PROCESS_KILL
-
- if (!dirs_left.len || !linked_node || get_dist(linked_node, src) > linked_node.node_range)
- return PROCESS_KILL
-
- for (var/D in dirs_left)
- var/turf/T = get_step(src, D)
-
- if (!isturf(T) || T.is_hole || T.is_space() || locate(/obj/structure/alien/weeds, T))
- dirs_left -= D
- continue
-
- if (T.density)
- continue
-
- var/spawn_new = TRUE
- for (var/aa in T)
- var/atom/A = aa
- if (istype(/obj/structure/alien/weeds, A))
- dirs_left -= D
- spawn_new = FALSE
- break
-
- else if (A.density)
- spawn_new = FALSE
-
- if (spawn_new)
- new /obj/structure/alien/weeds(T, linked_node)
-
-
-/obj/structure/alien/weeds/ex_act(severity)
- switch(severity)
- if(1.0)
- qdel(src)
- if(2.0)
- if (prob(50))
- qdel(src)
- if(3.0)
- if (prob(5))
- qdel(src)
- return
-
-/obj/structure/alien/weeds/attackby(var/obj/item/W, var/mob/user)
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
-
- visible_message("\The [src] have been attacked with \the [W][(user ? " by [user]." : ".")]")
-
- var/damage = W.force / 4.0
-
- if(W.iswelder())
- var/obj/item/weldingtool/WT = W
-
- if(WT.remove_fuel(0, user))
- damage = 15
- playsound(loc, 'sound/items/welder.ogg', 100, 1)
-
- health -= damage
- healthcheck()
-
-
-/obj/structure/alien/weeds/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
- if(exposed_temperature > 300 + T0C)
- health -= 5
- healthcheck()
-
-#undef NODERANGE
diff --git a/code/game/objects/structures/alien/resin.dm b/code/game/objects/structures/alien/resin.dm
deleted file mode 100644
index 464eacae6e9..00000000000
--- a/code/game/objects/structures/alien/resin.dm
+++ /dev/null
@@ -1,42 +0,0 @@
-/obj/structure/alien/resin
- name = "resin"
- desc = "Looks like some kind of slimy growth."
- icon_state = "resin"
-
- density = 1
- opacity = 1
- anchored = 1
- health = 200
-
-/obj/structure/alien/resin/wall
- name = "resin wall"
- desc = "Purple slime solidified into a wall."
- icon_state = "resinwall"
-
-/obj/structure/alien/resin/membrane
- name = "resin membrane"
- desc = "Purple slime just thin enough to let light pass through."
- icon_state = "resinmembrane"
- opacity = 0
- health = 120
-
-/obj/structure/alien/resin/New()
- ..()
- var/turf/T = get_turf(src)
- T.thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
-
-/obj/structure/alien/resin/Destroy()
- var/turf/T = get_turf(src)
- T.thermal_conductivity = initial(T.thermal_conductivity)
- return ..()
-
-/obj/structure/alien/resin/attack_hand(var/mob/user)
- if (HULK in user.mutations)
- visible_message("\The [user] destroys \the [name]!")
- health = 0
- else
- visible_message("\The [user] claws at \the [src]!")
- // Todo check attack datums.
- health -= rand(5,10)
- healthcheck()
- return
diff --git a/code/game/objects/structures/gore/core.dm b/code/game/objects/structures/gore/core.dm
new file mode 100644
index 00000000000..6d0533d151f
--- /dev/null
+++ b/code/game/objects/structures/gore/core.dm
@@ -0,0 +1,83 @@
+/obj/structure/gore
+ name = "alien thing"
+ desc = "There's something alien about this."
+ icon = 'icons/obj/gore_structures.dmi'
+ layer = ABOVE_CABLE_LAYER + 0.1
+ anchored = TRUE
+ density = FALSE
+ var/destroy_message = "THE STRUCTURE collapses in on itself!"
+ var/maxHealth = 50
+ var/health = 50
+
+/obj/structure/gore/Initialize(mapload)
+ . = ..()
+ health = maxHealth
+
+/obj/structure/gore/examine(mob/user)
+ if(..(user, 2))
+ var/health_div = health / maxHealth
+ if(health_div >= 0.9)
+ to_chat(user, SPAN_NOTICE("\The [src] appears completely intact."))
+ else if(health_div >= 0.7)
+ to_chat(user, SPAN_NOTICE("\The [src] is starting to tear somewhat."))
+ else if(health_div >= 0.4)
+ to_chat(user, SPAN_WARNING("\The [src] is starting to fall apart."))
+ else
+ to_chat(user, SPAN_WARNING("\The [src] is barely holding itself together!"))
+
+/obj/structure/gore/proc/healthcheck()
+ if(health <= 0)
+ var/final_message = replacetext(destroy_message, "THE STRUCTURE", "\The [src]")
+ visible_message(SPAN_WARNING(final_message))
+ qdel(src)
+
+/obj/structure/gore/bullet_act(var/obj/item/projectile/Proj)
+ health -= Proj.damage
+ healthcheck()
+ return ..()
+
+/obj/structure/gore/ex_act(severity)
+ switch(severity)
+ if(1.0)
+ health -= 50
+ if(2.0)
+ health -= 50
+ if(3.0)
+ if(prob(50))
+ health -= 50
+ else
+ health -= 25
+ healthcheck()
+
+/obj/structure/gore/hitby(atom/movable/AM)
+ . = ..()
+ visible_message(SPAN_WARNING("\The [src] was hit by \the [AM]."))
+ playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE)
+ var/throw_force = 0
+ if(isobj(AM))
+ var/obj/O = AM
+ throw_force = O.throwforce
+ else if(ismob(AM))
+ throw_force = 10
+ health -= throw_force
+ healthcheck()
+
+/obj/structure/gore/attack_generic()
+ attack_hand(usr)
+
+/obj/structure/gore/attackby(obj/item/W, mob/user)
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ user.do_attack_animation(src, W)
+ var/force_damage = W.force
+ if(W.damtype == BURN)
+ force_damage *= 1.25
+ health -= force_damage
+ playsound(loc, 'sound/effects/attackblob.ogg', 80, TRUE)
+ healthcheck()
+
+/obj/structure/gore/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+ if(air_group)
+ return FALSE
+ if(istype(mover) && mover.checkpass(PASSGLASS))
+ return !opacity
+ return !density
\ No newline at end of file
diff --git a/code/game/objects/structures/gore/nest.dm b/code/game/objects/structures/gore/nest.dm
new file mode 100644
index 00000000000..a08963b2106
--- /dev/null
+++ b/code/game/objects/structures/gore/nest.dm
@@ -0,0 +1,65 @@
+#define NEST_RESIST_TIME 1200
+
+/obj/structure/bed/nest
+ name = "gore nest"
+ desc = "It's a gruesome pile of thick, sticky flesh shaped like a nest."
+ icon = 'icons/obj/gore_structures.dmi'
+ icon_state = "nest"
+ var/destroy_message = "THE STRUCTURE collapses in on itself!"
+ var/maxHealth = 100
+ var/health = 100
+
+/obj/structure/bed/nest/Initialize(mapload, new_material, new_padding_material)
+ . = ..()
+ health = maxHealth
+
+/obj/structure/bed/nest/examine(mob/user)
+ if(..(user, 2))
+ var/health_div = health / maxHealth
+ if(health_div >= 0.9)
+ to_chat(user, SPAN_NOTICE("\The [src] appears completely intact."))
+ else if(health_div >= 0.7)
+ to_chat(user, SPAN_NOTICE("\The [src] is starting to tear somewhat."))
+ else if(health_div >= 0.4)
+ to_chat(user, SPAN_WARNING("\The [src] is starting to fall apart."))
+ else
+ to_chat(user, SPAN_WARNING("\The [src] is barely holding itself together!"))
+
+/obj/structure/bed/nest/update_icon()
+ return
+
+/obj/structure/bed/nest/user_unbuckle_mob(mob/user)
+ if(buckled_mob?.buckled == src)
+ add_fingerprint(user)
+ if(buckled_mob != user)
+ buckled_mob.visible_message("[user] pulls [buckled_mob] free from the sticky flesh!", SPAN_NOTICE("[user] pulls you free from the gelatinous flesh."), SPAN_WARNING("You hear squelching..."))
+ unbuckle_buckled_mob(buckled_mob)
+ else
+ if(world.time <= buckled_mob.last_special + NEST_RESIST_TIME)
+ return
+ buckled_mob.last_special = world.time
+ buckled_mob.visible_message(SPAN_WARNING("[buckled_mob] starts struggling to break free of the sticky flesh..."), SPAN_WARNING("You struggle to break free from the gelatinous flesh..."), SPAN_WARNING("You hear squelching..."))
+ if(do_after(buckled_mob, NEST_RESIST_TIME, TRUE))
+ unbuckle_buckled_mob(buckled_mob)
+
+/obj/structure/bed/nest/proc/unbuckle_buckled_mob(var/mob/buck)
+ if(buckled_mob == buck && buck.buckled == src)
+ buck.pixel_y = 0
+ buck.old_y = 0
+ unbuckle_mob()
+
+/obj/structure/bed/nest/attackby(obj/item/W, mob/user)
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ user.do_attack_animation(src, W)
+ var/force_damage = W.force
+ if(W.damtype == BURN)
+ force_damage *= 1.25
+ health -= force_damage
+ playsound(loc, 'sound/effects/attackblob.ogg', 80, TRUE)
+ healthcheck()
+
+/obj/structure/bed/nest/proc/healthcheck()
+ if(health <= 0)
+ var/final_message = replacetext(destroy_message, "THE STRUCTURE", "\The [src]")
+ visible_message(SPAN_WARNING(final_message))
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/objects/structures/gore/tendrils.dm b/code/game/objects/structures/gore/tendrils.dm
new file mode 100644
index 00000000000..22445ee431a
--- /dev/null
+++ b/code/game/objects/structures/gore/tendrils.dm
@@ -0,0 +1,145 @@
+#define NODERANGE 3
+
+/obj/structure/gore/tendrils
+ name = "bloody tendrils"
+ desc = "Bloody, pulsating tendrils."
+ icon_state = "tendril"
+ maxHealth = 40
+ var/being_destroyed = FALSE
+ var/is_node = FALSE
+ var/obj/structure/gore/tendrils/node/linked_node = null
+ var/list/dirs_left
+ var/grow_speed = 5 SECONDS
+ var/last_grow = 0
+
+/obj/structure/gore/tendrils/node
+ name = "flesh sac"
+ desc = "Clumped up flesh, pulsating in rhythm with the tendrils that surround it."
+ icon_state = "tendril_node"
+ density = TRUE
+ layer = ABOVE_CABLE_LAYER + 0.2
+ maxHealth = 150
+ light_range = NODERANGE
+ light_color = LIGHT_COLOR_EMERGENCY
+ is_node = TRUE
+ grow_speed = 3 SECONDS
+ var/node_range = NODERANGE
+
+/obj/structure/gore/tendrils/node/CanPass(atom/movable/mover, turf/target, height, air_group)
+ . = ..()
+ if(!.)
+ if(istype(mover, /mob/living/simple_animal/hostile/morph))
+ return TRUE
+ if(ishuman(mover))
+ var/mob/living/carbon/human/H = mover
+ if(H.mind.changeling)
+ return TRUE
+
+/obj/structure/gore/tendrils/Destroy()
+ var/turf/T = get_turf(src)
+ T.movement_cost = initial(T.movement_cost)
+ STOP_PROCESSING(SSprocessing, src)
+ being_destroyed = TRUE
+ update_neighbours()
+ return ..()
+
+/obj/structure/gore/tendrils/Initialize(pos, node)
+ . = ..()
+ if(istype(loc, /turf/space) || isopenturf(loc))
+ return INITIALIZE_HINT_QDEL
+
+ var/turf/T = get_turf(src)
+ T.movement_cost += 2
+ if(!is_node)
+ linked_node = node
+ update_sprite()
+ update_neighbours()
+ else
+ linked_node = src
+ new /obj/structure/gore/tendrils(loc, linked_node)
+
+ new /obj/effect/decal/cleanable/blood/no_dry(loc)
+ START_PROCESSING(SSprocessing, src)
+ dirs_left = cardinal.Copy()
+
+/obj/structure/gore/tendrils/proc/update_neighbours(turf/U)
+ if(!U)
+ U = loc
+ if(istype(U))
+ for(var/dirn in cardinal)
+ var/turf/T = get_step(U, dirn)
+ if(!istype(T))
+ continue
+ var/obj/structure/gore/tendrils/GT = locate() in T
+ if(GT)
+ GT.update_sprite()
+
+/obj/structure/gore/tendrils/proc/update_sprite()
+ var/my_dir = 0
+ for(var/check_dir in cardinal)
+ var/turf/check = get_step(src, check_dir)
+ if(!istype(check))
+ continue
+ var/obj/structure/gore/tendrils/GT = locate() in check
+ if(GT && !GT.being_destroyed)
+ my_dir |= check_dir
+
+ if(my_dir == 15) //weeds in all four directions
+ icon_state = "tendril[rand(0, 15)]"
+ else if(my_dir == 0) //no weeds in any direction
+ icon_state = "tendril"
+ else
+ icon_state = "tendril_dir[my_dir]"
+
+/obj/structure/gore/tendrils/node/update_sprite()
+ for(var/obj/structure/gore/tendrils/GT in loc)
+ if(GT == src)
+ continue
+ GT.update_sprite()
+
+/obj/structure/gore/tendrils/process()
+ if(last_grow + grow_speed > world.time)
+ return
+ last_grow = world.time
+ var/turf/U = get_turf(src)
+
+ if(istype(U, /turf/space) || isopenturf(U))
+ qdel(src)
+ return PROCESS_KILL
+
+ if(!length(dirs_left) || !linked_node || get_dist(linked_node, U) > linked_node.node_range)
+ return PROCESS_KILL
+
+ var/D = pick(dirs_left)
+ var/turf/T = get_step(U, D)
+
+ var/obj/machinery/door/MD = locate() in T
+ if(MD?.density)
+ return
+ var/obj/structure/simple_door/SD = locate() in T
+ if(SD?.density)
+ return
+
+ if(!isturf(T) || T.is_hole || T.is_space() || T.density || locate(/obj/structure/gore/tendrils, T))
+ dirs_left -= D
+ return
+
+ dirs_left -= D
+ new /obj/structure/gore/tendrils(T, linked_node)
+
+/obj/structure/gore/tendrils/ex_act(severity)
+ switch(severity)
+ if(1.0)
+ health = 0
+ if(2.0)
+ health -= maxHealth / 2
+ if(3.0)
+ health -= maxHealth / 5
+ healthcheck()
+
+/obj/structure/gore/tendrils/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
+ if(exposed_temperature > 300 + T0C)
+ health -= 5
+ healthcheck()
+
+#undef NODERANGE
\ No newline at end of file
diff --git a/code/game/objects/structures/gore/wall.dm b/code/game/objects/structures/gore/wall.dm
new file mode 100644
index 00000000000..a5d9e6450bc
--- /dev/null
+++ b/code/game/objects/structures/gore/wall.dm
@@ -0,0 +1,40 @@
+/obj/structure/gore/floor
+ name = "flesh floor"
+ desc = "Looks like the floor was covered by some fleshlike growth."
+ icon_state = "flesh_floor"
+ maxHealth = 100
+
+/obj/structure/gore/wall
+ name = "flesh wall"
+ desc = "Chunks of flesh sculpted to form an impassable wall."
+ icon_state = "flesh_wall"
+ opacity = TRUE
+ maxHealth = 200
+
+/obj/structure/gore/wall/membrane
+ name = "flesh membrane"
+ desc = "Skin and muscle stretched just thin enough to let light pass through."
+ icon_state = "flesh_membrane"
+ opacity = FALSE
+ maxHealth = 120
+
+/obj/structure/gore/wall/Initialize()
+ . = ..()
+ var/turf/T = get_turf(src)
+ T.thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
+
+/obj/structure/gore/wall/Destroy()
+ var/turf/T = get_turf(src)
+ T.thermal_conductivity = initial(T.thermal_conductivity)
+ return ..()
+
+/obj/structure/gore/resin/attack_hand(var/mob/user)
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ user.do_attack_animation(src, FIST_ATTACK_ANIMATION)
+ if(HULK in user.mutations)
+ visible_message(SPAN_DANGER("\The [user] destroys \the [src]!"))
+ health = 0
+ else
+ visible_message(SPAN_DANGER("\The [user] claws at \the [src]!"))
+ health -= rand(5, 10)
+ healthcheck()
\ No newline at end of file
diff --git a/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm b/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm
deleted file mode 100644
index 1f1c884deb6..00000000000
--- a/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-//Alium nests. Essentially beds with an unbuckle delay that only aliums can buckle mobs to.
-#define NEST_RESIST_TIME 1200
-
-/obj/structure/bed/nest
- name = "alien nest"
- desc = "It's a gruesome pile of thick, sticky resin shaped like a nest."
- icon = 'icons/mob/npc/alien.dmi'
- icon_state = "nest"
- var/health = 100
-
-/obj/structure/bed/nest/update_icon()
- return
-
-/obj/structure/bed/nest/user_unbuckle_mob(mob/user as mob)
- if(buckled_mob)
- if(buckled_mob.buckled == src)
- if(buckled_mob != user)
- buckled_mob.visible_message(\
- "[user.name] pulls [buckled_mob.name] free from the sticky nest!",\
- "[user.name] pulls you free from the gelatinous resin.",\
- "You hear squelching...")
- buckled_mob.pixel_y = 0
- buckled_mob.old_y = 0
- unbuckle_mob()
- else
- if(world.time <= buckled_mob.last_special+NEST_RESIST_TIME)
- return
- buckled_mob.last_special = world.time
- buckled_mob.visible_message(\
- "[buckled_mob.name] struggles to break free of the gelatinous resin...",\
- "You struggle to break free from the gelatinous resin...",\
- "You hear squelching...")
- spawn(NEST_RESIST_TIME)
- if(user && buckled_mob && user.buckled == src)
- buckled_mob.last_special = world.time
- buckled_mob.pixel_y = 0
- buckled_mob.old_y = 0
- unbuckle_mob()
- src.add_fingerprint(user)
- return
-
-/obj/structure/bed/nest/attackby(obj/item/W as obj, mob/user as mob)
- var/aforce = W.force
- health = max(0, health - aforce)
- playsound(loc, 'sound/effects/attackblob.ogg', 100, 1)
- for(var/mob/M in viewers(src, 7))
- M.show_message("[user] hits [src] with [W]!", 1)
- healthcheck()
-
-/obj/structure/bed/nest/proc/healthcheck()
- if(health <=0)
- density = 0
- qdel(src)
- return
diff --git a/code/modules/item_worth/worths_list.dm b/code/modules/item_worth/worths_list.dm
index 9e215af5bb4..044f6980b78 100644
--- a/code/modules/item_worth/worths_list.dm
+++ b/code/modules/item_worth/worths_list.dm
@@ -725,7 +725,7 @@ var/list/worths = list(
/obj/structure/transit_tube = 80,
/obj/structure/transit_tube_pod = 100,
/obj/structure/toilet = 50,
- /obj/structure/alien = 300,
+ /obj/structure/gore = 300,
/obj/structure/closet = 15,
/obj/structure/bed = 7,
/obj/structure/holostool = 0,
diff --git a/code/modules/mob/animations.dm b/code/modules/mob/animations.dm
index 81372bc1a88..767148cf47c 100644
--- a/code/modules/mob/animations.dm
+++ b/code/modules/mob/animations.dm
@@ -195,6 +195,8 @@ note dizziness decrements automatically in the mob's Life() proc.
..()
is_floating = 0 // If we were without gravity, the bouncing animation got stopped, so we make sure we restart the bouncing after the next movement.
+ if(attack_item == FIST_ATTACK_ANIMATION) // only play the physical movement
+ return
// What icon do we use for the attack?
var/image/I
if(attack_item)
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index c5c9441721e..f6d6f1c623b 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -87,7 +87,7 @@
tally = max(0, tally-3)
var/turf/T = get_turf(src)
- if(T)
+ if(T && !mind.changeling) // changelings don't get movement costs
tally += T.movement_cost
tally += config.human_delay
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 559c2c1729a..95b61ee21f7 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -794,7 +794,7 @@ default behaviour is:
set category = "IC"
resting = !resting
- to_chat(src, "You are now [resting ? "resting" : "getting up"]")
+ to_chat(src, "You are now [resting ? "resting" : "getting up"].")
update_vision_cone()
/mob/living/proc/cannot_use_vents()
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 79ba8abb60d..76733c07b95 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -441,7 +441,7 @@
else
resting = !resting
icon_state = resting ? "[chassis]_rest" : "[chassis]"
- to_chat(src, "You are now [resting ? "resting" : "getting up"]")
+ to_chat(src, "You are now [resting ? "resting" : "getting up"].")
canmove = !resting
diff --git a/code/modules/mob/living/simple_animal/hostile/morph.dm b/code/modules/mob/living/simple_animal/hostile/morph.dm
index 616a5d81ef0..928dd5bc8d7 100644
--- a/code/modules/mob/living/simple_animal/hostile/morph.dm
+++ b/code/modules/mob/living/simple_animal/hostile/morph.dm
@@ -8,6 +8,7 @@
icon = 'icons/mob/npc/animal.dmi'
icon_state = "morph"
icon_living = "morph"
+ icon_rest = "morph_rest"
icon_dead = "morph_dead"
speed = 2.5
stop_automated_movement = TRUE
@@ -28,12 +29,14 @@
maxHealth = 125
health = 125
+ max_stamina = -1
melee_damage_lower = 12
melee_damage_upper = 16
see_in_dark = 8
see_invisible = SEE_INVISIBLE_NOLIGHTING
+ stop_sight_update = TRUE
minbodytemp = 0
maxbodytemp = 350
@@ -51,12 +54,51 @@
var/eat_while_disguised = FALSE
var/atom/movable/form = null
var/morph_time = 0
- var/static/list/blacklist_typecache = typecacheof(list(/obj/screen, /obj/singularity, /mob/living/simple_animal/hostile/morph, /obj/effect))
+ var/static/list/blacklist_typecache = typecacheof(list(/obj/screen, /obj/singularity, /mob/living/simple_animal/hostile/morph, /obj/effect, /obj/structure/gore))
/mob/living/simple_animal/hostile/morph/Initialize()
. = ..()
verbs += /mob/living/proc/ventcrawl
+ var/list/morph_spells = list(/spell/aoe_turf/conjure/node, /spell/aoe_turf/conjure/nest)
+ for(var/spell in morph_spells)
+ add_spell(new spell, "const_spell_ready")
+
+/mob/living/simple_animal/hostile/morph/Life()
+ . = ..()
+ if(stat == DEAD && healths)
+ healths.icon_state = "health6"
+ if(.)
+ if(healths)
+ switch(health / maxHealth * 100)
+ if(100 to INFINITY)
+ healths.icon_state = "health0"
+ if(80 to 100)
+ healths.icon_state = "health1"
+ if(60 to 80)
+ healths.icon_state = "health2"
+ if(40 to 60)
+ healths.icon_state = "health3"
+ if(20 to 40)
+ healths.icon_state = "health4"
+ if(0 to 20)
+ healths.icon_state = "health5"
+ else
+ healths.icon_state = "health6"
+
+ if((stat == UNCONSCIOUS || resting) && locate(/obj/structure/gore/tendrils) in loc)
+ health = min(maxHealth, health + 1)
+
+/mob/living/simple_animal/hostile/morph/verb/toggle_darkview()
+ set name = "Toggle Darkvision"
+ set desc = "Toggles whether you see light or not."
+ set category = "Abilities"
+
+ if(see_invisible == SEE_INVISIBLE_NOLIGHTING)
+ see_invisible = SEE_INVISIBLE_LIVING
+ else
+ see_invisible = SEE_INVISIBLE_NOLIGHTING
+
/mob/living/simple_animal/hostile/morph/examine(mob/user)
if(morphed)
. = form.examine(user)
@@ -66,6 +108,13 @@
else
return ..()
+/mob/living/simple_animal/hostile/morph/proc/update_speed()
+ switch(m_intent)
+ if("run")
+ speed = 1.5
+ if("walk")
+ speed = 2.5
+
/mob/living/simple_animal/hostile/morph/proc/allowed(atom/movable/A)
return !is_type_in_typecache(A, blacklist_typecache) && (isobj(A) || ismob(A))
@@ -201,5 +250,11 @@
return
return ..()
+/mob/living/simple_animal/hostile/morph/add_spell(var/spell/spell_to_add, var/spell_base = "wiz_spell_ready", var/master_type = /obj/screen/movable/spell_master)
+ . = ..()
+ for(var/obj/screen/movable/spell_master/spell_master in spell_masters)
+ spell_master.open_state = "morph_open"
+ spell_master.closed_state = "morph_closed"
+
/mob/living/simple_animal/hostile/morph/do_animate_chat(var/message, var/datum/language/language, var/small, var/list/show_to, var/duration, var/list/message_override)
INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, language, small, show_to, duration)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index d6d207ae13f..d028cd278c5 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -696,7 +696,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
else
fall_asleep()
- to_chat(src, SPAN_NOTICE("You are now [resting ? "resting" : "getting up"]"))
+ to_chat(src, SPAN_NOTICE("You are now [resting ? "resting" : "getting up"]."))
update_icon()
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm
index 0b974663cbf..ddd000af576 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm
@@ -373,11 +373,7 @@
playsound(F, 'sound/species/diona/gestalt_grow.ogg', 30, TRUE)
/datum/reagent/toxin/plantbgone/touch_obj(var/obj/O, var/volume)
- if(istype(O, /obj/structure/alien/weeds))
- var/obj/structure/alien/weeds/alien_weeds = O
- alien_weeds.health -= rand(15, 35)
- alien_weeds.healthcheck()
- else if(istype(O, /obj/effect/plant))
+ if(istype(O, /obj/effect/plant))
qdel(O)
/datum/reagent/toxin/plantbgone/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
diff --git a/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_nest.dm b/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_nest.dm
new file mode 100644
index 00000000000..2203282b829
--- /dev/null
+++ b/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_nest.dm
@@ -0,0 +1,14 @@
+/spell/aoe_turf/conjure/nest
+ name = "Create Nest"
+ desc = "This spell creates a nest beneath you."
+
+ charge_max = 600
+ spell_flags = Z2NOCAST
+ override_base = "const"
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/obj/structure/bed/nest)
+ cast_sound = 'sound/effects/attackblob.ogg'
+
+ hud_state = "nest"
\ No newline at end of file
diff --git a/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_node.dm b/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_node.dm
new file mode 100644
index 00000000000..d394cc2a1a3
--- /dev/null
+++ b/code/modules/spell_system/spells/spell_list/self/conjure/morph_spells/create_node.dm
@@ -0,0 +1,14 @@
+/spell/aoe_turf/conjure/node
+ name = "Create Node"
+ desc = "This spell creates a node beneath you."
+
+ charge_max = 1800
+ spell_flags = Z2NOCAST
+ override_base = "const"
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/obj/structure/gore/tendrils/node)
+ cast_sound = 'sound/effects/attackblob.ogg'
+
+ hud_state = "tendril_node"
\ No newline at end of file
diff --git a/html/changelogs/geeves-gore_pile.yml b/html/changelogs/geeves-gore_pile.yml
new file mode 100644
index 00000000000..908513d4ccc
--- /dev/null
+++ b/html/changelogs/geeves-gore_pile.yml
@@ -0,0 +1,9 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "Morphs now have HUDs, move intent, health, zone selection, resist."
+ - rscadd: "Morphs now have the ability to put down flesh sac tendril nodes, gore nests, and toggle darkvision (the latter is in the abilities tab)."
+ - rscadd: "Morphs can now rest. Resting on flesh tendrils will heal them, albeit quite slowly."
+ - rscadd: "Tendrils slow people walking over them down, unless they're a changeling."
diff --git a/icons/mob/npc/animal.dmi b/icons/mob/npc/animal.dmi
index 102c9556efc..a287acddc44 100644
Binary files a/icons/mob/npc/animal.dmi and b/icons/mob/npc/animal.dmi differ
diff --git a/icons/mob/screen/morph.dmi b/icons/mob/screen/morph.dmi
new file mode 100644
index 00000000000..02edd70270a
Binary files /dev/null and b/icons/mob/screen/morph.dmi differ
diff --git a/icons/mob/screen/spells.dmi b/icons/mob/screen/spells.dmi
index 84d62c32ca9..41a6afc362e 100644
Binary files a/icons/mob/screen/spells.dmi and b/icons/mob/screen/spells.dmi differ
diff --git a/icons/obj/gore_structures.dmi b/icons/obj/gore_structures.dmi
new file mode 100644
index 00000000000..2374d2ee960
Binary files /dev/null and b/icons/obj/gore_structures.dmi differ