diff --git a/beestation.dme b/beestation.dme
index b8d52d1a0c4bc..0eba7be95547f 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -1755,6 +1755,7 @@
#include "code\modules\admin\smites\dchat_democracy.dm"
#include "code\modules\admin\smites\fireball.dm"
#include "code\modules\admin\smites\floorcluwne.dm"
+#include "code\modules\admin\smites\floorcluwne_trauma.dm"
#include "code\modules\admin\smites\floorcluwne_stalker.dm"
#include "code\modules\admin\smites\forcecryo.dm"
#include "code\modules\admin\smites\forcesay.dm"
diff --git a/code/modules/admin/smites/floorcluwne_stalker.dm b/code/modules/admin/smites/floorcluwne_stalker.dm
index 1f4b69a4f7919..09a41fab47ec6 100644
--- a/code/modules/admin/smites/floorcluwne_stalker.dm
+++ b/code/modules/admin/smites/floorcluwne_stalker.dm
@@ -1,6 +1,6 @@
/// Spawns the evil floor cluwne to terrorize people
/datum/smite/floorcluwne_stalker
- name = "Floor Cluwne (Stalker)"
+ name = "Floor Cluwne (Stalker, Non-Lethal)"
/datum/smite/floorcluwne_stalker/effect(client/user, mob/living/target)
. = ..()
@@ -8,3 +8,4 @@
var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(get_turf(target))
FC.force_target(H)
FC.delete_after_target_killed = TRUE
+ FC.dontkill = TRUE
diff --git a/code/modules/admin/smites/floorcluwne_trauma.dm b/code/modules/admin/smites/floorcluwne_trauma.dm
new file mode 100644
index 0000000000000..51be259cf09ff
--- /dev/null
+++ b/code/modules/admin/smites/floorcluwne_trauma.dm
@@ -0,0 +1,24 @@
+/// Spawns the evil floor cluwne to terrorize people
+/datum/smite/floorcluwne_trauma
+ name = "Floor Cluwne (Traumatize, Non-Lethal)"
+ var/floorcluwne_trauma_level_generated
+
+/datum/smite/floorcluwne_trauma/effect(client/user, mob/living/target)
+ . = ..()
+ if(!floorcluwne_trauma_level_generated)
+ floorcluwne_trauma_level_generated = TRUE
+ message_admins("Generating z-level for Floorcluwne sacrifices...")
+ INVOKE_ASYNC(src, PROC_REF(generate_floorcluwne_z_level))
+
+ var/mob/living/carbon/human/H = target
+ var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(get_turf(target))
+ FC.force_target(H)
+ FC.delete_after_target_killed = FALSE
+ FC.terrorize = TRUE
+
+/// Generate the z-level.
+/datum/smite/floorcluwne_trauma/proc/generate_floorcluwne_z_level()
+ var/datum/map_template/heretic_sacrifice_level/new_level = new() //re-using heretic level
+ if(!new_level.load_new_z())
+ message_admins("The floorcluwne_trauma z-level failed to load.")
+ CRASH("Failed to initialize floorcluwne_trauma z-level!")
diff --git a/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm b/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm
index 6bebc3e09797c..166a0c327b95f 100644
--- a/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm
+++ b/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm
@@ -41,6 +41,9 @@ GLOBAL_VAR_INIT(floor_cluwnes, 0)
var/invalid_area_typecache = list(/area/space, /area/lavaland, /area/centcom, /area/shuttle/syndicate)
var/eating = FALSE
var/dontkill = FALSE //for if we just wanna curse a fucker
+ var/terrorize = FALSE //for Heretic curse, rather than kill
+ var/terror_count = 0
+ var/return_timers
var/obj/effect/dummy/floorcluwne_orbit/poi
var/obj/effect/temp_visual/fcluwne_manifest/cluwnehole
move_resist = INFINITY
@@ -183,9 +186,9 @@ GLOBAL_VAR_INIT(floor_cluwnes, 0)
interest = 0
stage = STAGE_HAUNT
return target = current_victim
-
- message_admins("Floor Cluwne was deleted due to a lack of valid targets, if this was a manually targeted instance please re-evaluate your choice.")
- qdel(src)
+ if(!terrorize)
+ message_admins("Floor Cluwne was deleted due to a lack of valid targets, if this was a manually targeted instance please re-evaluate your choice.")
+ qdel(src)
/mob/living/simple_animal/hostile/floor_cluwne/proc/Manifest()//handles disappearing and appearance anim
@@ -388,10 +391,13 @@ GLOBAL_VAR_INIT(floor_cluwnes, 0)
for(var/turf/open/T in RANGE_TURFS(4, H))
H.add_splatter_floor(T)
if(do_after(src, 50, target = H))
- H.unequip_everything()//more runtime prevention
- if(prob(75))
+ if(terrorize)
+ begin_trauma(H)
+ else if(prob(75))
+ H.unequip_everything() //runtime prevention
H.gib(FALSE)
else
+ H.unequip_everything() //runtime prevention
H.cluwneify()
H.adjustBruteLoss(30)
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100)
@@ -443,6 +449,185 @@ GLOBAL_VAR_INIT(floor_cluwnes, 0)
name += " ([GLOB.floor_cluwnes])"
AddElement(/datum/element/point_of_interest)
+/mob/living/simple_animal/hostile/floor_cluwne/proc/begin_trauma(mob/living/carbon/human/sac_target)
+ if(!LAZYLEN(GLOB.heretic_sacrifice_landmarks))
+ CRASH("[type] - begin_trauma was called, but no floorcluwne_trauma landmarks were found!")
+
+ var/obj/effect/landmark/heretic/destination_landmark = GLOB.heretic_sacrifice_landmarks[HERETIC_PATH_ASH]
+ if(!destination_landmark)
+ CRASH("[type] - begin_trauma could not find a destination landmark to send the target!")
+
+ var/turf/destination = get_turf(destination_landmark)
+
+ sac_target.handcuffed = new /obj/item/restraints/handcuffs/energy/cult(sac_target)
+ sac_target.update_handcuffed()
+ sac_target.do_jitter_animation(100)
+
+ if(sac_target.legcuffed)
+ sac_target.legcuffed.forceMove(sac_target.drop_location())
+ sac_target.legcuffed.dropped(sac_target)
+ sac_target.legcuffed = null
+ sac_target.update_inv_legcuffed()
+
+ addtimer(CALLBACK(sac_target, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 100), SACRIFICE_SLEEP_DURATION * (1/3))
+ addtimer(CALLBACK(sac_target, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 100), SACRIFICE_SLEEP_DURATION * (2/3))
+
+ // Grab their ghost, just in case they're dead or something.
+ sac_target.grab_ghost()
+ // If our target is dead, try to revive them
+ // and if we fail to revive them, don't proceede the chain
+ if(!sac_target.heal_and_revive(50, "[sac_target]'s heart begins to beat with an unholy force as they return from death!"))
+ return
+
+ if(sac_target.AdjustUnconscious(SACRIFICE_SLEEP_DURATION))
+ to_chat(sac_target, "Your mind feels torn apart as you fall into a shallow slumber...")
+ else
+ to_chat(sac_target, "Your mind begins to tear apart as you watch dark tendrils envelop you.")
+
+ sac_target.AdjustParalyzed(SACRIFICE_SLEEP_DURATION * 1.2)
+ sac_target.AdjustImmobilized(SACRIFICE_SLEEP_DURATION * 1.2)
+
+ addtimer(CALLBACK(src, PROC_REF(after_target_sleeps), sac_target, destination), SACRIFICE_SLEEP_DURATION * 0.5) // Teleport to the minigame
+
+ return TRUE
+
+/mob/living/simple_animal/hostile/floor_cluwne/proc/after_target_sleeps(mob/living/carbon/human/sac_target, turf/destination)
+ if(QDELETED(sac_target))
+ return
+
+ // Grab ghost again, just to be safe.
+ sac_target.grab_ghost()
+ // The target disconnected or something, we shouldn't bother sending them along.
+ if(!sac_target.client || !sac_target.mind)
+ return
+
+ // Send 'em to the destination. If the teleport fails, do nothing.
+ if(!destination || !do_teleport(sac_target, destination, asoundin = 'sound/magic/repulse.ogg', asoundout = 'sound/magic/blind.ogg', no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE, no_wake = TRUE))
+ return
+
+ // If our target died during the (short) wait timer,
+ // and we fail to revive them (using a lower number than before), do nothing.
+ if(!sac_target.heal_and_revive(75, "[sac_target]'s heart begins to beat with an unholy force as they return from death!"))
+ return
+
+ sac_target.cure_blind(null)
+ sac_target.invisibility = initial(sac_target.invisibility)
+ sac_target.density = initial(sac_target.density)
+ sac_target.set_anchored(initial(sac_target.anchored))
+ to_chat(sac_target, "Unnatural forces begin to claw at your very being from beyond the veil.")
+
+ sac_target.apply_status_effect(/datum/status_effect/unholy_determination, SACRIFICE_REALM_DURATION)
+ addtimer(CALLBACK(src, PROC_REF(after_target_wakes), sac_target), SACRIFICE_SLEEP_DURATION * 0.5) // Begin the minigame
+
+/mob/living/simple_animal/hostile/floor_cluwne/proc/after_target_wakes(mob/living/carbon/human/sac_target)
+ if(QDELETED(sac_target))
+ return
+
+ // About how long should the helgrasp last? (1 metab a tick = helgrasp_time / 2 ticks (so, 1 minute = 60 seconds = 30 ticks))
+ var/helgrasp_time = 1 MINUTES
+
+ sac_target.reagents?.add_reagent(/datum/reagent/helgrasp/heretic, helgrasp_time / 20)
+ sac_target.apply_necropolis_curse(CURSE_BLINDING | CURSE_GRASPING)
+
+ SEND_SIGNAL(sac_target, COMSIG_ADD_MOOD_EVENT, "shadow_realm", /datum/mood_event/shadow_realm)
+
+ sac_target.flash_act()
+ sac_target.blur_eyes(15)
+ sac_target.Jitter(10)
+ sac_target.Dizzy(10)
+ sac_target.hallucination += 12
+ sac_target.emote("scream")
+
+ to_chat(sac_target, "The grasping hands reveal themselves to you!")
+ to_chat(sac_target, "You feel invigorated! Fight to survive!")
+ // When it runs out, let them know they're almost home free
+ addtimer(CALLBACK(src, PROC_REF(after_helgrasp_ends), sac_target), helgrasp_time)
+ // Win condition
+ var/win_timer = addtimer(CALLBACK(src, PROC_REF(return_target), sac_target), SACRIFICE_REALM_DURATION, TIMER_STOPPABLE)
+ LAZYSET(return_timers, REF(sac_target), win_timer)
+
+/**
+ * This proc is called from [proc/after_target_wakes] after the helgrasp runs out in the [sac_target].
+ *
+ * It gives them a message letting them know it's getting easier and they're almost free.
+ */
+/mob/living/simple_animal/hostile/floor_cluwne/proc/after_helgrasp_ends(mob/living/carbon/human/sac_target)
+ if(QDELETED(sac_target) || sac_target.stat == DEAD)
+ return
+
+ to_chat(sac_target, "The worst is behind you... Not much longer! Hold fast, or expire!")
+
+/mob/living/simple_animal/hostile/floor_cluwne/proc/return_target(mob/living/carbon/human/sac_target)
+ if(QDELETED(sac_target))
+ return
+
+ var/current_timer = LAZYACCESS(return_timers, REF(sac_target))
+ if(current_timer)
+ deltimer(current_timer)
+ LAZYREMOVE(return_timers, REF(sac_target))
+
+ UnregisterSignal(sac_target, COMSIG_MOVABLE_Z_CHANGED)
+ UnregisterSignal(sac_target, COMSIG_MOB_DEATH)
+ sac_target.remove_status_effect(/datum/status_effect/necropolis_curse)
+ sac_target.remove_status_effect(/datum/status_effect/unholy_determination)
+ sac_target.reagents?.del_reagent(/datum/reagent/helgrasp/heretic)
+ SEND_SIGNAL(sac_target, COMSIG_CLEAR_MOOD_EVENT, "shadow_realm")
+
+ // Wherever we end up, we sure as hell won't be able to explain
+ sac_target.slurring += 20
+ sac_target.cultslurring += 20
+ sac_target.stuttering += 20
+
+ // They're already back on the station for some reason, don't bother teleporting
+ if(is_station_level(sac_target.z))
+ return
+
+ // Teleport them to a random safe coordinate on the station z level.
+ var/turf/open/floor/safe_turf = find_safe_turf(extended_safety_checks = TRUE)
+ var/obj/effect/landmark/observer_start/backup_loc = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
+ if(!safe_turf)
+ safe_turf = get_turf(backup_loc)
+ stack_trace("[type] - return_target was unable to find a safe turf for [sac_target] to return to. Defaulting to observer start turf.")
+
+ if(!do_teleport(sac_target, safe_turf, asoundout = 'sound/magic/blind.ogg', no_effects = TRUE, channel = TELEPORT_CHANNEL_FREE, forced = TRUE, no_wake = TRUE))
+ safe_turf = get_turf(backup_loc)
+ sac_target.forceMove(safe_turf)
+ stack_trace("[type] - return_target was unable to teleport [sac_target] to the observer start turf. Forcemoving.")
+
+ after_return_live_target(sac_target)
+
+
+/**
+ * This proc is called from [proc/return_target] if the [sac_target] survives the shadow realm.
+ *
+ * Gives the sacrifice target some after effects upon ariving back to reality.
+ */
+/mob/living/simple_animal/hostile/floor_cluwne/proc/after_return_live_target(mob/living/carbon/human/sac_target)
+ if(sac_target.stat == DEAD)
+ sac_target.revive(TRUE, TRUE)
+ sac_target.grab_ghost()
+ to_chat(sac_target, "The fight is over, but at great cost. You have been returned to the station in one piece.")
+ to_chat(sac_target, "You don't remember anything leading up to the experience - All you can think about are those horrific hands...")
+
+ // Oh god where are we?
+ sac_target.flash_act()
+ sac_target.Jitter(60)
+ sac_target.blur_eyes(50)
+ sac_target.Dizzy(30)
+ sac_target.AdjustKnockdown(80)
+ sac_target.adjustStaminaLoss(120)
+
+ // Glad i'm outta there, though!
+ SEND_SIGNAL(sac_target, COMSIG_ADD_MOOD_EVENT, "shadow_realm_survived", /datum/mood_event/shadow_realm_live)
+ SEND_SIGNAL(sac_target, COMSIG_ADD_MOOD_EVENT, "shadow_realm_survived_sadness", /datum/mood_event/shadow_realm_live_sad)
+
+ // Could use a little pick-me-up...
+ sac_target.reagents?.add_reagent(/datum/reagent/eldritchkiss, 12) //this used to kill toxinlovers, hence the snowflake reagent
+ terror_count += 1
+ if(terror_count == 2)
+ message_admins("Floor Cluwne was deleted due to reaching its max terror count")
+ qdel(src)
+
#undef STAGE_HAUNT
#undef STAGE_SPOOK
#undef STAGE_TORMENT