diff --git a/modular_ss220/_defines220/code/antagonists.dm b/modular_ss220/_defines220/code/antagonists.dm index 9ec47589ae1a..d965d27e6c72 100644 --- a/modular_ss220/_defines220/code/antagonists.dm +++ b/modular_ss220/_defines220/code/antagonists.dm @@ -1,5 +1,6 @@ #define ROLE_BLOOD_BROTHER "blood brother" #define ROLE_VOX_RAIDER "vox raider" +#define ROLE_SHADOWLING "shadowling" #define VOX_RAID_FREQ 1220 diff --git a/modular_ss220/_defines220/code/gamemode.dm b/modular_ss220/_defines220/code/gamemode.dm index 85e57c57aa58..6fad373f856d 100644 --- a/modular_ss220/_defines220/code/gamemode.dm +++ b/modular_ss220/_defines220/code/gamemode.dm @@ -1,5 +1,8 @@ #define SPECIAL_ROLE_BLOOD_BROTHER "Blood Brother" #define SPECIAL_ROLE_VOX_RAIDER "Vox Raider" +#define SPECIAL_ROLE_SHADOWLING "Shadowling" +#define SPECIAL_ROLE_SHADOWLING_THRALL "Shadowling Thrall" +#define SPECIAL_ROLE_SHADOW_FATHER "Shadow Father" #define isvoxcash(W) (istype(W, /obj/item/stack/vox_cash)) diff --git a/modular_ss220/_defines220/code/species.dm b/modular_ss220/_defines220/code/species.dm index 611dbc273878..32bee4f27123 100644 --- a/modular_ss220/_defines220/code/species.dm +++ b/modular_ss220/_defines220/code/species.dm @@ -1,5 +1,8 @@ #define isnucleation(A) (is_species(A, /datum/species/nucleation)) +#define isshadowling(A) (is_species(A, /datum/species/shadow/ling)) +#define isshadowlinglesser(A) (is_species(A, /datum/species/shadow/ling/lesser)) + //MATERIAL CLASS FOR RACE EAT #define MATERIAL_CLASS_NONE 0 #define MATERIAL_CLASS_CLOTH 1 diff --git a/modular_ss220/antagonists/_antagonists.dm b/modular_ss220/antagonists/_antagonists.dm index ea08e2e1f0c0..b8a8944d8539 100644 --- a/modular_ss220/antagonists/_antagonists.dm +++ b/modular_ss220/antagonists/_antagonists.dm @@ -19,6 +19,9 @@ GLOB.huds += new/datum/atom_hud/antag() GLOB.special_roles |= ROLE_VOX_RAIDER + GLOB.huds += new/datum/atom_hud/antag() + GLOB.special_roles |= ROLE_SHADOWLING + SSradio.ANTAG_FREQS |= list(VOX_RAID_FREQ) SSradio.radiochannels |= list( diff --git a/modular_ss220/antagonists/_antagonists.dme b/modular_ss220/antagonists/_antagonists.dme index 95aa531b404b..832a1c5d1e2f 100644 --- a/modular_ss220/antagonists/_antagonists.dme +++ b/modular_ss220/antagonists/_antagonists.dme @@ -8,3 +8,24 @@ #include "code/configuration/antag_mix_configuration.dm" #include "code/mind/memory_edit.dm" #include "code/antag_mix/antag_mix.dm" +#include "code/shadowlings/shadowling_datum.dm" +#include "code/shadowlings/shadowling_species.dm" +#include "code/shadowlings/shadowling_language.dm" +#include "code/shadowlings/shadowling_eyes.dm" +#include "code/shadowlings/spells/shadowling_spell.dm" +#include "code/shadowlings/spells/shadowling_glare.dm" +#include "code/shadowlings/spells/shadowling_shadow_walk.dm" +#include "code/shadowlings/spells/shadowling_icy_veins.dm" +#include "code/shadowlings/spells/shadowling_screech.dm" +#include "code/shadowlings/spells/shadowling_blindness_smoke.dm" +#include "code/shadowlings/spells/shadowling_veil.dm" +#include "code/shadowlings/spells/shadowling_enthrall.dm" +#include "code/shadowlings/spells/shadowling_tear_the_reality.dm" +#include "code/shadowlings/spells/shadowling_rift_in.dm" +#include "code/shadowlings/spells/shadow_father_crawl.dm" +#include "code/shadowlings/shadowling_clothes.dm" +#include "code/shadowlings/spells/shadowling_hatch.dm" +#include "code/shadowlings/father_of_shadows.dm" +#include "code/shadowlings/shadowling_traps.dm" +#include "code/shadowlings/shadowling_gamemode.dm" +#include "code/shadowlings/shadowling_mob.dm" diff --git a/modular_ss220/antagonists/code/shadowlings/father_of_shadows.dm b/modular_ss220/antagonists/code/shadowlings/father_of_shadows.dm new file mode 100644 index 000000000000..b39c6de20569 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/father_of_shadows.dm @@ -0,0 +1,164 @@ +#define HEALTH_DECREASING_AMOUNT 70 +#define MAX_CONSUMED_CORPSES 4 + +/mob/living/simple_animal/demon/shadow_father // Can't inherit from demon/shadow because of 'initialize' proc + name = "отец теней" + desc = "Крупное демоническое существо, сотканное из теней. Из его красных глаз сочится потусторонняя энергия." + icon = 'modular_ss220/antagonists/icons/shadowlings/father_of_shadows.dmi' + icon_state = "father_of_shadows" + icon_living = "father_of_shadows" + 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") + move_resist = MOVE_FORCE_STRONG + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + see_in_dark = 10 + death_sound = 'sound/shadowdemon/shadowdeath.ogg' + speed = 1 + maxHealth = 350 + health = 350 + /// Used to check light exposure alert state + var/thrown_alert + /// Contain list of all traps placed by this shadow father + var/list/obj/structure/shadow_trap/placed_traps = list() + /// Used to check state in consumption logic + var/is_consuming = FALSE + /// Amount of bodies, consumed by the shadow father + var/consumed = 0 + /// Used to initialize hud + var/mind_initialized = FALSE + +/mob/living/simple_animal/demon/shadow_father/Login() + . = ..() + var/list/L = list( + "Вы Отец Тьмы!", + "Вы коварный и подлый антагонист, который должен подготовить эту станцию для прихода своих детей.", + "Свет - ваш худший враг, но вы не получаете от него урона, находясь в астральной форме.", + "Подготавливайте ловушки и засады в технических туннелях, после чего заманивайте в них жертв.", + "Погружайте в тень тела погибших членов экипажа, чтобы, в будущем, превратить их в своих детей.", + "Накопив достаточно погружённых, издайте свой последний вопль и призовите на станцию тенелингов.", + "", + "
Для подробной информации, посетите вики: [wiki_link("Shadowlings")]" + ) + to_chat(src, chat_box_red(L.Join("
"))) + if(!mind || mind_initialized) + return + mind_initialized = TRUE + var/datum/atom_hud/hud = GLOB.huds[ANTAG_HUD_SHADOW] + hud.add_hud_to(src) + mind.add_antag_datum(/datum/antagonist/shadow_father) + +/mob/living/simple_animal/demon/shadow_father/Initialize(mapload) + . = ..() + add_overlay(emissive_appearance(icon, "father_of_shadows_eye_glow_overlay")) + grant_spells() + +/mob/living/simple_animal/demon/shadow_father/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_father/death(gibbed) + for(var/trap in placed_traps) + qdel(trap) + INVOKE_ASYNC(SSticker.mode, TYPE_PROC_REF(/datum/game_mode, begin_shadowling_invasion), src, FALSE) + return ..(gibbed) + +/mob/living/simple_animal/demon/shadow_father/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", /atom/movable/screen/alert/lightexposure) + alpha = 255 + speed = initial(speed) + else + if(thrown_alert) + thrown_alert = FALSE + clear_alert("light") + alpha = 125 + speed = 0.5 + return lum_count + +/mob/living/simple_animal/demon/shadow_father/proc/grant_spells() + whisper_action.button_overlay_icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_actions.dmi' + whisper_action.button_overlay_icon_state = "father_whisper" + whisper_action.button_background_icon_state = "shadow_demon_bg" + + var/list/datum/spell/spells_to_grant = list( + new /datum/spell/bloodcrawl/shadow_crawl/father, + new /datum/spell/shadowling/veil, + new /datum/spell/shadowling/screech, + new /datum/spell/shadowling/glare, + new /datum/spell/shadowling/self/place_trap/stun, + new /datum/spell/shadowling/self/place_trap/poison, + new /datum/spell/shadowling/self/place_trap/blindness, + new /datum/spell/shadowling/self/tear_the_reality, + ) + for(var/datum/spell/spell in spells_to_grant) + AddSpell(spell) + +/mob/living/simple_animal/demon/shadow_father/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_father/UnarmedAttack(atom/A) + // Prevent self-attack + if(A == src) + return + // Pick a random attack sound for each attack + attack_sound = pick('sound/shadowdemon/shadowattack2.ogg', 'sound/shadowdemon/shadowattack3.ogg', 'sound/shadowdemon/shadowattack4.ogg') + if(!ishuman(A)) + if(isitem(A)) + A.extinguish_light() + return ..() + var/mob/living/carbon/human/target = A + if(target.stat != DEAD) + return ..() + + if(isLivingSSD(target) && client.send_ssd_warning(target) || isnull(target.mind)) + return + + if(is_consuming) + to_chat(src, span_notice("Мы уже поглощаем кого-то.")) + return + + if(consumed == MAX_CONSUMED_CORPSES) + return + + visible_message(span_danger("[src] начинает окутывать [target] теневой пеленой!")) + is_consuming = TRUE + playsound(src, 'modular_ss220/antagonists/sound/shadowlings/shadow_consumption_start.ogg', 50, TRUE) + if(!do_after(src, 15 SECONDS, FALSE, target = target)) + is_consuming = FALSE + return + + target.visible_message(span_danger("[src] полностью окутывает [target] тенями и тело исчезает в потусторонней пелене! [src], похоже, стал слабее!")) + qdel(target) + playsound(src, 'modular_ss220/antagonists/sound/shadowlings/shadow_consumption_end.ogg', 50, TRUE) + maxHealth -= HEALTH_DECREASING_AMOUNT + health = clamp(health, 0, maxHealth) + consumed++ + if(consumed < MAX_CONSUMED_CORPSES) + to_chat(src, span_purple("Мы успешно погрузили тело жервты во тьму. Мы слабеем, но теперь после нашей смерти в мир явится больше тенелингов. Мы можем поглотить ещё [MAX_CONSUMED_CORPSES - consumed] тел.")) + else + to_chat(src, span_purple("Это была последняя жертва. Всё что мы теперь можем сделать, это начать прорыв завесы.")) + is_consuming = FALSE + +#undef HEALTH_DECREASING_AMOUNT +#undef MAX_CONSUMED_CORPSES diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_clothes.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_clothes.dm new file mode 100644 index 000000000000..7bc1230f1b98 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_clothes.dm @@ -0,0 +1,81 @@ +/obj/item/clothing/under/shadowling + name = "почерневшая плоть" + desc = "Чёрная хитинистая кожа с выступающими красными венами." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_uniform" + origin_tech = null + flags = NODROP | DROPDEL + has_sensor = FALSE + displays_id = FALSE + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + + +/obj/item/clothing/suit/space/shadowling + name = "теневой панцирь" + desc = "Тёмный полупросрачный панцирь, защищающий тенелинга от урона и немного от космоса." //Still takes damage from spacewalking but is immune to space itself + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_suit" + icon_override = 'icons/mob/clothing/underwear.dmi' // It's required to avoid 'suit' test icon + body_parts_covered = FULL_BODY //Shadowlings are immune to space + cold_protection = FULL_BODY + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEGLOVES | HIDESHOES | HIDEJUMPSUIT + flags = NODROP | DROPDEL + slowdown = 0 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + heat_protection = null //You didn't expect a light-sensitive creature to have heat resistance, did you? + max_heat_protection_temperature = null + armor = list(melee = 25, bullet = 25, laser = 0, energy = 10, bomb = 25, bio = 100, rad = 100, fire = 100, acid = 100) + + +/obj/item/clothing/shoes/shadowling + name = "теневые крюки" + desc = "Угольно-чёрные крюки, расположенные на ногах тенелингов." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_shoes" + resistance_flags = LAVA_PROOF|FIRE_PROOF|ACID_PROOF + flags = NODROP | DROPDEL + no_slip = TRUE + +/obj/item/clothing/mask/gas/shadowling + name = "теневая маска" + desc = "Тёмная хитиновая маска, располагающаяся на лицах тенелингов." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_mask" + siemens_coefficient = 0 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flags_cover = MASKCOVERSEYES + flags = NODROP | DROPDEL + +/obj/item/clothing/gloves/shadowling + name = "теневые наручи" + desc = "Плотной слой теневой материи, защищающий руки тенелинга от и электричества." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_gloves" + siemens_coefficient = 0 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flags = NODROP | DROPDEL + +/obj/item/clothing/head/shadowling + name = "теневой шлем" + desc = "Шлемообразный защитный панцирь, расположенный на голове тенелинга." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_helmet" + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flags_cover = HEADCOVERSEYES //We don't need to cover mouth + flags = NODROP | DROPDEL | BLOCKHAIR + +/obj/item/clothing/glasses/shadowling + name = "багровые линзы" + desc = "Небольшие красные мембраны, защищающие уязвимые глаза тенелингов." + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi' + icon_state = "shadowling_glasses" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flash_protect = FLASH_PROTECTION_SENSITIVE + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + flags = NODROP | DROPDEL diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_datum.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_datum.dm new file mode 100644 index 000000000000..b7126c42c722 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_datum.dm @@ -0,0 +1,32 @@ +/datum/antagonist/shadowling + name = "Shadowling" + antag_hud_type = ANTAG_HUD_SHADOW + antag_hud_name = "hudshadowling" + job_rank = ROLE_SHADOWLING + special_role = SPECIAL_ROLE_SHADOWLING + wiki_page_name = "Shadowlings" + +/datum/antagonist/shadow_father + name = "Shadow Father" + antag_hud_type = ANTAG_HUD_SHADOW + antag_hud_name = "hudshadowfather" + job_rank = ROLE_SHADOWLING // I don't change it, so there is no different database line for shadowling roles + special_role = SPECIAL_ROLE_SHADOW_FATHER + wiki_page_name = "Shadowlings" + +/datum/antagonist/shadowling_thrall + name = "Shadowling Thrall" + antag_hud_type = ANTAG_HUD_SHADOW + antag_hud_name = "hudshadowlingthrall" + job_rank = ROLE_SHADOWLING // I don't change it, so there is no different database line for shadowling roles + special_role = SPECIAL_ROLE_SHADOWLING_THRALL + wiki_page_name = "Shadowlings" + +/proc/is_thrall(var/mob/living/M) + return TRUE + +/proc/is_shadow_or_thrall(var/mob/living/M) + return TRUE + +/proc/is_shadow(var/mob/living/M) + return TRUE diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_eyes.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_eyes.dm new file mode 100644 index 000000000000..89dae4a4c1e4 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_eyes.dm @@ -0,0 +1,6 @@ +// Changing the organ usage action background +/obj/item/organ/internal/eyes/night_vision/nightmare + actions_types = list(/datum/action/item_action/organ_action/use/shadowling) + +/datum/action/item_action/organ_action/use/shadowling + button_background_icon_state = "shadow_demon_bg" diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_gamemode.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_gamemode.dm new file mode 100644 index 000000000000..8394f1e81533 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_gamemode.dm @@ -0,0 +1,31 @@ +/datum/game_mode + var/list/datum/mind/shadowlings = list() + var/list/datum/mind/shadowling_thralls = list() + var/list/datum/mind/shadow_fathers = list() + var/victory_warning_announced = FALSE + +/// Called when shadow father is killed or when special spell is used +/datum/game_mode/proc/begin_shadowling_invasion(mob/living/simple_animal/demon/shadow_father/father, spell_used) + // Spawning father as shadowling + if(spell_used) + var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(father.loc) + var/mob/living/carbon/human/shadowling/ling = new /mob/living/carbon/human/shadowling(holder) + ling.AddSpell(new /datum/spell/shadowling/self/rift_in) + ling.key = father.key + else + father.consumed++ //At least one shadowling + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Вы хотите поиграть за тенелинга?", ROLE_SHADOWLING, TRUE, 25 SECONDS, source = /mob/living/simple_animal/demon/shadow_father, role_cleanname = "Shadowling") + while(father.consumed > 0) + if(candidates.len < 1) + break + var/player_to_spawn + var/mob/dead/observer/ghost = pick(candidates) + candidates.Remove(ghost) + player_to_spawn = ghost.key + var/obj/effect/dummy/slaughter/holder = new /obj/effect/dummy/slaughter(father.loc) + var/mob/living/carbon/human/shadowling/ling = new /mob/living/carbon/human/shadowling(holder) + ling.AddSpell(new /datum/spell/shadowling/self/rift_in) + dust_if_respawnable(ghost) + ling.key = player_to_spawn + father.consumed-- + diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_language.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_language.dm new file mode 100644 index 000000000000..25ea1cbf3fd9 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_language.dm @@ -0,0 +1,18 @@ +/datum/language/shadowling + name = "Шёпот из тени" + desc = "В тени всегда есть те, кто слышит все ваши секреты. Шадоулинги используют рэдспейс для связи между собой." + speech_verb = "says" + colour = "purple" + key = "sl" + flags = RESTRICTED | HIVEMIND | NOBABEL + follow = TRUE + +/datum/language/shadowling/get_random_name() + var/new_name + if(prob(0.1)) + new_name = "Лучик Доброты" // :) + else + // Edgelord names ahead + new_name += "[pick("Владыка", "Поработитель", "Повелитель", "Мучитель", "Пожиратель", "Угнетатель", "Судья", "Убийца", "Уничтожитель", "Тиран")]" + new_name += " [pick("Тьмы", "Тени", "Темноты", "Ужаса", "Страха", "Ненависти", "Зависти", "Злости", "Греха", "Мрака", "Боли", "Кары")]" + return new_name diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_mob.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_mob.dm new file mode 100644 index 000000000000..087b254f914b --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_mob.dm @@ -0,0 +1,3 @@ +// Not subtype of /mob/living/carbon/human/shadow because of weird parent proc call +/mob/living/carbon/human/shadowling/Initialize(mapload) + . = ..(mapload, /datum/species/shadow/ling) diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_species.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_species.dm new file mode 100644 index 000000000000..1c5c511e0283 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_species.dm @@ -0,0 +1,74 @@ +#define LIGHT_DAM_THRESHOLD 4 +#define LIGHT_HEAL_THRESHOLD 2 +#define LIGHT_DAMAGE_TAKEN 6 + +/datum/species/shadow/ling + //Normal shadowpeople but with enhanced effects + name = "Shadowling" + name_plural = "Shadowlings" + + default_language = "Шёпот из тени" + language = "Шёпот из тени" + icobase = 'modular_ss220/antagonists/icons/shadowlings/r_shadowling.dmi' + blacklisted = TRUE + + blood_color = "#555555" + flesh_color = "#222222" + + species_traits = list(NO_BLOOD, NO_HAIR, NOT_SELECTABLE) + inherent_traits = list(TRAIT_RESISTHEAT, TRAIT_NOBREATH, TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE, TRAIT_CHUNKYFINGERS, TRAIT_NOPAIN, TRAIT_NO_BONES, TRAIT_STURDY_LIMBS, TRAIT_XENO_IMMUNE, TRAIT_NOHUNGER) + + burn_mod = 1.25 + heatmod = 1.5 + +/datum/species/shadow/ling/proc/handle_light(mob/living/carbon/human/H) + var/light_amount = 0 + if(isturf(H.loc)) + var/turf/T = H.loc + light_amount = T.get_lumcount() * 10 + if(light_amount > LIGHT_DAM_THRESHOLD && !H.incorporeal_move) //Can survive in very small light levels. Also doesn't take damage while incorporeal, for shadow walk purposes + H.throw_alert("lightexposure", /atom/movable/screen/alert/lightexposure) + if(is_species(H, /datum/species/shadow/ling/lesser)) + H.take_overall_damage(0, LIGHT_DAMAGE_TAKEN/2) + else + H.take_overall_damage(0, LIGHT_DAMAGE_TAKEN) + if(H.stat != DEAD) + to_chat(H, span_userdanger("Свет жжёт вас!")) + H << 'sound/weapons/sear.ogg' + else if(light_amount < LIGHT_HEAL_THRESHOLD) + H.clear_alert("lightexposure") + var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes) + if(istype(E)) + E.receive_damage(-1) + if(is_species(H, /datum/species/shadow/ling/lesser)) + H.heal_overall_damage(2, 3) + else + H.heal_overall_damage(5, 7) + H.adjustToxLoss(-5) + H.adjustBrainLoss(-25) + H.AdjustEyeBlurry(-2 SECONDS) + H.adjustCloneLoss(-1) + H.SetWeakened(0) + H.SetStunned(0) + + else + if(H.health <= HEALTH_THRESHOLD_CRIT) // to finish shadowlings in rare occations + H.adjustBruteLoss(1) + +/datum/species/shadow/ling/handle_life(mob/living/carbon/human/H) + ..(H) + handle_light(H) + +/datum/species/shadow/ling/lesser //Empowered thralls. Obvious, but powerful + name = "Lesser Shadowling" + + icobase = 'modular_ss220/antagonists/icons/shadowlings/r_lshadowling.dmi' + blood_color = "#CCCCCC" + flesh_color = "#AAAAAA" + + burn_mod = 1.1 + heatmod = 1.2 + +#undef LIGHT_DAM_THRESHOLD +#undef LIGHT_HEAL_THRESHOLD +#undef LIGHT_DAMAGE_TAKEN diff --git a/modular_ss220/antagonists/code/shadowlings/shadowling_traps.dm b/modular_ss220/antagonists/code/shadowlings/shadowling_traps.dm new file mode 100644 index 000000000000..6bf24607288b --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/shadowling_traps.dm @@ -0,0 +1,183 @@ +#define MAX_SHADOWLING_TRAPS 6 + +/obj/structure/shadow_trap + name = "тёмное пятно" + desc = "Большое тёмное пятно на полу, стенах и потолке. Вы не уверены что это такое, но лучше держаться подальше." + var/trap_adjective = "сломанная" + var/hud_icon_state = "broken" + icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_traps.dmi' + icon_state = "trap" + anchored = TRUE + opacity = FALSE + layer = ABOVE_OBJ_LAYER + max_integrity = 50 + hud_possible = list(SPECIALROLE_HUD) + + var/created_by + +/obj/structure/shadow_trap/Initialize(mapload) + . = ..() + prepare_huds() + +/// This one is used to change path to special role hud icon file for modular behavior +/obj/structure/shadow_trap/prepare_huds() + ..() + // Changing path to special_role HUD + var/image/I = image('modular_ss220/antagonists/icons/shadowlings/shadowlings_traps.dmi', src, "") + I.appearance_flags = RESET_COLOR | RESET_TRANSFORM + hud_list[SPECIALROLE_HUD] = I + // Toggle visibility + var/datum/atom_hud/hud = GLOB.huds[ANTAG_HUD_SHADOW] + hud.add_to_hud(src) + // Changing icon_state of holder + I.icon_state = hud_icon_state + + +/obj/structure/shadow_trap/Crossed(atom/movable/AM, oldloc) + . = ..() + if(is_shadow_or_thrall(AM)) + return + if(!trap_activate(AM)) // Returns false if trap isn't triggered by this type + return + if(created_by) + to_chat(created_by, span_purple("Ваша [trap_adjective] ловушка сработала на [AM] в [get_turf(AM.loc)]")) + else + created_by = "Admin spawn" + add_attack_logs(AM, AM, "activated shadowling trap placed by [created_by]", ATKLOG_ALL) + Destroy() + +/obj/structure/shadow_trap/proc/trap_activate(mob/living/target) + return + +/obj/structure/shadow_trap/examine(mob/user) + . = ..() + if(is_shadow_or_thrall(user)) + . += "Это [trap_adjective] ловушка." + +/obj/structure/shadow_trap/Destroy() + if(!istype(created_by, /mob/living/simple_animal/demon/shadow_father)) + return ..() + var/mob/living/simple_animal/demon/shadow_father/father = created_by + father.placed_traps.Remove(src) + . = ..() + +// Spell + +/datum/spell/shadowling/self/place_trap + name = "Установить ловушку" + action_icon_state = "vampire_glare" + base_cooldown = 30 SECONDS + stat_allowed = UNCONSCIOUS + var/trap_type = /obj/structure/shadow_trap + +/datum/spell/shadowling/self/place_trap/can_cast(mob/user, charge_check, show_message) + . = ..() + if(!istype(user, /mob/living/simple_animal/demon/shadow_father)) + to_chat(user, span_warning("Вы должны быть отцом тьмы для установки ловушек.")) + return FALSE + var/mob/living/simple_animal/demon/shadow_father/father = user + if(ismob(father.pulling)) + to_chat(user, span_warning("Вы не можете устанавливать ловушки, пока тащите кого-то.")) + return FALSE + if(father.placed_traps.len >= MAX_SHADOWLING_TRAPS) + to_chat(user, span_warning("Вы построили уже построили максимум ([MAX_SHADOWLING_TRAPS]) ловушек. Разрушьте старые для установки новых.")) + return FALSE + return + +/datum/spell/shadowling/self/place_trap/cast(list/targets, mob/user) + var/mob/living/simple_animal/demon/shadow_father/father = user + var/obj/structure/shadow_trap/trap = new trap_type(get_turf(father)) + father.placed_traps.Add(trap) + trap.created_by = father + + for(var/datum/spell/shadowling/self/place_trap/spell in user.mob_spell_list) + if(spell != src) + spell.cooldown_handler.start_recharge() + +// Stun Trap +// Disable headset, stun for 6 seconds and disable light + +/obj/structure/shadow_trap/stun + trap_adjective = "оглущающая" + hud_icon_state = "stun" + +/obj/structure/shadow_trap/stun/trap_activate(mob/living/target) + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + to_chat(human_target, span_boldwarning("Вас что-то схватило за ногу!")) + human_target.emote("scream") + human_target.KnockDown(6 SECONDS) + human_target.Confused(7 SECONDS) + human_target.extinguish_light() + for(var/obj/item/headset in human_target.contents) + if(istype(headset, /obj/item/radio)) + headset.emp_act() + return TRUE + if(isrobot(target)) + var/mob/living/silicon/robot/robot_target = target + to_chat(robot_target, span_boldwarning("ОШИБКА: Неизвестное проникновение в п$о%#@*&...")) + robot_target << 'sound/misc/interference.ogg' + playsound(robot_target, 'sound/machines/warning-buzzer.ogg', 50, TRUE) + do_sparks(5, 1, robot_target) + robot_target.extinguish_light() + robot_target.Weaken(6 SECONDS) + return TRUE + return FALSE + +/datum/spell/shadowling/self/place_trap/stun + name = "Установить оглущающую ловушку" + trap_type = /obj/structure/shadow_trap/stun + action_icon_state = "stun_trap" + +// Poison Trap +// Deal burn + toxin damage to target and force them to sleep + +/obj/structure/shadow_trap/poison + trap_adjective = "отравляющая" + hud_icon_state = "poison" + +/obj/structure/shadow_trap/poison/trap_activate(mob/living/target) + if(!ishuman(target)) + return FALSE + if(ismachineperson(target)) + return FALSE + if(target.reagents) + to_chat(target, span_boldwarning("Вас что-то ужалило за ногу!")) + target.reagents.add_reagent("frostoil", 30) + target.reagents.add_reagent("neurotoxin", 30) + return TRUE + return FALSE + +/datum/spell/shadowling/self/place_trap/poison + name = "Установить ядовитую ловушку" + trap_type = /obj/structure/shadow_trap/poison + action_icon_state = "poison_trap" + +// Blindness Trap +// Deal damage to eyes and create blindsmoke cloud + +/obj/structure/shadow_trap/blindness + trap_adjective = "ослепляющая" + hud_icon_state = "blindness" + +/obj/structure/shadow_trap/blindness/trap_activate(mob/living/target) + if(!ishuman(target)) + return FALSE + to_chat(target, span_boldwarning("С потолка на вас капает чёрная вязкая слизь. Она начинает выделять огромное колличество чёрного дыма!")) + playsound(src, 'sound/effects/bamf.ogg', 50, TRUE) + var/datum/reagents/reagents_list = new (1000) + reagents_list.add_reagent("blindness_smoke", 810) + var/datum/effect_system/smoke_spread/chem/chem_smoke = new + chem_smoke.set_up(reagents_list, loc, TRUE) + chem_smoke.start(4) + if(target.reagents) + target.reagents.add_reagent("blindness_smoke", 30) + var/obj/item/organ/internal/eyes/eyes = target.get_int_organ(/obj/item/organ/internal/eyes) + eyes.receive_damage(15, 1) + return TRUE + +/datum/spell/shadowling/self/place_trap/blindness + name = "Установить ослепляющую ловушку" + trap_type = /obj/structure/shadow_trap/blindness + action_icon_state = "blindness_trap" + diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadow_father_crawl.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadow_father_crawl.dm new file mode 100644 index 000000000000..6ce0bab64366 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadow_father_crawl.dm @@ -0,0 +1,4 @@ +// Can be used even while on light + +/datum/spell/bloodcrawl/shadow_crawl/father/valid_target(turf/target, user) + return TRUE diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_blindness_smoke.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_blindness_smoke.dm new file mode 100644 index 000000000000..4d70233b3fed --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_blindness_smoke.dm @@ -0,0 +1,39 @@ +/datum/spell/shadowling/self/blindness_smoke + name = "Ослепляющий дым" + desc = "Вы выдыхаете облако густого ослепляющего врагов дыма, однако лечащего лояльных тьме слуг." + base_cooldown = 30 SECONDS + stat_allowed = UNCONSCIOUS + action_icon_state = "blindness_smoke" + + +/datum/spell/shadowling/self/blindness_smoke/cast(list/targets, mob/user) + user.visible_message(span_warning("[user] внезапно выдыхает облако чёрного дыма, который начинает быстро распространяться!")) + playsound(user, 'sound/effects/bamf.ogg', 50, TRUE) + var/datum/reagents/reagents_list = new (1000) + reagents_list.add_reagent("blindness_smoke", 810) + var/datum/effect_system/smoke_spread/chem/chem_smoke = new + chem_smoke.set_up(reagents_list, user.loc, TRUE) + chem_smoke.start(4) + +/datum/reagent/shadowling_blindness_smoke //Blinds non-shadowlings, heals shadowlings/thralls + name = "odd black liquid" + id = "blindness_smoke" + description = "<::ОШИБКА::> НЕВОЗМОЖНО ПРОАНАЛИЗИРОВАТЬ РЕАГЕНТ <::ОШИБКА::>" + color = "#000000" //Complete black (RGB: 0, 0, 0) + metabolization_rate = 250 * REAGENTS_METABOLISM //still lel + + +/datum/reagent/shadowling_blindness_smoke/on_mob_life(mob/living/M) + var/update_flags = STATUS_UPDATE_NONE + if(!is_shadow_or_thrall(M)) + to_chat(M, span_boldwarning("Вы вдыхаете чёрный дым и ваши глаза начинают ужасно болеть!")) + M.EyeBlind(10 SECONDS) + if(prob(25)) + M.visible_message(span_warning("[M] трёт свои глаза.")) + M.Stun(4 SECONDS) + else + to_chat(M, span_notice("Вы вдыхаете чёрный дым и чувствуете облегчение!")) + update_flags |= M.heal_organ_damage(10, 10, updating_health = FALSE) + update_flags |= M.adjustOxyLoss(-10, FALSE) + update_flags |= M.adjustToxLoss(-10, FALSE) + return ..() | update_flags diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_enthrall.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_enthrall.dm new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_glare.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_glare.dm new file mode 100644 index 000000000000..a9ef5fc4332d --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_glare.dm @@ -0,0 +1,106 @@ +// Vampire glare with some additions + +/// No deviation at all. Flashed from the front or front-left/front-right. Alternatively, flashed in direct view. +#define DEVIATION_NONE 3 +/// Partial deviation. Flashed from the side. Alternatively, flashed out the corner of your eyes. +#define DEVIATION_PARTIAL 2 +/// Full deviation. Flashed from directly behind or behind-left/behind-rack. Not flashed at all. +#define DEVIATION_FULL 1 + +/// Light level, required to apply additional effects +#define GLARE_REQUIRED_DARKNESS 4 + +/datum/spell/shadowling/glare + name = "Блик" + desc = "Ваши красные глаза сверкают, завораживая и очаровывая смертных перед вами. Для лучшего эффекта необходим близкий зрительный контакт и тёмное окружение." + base_cooldown = 40 SECONDS + stat_allowed = UNCONSCIOUS + action_icon_state = "glare" + +/datum/spell/shadowling/glare/can_cast(mob/user, charge_check, show_message) + . = ..() + // Veil shouldn't work in shadow crawl + if(istype(user.loc, /obj/effect/dummy/slaughter)) + return FALSE + +/datum/spell/shadowling/glare/create_new_targeting() + var/datum/spell_targeting/aoe/T = new + T.allowed_type = /mob/living + T.range = 1 + return T + +/datum/spell/shadowling/glare/create_new_cooldown() + var/datum/spell_cooldown/charges/C = new + C.max_charges = 2 + C.recharge_duration = base_cooldown + C.charge_duration = 2 SECONDS + return C + +/datum/spell/shadowling/glare/proc/calculate_deviation(mob/victim, mob/attacker) + + // If the victim was looking at the attacker, this is the direction they'd have to be facing. + var/attacker_to_victim = get_dir(attacker, victim) + // The victim's dir is necessarily a cardinal value. + var/attacker_dir = attacker.dir + + // - - - + // - V - Attacker facing south + // # # # + // Attacker within 45 degrees of where the victim is facing. + if(attacker_dir & attacker_to_victim) + return DEVIATION_NONE + // Are they on the same tile? This is probably the victim crawling under the vampire, and looking down shouldn't be too tough. + if(victim.loc == attacker.loc) + return DEVIATION_NONE + // # # # + // - V - Attacker facing south + // - - - + // Victim at 135 or more degrees of where the victim is facing. + if(attacker_dir & REVERSE_DIR(attacker_to_victim)) + return DEVIATION_FULL + // - - - + // # V # Attacker facing south + // - - - + // Victim lateral to the victim. + return DEVIATION_PARTIAL + +/datum/spell/shadowling/glare/cast(list/targets, mob/living/user = usr) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(istype(H.glasses, /obj/item/clothing/glasses/sunglasses/blindfold)) + var/obj/item/clothing/glasses/sunglasses/blindfold/B = H.glasses + if(B.tint) + to_chat(user, span_warning("Ваши глаза закрыты! Взгляд не работает!")) + return + user.mob_light(LIGHT_COLOR_BLOOD_MAGIC, 3, _duration = 2) + user.visible_message(span_warning("Вы используйете очарование своих глаз, вводя в замешательство окружающих")) + + for(var/mob/living/target in targets) + if(shadowling_check(target)) + continue + + var/deviation + if(IS_HORIZONTAL(user)) + deviation = DEVIATION_PARTIAL + else + deviation = calculate_deviation(target, user) + if(deviation == DEVIATION_FULL) + target.Confused(6 SECONDS) + target.apply_damage(20, STAMINA) + else if((deviation == DEVIATION_PARTIAL) || (get_light_level(target) > GLARE_REQUIRED_DARKNESS)) + target.KnockDown(5 SECONDS) + target.Confused(6 SECONDS) + target.apply_damage(40, STAMINA) + else + target.Confused(10 SECONDS) + target.apply_damage(70, STAMINA) + target.KnockDown(12 SECONDS) + target.AdjustSilence(8 SECONDS) + target.flash_eyes(1, TRUE, TRUE) + to_chat(target, span_warning("Глаза [user] источают чарующую красную ауру. Ваше тело слабеет...")) + add_attack_logs(user, target, "(Shadowling) Glared at") + +#undef DEVIATION_NONE +#undef DEVIATION_PARTIAL +#undef DEVIATION_FULL +#undef GLARE_REQUIRED_DARKNESS diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_hatch.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_hatch.dm new file mode 100644 index 000000000000..bc9d979c9a31 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_hatch.dm @@ -0,0 +1,125 @@ +/obj/structure/alien/resin/wall/shadowling + name = "Теневая стена" + desc = "Стена из дымящейся чёрной материи, напоминающей смолу. Выглядит неразрушимой." + max_integrity = INFINITY + +/datum/spell/shadowling/self/hatch + name = "Вылупиться" + desc = "Вы окружаете себя теневым коконом и разрываете оболочку, обнажая свою истинную форму. Лучше использовать в укромном месте" + stat_allowed = CONSCIOUS + base_cooldown = 5 MINUTES + action_icon_state = "ascend" + +/datum/spell/shadowling/self/hatch/can_cast(mob/user, charge_check, show_message) + if(!ishuman(user)) + return FALSE + if(!isturf(user.loc)) + return FALSE + if(istype(user.loc, /turf/space)) + return FALSE + . = ..() + +/datum/spell/shadowling/self/hatch/cast(list/targets, mob/user) + . = ..() + var/mob/living/carbon/human/shadowling = user + if(!istype(shadowling)) + return FALSE + var/result = tgui_alert(shadowling, "Вы уверены что хотите скинуть оболочку? Это займёт некоторое время и после этого всем станет ясна ваша сущность.", "Вылупиться?", list("Да", "Нет")) + if(result != "Да") + return FALSE + + for(var/obj/item/item in shadowling.get_equipped_items(include_pockets = TRUE)) + shadowling.unEquip(item, TRUE) + + sleep(5 SECONDS) + + // Creating cocoon + var/turf/simulated/floor/F + var/turf/shadowturf = get_turf(shadowling) + for(F in orange(1, shadowling)) + new /obj/structure/alien/resin/wall/shadowling(F) + + for(var/obj/structure/alien/resin/wall/shadowling/R in shadowturf) + qdel(R) + + // Temporal godmode and stun for shadowling + + shadowling.dna.species.brute_mod = 0; + shadowling.dna.species.burn_mod = 0; + shadowling.dna.species.tox_mod = 0; + shadowling.dna.species.oxy_mod = 0; + shadowling.dna.species.clone_mod = 0; + shadowling.dna.species.brain_mod = 0; + shadowling.Stun(INFINITY) // Until hatching complete + + shadowling.visible_message( + span_warning("Тёмная мембрана образуется вокруг [shadowling]. Изнутри начинают доноситься странные булькающие звуки.."), + span_notice("Вы начинаете свою метаморфозу. Внутри этого кокона вы вскоре избавитесь от ограничевающей вас оболочки.") + ) + sleep(10 SECONDS) + + shadowling.visible_message( + span_warning("Кожа [shadowling] пузырится и неествественно извивается, постепенно отваливаясь целыми лоскутами. За ней виднеется чёрная плоть."), + span_notice("Кожа начинает постепенно сползать с вас. Вы чувствуете как ваша истинная сила возвращается к вам.") + ) + sleep(10 SECONDS) + + sleep(8 SECONDS) + playsound(shadowling.loc, 'sound/weapons/slash.ogg', 25, TRUE) + to_chat(shadowling, span_purple("Вы начинаете вырываться из кокона.")) + + sleep(1 SECONDS) + playsound(shadowling.loc, 'sound/weapons/slashmiss.ogg', 25, TRUE) + to_chat(shadowling, span_purple("Кокон рушится под вашими ударами.")) + + sleep(1 SECONDS) + playsound(shadowling.loc, 'sound/weapons/slice.ogg', 25, TRUE) + to_chat(shadowling, span_purple("ВЫ СВОБОДНЫ!")) + + sleep(1 SECONDS) + playsound(shadowling.loc, 'sound/effects/ghost.ogg', 50, TRUE) + + // Removing cocoon + for(var/obj/structure/alien/resin/wall/shadowling/resin in orange(shadowling, 1)) + playsound(resin, 'sound/effects/splat.ogg', 50, TRUE) + qdel(resin) + + for(var/obj/structure/alien/weeds/node/node in shadowturf) + qdel(node) + + // Transformation + shadowling.set_species(/datum/species/shadow/ling) + + shadowling.equip_to_slot_or_del(new /obj/item/clothing/under/shadowling(user), SLOT_HUD_JUMPSUIT) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/shoes/shadowling(user), SLOT_HUD_SHOES) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/suit/space/shadowling(user), SLOT_HUD_OUTER_SUIT) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/head/shadowling(user), SLOT_HUD_HEAD) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/gloves/shadowling(user), SLOT_HUD_GLOVES) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/shadowling(user), SLOT_HUD_WEAR_MASK) + shadowling.equip_to_slot_or_del(new /obj/item/clothing/glasses/shadowling(user), SLOT_HUD_GLASSES) + + shadowling.name = random_name(MALE, "Shadowling") + shadowling.real_name = shadowling.name + shadowling.SetStunned(0) + shadowling.underwear = "Nude" + shadowling.undershirt = "Nude" + shadowling.socks = "Nude" + + shadowling.ExtinguishMob() + shadowling.set_nutrition(NUTRITION_LEVEL_FED + 50) + + // Granting spells + var/list/datum/spell/spells_to_grant = list( + new /datum/spell/shadowling/glare, + new /datum/spell/shadowling/veil, + new /datum/spell/shadowling/screech, + new /datum/spell/shadowling/icy_veins, + // TODO + // new /datum/spell/shadowling/entrall, + new /datum/spell/shadowling/self/blindness_smoke, + new /datum/spell/shadowling/self/shadow_walk + ) + for(var/datum/spell/spell in spells_to_grant) + shadowling.AddSpell(spell) + + shadowling.RemoveSpell(src) diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_icy_veins.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_icy_veins.dm new file mode 100644 index 000000000000..d003a2bf1043 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_icy_veins.dm @@ -0,0 +1,28 @@ +/datum/spell/shadowling/icy_veins + name = "Стынущие жилы" + desc = "Вы замораживаете кровь в венах окружающих вас врагов в радиусе 4-х клеток, замедляя их и нанося им урон от холода." + base_cooldown = 35 SECONDS + stat_allowed = UNCONSCIOUS + action_icon_state = "icy_veins" + +/datum/spell/shadowling/icy_veins/create_new_targeting() + var/datum/spell_targeting/aoe/T = new + T.allowed_type = /mob/living + T.range = 4 + return T + +/datum/spell/shadowling/icy_veins/cast(list/targets, mob/living/user = usr) + to_chat(user, span_purple("Вы посылаете волну холода.")) + playsound(user.loc, 'sound/effects/ghost2.ogg', 50, TRUE) + + for(var/mob/living/carbon/target in targets) + if(shadowling_check(target)) + to_chat(target, span_warning("Вы чувствуете волну холода, но для вас она безвредна")) + continue + to_chat(target, span_danger("Вас пронзает волна холода! У вас буквально стынет кровь в жилах!")) + target.Stun(2 SECONDS) + target.apply_damage(10, BURN) + target.adjust_bodytemperature(-200) //Extreme amount of initial cold + if(target.reagents) + target.reagents.add_reagent("frostoil", 15) //Half of a cryosting + diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_rift_in.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_rift_in.dm new file mode 100644 index 000000000000..6e4fddd1fb87 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_rift_in.dm @@ -0,0 +1,14 @@ +/datum/spell/shadowling/self/rift_in + name = "Проникнуть через разлом" + desc = "Вы проникаете через разрыв из мира теней в материальную реальность" + stat_allowed = UNCONSCIOUS + action_icon_state = "ascend" + +/datum/spell/shadowling/self/rift_in/cast(list/targets, mob/user = usr) + var/obj/effect/dummy/slaughter/holder = user.loc + if(istype(holder)) + user.forceMove(holder.loc) + qdel(holder) + user.RemoveSpell(src) + return + diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_screech.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_screech.dm new file mode 100644 index 000000000000..25eda6727db1 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_screech.dm @@ -0,0 +1,40 @@ +/datum/spell/shadowling/screech + name = "Вопль" + desc = "Вы громко вопите, дизориентируя врагов, отключая боргов и повреждая стёкла в радиусе 5-и клеток." + base_cooldown = 40 SECONDS + stat_allowed = UNCONSCIOUS + create_attack_logs = FALSE // Required to stop turf spam to logs + action_icon_state = "screech" + +/datum/spell/shadowling/screech/create_new_targeting() + var/datum/spell_targeting/aoe/turf/T = new + T.range = 5 + return T + +/datum/spell/shadowling/screech/cast(list/targets, mob/user) + user.audible_message(span_boldwarning("[user] издаёт ужасающий крик!")) + playsound(user.loc, 'sound/effects/screech.ogg', 100, TRUE) + + for(var/turf/turf in targets) + for(var/mob/target in turf.contents) + if(shadowling_check(target)) + continue + + if(iscarbon(target)) + var/mob/living/carbon/c_mob = target + to_chat(c_mob, span_boldwarning("Боль пронзает вашу голову! Этот звук НЕВЫНОСИМ!")) + c_mob.AdjustConfused(20 SECONDS) + c_mob.AdjustDeaf(6 SECONDS) + + else if(issilicon(target)) + var/mob/living/silicon/robot = target + to_chat(robot, span_boldwarning("ОШИБКА: Внешние сенсоры пе&рег?3%8@_#...")) + robot << 'sound/misc/interference.ogg' + playsound(robot, 'sound/machines/warning-buzzer.ogg', 50, TRUE) + do_sparks(5, 1, robot) + robot.Weaken(12 SECONDS) + + add_attack_logs(user, target, "cast the spell [name]", ATKLOG_ALL) + + for(var/obj/structure/window/window in turf.contents) + window.take_damage(rand(80, 100)) diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_shadow_walk.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_shadow_walk.dm new file mode 100644 index 000000000000..f32dbad5fb84 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_shadow_walk.dm @@ -0,0 +1,28 @@ +/datum/spell/shadowling/self/shadow_walk + name = "Шаг в тень" + desc = "Вы просачиваетесь в пространство меж миров, получая способность быстро перемещаться сквозь стены на 4 секунды. Свет по-прежнему опасен для вас." + base_cooldown = 60 SECONDS + stat_allowed = UNCONSCIOUS + action_icon_state = "shadowling_crawl" + +/datum/spell/shadowling/self/shadow_walk/cast(list/targets, mob/living/user = usr) + playsound(user.loc, 'sound/effects/bamf.ogg', 50, 1) + user.visible_message(span_warning("[user] исчезает в облаке тёмного тумана!"), span_purple("Вы просачиваетесь в пространство меж миров на 4 секунды.")) + user.SetStunned(0) + user.SetWeakened(0) + user.SetKnockDown(0) + user.incorporeal_move = INCORPOREAL_MOVE_NORMAL + user.alpha = 0 + user.ExtinguishMob() + user.forceMove(get_turf(user)) //to properly move the mob out of a potential container + user.pulledby?.stop_pulling() + user.stop_pulling() + + sleep(4 SECONDS) + if(QDELETED(user)) + return + + user.visible_message(span_warning("[user] внезапно появляется из ниоткуда!"), span_purple("Давление реальности становится невыносимым. Вы вынуждены вернуться в материальный мир")) + user.incorporeal_move = NO_INCORPOREAL_MOVE + user.alpha = 255 + user.forceMove(get_turf(user)) diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_spell.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_spell.dm new file mode 100644 index 000000000000..7ab08b77c9c4 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_spell.dm @@ -0,0 +1,42 @@ +// Basic + +/datum/spell/shadowling + school = "shadowling" + action_background_icon_state = "shadow_demon_bg" + action_icon = 'modular_ss220/antagonists/icons/shadowlings/shadowlings_actions.dmi' + clothes_req = FALSE + + /// If true, can be casted while using shadowstep and other incorporeal abilities + var/cast_incorporeal = FALSE + +/datum/spell/shadowling/can_cast(mob/user, charge_check, show_message) + if(isliving(user)) + var/mob/living/living_user = user + if(living_user.incorporeal_move != NO_INCORPOREAL_MOVE) + to_chat(user, span_warning("Вы не можете использовать эту способность не в физической форме!")) + return + return ..() + +/datum/spell/shadowling/proc/get_light_level(mob/living/target) + var/light_amount = 0 + if(isturf(target.loc)) + var/turf/T = target.loc + light_amount = T.get_lumcount() * 10 + return light_amount + +/datum/spell/shadowling/proc/shadowling_check(mob/living/carbon/human/user) + if(!istype(user)) + return FALSE + + if(isshadowling(user) && is_shadow(user)) + return TRUE + + if(isshadowlinglesser(user) && is_thrall(user)) + return TRUE + + return FALSE + +// Self + +/datum/spell/shadowling/self/create_new_targeting() + return new /datum/spell_targeting/self diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_tear_the_reality.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_tear_the_reality.dm new file mode 100644 index 000000000000..3c8b5067f473 --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_tear_the_reality.dm @@ -0,0 +1,24 @@ +/datum/spell/shadowling/self/tear_the_reality + name = "Прорвать реальность" + desc = "Вы используете накопленную от поглощённых смертных силу, чтобы создать разрыв в реальности и призвать в этот мир своих детей. Летально для вас, но гарантирует то, что ваш дух возродится в одном из детей." + base_cooldown = 0 SECONDS + stat_allowed = UNCONSCIOUS + action_icon_state = "ascend" + +/datum/spell/shadowling/self/tear_the_reality/can_cast(mob/user, charge_check, show_message) + . = ..() + if(!istype(user, /mob/living/simple_animal/demon/shadow_father)) + return FALSE + var/mob/living/simple_animal/demon/shadow_father/father = user + if(father.consumed < 1) + to_chat(father, span_warning("Вам нужно поглотить как минимум одного смертного, чтобы прорвать реальность.")) + return FALSE + +/datum/spell/shadowling/self/tear_the_reality/cast(list/targets, mob/user) + var/mob/living/simple_animal/demon/shadow_father/father = user + var/confirm = tgui_alert(father, "Вы уверены что хотите закончить свою охоту и прорвать реальность? Вы призовёте [father.consumed + 1] тенелингов и гарантированно станите одним из них.", "Закончить охоту?", list("Да", "Я ещё поохочусь")) + if(confirm != "Да") + return + INVOKE_ASYNC(SSticker.mode, TYPE_PROC_REF(/datum/game_mode, begin_shadowling_invasion), src, TRUE) + qdel(user) + diff --git a/modular_ss220/antagonists/code/shadowlings/spells/shadowling_veil.dm b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_veil.dm new file mode 100644 index 000000000000..37587d25a5bc --- /dev/null +++ b/modular_ss220/antagonists/code/shadowlings/spells/shadowling_veil.dm @@ -0,0 +1,26 @@ +/datum/spell/shadowling/veil + name = "Пелена" + desc = "Вы отключаете большинство источников освещения в радиусе 5-и клеток" + base_cooldown = 15 SECONDS + stat_allowed = UNCONSCIOUS + create_attack_logs = FALSE // Required to stop turf spam to logs + action_icon_state = "veil" + +/datum/spell/shadowling/veil/create_new_targeting() + var/datum/spell_targeting/aoe/turf/T = new + T.range = 5 + return T + +/datum/spell/shadowling/veil/can_cast(mob/user, charge_check, show_message) + . = ..() + // Veil shouldn't work in shadow crawl + if(istype(user.loc, /obj/effect/dummy/slaughter)) + return FALSE + +/datum/spell/shadowling/veil/cast(list/targets, mob/user = usr) + add_attack_logs(user, user, "cast the spell [name]", ATKLOG_ALL) + to_chat(user, span_purple("Вы бесшумно отключаете источники света поблизости.")) + for(var/turf/T in targets) + T.extinguish_light() + for(var/atom/A in T.contents) + A.extinguish_light() diff --git a/modular_ss220/antagonists/icons/shadowlings/father_of_shadows.dmi b/modular_ss220/antagonists/icons/shadowlings/father_of_shadows.dmi new file mode 100644 index 000000000000..297558f676e0 Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/father_of_shadows.dmi differ diff --git a/modular_ss220/antagonists/icons/shadowlings/r_lshadowling.dmi b/modular_ss220/antagonists/icons/shadowlings/r_lshadowling.dmi new file mode 100644 index 000000000000..0e955e5bd977 Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/r_lshadowling.dmi differ diff --git a/modular_ss220/antagonists/icons/shadowlings/r_shadowling.dmi b/modular_ss220/antagonists/icons/shadowlings/r_shadowling.dmi new file mode 100644 index 000000000000..e630750aeb6c Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/r_shadowling.dmi differ diff --git a/modular_ss220/antagonists/icons/shadowlings/shadowlings_actions.dmi b/modular_ss220/antagonists/icons/shadowlings/shadowlings_actions.dmi new file mode 100644 index 000000000000..0999fda978f4 Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/shadowlings_actions.dmi differ diff --git a/modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi b/modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi new file mode 100644 index 000000000000..4c822250f813 Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/shadowlings_clothes.dmi differ diff --git a/modular_ss220/antagonists/icons/shadowlings/shadowlings_traps.dmi b/modular_ss220/antagonists/icons/shadowlings/shadowlings_traps.dmi new file mode 100644 index 000000000000..f76d8bc62e98 Binary files /dev/null and b/modular_ss220/antagonists/icons/shadowlings/shadowlings_traps.dmi differ diff --git a/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_end.ogg b/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_end.ogg new file mode 100644 index 000000000000..06180446307d Binary files /dev/null and b/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_end.ogg differ diff --git a/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_start.ogg b/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_start.ogg new file mode 100644 index 000000000000..ccb6b85c211d Binary files /dev/null and b/modular_ss220/antagonists/sound/shadowlings/shadow_consumption_start.ogg differ