diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm index ea98cf2c102..f2fd4f881b5 100644 --- a/code/__DEFINES/gamemode.dm +++ b/code/__DEFINES/gamemode.dm @@ -46,7 +46,7 @@ #define SPECIAL_ROLE_REVENANT "Revenant" #define SPECIAL_ROLE_SHADOWLING "Shadowling" #define SPECIAL_ROLE_SHADOWLING_THRALL "Shadowling Thrall" -#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/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 8fff75112e5..c58879d2614 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -36,7 +36,7 @@ #define ROLE_TERROR_SPIDER "Terror Spider" // Role tags for EVERYONE! #define ROLE_BORER "cortical borer" -#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/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm index 5ba00b54327..daa4b4a9b3a 100644 --- a/code/datums/spells/bloodcrawl.dm +++ b/code/datums/spells/bloodcrawl.dm @@ -88,7 +88,7 @@ /obj/effect/proc_holder/spell/bloodcrawl/proc/block_hands(mob/living/carbon/user) if(user.l_hand || user.r_hand) - to_chat(user, "You may not hold items while blood crawling!") + to_chat(user, span_warning("You may not hold items while blood crawling!")) return FALSE var/obj/item/bloodcrawl/left_hand = new(user) @@ -113,61 +113,61 @@ flick(animation_state, src) // Setting the icon_state to the animation has timing issues and can cause frame skips -/obj/effect/proc_holder/spell/bloodcrawl/proc/sink_animation(atom/target, mob/living/user) +/obj/effect/proc_holder/spell/bloodcrawl/proc/sink_animation(atom/enter_point, mob/living/user) var/turf/mob_loc = get_turf(user) - visible_message("[user] sinks into [target].") + visible_message(span_danger("[user] sinks into [enter_point].")) playsound(mob_loc, 'sound/misc/enter_blood.ogg', 100, TRUE, -1) new /obj/effect/temp_visual/dir_setting/bloodcrawl(mob_loc, user.dir, "jaunt") -/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)) +/obj/effect/proc_holder/spell/bloodcrawl/proc/handle_consumption(mob/living/user, mob/living/victim, atom/enter_point, obj/effect/dummy/slaughter/holder) + if(!HAS_TRAIT(user, 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() + enter_point.visible_message(span_warning("[victim] kicks free of [enter_point] just before entering it!")) + user.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...") + enter_point.visible_message(span_warning("[user] drags [victim] into [enter_point]!")) + user.stop_pulling() + to_chat(user, "You begin to feast on [victim]. You can not move while you are doing this.") + enter_point.visible_message(span_warning("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 + if(isslaughterdemon(user)) + var/mob/living/simple_animal/demon/slaughter/demon = user + sound = demon.feast_sound else sound = 'sound/misc/demon_consume.ogg' for(var/i in 1 to 3) - playsound(get_turf(L), sound, 100, 1) + playsound(get_turf(user), sound, 100, TRUE) sleep(3 SECONDS) if(!victim) - to_chat(L, "You happily devour... nothing? Your meal vanished at some point!") + to_chat(user, span_danger("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) + to_chat(user, span_warning("You devour [victim]. Your health is fully restored.")) + user.adjustBruteLoss(-1000) + user.adjustFireLoss(-1000) + user.adjustOxyLoss(-1000) + user.adjustToxLoss(-1000) else - to_chat(L, "You devour [victim], but this measly meal barely sates your appetite!") - L.adjustBruteLoss(-25) - L.adjustFireLoss(-25) + to_chat(user, span_warning("You devour [victim], but this measly meal barely sates your appetite!")) + user.adjustBruteLoss(-25) + user.adjustFireLoss(-25) - if(isslaughterdemon(L)) - var/mob/living/simple_animal/demon/slaughter/demon = L + if(isslaughterdemon(user)) + var/mob/living/simple_animal/demon/slaughter/demon = user demon.devoured++ - to_chat(victim, "You feel teeth sink into your flesh, and the--") + to_chat(victim, span_userdanger("You feel teeth sink into your flesh, and the--")) var/obj/item/organ/internal/regenerative_core/legion/core = victim.get_int_organ(/obj/item/organ/internal/regenerative_core/legion) if(core) core.remove(victim) @@ -177,126 +177,126 @@ demon.consumed_mobs.Add(victim) //ADD_TRAIT(victim, TRAIT_UNREVIVABLE, "demon") 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 + var/mob/living/carbon/human/h_victim = victim + if(h_victim.w_uniform && istype(h_victim.w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/uniform = h_victim.w_uniform + uniform.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/post_phase_in(mob/living/user, obj/effect/dummy/slaughter/holder) + user.notransform = FALSE -/obj/effect/proc_holder/spell/bloodcrawl/proc/phaseout(obj/effect/decal/cleanable/B, mob/living/L) +/obj/effect/proc_holder/spell/bloodcrawl/proc/phaseout(obj/effect/decal/cleanable/enter_point, mob/living/carbon/user) - if(iscarbon(L) && !block_hands(L)) + if(istype(user) && !block_hands(user)) return FALSE - L.notransform = TRUE - INVOKE_ASYNC(src, PROC_REF(async_phase), B, L) + user.notransform = TRUE + INVOKE_ASYNC(src, PROC_REF(async_phase), enter_point, user) 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) +/obj/effect/proc_holder/spell/bloodcrawl/proc/async_phase(obj/effect/decal/cleanable/enter_point, mob/living/user) + var/turf/mobloc = get_turf(user) + sink_animation(enter_point, user) 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) + user.forceMove(holder) + user.ExtinguishMob() + handle_consumption(user, user.pulling, enter_point, holder) + post_phase_in(user, holder) -/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_animation(turf/tele_loc, mob/living/L, atom/A) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(tele_loc, L.dir, "jauntup") - if(prob(25) && isdemon(L)) +/obj/effect/proc_holder/spell/bloodcrawl/proc/rise_animation(turf/tele_loc, mob/living/user, atom/exit_point) + new /obj/effect/temp_visual/dir_setting/bloodcrawl(tele_loc, user.dir, "jauntup") + if(prob(25) && isdemon(user)) 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) + playsound(tele_loc, pick(voice), 50, TRUE, -1) + exit_point.visible_message(span_warning("[user] rises out of [exit_point]!")) + playsound(get_turf(tele_loc), 'sound/misc/exit_blood.ogg', 100, TRUE, -1) -/obj/effect/proc_holder/spell/bloodcrawl/proc/unblock_hands(mob/living/carbon/C) - if(!istype(C)) +/obj/effect/proc_holder/spell/bloodcrawl/proc/unblock_hands(mob/living/carbon/user) + if(!istype(user)) return - for(var/obj/item/bloodcrawl/BC in C) - qdel(BC) + for(var/obj/item/bloodcrawl/item in user) + qdel(item) -/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/rise_message(atom/exit_point) + exit_point.visible_message(span_warning("[exit_point] 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/post_phase_out(atom/exit_point, mob/living/user) + if(isslaughterdemon(user)) + var/mob/living/simple_animal/demon/slaughter/demon = user + demon.speed = 0 + demon.boost = world.time + 6 SECONDS + user.color = exit_point.color + addtimer(VARSET_CALLBACK(user, color, null), 6 SECONDS) -/obj/effect/proc_holder/spell/bloodcrawl/proc/phasein(atom/A, mob/living/L) +/obj/effect/proc_holder/spell/bloodcrawl/proc/phasein(atom/enter_point, mob/living/user) - if(L.notransform) - to_chat(L, "Finish eating first!") + if(user.notransform) + to_chat(user, span_warning("Finish eating first!")) return FALSE - rise_message(A) - if(!do_after(L, 2 SECONDS, target = A)) + rise_message(enter_point) + if(!do_after(user, 2 SECONDS, target = enter_point)) return FALSE - if(!A) + if(!enter_point) return FALSE - var/turf/tele_loc = isturf(A) ? A : A.loc - var/holder = L.loc - L.forceMove(tele_loc) - L.client.eye = L + var/turf/tele_loc = isturf(enter_point) ? enter_point : enter_point.loc + var/holder = user.loc + user.forceMove(tele_loc) + user.client.eye = user - rise_animation(tele_loc, L, A) + rise_animation(tele_loc, user, enter_point) - unblock_hands(L) + unblock_hands(user) QDEL_NULL(holder) - post_phase_out(A, L) + post_phase_out(enter_point, user) return TRUE -/*/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl +/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" + allowed_type = /turf /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) +/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_message(atom/exit_point) return -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_animation(turf/tele_loc, mob/living/L, atom/A) - new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(L), L.dir, "shadowwalk_appear") +/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/rise_animation(turf/tele_loc, mob/living/user, atom/exit_point) + new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(user), user.dir, "shadowwalk_appear") -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/handle_consumption(mob/living/L, mob/living/victim, atom/A, obj/effect/dummy/slaughter/holder) +/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/handle_consumption(mob/living/L, mob/living/victim, atom/enter_point, 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...") - new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(L), L.dir, "shadowwalk_disappear") +/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/sink_animation(atom/enter_point, mob/living/user) + enter_point.visible_message(span_danger("[user] sinks into the shadows...")) + new /obj/effect/temp_visual/dir_setting/bloodcrawl(get_turf(user), user.dir, "shadowwalk_disappear") -/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/post_phase_in(mob/living/L, obj/effect/dummy/slaughter/holder) +/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/post_phase_in(mob/living/user, obj/effect/dummy/slaughter/holder) ..() - if(!istype(L, /mob/living/simple_animal/demon/shadow)) + if(!istype(user, /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))*/ + var/mob/living/simple_animal/demon/shadow/demon = user + demon.RegisterSignal(holder, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/living/simple_animal/demon/shadow, check_darkness)) diff --git a/code/game/gamemodes/miniantags/demons/demon.dm b/code/game/gamemodes/miniantags/demons/demon.dm new file mode 100644 index 00000000000..5255e63708f --- /dev/null +++ b/code/game/gamemodes/miniantags/demons/demon.dm @@ -0,0 +1,112 @@ +/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") + tts_seed = "Mannoroth" + response_help = "thinks better of touching" + response_disarm = "flails at" + response_harm = "punches" + speed = 1 + a_intent = INTENT_HARM + 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 = "неистово терзает" + 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/vialspawned = FALSE + var/playstyle_string + var/datum/action/innate/demon/whisper/whisper_action + + +/mob/living/simple_animal/demon/Initialize(mapload) + . = ..() + whisper_action = new() + whisper_action.Grant(src) + addtimer(CALLBACK(src, PROC_REF(attempt_objectives)), 5 SECONDS) + + +/mob/living/simple_animal/demon/Destroy() + if(whisper_action) + whisper_action = null + return ..() + + +/datum/action/innate/demon/whisper + name = "Demonic Whisper" + button_icon_state = "cult_comms" + background_icon_state = "bg_demon" + + +/datum/action/innate/demon/whisper/proc/choose_targets(mob/user = usr)//yes i am copying from telepathy..hush... + var/list/validtargets = list() + for(var/mob/living/target in (view(user.client.view) - user)) + if(target && target.mind && target.stat != DEAD) + validtargets += target + + if(!length(validtargets)) + to_chat(usr, span_warning("There are no valid targets!")) + return + + var/mob/living/target = input("Choose the target to talk to.", "Targeting") as null|mob in validtargets + return target + + +/datum/action/innate/demon/whisper/Activate() + var/mob/living/choice = choose_targets() + if(!choice) + return + + var/msg = stripped_input(usr, "What do you wish to tell [choice]?", null, "") + if(!(msg)) + return + + add_say_logs(usr, msg, choice, "SLAUGHTER") + to_chat(usr, span_info("You whisper to [choice]: [msg]")) + to_chat(choice, "[span_deadsay("Suddenly a strange, demonic voice resonates in your head... ")][span_danger(" [msg]")]") + for(var/mob/dead/observer/G in GLOB.player_list) + G.show_message("Demonic message from [usr] ([ghost_follow_link(usr, ghost=G)]) to [choice] ([ghost_follow_link(choice, ghost=G)]): [msg]") + + +/obj/item/organ/internal/heart/demon + name = "demon heart" + desc = "Still it beats furiously, emanating an aura of utter hate." + icon = 'icons/obj/surgery.dmi' + icon_state = "demon_heart" + origin_tech = "combat=5;biotech=7" + + +/obj/item/organ/internal/heart/demon/update_icon() + return //always beating visually + + +/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 // Always beating. + + +/obj/item/organ/internal/heart/demon/attack_self(mob/living/user) + user.visible_message(span_warning("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!"), \ + span_danger("An unnatural hunger consumes you. You raise [src] to your mouth and devour it!")) + playsound(user, 'sound/misc/demon_consume.ogg', 50, TRUE) + + +/mob/living/simple_animal/demon/proc/attempt_objectives() + return mind + 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..75c2436b2f4 --- /dev/null +++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm @@ -0,0 +1,298 @@ +/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" + speed = 0 + maxHealth = 300 + health = 300 + 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 + var/wrapping = FALSE + var/list/wrapped_victims + playstyle_string = "You are the Shadow Demon, a terrible creature from another existence. You have only two desires to survive and to lurk and ambush careless preys. \ + You may use the Shadow Crawl ability when near the dark spots, appearing and dissapearing from the station at will. \ + Your Shadow Grapple ability allows you to pull living preys or to push yourself to the other objects. Also extinguishes all light sources at the area of impact. \ + You can wrap dead humanoid bodies by attacking them, use Alt+Click on the shadow cocoon afterwards to lure more victims. \ + You move quickly and regenerate fast in the shadows, but any light source will hurt you to the death. STAY AWAY FROM THE LIGHT! " + + +/mob/living/simple_animal/demon/shadow/Initialize(mapload) + . = ..() + remove_from_all_data_huds() + AddSpell(new /obj/effect/proc_holder/spell/fireball/shadow_grapple) + var/obj/effect/proc_holder/spell/bloodcrawl/shadow_crawl/crawl = new + AddSpell(crawl) + whisper_action.button_icon_state = "shadow_whisper" + whisper_action.background_icon_state = "shadow_demon_bg" + if(istype(loc, /obj/effect/dummy/slaughter)) + crawl.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/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(30 * damage_mod) // 20 seconds in light and you are done + SEND_SOUND(src, sound('sound/weapons/sear.ogg')) + to_chat(src, span_dangerbigger("The light scalds you!")) + else + adjustBruteLoss(-30) + + +/mob/living/simple_animal/demon/shadow/proc/check_darkness() + var/turf/source_turf = get_turf(src) + var/lum_count = source_turf.get_lumcount() + if(lum_count > 0.2) + if(!thrown_alert) + thrown_alert = TRUE + throw_alert("light", /obj/screen/alert/lightexposure) + animate(src, alpha = 255, time = 0.5 SECONDS) + speed = initial(speed) + else + if(thrown_alert) + thrown_alert = FALSE + clear_alert("light") + animate(src, alpha = 125, time = 0.5 SECONDS) + speed = -0.3 + return lum_count + + +/mob/living/simple_animal/demon/shadow/UnarmedAttack(atom/target) + if(!ishuman(target)) + if(isitem(target)) + target.extinguish_light(TRUE) + return ..() + + var/mob/living/carbon/human/h_target = target + if(h_target.stat != DEAD) + return ..() + + if(isLivingSSD(h_target) && client.send_ssd_warning(h_target)) //Similar to revenants, only wrap SSD targets if you've accepted the SSD warning + return + + if(wrapping) + to_chat(src, span_notice("We are already wrapping something.")) + return + + visible_message(span_danger("[src] begins wrapping [h_target] in shadowy threads.")) + wrapping = TRUE + if(!do_after(src, 4 SECONDS, FALSE, target = h_target)) + wrapping = FALSE + return + + h_target.visible_message(span_warning("[src] envelops [h_target] into an ethereal cocoon, and darkness begins to creep from it.")) + var/obj/structure/shadowcocoon/cocoon = new(get_turf(h_target)) + h_target.extinguish_light(TRUE) // may as well be safe + h_target.forceMove(cocoon) + wrapping = FALSE + + if(!h_target.mind) + return + + if(!wrapped_victims) + wrapped_victims = list() + var/human_UID = h_target.UID() + if(!(human_UID in wrapped_victims)) + wrapped_victims += human_UID + + +/obj/structure/shadowcocoon + name = "shadowy cocoon" + desc = "Something wrapped in what seems to be manifested darkness. Its surface distorts unnaturally, and it emanates deep shadows." + icon = 'icons/effects/effects.dmi' + icon_state = "shadowcocoon" + light_power = -4 + light_range = 6 + max_integrity = 100 + light_color = "#ddd6cf" + anchored = TRUE + /// Amount of SSobj ticks (Roughly 2 seconds) since the last hallucination proc'd + var/time_since_last_hallucination = 0 + /// Will we play hallucination sounds or not + var/silent = TRUE + + +/obj/structure/shadowcocoon/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + + +/obj/structure/shadowcocoon/process() + time_since_last_hallucination++ + for(var/atom/to_darken in range(4, src)) + if(prob(60) || !length(to_darken.light_sources)) + continue + if(iswelder(to_darken) && length(to_darken.light_sources)) + to_darken.visible_message(span_notice("The shadows swarm around and overwhelm the flame of [to_darken].")) + to_darken.extinguish_light(TRUE) + if(!silent && time_since_last_hallucination >= rand(8, 12)) + playsound(src, pick('sound/items/deconstruct.ogg', 'sound/weapons/handcuffs.ogg', 'sound/machines/airlock_open.ogg', 'sound/machines/airlock_close.ogg', 'sound/machines/boltsup.ogg', 'sound/effects/eleczap.ogg', get_sfx("bodyfall"), get_sfx("gunshot"), 'sound/weapons/egloves.ogg'), 50) + time_since_last_hallucination = 0 + + +/obj/structure/shadowcocoon/AltClick(mob/user) + if(!isdemon(user)) + return ..() + if(silent) + to_chat(user, span_notice("You twist and change your trapped victim in [src] to lure in more prey.")) + silent = FALSE + return + to_chat(user, span_notice("The tendrils from [src] snap back to their orignal form.")) + silent = TRUE + + +/obj/structure/shadowcocoon/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = NONE) + if(damage_type != BURN) //I unashamedly stole this from spider cocoon code + return + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/structure/shadowcocoon/obj_destruction() + visible_message(span_danger("[src] splits open, and the shadows dancing around it fade.")) + return ..() + + +/obj/structure/shadowcocoon/Destroy() + for(var/atom/movable/AM in contents) + AM.forceMove(loc) + return..() + + +/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 + ..() + + +/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." + panel = "Demon" + action_background_icon_state = "shadow_demon_bg" + action_icon_state = "shadow_grapple" + invocation_type = "none" + invocation = null + sound = null + need_active_overlay = TRUE + human_req = FALSE + selection_activated_message = span_notice("You raise your hand, full of demonic energy! Left-click to cast at a target!") + selection_deactivated_message = span_notice("You re-absorb the energy...for now.") + base_cooldown = 10 SECONDS + fireball_type = /obj/item/projectile/magic/shadow_hand + + +/obj/effect/proc_holder/spell/fireball/shadow_grapple/update_icon() + return + + +/obj/item/projectile/magic/shadow_hand + name = "shadow hand" + icon_state = "shadow_hand" + plane = FLOOR_PLANE + speed = 1 + 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 + . = ..() + for(var/atom/extinguish_target in range(2, src)) + extinguish_target.extinguish_light(TRUE) + if(isliving(target)) + var/mob/living/l_target = target + l_target.Immobilize(4 SECONDS) + l_target.apply_damage(40, BRUTE, BODY_ZONE_CHEST) + l_target.throw_at(get_step(firer, get_dir(firer, target)), 50, 10) + else + firer.throw_at(get_step(target, get_dir(target, firer)), 50, 10) + + +/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_from_active_hand() + 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) + + +/mob/living/simple_animal/demon/shadow/attempt_objectives() + if(!..()) + return + + to_chat(src, playstyle_string) + to_chat(src, span_notice("You are not currently in the same plane of existence as the station. Use the shadow crawl action near any dark spot.")) + src << 'sound/misc/demon_dies.ogg' + if(vialspawned) + return + + var/datum/objective/wrap/wrap_objective = new + var/datum/objective/survive/survive_objective = new + SSticker.mode.traitors |= mind + wrap_objective.owner = mind + survive_objective.owner = mind + mind.objectives += wrap_objective + mind.objectives += survive_objective + mind.announce_objectives() + + +/datum/objective/wrap + name = "Wrap" + needs_target = FALSE + target_amount = 10 + + +/datum/objective/wrap/New(text, datum/team/team_to_join) + target_amount = rand(10,20) + explanation_text = "Ambush those who dare to challenge the shadows. Wrap at least [target_amount] mortals." + ..() + + +/datum/objective/wrap/check_completion() + var/wrap_count = 0 + for(var/datum/mind/player in get_owners()) + if(!istype(player.current, /mob/living/simple_animal/demon/shadow) || QDELETED(player.current)) + continue + + var/mob/living/simple_animal/demon/shadow/demon = player.current + if(!demon.wrapped_victims || !length(demon.wrapped_victims)) + continue + + wrap_count += length(demon.wrapped_victims) + + return wrap_count >= target_amount + diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm similarity index 56% rename from code/game/gamemodes/miniantags/slaughter/slaughter.dm rename to code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm index 87f2e5adcb8..40f57007b59 100644 --- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm +++ b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter_demon.dm @@ -1,48 +1,3 @@ -//////////////////The Monster -/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") - tts_seed = "Mannoroth" - response_help = "thinks better of touching" - response_disarm = "flails at" - response_harm = "punches" - speed = 1 - a_intent = INTENT_HARM - 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 = "неистово терзает" - 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) - - -/mob/living/simple_animal/demon/Destroy() - if(whisper_action) - whisper_action = null - return ..() - - /mob/living/simple_animal/demon/slaughter name = "slaughter demon" real_name = "slaughter demon" @@ -52,7 +7,7 @@ icon_state = "daemon" icon_living = "daemon" deathmessage = "screams in anger as it collapses into a puddle of viscera!" - 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/feast_sound = 'sound/misc/demon_consume.ogg' var/boost = 0 var/devoured = 0 @@ -62,9 +17,8 @@ var/cooldown = 0 var/gorecooldown = 0 - var/vialspawned = FALSE - var/playstyle_string = "You are the Slaughter Demon, a terrible creature from another existence. You have a single desire: to kill. \ + 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. \ You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. " @@ -73,12 +27,11 @@ /mob/living/simple_animal/demon/slaughter/Initialize(mapload) . = ..() remove_from_all_data_huds() - ADD_TRAIT(src, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat") + ADD_TRAIT(src, TRAIT_BLOODCRAWL_EAT, TRAIT_BLOODCRAWL_EAT) var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new AddSpell(bloodspell) if(istype(loc, /obj/effect/dummy/slaughter)) bloodspell.phased = TRUE - addtimer(CALLBACK(src, PROC_REF(attempt_objectives)), 5 SECONDS) /mob/living/simple_animal/demon/slaughter/Destroy() @@ -96,22 +49,25 @@ speed = 0 -/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.") - src << 'sound/misc/demon_dies.ogg' - if(!(vialspawned)) - var/datum/objective/slaughter/objective = new - var/datum/objective/demonFluff/fluffObjective = new - SSticker.mode.traitors |= mind - objective.owner = mind - fluffObjective.owner = mind - //Paradise Port:I added the objective for one spawned like this - mind.objectives += objective - mind.objectives += fluffObjective - to_chat(src, "Objective #[1]: [objective.explanation_text]") - to_chat(src, "Objective #[2]: [fluffObjective.explanation_text]") +/mob/living/simple_animal/demon/slaughter/attempt_objectives() + if(!..()) + return + + to_chat(src, playstyle_string) + to_chat(src, span_notice("You are not currently in the same plane of existence as the station. Use the blood crawl action at a blood pool to manifest.")) + src << 'sound/misc/demon_dies.ogg' + if(vialspawned) + return + + var/datum/objective/slaughter/objective = new + var/datum/objective/demonFluff/fluffObjective = new + SSticker.mode.traitors |= mind + objective.owner = mind + fluffObjective.owner = mind + //Paradise Port:I added the objective for one spawned like this + mind.objectives += objective + mind.objectives += fluffObjective + mind.announce_objectives() /obj/effect/decal/cleanable/blood/innards @@ -121,14 +77,9 @@ desc = "A repulsive pile of guts and gore." - /mob/living/simple_animal/demon/slaughter/proc/release_consumed(mob/living/M) M.forceMove(get_turf(src)) -/mob/living/simple_animal/demon/slaughter/phasein() - . = ..() - speed = 0 - boost = world.time + 60 // Cult slaughter demon /mob/living/simple_animal/demon/slaughter/cult //Summoned as part of the cult objective "Bring the Slaughter" @@ -146,6 +97,11 @@ to emerge from it. You are fast, powerful, and almost invincible. By dragging a dead or unconscious body into a blood pool with you, you will consume it after a time and fully regain \ your health. You may use the ability 'Sense Victims' in your Cultist tab to locate a random, living heretic." + +/mob/living/simple_animal/demon/slaughter/cult/attempt_objectives() + return + + /obj/effect/proc_holder/spell/sense_victims name = "Sense Victims" desc = "Sense the location of heretics" @@ -169,13 +125,13 @@ /obj/effect/proc_holder/spell/sense_victims/cast(list/targets, mob/user) var/mob/living/victim = targets[1] - to_chat(victim, "You feel an awful sense of being watched...") + to_chat(victim, span_userdanger("You feel an awful sense of being watched...")) victim.Stun(6 SECONDS) //HUE var/area/A = get_area(victim) if(!A) - to_chat(user, "You could not locate any sapient heretics for the Slaughter.") + to_chat(user, span_warning("You could not locate any sapient heretics for the Slaughter.")) return - to_chat(user, "You sense a terrified soul at [A]. Show [A.p_them()] the error of [A.p_their()] ways.") + to_chat(user, span_danger("You sense a terrified soul at [A]. Show [A.p_them()] the error of [A.p_their()] ways.")) /mob/living/simple_animal/demon/slaughter/cult/Initialize(mapload) @@ -183,13 +139,13 @@ spawn(0.5 SECONDS) 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!") + visible_message(span_warning("[src] disappears in a flash of red light!")) qdel(src) return var/mob/M = pick(demon_candidates) var/mob/living/simple_animal/demon/slaughter/cult/S = src if(!M || !M.client) - visible_message("[src] disappears in a flash of red light!") + visible_message(span_warning("[src] disappears in a flash of red light!")) qdel(src) return var/client/C = M.client @@ -207,75 +163,17 @@ S.mind.objectives += new_objective to_chat(S, "Objective #[1]: [new_objective.explanation_text]") -////////////////////The Powers - -//Paradise Port: I added this because..SPOOPY DEMON IN YOUR BRAIN - - -/datum/action/innate/demon/whisper - name = "Demonic Whisper" - button_icon_state = "cult_comms" - background_icon_state = "bg_demon" - -/datum/action/innate/demon/whisper/IsAvailable() - return ..() - -/datum/action/innate/demon/whisper/proc/choose_targets(mob/user = usr)//yes i am copying from telepathy..hush... - var/list/validtargets = list() - for(var/mob/living/M in view(user.client.maxview(), get_turf(user))) - if(M && M.mind && M.stat != DEAD) - if(M == user) - continue - validtargets += M - - if(!validtargets.len) - to_chat(usr, "There are no valid targets!") - return - - var/mob/living/target = input("Choose the target to talk to.", "Targeting") as null|mob in validtargets - return target - -/datum/action/innate/demon/whisper/Activate() - var/mob/living/choice = choose_targets() - if(!choice) - return - - var/msg = stripped_input(usr, "What do you wish to tell [choice]?", null, "") - if(!(msg)) - return - add_say_logs(usr, msg, choice, "SLAUGHTER") - to_chat(usr, "You whisper to [choice]: [msg]") - to_chat(choice, "Suddenly a strange, demonic voice resonates in your head... [msg]") - for(var/mob/dead/observer/G in GLOB.player_list) - G.show_message("Demonic message from [usr] ([ghost_follow_link(usr, ghost=G)]) to [choice] ([ghost_follow_link(choice, ghost=G)]): [msg]") - - -//////////The Loot - -//The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl -/obj/item/organ/internal/heart/demon - name = "demon heart" - desc = "Still it beats furiously, emanating an aura of utter hate." - icon = 'icons/obj/surgery.dmi' - icon_state = "demon_heart" - origin_tech = "combat=5;biotech=7" - -/obj/item/organ/internal/heart/demon/update_icon() - return //always beating visually - -/obj/item/organ/internal/heart/demon/prepare_eat() - return // Just so people don't accidentally waste it - -/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 from killing a slaughter demon - can be consumed to allow the user to blood crawl. + */ +/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!", \ - "You feel a strange power seep into your body... you have absorbed the demon's blood-travelling powers!") + user.visible_message(span_warning("[user]'s eyes flare a deep crimson!"), \ + span_userdanger("You feel a strange power seep into your body... you have absorbed the demon's blood-travelling powers!")) ADD_TRAIT(user, TRAIT_BLOODCRAWL, "bloodcrawl") user.drop_from_active_hand() insert(user) //Consuming the heart literally replaces your heart with a demon heart. H A R D C O R E. @@ -283,31 +181,33 @@ // Eating a 2nd heart. Gives the ability to drag people into blood and eat them. if(HAS_TRAIT(user, TRAIT_BLOODCRAWL)) - to_chat(user, "You feel differ- CONSUME THEM! ") - ADD_TRAIT(user, TRAIT_BLOODCRAWL_EAT, "bloodcrawl_eat") + to_chat(user, "You feel differ-[span_warning(" CONSUME THEM! ")]") + ADD_TRAIT(user, TRAIT_BLOODCRAWL_EAT, TRAIT_BLOODCRAWL_EAT) qdel(src) // Replacing their demon heart with another demon heart is pointless, just delete this one and return. return TRUE // Eating any more than 2 demon hearts does nothing. - to_chat(user, "...and you don't feel any different.") + to_chat(user, span_warning("...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") + REMOVE_TRAIT(M, TRAIT_BLOODCRAWL, TRAIT_BLOODCRAWL) + REMOVE_TRAIT(M, TRAIT_BLOODCRAWL_EAT, TRAIT_BLOODCRAWL_EAT) M.mind.RemoveSpell(/obj/effect/proc_holder/spell/bloodcrawl) -/obj/item/organ/internal/heart/demon/Stop() - return 0 // Always beating. - +/** + * LAUGHTER DEMON + */ /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! @@ -333,11 +233,11 @@ loot = list(/mob/living/simple_animal/pet/cat/kitten{name = "Laughter"}) -/mob/living/simple_animal/demon/slaughter/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) - to_chat(M, "You leave [src]'s warm embrace, and feel ready to take on the world.") + to_chat(M, span_clown("You leave [src]'s warm embrace, and feel ready to take on the world.")) ..(M) diff --git a/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm b/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm deleted file mode 100644 index 109b33bcd7d..00000000000 --- a/code/game/gamemodes/miniantags/slaughter/bloodcrawl.dm +++ /dev/null @@ -1,172 +0,0 @@ -//Travel through pools of blood. Slaughter Demon powers for everyone! -/mob/living/proc/phaseout(var/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 = 0 - animation.anchored = 1 - 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(issimulatedturf(src)) - var/mob/living/simple_animal/demon/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) - to_chat(src, "You devour [kidnapped]. Your health is fully restored.") - adjustBruteLoss(-1000) - adjustFireLoss(-1000) - adjustOxyLoss(-1000) - adjustToxLoss(-1000) - - if(isslaughterdemon(src)) //rason, do not want humans to get this - var/mob/living/simple_animal/demon/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 = 0 - 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(var/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 = 0 - animation.anchored = 1 - 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.temporarily_remove_item_from_inventory(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 = 0 - anchored = 1 - 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/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index 31d4dc467db..dedbd45392d 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -517,6 +517,14 @@ category = "Summons" limit = 3 +/datum/spellbook_entry/item/shadowbottle + name = "Bottle of Shadows" + desc = "A bottle of pure darkness, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned from the shadows are indiscriminate in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon/shadow + category = "Summons" + limit = 3 + cost = 1 //Unless you blackout the station this ain't going to do much, wizard doesn't get NV, still dies easily to a group of 2 and it doesn't eat bodies. + /datum/spellbook_entry/item/mayhembottle name = "Mayhem in a Bottle" desc = "A magically infused bottle of blood, the scent of which will drive anyone nearby into a murderous frenzy." @@ -650,6 +658,11 @@ for(var/datum/spellbook_entry/item/hugbottle/HB in entries) if(!isnull(HB.limit)) HB.limit++ + else if(istype(O, /obj/item/antag_spawner/slaughter_demon/shadow)) + uses += 1 + for(var/datum/spellbook_entry/item/shadowbottle/SB in entries) + if(!isnull(SB.limit)) + SB.limit++ else uses += 2 for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 61fefcf1c0e..17a2f28fad9 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -195,27 +195,29 @@ used = FALSE to_chat(user, "The demons do not respond to your summon. Perhaps you should try again later.") + /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/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 - SSticker.mode.traitors += S.mind + var/mob/living/simple_animal/demon/demon = new demon_type(holder) + demon.vialspawned = TRUE + demon.holder = holder + demon.key = C.key + demon.mind.assigned_role = demon.name + demon.mind.special_role = demon.name + SSticker.mode.traitors += demon.mind var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate - KillDaWiz.owner = S.mind + KillDaWiz.owner = demon.mind KillDaWiz.target = user.mind KillDaWiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to summon you." - S.mind.objectives += KillDaWiz + demon.mind.objectives += KillDaWiz var/datum/objective/KillDaCrew = new /datum/objective - KillDaCrew.owner = S.mind + KillDaCrew.owner = demon.mind KillDaCrew.explanation_text = "[objective_verb] everyone else while you're at it." KillDaCrew.completed = TRUE - S.mind.objectives += KillDaCrew - to_chat(S, "Objective #[1]: [KillDaWiz.explanation_text]") - to_chat(S, "Objective #[2]: [KillDaCrew.explanation_text]") + demon.mind.objectives += KillDaCrew + to_chat(demon, "Objective #[1]: [KillDaWiz.explanation_text]") + to_chat(demon, "Objective #[2]: [KillDaCrew.explanation_text]") + /obj/item/antag_spawner/slaughter_demon/laughter name = "vial of tickles" @@ -229,6 +231,20 @@ objective_verb = "Hug and tickle" demon_type = /mob/living/simple_animal/demon/slaughter/laughter + +/obj/item/antag_spawner/slaughter_demon/shadow + name = "vial of shadow" + desc = "A magically infused bottle of pure darkness, distilled from \ + ground up shadowling bones. Used in dark rituals to attract \ + dark creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vialshadows" + veil_msg = "You sense a dark presence \ + lurking in the shadows..." + objective_verb = "Kill" + demon_type = /mob/living/simple_animal/demon/shadow + + ///////////MORPH /obj/item/antag_spawner/morph diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index 46e7b477af8..1be7a276cc1 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -241,7 +241,9 @@ GLOBAL_LIST_EMPTY(event_last_fired) new /datum/event_meta(EVENT_LEVEL_MAJOR, "Визит абдукторов",/datum/event/abductor, 20, list(ASSIGNMENT_SECURITY = 3), TRUE), // 5.8% on high pop, 4.5% on low pop new /datum/event_meta/alien(EVENT_LEVEL_MAJOR, "Заражение ксеноморфами", /datum/event/alien_infestation, 20, list(ASSIGNMENT_SECURITY = 4), TRUE), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Пауки Ужаса", /datum/event/spider_terror, 20, list(ASSIGNMENT_SECURITY = 4), TRUE), // 7.1% on high pop, 5.3% on low pop - new /datum/event_meta(EVENT_LEVEL_MAJOR, "Демон Резни", /datum/event/spawn_slaughter, 10, is_one_shot = TRUE), // 3% on high pop, 2.1% on low pop + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Демон Резни", /datum/event/spawn_slaughter, 20, is_one_shot = TRUE), // 3% on high pop, 2.1% on low pop + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Демон Смеха", /datum/event/spawn_slaughter/laughter, 20, is_one_shot = TRUE), + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Теневой Демон", /datum/event/spawn_slaughter/shadow, 20, is_one_shot = TRUE), //new /datum/event_meta(EVENT_LEVEL_MAJOR, "Floor Cluwne", /datum/event/spawn_floor_cluwne, 15, is_one_shot = TRUE) new /datum/event_meta(EVENT_LEVEL_MAJOR, "Космический Дракон", /datum/event/space_dragon, 20, list(ASSIGNMENT_SECURITY = 4), TRUE), ) diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm index a4d69b7bae8..d54072c2748 100644 --- a/code/modules/events/slaughterevent.dm +++ b/code/modules/events/slaughterevent.dm @@ -1,49 +1,70 @@ /datum/event/spawn_slaughter var/key_of_slaughter + var/mob/living/simple_animal/demon/demon = /mob/living/simple_animal/demon/slaughter -/datum/event/spawn_slaughter/proc/get_slaughter(var/end_if_fail = 0) - spawn() - var/list/candidates = SSghost_spawns.poll_candidates("Вы хотите занять роль Демона Резни?", ROLE_DEMON, TRUE, source = /mob/living/simple_animal/demon/slaughter) - if(!candidates.len) - key_of_slaughter = null - kill() - return - var/mob/C = pick(candidates) - key_of_slaughter = C.key - - if(!key_of_slaughter) - kill() - return - - var/datum/mind/player_mind = new /datum/mind(key_of_slaughter) - player_mind.active = 1 - var/list/spawn_locs = list() + +/datum/event/spawn_slaughter/proc/get_slaughter() + var/list/candidates = SSghost_spawns.poll_candidates("Вы хотите занять роль [initial(demon.name)]?", ROLE_DEMON, TRUE, source = demon) + if(!length(candidates)) + kill() + return + + var/mob/canidate = pick(candidates) + key_of_slaughter = canidate.key + + if(!key_of_slaughter) + kill() + return + + var/datum/mind/player_mind = new /datum/mind(key_of_slaughter) + player_mind.active = TRUE + 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/new_demon = new demon(holder) + new_demon.holder = holder + player_mind.transfer_to(new_demon) + player_mind.assigned_role = ROLE_DEMON + player_mind.special_role = SPECIAL_ROLE_DEMON + message_admins("[key_name_admin(new_demon)] has been made into a [new_demon.name] by an event.") + log_game("[key_name_admin(new_demon)] was spawned as a [new_demon.name] by an event.") + + +/datum/event/spawn_slaughter/proc/get_spawn_loc(mob/player) + RETURN_TYPE(/turf) + var/list/spawn_locs = list() + for(var/thing in GLOB.landmarks_list) + var/obj/effect/landmark/landmark = thing + if(isturf(landmark.loc) && landmark.name == "revenantspawn") + spawn_locs += landmark.loc + if(!spawn_locs) // If we can't find any good spots, try the carp spawns for(var/thing in GLOB.landmarks_list) - var/obj/effect/landmark/L = thing - if(isturf(L.loc)) - switch(L.name) - if("revenantspawn") - spawn_locs += L.loc - if(!spawn_locs) //If we can't find any revenant spawns, try the carp spawns - for(var/thing in GLOB.landmarks_list) - var/obj/effect/landmark/L = thing - if(isturf(L.loc)) - switch(L.name) - if("carpspawn") - spawn_locs += L.loc - if(!spawn_locs) //If we can't find either, 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 just retry - kill() - return - var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(pick(spawn_locs)) - var/mob/living/simple_animal/demon/slaughter/S = new(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)] выбран на роль Демона Резни по событию.") - add_game_logs("выбран на роль Демона Резни по событию.", S) + var/obj/effect/landmark/landmark = thing + if(isturf(landmark.loc) && landmark.name == "carpspawn") + spawn_locs += landmark.loc + if(!spawn_locs) //If we can't find a good place, just spawn at the player's location + spawn_locs += get_turf(player) + if(!spawn_locs) //If we can't find THAT, then give up + kill() + return + return pick(spawn_locs) + /datum/event/spawn_slaughter/start() - get_slaughter() + INVOKE_ASYNC(src, PROC_REF(get_slaughter)) + + +/datum/event/spawn_slaughter/laughter + demon = /mob/living/simple_animal/demon/slaughter/laughter + +/datum/event/spawn_slaughter/shadow + demon = /mob/living/simple_animal/demon/shadow + + +/datum/event/spawn_slaughter/shadow/get_spawn_loc() + var/turf/spawn_loc = ..() + for(var/turf/check in range(50, spawn_loc)) + if(check.get_lumcount()) // if the turf is not pitch black + continue + return check // return the first turf that is dark nearby. + kill() + diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index 101d167cca2..583644e5dbb 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 1eb28689431..b7aa3d59723 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index dacdbdf409f..f84d354ae37 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/obj/weapons/projectiles.dmi b/icons/obj/weapons/projectiles.dmi index fc333a4e6e5..e48d86495bd 100644 Binary files a/icons/obj/weapons/projectiles.dmi and b/icons/obj/weapons/projectiles.dmi differ diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi index cee6ad5438a..61d1672da52 100644 Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ diff --git a/paradise.dme b/paradise.dme index 93ceada0caf..51fd47b8ca3 100644 --- a/paradise.dme +++ b/paradise.dme @@ -710,6 +710,9 @@ #include "code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm" #include "code\game\gamemodes\miniantags\changeling_slug\changeling_slug.dm" #include "code\game\gamemodes\miniantags\changeling_slug\changeling_slug_event.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_demon.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" @@ -733,8 +736,6 @@ #include "code\game\gamemodes\miniantags\revenant\revenant_abilities.dm" #include "code\game\gamemodes\miniantags\revenant\revenant_spawn_event.dm" #include "code\game\gamemodes\miniantags\sintouched\objectives.dm" -#include "code\game\gamemodes\miniantags\slaughter\bloodcrawl.dm" -#include "code\game\gamemodes\miniantags\slaughter\slaughter.dm" #include "code\game\gamemodes\miniantags\space_dragon\space_dragon.dm" #include "code\game\gamemodes\nuclear\nuclear.dm" #include "code\game\gamemodes\nuclear\nuclear_challenge.dm"