diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index 98d009ef143..d9ca290f684 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -37,7 +37,7 @@
#define SPECIAL_ROLE_NUKEOPS "Syndicate"
#define SPECIAL_ROLE_PYROCLASTIC_SLIME "Pyroclastic Anomaly Slime"
#define SPECIAL_ROLE_REVENANT "Revenant"
-#define SPECIAL_ROLE_SLAUGHTER_DEMON "Slaughter Demon"
+#define SPECIAL_ROLE_DEMON "Demon"
#define SPECIAL_ROLE_SUPER "Super"
#define SPECIAL_ROLE_SYNDICATE_DEATHSQUAD "Syndicate Commando"
#define SPECIAL_ROLE_TRAITOR "Traitor"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index bb4e7e4d30c..e7aae77bc2a 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -244,7 +244,8 @@
#define isnymph(A) (istype((A), /mob/living/simple_animal/diona))
#define ishostile(A) (istype((A), /mob/living/simple_animal/hostile))
#define isterrorspider(A) (istype((A), /mob/living/simple_animal/hostile/poison/terror_spider))
-#define isslaughterdemon(A) (istype((A), /mob/living/simple_animal/slaughter))
+#define isslaughterdemon(A) (istype((A), /mob/living/simple_animal/demon/slaughter))
+#define isdemon(A) (istype((A), /mob/living/simple_animal/demon))
#define issilicon(A) (istype((A), /mob/living/silicon))
#define isAI(A) (istype((A), /mob/living/silicon/ai))
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 89847fe1246..55d9dce7968 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -28,7 +28,7 @@
#define ROLE_TRADER "trader"
#define ROLE_VAMPIRE "vampire"
// Role tags for EVERYONE!
-#define ROLE_DEMON "slaughter demon"
+#define ROLE_DEMON "demon"
#define ROLE_SENTIENT "sentient animal"
#define ROLE_POSIBRAIN "positronic brain"
#define ROLE_GUARDIAN "guardian"
diff --git a/code/datums/spell_targeting/aoe.dm b/code/datums/spell_targeting/aoe.dm
index e3369de737d..306a59798d1 100644
--- a/code/datums/spell_targeting/aoe.dm
+++ b/code/datums/spell_targeting/aoe.dm
@@ -8,8 +8,9 @@
/datum/spell_targeting/aoe/choose_targets(mob/user, obj/effect/proc_holder/spell/spell, params, atom/clicked_atom)
var/list/targets = list()
+ var/spell_center = use_turf_of_user ? get_turf(user) : user
- for(var/atom/target in view_or_range(range, user, selection_type))
+ for(var/atom/target in view_or_range(range, spell_center, selection_type))
if(valid_target(target, user, spell, FALSE))
targets += target
if(inner_radius >= 0)
diff --git a/code/datums/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm
index d8299e43ac2..6c3b770e817 100644
--- a/code/datums/spells/bloodcrawl.dm
+++ b/code/datums/spells/bloodcrawl.dm
@@ -9,12 +9,13 @@
action_icon_state = "bloodcrawl"
action_background_icon_state = "bg_demon"
panel = "Demon"
+ var/allowed_type = /obj/effect/decal/cleanable
var/phased = FALSE
/obj/effect/proc_holder/spell/bloodcrawl/create_new_targeting()
var/datum/spell_targeting/targeted/T = new()
T.selection_type = SPELL_SELECTION_RANGE
- T.allowed_type = /obj/effect/decal/cleanable
+ T.allowed_type = allowed_type
T.random_target = TRUE
T.range = 1
T.use_turf_of_user = TRUE
@@ -31,11 +32,241 @@
return FALSE
/obj/effect/proc_holder/spell/bloodcrawl/cast(list/targets, mob/living/user)
- var/obj/effect/decal/cleanable/target = targets[1] // TODO Test this spell
+ var/atom/target = targets[1]
if(phased)
- if(user.phasein(target))
+ if(phasein(target, user))
phased = FALSE
else
- if(user.phaseout(target))
+ if(phaseout(target, user))
phased = TRUE
cooldown_handler.start_recharge()
+
+//Travel through pools of blood. Slaughter Demon powers for everyone!
+#define BLOODCRAWL 1
+#define BLOODCRAWL_EAT 2
+
+
+/obj/item/bloodcrawl
+ name = "blood crawl"
+ desc = "You are unable to hold anything while in this form."
+ icon = 'icons/effects/blood.dmi'
+ flags = NODROP|ABSTRACT
+
+/obj/effect/dummy/slaughter //Can't use the wizard one, blocked by jaunt/slow
+ name = "odd blood"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "nothing"
+ density = FALSE
+ anchored = TRUE
+ invisibility = 60
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/obj/effect/dummy/slaughter/relaymove(mob/user, direction)
+ forceMove(get_step(src, direction))
+
+/obj/effect/dummy/slaughter/ex_act()
+ return
+
+/obj/effect/dummy/slaughter/bullet_act()
+ return
+
+/obj/effect/dummy/slaughter/singularity_act()
+ return
+
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/block_hands(mob/living/carbon/C)
+ if(C.l_hand || C.r_hand)
+ to_chat(C, "You may not hold items while blood crawling!")
+ return FALSE
+ var/obj/item/bloodcrawl/B1 = new(C)
+ var/obj/item/bloodcrawl/B2 = new(C)
+ B1.icon_state = "bloodhand_left"
+ B2.icon_state = "bloodhand_right"
+ C.put_in_hands(B1)
+ C.put_in_hands(B2)
+ C.regenerate_icons()
+ return TRUE
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/sink_animation(atom/A, mob/living/L)
+ var/turf/mob_loc = get_turf(L)
+ visible_message("[L] sinks into [A].")
+ playsound(mob_loc, 'sound/misc/enter_blood.ogg', 100, 1, -1)
+ var/atom/movable/overlay/animation = new(mob_loc)
+ animation.name = "odd blood"
+ animation.density = FALSE
+ animation.anchored = TRUE
+ animation.icon = 'icons/mob/mob.dmi'
+ animation.icon_state = "jaunt"
+ animation.layer = 5
+ animation.master = mob_loc
+ animation.dir = L.dir
+ QDEL_IN(animation, 0.6 SECONDS)
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/handle_consumption(mob/living/L, mob/living/victim, atom/A, obj/effect/dummy/slaughter/holder)
+ if(!HAS_TRAIT(L, TRAIT_BLOODCRAWL_EAT))
+ return
+
+ if(!istype(victim))
+ return
+ if(victim.stat == CONSCIOUS)
+ A.visible_message("[victim] kicks free of [A] just before entering it!")
+ L.stop_pulling()
+ return
+
+ victim.forceMove(holder)
+ victim.emote("scream")
+ A.visible_message("[L] drags [victim] into [A]!")
+ L.stop_pulling()
+ to_chat(L, "You begin to feast on [victim]. You can not move while you are doing this.")
+ A.visible_message("Loud eating sounds come from the blood...")
+ var/sound
+ if(isslaughterdemon(L))
+ var/mob/living/simple_animal/demon/slaughter/SD = L
+ sound = SD.feast_sound
+ else
+ sound = 'sound/misc/demon_consume.ogg'
+
+ for(var/i in 1 to 3)
+ playsound(get_turf(L), sound, 100, 1)
+ sleep(3 SECONDS)
+
+ if(!victim)
+ to_chat(L, "You happily devour... nothing? Your meal vanished at some point!")
+ return
+
+ if(ishuman(victim) || isrobot(victim))
+ to_chat(L, "You devour [victim]. Your health is fully restored.")
+ L.adjustBruteLoss(-1000)
+ L.adjustFireLoss(-1000)
+ L.adjustOxyLoss(-1000)
+ L.adjustToxLoss(-1000)
+ else
+ to_chat(L, "You devour [victim], but this measly meal barely sates your appetite!")
+ L.adjustBruteLoss(-25)
+ L.adjustFireLoss(-25)
+
+ if(isslaughterdemon(L))
+ var/mob/living/simple_animal/demon/slaughter/demon = L
+ demon.devoured++
+ to_chat(victim, "You feel teeth sink into your flesh, and the--")
+ victim.adjustBruteLoss(1000)
+ victim.forceMove(demon)
+ demon.consumed_mobs.Add(victim)
+ if(ishuman(victim))
+ var/mob/living/carbon/human/H = victim
+ if(H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/U = H.w_uniform
+ U.sensor_mode = SENSOR_OFF
+ else
+ victim.ghostize()
+ qdel(victim)
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/post_phase_in(mob/living/L, obj/effect/dummy/slaughter/holder)
+ L.notransform = FALSE
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/phaseout(obj/effect/decal/cleanable/B, mob/living/L)
+
+ if(iscarbon(L) && !block_hands(L))
+ return FALSE
+
+ L.notransform = TRUE
+ INVOKE_ASYNC(src, PROC_REF(async_phase), B, L)
+ return TRUE
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/async_phase(obj/effect/decal/cleanable/B, mob/living/L)
+ var/turf/mobloc = get_turf(L)
+ sink_animation(B, L)
+ var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(mobloc)
+ L.forceMove(holder)
+ L.ExtinguishMob()
+ handle_consumption(L, L.pulling, B, holder)
+ post_phase_in(L, holder)
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_animation(turf/tele_loc, mob/living/L, atom/A)
+ var/atom/movable/overlay/animation = new(tele_loc)
+ animation.name = "odd blood"
+ animation.density = FALSE
+ animation.anchored = TRUE
+ animation.icon = 'icons/mob/mob.dmi'
+ animation.icon_state = "jauntup" //Paradise Port:I reversed the jaunt animation so it looks like its rising up
+ animation.layer = 5
+ animation.master = tele_loc
+ animation.dir = L.dir
+ if(prob(25) && isdemon(L))
+ var/list/voice = list('sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/i_see_you1.ogg')
+ playsound(tele_loc, pick(voice),50, 1, -1)
+ A.visible_message("[L] rises out of [A]!")
+ playsound(get_turf(tele_loc), 'sound/misc/exit_blood.ogg', 100, 1, -1)
+ QDEL_IN(animation, 0.6 SECONDS)
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/unblock_hands(mob/living/carbon/C)
+ if(!istype(C))
+ return
+ for(var/obj/item/bloodcrawl/BC in C)
+ qdel(BC)
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_message(atom/A)
+ A.visible_message("[A] starts to bubble...")
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/post_phase_out(atom/A, mob/living/L)
+ if(isslaughterdemon(L))
+ var/mob/living/simple_animal/demon/slaughter/S = L
+ S.speed = 0
+ S.boost = world.time + 6 SECONDS
+ L.color = A.color
+ addtimer(VARSET_CALLBACK(L, color, null), 6 SECONDS)
+
+
+/obj/effect/proc_holder/spell/bloodcrawl/proc/phasein(atom/A, mob/living/L)
+
+ if(L.notransform)
+ to_chat(L, "Finish eating first!")
+ return FALSE
+ rise_message(A)
+ if(!do_after(L, 2 SECONDS, target = A))
+ return FALSE
+ if(!A)
+ return FALSE
+ var/turf/tele_loc = isturf(A) ? A : A.loc
+ var/holder = L.loc
+ L.forceMove(tele_loc)
+ L.client.eye = L
+
+ rise_animation(tele_loc, L, A)
+
+ unblock_hands(L)
+
+ QDEL_NULL(holder)
+
+ post_phase_out(A, L)
+ return TRUE
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl
+ name = "Shadow Crawl"
+ desc = "Use darkness to phase out of existence."
+ allowed_type = /turf
+ action_background_icon_state = "shadow_demon_bg"
+ action_icon_state = "shadow_crawl"
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/valid_target(turf/target, user)
+ return target.get_lumcount() < 0.2
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_message(atom/A)
+ return
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_animation(turf/tele_loc, mob/living/L, atom/A)
+ return
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/handle_consumption(mob/living/L, mob/living/victim, atom/A, obj/effect/dummy/slaughter/holder)
+ return
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/sink_animation(atom/A, mob/living/L)
+ A.visible_message("[L] sinks into the shadows...")
+
+/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/post_phase_in(mob/living/L, obj/effect/dummy/slaughter/holder)
+ ..()
+ if(!istype(L, /mob/living/simple_animal/demon/shadow))
+ return
+ var/mob/living/simple_animal/demon/shadow/S = L
+ S.RegisterSignal(holder, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness))
+
diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm
index 449a109f315..26c08709a2d 100644
--- a/code/game/gamemodes/cult/cult_actions.dm
+++ b/code/game/gamemodes/cult/cult_actions.dm
@@ -47,7 +47,7 @@
var/title
var/large = FALSE
var/living_message
- if(istype(user, /mob/living/simple_animal/slaughter/cult)) //Harbringers of the Slaughter
+ if(istype(user, /mob/living/simple_animal/demon/slaughter/cult)) //Harbringers of the Slaughter
title = "Harbringer of the Slaughter"
large = TRUE
else
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 2b1487db3ed..22eeda637d8 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -303,7 +303,7 @@
if(curselimit > 1)
to_chat(user, "We have exhausted our ability to curse the shuttle.")
return
- if(locate(/obj/singularity/narsie) in GLOB.poi_list || locate(/mob/living/simple_animal/slaughter/cult) in GLOB.mob_list)
+ if(locate(/obj/singularity/narsie) in GLOB.poi_list || locate(/mob/living/simple_animal/demon/slaughter/cult) in GLOB.mob_list)
to_chat(user, "Nar'Sie or her avatars are already on this plane, there is no delaying the end of all things.")
return
diff --git a/code/game/gamemodes/miniantags/demons/demon.dm b/code/game/gamemodes/miniantags/demons/demon.dm
new file mode 100644
index 00000000000..e75851d9c4a
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/demon.dm
@@ -0,0 +1,36 @@
+/mob/living/simple_animal/demon
+ name = "a generic demon"
+ desc = "you shouldnt be reading this, file a github report"
+ speak_emote = list("gurgles")
+ emote_hear = list("wails","screeches")
+ response_help = "thinks better of touching"
+ response_disarm = "flails at"
+ response_harm = "punches"
+ speed = 1
+ a_intent = INTENT_HARM
+ mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
+ stop_automated_movement = TRUE
+ status_flags = CANPUSH
+ attack_sound = 'sound/misc/demon_attack1.ogg'
+ death_sound = 'sound/misc/demon_dies.ogg'
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ maxbodytemp = INFINITY
+ faction = list("demon")
+ attacktext = "wildly tears into"
+ maxHealth = 200
+ health = 200
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ obj_damage = 50
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ see_in_dark = 8
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ del_on_death = TRUE
+ var/datum/action/innate/demon/whisper/whisper_action
+
+/mob/living/simple_animal/demon/Initialize(mapload)
+ . = ..()
+ whisper_action = new()
+ whisper_action.Grant(src)
+
diff --git a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
new file mode 100644
index 00000000000..34869d46d84
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
@@ -0,0 +1,122 @@
+/mob/living/simple_animal/demon/shadow
+ name = "shadow demon"
+ desc = "A creature that's barely tangible, you can feel its gaze piercing you"
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "shadow_demon"
+ icon_living = "shadow_demon"
+ move_resist = MOVE_FORCE_STRONG
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE // so they can tell where the darkness is
+ loot = list(/obj/item/organ/internal/heart/demon/shadow)
+ var/thrown_alert = FALSE
+
+/mob/living/simple_animal/demon/shadow/Life(seconds, times_fired)
+ . = ..()
+ var/lum_count = check_darkness()
+ var/damage_mod = istype(loc, /obj/effect/dummy/slaughter) ? 0.5 : 1
+ if(lum_count > 0.2)
+ adjustBruteLoss(40 * damage_mod) // 10 seconds in light
+ SEND_SOUND(src, sound('sound/weapons/sear.ogg'))
+ to_chat(src, "The light scalds you!")
+ else
+ adjustBruteLoss(-20)
+
+
+/mob/living/simple_animal/demon/shadow/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ if(isliving(AM)) // when a living creature is thrown at it, dont knock it back
+ return
+ ..()
+
+
+/mob/living/simple_animal/demon/shadow/Initialize(mapload)
+ . = ..()
+ AddSpell(new /obj/effect/proc_holder/spell/fireball/shadow_grapple)
+ var/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/S = new
+ AddSpell(S)
+ if(istype(loc, /obj/effect/dummy/slaughter))
+ S.phased = TRUE
+ RegisterSignal(loc, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness))
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(check_darkness))
+
+/mob/living/simple_animal/demon/shadow/proc/check_darkness()
+ var/turf/T = get_turf(src)
+ var/lum_count = T.get_lumcount()
+ if(lum_count > 0.2)
+ if(!thrown_alert)
+ thrown_alert = TRUE
+ throw_alert("light", /obj/screen/alert/lightexposure)
+ alpha = 255
+ else
+ if(thrown_alert)
+ thrown_alert = FALSE
+ clear_alert("light")
+ alpha = 125
+ return lum_count
+
+
+/obj/effect/proc_holder/spell/fireball/shadow_grapple
+ name = "Shadow Grapple"
+ desc = "Fire one of your hands, if it hits a person it pulls them in. If you hit a structure you get pulled to the structure."
+ base_cooldown = 10 SECONDS
+ fireball_type = /obj/item/projectile/magic/shadow_hand
+
+ selection_activated_message = "You raise your hand, full of demonic energy! Left-click to cast at a target!"
+ selection_deactivated_message = "You re-absorb the energy...for now."
+
+ action_background_icon_state = "shadow_demon_bg"
+ action_icon_state = "shadow_grapple"
+ panel = "Demon"
+
+ sound = null
+ invocation_type = "none"
+ invocation = null
+
+/obj/effect/proc_holder/spell/fireball/shadow_grapple/update_icon_state()
+ return
+
+/obj/item/projectile/magic/shadow_hand
+ name = "shadow hand"
+ icon_state = "shadow_hand"
+ plane = FLOOR_PLANE
+ var/hit = FALSE
+
+/obj/item/projectile/magic/shadow_hand/fire(setAngle)
+ if(firer)
+ firer.Beam(src, icon_state = "grabber_beam", time = INFINITY, maxdistance = INFINITY, beam_sleep_time = 1, beam_type = /obj/effect/ebeam/floor)
+ return ..()
+
+/obj/item/projectile/magic/shadow_hand/on_hit(atom/target, blocked, hit_zone)
+ if(hit)
+ return
+ hit = TRUE // to prevent double hits from the pull
+ . = ..()
+ if(!isliving(target))
+ firer.throw_at(get_step(target, get_dir(target, firer)), 50, 10)
+ else
+ var/mob/living/L = target
+ L.Immobilize(2 SECONDS)
+ L.apply_damage(40, BRUTE, BODY_ZONE_CHEST)
+ L.throw_at(get_step(firer, get_dir(firer, target)), 50, 10)
+ target.extinguish_light(TRUE)
+
+/obj/effect/ebeam/floor
+ plane = FLOOR_PLANE
+
+/obj/item/organ/internal/heart/demon/shadow
+ name = "heart of darkness"
+ desc = "It still beats furiously, emitting an aura of fear."
+ color = COLOR_BLACK
+
+/obj/item/organ/internal/heart/demon/shadow/attack_self(mob/living/user)
+ . = ..()
+ user.drop_item()
+ insert(user)
+
+/obj/item/organ/internal/heart/demon/shadow/insert(mob/living/carbon/M, special = 0)
+ . = ..()
+ if(M.mind)
+ M.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball/shadow_grapple)
+
+/obj/item/organ/internal/heart/demon/shadow/remove(mob/living/carbon/M, special = 0)
+ ..()
+ if(M.mind)
+ M.mind.RemoveSpell(/obj/effect/proc_holder/spell/fireball/shadow_grapple)
diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm
similarity index 86%
rename from code/game/gamemodes/miniantags/slaughter/slaughter.dm
rename to code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm
index 019cc987c05..6f00c71c91d 100644
--- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm
+++ b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm
@@ -1,42 +1,15 @@
//////////////////The Monster
-/mob/living/simple_animal/slaughter
+/mob/living/simple_animal/demon/slaughter
name = "slaughter demon"
real_name = "slaughter demon"
desc = "A large, menacing creature covered in armored black scales. You should run."
speak = list("ire", "ego", "nahlizet", "certum", "veri", "jatkaa", "balaq", "mgar", "karazet", "geeri", "orkan", "allaq")
- speak_emote = list("gurgles")
- emote_hear = list("wails","screeches")
- response_help = "thinks better of touching"
- response_disarm = "flails at"
- response_harm = "punches"
icon = 'icons/mob/mob.dmi'
icon_state = "daemon"
icon_living = "daemon"
- speed = 1
- a_intent = INTENT_HARM
- mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
- stop_automated_movement = TRUE
- status_flags = CANPUSH
- attack_sound = 'sound/misc/demon_attack1.ogg'
- var/feast_sound = 'sound/misc/demon_consume.ogg'
- death_sound = 'sound/misc/demon_dies.ogg'
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- maxbodytemp = INFINITY
- faction = list("slaughter")
- attacktext = "wildly tears into"
- maxHealth = 200
- health = 200
- environment_smash = 1
- obj_damage = 50
- melee_damage_lower = 30
- melee_damage_upper = 30
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
var/boost = 0
-
-
+ var/feast_sound = 'sound/misc/demon_consume.ogg'
var/devoured = 0
var/list/consumed_mobs = list()
@@ -44,7 +17,7 @@
var/cooldown = 0
var/gorecooldown = 0
var/vialspawned = FALSE
- loot = list(/obj/effect/decal/cleanable/blood/innards, /obj/effect/decal/cleanable/blood, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic, /obj/item/organ/internal/heart/demon)
+ loot = list(/obj/effect/decal/cleanable/blood/innards, /obj/effect/decal/cleanable/blood, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic, /obj/item/organ/internal/heart/demon/slaughter)
var/playstyle_string = "You are the Slaughter Demon, a terrible creature from another existence. You have a single desire: to kill. \
You may use the blood crawl icon when on blood pools to travel through them, appearing and dissapearing from the station at will. \
Pulling a dead or critical mob while you enter a pool will pull them in with you, allowing you to feast. \
@@ -52,30 +25,26 @@
del_on_death = TRUE
deathmessage = "screams in anger as it collapses into a puddle of viscera!"
- var/datum/action/innate/demon/whisper/whisper_action
-
-/mob/living/simple_animal/slaughter/New()
+/mob/living/simple_animal/demon/slaughter/New()
..()
remove_from_all_data_huds()
ADD_TRAIT(src, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat")
var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new
AddSpell(bloodspell)
- whisper_action = new()
- whisper_action.Grant(src)
if(istype(loc, /obj/effect/dummy/slaughter))
bloodspell.phased = TRUE
addtimer(CALLBACK(src, PROC_REF(attempt_objectives)), 5 SECONDS)
-/mob/living/simple_animal/slaughter/Life(seconds, times_fired)
+/mob/living/simple_animal/demon/slaughter/Life(seconds, times_fired)
..()
if(boost < world.time)
speed = 1
else
speed = 0
-/mob/living/simple_animal/slaughter/proc/attempt_objectives()
+/mob/living/simple_animal/demon/slaughter/proc/attempt_objectives()
if(mind)
to_chat(src, src.playstyle_string)
to_chat(src, "You are not currently in the same plane of existence as the station. Use the blood crawl action at a blood pool to manifest.")
@@ -100,28 +69,24 @@
name = "pile of viscera"
desc = "A repulsive pile of guts and gore."
-/mob/living/simple_animal/slaughter/Destroy()
+/mob/living/simple_animal/demon/slaughter/Destroy()
// Only execute the below if we successfully died
for(var/mob/living/M in consumed_mobs)
release_consumed(M)
. = ..()
-/mob/living/simple_animal/slaughter/proc/release_consumed(mob/living/M)
+/mob/living/simple_animal/demon/slaughter/proc/release_consumed(mob/living/M)
M.forceMove(get_turf(src))
-/mob/living/simple_animal/slaughter/phasein()
- . = ..()
- speed = 0
- boost = world.time + 60
// Midround slaughter demon, less tanky
-/mob/living/simple_animal/slaughter/lesser
+/mob/living/simple_animal/demon/slaughter/lesser
maxHealth = 130
health = 130
// Cult slaughter demon
-/mob/living/simple_animal/slaughter/cult //Summoned as part of the cult objective "Bring the Slaughter"
+/mob/living/simple_animal/demon/slaughter/cult //Summoned as part of the cult objective "Bring the Slaughter"
name = "harbinger of the slaughter"
real_name = "harbinger of the Slaughter"
desc = "An awful creature from beyond the realms of madness."
@@ -163,10 +128,10 @@
return 0
to_chat(user, "You sense a terrified soul at [A]. Show [A.p_them()] the error of [A.p_their()] ways.")
-/mob/living/simple_animal/slaughter/cult/New()
+/mob/living/simple_animal/demon/slaughter/cult/New()
..()
spawn(5)
- var/list/demon_candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, 10 SECONDS, source = /mob/living/simple_animal/slaughter/cult)
+ var/list/demon_candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, 10 SECONDS, source = /mob/living/simple_animal/demon/slaughter/cult)
if(!demon_candidates.len)
visible_message("[src] disappears in a flash of red light!")
qdel(src)
@@ -174,7 +139,7 @@
if(QDELETED(src)) // Just in case
return
var/mob/M = pick(demon_candidates)
- var/mob/living/simple_animal/slaughter/cult/S = src
+ var/mob/living/simple_animal/demon/slaughter/cult/S = src
if(!M || !M.client)
visible_message("[src] disappears in a flash of red light!")
qdel(src)
@@ -240,7 +205,7 @@
//////////The Loot
-//The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl
+// Demon heart base type
/obj/item/organ/internal/heart/demon
name = "demon heart"
desc = "Still it beats furiously, emanating an aura of utter hate."
@@ -254,11 +219,22 @@
/obj/item/organ/internal/heart/demon/prepare_eat()
return // Just so people don't accidentally waste it
+/obj/item/organ/internal/heart/demon/Stop()
+ return 0 // Always beating.
+
/obj/item/organ/internal/heart/demon/attack_self(mob/living/user)
user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \
"An unnatural hunger consumes you. You raise [src] to your mouth and devour it!")
playsound(user, 'sound/misc/demon_consume.ogg', 50, 1)
+//////////The Loot
+
+//The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl
+/// SLAUGHTER DEMON HEART
+
+/obj/item/organ/internal/heart/demon/slaughter/attack_self(mob/living/user)
+ ..()
+
// Eating the heart for the first time. Gives basic bloodcrawling. This is the only time we need to insert the heart.
if(!HAS_TRAIT(user, TRAIT_BLOODCRAWL))
user.visible_message("[user]'s eyes flare a deep crimson!", \
@@ -279,23 +255,19 @@
to_chat(user, "...and you don't feel any different.")
qdel(src)
-/obj/item/organ/internal/heart/demon/insert(mob/living/carbon/M, special = 0)
+/obj/item/organ/internal/heart/demon/slaughter/insert(mob/living/carbon/M, special = 0)
. = ..()
if(M.mind)
M.mind.AddSpell(new /obj/effect/proc_holder/spell/bloodcrawl(null))
-/obj/item/organ/internal/heart/demon/remove(mob/living/carbon/M, special = 0)
+/obj/item/organ/internal/heart/demon/slaughter/remove(mob/living/carbon/M, special = 0)
..()
if(M.mind)
REMOVE_TRAIT(M, TRAIT_BLOODCRAWL, "bloodcrawl")
REMOVE_TRAIT(M, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat")
M.mind.RemoveSpell(/obj/effect/proc_holder/spell/bloodcrawl)
-/obj/item/organ/internal/heart/demon/Stop()
- return 0 // Always beating.
-
-
-/mob/living/simple_animal/slaughter/laughter
+/mob/living/simple_animal/demon/slaughter/laughter
// The laughter demon! It's everyone's best friend! It just wants to hug
// them so much, it wants to hug everyone at once!
name = "laughter demon"
@@ -324,7 +296,7 @@
deathmessage = "fades out, as all of its friends are released from its prison of hugs."
loot = list(/mob/living/simple_animal/pet/cat/kitten{name = "Laughter"})
-/mob/living/simple_animal/slaughter/laughter/release_consumed(mob/living/M)
+/mob/living/simple_animal/demon/slaughter/laughter/release_consumed(mob/living/M)
if(M.revive())
M.grab_ghost(force = TRUE)
playsound(get_turf(src), feast_sound, 50, 1, -1)
@@ -348,7 +320,7 @@
for(var/datum/mind/M in get_owners())
if(!isslaughterdemon(M.current) || QDELETED(M.current))
continue
- var/mob/living/simple_animal/slaughter/R = M.current
+ var/mob/living/simple_animal/demon/slaughter/R = M.current
kill_count += R.devoured
if(kill_count >= targetKill)
return TRUE
diff --git a/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm b/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm
deleted file mode 100644
index 988ff1f4595..00000000000
--- a/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm
+++ /dev/null
@@ -1,180 +0,0 @@
-//Travel through pools of blood. Slaughter Demon powers for everyone!
-#define BLOODCRAWL 1
-#define BLOODCRAWL_EAT 2
-
-/mob/living/proc/phaseout(obj/effect/decal/cleanable/B)
-
- if(iscarbon(src))
- var/mob/living/carbon/C = src
- if(C.l_hand || C.r_hand)
- to_chat(C, "You may not hold items while blood crawling!")
- return 0
- var/obj/item/bloodcrawl/B1 = new(C)
- var/obj/item/bloodcrawl/B2 = new(C)
- B1.icon_state = "bloodhand_left"
- B2.icon_state = "bloodhand_right"
- C.put_in_hands(B1)
- C.put_in_hands(B2)
- C.regenerate_icons()
-
- var/mob/living/kidnapped = null
- var/turf/mobloc = get_turf(loc)
- notransform = TRUE
- spawn(0)
- visible_message("[src] sinks into [B].")
- playsound(get_turf(src), 'sound/misc/enter_blood.ogg', 100, 1, -1)
- var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(mobloc)
- var/atom/movable/overlay/animation = new /atom/movable/overlay(mobloc)
- animation.name = "odd blood"
- animation.density = FALSE
- animation.anchored = TRUE
- animation.icon = 'icons/mob/mob.dmi'
- animation.icon_state = "jaunt"
- animation.layer = 5
- animation.master = holder
- animation.dir = dir
-
- ExtinguishMob()
- if(pulling && HAS_TRAIT(src, TRAIT_BLOODCRAWL_EAT))
- if(isliving(pulling))
- var/mob/living/victim = pulling
- if(victim.stat == CONSCIOUS)
- visible_message("[victim] kicks free of [B] just before entering it!")
- stop_pulling()
- else
- victim.forceMove(holder)//holder
- victim.emote("scream")
- visible_message("[src] drags [victim] into [B]!")
- kidnapped = victim
- stop_pulling()
- flick("jaunt",animation)
-
- src.holder = holder
- forceMove(holder)
-
- if(kidnapped)
- to_chat(src, "You begin to feast on [kidnapped]. You can not move while you are doing this.")
- visible_message("Loud eating sounds come from the blood...")
- sleep(6)
- if(animation)
- qdel(animation)
- var/sound
- if(isslaughterdemon(src))
- var/mob/living/simple_animal/slaughter/SD = src
- sound = SD.feast_sound
- else
- sound = 'sound/misc/demon_consume.ogg'
-
- for(var/i in 1 to 3)
- playsound(get_turf(src), sound, 100, 1)
- sleep(30)
-
- if(kidnapped)
- if(ishuman(kidnapped) || isrobot(kidnapped))
- to_chat(src, "You devour [kidnapped]. Your health is fully restored.")
- adjustBruteLoss(-1000)
- adjustFireLoss(-1000)
- adjustOxyLoss(-1000)
- adjustToxLoss(-1000)
- else
- to_chat(src, "You devour [kidnapped], but this measly meal barely sates your appetite!")
- adjustBruteLoss(-25)
- adjustFireLoss(-25)
- if(istype(src, /mob/living/simple_animal/slaughter)) //rason, do not want humans to get this
- var/mob/living/simple_animal/slaughter/demon = src
- demon.devoured++
- to_chat(kidnapped, "You feel teeth sink into your flesh, and the--")
- kidnapped.adjustBruteLoss(1000)
- kidnapped.forceMove(src)
- demon.consumed_mobs.Add(kidnapped)
- if(ishuman(kidnapped))
- var/mob/living/carbon/human/H = kidnapped
- if(H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under))
- var/obj/item/clothing/under/U = H.w_uniform
- U.sensor_mode = SENSOR_OFF
- else
- kidnapped.ghostize()
- qdel(kidnapped)
- else
- to_chat(src, "You happily devour... nothing? Your meal vanished at some point!")
- else
- sleep(6)
- if(animation)
- qdel(animation)
- notransform = FALSE
- return 1
-
-/obj/item/bloodcrawl
- name = "blood crawl"
- desc = "You are unable to hold anything while in this form."
- icon = 'icons/effects/blood.dmi'
- flags = NODROP|ABSTRACT
-
-/mob/living/proc/phasein(obj/effect/decal/cleanable/B)
-
- if(notransform)
- to_chat(src, "Finish eating first!")
- return 0
- B.visible_message("[B] starts to bubble...")
- if(!do_after(src, 20, target = B))
- return
- if(!B)
- return
- forceMove(B.loc)
- client.eye = src
-
- var/atom/movable/overlay/animation = new /atom/movable/overlay( B.loc )
- animation.name = "odd blood"
- animation.density = FALSE
- animation.anchored = TRUE
- animation.icon = 'icons/mob/mob.dmi'
- animation.icon_state = "jauntup" //Paradise Port:I reversed the jaunt animation so it looks like its rising up
- animation.layer = 5
- animation.master = B.loc
- animation.dir = dir
-
- if(prob(25) && isslaughterdemon(src))
- var/list/voice = list('sound/hallucinations/behind_you1.ogg','sound/hallucinations/im_here1.ogg','sound/hallucinations/turn_around1.ogg','sound/hallucinations/i_see_you1.ogg')
- playsound(get_turf(src), pick(voice),50, 1, -1)
- visible_message("\The [src] rises out of \the [B]!")
- playsound(get_turf(src), 'sound/misc/exit_blood.ogg', 100, 1, -1)
-
- flick("jauntup",animation)
- QDEL_NULL(holder)
-
- if(iscarbon(src))
- var/mob/living/carbon/C = src
- for(var/obj/item/bloodcrawl/BC in C)
- C.flags = null
- C.unEquip(BC)
- qdel(BC)
-
- var/oldcolor = color
- color = B.color
- sleep(6)//wait for animation to finish
- if(animation)
- qdel(animation)
- spawn(30)
- color = oldcolor
- return 1
-
-/obj/effect/dummy/slaughter //Can't use the wizard one, blocked by jaunt/slow
- name = "odd blood"
- icon = 'icons/effects/effects.dmi'
- icon_state = "nothing"
- density = FALSE
- anchored = TRUE
- invisibility = 60
- resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-
-/obj/effect/dummy/slaughter/relaymove(mob/user, direction)
- forceMove(get_step(src,direction))
-
-/obj/effect/dummy/slaughter/ex_act()
- return
-
-/obj/effect/dummy/slaughter/bullet_act()
- return
-
-/obj/effect/dummy/slaughter/singularity_act()
- return
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index df331cda39c..e4ecff59ca6 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -30,7 +30,7 @@
return FALSE
return TRUE
-/obj/machinery/computer/extinguish_light()
+/obj/machinery/computer/extinguish_light(force = FALSE)
set_light(0)
underlays.Cut()
visible_message("[src] grows dim, its screen barely readable.")
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index dd349be81fe..68c16ce8be7 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -97,7 +97,7 @@
else
set_light(1, LIGHTING_MINIMUM_POWER)
-/obj/machinery/door/firedoor/extinguish_light()
+/obj/machinery/door/firedoor/extinguish_light(force = FALSE)
set_light(0)
update_icon(UPDATE_OVERLAYS)
diff --git a/code/game/machinery/dye_generator.dm b/code/game/machinery/dye_generator.dm
index fb2551c3e45..8a0def9bb5b 100644
--- a/code/game/machinery/dye_generator.dm
+++ b/code/game/machinery/dye_generator.dm
@@ -26,7 +26,7 @@
set_light(0)
update_icon(UPDATE_OVERLAYS)
-/obj/machinery/dye_generator/extinguish_light()
+/obj/machinery/dye_generator/extinguish_light(force = FALSE)
set_light(0)
underlays.Cut()
diff --git a/code/game/machinery/floodlight.dm b/code/game/machinery/floodlight.dm
index 635608a0fe9..b780768ad90 100644
--- a/code/game/machinery/floodlight.dm
+++ b/code/game/machinery/floodlight.dm
@@ -145,7 +145,7 @@
update_icon(UPDATE_ICON_STATE)
return TRUE
-/obj/machinery/floodlight/extinguish_light()
+/obj/machinery/floodlight/extinguish_light(force = FALSE)
on = FALSE
set_light(0)
update_icon(UPDATE_ICON_STATE)
diff --git a/code/game/machinery/vendors/vending.dm b/code/game/machinery/vendors/vending.dm
index 7470e1d1373..f3e354b37f7 100644
--- a/code/game/machinery/vendors/vending.dm
+++ b/code/game/machinery/vendors/vending.dm
@@ -785,7 +785,7 @@
if(shoot_inventory && prob(shoot_chance))
throw_item()
-/obj/machinery/economy/vending/extinguish_light()
+/obj/machinery/economy/vending/extinguish_light(force = FALSE)
set_light(0)
underlays.Cut()
diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm
index 27e791024d4..562c7390b6c 100644
--- a/code/game/objects/effects/glowshroom.dm
+++ b/code/game/objects/effects/glowshroom.dm
@@ -13,7 +13,7 @@
var/floor = 0
var/obj/item/seeds/myseed = /obj/item/seeds/glowshroom
-/obj/structure/glowshroom/extinguish_light()
+/obj/structure/glowshroom/extinguish_light(force = FALSE)
visible_message("[src] withers away!")
qdel(src)
@@ -29,7 +29,7 @@
icon_state = "shadowshroom"
myseed = /obj/item/seeds/glowshroom/shadowshroom
-/obj/structure/glowshroom/shadowshroom/extinguish_light()
+/obj/structure/glowshroom/shadowshroom/extinguish_light(force = FALSE)
return
/obj/structure/glowshroom/Destroy()
diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm
index c3d4c366d0e..dda80c6fef4 100644
--- a/code/game/objects/items/candle.dm
+++ b/code/game/objects/items/candle.dm
@@ -141,6 +141,13 @@
if(lit)
set_light(CANDLE_LUM * 2)
+
+/obj/item/candle/extinguish_light(force)
+ if(!force)
+ return
+ infinite = FALSE
+ wax = 1 // next process will burn it out
+
#undef TALL_CANDLE
#undef MID_CANDLE
#undef SHORT_CANDLE
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 21eca98f149..ef4561b5720 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -82,7 +82,7 @@
else
return ..()
-/obj/item/flashlight/extinguish_light()
+/obj/item/flashlight/extinguish_light(force = FALSE)
if(on)
on = FALSE
update_brightness()
@@ -283,8 +283,12 @@
new T(loc)
qdel(src) // return INITIALIZE_HINT_QDEL <-- Doesn't work
-/obj/item/flashlight/flare/extinguish_light()
- visible_message("[src] dims slightly before scattering the shadows around it.")
+/obj/item/flashlight/flare/extinguish_light(force = FALSE)
+ if(force)
+ fuel = 0
+ visible_message("[src] burns up rapidly!")
+ else
+ visible_message("[src] dims slightly before scattering the shadows around it.")
/obj/item/flashlight/flare/torch
name = "torch"
@@ -321,8 +325,12 @@
/obj/item/flashlight/slime/attack_self(mob/user)
return //Bio-luminescence does not toggle.
-/obj/item/flashlight/slime/extinguish_light()
- visible_message("[src] dims slightly before scattering the shadows around it.")
+/obj/item/flashlight/slime/extinguish_light(force = FALSE)
+ if(force)
+ visible_message("[src] withers away.")
+ qdel(src)
+ else
+ visible_message("[src] dims slightly before scattering the shadows around it.")
/obj/item/flashlight/emp
origin_tech = "magnets=3;syndicate=1"
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 800f4163ed6..3610a8b4919 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -329,7 +329,7 @@
M.emp_act(severity)
..()
-/obj/item/paicard/extinguish_light()
+/obj/item/paicard/extinguish_light(force = FALSE)
if(pai)
pai.extinguish_light()
set_light(0)
diff --git a/code/game/objects/items/tools/welder.dm b/code/game/objects/items/tools/welder.dm
index af6f10c8f66..9faa226cbd5 100644
--- a/code/game/objects/items/tools/welder.dm
+++ b/code/game/objects/items/tools/welder.dm
@@ -72,6 +72,13 @@
reagents.add_reagent("fuel", 1)
..()
+/obj/item/weldingtool/extinguish_light(force)
+ if(!force)
+ return
+ if(!tool_enabled)
+ return
+ remove_fuel(maximum_fuel)
+
/obj/item/weldingtool/attack_self(mob/user)
if(tool_enabled) //Turn off the welder if it's on
to_chat(user, "You switch off [src].")
diff --git a/code/game/objects/items/weapons/cigs.dm b/code/game/objects/items/weapons/cigs.dm
index f789c869865..3ef91574a60 100644
--- a/code/game/objects/items/weapons/cigs.dm
+++ b/code/game/objects/items/weapons/cigs.dm
@@ -189,6 +189,11 @@ LIGHTERS ARE IN LIGHTERS.DM
smoke()
+/obj/item/clothing/mask/cigarette/extinguish_light(force)
+ if(!force)
+ return
+ die()
+
/obj/item/clothing/mask/cigarette/attack_self(mob/user)
if(lit)
user.visible_message("[user] calmly drops and treads on [src], putting it out instantly.")
@@ -362,6 +367,9 @@ LIGHTERS ARE IN LIGHTERS.DM
chem_volume = 200
list_reagents = list("nicotine" = 200)
+/obj/item/clothing/mask/cigarette/pipe/die()
+ return
+
/obj/item/clothing/mask/cigarette/pipe/light(flavor_text = null)
if(!lit)
lit = TRUE
diff --git a/code/game/objects/items/weapons/lighters.dm b/code/game/objects/items/weapons/lighters.dm
index fae9a68e9ae..256f9656863 100644
--- a/code/game/objects/items/weapons/lighters.dm
+++ b/code/game/objects/items/weapons/lighters.dm
@@ -69,10 +69,16 @@
force = 0
attack_verb = null //human_defense.dm takes care of it
- show_off_message(user)
+ if(user)
+ show_off_message(user)
set_light(0)
STOP_PROCESSING(SSobj, src)
+/obj/item/lighter/extinguish_light(force)
+ if(!force)
+ return
+ turn_off_lighter()
+
/obj/item/lighter/proc/show_off_message(mob/living/user)
to_chat(user, "You shut off [src].")
@@ -124,6 +130,9 @@
/obj/item/lighter/zippo/turn_off_lighter(mob/living/user)
. = ..()
+ if(!user)
+ return
+
if(world.time > next_off_message)
user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.")
playsound(src.loc, 'sound/items/zippoclose.ogg', 25, 1)
@@ -201,6 +210,11 @@
..()
matchignite()
+/obj/item/match/extinguish_light(force)
+ if(!force)
+ return
+ matchburnout()
+
/obj/item/match/proc/matchignite()
if(!lit && !burnt)
lit = TRUE
diff --git a/code/game/turfs/simulated/floor/light_floor.dm b/code/game/turfs/simulated/floor/light_floor.dm
index 033e8ac8bef..5c8fd14852c 100644
--- a/code/game/turfs/simulated/floor/light_floor.dm
+++ b/code/game/turfs/simulated/floor/light_floor.dm
@@ -103,8 +103,7 @@
A.addStaticPower(100, STATIC_LIGHT)
update_icon()
-
-/turf/simulated/floor/light/extinguish_light()
+/turf/simulated/floor/light/extinguish_light(force = FALSE)
toggle_light(FALSE)
visible_message("[src] flickers and falls dark.")
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 2d8d485df45..efd2d781047 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -165,7 +165,7 @@
var/veil_msg = "You sense a dark presence lurking \
just beyond the veil..."
var/objective_verb = "Kill"
- var/mob/living/demon_type = /mob/living/simple_animal/slaughter
+ var/mob/living/demon_type = /mob/living/simple_animal/demon/slaughter
/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user)
if(level_blocks_magic(user.z)) //this is to make sure the wizard does NOT summon a demon from the Den..
@@ -179,7 +179,7 @@
to_chat(user, "You break the seal on the bottle, calling upon the dire spirits of the underworld...")
var/type = "slaughter"
- if(demon_type == /mob/living/simple_animal/slaughter/laughter)
+ if(demon_type == /mob/living/simple_animal/demon/slaughter/laughter)
type = "laughter"
var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [type] demon summoned by [user.real_name]?", ROLE_DEMON, TRUE, 10 SECONDS, source = demon_type)
@@ -196,9 +196,8 @@
/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, type = "", mob/user)
var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(T)
- var/mob/living/simple_animal/slaughter/S = new demon_type(holder)
+ var/mob/living/simple_animal/demon/slaughter/S = new demon_type(holder)
S.vialspawned = TRUE
- S.holder = holder
S.key = C.key
S.mind.assigned_role = S.name
S.mind.special_role = S.name
@@ -226,7 +225,7 @@
veil_msg = "You sense an adorable presence \
lurking just beyond the veil..."
objective_verb = "Hug and tickle"
- demon_type = /mob/living/simple_animal/slaughter/laughter
+ demon_type = /mob/living/simple_animal/demon/slaughter/laughter
///////////MORPH
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index 0b198442e12..14fd62bb2b6 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -44,10 +44,10 @@
/obj/item/clothing/head/hardhat/proc/turn_off(mob/user)
set_light(0)
-/obj/item/clothing/head/hardhat/extinguish_light(mob/living/user)
+/obj/item/clothing/head/hardhat/extinguish_light(force = FALSE)
if(on)
on = FALSE
- turn_off(user)
+ turn_off()
update_icon(UPDATE_ICON_STATE)
visible_message("[src]'s light fades and turns off.")
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 28f39c2f5f7..7e09f75ab8d 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -68,7 +68,7 @@
var/datum/action/A = X
A.UpdateButtonIcon()
-/obj/item/clothing/head/helmet/space/hardsuit/extinguish_light()
+/obj/item/clothing/head/helmet/space/hardsuit/extinguish_light(force = FALSE)
if(on)
toggle_light()
visible_message("[src]'s light fades and turns off.")
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index 5bc889da767..b5f7cc3a583 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -82,7 +82,7 @@
set_light(brightness_on)
-/obj/item/clothing/head/helmet/space/plasmaman/extinguish_light()
+/obj/item/clothing/head/helmet/space/plasmaman/extinguish_light(force = FALSE)
if(on)
toggle_light()
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 6b21388b23b..8c9e77b8bc2 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -197,6 +197,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Traders", /datum/event/traders, 85, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Terror Spiders", /datum/event/spider_terror, 20, list(ASSIGNMENT_SECURITY = 4), TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Slaughter Demon", /datum/event/spawn_slaughter, 10, is_one_shot = TRUE),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Shadow Demon", /datum/event/spawn_slaughter/shadow, 50, is_one_shot = TRUE)
//new /datum/event_meta(EVENT_LEVEL_MAJOR, "Floor Cluwne", /datum/event/spawn_floor_cluwne, 15, is_one_shot = TRUE)
)
diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm
index 018cfe636c9..30332a91e39 100644
--- a/code/modules/events/slaughterevent.dm
+++ b/code/modules/events/slaughterevent.dm
@@ -1,9 +1,9 @@
/datum/event/spawn_slaughter
var/key_of_slaughter
- var/demon = /mob/living/simple_animal/slaughter/lesser
+ var/mob/living/simple_animal/demon/demon = /mob/living/simple_animal/demon/slaughter/lesser
/datum/event/spawn_slaughter/proc/get_slaughter()
- var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, source = /mob/living/simple_animal/slaughter)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [initial(demon.name)]?", ROLE_DEMON, TRUE, source = demon)
if(!length(candidates))
kill()
return
@@ -17,25 +17,41 @@
var/datum/mind/player_mind = new /datum/mind(key_of_slaughter)
player_mind.active = TRUE
- var/list/spawn_locs = list()
+ var/turf/spawn_loc = get_spawn_loc(player_mind.current)
+ var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(spawn_loc)
+ var/mob/living/simple_animal/demon/S = new demon(holder)
+ player_mind.transfer_to(S)
+ player_mind.assigned_role = "Demon"
+ player_mind.special_role = SPECIAL_ROLE_DEMON
+ message_admins("[key_name_admin(S)] has been made into a [S.name] by an event.")
+ log_game("[key_name_admin(S)] was spawned as a [S.name] by an event.")
+
+/datum/event/spawn_slaughter/proc/get_spawn_loc(mob/player)
+ RETURN_TYPE(/turf)
+ var/list/spawn_centers = list()
for(var/obj/effect/landmark/spawner/rev/L in GLOB.landmarks_list)
- spawn_locs += get_turf(L)
- if(!spawn_locs) //If we can't find a good place, just spawn the revenant at the player's location
- spawn_locs += get_turf(player_mind.current)
- if(!spawn_locs) //If we can't find THAT, then give up
+ spawn_centers += get_turf(L)
+ if(!spawn_centers) //If we can't find a good place, just spawn the revenant at the player's location
+ spawn_centers += get_turf(player)
+ if(!spawn_centers) //If we can't find THAT, then give up
kill()
return
- var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(pick(spawn_locs))
- var/mob/living/simple_animal/slaughter/S = new demon(holder)
- S.holder = holder
- player_mind.transfer_to(S)
- player_mind.assigned_role = "Slaughter Demon"
- player_mind.special_role = SPECIAL_ROLE_SLAUGHTER_DEMON
- message_admins("[key_name_admin(S)] has been made into a Slaughter Demon by an event.")
- log_game("[key_name_admin(S)] was spawned as a Slaughter Demon by an event.")
+ return pick(spawn_centers)
+
/datum/event/spawn_slaughter/start()
INVOKE_ASYNC(src, PROC_REF(get_slaughter))
/datum/event/spawn_slaughter/greater
- demon = /mob/living/simple_animal/slaughter
+ demon = /mob/living/simple_animal/demon/slaughter
+
+/datum/event/spawn_slaughter/shadow
+ demon = /mob/living/simple_animal/demon/shadow
+
+/datum/event/spawn_slaughter/shadow/get_spawn_loc()
+ var/turf/spawn_center = ..()
+ for(var/turf/T in range(50, spawn_center))
+ if(T.get_lumcount()) // if the turf is not pitch black
+ continue
+ return T // return the first turf that is dark nearby.
+ kill()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index 8582dba71de..6bd4cbc4b7c 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -115,7 +115,7 @@
if(old_stat != stat)
update_icon(UPDATE_OVERLAYS)
-/obj/machinery/smartfridge/extinguish_light()
+/obj/machinery/smartfridge/extinguish_light(force = FALSE)
set_light(0)
underlays.Cut()
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index b3baeb01333..df6604dedb2 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -206,3 +206,9 @@
add_attack_logs(user, target, "[what_done] ([reagent_str] | [genes_str])")
+/obj/item/reagent_containers/food/snacks/grown/extinguish_light(force = FALSE)
+ if(!force)
+ return
+ if(seed.get_gene(/datum/plant_gene/trait/glow/shadow))
+ return
+ set_light(0)
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index e87af235d02..71063fde945 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -61,3 +61,10 @@
if(seed)
for(var/datum/plant_gene/trait/T in seed.genes)
T.on_throw_impact(src, hit_atom)
+
+/obj/item/grown/extinguish_light(force = FALSE)
+ if(!force)
+ return
+ if(seed.get_gene(/datum/plant_gene/trait/glow/shadow))
+ return
+ set_light(0)
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index 3870fb5937e..d2d4c87eadd 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -54,7 +54,7 @@
else
light = new/datum/light_source(src, .)
-/atom/proc/extinguish_light()
+/atom/proc/extinguish_light(force = FALSE)
return
// If we have opacity, make sure to tell (potentially) affected light sources.
diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm
index e328e3be8b8..2687876d69e 100644
--- a/code/modules/mob/dead/observer/orbit.dm
+++ b/code/modules/mob/dead/observer/orbit.dm
@@ -134,9 +134,9 @@
var/list/antag_serialized = serialized.Copy()
antag_serialized["antag"] = "Xenomorph"
antagonists += list(antag_serialized)
- else if(isslaughterdemon(M))
+ else if(isdemon(M))
var/list/antag_serialized = serialized.Copy()
- antag_serialized["antag"] = "Slaughter Demon"
+ antag_serialized["antag"] = "Demon"
antagonists += list(antag_serialized)
else
if(length(orbiters) >= 0.2 * length_of_ghosts) // If a bunch of people are orbiting an object, like the nuke disk.
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 6187846e42d..04fed84f41f 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1966,7 +1966,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
to_chat(src, "[pick(GLOB.boo_phrases)]")
return TRUE
-/mob/living/carbon/human/extinguish_light()
+/mob/living/carbon/human/extinguish_light(force = FALSE)
// Parent function handles stuff the human may be holding
..()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 47778fc9f91..c2ceab12a72 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1092,10 +1092,10 @@
/mob/living/proc/fakefire()
return
-/mob/living/extinguish_light()
+/mob/living/extinguish_light(force = FALSE)
for(var/atom/A in src)
if(A.light_range > 0)
- A.extinguish_light()
+ A.extinguish_light(force)
/mob/living/vv_edit_var(var_name, var_value)
switch(var_name)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index d46d69b08af..a7fbd9182f5 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -42,8 +42,6 @@
var/mob_biotypes = MOB_ORGANIC
var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature..
- var/holder = null //The holder for blood crawling
-
var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always
var/list/icon/pipes_shown = list()
var/last_played_vent
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 5d1ea76cd4c..1edeb2a30f2 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -515,7 +515,7 @@
CRASH("pAI without card")
loc = card
-/mob/living/silicon/pai/extinguish_light()
+/mob/living/silicon/pai/extinguish_light(force = FALSE)
flashlight_on = FALSE
set_light(0)
card.set_light(0)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 97e03355bfc..60cdacafe29 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1473,7 +1473,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
overlays += "[base_icon]-shield"
-/mob/living/silicon/robot/extinguish_light()
+/mob/living/silicon/robot/extinguish_light(force = FALSE)
update_headlamp(1, 150)
/mob/living/silicon/robot/rejuvenate()
diff --git a/code/modules/pda/PDA.dm b/code/modules/pda/PDA.dm
index d6800fdf19a..099f88acfec 100644
--- a/code/modules/pda/PDA.dm
+++ b/code/modules/pda/PDA.dm
@@ -390,7 +390,7 @@ GLOBAL_LIST_EMPTY(PDAs)
if(current_app)
current_app.program_process()
-/obj/item/pda/extinguish_light()
+/obj/item/pda/extinguish_light(force = FALSE)
var/datum/data/pda/utility/flashlight/FL = find_program(/datum/data/pda/utility/flashlight)
if(FL && FL.fon)
FL.start()
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 8b33d819f3b..d12a46a07c1 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -941,7 +941,7 @@
limb.droplimb(0, DROPLIMB_BURN)
return FIRELOSS
-/obj/machinery/light/extinguish_light()
+/obj/machinery/light/extinguish_light(force = FALSE)
on = FALSE
extinguished = TRUE
emergency_mode = FALSE
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index f786178214c..1735648efbf 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -368,14 +368,11 @@
if(!gun_light)
return
-
- var/mob/living/carbon/human/user = usr
- if(!isturf(user.loc))
- to_chat(user, "You cannot turn the light on while in this [user.loc]!")
gun_light.on = !gun_light.on
- to_chat(user, "You toggle the gun light [gun_light.on ? "on":"off"].")
-
- playsound(user, 'sound/weapons/empty.ogg', 100, 1)
+ var/mob/living/carbon/human/user = usr
+ if(user)
+ to_chat(user, "You toggle the gun light [gun_light.on ? "on":"off"].")
+ playsound(src, 'sound/weapons/empty.ogg', 100, 1)
update_gun_light(user)
/obj/item/gun/proc/update_gun_light(mob/user = null)
@@ -401,7 +398,7 @@
knife_overlay = null
return TRUE
-/obj/item/gun/extinguish_light()
+/obj/item/gun/extinguish_light(force = FALSE)
if(gun_light?.on)
toggle_gunlight()
visible_message("[src]'s light fades and turns off.")
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index f3195929c8c..24940ee6a83 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi
index ef27b7fc377..4a939231c99 100644
Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index c485661b91f..9d8d4181dce 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index a042e446cf7..7df80247c19 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 03271c7c0ba..32e2adb15ce 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -617,6 +617,9 @@
#include "code\game\gamemodes\miniantags\abduction\machinery\dispenser.dm"
#include "code\game\gamemodes\miniantags\abduction\machinery\experiment.dm"
#include "code\game\gamemodes\miniantags\abduction\machinery\pad.dm"
+#include "code\game\gamemodes\miniantags\demons\demon.dm"
+#include "code\game\gamemodes\miniantags\demons\shadow_demon\shadow_demon.dm"
+#include "code\game\gamemodes\miniantags\demons\slaughter demon\slaughter.dm"
#include "code\game\gamemodes\miniantags\guardian\guardian.dm"
#include "code\game\gamemodes\miniantags\guardian\host_actions.dm"
#include "code\game\gamemodes\miniantags\guardian\types\assassin.dm"
@@ -638,8 +641,6 @@
#include "code\game\gamemodes\miniantags\revenant\revenant.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_abilities.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_spawn_event.dm"
-#include "code\game\gamemodes\miniantags\slaughter\bloodcrawl.dm"
-#include "code\game\gamemodes\miniantags\slaughter\slaughter.dm"
#include "code\game\gamemodes\nuclear\nuclear.dm"
#include "code\game\gamemodes\nuclear\nuclear_challenge.dm"
#include "code\game\gamemodes\nuclear\nuclearbomb.dm"