Make Abductor Team (Requires Ghosts)
Make Revenant (Requires Ghost)
- Make Shadowling
Make Darkspawn
Make Vampire
Make Infiltration Team (Requires Ghosts) diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm index ef0ad64068b6..0123e6763971 100644 --- a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm +++ b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm @@ -52,7 +52,7 @@ bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/restingplace) /datum/bloodsucker_clan/lasombra/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum) - vassaldatum.BuyPower(new /datum/action/cooldown/spell/pointed/lesser_glare) + vassaldatum.BuyPower(new /datum/action/cooldown/spell/pointed/seize/lesser) vassaldatum.BuyPower(new /datum/action/cooldown/spell/jaunt/shadow_walk) if(ishuman(vassaldatum.owner.current)) var/mob/living/carbon/human/vassal = vassaldatum.owner.current diff --git a/code/modules/antagonists/clockcult/clockcult.dm b/code/modules/antagonists/clockcult/clockcult.dm index 99396e877ddd..c55c552c3d4a 100644 --- a/code/modules/antagonists/clockcult/clockcult.dm +++ b/code/modules/antagonists/clockcult/clockcult.dm @@ -44,7 +44,6 @@ var/list/no_team_antag = list( /datum/antagonist/rev, /datum/antagonist/darkspawn, - /datum/antagonist/shadowling, /datum/antagonist/cult, /datum/antagonist/zombie ) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index a73e265331c3..28af3c5134bf 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -53,7 +53,6 @@ /datum/antagonist/rev, /datum/antagonist/clockcult, /datum/antagonist/darkspawn, - /datum/antagonist/shadowling, /datum/antagonist/zombie ) for(var/datum/antagonist/NTA in new_owner.antag_datums) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_rune_knife.dm b/code/modules/antagonists/eldritch_cult/eldritch_rune_knife.dm index b5f4c4f72797..4947ff92261f 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_rune_knife.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_rune_knife.dm @@ -178,7 +178,7 @@ if(new_owner) owner = WEAKREF(new_owner) -/obj/structure/trap/eldritch/on_entered(datum/source, atom/movable/entering_atom) +/obj/structure/trap/eldritch/on_trap_entered(datum/source, atom/movable/entering_atom) if(!isliving(entering_atom)) return ..() var/mob/living/living_mob = entering_atom diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index 508c4611361f..8c678672bc2d 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -22,7 +22,6 @@ var/list/no_team_antag = list( /datum/antagonist/clockcult, /datum/antagonist/darkspawn, - /datum/antagonist/shadowling, /datum/antagonist/cult, /datum/antagonist/zombie ) diff --git a/code/modules/events/darkspawn.dm b/code/modules/events/darkspawn.dm deleted file mode 100644 index caae30f2770a..000000000000 --- a/code/modules/events/darkspawn.dm +++ /dev/null @@ -1,55 +0,0 @@ -/datum/round_event_control/darkspawn - name = "Spawn Darkspawn(s)" - typepath = /datum/round_event/ghost_role/darkspawn - max_occurrences = 0 //Disabled - min_players = 30 - dynamic_should_hijack = TRUE - gamemode_blacklist = list("darkspawn", "shadowling") - -/datum/round_event/ghost_role/darkspawn - minimum_required = 1 - role_name = "darkspawn" - fakeable = FALSE - -/datum/round_event/ghost_role/darkspawn/spawn_role() - var/list/candidates = get_candidates(ROLE_DARKSPAWN, null, ROLE_DARKSPAWN) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/list/spawn_locs = list() - for(var/turf/T in GLOB.xeno_spawn) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - spawn_locs += T - - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - - var/darkspawn_to_spawn = 1 - var/datum/job/hos = SSjob.GetJob("Head of Security") - var/datum/job/warden = SSjob.GetJob("Warden") - var/datum/job/officers = SSjob.GetJob("Security Officer") - var/sec_amount = hos.current_positions + warden.current_positions + officers.current_positions - if(sec_amount >= 5 && candidates.len >= 2 && spawn_locs.len >= 2) - darkspawn_to_spawn = 2 - - for(var/i=0,i
The darkspawn were:" - for(var/D in darkspawn) - var/datum/mind/darkboi = D - text += printplayer(darkboi) - text += "
" - if(veils.len) - text += "
The veils were:" - for(var/V in veils) - var/datum/mind/veil = V - text += printplayer(veil) - text += "
" - to_chat(world, text) +/datum/game_mode/darkspawn/generate_report() + return "Sightings of strange alien creatures have been observed in your area. These aliens appear to be searching for specific patterns of brain activity, with their method for doing so causing victims to lapse into a short coma. \ + Be wary of dark areas and ensure all lights are kept well-maintained. Investigate all reports of odd or suspicious sightings in maintenance, and be on the lookout for anyone sympathizing with these aliens, as they may be compromised" +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------------Game end checks---------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/game_mode/darkspawn/are_special_antags_dead() + if(team) + return team.check_darkspawn_death() + return ..() + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------------After game end stuff--------------------------------// +//////////////////////////////////////////////////////////////////////////////////// /datum/game_mode/darkspawn/set_round_result() ..() - if(check_darkspawn_victory()) - SSticker.mode_result = "win - the darkspawn have completed the Sacrament" + if(GLOB.sacrament_done) + SSticker.mode_result = "win - the darkspawn have completed the sacrament" else SSticker.mode_result = "loss - staff stopped the darkspawn" +/datum/game_mode/darkspawn/generate_credit_text() + var/list/round_credits = list() + var/len_before_addition + + round_credits += "
The Darkspawn:
" + len_before_addition = round_credits.len + if(team) + for(var/datum/mind/current in team.members) + round_credits += "[current.name] as the [current.assigned_role]
" + if(len_before_addition == round_credits.len) + round_credits += list("The Darkspawn have moved to the shadows!
", "We couldn't locate them!
") + round_credits += "" + + round_credits += ..() + return round_credits + +//////////////////////////////////////////////////////////////////////////////////// +//-------------------------------Mob assign procs---------------------------------// +//////////////////////////////////////////////////////////////////////////////////// /mob/living/proc/add_darkspawn() if(!istype(mind)) - return FALSE + return FALSE return mind.add_antag_datum(/datum/antagonist/darkspawn) /mob/living/proc/remove_darkspawn() @@ -100,37 +111,12 @@ return FALSE return mind.remove_antag_datum(/datum/antagonist/darkspawn) -/mob/living/proc/add_veil() +/mob/living/proc/add_thrall() if(!istype(mind)) return FALSE - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - src.visible_message(span_warning("[src] seems to resist an unseen force!")) - to_chat(src, "Your mind goes numb. Your thoughts go blank. You feel utterly empty. \n\ - A mind brushes against your own. You dream.\n\ - Of a vast, empty Void in the deep of space.\n\ - Something lies in the Void. Ancient. Unknowable. It watches you with hungry eyes. \n\ - Eyes filled with stars.\n\ - [span_boldwarning("The creature's gaze swallows the universe into blackness.")])\n\ - [span_boldwarning("It cannot be permitted to succeed.")]") - return FALSE - return mind.add_antag_datum(/datum/antagonist/veil) + return mind.add_antag_datum(/datum/antagonist/thrall) -/mob/living/proc/remove_veil() +/mob/living/proc/remove_thrall() if(!istype(mind)) return FALSE - return mind.remove_antag_datum(/datum/antagonist/veil) - -/datum/game_mode/darkspawn/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - round_credits += "
The Darkspawn:
" - len_before_addition = round_credits.len - for(var/datum/mind/darkboi in darkspawn) - round_credits += "[darkboi.name] as a Darkspawn
" - if(len_before_addition == round_credits.len) - round_credits += list("The Darkspawn have moved to the shadows!
", "We couldn't locate them!
") - round_credits += "" - - round_credits += ..() - return round_credits + return mind.remove_antag_datum(/datum/antagonist/thrall) diff --git a/yogstation/code/game/gamemodes/darkspawn/veil.dm b/yogstation/code/game/gamemodes/darkspawn/veil.dm deleted file mode 100644 index e3f79e9c4adb..000000000000 --- a/yogstation/code/game/gamemodes/darkspawn/veil.dm +++ /dev/null @@ -1,71 +0,0 @@ -/datum/antagonist/veil - name = "Darkspawn Veil" - job_rank = ROLE_DARKSPAWN - antag_hud_name = "veil" - roundend_category = "veils" - antagpanel_category = "Darkspawn" - antag_moodlet = /datum/mood_event/thrall - var/mutable_appearance/veil_sigils - -/datum/antagonist/veil/on_gain() - . = ..() - SSticker.mode.veils += owner - owner.special_role = "veil" - message_admins("[key_name_admin(owner.current)] was veiled by a darkspawn!") - log_game("[key_name(owner.current)] was veiled by a darkspawn!") - -/datum/antagonist/veil/on_removal() - SSticker.mode.veils -= owner - message_admins("[key_name_admin(owner.current)] was deveiled!") - log_game("[key_name(owner.current)] was deveiled!") - owner.special_role = null - var/mob/living/M = owner.current - if(issilicon(M)) - M.audible_message(span_notice("[M] lets out a short blip, followed by a low-pitched beep.")) - to_chat(M,span_userdanger("You have been turned into a[ iscyborg(M) ? " cyborg" : "n AI" ]! You are no longer a thrall! Though you try, you cannot remember anything about your servitude...")) - else - M.visible_message(span_big("[M] looks like their mind is their own again!")) - to_chat(M,span_userdanger("A piercing white light floods your eyes. Your mind is your own again! Though you try, you cannot remember anything about the darkspawn or your time under their command...")) - to_chat(owner, span_notice("As your mind is released from their grasp, you feel your strength returning.")) - M.update_sight() - return ..() - -/datum/antagonist/veil/apply_innate_effects(mob/living/mob_override) - var/mob/living/current_mob = mob_override || owner.current - current_mob.maxHealth -= 40 - veil_sigils = mutable_appearance('yogstation/icons/mob/actions/actions_darkspawn.dmi', "veil_sigils", -UNDER_SUIT_LAYER) //show them sigils - current_mob.add_overlay(veil_sigils) - add_team_hud(current_mob, /datum/antagonist/darkspawn) - -/datum/antagonist/veil/remove_innate_effects(mob/living/mob_override) - var/mob/living/current_mob = mob_override || owner.current - current_mob.maxHealth += 40 - current_mob.cut_overlay(veil_sigils) - QDEL_NULL(veil_sigils) - -/datum/antagonist/veil/greet() - to_chat(owner, "ukq wna ieja jks" ) - if(ispreternis(owner.current)) - to_chat(owner, "Your mind goes numb. Your thoughts go blank. You feel utterly empty. \n\ - A consciousness brushes against your own. You dream.\n\ - Of a vast, glittering empire stretching from star to star. \n\ - Then, a Void blankets the canopy, suffocating the light. \n\ - Hungry eyes bear into you from the blackness. Ancient. Familiar. \n\ - You feel the warm consciousness welcome your own. Realization spews forth as the veil recedes. \n\ - Serve the darkspawn above all else. Your former allegiances are now forfeit. Their goal is yours, and yours is theirs.") - else - to_chat(owner, "Your mind goes numb. Your thoughts go blank. You feel utterly empty. \n\ - A consciousness brushes against your own. You dream. \n\ - Of a vast, empty Void in the deep of space. \n\ - Something lies in the Void. Ancient. Unknowable. It watches you with hungry eyes. \n\ - Eyes filled with stars. \n\ - You feel the vast consciousness slowly consume your own and the veil falls away. \n\ - Serve the darkspawn above all else. Your former allegiances are now forfeit. Their goal is yours, and yours is theirs.") - to_chat(owner, "Use :[MODE_KEY_DARKSPAWN] or .[MODE_KEY_DARKSPAWN] before your messages to speak over the Mindlink. This only works across your current z-level.") - to_chat(owner, "Ask for help from your masters or fellows if you're new to this role.") - to_chat(owner, span_danger("Your drained will has left you feeble and weak! You will go down with many fewer hits!")) - SEND_SOUND(owner.current, sound ('yogstation/sound/ambience/antag/become_veil.ogg', volume = 50)) - flash_color(owner, flash_color = "#21007F", flash_time = 10 SECONDS) - -/datum/antagonist/veil/roundend_report() - return "[printplayer(owner)]" diff --git a/yogstation/code/game/gamemodes/shadowling/shadowling.dm b/yogstation/code/game/gamemodes/shadowling/shadowling.dm deleted file mode 100644 index d9c11c590596..000000000000 --- a/yogstation/code/game/gamemodes/shadowling/shadowling.dm +++ /dev/null @@ -1,274 +0,0 @@ -#define LIGHT_HEAL_THRESHOLD 2 -#define LIGHT_DAMAGE_TAKEN 6 -#define LIGHT_DAM_THRESHOLD 0.25 - -/* -SHADOWLING: A gamemode based on previously-run events -Aliens called shadowlings are on the station. -These shadowlings can 'enthrall' crew members and enslave them. -They also burn in the light but heal rapidly whilst in the dark. -The game will end under two conditions: - 1. The shadowlings die - 2. The emergency shuttle docks at CentCom -Shadowling strengths: - - The dark - - Hard vacuum (They are not affected by it, but are affected by starlight!) - - Their thralls who are not harmed by the light - - Stealth -Shadowling weaknesses: - - The light - - Fire - - Enemy numbers - - Burn-based weapons and items (flashbangs, lasers, etc.) -Shadowlings start off disguised as normal crew members, and they only have two abilities: Hatch and Enthrall. -They can still enthrall and perhaps complete their objectives in this form. -Hatch will, after a short time, cast off the human disguise and assume the shadowling's true identity. -They will then assume the normal shadowling form and gain their abilities. -The shadowling will seem OP, and that's because it kinda is. Being restricted to the dark while being alone most of the time is extremely difficult and as such the shadowling needs powerful abilities. -Made by Xhuis -*/ -/* - GAMEMODE -*/ -/datum/game_mode - var/list/datum/mind/shadows = list() - var/list/datum/mind/thralls = list() - var/required_thralls = 15 //How many thralls are needed (this is changed in pre_setup, so it scales based on pop) - var/shadowling_ascended = FALSE //If at least one shadowling has ascended - var/thrall_ratio = 1 - -/datum/game_mode/proc/replace_jobbaned_player(mob/living/M, role_type, pref) - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [role_type]?", "[role_type]", null, pref, 50, M) - var/mob/dead/observer/theghost = null - if(candidates.len) - theghost = pick(candidates) - to_chat(M, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbaned player.") - M.ghostize(0) - M.key = theghost.key - -/datum/game_mode/shadowling - name = "shadowling" - config_tag = "shadowling" - antag_flag = ROLE_SHADOWLING - required_players = 38 - required_enemies = 3 - recommended_enemies = 3 - enemy_minimum_age = 14 - restricted_jobs = list("AI", "Cyborg", "Synthetic") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Research Director", "Chief Engineer", "Chief Medical Officer", "Brig Physician") - title_icon = "ss13" - -/datum/game_mode/shadowling/announce() - to_chat(world, "The current game mode is - Shadowling!") - to_chat(world, "There are alien [span_shadowling("shadowlings")] on the station. Crew: Kill the shadowlings before they can enthrall the crew. Shadowlings: Enthrall the crew while remaining in hiding.") - -/datum/game_mode/shadowling/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - var/shadowlings = max(3, round(num_players()/14)) - while(shadowlings) - var/datum/mind/shadow = pick(antag_candidates) - shadows += shadow - antag_candidates -= shadow - shadow.special_role = "Shadowling" - shadow.restricted_roles = restricted_jobs - shadowlings-- - var/thrall_scaling = round(num_players() / 3) - required_thralls = clamp(thrall_scaling, 15, 30) - thrall_ratio = required_thralls / 15 - return TRUE - -/datum/game_mode/shadowling/generate_report() - return "Sightings of strange alien creatures have been observed in your area. These aliens supposedly possess the ability to enslave unwitting personnel and leech from their power. \ - Be wary of dark areas and ensure all lights are kept well-maintained. Closely monitor all crew for suspicious behavior and perform dethralling surgery if they have obvious tells. Investigate all \ - reports of odd or suspicious sightings in maintenance." - -/datum/game_mode/shadowling/post_setup() - for(var/T in shadows) - var/datum/mind/shadow = T - log_game("[shadow.key] (ckey) has been selected as a Shadowling.") - shadow.current.add_sling() - . = ..() - return - -/datum/game_mode/shadowling/proc/check_shadow_victory() - return shadowling_ascended - -/datum/game_mode/shadowling/proc/check_shadow_death() - for(var/SM in get_antag_minds(/datum/antagonist/shadowling)) - var/datum/mind/shadow_mind = SM - if(istype(shadow_mind)) - var/turf/T = get_turf(shadow_mind.current) - if((shadow_mind) && (shadow_mind.current.stat != DEAD) && T && is_station_level(T.z) && ishuman(shadow_mind.current)) - return FALSE - return TRUE - -/datum/game_mode/shadowling/check_finished() - . = ..() - if(check_shadow_death()) - return TRUE - -/datum/game_mode/proc/auto_declare_completion_shadowling() - var/text = "" - if(shadows.len) - text += "
The shadowlings were:" - for(var/S in shadows) - var/datum/mind/shadow = S - text += printplayer(shadow) - text += "
" - if(thralls.len) - text += "
The thralls were:" - for(var/T in thralls) - var/datum/mind/thrall = T - text += printplayer(thrall) - text += "
" - to_chat(world, text) - -/datum/game_mode/shadowling/set_round_result() - ..() - if(check_shadow_victory()) - SSticker.mode_result = "win - shadowlings have ascended" - else - SSticker.mode_result = "loss - staff stopped the shadowlings" - -/* - MISCELLANEOUS -*/ -/datum/species/shadow/ling - //Normal shadowpeople but with enhanced effects - name = "Shadowling" - id = "shadowling" - say_mod = "chitters" - species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES,NOFLASH) - inherent_traits = list(TRAIT_NOGUNS, TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE, TRAIT_NOBREATH, TRAIT_RADIMMUNE, TRAIT_VIRUSIMMUNE, TRAIT_PIERCEIMMUNE) - no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_EYES, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE) - nojumpsuit = TRUE - mutanteyes = /obj/item/organ/eyes/alien/sling - burnmod = 1.5 //1.5x burn damage, 2x is excessive - heatmod = 1.5 - var/mutable_appearance/eyes_overlay - var/shadow_charges = 3 - var/last_charge = 0 - -/datum/species/shadow/ling/negates_gravity(mob/living/carbon/human/H) - return TRUE - -/datum/species/shadow/ling/on_species_gain(mob/living/carbon/human/C) - C.draw_yogs_parts(TRUE) - eyes_overlay = mutable_appearance('yogstation/icons/mob/sling.dmi', "eyes", 25) - C.add_overlay(eyes_overlay) - RegisterSignal(C, COMSIG_MOVABLE_MOVED, PROC_REF(apply_darkness_speed)) - . = ..() - -/datum/species/shadow/ling/on_species_loss(mob/living/carbon/human/C) - C.draw_yogs_parts(FALSE) - UnregisterSignal(C, COMSIG_MOVABLE_MOVED) - C.remove_movespeed_modifier(id) - if(eyes_overlay) - C.cut_overlay(eyes_overlay) - QDEL_NULL(eyes_overlay) - . = ..() - -/datum/species/shadow/ling/spec_life(mob/living/carbon/human/H) - H.nutrition = NUTRITION_LEVEL_WELL_FED //i aint never get hongry - if(isturf(H.loc)) - var/turf/T = H.loc - var/light_amount = T.get_lumcount() - if(light_amount > LIGHT_DAM_THRESHOLD) //Can survive in very small light levels. Also doesn't take damage while incorporeal, for shadow walk purposes - H.adjustCloneLoss(LIGHT_DAMAGE_TAKEN) - if(H.stat != DEAD) - to_chat(H, span_userdanger("The light burns you!")) //Message spam to say "GET THE FUCK OUT" - H.playsound_local(get_turf(H), 'sound/weapons/sear.ogg', 150, 1, pressure_affected = FALSE) - else if (light_amount < LIGHT_HEAL_THRESHOLD && !istype(H.loc, /obj/effect/dummy/phased_mob/shadowling)) //Can't heal while jaunting - H.heal_overall_damage(5,5) - H.adjustToxLoss(-5) - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, -25) //Shad O. Ling gibbers, "CAN U BE MY THRALL?!!" - H.adjustCloneLoss(-5) - H.SetKnockdown(0) - H.SetStun(0) - H.SetParalyzed(0) - var/charge_time = 400 - ((SSticker.mode.thralls && SSticker.mode.thralls.len) || 0)*10 - if(world.time >= charge_time+last_charge) - shadow_charges = min(shadow_charges + 1, 3) - last_charge = world.time - -/datum/species/shadow/ling/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T) && shadow_charges > 0) - var/light_amount = T.get_lumcount() - if(light_amount < LIGHT_DAM_THRESHOLD) - H.visible_message(span_danger("The shadows around [H] ripple as they absorb \the [P]!")) - playsound(T, "bullet_miss", 75, 1) - shadow_charges = min(shadow_charges - 1, 0) - return -1 - return 0 - -/datum/species/shadow/ling/proc/apply_darkness_speed(mob/living/carbon/C) - var/turf/T = get_turf(C) - var/light_amount = T.get_lumcount() - if(light_amount > LIGHT_DAM_THRESHOLD) - C.remove_movespeed_modifier(id) - else - C.add_movespeed_modifier(id, update=TRUE, priority=100, multiplicative_slowdown=-1, blacklisted_movetypes=(FLYING|FLOATING)) - - -/datum/species/shadow/ling/lesser //Empowered thralls. Obvious, but powerful - name = "Lesser Shadowling" - id = "l_shadowling" - say_mod = "chitters" - species_traits = list(NOBLOOD,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES,NOFLASH) - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RADIMMUNE, TRAIT_PIERCEIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD) - burnmod = 1.25 - heatmod = 1.25 - brutemod = 0.75 - -/datum/species/shadow/ling/lesser/spec_life(mob/living/carbon/human/H) - H.nutrition = NUTRITION_LEVEL_WELL_FED //i aint never get hongry - if(isturf(H.loc)) - var/turf/T = H.loc - var/light_amount = T.get_lumcount() - if(light_amount > LIGHT_DAM_THRESHOLD && !H.incorporeal_move) - H.adjustCloneLoss(LIGHT_DAMAGE_TAKEN/2) - else if (light_amount < LIGHT_HEAL_THRESHOLD) - H.heal_overall_damage(4,4) - H.adjustToxLoss(-5) - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, -25) - H.adjustCloneLoss(-5) - -/mob/living/proc/add_thrall() - if(!istype(mind)) - return FALSE - return mind.add_antag_datum(ANTAG_DATUM_THRALL) - -/mob/living/proc/add_sling() - if(!istype(mind)) - return FALSE - return mind.add_antag_datum(ANTAG_DATUM_SLING) - -/mob/living/proc/remove_thrall() - if(!istype(mind)) - return FALSE - return mind.remove_antag_datum(ANTAG_DATUM_THRALL) - -/mob/living/proc/remove_sling() - if(!istype(mind)) - return FALSE - return mind.remove_antag_datum(ANTAG_DATUM_SLING) - -/datum/game_mode/shadowling/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - round_credits += "
The Shadowlings:
" - len_before_addition = round_credits.len - for(var/datum/mind/shadow in shadows) - round_credits += "[shadow.name] as a Shadowling
" - if(len_before_addition == round_credits.len) - round_credits += list("The Shadowlings have moved to the shadows!
", "We couldn't locate them!
") - round_credits += "" - - round_credits += ..() - return round_credits diff --git a/yogstation/code/modules/admin/verbs/one_click_antag.dm b/yogstation/code/modules/admin/verbs/one_click_antag.dm index 9bb312e20ae4..475395ff0571 100644 --- a/yogstation/code/modules/admin/verbs/one_click_antag.dm +++ b/yogstation/code/modules/admin/verbs/one_click_antag.dm @@ -1,31 +1,3 @@ -//Shadowling -/datum/admins/proc/makeShadowling() - var/datum/game_mode/shadowling/temp = new - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - temp.restricted_jobs += temp.protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - temp.restricted_jobs += "Assistant" - var/list/mob/living/carbon/human/candidates = list() - var/mob/living/carbon/human/H - for(var/mob/living/carbon/human/applicant in GLOB.player_list) - if(ROLE_DARKSPAWN in applicant.client.prefs.be_special) - if(!applicant.stat || !applicant.mind) - continue - if(applicant.mind.special_role) - continue - if(is_banned_from(applicant.ckey, "shadowling") || is_banned_from(applicant.ckey, "Syndicate")) - continue - if(!temp.age_check(applicant.client) || (applicant.job in temp.restricted_jobs)) - continue - if(is_shadow_or_thrall(applicant)) - continue - candidates += applicant - if(candidates.len) - H = pick(candidates) - H.add_sling() - return TRUE - return FALSE - //Darkspawn /datum/admins/proc/makeDarkspawn() var/datum/game_mode/darkspawn/temp = new @@ -36,7 +8,7 @@ var/list/mob/living/carbon/human/candidates = list() var/mob/living/carbon/human/H for(var/mob/living/carbon/human/applicant in GLOB.player_list) - if(ROLE_SHADOWLING in applicant.client.prefs.be_special) + if(ROLE_DARKSPAWN in applicant.client.prefs.be_special) if(!applicant.stat || !applicant.mind) continue if(applicant.mind.special_role) @@ -45,7 +17,7 @@ continue if(!temp.age_check(applicant.client) || (applicant.job in temp.restricted_jobs)) continue - if(is_darkspawn_or_veil(applicant)) + if(is_darkspawn_or_thrall(applicant)) continue candidates += applicant if(candidates.len) diff --git a/yogstation/code/modules/antagonists/darkspawn/_psi_web.dm b/yogstation/code/modules/antagonists/darkspawn/_psi_web.dm new file mode 100644 index 000000000000..08cee37c0bf5 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/_psi_web.dm @@ -0,0 +1,157 @@ +//shadow store datums (upgrades and abilities) +/datum/psi_web + ///Name of the effect + var/name = "Basic knowledge" + ///Description of the effect + var/desc = "Basic knowledge of forbidden arts." + ///Fancy description about the effect + var/lore_description = "" + ///Icon file used for the tgui menu + var/icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + ///Specific icon used for the tgui menu + var/icon_state = null + ///Cost of to learn this + var/willpower_cost = 0 + ///What specialization can buy this + var/shadow_flags = NONE + ///what ability is granted if any + var/list/datum/action/learned_abilities = list() + ///what tab of the antag menu does it fall under + var/menu_tab + ///The owner of the psi_web datum that effects will be applied to + var/datum/mind/owner + ///The human mob of darkspawn + var/mob/living/carbon/human/shadowhuman + ///The antag datum of the owner(used for modifying) + var/datum/antagonist/darkspawn/darkspawn + ///If it can be bought infinite times for incremental upgrades + var/infinite = FALSE + +///When the button to purchase is clicked +/datum/psi_web/proc/on_purchase(datum/mind/user, silent = FALSE) + if(!istype(user, /datum/mind)) + return + owner = user + if(!owner.current || !ishuman(owner.current)) + to_chat(user, span_warning("You can only research as a human")) + return + shadowhuman = owner.current + darkspawn = owner.has_antag_datum(/datum/antagonist/darkspawn) + if(!darkspawn) + CRASH("[owner] tried to gain a psi_web datum despite not being a darkspawn") + if(darkspawn.willpower < willpower_cost) + return + + darkspawn.willpower -= willpower_cost + if(willpower_cost && !silent) + to_chat(user, span_velvet("You have unlocked [name]")) + on_gain() + for(var/ability in learned_abilities) + if(ispath(ability, /datum/action)) + var/datum/action/action = new ability(owner) + action.Grant(shadowhuman) + return TRUE + +///If the purchase goes through, this gets called +/datum/psi_web/proc/on_gain() + return + +/datum/psi_web/proc/on_loss() + return + +/datum/psi_web/proc/remove(refund = FALSE) + for(var/ability in learned_abilities) + if(ispath(ability, /datum/action)) + var/datum/action/action = locate(ability) in owner.current.actions + if(action) + if(shadowhuman) + action.Remove(shadowhuman) + qdel(action) + + on_loss() + + if(refund && darkspawn) + darkspawn.willpower += willpower_cost + + return QDEL_HINT_QUEUE + +/datum/psi_web/Destroy(force, ...) + remove() + return ..() + +//////////////////////////////////////////////////////////////////////////////////// +//---------------------Darkspawn innate Upgrades----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/innate_darkspawn + name = "darkspawn progression abilities" + desc = "me no think so good" + shadow_flags = ALL_DARKSPAWN_CLASSES + learned_abilities = list(/datum/action/cooldown/spell/sacrament, /datum/action/cooldown/spell/touch/restrain_body, /datum/action/cooldown/spell/touch/devour_will) + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------Specialization innate Upgrades----------------------------// +//////////////////////////////////////////////////////////////////////////////////// +//fighter +/datum/psi_web/fighter + name = "fighter innate abilities" + desc = "me no think so good" + shadow_flags = DARKSPAWN_FIGHTER + learned_abilities = list(/datum/action/cooldown/spell/toggle/shadow_tendril) + +/datum/psi_web/fighter/on_gain() + darkspawn.brute_mod *= 0.7 + darkspawn.dark_healing += 2 //so they're just a little bit faster at healing since they're gonna take damage the most + +/datum/psi_web/fighter/on_loss() + darkspawn.brute_mod /= 0.7 + darkspawn.dark_healing -= 2 + +//scout +/datum/psi_web/scout + name = "scout innate abilities" + desc = "GO FAST, TOUCH GRASS" + shadow_flags = DARKSPAWN_SCOUT + learned_abilities = list(/datum/action/cooldown/spell/toggle/light_eater) + +/datum/psi_web/scout/on_gain() + shadowhuman.LoadComponent(/datum/component/walk/shadow) + +/datum/psi_web/scout/on_loss() + qdel(shadowhuman.GetComponent(/datum/component/walk/shadow)) + +//warlock +/datum/psi_web/warlock + name = "warlock innate abilities" + desc = "apartment \"complex\"... really? I find it quite simple" + shadow_flags = DARKSPAWN_WARLOCK + learned_abilities = list(/datum/action/cooldown/spell/touch/thrall_mind, /datum/action/cooldown/spell/release_thrall, /datum/action/cooldown/spell/pointed/darkspawn_build/thrall_cam, /datum/action/cooldown/spell/pointed/darkspawn_build/thrall_eye, /datum/action/cooldown/spell/toggle/dark_staff) + +/datum/psi_web/warlock/on_gain() + darkspawn.psi_cap *= 2 + darkspawn.psi_per_second *= 2 + var/datum/team/darkspawn/team = darkspawn.get_team() + if(team) + team.max_thralls += 3 + +/datum/psi_web/warlock/on_loss() + darkspawn.psi_cap /= 2 + darkspawn.psi_per_second /= 2 + var/datum/team/darkspawn/team = darkspawn.get_team() + if(team) + team.max_thralls -= 3 + +//////////////////////////////////////////////////////////////////////////////////// +//-------------------------Helper for ability upgrades----------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/ability_upgrade + name = "Upgrades ability" + desc = "you shouldn't be seeing this, let a coder or maintainer know." + var/flag_to_add + +/datum/psi_web/ability_upgrade/on_gain() + if(flag_to_add) + SEND_SIGNAL(owner, COMSIG_DARKSPAWN_UPGRADE_ABILITY, flag_to_add) + +/datum/psi_web/ability_upgrade/on_loss() + if(flag_to_add) + SEND_SIGNAL(owner, COMSIG_DARKSPAWN_DOWNGRADE_ABILITY, flag_to_add) diff --git a/yogstation/code/modules/antagonists/darkspawn/crawling_shadows.dm b/yogstation/code/modules/antagonists/darkspawn/crawling_shadows.dm deleted file mode 100644 index ee01d084a31d..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/crawling_shadows.dm +++ /dev/null @@ -1,133 +0,0 @@ -/mob/living/simple_animal/hostile/crawling_shadows - name = "crawling shadows" - desc = "A formless mass of blackness with two huge, clawed hands and piercing white eyes." - icon = 'icons/effects/effects.dmi' //Placeholder sprite - icon_state = "blank_dspawn" - icon_living = "blank_dspawn" - response_help = "backs away from" - response_disarm = "shoves away" - response_harm = "flails at" - speed = 0 - ventcrawler = TRUE - maxHealth = 125 - health = 125 - - harm_intent_damage = 5 - obj_damage = 50 - melee_damage_lower = 5 //it has a built in stun if you want to kill someone kill them like a man - melee_damage_upper = 5 - attacktext = "claws" - attack_sound = 'sound/magic/demon_attack1.ogg' - speak_emote = list("whispers") - - 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 - - movement_type = FLYING - pressure_resistance = INFINITY - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_MINIMUM - gold_core_spawnable = FALSE - - del_on_death = TRUE - deathmessage = "tremble, break form, and disperse!" - deathsound = 'yogstation/sound/magic/devour_will_victim.ogg' - - var/move_count = 0 //For spooky sound effects - var/knocking_out = FALSE - var/mob/living/darkspawn_mob - -/mob/living/simple_animal/hostile/crawling_shadows/New() - ..() - addtimer(CALLBACK(src, PROC_REF(check_darkspawn)), 1) - -/mob/living/simple_animal/hostile/crawling_shadows/Destroy() - if(darkspawn_mob && mind) - visible_message(span_warning("[src] transforms into a humanoid figure!"), span_warning("You return to your normal form.")) - playsound(src, 'yogstation/sound/magic/devour_will_end.ogg', 50, 1) - if(mind) - mind.transfer_to(darkspawn_mob) - darkspawn_mob.forceMove(get_turf(src)) - darkspawn_mob.status_flags &= ~GODMODE - return ..() - -/mob/living/simple_animal/hostile/crawling_shadows/Move() - move_count++ - if(move_count >= 5) - playsound(src, "crawling_shadows_walk", 25, 0) - move_count = 0 - ..() - -/mob/living/simple_animal/hostile/crawling_shadows/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - var/turf/T = get_turf(src) - var/lums = T.get_lumcount() - if(lums < DARKSPAWN_BRIGHT_LIGHT) - invisibility = INVISIBILITY_OBSERVER //Invisible in complete darkness - speed = -1 //Faster, too - alpha = 255 - else - invisibility = initial(invisibility) - speed = 0 - alpha = min(lums * 60, 255) //Slowly becomes more visible in brighter light - -/mob/living/simple_animal/hostile/crawling_shadows/death(gibbed) - if(darkspawn_mob) - mind.transfer_to(darkspawn_mob) - ..(gibbed) - -/mob/living/simple_animal/hostile/crawling_shadows/proc/check_darkspawn() - if(!darkspawn_mob) - qdel(src) - return - darkspawn_mob.forceMove(src) - darkspawn_mob.status_flags |= GODMODE - darkspawn_mob.mind.transfer_to(src) - to_chat(src, span_warning("This will last for around a minute.")) - var/datum/action/innate/darkspawn/end_shadows/E = new - E.Grant(src) - QDEL_IN(src, 600) - -/mob/living/simple_animal/hostile/crawling_shadows/AttackingTarget() - if(ishuman(target) && !knocking_out) - var/mob/living/carbon/human/H = target - if(H.stat) - return ..() - knocking_out = TRUE - visible_message(span_warning("[src] pick up [H] and dangle \him in the air!"), span_notice("You pluck [H] from the ground...")) - to_chat(H, span_userdanger("[src] grab you and dangle you in the air!")) - H.Stun(30) - H.pixel_y += 4 - if(!do_after(src, 1 SECONDS, target)) - H.pixel_y -= 4 - knocking_out = FALSE - return - visible_message(span_warning("[src] gently press a hand against [H]'s face, and \he falls limp..."), span_notice("You quietly incapacitate [H].")) - H.pixel_y -= 4 - to_chat(H, span_userdanger("[src] press a hand to your face, and docility comes over you...")) - H.Paralyze(60) - knocking_out = FALSE - return TRUE - else if(istype(target, /obj/machinery/door)) - forceMove(get_turf(target)) - visible_message(span_warning("Shadows creep through [target]..."), span_notice("You slip through [target].")) - return - ..() - - -/datum/action/innate/darkspawn/end_shadows - name = "End Crawling Shadows" - id = "end_shadows" - desc = "Reverts you to your humanoid form." - button_icon_state = "crawling_shadows" - blacklisted = TRUE - -/datum/action/innate/darkspawn/end_shadows/Activate() - qdel(owner) //edgi - qdel(src) - -/datum/action/innate/darkspawn/end_shadows/IsAvailable(feedback = FALSE) - if(istype(owner, /mob/living/simple_animal/hostile/crawling_shadows)) - return TRUE - return FALSE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn.dm deleted file mode 100644 index 71929e7a87f6..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn.dm +++ /dev/null @@ -1,391 +0,0 @@ -#define MUNDANE 0 -#define DIVULGED 1 -#define PROGENITOR 2 - -//aka Shadowlings/umbrages/whatever -/datum/antagonist/darkspawn - name = "Darkspawn" - roundend_category = "darkspawn" - antagpanel_category = "Darkspawn" - job_rank = ROLE_DARKSPAWN - antag_hud_name = "darkspawn" - var/darkspawn_state = MUNDANE //0 for normal crew, 1 for divulged, and 2 for progenitor - antag_moodlet = /datum/mood_event/sling - - //Psi variables - var/psi = 100 //Psi is the resource used for darkspawn powers - var/psi_cap = 100 //Max Psi by default - var/psi_regen = 20 //How much Psi will regenerate after using an ability - var/psi_regen_delay = 5 //How many ticks need to pass before Psi regenerates - var/psi_regen_ticks = 0 //When this hits 0, regenerate Psi and return to psi_regen_delay - var/psi_used_since_regen = 0 //How much Psi has been used since we last regenerated - var/psi_regenerating = FALSE //Used to prevent duplicate regen proc calls - - //Lucidity variables - var/lucidity = 3 //Lucidity is used to buy abilities and is gained by using Devour Will - var/lucidity_drained = 0 //How much lucidity has been drained from unique players - - //Ability and upgrade variables - var/list/abilities = list() //An associative list ("id" = ability datum) containing the abilities the darkspawn has - var/list/upgrades = list() //An associative list ("id" = null or TRUE) containing the passive upgrades the darkspawn has - var/datum/antag_menu/psi_web/psi_web //Antag menu used for opening the UI - var/datum/action/innate/darkspawn/psi_web/psi_web_action //Used to link the menu with our antag datum - - -// Antagonist datum things like assignment // - -/datum/antagonist/darkspawn/on_gain() - SSticker.mode.darkspawn += owner - owner.special_role = "darkspawn" - owner.current.hud_used.psi_counter.invisibility = 0 - update_psi_hud() - add_ability("divulge") - addtimer(CALLBACK(src, PROC_REF(begin_force_divulge)), 23 MINUTES) //this won't trigger if they've divulged when the proc runs - START_PROCESSING(SSprocessing, src) - var/datum/objective/darkspawn/O = new - objectives += O - O.update_explanation_text() - owner.announce_objectives() - return ..() - -/datum/antagonist/darkspawn/on_removal() - SSticker.mode.darkspawn -= owner - owner.special_role = null - owner.current.hud_used.psi_counter.invisibility = initial(owner.current.hud_used.psi_counter.invisibility) - owner.current.hud_used.psi_counter.maptext = "" - STOP_PROCESSING(SSprocessing, src) - return ..() - -/datum/antagonist/darkspawn/apply_innate_effects(mob/living/mob_override) - var/mob/living/current_mob = mob_override || owner.current - handle_clown_mutation(current_mob, mob_override ? null : "Our powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.") - current_mob.grant_language(/datum/language/darkspawn) - add_team_hud(current_mob) - -/datum/antagonist/darkspawn/remove_innate_effects() - owner.current.remove_language(/datum/language/darkspawn) - -//Round end stuff -/datum/antagonist/darkspawn/proc/check_darkspawn_death() - for(var/DM in get_antag_minds(/datum/antagonist/darkspawn)) - var/datum/mind/dark_mind = DM - if(istype(dark_mind)) - if((dark_mind) && (dark_mind.current.stat != DEAD) && ishuman(dark_mind.current)) - return FALSE - return TRUE - -/datum/antagonist/darkspawn/roundend_report() - return "[owner ? printplayer(owner) : "Unnamed Darkspawn"]" - -/datum/antagonist/darkspawn/roundend_report_header() - if(SSticker.mode.sacrament_done) - return "The darkspawn have completed the Sacrament!
" - else if(!SSticker.mode.sacrament_done && check_darkspawn_death()) - return "The darkspawn have been killed by the crew!
" - else if(!SSticker.mode.sacrament_done && SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) - return "The crew escaped the station before the darkspawn could complete the Sacrament!
" - else - return "The darkspawn have failed!
" - -//Admin panel stuff - -/datum/antagonist/darkspawn/antag_panel_data() - . = "Abilities:
" - for(var/V in abilities) - var/datum/action/innate/darkspawn/D = has_ability(V) - if(D && istype(D)) - . += "[D.name] ([D.id])
" - . += "
Upgrades:
" - for(var/V in upgrades) - . += "[V]
" - -/datum/antagonist/darkspawn/get_admin_commands() - . = ..() - .["Give Ability"] = CALLBACK(src, PROC_REF(admin_give_ability)) - .["Take Ability"] = CALLBACK(src, PROC_REF(admin_take_ability)) - if(darkspawn_state == MUNDANE) - .["Divulge"] = CALLBACK(src, PROC_REF(divulge)) - .["Force-Divulge (Obvious)"] = CALLBACK(src, PROC_REF(force_divulge)) - else if(darkspawn_state == DIVULGED) - .["Give Upgrade"] = CALLBACK(src, PROC_REF(admin_give_upgrade)) - .["[psi]/[psi_cap] Psi"] = CALLBACK(src, PROC_REF(admin_edit_psi)) - .["[lucidity] Lucidity"] = CALLBACK(src, PROC_REF(admin_edit_lucidity)) - .["[lucidity_drained] / [SSticker.mode.required_succs] Unique Lucidity"] = CALLBACK(src, PROC_REF(admin_edit_lucidity_drained)) - .["Sacrament (ENDS THE ROUND)"] = CALLBACK(src, PROC_REF(sacrament)) - -/datum/antagonist/darkspawn/proc/admin_give_ability(mob/admin) - var/id = stripped_input(admin, "Enter an ability ID, for \"all\" to give all of them.", "Give Ability") - if(!id) - return - if(has_ability(id)) - to_chat(admin, span_warning("[owner.current] already has this ability!")) - return - if(id != "all") - add_ability(id) - to_chat(admin, span_notice("Gave [owner.current] the ability \"[id]\".")) - else - for(var/V in subtypesof(/datum/action/innate/darkspawn)) - var/datum/action/innate/darkspawn/D = V - if(!has_ability(initial(D.id)) && !initial(D.blacklisted)) - add_ability(initial(D.id)) - to_chat(admin, span_notice("Gave [owner.current] all abilities.")) - -/datum/antagonist/darkspawn/proc/admin_take_ability(mob/admin) - var/id = stripped_input(admin, "Enter an ability ID.", "Take Ability") - if(!id) - return - if(!has_ability(id)) - to_chat(admin, span_warning("[owner.current] does not have this ability!")) - return - remove_ability(id) - to_chat(admin, span_danger("Took from [owner.current] the ability \"[id]\".")) - -/datum/antagonist/darkspawn/proc/admin_give_upgrade(mob/admin) - var/id = stripped_input(admin, "Enter an upgrade ID, for \"all\" to give all of them.", "Give Upgrade") - if(!id) - return - if(has_upgrade(id)) - to_chat(admin, span_warning("[owner.current] already has this upgrade!")) - return - if(id != "all") - add_upgrade(id) - to_chat(admin, span_notice("Gave [owner.current] the upgrade \"[id]\".")) - else - for(var/V in subtypesof(/datum/darkspawn_upgrade)) - var/datum/darkspawn_upgrade/D = V - if(!has_upgrade(initial(D.id))) - add_upgrade(initial(D.id)) - to_chat(admin, span_notice("Gave [owner.current] all upgrades.")) - -/datum/antagonist/darkspawn/proc/admin_edit_psi(mob/admin) - var/new_psi = input(admin, "Enter a new psi amount. (Current: [psi]/[psi_cap])", "Change Psi", psi) as null|num - if(!new_psi) - return - new_psi = clamp(new_psi, 0, psi_cap) - psi = new_psi - -/datum/antagonist/darkspawn/proc/admin_edit_lucidity(mob/admin) - var/newcidity = input(admin, "Enter a new lucidity amount. (Current: [lucidity])", "Change Lucidity", lucidity) as null|num - if(!newcidity) - return - newcidity = max(0, newcidity) - lucidity = newcidity - -/datum/antagonist/darkspawn/proc/admin_edit_lucidity_drained(mob/admin) - var/newcidity = input(admin, "Enter a new lucidity amount. (Current: [lucidity_drained])", "Change Lucidity Drained", lucidity_drained) as null|num - if(!newcidity) - return - newcidity = max(0, newcidity) - lucidity_drained = newcidity - -/datum/antagonist/darkspawn/greet() - to_chat(owner.current, "You are a darkspawn!") - to_chat(owner.current, "Append :[MODE_KEY_DARKSPAWN] or .[MODE_KEY_DARKSPAWN] before your message to silently speak with any other darkspawn.") - to_chat(owner.current, "When you're ready, retreat to a hidden location and Divulge to shed your human skin.") - to_chat(owner.current, span_boldwarning("If you do not do this within twenty five minutes, this will happen involuntarily. Prepare quickly.")) - to_chat(owner.current, "Remember that this will make you die in the light and heal in the dark - keep to the shadows.") - owner.current.playsound_local(get_turf(owner.current), 'yogstation/sound/ambience/antag/darkspawn.ogg', 50, FALSE) - -/datum/objective/darkspawn - explanation_text = "Become lucid and perform the Sacrament." - -/datum/objective/darkspawn/update_explanation_text() - explanation_text = "Become lucid and perform the Sacrament. You will need to devour [SSticker.mode.required_succs] different people's wills and purchase all passive upgrades to do so." - -/datum/objective/darkspawn/check_completion() - if(..()) - return TRUE - return (SSticker.mode.sacrament_done) - -// Darkspawn-related things like Psi // - -/datum/antagonist/darkspawn/process() //This is here since it controls most of the Psi stuff - psi = min(psi, psi_cap) - if(psi != psi_cap) - psi_regen_ticks-- - if(!psi_regen_ticks) - regenerate_psi() - update_psi_hud() - -/datum/antagonist/darkspawn/proc/has_psi(amt) - return psi >= amt - -/datum/antagonist/darkspawn/proc/use_psi(amt) - if(!has_psi(amt)) - return - psi_regen_ticks = psi_regen_delay - psi_used_since_regen += amt - psi -= amt - psi = round(psi, 0.2) - update_psi_hud() - return TRUE - -/datum/antagonist/darkspawn/proc/regenerate_psi() - set waitfor = FALSE - if(psi_regenerating) - return - psi_regenerating = TRUE - var/total_regen = min(psi_regen, psi_used_since_regen) - for(var/i in 1 to psi_cap) //tick it up very quickly instead of just increasing it by the regen; also include a failsafe to avoid infinite loops - if(!total_regen || psi >= psi_cap) - break - psi = min(psi + 1, psi_cap) - total_regen-- - update_psi_hud() - sleep(0.05 SECONDS) - psi_used_since_regen = 0 - psi_regen_ticks = psi_regen_delay - psi_regenerating = FALSE - return TRUE - -/datum/antagonist/darkspawn/proc/update_psi_hud() - if(!owner.current || !owner.current.hud_used) - return - var/atom/movable/screen/counter = owner.current.hud_used.psi_counter - counter.maptext = ANTAG_MAPTEXT(psi, COLOR_DARKSPAWN_PSI) - -/datum/antagonist/darkspawn/proc/regain_abilities() - for(var/A in abilities) - var/datum/action/innate/darkspawn/ability = abilities[A] - if(ability) - ability.Remove(ability.owner) - ability.Grant(owner.current) - -/datum/antagonist/darkspawn/proc/has_ability(id) - if(isnull(abilities[id])) - return - return abilities[id] - -/datum/antagonist/darkspawn/proc/add_ability(id, silent, no_cost) - if(has_ability(id)) - return - for(var/V in subtypesof(/datum/action/innate/darkspawn)) - var/datum/action/innate/darkspawn/D = V - if(initial(D.id) == id) - var/datum/action/innate/darkspawn/action = new D - action.Grant(owner.current) - action.darkspawn = src - abilities[id] = action - if(!silent) - to_chat(owner.current, span_velvet("You have learned the [action.name] ability.")) - if(!no_cost) - lucidity = max(0, lucidity - action.lucidity_price) - return TRUE - -/datum/antagonist/darkspawn/proc/remove_ability(id, silent) - if(!has_ability(id)) - return - var/datum/action/innate/darkspawn/D = abilities[id] - if(!silent) - to_chat(owner.current, span_velvet("You have lost the [D.name] ability.")) - D.Remove(owner.current) - abilities -= D - QDEL_NULL(D) - return TRUE - -/datum/antagonist/darkspawn/proc/has_upgrade(id) - return upgrades[id] - -/datum/antagonist/darkspawn/proc/add_upgrade(id, silent, no_cost) - if(has_upgrade(id)) - return - for(var/V in subtypesof(/datum/darkspawn_upgrade)) - var/datum/darkspawn_upgrade/_U = V - if(initial(_U.id) == id) - var/datum/darkspawn_upgrade/U = new _U(src) - upgrades[id] = TRUE - if(!silent) - to_chat(owner.current, "You have adapted the \"[U.name]\" upgrade.") - if(!no_cost) - lucidity = max(0, lucidity - initial(U.lucidity_price)) - U.unlock() - -/datum/antagonist/darkspawn/proc/begin_force_divulge() - if(darkspawn_state != MUNDANE) - return - to_chat(owner.current, span_userdanger("You feel the skin you're wearing crackling like paper - you will forcefully divulge soon! Get somewhere hidden and dark!")) - owner.current.playsound_local(owner.current, 'yogstation/sound/magic/divulge_01.ogg', 50, FALSE, pressure_affected = FALSE) - addtimer(CALLBACK(src, PROC_REF(force_divulge), 2 MINUTES)) - -/datum/antagonist/darkspawn/proc/force_divulge() - if(darkspawn_state != MUNDANE) - return - var/mob/living/carbon/C = owner.current - if(C && !ishuman(C)) - C.humanize() - var/mob/living/carbon/human/H = owner.current - if(!H) - owner.current.gib(TRUE) - H.visible_message(span_boldwarning("[H]'s skin begins to slough off in sheets!"), \ - span_userdanger("You can't maintain your disguise any more! It begins sloughing off!")) - playsound(H, 'yogstation/sound/creatures/darkspawn_force_divulge.ogg', 50, FALSE) - H.do_jitter_animation(1000) - var/processed_message = span_velvet("\[Mindlink\] [H.real_name] has not divulged in time and is now forcefully divulging.") - for(var/mob/M in GLOB.player_list) - if(M.stat != DEAD && isdarkspawn(M)) - to_chat(M, processed_message) - deadchat_broadcast(processed_message, null, H) - addtimer(CALLBACK(src, PROC_REF(divulge), TRUE), 2.5 SECONDS) - -/datum/antagonist/darkspawn/proc/divulge(forced = FALSE) - if(darkspawn_state >= DIVULGED) - return - if(forced) - owner.current.visible_message( - span_boldwarning("[owner.current]'s skin sloughs off, revealing black flesh covered in symbols!"), - span_userdanger("You have forcefully divulged!")) - var/mob/living/carbon/human/user = owner.current - to_chat(user, "Your mind has expanded. The Psi Web is now available. Avoid the light. Keep to the shadows. Your time will come.") - user.fully_heal() - user.set_species(/datum/species/darkspawn) - show_to_ghosts = TRUE - //Handles psi_web granting, has to be different to fulfill everything - psi_web = new(src) - psi_web_action = new(psi_web) - psi_web_action.Grant(owner.current) - psi_web_action.darkspawn = src - abilities[psi_web_action.id] = psi_web_action - add_ability("sacrament", TRUE) - add_ability("devour_will", TRUE) - add_ability("pass", TRUE) - remove_ability("divulge", TRUE) - darkspawn_state = DIVULGED - -/datum/antagonist/darkspawn/proc/sacrament() - var/mob/living/carbon/human/user = owner.current - if(!SSticker.mode.sacrament_done) - SSsecurity_level.set_level(SEC_LEVEL_GAMMA) - addtimer(CALLBACK(src, PROC_REF(sacrament_shuttle_call)), 50) - for(var/V in abilities) - remove_ability(abilities[V], TRUE) - for(var/datum/action/innate/darkspawn/leftover_ability in user.actions) - leftover_ability.Remove(user) - QDEL_NULL(leftover_ability) - // Spawn the cosmic progenitor - var/mob/living/simple_animal/hostile/darkspawn_progenitor/progenitor = new(get_turf(user)) - user.status_flags |= GODMODE - user.mind.transfer_to(progenitor) - var/datum/action/cooldown/spell/list_target/progenitor_curse/curse = new(progenitor) - curse.Grant(progenitor) - sound_to_playing_players('yogstation/sound/magic/sacrament_complete.ogg', 50, FALSE, pressure_affected = FALSE) - psi = 9999 - psi_cap = 9999 - psi_regen = 9999 - psi_regen_delay = 1 - SSticker.mode.sacrament_done = TRUE - darkspawn_state = PROGENITOR - QDEL_IN(user, 5) - -/datum/antagonist/darkspawn/proc/sacrament_shuttle_call() - SSshuttle.emergency.request(null, 0, null, 0.1) - -/datum/antagonist/darkspawn/get_preview_icon() - var/icon/darkspawn_icon = icon('yogstation/icons/mob/darkspawn_progenitor.dmi', "darkspawn_progenitor") - - darkspawn_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE) - - return darkspawn_icon - -#undef MUNDANE -#undef DIVULGED -#undef PROGENITOR diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/__psi_web.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/__psi_web.dm deleted file mode 100644 index 3b46959fd6e4..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/__psi_web.dm +++ /dev/null @@ -1,91 +0,0 @@ -//Used to access the Psi Web to buy abilities. -//Accesses the Psi Web, which darkspawn use to purchase abilities using lucidity. Lucidity is drained from people using the Devour Will ability. -/datum/antag_menu/psi_web - name = "psi web" - ui_name = "PsiWeb" - -/datum/antag_menu/psi_web/ui_data(mob/user) - var/list/data = list() - var/datum/antagonist/darkspawn/darkspawn = antag_datum - - if(!istype(darkspawn)) - CRASH("darkspawn menu started with wrong datum.") - - data["lucidity"] = "[darkspawn.lucidity] | [darkspawn.lucidity_drained] / 20 unique drained total" - - var/list/abilities = list() - var/list/upgrades = list() - - for(var/path in subtypesof(/datum/action/innate/darkspawn)) - var/datum/action/innate/darkspawn/ability = path - - if(initial(ability.blacklisted)) - continue - - var/list/AL = list() //This is mostly copy-pasted from the cellular emporium, but it should be fine regardless - AL["name"] = initial(ability.name) - AL["id"] = initial(ability.id) - AL["desc"] = initial(ability.desc) - AL["psi_cost"] = "[initial(ability.psi_cost)][initial(ability.psi_addendum)]" - AL["lucidity_cost"] = initial(ability.lucidity_price) - AL["owned"] = darkspawn.has_ability(initial(ability.id)) - AL["can_purchase"] = !AL["owned"] && darkspawn.lucidity >= initial(ability.lucidity_price) - - abilities += list(AL) - - data["abilities"] = abilities - - for(var/path in subtypesof(/datum/darkspawn_upgrade)) - var/datum/darkspawn_upgrade/upgrade = path - - var/list/DE = list() - DE["name"] = initial(upgrade.name) - DE["id"] = initial(upgrade.id) - DE["desc"] = initial(upgrade.desc) - DE["lucidity_cost"] = initial(upgrade.lucidity_price) - DE["owned"] = darkspawn.has_upgrade(initial(upgrade.id)) - DE["can_purchase"] = !DE["owned"] && darkspawn.lucidity >= initial(upgrade.lucidity_price) - - upgrades += list(DE) - - data["upgrades"] = upgrades - - return data - -/datum/antag_menu/psi_web/ui_act(action, params) - if(..()) - return - var/datum/antagonist/darkspawn/darkspawn = antag_datum - switch(action) - if("unlock") - darkspawn.add_ability(params["id"]) - if("upgrade") - darkspawn.add_upgrade(params["id"]) - -/datum/action/innate/darkspawn/psi_web - name = "Psi Web" - id = "psi_web" - desc = "Access the Mindlink directly to unlock and upgrade your supernatural powers." - button_icon_state = "psi_web" - check_flags = AB_CHECK_CONSCIOUS - blacklisted = TRUE - psi_cost = 0 - var/datum/antag_menu/psi_web/psi_web - -/datum/action/innate/darkspawn/psi_web/New(our_target) - . = ..() - if(istype(our_target, /datum/antag_menu/psi_web)) - psi_web = our_target - else - CRASH("psi_web action created with non web.") - -/datum/action/innate/darkspawn/psi_web/Destroy() - psi_web = null - return ..() - -/datum/action/innate/darkspawn/psi_web/Activate() - if(!darkspawn) - return - to_chat(usr, "You retreat inwards and touch the Mindlink...") - psi_web.ui_interact(usr) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_divulge.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_divulge.dm index 4ce18f334fee..7da0fd9eaa07 100644 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_divulge.dm +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_divulge.dm @@ -1,48 +1,82 @@ //A channeled ability that turns the darkspawn into their main form. -/datum/action/innate/darkspawn/divulge +/datum/action/cooldown/spell/divulge name = "Divulge" - id = "divulge" desc = "Sheds your human disguise. This is obvious and so should be done in a secluded area. You cannot reverse this." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" button_icon_state = "divulge" check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS | AB_CHECK_LYING - blacklisted = TRUE + spell_requirements = NONE + ///if they're actively divulging + var/in_use = FALSE -/datum/action/innate/darkspawn/divulge/Activate() +/datum/action/cooldown/spell/divulge/IsAvailable(feedback) + if(in_use) + if (feedback) + owner.balloon_alert(owner, "already in use!") + return + return ..() + +/datum/action/cooldown/spell/divulge/cast(atom/cast_on) set waitfor = FALSE + . = ..() var/mob/living/carbon/human/user = usr var/turf/spot = get_turf(user) if(!ishuman(user)) to_chat(user, span_warning("You need to be human-er to do that!")) return - if(isethereal(user)) - user.set_light(0) - if(spot.get_lumcount() > DARKSPAWN_DIM_LIGHT) + + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(user) + if(darkspawn) + if(!darkspawn.picked_class) + to_chat(user, span_warning("You must pick a class to divulge!")) + return + if(darkspawn.darkspawn_state != DARKSPAWN_MUNDANE) //if they've somehow gone back to being a non-darkspawn, let them skip the process + darkspawn.divulge() + return + + if(isethereal(user))//disable the light for long enough to start divulge + user.dna.species.spec_emp_act(user, EMP_HEAVY) + + if(spot.get_lumcount() > SHADOW_SPECIES_DIM_LIGHT) to_chat(user, span_warning("You are only able to divulge in darkness!")) return - if(alert(user, "You are ready to divulge. Are you sure?", name, "Yes", "No") == "No") + + if(tgui_alert(user, "You are ready to divulge. This will leave you vulnerable and take a long time, are you sure?", name, list("Yes", "No")) != "Yes") return in_use = TRUE + if(istype(user.dna.species, /datum/species/pod)) to_chat(user, span_notice("Your disguise is stabilized by the divulgance...")) user.reagents.add_reagent(/datum/reagent/medicine/salbutamol,20) + if(istype(user.dna.species, /datum/species/plasmaman)) to_chat(user, span_notice("Your bones harden to protect you from the atmosphere...")) user.set_species(/datum/species/skeleton) - user.visible_message("[user] flaps their wings.", span_velvet("You begin creating a psychic barrier around yourself...")) + + to_chat(user, span_velvet("You begin creating a psychic barrier around yourself...")) if(!do_after(user, 3 SECONDS, user)) in_use = FALSE return + for(var/turf/T in RANGE_TURFS(1, user)) + if(isclosedturf(T)) + continue + new/obj/structure/psionic_barrier(T, 35 SECONDS) + user.visible_message(span_warning("A vortex of violet energies surrounds [user]!"), span_velvet("Your barrier will keep you shielded to a point..")) + var/image/alert_overlay = image('yogstation/icons/mob/actions/actions_darkspawn.dmi', "divulge") notify_ghosts("Darkspawn [user.real_name] has begun divulging at [get_area(user)]! ", source = user, ghost_sound = 'yogstation/sound/magic/devour_will_victim.ogg', alert_overlay = alert_overlay, action = NOTIFY_ORBIT) - user.visible_message(span_warning("A vortex of violet energies surrounds [user]!"), span_velvet("Your barrier will keep you shielded to a point..")) - user.visible_message(span_danger("[user] slowly rises into the air, their belongings falling away, and begins to shimmer..."), \ - "You begin the removal of your human disguise. You will be completely vulnerable during this time.") + + user.visible_message(span_danger("[user] slowly rises into the air, their belongings falling away, and begins to shimmer..."), span_progenitor("You begin the removal of your human disguise. You will be completely vulnerable during this time.")) user.setDir(SOUTH) - for(var/obj/item/I in user) - user.dropItemToGround(I) - for(var/turf/T in RANGE_TURFS(1, user)) - new/obj/structure/psionic_barrier(T, 500) + user.unequip_everything() + for(var/stage in 1 to 3) + if(isethereal(user))//keep the light disabled + user.dna.species.spec_emp_act(user, EMP_HEAVY) switch(stage) if(1) user.visible_message(span_userdanger("Vibrations pass through the air. [user]'s eyes begin to glow a deep violet."), \ @@ -50,42 +84,44 @@ playsound(user, 'yogstation/sound/magic/divulge_01.ogg', 30, 0) if(2) user.visible_message(span_userdanger("Gravity fluctuates. Psychic tendrils extend outward and feel blindly around the area."), \ - span_velvet("Gravity around you fluctuates. You tentatively reach out, feel with your mind.")) - user.Shake(0, 3, 750) //50 loops in a second times 15 seconds = 750 loops + span_velvet("Gravity around you fluctuates. You tentatively reach out, feeling with your mind.")) + user.Shake(0, 3, 500) //50 loops in a second times 15 seconds = 750 loops playsound(user, 'yogstation/sound/magic/divulge_02.ogg', 40, 0) if(3) - user.visible_message(span_userdanger("Sigils form along [user]'s body. \His skin blackens as \he glows a blinding purple."), \ + user.visible_message(span_userdanger("Sigils form along [user]'s body. [user.p_their()] skin blackens as [user.p_they()] glow a blinding purple."), \ span_velvet("Your body begins to warp. Sigils etch themselves upon your flesh.")) - animate(user, color = list(rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0)), time = 15 SECONDS) //Produces a slow skin-blackening effect + animate(user, color = list(rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0)), time = 10 SECONDS) //Produces a slow skin-blackening effect playsound(user, 'yogstation/sound/magic/divulge_03.ogg', 50, 0) - if(!do_after(user, 15 SECONDS, user)) + if(!do_after(user, 10 SECONDS, user)) user.visible_message(span_warning("[user] falls to the ground!"), span_userdanger("Your transformation was interrupted!")) animate(user, color = initial(user.color), pixel_y = initial(user.pixel_y), time = 1 SECONDS) in_use = FALSE return + if(isethereal(user))//keep the light disabled + user.dna.species.spec_emp_act(user, EMP_HEAVY) playsound(user, 'yogstation/sound/magic/divulge_ending.ogg', 50, 0) - user.visible_message(span_userdanger("[user] rises into the air, crackling with power!"), "Your mind...! can't--- THINK--") + user.visible_message(span_userdanger("[user] rises into the air, crackling with power!"), span_progenitor("Your mind...! can't--- THINK--")) animate(user, pixel_y = user.pixel_y + 8, time = 6 SECONDS) sleep(4.5 SECONDS) + user.Shake(5, 5, 11 SECONDS) - for(var/i in 1 to 20) - to_chat(user, "[pick("I- I- I-", "Mind-", "Sigils-", "Can't think-", "POWER-","TAKE-", "M-M-MOOORE-")]") - sleep(0.11 SECONDS) //Spooky flavor message spam - user.visible_message(span_userdanger("A tremendous shockwave emanates from [user]!"), "YOU ARE FREE!!") + var/list/spooky = list( + "I- I- I-", "Mind-", "Sigils-", "Can't think-", + "POWER-","TAKE-", "M-M-MOOORE-", + "THINK-", "EMBRACE-", "BECOME-") + for(var/i in 1 to 40) + to_chat(user, span_progenitor("[pick(spooky)]")) + sleep(0.05 SECONDS) //Spooky flavor message spam + + user.visible_message(span_userdanger("A tremendous shockwave emanates from [user]!"), span_progenitor("YOU ARE FREE!!")) playsound(user, 'yogstation/sound/magic/divulge_end.ogg', 50, 0) animate(user, color = initial(user.color), pixel_y = initial(user.pixel_y), time = 3 SECONDS) + for(var/mob/living/L in view(7, user)) - if(L == user) + if(is_team_darkspawn(L) || L == user) //probably won't have thralls yet, but might as well check just in case continue L.flash_act(1, 1) L.Knockdown(5 SECONDS) - var/old_name = user.real_name - darkspawn.divulge() - var/processed_message = span_velvet("\[Mindlink\] [old_name] has removed their human disguise and is now [user.real_name].") - for(var/T in GLOB.alive_mob_list) - var/mob/M = T - if(is_darkspawn_or_veil(M)) - to_chat(M, processed_message) - for(var/T in GLOB.dead_mob_list) - var/mob/M = T - to_chat(M, "(F) [processed_message]") + + if(darkspawn)//sanity check + darkspawn.divulge() diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_sacrament.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_sacrament.dm index b6561a5d0fd4..7e11b91d9f85 100644 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_sacrament.dm +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/_sacrament.dm @@ -1,86 +1,118 @@ //Turns the darkspawn into a progenitor. -/datum/action/innate/darkspawn/sacrament +/datum/action/cooldown/spell/sacrament name = "Sacrament" - id = "sacrament" - desc = "Ascends into a progenitor. Unless someone else has performed the Sacrament, you must have drained lucidity from 15-30 (check your objective) different people for this to work, and purchased all passive upgrades." + desc = "Ascends into a progenitor. You must have drained lucidity from a certain number of different people for this to work." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" button_icon_state = "sacrament" check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS - blacklisted = TRUE //baseline + spell_requirements = NONE var/datum/looping_sound/sacrament/soundloop + var/datum/antagonist/darkspawn/darkspawn + ///if they're actively performing the sacrament + var/in_use = FALSE -/datum/action/innate/darkspawn/sacrament/Activate() - if(SSticker.mode.sacrament_done) - darkspawn.sacrament() +/datum/action/cooldown/spell/sacrament/IsAvailable(feedback) + if(in_use) + if (feedback) + owner.balloon_alert(owner, "already in use!") return - if(!darkspawn || darkspawn.lucidity_drained < SSticker.mode.required_succs) - to_chat(usr, span_warning("You do not have enough unique lucidity! ([darkspawn.lucidity_drained] / [SSticker.mode.required_succs])")) + return ..() + +/datum/action/cooldown/spell/sacrament/cast(atom/cast_on) + . = ..() + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + if(!darkspawn) + to_chat(owner, span_warning("Error with non darkspawn using sacrament spell")) + return + var/datum/team/darkspawn/team = darkspawn.get_team() + if(!team) + stack_trace("Non-Darkspawn or Darkspawn without team tried to perform the sacrament") return - var/list/unpurchased_upgrades = list() - for(var/V in subtypesof(/datum/darkspawn_upgrade)) - var/datum/darkspawn_upgrade/D = V - if(!darkspawn.has_upgrade(initial(D.id))) - unpurchased_upgrades += initial(D.name) - if(unpurchased_upgrades.len) - var/upgrade_string = unpurchased_upgrades.Join(", ") - to_chat(usr, "[span_warning("You have not purchased all passive upgrades! You are missing:")] [span_danger("[upgrade_string].")]") + if(team.lucidity < team.required_succs) + to_chat(owner, span_warning("You do not have enough unique lucidity! ([team.lucidity] / [team.required_succs])")) return - if(alert(usr, "The Sacrament is ready! Are you prepared?", name, "Yes", "No") == "No") + if(tgui_alert(owner, "The Sacrament is ready! Are you prepared?", name, list("Yes", "No")) != "Yes") return + if(GLOB.sacrament_done) + darkspawn.sacrament() //if someone else has already done the sacrament, skip to the good part + return + + var/mob/living/carbon/human/user = owner + if(!user || !istype(user))//sanity check + return + + var/processed_message = span_progenitor("\[Mindlink\] [user] has begun performing the sacrament.") + for(var/mob/M as anything in GLOB.alive_mob_list) + if(is_darkspawn_or_thrall(M)) + to_chat(M, processed_message) + deadchat_broadcast(processed_message, null, user) + in_use = TRUE - var/mob/living/carbon/human/user = usr + user.visible_message(span_warning("[user]'s sigils flare as energy swirls around them..."), span_velvet("You begin creating a psychic barrier around yourself...")) - playsound(user, 'yogstation/sound/magic/sacrament_begin.ogg', 50, FALSE) + sound_to_playing_players('yogstation/sound/magic/sacrament_begin.ogg', 50, FALSE, pressure_affected = FALSE) if(!do_after(user, 3 SECONDS, user)) in_use = FALSE return - var/image/alert_overlay = image('yogstation/icons/mob/actions/actions_darkspawn.dmi', "sacrament") - notify_ghosts("Darkspawn [user.real_name] has begun the Sacrament at [get_area(user)]! ", source = user, ghost_sound = 'yogstation/sound/magic/devour_will_victim.ogg', alert_overlay = alert_overlay, action = NOTIFY_ORBIT) user.visible_message(span_warning("A vortex of violet energies surrounds [user]!"), span_velvet("Your barrier will protect you.")) - user.visible_message(span_danger("[user] suddenly jolts into the air, pulsing with screaming violet light."), \ - "You begin the Sacrament.") - soundloop = new(GLOB.player_list, TRUE, TRUE) for(var/turf/T in RANGE_TURFS(2, user)) + if(isclosedturf(T)) + continue new/obj/structure/psionic_barrier(T, 340) + + var/image/alert_overlay = image('yogstation/icons/mob/actions/actions_darkspawn.dmi', "sacrament") + notify_ghosts("Darkspawn [user.real_name] has begun the Sacrament at [get_area(user)]! ", source = user, ghost_sound = 'yogstation/sound/magic/devour_will_victim.ogg', alert_overlay = alert_overlay, action = NOTIFY_ORBIT) + soundloop = new(GLOB.player_list, TRUE, TRUE) + + user.visible_message(span_danger("[user] suddenly jolts into the air, pulsing with screaming violet light."), span_progenitor("You begin the Sacrament.")) + for(var/stage in 1 to 2) soundloop.stage = stage switch(stage) if(1) user.visible_message(span_userdanger("[user]'s sigils howl out light. Their limbs twist and move, glowing cracks forming across their chitin."), \ - span_velvet("Power... power... flooding through you, the dreams and thoughts of those you've touched whispering in your ears...")) - for(var/mob/M in GLOB.player_list) - M.playsound_local(M, 'yogstation/sound/magic/sacrament_01.ogg', 20, FALSE, pressure_affected = FALSE) - if(M != user) - to_chat(M, span_warning("What is that sound...?")) + span_velvet("Power... power... flooding through you, the dreams and thoughts of the wills you've devoured whispering in your ears...")) + sound_to_playing_players('yogstation/sound/magic/sacrament_01.ogg', 20, FALSE, pressure_affected = FALSE) if(2) - user.visible_message(span_userdanger("[user] begins to... grow.."), \ - span_velvet("Yes! Yes! You feel the weak mortal shell coming apart!")) - for(var/mob/M in GLOB.player_list) - M.playsound_local(M, 'yogstation/sound/magic/sacrament_02.ogg', 20, FALSE, pressure_affected = FALSE) + for(var/turf/T in range(10, owner)) //add spooky visuals to the mounting power + if(prob(10)) + addtimer(CALLBACK(src, PROC_REF(unleashed_psi), T), rand(1, 15 SECONDS)) + animate(user, transform = matrix() * 2, time = 15 SECONDS) + user.visible_message(span_userdanger("[user] begins to... grow.."), span_progenitor("Your mortal shell begins to fracture as power swells within it!")) + sound_to_playing_players('yogstation/sound/magic/sacrament_02.ogg', 20, FALSE, pressure_affected = FALSE) + + //if you somehow get interrupted if(!do_after(user, 15 SECONDS, user)) user.visible_message(span_warning("[user] falls to the ground!"), span_userdanger("Your transformation was interrupted!")) animate(user, transform = matrix(), pixel_y = initial(user.pixel_y), time = 3 SECONDS) in_use = FALSE QDEL_NULL(soundloop) return - for(var/mob/M in GLOB.player_list) - M.playsound_local(M, 'yogstation/sound/magic/sacrament_ending.ogg', 75, FALSE, pressure_affected = FALSE) + + soundloop.stage = 3 - user.visible_message(span_userdanger("[user] rises into the air, crackling with power!"), "AND THE WEAK WILL KNOW FEAR--") - for(var/turf/T in range(7, owner)) - if(prob(25)) - addtimer(CALLBACK(src, PROC_REF(unleashed_psi), T), rand(0.1, 4) SECONDS) - addtimer(CALLBACK(src, PROC_REF(shatter_lights)), 3.5 SECONDS) - QDEL_IN(soundloop, 39) animate(user, pixel_y = user.pixel_y + 20, time = 4 SECONDS) - addtimer(CALLBACK(darkspawn, TYPE_PROC_REF(/datum/antagonist/darkspawn, sacrament)), 4 SECONDS) + user.visible_message(span_userdanger("[user] rises into the air, crackling with power!")) -/datum/action/innate/darkspawn/sacrament/proc/unleashed_psi(turf/T) - playsound(T, 'yogstation/sound/magic/divulge_end.ogg', 25, FALSE) - new/obj/effect/temp_visual/revenant/cracks(T) + for(var/turf/T in range(10, owner)) + if(prob(35)) + addtimer(CALLBACK(src, PROC_REF(unleashed_psi), T), rand(1, 40)) + + addtimer(CALLBACK(src, PROC_REF(finalize), user), 3 SECONDS)//ominous delay that's eerily quiet -/datum/action/innate/darkspawn/sacrament/proc/shatter_lights() - if(SSticker.mode.sacrament_done) +/datum/action/cooldown/spell/sacrament/proc/finalize(mob/living/carbon/human/user) + to_chat(user, span_progenitor("AND THE WEAK WILL KNOW FEAR--")) + sound_to_playing_players('yogstation/sound/magic/sacrament_ending.ogg', 75, FALSE, pressure_affected = FALSE) + QDEL_IN(soundloop, 1 SECONDS) + addtimer(CALLBACK(darkspawn, TYPE_PROC_REF(/datum/antagonist/darkspawn, sacrament)), 1 SECONDS) + +/datum/action/cooldown/spell/sacrament/proc/unleashed_psi(turf/T) + if(!in_use) return - for(var/obj/machinery/light/light in SSmachines.processing) - light.break_light_tube() + playsound(T, 'yogstation/sound/magic/divulge_end.ogg', 25, FALSE) + new/obj/effect/temp_visual/revenant/cracks(T) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/core_abilities.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/core_abilities.dm new file mode 100644 index 000000000000..24569d4798ff --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/core_abilities.dm @@ -0,0 +1,372 @@ +////////////////////////////////////////////////////////////////////////// +//--------------------Abilities all three classes get-------------------// +////////////////////////////////////////////////////////////////////////// +/obj/item/melee/touch_attack/darkspawn + name = "Psionic hand" + desc = "Concentrated psionic power, primed to toy with mortal minds." + icon_state = "flagellation" + item_state = "hivemind" + +/obj/item/melee/touch_attack/devour_will + name = "Psionic hand" + desc = "Concentrated psionic power, primed to toy with mortal minds." + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "dark_bead" + item_state = "hivemind" +////////////////////////////////////////////////////////////////////////// +//-----------------------Main progression ability-----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/devour_will + name = "Devour Will" + desc = "Creates a dark bead that can be used on a human to fully recharge Psi, gain one lucidity, and knock them unconscious. The victim will be stunned for the duration of the channel, being interrupted \ + will knock both you and the victim down. Costs 5 Psi." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + sound = null + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "devour_will" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_LYING | AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + invocation_type = INVOCATION_NONE + psi_cost = 5 + hand_path = /obj/item/melee/touch_attack/devour_will + var/eating = FALSE //If we're devouring someone's will + +/datum/action/cooldown/spell/touch/devour_will/can_cast_spell(feedback) + if(eating) + return + return ..() + +/datum/action/cooldown/spell/touch/devour_will/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/touch/devour_will/cast(mob/living/carbon/cast_on) + . = ..() + playsound(owner, 'yogstation/sound/magic/devour_will_form.ogg', 50, 1) + +/datum/action/cooldown/spell/touch/devour_will/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/target, mob/living/carbon/caster) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(caster) + if(!darkspawn || eating || target == caster) + return + if(!target.mind) + to_chat(caster, span_warning("You cannot drain the mindless.")) + return + if(is_darkspawn_or_thrall(target)) + to_chat(caster, span_warning("You cannot drain allies.")) + return + if(!istype(target)) + to_chat(caster, span_warning("[target]'s mind is too pitiful to be of any use.")) + return + if(target.stat == DEAD) + to_chat(caster, span_warning("[target] is too weak to drain.")) + return + if(target.has_status_effect(STATUS_EFFECT_DEVOURED_WILL)) + to_chat(caster, span_warning("[target]'s mind has not yet recovered enough willpower to be worth devouring.")) + return + + caster.Immobilize(1 SECONDS) // So they don't accidentally move while beading + target.silent += 5 + + caster.balloon_alert(caster, "Cera ko...") + to_chat(caster, span_velvet("You begin siphoning [target]'s will...")) + target.emote("scream") + target.visible_message(span_danger("[target] suddenly howls and clutches their face as violet light screams from their eyes!"), span_userdanger("AAAAAAAAAAAAAAA-")) + playsound(target, 'yogstation/sound/magic/devour_will_long.ogg', 65, FALSE) + + eating = TRUE + if(!do_after(caster, 5 SECONDS, target)) + to_chat(target, span_boldwarning("All right... You're all right.")) + caster.Knockdown(5 SECONDS) + eating = FALSE + return FALSE + eating = FALSE + + if(target.has_status_effect(STATUS_EFFECT_DEVOURED_WILL)) + to_chat(caster, span_warning("[target]'s mind has not yet recovered enough willpower to be worth devouring.")) + return + + //put the victim to sleep before the visible_message proc so the victim doesn't see it + to_chat(target, span_progenitor("You suddenly feel... empty. Thoughts try to form, but flit away. You slip into a deep, deep slumber...")) + playsound(target, 'yogstation/sound/magic/devour_will_end.ogg', 75, FALSE) + target.playsound_local(target, 'yogstation/sound/magic/devour_will_victim.ogg', 50, FALSE) + target.Unconscious(5 SECONDS) + + //get how much lucidity and willpower will be given + var/willpower_amount = 2 + var/lucidity_amount = 1 + if(HAS_TRAIT(target, TRAIT_DARKSPAWN_DEVOURED)) //change the numbers before text + lucidity_amount = 0 + willpower_amount *= 0.5 + willpower_amount = round(willpower_amount) //make sure it's a whole number still + + //format the text output to the darkspawn + var/list/self_text = list() + + caster.balloon_alert(caster, "...akkraup'dej") + self_text += span_velvet("You devour [target]'s will.") + if(HAS_TRAIT(target, TRAIT_DARKSPAWN_DEVOURED)) + self_text += span_warning("[target]'s mind is already damaged by previous devouring and has granted less willpower and no lucidity.") + else + self_text += span_velvet("This individual's lucidity brings you one step closer to the sacrament...") + self_text += span_warning("After meddling with [target]'s mind, they will grant less willpower and no lucidity any future times their will is devoured.") + self_text += span_warning("[target] is now severely weakened and will take some time to recover.") + caster.visible_message(span_warning("[caster] gently lowers [target] to the ground..."), self_text.Join("
")) + + //pass out the willpower and lucidity to the darkspawns + var/datum/team/darkspawn/team = darkspawn.get_team() + if(team) + team.grant_willpower(willpower_amount) + team.grant_lucidity(lucidity_amount) + + //apply the long-term debuffs to the victim + target.apply_status_effect(STATUS_EFFECT_BROKEN_WILL) + target.apply_status_effect(STATUS_EFFECT_DEVOURED_WILL) + ADD_TRAIT(target, TRAIT_DARKSPAWN_DEVOURED, type) + return TRUE + +////////////////////////////////////////////////////////////////////////// +//--------------------------Glorified handcuffs-------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/restrain_body + name = "Restrain body" + desc = "Forms rudimentary restraints on a target's hands." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + sound = null + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "restrain_body" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_LYING | AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + invocation_type = INVOCATION_NONE + hand_path = /obj/item/melee/touch_attack/darkspawn + psi_cost = 5 + //Boolean on whether we're tying someone's hands + var/tying = FALSE + +/datum/action/cooldown/spell/touch/restrain_body/can_cast_spell(feedback) + if(tying) + return + return ..() + +/datum/action/cooldown/spell/touch/restrain_body/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/touch/restrain_body/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/target, mob/living/carbon/caster) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(caster) + if(!darkspawn || tying || target == caster) //no tying yourself + return + if(is_darkspawn_or_thrall(target)) + to_chat(caster, span_warning("You cannot restrain allies.")) + return + if(!istype(target)) + to_chat(caster, span_warning("[target]'s mind is too pitiful to be of any use.")) + return + if(target.handcuffed) + to_chat(caster, span_warning("[target] is already restrained.")) + return + + caster.balloon_alert(caster, "Koce ra...") + to_chat(caster, span_velvet("You begin restraining [target]...")) + playsound(target, 'yogstation/sound/ambience/antag/veil_mind_gasp.ogg', 50, TRUE) + tying = TRUE + if(!do_after(caster, 1.5 SECONDS, target, progress = FALSE)) + tying = FALSE + return FALSE + tying = FALSE + + target.silent += 5 + + if(target.handcuffed) + to_chat(caster, span_warning("[target] is already restrained.")) + return + + playsound(target, 'yogstation/sound/magic/devour_will_form.ogg', 50, TRUE) + target.set_handcuffed(new /obj/item/restraints/handcuffs/darkspawn(target)) + target.update_handcuffed() + + return TRUE + +//the restrains in question +/obj/item/restraints/handcuffs/darkspawn + name = "shadow stitched restraints" + desc = "Bindings created by stitching together shadows." + icon_state = "handcuffAlien" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + breakouttime = 30 SECONDS + flags_1 = NONE + item_flags = DROPDEL + +/obj/item/restraints/handcuffs/darkspawn/Initialize(mapload) + . = ..() + add_atom_colour(COLOR_VELVET, FIXED_COLOUR_PRIORITY) + +////////////////////////////////////////////////////////////////////////// +//-----------------------Recall shuttle ability-------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/silver_tongue + name = "Silver Tongue" + desc = "When used near a communications console, allows you to forcefully transmit a message to Central Command, initiating a shuttle recall. Only usable if the shuttle is inbound. Costs 60 Psi." + button_icon_state = "silver_tongue" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + invocation_type = INVOCATION_NONE + antimagic_flags = NONE + panel = "Darkspawn" + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_IMMOBILE + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 60 + hand_path = /obj/item/melee/touch_attack/darkspawn + ///Boolean on whether this is in use. + var/in_use = FALSE + ///The time it takes to use this spell, used in do after and to play sounds as it goes. + var/duration = 8 SECONDS + +/datum/action/cooldown/spell/touch/silver_tongue/can_cast_spell(feedback) + if(SSshuttle.emergency.mode != SHUTTLE_CALL || in_use) + return + return ..() + +/datum/action/cooldown/spell/touch/silver_tongue/is_valid_target(atom/cast_on) + return istype(cast_on, /obj/machinery/computer/communications) + +/datum/action/cooldown/spell/touch/silver_tongue/cast_on_hand_hit(obj/item/melee/touch_attack/hand, obj/machinery/computer/communications/target, mob/living/carbon/caster) + if(in_use) + return + if(target.stat) + to_chat(owner, span_warning("[target] is depowered.")) + return FALSE + + caster.balloon_alert(caster, "[pick("Pda ykw'lpwe skwo h'kccaz ej.", "Pda aiank'cajyu eo kran.", "Oknnu, bkn swop'ejc ukqn pkza.", "Wke swo kxn'znaz xu hws psk.")]") + owner.visible_message(span_warning("[owner] briefly touches [target]'s screen, and the keys begin to move by themselves!"), span_velvet("You begin transmitting a recall message to Central Command...")) + in_use = TRUE + play_recall_sounds(target, (duration/10)-1) + if(!do_after(owner, duration, target)) + in_use = FALSE + return + in_use = FALSE + if(!target) + return + if(target.stat) + to_chat(owner, span_warning("[target] has lost power.")) + return + SSshuttle.emergency.cancel() + to_chat(owner, span_velvet("The ruse was a success. The shuttle is on its way back.")) + return TRUE + +/datum/action/cooldown/spell/touch/silver_tongue/proc/play_recall_sounds(obj/machinery/C, iterations) //neato sound effects + set waitfor = FALSE + if(!C || C.stat || !in_use) + return + playsound(C, "terminal_type", 50, TRUE) + if(prob(25)) + playsound(C, 'sound/machines/terminal_alert.ogg', 50, FALSE) + do_sparks(5, TRUE, get_turf(C)) + + if(iterations <= 0) + addtimer(CALLBACK(src, PROC_REF(end_recall_sounds), C), 0.4 SECONDS) + else + addtimer(CALLBACK(src, PROC_REF(play_recall_sounds), C, iterations - 1), 1 SECONDS) + +/datum/action/cooldown/spell/touch/silver_tongue/proc/end_recall_sounds(obj/machinery/C) //end the neato sound effects + set waitfor = FALSE + if(!C || C.stat || !in_use) + return + playsound(C, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + sleep(0.4 SECONDS) + if(!C || C.stat || !in_use) + return + playsound(C, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + +////////////////////////////////////////////////////////////////////////// +//-----------------Used for placing things into the world---------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/darkspawn_build + name = "Darkspawn building thing" + desc = "You shouldn't be able to see this." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "sacrament(old)" + antimagic_flags = NONE + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + psi_cost = 35 + cooldown_time = 15 SECONDS + cast_range = 2 + ///Whether or not the user is in the process of "building" + var/casting = FALSE + ///How long it takes to "build" + var/cast_time = 2 SECONDS + ///The object type that is placed at the end + var/object_type + ///Whether or not the object can be placed on a tile containing dense things + var/can_density = FALSE + ///The final text output when the spell finishes (flavour) + var/language_final = "xom" + +/datum/action/cooldown/spell/pointed/darkspawn_build/can_cast_spell(feedback) + if(casting) + return FALSE + return ..() + +/datum/action/cooldown/spell/pointed/darkspawn_build/before_cast(atom/cast_on) + . = ..() + if(!object_type) + . |= SPELL_CANCEL_CAST + CRASH("someone forgot to set the placed object of a darkspawn building ability") + if(!can_density && cast_on.density) + return . | SPELL_CANCEL_CAST + if(casting) + return . | SPELL_CANCEL_CAST + if(. & SPELL_CANCEL_CAST) + return . + if(cast_time) + casting = TRUE + owner.balloon_alert(owner, "Xkla'thra...") + playsound(get_turf(owner), 'yogstation/sound/magic/devour_will_begin.ogg', 50, TRUE) + if(!do_after(owner, cast_time, cast_on)) + casting = FALSE + return . | SPELL_CANCEL_CAST + casting = FALSE + +/datum/action/cooldown/spell/pointed/darkspawn_build/cast(atom/cast_on) + . = ..() + if(!object_type) //sanity check + return + playsound(get_turf(cast_on), 'yogstation/sound/magic/devour_will_end.ogg', 50, TRUE) + var/obj/thing = new object_type(get_turf(cast_on)) + owner.balloon_alert(owner, "...[language_final]") + owner.visible_message(span_warning("[owner] knits shadows together into [thing]!"), span_velvet("You create [thing]")) + +////////////////////////////////////////////////////////////////////////// +//----------Reform the darkspawn body after death from mmi or borg------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/reform_body + name = "Reform body" + desc = "You may have lost your body, but it matters not." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "sacrament(old)" + antimagic_flags = NONE + spell_requirements = SPELL_CASTABLE_AS_BRAIN + +/datum/action/cooldown/spell/reform_body/cast(atom/cast_on) + . = ..() + if(isdarkspawn(owner)) + var/datum/antagonist/darkspawn/darkmind = isdarkspawn(owner) + if(darkmind)//sanity check + darkmind.reform_body() + diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/crawling_shadows.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/crawling_shadows.dm deleted file mode 100644 index 360ead2cc1ee..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/crawling_shadows.dm +++ /dev/null @@ -1,23 +0,0 @@ -//Assumes a stealthier form for sixty seconds or until cancelled. -/datum/action/innate/darkspawn/crawling_shadows - name = "Crawling Shadows" - id = "crawling_shadows" - desc = "Assumes a shadowy form for a minute that can crawl through vents and squeeze through the cracks in doors. You can also knock people out by attacking them." - button_icon_state = "crawling_shadows" - check_flags = AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS - psi_cost = 60 - lucidity_price = 2 //probably going to replace creep with this - -/datum/action/innate/darkspawn/crawling_shadows/IsAvailable(feedback = FALSE) - var/mob/living/L = owner - if(L.has_status_effect(STATUS_EFFECT_TAGALONG)) - return - return ..() - -/datum/action/innate/darkspawn/crawling_shadows/Activate() - owner.visible_message(span_warning("[owner] falls to the ground and transforms into a shadowy creature!"), "sa iahz sepd zwng\n\ - [span_notice("You assume a stealthier form.")]") - playsound(owner, 'yogstation/sound/magic/devour_will_end.ogg', 50, 1) - var/mob/living/simple_animal/hostile/crawling_shadows/CS = new /mob/living/simple_animal/hostile/crawling_shadows(get_turf(owner)) - CS.darkspawn_mob = owner - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/creep.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/creep.dm deleted file mode 100644 index 5887ec46ca63..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/creep.dm +++ /dev/null @@ -1,31 +0,0 @@ -//Allows you to move through light unimpeded while active. Drains 5 Psi per second. -/datum/action/innate/darkspawn/creep - name = "Creep" - id = "creep" - desc = "Grants immunity to lightburn while active. Can be toggled on and off. Drains 5 Psi per second." - button_icon_state = "creep" - check_flags = AB_CHECK_CONSCIOUS - psi_cost = 5 - psi_addendum = " to activate and per second" - lucidity_price = 2 - -/datum/action/innate/darkspawn/creep/IsAvailable(feedback = FALSE) - if(istype(owner, /mob/living/simple_animal/hostile/crawling_shadows)) - return - return ..() - -/datum/action/innate/darkspawn/creep/process() - var/mob/living/L = owner - active = L.has_status_effect(STATUS_EFFECT_CREEP) - -/datum/action/innate/darkspawn/creep/Activate() - var/mob/living/L = owner - owner.visible_message(span_warning("Velvety shadows coalesce around [owner]!"), span_velvet("odeahz
You begin using Psi to shield yourself from lightburn.")) - playsound(owner, 'yogstation/sound/magic/devour_will_victim.ogg', 50, TRUE) - L.apply_status_effect(STATUS_EFFECT_CREEP, darkspawn) - -/datum/action/innate/darkspawn/creep/Deactivate() - var/mob/living/L = owner - to_chat(owner, span_velvet("You release your grip on the shadows.")) - playsound(owner, 'yogstation/sound/magic/devour_will_end.ogg', 50, TRUE) - L.remove_status_effect(STATUS_EFFECT_CREEP) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/demented_outburst.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/demented_outburst.dm deleted file mode 100644 index e9f7ef0a0ade..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/demented_outburst.dm +++ /dev/null @@ -1,61 +0,0 @@ -//Emits a shockwave that blasts everyone and everything nearby far away. People close to the user are deafened and stunned. -/datum/action/innate/darkspawn/demented_outburst - name = "Demented Outburst" - id = "demented_outburst" - desc = "Deafens and confuses listeners after a five-second charge period, knocking away everyone nearby. Costs 50 Psi." - button_icon_state = "demented_outburst" - check_flags = AB_CHECK_CONSCIOUS - psi_cost = 50 //big boom = big cost - lucidity_price = 2 - -/datum/action/innate/darkspawn/demented_outburst/Activate() - in_use = TRUE - owner.visible_message(span_boldwarning("[owner] begins to growl as their chitin hardens..."), "cap...
\ - [span_danger("You begin harnessing your power...")]") - playsound(owner, 'yogstation/sound/magic/demented_outburst_charge.ogg', 50, 0) - addtimer(CALLBACK(src, PROC_REF(outburst), owner), 50) - addtimer(CALLBACK(src, PROC_REF(reset)), 50) - return TRUE - -/datum/action/innate/darkspawn/demented_outburst/IsAvailable(feedback = FALSE) - if(istype(owner, /mob/living/simple_animal/hostile/crawling_shadows)) - return - return ..() - -/datum/action/innate/darkspawn/demented_outburst/proc/outburst() - in_use = FALSE - if(!owner || owner.stat) - return - owner.visible_message(span_userdanger("[owner] lets out a deafening scream!"), "WSWU!
\ - [span_danger("You let out a deafening outburst!")]") - playsound(owner, 'yogstation/sound/magic/demented_outburst_scream.ogg', 75, 0) - var/list/thrown_atoms = list() - for(var/turf/T in view(5, owner)) - for(var/atom/movable/AM in T) - thrown_atoms += AM - for(var/V in thrown_atoms) - var/atom/movable/AM = V - if(AM == owner || AM.anchored) - continue - var/distance = get_dist(owner, AM) - var/turf/target = get_edge_target_turf(owner, get_dir(owner, get_step_away(AM, owner))) - AM.throw_at(target, ((clamp((5 - (clamp(distance - 2, 0, distance))), 3, 5))), 1, owner) - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - if(distance <= 1) //you done fucked up now - C.visible_message(span_warning("The blast sends [C] flying!"), span_userdanger("The force sends you flying!")) - C.Paralyze(50) - C.Knockdown(50) - C.adjustBruteLoss(10) - C.soundbang_act(1, 5, 15, 5) - else if(distance <= 3) - C.visible_message(span_warning("The blast knocks [C] off their feet!"), span_userdanger("The force bowls you over!")) - C.Paralyze(25) - C.Knockdown(30) - C.soundbang_act(1, 3, 5, 0) - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - R.visible_message(span_warning("The blast sends [R] flying!"), span_userdanger("The force sends you flying!")) - R.Paralyze(100) //fuck borgs - R.soundbang_act(1, 5, 15, 5) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/devour_will.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/devour_will.dm deleted file mode 100644 index b049adf68f3f..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/devour_will.dm +++ /dev/null @@ -1,37 +0,0 @@ -//After a brief charge-up, equips a temporary dark bead that can be used on a human to knock them out and drain their will, making them vulnerable to conversion. -/datum/action/innate/darkspawn/devour_will - name = "Devour Will" - id = "devour_will" - desc = "Creates a dark bead that can be used on a human to fully recharge Psi, gain one lucidity, and knock them unconscious. The victim will be stunned for the duration of the channel, being interrupted \ - will knock both you and the victim down. Costs 5 Psi." - button_icon_state = "devour_will" - check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_LYING | AB_CHECK_CONSCIOUS - psi_cost = 5 - blacklisted = TRUE - var/list/victims //A list of people we've used the bead on recently; we can't drain them again so soon - var/last_victim - -/datum/action/innate/darkspawn/devour_will/New() - ..() - victims = list() - -/datum/action/innate/darkspawn/devour_will/IsAvailable(feedback = FALSE) - if(!owner || istype(owner, /mob/living/simple_animal/hostile/crawling_shadows) ||istype(owner, /mob/living/simple_animal/hostile/darkspawn_progenitor) || !owner.get_empty_held_indexes()) - return - return ..() - -/datum/action/innate/darkspawn/devour_will/Activate() - owner.visible_message(span_warning("A glowing black orb appears in [owner]'s hand!"), "pwga...iejz
\ - You form a dark bead in your hand.") - playsound(owner, 'yogstation/sound/magic/devour_will_form.ogg', 50, 1) - var/obj/item/dark_bead/B = new - owner.put_in_hands(B) - B.linked_ability = src - return TRUE - -/datum/action/innate/darkspawn/devour_will/proc/make_eligible(mob/living/L) - if(!L || !victims[L]) - return - victims[L] = FALSE - to_chat(owner, span_notice("[L] has recovered from their draining and is vulnerable to Devour Will again.")) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/fighter_abilities.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/fighter_abilities.dm new file mode 100644 index 000000000000..8a0ab9312436 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/fighter_abilities.dm @@ -0,0 +1,449 @@ +////////////////////////////////////////////////////////////////////////// +//----------------------Fighter light eater ability---------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/toggle/shadow_tendril + name = "Umbral Tendril" + desc = "Twists an active arm into a mass of tendrils with many uses." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "pass" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + ///what additional effects the ability has + var/ability_flags = NONE + +/datum/action/cooldown/spell/toggle/shadow_tendril/link_to(Target) + . = ..() + if(istype(target, /datum/mind)) + RegisterSignal(target, COMSIG_DARKSPAWN_UPGRADE_ABILITY, PROC_REF(handle_upgrade)) + RegisterSignal(target, COMSIG_DARKSPAWN_DOWNGRADE_ABILITY, PROC_REF(handle_downgrade)) + +/datum/action/cooldown/spell/toggle/shadow_tendril/proc/handle_upgrade(atom/source, flag) + ability_flags |= flag + if(flag & TENDRIL_UPGRADE_TWIN) + name = "Twinned [initial(name)]s" + desc = "Twists one or both of your arms into tendrils with many uses." + +/datum/action/cooldown/spell/toggle/shadow_tendril/proc/handle_downgrade(atom/source, flag) + ability_flags -= flag + if(flag & TENDRIL_UPGRADE_TWIN) + name = initial(name) + desc = initial(desc) + +/datum/action/cooldown/spell/toggle/shadow_tendril/can_cast_spell(feedback) + if(!owner.get_empty_held_indexes() && !active) + if(feedback) + to_chat(owner, span_warning("You need an empty hand for this!")) + return FALSE + return ..() + +/datum/action/cooldown/spell/toggle/shadow_tendril/process() + active = owner.is_holding_item_of_type(/obj/item/umbral_tendrils) + return ..() + +/datum/action/cooldown/spell/toggle/shadow_tendril/Enable() + var/list/hands_free = owner.get_empty_held_indexes() + var/num_tendrils = min((ability_flags & TENDRIL_UPGRADE_TWIN) ? 2 : 1, LAZYLEN(hands_free)) + + if(!num_tendrils) + return + + owner.visible_message(span_warning("[owner]'s arm[num_tendrils > 1 ? "s" : ""] contort into tentacles!"), \ + span_velvet("You transform your arm[num_tendrils > 1 ? "s" : ""] into umbral tendrils. Examine them to see possible uses.")) + owner.balloon_alert(owner, "Ikna") + playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, TRUE) + + if(num_tendrils > 1) //add an additional sound and balloon alert for the extra tendril + addtimer(CALLBACK(src, PROC_REF(echo)), 1) + + for(var/i in 1 to num_tendrils) + var/obj/item/umbral_tendrils/T = new(owner, isdarkspawn(owner)) + if(ability_flags & TENDRIL_UPGRADE_CLEAVE) + T.AddComponent(/datum/component/cleave_attack, arc_size=180) + owner.put_in_hands(T) + +/datum/action/cooldown/spell/toggle/shadow_tendril/proc/echo() + owner.balloon_alert(owner, "Ikna") + playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, TRUE) + +/datum/action/cooldown/spell/toggle/shadow_tendril/Disable() + owner.balloon_alert(owner, "Haoo") + owner.visible_message(span_warning("[owner]'s tentacles transform back!"), span_notice("You dispel the tendrils.")) + playsound(owner, 'yogstation/sound/magic/pass_dispel.ogg', 50, 1) + for(var/obj/item/umbral_tendrils/T in owner) + QDEL_NULL(T) + +////////////////////////////////////////////////////////////////////////// +//---------------------Fighter anti-fire ability------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/deluge + name = "Deluge" + desc = "Calls upon the endless depths to douse all with the beyond." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "deluge" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 25 + cooldown_time = 45 SECONDS + +/datum/action/cooldown/spell/aoe/deluge/cast(atom/cast_on) + . = ..() + if(isliving(owner)) + owner.balloon_alert(owner, "Wyrmul") + var/mob/living/target = owner + target.extinguish_mob() + target.adjust_wet_stacks(20) + target.adjust_wet_stacks(20) + +/datum/action/cooldown/spell/aoe/deluge/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(!can_see(caster, victim)) + return + if(isliving(victim)) + var/mob/living/target = victim + target.extinguish_mob() + if(is_darkspawn_or_thrall(target) && ispreternis(target)) //don't make preterni allies wet + return + target.adjust_wet_stacks(20) + target.adjust_wet_stacks(20) + else if(isobj(victim)) + var/obj/target = victim + target.extinguish() + +////////////////////////////////////////////////////////////////////////// +//-----------------------Targeted Dash with CC--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/shadow_crash + name = "Shadow crash" + desc = "Charge in a direction." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "shadow_crash" + panel = "Darkspawn" + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED | AB_CHECK_LYING + spell_requirements = NONE + cooldown_time = 5 SECONDS + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + psi_cost = 20 + var/charging = FALSE + +/datum/action/cooldown/spell/pointed/shadow_crash/Grant(mob/grant_to) + . = ..() + ADD_TRAIT(owner, TRAIT_IMPACTIMMUNE, type) + RegisterSignal(owner, COMSIG_MOVABLE_IMPACT, PROC_REF(impact)) + +/datum/action/cooldown/spell/pointed/shadow_crash/Remove(mob/living/remove_from) + UnregisterSignal(owner, COMSIG_MOVABLE_IMPACT) + REMOVE_TRAIT(owner, TRAIT_IMPACTIMMUNE, type) + return ..() + +/datum/action/cooldown/spell/pointed/shadow_crash/cast(atom/cast_on) + . = ..() + if(!isliving(owner)) + return + var/mob/living/thing = owner + owner.balloon_alert(owner, "Vorl'ax") + charging = TRUE + thing.SetImmobilized(1 SECONDS, TRUE, TRUE) //to prevent walking out of your charge + thing.throw_at(cast_on, 4, 1, thing, FALSE, callback = CALLBACK(src, PROC_REF(end_dash), thing)) + +/datum/action/cooldown/spell/pointed/shadow_crash/proc/end_dash(mob/living/H) + charging = FALSE + H.SetImmobilized(0, TRUE, TRUE) //remove the block on movement + +/datum/action/cooldown/spell/pointed/shadow_crash/proc/impact(atom/source, atom/hit_atom, datum/thrownthing/throwingdatum) + if(!charging) + return + + if(isturf(hit_atom)) + return + + if(isobj(hit_atom)) + var/obj/thing = hit_atom + thing.take_damage(29) //does 29 twice so it can break weak things, but doesn't do anything to structures with reinforcement + thing.take_damage(29) + + if(!isliving(hit_atom)) + return + + var/mob/living/target = hit_atom + var/blocked = FALSE + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(H.check_shields(src, 0, "[source]", attack_type = LEAP_ATTACK)) + blocked = TRUE + + var/destination = get_ranged_target_turf(get_turf(target), throwingdatum.init_dir, 5) + if(blocked) + target.throw_at(destination, 3, 2) + else + target.throw_at(destination, 3, 2, callback = CALLBACK(target, TYPE_PROC_REF(/mob/living, Paralyze), 2 SECONDS)) + +////////////////////////////////////////////////////////////////////////// +//------------------------Action speed boost----------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/time_dilation + name = "Time Dilation" + desc = "Greatly increases reaction times and action speed, and provides immunity to slowdown. This lasts for 1 minute. Costs 75 Psi." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "time_dilation" + check_flags = AB_CHECK_CONSCIOUS + panel = "Darkspawn" + antimagic_flags = NONE + spell_requirements = SPELL_REQUIRES_HUMAN + sound = 'yogstation/sound/creatures/darkspawn_howl.ogg' + psi_cost = 75 + +/datum/action/cooldown/spell/time_dilation/can_cast_spell(feedback) + if(owner.has_status_effect(STATUS_EFFECT_TIME_DILATION)) + if(feedback) + to_chat(owner, span_notice("You still have time dilation in effect.")) + return FALSE + return ..() + +/datum/action/cooldown/spell/time_dilation/cast(atom/cast_on) + . = ..() + var/mob/living/L = owner + L.apply_status_effect(STATUS_EFFECT_TIME_DILATION) + L.balloon_alert(L, "Quix'thra ZYXAR!") + L.visible_message(span_warning("[L] howls as their body sigils begin to scream light in every direction!"), span_velvet("Your sigils howl out light as your body moves at incredible speed!")) + +////////////////////////////////////////////////////////////////////////// +//----------------------------Delayed AOE CC----------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/demented_outburst + name = "Demented Outburst" + desc = "Deafens and confuses listeners after a five-second charge period, knocking away everyone nearby. Costs 50 Psi." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "demented_outburst" + antimagic_flags = NONE + panel = "Darkspawn" + spell_requirements = NONE + check_flags = AB_CHECK_CONSCIOUS + psi_cost = 50 //big boom = big cost + cooldown_time = 20 SECONDS + aoe_radius = 7 + ///Boolean, if the spell is being charged up + var/casting = FALSE + ///Duration spent charging the spell + var/cast_time = 5 SECONDS + +/datum/action/cooldown/spell/aoe/demented_outburst/can_cast_spell(feedback) + if(casting) + return FALSE + return ..() + +/datum/action/cooldown/spell/aoe/demented_outburst/before_cast(atom/cast_on) + . = ..() + if(casting) + return . | SPELL_CANCEL_CAST + if(. & SPELL_CANCEL_CAST) + return . + casting = TRUE + owner.balloon_alert(owner, "Kap...") + owner.visible_message(span_boldwarning("[owner] begins to growl as their chitin hardens..."), span_velvet("You begin focusing your power...")) + playsound(owner, 'yogstation/sound/magic/demented_outburst_charge.ogg', 50, 0) + if(!do_after(owner, cast_time, cast_on)) + casting = FALSE + return . | SPELL_CANCEL_CAST + casting = FALSE + +/datum/action/cooldown/spell/aoe/demented_outburst/cast(atom/cast_on) + . = ..() + owner.balloon_alert(owner, "...WXSU!") + owner.visible_message(span_userdanger("[owner] lets out a deafening scream!"), span_velvet("You let out a deafening outburst!")) + playsound(owner, 'yogstation/sound/magic/demented_outburst_scream.ogg', 75, 0) + +/datum/action/cooldown/spell/aoe/demented_outburst/cast_on_thing_in_aoe(atom/movable/victim, atom/caster) + if(!can_see(victim, caster, aoe_radius)) + return + if(!ismovable(victim)) + return + if(victim.anchored) + return + if(isitem(victim) && isliving(victim.loc))//don't throw anything being held by someone + return + if(isliving(victim)) + var/mob/living/dude = victim + if(is_team_darkspawn(dude)) + return + var/distance = get_dist(owner, victim) + var/turf/target = get_edge_target_turf(owner, get_dir(owner, get_step_away(victim, owner))) + victim.throw_at(target, ((clamp((5 - (clamp(distance - 2, 0, distance))), 3, 5))), 1, owner) + if(iscarbon(victim)) + var/mob/living/carbon/C = victim + if(distance <= 2) //you done fucked up now + C.visible_message(span_warning("The blast sends [C] flying!"), span_userdanger("The force sends you flying!")) + C.Stun(5 SECONDS) + C.adjustBruteLoss(10) + C.soundbang_act(1, 5, 15, 5) + else if(distance <= 5) + C.visible_message(span_warning("The blast knocks [C] off their feet!"), span_userdanger("The force bowls you over!")) + C.Stun(3 SECONDS) + C.soundbang_act(1, 3, 5, 0) + if(iscyborg(victim)) + var/mob/living/silicon/robot/R = victim + R.visible_message(span_warning("The blast sends [R] flying!"), span_userdanger("The force sends you flying!")) + R.Paralyze(10 SECONDS) //fuck borgs + R.soundbang_act(1, 5, 15, 5) + +////////////////////////////////////////////////////////////////////////// +//----------------Complete Protection from light at a cost--------------// +////////////////////////////////////////////////////////////////////////// +//Allows you to move through light unimpeded while active. Drains 5 Psi per second. +/datum/action/cooldown/spell/toggle/creep + name = "Encroach" + desc = "Grants immunity to lightburn while active. Can be toggled on and off. Drains 5 Psi per second." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "encroach" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + ///Antag datum that the psi is coming from + var/datum/antagonist/darkspawn/cost + ///Psi cost of maintaining the spell + var/upkeep_cost = 1 + +/datum/action/cooldown/spell/toggle/creep/Grant(mob/grant_to) + . = ..() + if(isdarkspawn(owner)) + cost = isdarkspawn(owner) + +/datum/action/cooldown/spell/toggle/creep/process() + if(active && cost && (!cost.use_psi(upkeep_cost))) + Activate(owner) + return ..() + +/datum/action/cooldown/spell/toggle/creep/Enable() + owner.balloon_alert(owner, "Odeahz") + owner.visible_message(span_warning("Velvety shadows coalesce around [owner]!"), span_velvet("You begin using Psi to shield yourself from lightburn.")) + playsound(owner, 'yogstation/sound/magic/devour_will_victim.ogg', 50, TRUE) + var/datum/antagonist/darkspawn/dude = isdarkspawn(owner) + if(dude) + ADD_TRAIT(dude, TRAIT_DARKSPAWN_CREEP, type) + +/datum/action/cooldown/spell/toggle/creep/Disable() + owner.balloon_alert(owner, "Phwo") + to_chat(owner, span_velvet("You release your grip on the shadows.")) + playsound(owner, 'yogstation/sound/magic/devour_will_end.ogg', 50, TRUE) + var/datum/antagonist/darkspawn/dude = isdarkspawn(owner) + if(dude) + REMOVE_TRAIT(dude, TRAIT_DARKSPAWN_CREEP, type) + +////////////////////////////////////////////////////////////////////////// +//------------Toggled CC immunity force walking with psi drain----------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/toggle/indomitable + name = "Indomitable" + desc = "Grants immunity to all CC effects, but locks the user into walking." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "indomitable" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = NONE + cooldown_time = 1 SECONDS + ///Antag datum that the psi is coming from + var/datum/antagonist/darkspawn/cost + ///Psi cost of maintaining the spell + var/upkeep_cost = 1 + ///Boolean, if the user was running before activating this spell + var/was_running + ///List of traits applied during the effect + var/list/traits = list(TRAIT_STUNIMMUNE, TRAIT_PUSHIMMUNE, TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_NODEATH, TRAIT_IGNOREDAMAGESLOWDOWN) + +/datum/action/cooldown/spell/toggle/indomitable/Grant(mob/grant_to) + . = ..() + if(isdarkspawn(owner)) + cost = isdarkspawn(owner) + +/datum/action/cooldown/spell/toggle/indomitable/process() + if(active && cost && (!cost.use_psi(upkeep_cost))) + Activate(owner) + if(active && owner.m_intent != MOVE_INTENT_WALK) + owner.toggle_move_intent() + return ..() + +/datum/action/cooldown/spell/toggle/indomitable/Enable() + owner.balloon_alert(owner, "Zhaedo") + owner.visible_message(span_warning("Shadows stitch [owner]'s legs to the ground!"), span_velvet("You begin using Psi to defend yourself from disruption.")) + playsound(owner, 'yogstation/sound/magic/devour_will_form.ogg', 50, TRUE) + owner.add_traits(traits, type) + owner.move_resist = INFINITY + was_running = (owner.m_intent == MOVE_INTENT_RUN) + if(was_running) + owner.toggle_move_intent() + +/datum/action/cooldown/spell/toggle/indomitable/Disable() + owner.balloon_alert(owner, "Phwo") + to_chat(owner, span_velvet("You release your grip on the shadows.")) + playsound(owner, 'yogstation/sound/magic/devour_will_end.ogg', 50, TRUE) + owner.remove_traits(traits, type) + owner.move_resist = initial(owner.move_resist) + if(was_running && owner.m_intent == MOVE_INTENT_WALK) + owner.toggle_move_intent() + +////////////////////////////////////////////////////////////////////////// +//-------------------AOE forced movement towards user-------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/taunt + name = "Incite" + desc = "Force everyone nearby to walk towards you, but disables your ability to attack for a time." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "incite" + sound = 'yogstation/sound/ambience/antag/veil_mind_scream.ogg' + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 15 + cooldown_time = 20 SECONDS + invocation_type = INVOCATION_SHOUT + invocation = "Kmmo'axhe!" + +/datum/action/cooldown/spell/aoe/taunt/cast(atom/cast_on) + . = ..() + if(isliving(owner)) + var/mob/living/target = owner + target.SetDaze(5000 SECONDS, TRUE, TRUE) + ADD_TRAIT(target, TRAIT_PUSHIMMUNE, type) + target.move_resist = INFINITY + addtimer(CALLBACK(src, PROC_REF(unlock), target), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + +/datum/action/cooldown/spell/aoe/taunt/proc/unlock(mob/living/target) + REMOVE_TRAIT(target, TRAIT_PUSHIMMUNE, type) + target.move_resist = initial(target.move_resist) + target.SetDaze(0, TRUE, TRUE) + +/datum/action/cooldown/spell/aoe/taunt/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(!isliving(victim) || !can_see(caster, victim)) + return + var/mob/living/target = victim + if(is_darkspawn_or_thrall(target)) + return + target.apply_status_effect(STATUS_EFFECT_TAUNT, owner) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/mixed_abilities.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/mixed_abilities.dm new file mode 100644 index 000000000000..b7fded7c56f0 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/mixed_abilities.dm @@ -0,0 +1,241 @@ +////////////////////////////////////////////////////////////////////////// +//-------------------Abilities that only two classes get----------------// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +//-------------------Scout and warlock, hide in person------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/umbral_trespass + name = "Umbral trespass" + desc = "Melds with a target's shadow, causing you to invisibly follow them. Only works in lit areas, and you will be forced out if you hold any items. Costs 30 Psi." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "umbral_trespass" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + invocation_type = INVOCATION_NONE + psi_cost = 30 + cooldown_time = 10 SECONDS + hand_path = /obj/item/melee/touch_attack/darkspawn + ///status effect applied by the spell + var/datum/status_effect/tagalong/tagalong + +/datum/action/cooldown/spell/touch/umbral_trespass/cast(mob/living/carbon/cast_on) + if(tagalong) + var/possessing = FALSE + if(cast_on.has_status_effect(STATUS_EFFECT_TAGALONG)) + possessing = TRUE + QDEL_NULL(tagalong) + if(possessing) + return //only return if the user is actually still hiding + return ..() + +/datum/action/cooldown/spell/touch/umbral_trespass/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/human/target, mob/living/carbon/human/caster) + tagalong = caster.apply_status_effect(STATUS_EFFECT_TAGALONG, target) + caster.balloon_alert(caster, "Iahz") + to_chat(caster, span_velvet("You slip into [target]'s shadow. This will last five minutes, until canceled, or you are forced out by darkness.")) + caster.forceMove(target) + return TRUE + +////////////////////////////////////////////////////////////////////////// +//-----------------Scout and warlock, aoe slow and chill----------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/icyveins //Stuns and freezes nearby people - a bit more effective than a changeling's cryosting + name = "Icy Veins" + desc = "Instantly freezes the blood of nearby people, slowing them and rapidly chilling their body." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "icy_veins" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + sound = 'sound/effects/ghost2.ogg' + aoe_radius = 3 + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 60 + cooldown_time = 45 SECONDS + +/datum/action/cooldown/spell/aoe/icyveins/cast(atom/cast_on) + . = ..() + owner.balloon_alert(owner, "Syn'thra") + to_chat(owner, span_velvet("You freeze the nearby air.")) + if(isliving(owner)) + var/mob/living/target = owner + target.extinguish_mob() + +/datum/action/cooldown/spell/aoe/icyveins/cast_on_thing_in_aoe(atom/target, atom/user) + if(!can_see(user, target)) + return + if(!isliving(target)) + return + var/mob/living/victim = target + if(is_team_darkspawn(victim)) //no friendly fire + return + to_chat(victim, span_userdanger("A wave of shockingly cold air engulfs you!")) + victim.apply_damage(10, BURN) + if(victim.reagents) + victim.reagents.add_reagent(/datum/reagent/consumable/frostoil, 10) + victim.reagents.add_reagent(/datum/reagent/shadowfrost, 10) + +////////////////////////////////////////////////////////////////////////// +//--------------------Transform into a simplemob------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/shapeshift/crawling_shadows + name = "Crawling Shadows" + desc = "Assumes a shadowy form that can crawl through vents and squeeze through the cracks in doors." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "crawling_shadows" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = NONE + psi_cost = 55 + cooldown_time = 1 SECONDS //to prevent double clicking by accident + die_with_shapeshifted_form = FALSE + convert_damage = TRUE + convert_damage_type = STAMINA + sound = 'yogstation/sound/magic/devour_will_end.ogg' + possible_shapes = list(/mob/living/simple_animal/hostile/crawling_shadows) + +/datum/action/cooldown/spell/shapeshift/crawling_shadows/do_shapeshift(mob/living/caster) + . = ..() + if(.) + owner.balloon_alert(owner, "Zov...") + +/datum/action/cooldown/spell/shapeshift/crawling_shadows/do_unshapeshift(mob/living/caster) + . = ..() + if(.) + owner.balloon_alert(owner, "...Voz") + +/datum/action/cooldown/spell/shapeshift/crawling_shadows/can_cast_spell(feedback) + if(owner.has_status_effect(/datum/status_effect/shapechange_mob/from_spell)) //so it's free to change back, but costs psi to change + psi_cost = 0 + else + psi_cost = initial(psi_cost) + if(owner.has_status_effect(STATUS_EFFECT_TAGALONG)) + return FALSE + if(owner.movement_type & VENTCRAWLING) //don't let them smoosh themselves + if(feedback) + to_chat(owner, span_warning("There isn't enough room to release your transformation")) + return FALSE + return ..() + +////////////////////////////////////////////////////////////////////////// +//------------------------Summon a distraction--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/simulacrum + name = "Simulacrum" + desc = "Creates an illusion that closely resembles you. The illusion will fight nearby enemies in your stead for 10 seconds. Costs 40 Psi." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "simulacrum" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 40 + ///How long the clones last + var/duration = 10 SECONDS + +/datum/action/cooldown/spell/simulacrum/cast(atom/cast_on) + . = ..() + if(!isliving(owner)) + return + var/mob/living/L = owner + L.balloon_alert(L, "Zkxa'ya") + L.visible_message(span_warning("[L] breaks away from [L]'s shadow!"), span_velvet("You create an illusion of yourself.")) + playsound(L, 'yogstation/sound/magic/devour_will_form.ogg', 50, 1) + + var/mob/living/simple_animal/hostile/illusion/darkspawn/M = new(get_turf(L)) + M.Copy_Parent(L, duration, 100, 10) //closely follows regular player stats so it's not painfully obvious (still sorta is) + M.move_to_delay = L.movement_delay() + +////////////////////////////////////////////////////////////////////////// +//--------------------Summon a sentient distraction---------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/fray_self + name = "Fray self" + desc = "Attemps to split a piece of your psyche into a sentient copy of yourself that lasts until destroyed. Costs 80 Psi." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "fray_self" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 100 + cooldown_time = 3 MINUTES + ///mob summoned by the spell + var/mob/living/simple_animal/hostile/illusion/darkspawn/psyche/dude + ///health of the mob summoned by the spell + var/health = 100 + ///punch damage of the mob summoned by the spell + var/damage = 10 + ///Boolean, whether or not the spell is trying to call a ghost + var/searching = FALSE + +/datum/action/cooldown/spell/fray_self/can_cast_spell(feedback) + if(dude) + if(feedback) + to_chat(owner, span_velvet("The piece of your psyche hasn't yet returned to you.")) + return FALSE + if(searching) + return FALSE + return ..() + +/datum/action/cooldown/spell/fray_self/cast(atom/cast_on) + . = ..() + if(dude || searching) + return + if(!isliving(owner)) + return + INVOKE_ASYNC(src, PROC_REF(fray)) + +///Attempt to spawn the sentient ghost mob +/datum/action/cooldown/spell/fray_self/proc/fray() + var/mob/living/caster = owner + + to_chat(caster, span_velvet("You attempt to split a piece of your psyche.")) + searching = TRUE + var/mob/dead/observer/chosen_ghost + var/list/consenting_candidates = pollGhostCandidates("Would you like to play as piece of [caster]'s psyche?", "Darkspawn", null, ROLE_DARKSPAWN, 10 SECONDS, POLL_IGNORE_DARKSPAWN_PSYCHE) + if(consenting_candidates.len) + chosen_ghost = pick(consenting_candidates) + searching = FALSE + if(!chosen_ghost) + to_chat(caster, span_danger("You fail to split a piece of your psyche.")) + return + + caster.balloon_alert(caster, "Zkxa'yaera Hohef'era!") + caster.visible_message(span_warning("[caster] breaks away from [caster]'s shadow!"), span_velvet("The piece of your psyche creates a form for itself.")) + playsound(caster, 'yogstation/sound/magic/devour_will_form.ogg', 50, 1) + + if(!dude) + dude = new(get_turf(caster)) + RegisterSignal(dude, COMSIG_LIVING_DEATH, PROC_REF(rejoin)) + dude.Copy_Parent(caster, 100, health, damage) + dude.ckey = chosen_ghost.ckey + dude.name = caster.name + dude.real_name = caster.real_name + if(isdarkspawn(caster)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(caster) + darkspawn.block_psi(30 SECONDS, type) + +///Make sure to properly reset the ability when the ghost mob dies +/datum/action/cooldown/spell/fray_self/proc/rejoin() + to_chat(owner, span_velvet("You feel your psyche form back into a singular entity.")) + if(!QDELETED(dude)) + qdel(dude) + dude = null diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/pass.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/pass.dm deleted file mode 100644 index eef2bbbeaae6..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/pass.dm +++ /dev/null @@ -1,50 +0,0 @@ -//Equips umbral tendrils with many uses. -/datum/action/innate/darkspawn/pass - name = "Pass" - id = "pass" - desc = "Twists an active arm into tendrils with many important uses. Examine the tendrils to see a list of uses." - button_icon_state = "pass" - check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS - blacklisted = TRUE //baseline - -/datum/action/innate/darkspawn/pass/IsAvailable(feedback = FALSE) - if(istype(owner, /mob/living/simple_animal/hostile/crawling_shadows) || istype(owner, /mob/living/simple_animal/hostile/darkspawn_progenitor) || !owner.get_empty_held_indexes() && !active) - return - return ..() - -/datum/action/innate/darkspawn/pass/process() - ..() - active = locate(/obj/item/umbral_tendrils) in owner - if(darkspawn.upgrades["twin_tendrils"]) - name = "Twinned Pass" - desc = "Twists one or both of your arms into tendrils with many uses." - -/datum/action/innate/darkspawn/pass/Activate() - var/mob/living/carbon/C = owner - if(!(C.mobility_flags & MOBILITY_STAND)) - to_chat(owner, span_warning("Stand up first!")) - return - var/list/hands_free = owner.get_empty_held_indexes() - if(!darkspawn.upgrades["twin_tendrils"] || hands_free.len < 2) - owner.visible_message(span_warning("[owner]'s arm contorts into tentacles!"), "ikna
\ - [span_notice("You transform your arm into umbral tendrils. Examine them to see possible uses.")]") - playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, 1) - var/obj/item/umbral_tendrils/T = new(owner, darkspawn) - owner.put_in_hands(T) - else - owner.visible_message(span_warning("[owner]'s arms contort into tentacles!"), "ikna ikna
\ - You transform both arms into umbral tendrils. Examine them to see possible uses.") - playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, TRUE) - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), owner, 'yogstation/sound/magic/pass_create.ogg', 50, TRUE), 1) - for(var/i in 1 to 2) - var/obj/item/umbral_tendrils/T = new(owner, darkspawn) - owner.put_in_hands(T) - return TRUE - -/datum/action/innate/darkspawn/pass/Deactivate() - owner.visible_message(span_warning("[owner]'s tentacles transform back!"), "haoo
\ - [span_notice("You dispel the tendrils.")]") - playsound(owner, 'yogstation/sound/magic/pass_dispel.ogg', 50, 1) - for(var/obj/item/umbral_tendrils/T in owner) - qdel(T) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/scout_abilities.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/scout_abilities.dm new file mode 100644 index 000000000000..7e8a220c7536 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/scout_abilities.dm @@ -0,0 +1,311 @@ +////////////////////////////////////////////////////////////////////////// +//----------------------Scout light eater ability-----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/toggle/light_eater + name = "Light Eater" + desc = "Twists an active arm into a blade of all-consuming shadow." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "light_eater" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + ///The blade spawned by the ability + var/obj/item/light_eater/armblade + +/datum/action/cooldown/spell/toggle/light_eater/process() + active = owner.is_holding_item_of_type(/obj/item/light_eater) + return ..() + +/datum/action/cooldown/spell/toggle/light_eater/can_cast_spell(feedback) + if(!owner.get_empty_held_indexes() && !active) + if(feedback) + to_chat(owner, span_warning("You need an empty hand for this!")) + return FALSE + return ..() + +/datum/action/cooldown/spell/toggle/light_eater/Enable() + owner.balloon_alert(owner, "Akna") + owner.visible_message(span_warning("[owner]'s arm contorts into a blade!"), span_velvet("You transform your arm into a blade.")) + playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, 1) + if(!armblade) + armblade = new(owner) + owner.put_in_hands(armblade) + +/datum/action/cooldown/spell/toggle/light_eater/Disable() + owner.balloon_alert(owner, "Haoo") + owner.visible_message(span_warning("[owner]'s blade transforms back!"), span_velvet("You dispel the blade.")) + playsound(owner, 'yogstation/sound/magic/pass_dispel.ogg', 50, 1) + if(armblade) + armblade.moveToNullspace() + +////////////////////////////////////////////////////////////////////////// +//---------------------Scout Long range option--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/toggle/shadow_caster + name = "Shadow caster" + desc = "Twists an active arm into a bow that shoots arrows made of solid darkness." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "shadow_caster" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + ///the bow spawned by the ability + var/obj/item/gun/ballistic/bow/energy/shadow_caster/bow + +/datum/action/cooldown/spell/toggle/shadow_caster/process() + active = owner.is_holding_item_of_type(/obj/item/gun/ballistic/bow/energy/shadow_caster) + return ..() + +/datum/action/cooldown/spell/toggle/shadow_caster/can_cast_spell(feedback) + if(!owner.get_empty_held_indexes() && !active) + if(feedback) + to_chat(owner, span_warning("You need an empty hand for this!")) + return FALSE + return ..() + +/datum/action/cooldown/spell/toggle/shadow_caster/Enable() + owner.balloon_alert(owner, "Crxkna") + owner.visible_message(span_warning("[owner]'s arm contorts into a bow!"), span_velvet("You transform your arm into a bow.")) + playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, 1) + if(!bow) + bow = new (owner) + owner.put_in_hands(bow) + +/datum/action/cooldown/spell/toggle/shadow_caster/Disable() + owner.balloon_alert(owner, "Haoo") + owner.visible_message(span_warning("[owner]'s bow transforms back!"), span_velvet("You dispel the bow.")) + playsound(owner, 'yogstation/sound/magic/pass_dispel.ogg', 50, 1) + if(bow) + bow.moveToNullspace() + +////////////////////////////////////////////////////////////////////////// +//----------------------Temporary Darkness in aoe-----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/darkness_smoke + name = "Blinding Miasma" + desc = "Spews a cloud of smoke which will blind enemies and provide cover from light." + panel = "Darkspawn" + button_icon_state = "blinding_miasma" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + + sound = 'sound/effects/bamf.ogg' + psi_cost = 35 + cooldown_time = 45 SECONDS + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = NONE + ///The size of the smoke cloud spawned by the ability + var/range = 4 + +/datum/action/cooldown/spell/darkness_smoke/cast(mob/living/carbon/human/user) //Extremely hacky ---- (oh god, it really is) + . = ..() + owner.balloon_alert(owner, "Hwlok'krotho") + owner.visible_message(span_warning("[owner] bends over and bellows out a cloud of black smoke!"), span_velvet("You expel a vast cloud of blinding smoke.")) + var/obj/item/reagent_containers/glass/beaker/large/B = new /obj/item/reagent_containers/glass/beaker/large(get_turf(owner)) //hacky + B.reagents.clear_reagents() //Just in case! + B.invisibility = INFINITY //This ought to do the trick + B.reagents.add_reagent(/datum/reagent/darkspawn_darkness_smoke, 50) + var/datum/effect_system/fluid_spread/smoke/chem/darkspawn/S = new //it doesn't actually block light anyways, so let's not block vision either + S.attach(B) + if(S) + S.set_up(range, location = B.loc, carry = B.reagents) + S.start() + qdel(B) + +////////////////////////////////////////////////////////////////////////// +//----------------------------Trap abilities----------------------------// +////////////////////////////////////////////////////////////////////////// +//Reskinned punji sticks that don't stun for as long +/datum/action/cooldown/spell/pointed/darkspawn_build/trap + name = "Psi Trap" + desc = "Stitch together shadows into a trap." + button_icon_state = "psi_trap_damage" + language_final = "DEBUGIFY" + +/datum/action/cooldown/spell/pointed/darkspawn_build/trap/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return . + var/turf/target_loc = get_turf(cast_on) + var/obj/structure/trap/darkspawn/trap = locate() in target_loc + if(trap) + owner.balloon_alert(owner, "There is already a trap there") + return . | SPELL_CANCEL_CAST + +//Reskinned punji sticks that don't stun for as long +/datum/action/cooldown/spell/pointed/darkspawn_build/trap/damage + name = "Psi Trap (damage)" + desc = "Stitch together shadows into a trap that deals damage to non-ally that crosses it." + button_icon_state = "psi_trap_damage" + object_type = /obj/structure/trap/darkspawn/damage + language_final = "ksha" + +//Reskinned bear trap that doesn't slow as much and can't be picked up +/datum/action/cooldown/spell/pointed/darkspawn_build/trap/legcuff + name = "Psi Trap (restrain)" + desc = "Stitch together shadows into a trap that restrains the legs of any non-ally that crosses it." + button_icon_state = "psi_trap_bear" + object_type = /obj/structure/trap/darkspawn/legcuff + language_final = "xcrak" + +//Discombobulates people +/datum/action/cooldown/spell/pointed/darkspawn_build/trap/nausea + name = "Psi Trap (nausea)" + desc = "Stitch together shadows into a trap that makes any non-ally that crosses it sick to their stomach." + button_icon_state = "psi_trap_nausea" + object_type = /obj/structure/trap/darkspawn/nausea + language_final = "guhxo" + +//Discombobulates people +/datum/action/cooldown/spell/pointed/darkspawn_build/trap/teleport + name = "Psi Trap (teleport)" + desc = "Stitch together shadows into a trap that teleports any non-ally to a random location on the station." + button_icon_state = "psi_trap_teleport" + object_type = /obj/structure/trap/darkspawn/teleport + language_final = "hwkwo" + +////////////////////////////////////////////////////////////////////////// +//-------------------It's a jaunt, what do you expect-------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/void_jaunt + name = "Void Jaunt" + desc = "Move through the veil for a time, avoiding mortal eyes and lights." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "void_jaunt" + + antimagic_flags = NONE + panel = "Darkspawn" + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 70 + cooldown_time = 90 SECONDS + + sound = 'sound/effects/bamf.ogg' + exit_jaunt_sound = 'yogstation/sound/magic/devour_will_begin.ogg' + jaunt_in_type = /obj/effect/temp_visual/dir_setting/ninja/cloak + jaunt_out_type = /obj/effect/temp_visual/dir_setting/ninja + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/void_jaunt/do_steam_effects(turf/loc) + return FALSE + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/void_jaunt/cast(mob/living/cast_on) + . = ..() + owner.balloon_alert(owner, "Vxklu'wop sla'txhaka") + +////////////////////////////////////////////////////////////////////////// +//--------------------------Targeted Teleport---------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/phase_jump/void_jump + name = "Void jump" + desc = "A short range targeted teleport." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "shadow_jump" + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + panel = "Darkspawn" + sound = 'sound/magic/voidblink.ogg' + + psi_cost = 20 + cooldown_time = 20 SECONDS + cast_range = 7 + active_msg = span_velvet("You prepare to take a step through the void.") + deactive_msg = span_notice("You relax your mind.") + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_CASTABLE_AS_BRAIN + beam_icon = "curse0" + +/datum/action/cooldown/spell/pointed/phase_jump/void_jump/InterceptClickOn(mob/living/user, params, atom/target) + . = ..() + if(.) + owner.balloon_alert(owner, "Vxklu'wop") + +////////////////////////////////////////////////////////////////////////// +//-----------------------------AOE ice field----------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/permafrost + name = "Permafrost" + desc = "Banish heat from the surrounding terrain, freezing it instantly." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "permafrost" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 65 + cooldown_time = 60 SECONDS + sound = 'yogstation/sound/ambience/antag/veil_mind_scream.ogg' + aoe_radius = 3 + +/datum/action/cooldown/spell/aoe/permafrost/cast(atom/cast_on) + . = ..() + owner.balloon_alert(owner, "Syn'thxklp") + if(isliving(owner)) + var/mob/living/target = owner + target.extinguish_mob() + +/datum/action/cooldown/spell/aoe/permafrost/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(!isopenturf(victim)) + return + var/turf/open/target = victim + target.MakeSlippery(TURF_WET_PERMAFROST, 10 SECONDS) + +////////////////////////////////////////////////////////////////////////// +//------------------------Chameleon projector---------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/disguise //chameleon projector as a spell + name = "Shadow disguise" + desc = "Restrain a target's mental faculties, preventing speech and actions of any kind for a moderate duration." + panel = "Darkspawn" + button_icon_state = "glare" + button_icon = 'yogstation/icons/mob/actions.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + cooldown_time = 30 SECONDS + ranged_mousepointer = 'icons/effects/mouse_pointers/gaze_target.dmi' + ///Item stored by the projector + var/obj/item/chameleon/handler + ///boolean, if the chameleon projector is active + var/active = FALSE + +/datum/action/cooldown/spell/pointed/disguise/New(Target) + . = ..() + handler = new() + +/datum/action/cooldown/spell/pointed/disguise/before_cast(atom/cast_on) + . = ..() + if(!handler) + return . | SPELL_CANCEL_CAST + if(!cast_on || !isobj(cast_on)) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/pointed/disguise/cast(atom/cast_on) + . = ..() + if(!handler) + return + if(active) + handler.disrupt() + return + + handler.afterattack(cast_on, owner, TRUE) + handler.toggle(owner) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/silver_tongue.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/silver_tongue.dm deleted file mode 100644 index b7bb50dbeaa7..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/silver_tongue.dm +++ /dev/null @@ -1,58 +0,0 @@ -//Can be used on a communications console to recall the shuttle. Leaves visible evidence. -/datum/action/innate/darkspawn/silver_tongue - name = "Silver Tongue" - id = "silver_tongue" - desc = "When used near a communications console, allows you to forcefully transmit a message to Central Command, initiating a shuttle recall. Only usable if the shuttle is inbound. Costs 60 Psi." - button_icon_state = "silver_tongue" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_IMMOBILE - psi_cost = 60 - lucidity_price = 1 //Very niche, so low cost - -/datum/action/innate/darkspawn/silver_tongue/IsAvailable(feedback = FALSE) - if(SSshuttle.emergency.mode != SHUTTLE_CALL) - return - return ..() - -/datum/action/innate/darkspawn/silver_tongue/Activate() - in_use = TRUE - var/obj/machinery/computer/communications/C = locate() in range(1, owner) - if(!C) - to_chat(owner, span_warning("There are no communications consoles nearby")) - return - if(C.stat) - to_chat(owner, span_warning("[C] is depowered.")) - return - owner.visible_message(span_warning("[owner] briefly touches [src]'s screen, and the keys begin to move by themselves!"), \ - "[pick("Oknnu. Pda ywlpwej swo hkccaz ej.", "Pda aiancajyu eo kran. Oknnu bkn swopejc ukqn peia.", "We swo knzanaz xu Hws Psk. Whh ckkz jks.")]
\ - [span_velvet("You begin transmitting a recall message to Central Command...")]") - play_recall_sounds(C) - if(!do_after(owner, 8 SECONDS, C)) - in_use = FALSE - return - if(!C) - in_use = FALSE - return - if(C.stat) - to_chat(owner, span_warning("[C] has lost power.")) - in_use = FALSE - return - in_use = FALSE - SSshuttle.emergency.cancel() - to_chat(owner, span_velvet("The ruse was a success. The shuttle is on its way back.")) - return TRUE - -/datum/action/innate/darkspawn/silver_tongue/proc/play_recall_sounds(obj/machinery/C) //neato sound effects - set waitfor = FALSE - for(var/i in 1 to 4) - sleep(1 SECONDS) - if(!C || C.stat) - return - playsound(C, "terminal_type", 50, TRUE) - if(prob(25)) - playsound(C, 'sound/machines/terminal_alert.ogg', 50, FALSE) - do_sparks(5, TRUE, get_turf(C)) - playsound(C, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - sleep(0.5 SECONDS) - if(!C || C.stat) - return - playsound(C, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/simulacrum.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/simulacrum.dm deleted file mode 100644 index 25d43b187186..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/simulacrum.dm +++ /dev/null @@ -1,23 +0,0 @@ -//Creates an illusionary copy of the caster that runs in their direction for ten seconds and then vanishes. -/datum/action/innate/darkspawn/simulacrum - name = "Simulacrum" - id = "simulacrum" - desc = "Creates an illusion that closely resembles you. The illusion will run forward for ten seconds. Costs 20 Psi." - button_icon_state = "simulacrum" - check_flags = AB_CHECK_CONSCIOUS - psi_cost = 20 - lucidity_price = 1 - -/datum/action/innate/darkspawn/simulacrum/Activate() - if(isliving(owner.loc)) - var/mob/living/L = owner.loc - L.visible_message(span_warning("[owner] breaks away from [L]'s shadow!"), \ - span_userdanger("You feel a sense of freezing cold pass through you!")) - to_chat(owner, span_velvet("zayaera
You create an illusion of yourself.")) - else - owner.visible_message(span_warning("[owner] splits in two!"), \ - span_velvet("zayaera
You create an illusion of yourself.")) - playsound(owner, 'yogstation/sound/magic/devour_will_form.ogg', 50, 1) - var/obj/effect/simulacrum/simulacrum = new(get_turf(owner)) - simulacrum.mimic(owner) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/tagalong.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/tagalong.dm deleted file mode 100644 index 266a88842076..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/tagalong.dm +++ /dev/null @@ -1,49 +0,0 @@ -//Melds with a mob's shadow, allowing the caster to "shadow" (HA) them while they're not in darkness. -/datum/action/innate/darkspawn/tagalong - name = "Tagalong" - id = "tagalong" - desc = "Melds with a target's shadow, causing you to invisibly follow them. Only works in lit areas, and you will be forced out if you hold any items. Costs 30 Psi." - button_icon_state = "tagalong" - check_flags = AB_CHECK_CONSCIOUS - psi_cost = 30 - psi_addendum = ", but is free to cancel" - lucidity_price = 2 - var/datum/status_effect/tagalong/tagalong - -/datum/action/innate/darkspawn/tagalong/IsAvailable(feedback = FALSE) - if(istype(owner, /mob/living/simple_animal/hostile/crawling_shadows)) - return - return ..() - -/datum/action/innate/darkspawn/tagalong/process() - psi_cost = 30 * isnull(tagalong) - -/datum/action/innate/darkspawn/tagalong/Activate() - if(tagalong) - QDEL_NULL(tagalong) - return - if(owner.get_active_held_item() || owner.get_inactive_held_item()) - to_chat(owner, span_warning("Your hands must be empty to accompany someone!")) - return - var/list/targets = list() - var/mob/living/target - var/turf/T - for(var/mob/living/L in range(7, owner) - owner) - T = get_turf(L) - if(!isdarkspawn(L) && L.stat != DEAD && T.get_lumcount() >= DARKSPAWN_DIM_LIGHT) - targets += L - if(!targets.len) - to_chat(owner, span_warning("There is nobody nearby in any lit areas!")) - return - if(targets.len == 1) - target = targets[1] - else - target = input(owner, "Choose a target to accompany.", "Tagalong") as null|anything in targets - if(!target) - return - var/mob/living/L = owner - tagalong = L.apply_status_effect(STATUS_EFFECT_TAGALONG, target) - to_chat(owner, "iahz
\ - You slip into [target]'s shadow. This will last five minutes, until canceled, or you are forced out.") - owner.forceMove(target) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm new file mode 100644 index 000000000000..38e29d47ee74 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm @@ -0,0 +1,459 @@ +////////////////////////////////////////////////////////////////////////// +//-----------------------------Veil Creation----------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/thrall_mind + name = "Thrall mind" + desc = "Consume 1 willpower to thrall a target's mind. To be eligible, they must be alive and recently drained by Devour Will. Can also be used to revive deceased thralls." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "veil_mind" + antimagic_flags = MAGIC_RESISTANCE_MIND + panel = "Darkspawn" + check_flags = AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + invocation_type = INVOCATION_NONE + psi_cost = 100 + hand_path = /obj/item/melee/touch_attack/darkspawn + ///Willpower spent by the darkspawn datum to thrall a mind + var/willpower_cost = 1 + +/datum/action/cooldown/spell/touch/thrall_mind/can_cast_spell(feedback) + var/datum/antagonist/darkspawn/master = isdarkspawn(owner) + if(master && master.willpower < willpower_cost) + if(feedback) + to_chat(owner, span_danger("You do not have enough will to thrall [target].")) + return FALSE + return ..() + +/datum/action/cooldown/spell/touch/thrall_mind/is_valid_target(atom/cast_on) + return ishuman(cast_on) + +/datum/action/cooldown/spell/touch/thrall_mind/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/human/target, mob/living/carbon/human/caster) + if(!isdarkspawn(caster))//sanity check + return + if(!(target.mind || target.ckey)) + to_chat(owner, "This mind is too feeble to even be worthy of thralling.") + return + if(!target.getorganslot(ORGAN_SLOT_BRAIN)) + to_chat(owner, span_danger("[target]'s brain is missing, you lack the conduit to control them.")) + return FALSE + if(isdarkspawn(target)) + to_chat(owner, span_velvet("You will never be strong enough to control the will of another.")) + return + var/datum/antagonist/darkspawn/master = isdarkspawn(caster) + + if(!isthrall(target)) + if(!target.has_status_effect(STATUS_EFFECT_DEVOURED_WILL)) + to_chat(owner, span_danger("[target]'s will is still too strong to thrall.")) + return FALSE + if(master.willpower < willpower_cost) + to_chat(owner, span_danger("You do not have enough will to thrall [target].")) + return FALSE + + owner.balloon_alert(owner, "Krx'lna tyhx graha...") + to_chat(owner, span_velvet("You begin to channel your psionic powers through [target]'s mind.")) + playsound(owner, 'yogstation/sound/magic/devour_will_victim.ogg', 50) + if(!do_after(owner, 2 SECONDS, target)) + return FALSE + + if(!isthrall(target)) + var/list/flavour = list() + + flavour += "Your mind goes numb. Your thoughts go blank. You feel utterly empty." + flavour += "A consciousness brushes against your own. You dream." + if(ispreternis(target)) + flavour += "Of a vast, glittering empire stretching from star to star." + flavour += "Then, a Void blankets the canopy, suffocating the light." + flavour += "Hungry eyes bear into you from the blackness. Ancient. Familiar." + else + flavour += "Of a vast, empty Void in the deep of space." + flavour += "Something lies in the Void. Ancient. Unknowable." + flavour += "It watches you with hungry eyes. Eyes filled with stars." + + if(HAS_TRAIT(target, TRAIT_MINDSHIELD)) + flavour += span_boldwarning("The creature's gaze swallows the universe into blackness.") + + to_chat(target, flavour.Join("
")) + if(HAS_TRAIT(target, TRAIT_MINDSHIELD)) + to_chat(owner, span_warning("[target] has foreign machinery that resists our thralling, we shall attempt to destroy it.")) + target.visible_message(span_warning("[target] seems to resist an unseen force!")) + if(!do_after(owner, 8 SECONDS, target)) + to_chat(target, span_userdanger("It cannot be permitted to succeed.")) + return FALSE + for(var/obj/item/implant/mindshield/L in target) + qdel(L) + + playsound(owner, 'yogstation/sound/ambience/antag/veil_mind_gasp.ogg', 25) + + if(!do_after(owner, 2 SECONDS, target)) + return FALSE + + playsound(owner, 'yogstation/sound/ambience/antag/veil_mind_scream.ogg', 100) + if(isthrall(target)) + owner.balloon_alert(owner, "...tia") + to_chat(owner, span_velvet("You revitalize your thrall [target.real_name].")) + target.revive(TRUE, TRUE) + target.grab_ghost() + return TRUE + + var/datum/team/darkspawn/team = master.get_team() + if(team && LAZYLEN(team.thralls) >= team.max_thralls) + to_chat(owner, span_velvet("Your power is incapable of controlling [target].")) + return FALSE + + if(master.willpower < willpower_cost) //sanity check + to_chat(owner, span_velvet("You do not have enough will to thrall [target].")) + return FALSE + + if(target.add_thrall()) + master.willpower -= willpower_cost + owner.balloon_alert(owner, "...xthl'kap") + to_chat(owner, span_velvet("[target.real_name] has become a thrall!")) + to_chat(owner, span_velvet("Thralls will serve your every command and passively generate willpower for being nearby non thralls.")) + else + to_chat(owner, span_velvet("Your power is incapable of controlling [target].")) + return TRUE + +////////////////////////////////////////////////////////////////////////// +//----------------------------Get rid of a thrall-----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/release_thrall + name = "Release thrall" + desc = "Release a thrall from your control, freeing your power to be redistributed and restoring a portion of the spent willpower." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "veiling_touch" + antimagic_flags = NONE + panel = "Darkspawn" + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_CASTABLE_AS_BRAIN + +/datum/action/cooldown/spell/release_thrall/can_cast_spell(feedback) + var/datum/antagonist/darkspawn/dude = isdarkspawn(owner) + if(dude && istype(dude)) + var/datum/team/darkspawn/team = dude.get_team() + if(team &&!LAZYLEN(team.thralls)) + if(feedback) + to_chat(owner, "You have no thralls to release.") + return + return ..() + +/datum/action/cooldown/spell/release_thrall/cast(atom/cast_on) + . = ..() + if(!isdarkspawn(owner)) + return + + var/datum/antagonist/darkspawn/dude = isdarkspawn(owner) + if(!dude.get_team()) + return + + var/datum/team/darkspawn/team = dude.get_team() + + var/loser = tgui_input_list(owner, "Select a thrall to release from your control.", "Release a thrall", team.thralls) + if(!loser || !istype(loser, /datum/mind)) + return + var/datum/mind/unveiled = loser + if(!unveiled.current) + return + if(unveiled.current.remove_thrall()) + owner.balloon_alert(owner, "Fk'koht") + to_chat(owner, span_velvet("You release your control over [unveiled]")) + +////////////////////////////////////////////////////////////////////////// +//--------------------------Veil Camera System--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/darkspawn_build/thrall_cam + name = "Panopticon" + desc = "Watch what your allies and servants are doing at all times." + button_icon_state = "panopticon" + cooldown_time = 1 MINUTES + cast_time = 2 SECONDS + object_type = /obj/machinery/computer/camera_advanced/darkspawn + language_final = "kxmiv'ixnce" + +////////////////////////////////////////////////////////////////////////// +//-----Shoots a projectile, but can be used through the cam system------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/mindblast + name = "Mind blast" + desc = "Focus your psionic energy into a blast that deals physical damage. Can also be projected from the minds of allies." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "mind_blast" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + + cast_range = INFINITY //lol + psi_cost = 40 + cooldown_time = 5 SECONDS + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_CASTABLE_AS_BRAIN + sound = 'sound/weapons/resonator_blast.ogg' + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + + ///how far the projectile can shoot from a body + var/body_range = 9 + ///mob to shoot the projectile from + var/mob/shoot_from + +/datum/action/cooldown/spell/pointed/mindblast/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + var/closest_dude_dist = body_range + if(get_dist(owner, cast_on) > body_range) + for(var/mob/living/dude in range(body_range, cast_on)) + if(is_team_darkspawn(dude)) + if(!isturf(dude.loc)) + continue + if(get_dist(cast_on, dude) < closest_dude_dist)//always only get the closest dude + shoot_from = dude + closest_dude_dist = get_dist(cast_on, dude) + else + shoot_from = owner + if(!shoot_from) + to_chat(owner, span_warning("There is no one nearby to channel your power through.")) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/pointed/mindblast/cast(atom/cast_on) + . = ..() + if(!shoot_from) + return + fire_projectile(cast_on, shoot_from) + owner.balloon_alert(owner, "Vyk'thunak") + if(shoot_from != owner) + playsound(get_turf(shoot_from), 'sound/weapons/resonator_blast.ogg', 50, 1) + shoot_from = null + +/datum/action/cooldown/spell/pointed/mindblast/proc/fire_projectile(atom/target, mob/shooter) + var/obj/projectile/magic/mindblast/to_fire = new () + ready_projectile(to_fire, target, shooter) + SEND_SIGNAL(owner, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) + to_fire.fire() + +/datum/action/cooldown/spell/pointed/mindblast/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/shooter) + to_fire.firer = owner + to_fire.fired_from = shooter + to_fire.preparePixelProjectile(target, shooter) + + if(istype(to_fire, /obj/projectile/magic)) + var/obj/projectile/magic/magic_to_fire = to_fire + magic_to_fire.antimagic_flags = antimagic_flags + +/obj/projectile/magic/mindblast + name ="mindbolt" + icon = 'yogstation/icons/obj/darkspawn_projectiles.dmi' + icon_state = "mind_blast" + damage = 35 + armour_penetration = 100 + speed = 1 + damage_type = BRUTE + nodamage = FALSE + pass_flags = PASSMACHINES | PASSCOMPUTER | PASSTABLE + range = 10 + +/obj/projectile/magic/mindblast/Initialize(mapload) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/projectile/magic/mindblast/can_hit_target(atom/target, list/passthrough, direct_target, ignore_loc) + if(isliving(target)) + var/mob/living/victim = target + if(is_team_darkspawn(victim)) + return FALSE + return ..() + +/obj/projectile/magic/mindblast/update_overlays() + . = ..() + . += emissive_appearance(icon, "[icon_state]_emissive", src) + +////////////////////////////////////////////////////////////////////////// +//-----------------------Global AOE Buff spells-------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/thrallbuff + name = "Empower thrall" + desc = "buffs all thralls with some sort of effect." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "speedboost_veils" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + psi_cost = 50 + cooldown_time = 1 MINUTES + spell_requirements = SPELL_CASTABLE_AS_BRAIN + sound = 'sound/magic/staff_healing.ogg' + /// If the buff also buffs all darkspawns + var/darkspawns_too = FALSE + /// Text to be put in the balloon alert upon cast + var/language_output = "DEBUGIFY" + +/datum/action/cooldown/spell/thrallbuff/before_cast(atom/cast_on) + . = ..() + var/datum/antagonist/darkspawn/dude = isdarkspawn(owner) + if(dude) + darkspawns_too = HAS_TRAIT(dude, TRAIT_DARKSPAWN_BUFFALLIES) + +/datum/action/cooldown/spell/thrallbuff/cast(atom/cast_on) //this looks like a mess, i'm sure it can be optimized + . = ..() + owner.balloon_alert(owner, "[language_output]") + for(var/datum/antagonist/thrall/lackey in GLOB.antagonists) + if(lackey.owner?.current && ishuman(lackey.owner.current)) + var/mob/living/carbon/human/target = lackey.owner.current + if(target && istype(target))//sanity check + empower(target) + if(darkspawns_too) + for(var/datum/antagonist/darkspawn/ally in GLOB.antagonists) + if(ally.owner?.current && ishuman(ally.owner.current)) + var/mob/living/carbon/human/target = ally.owner.current + if(target && istype(target))//sanity check + if(target == owner)//no self buffing + continue + empower(target) + +/datum/action/cooldown/spell/thrallbuff/proc/empower(mob/living/carbon/human/target) + return + +////////////////////////////Global AOE heal////////////////////////// +/datum/action/cooldown/spell/thrallbuff/heal + name = "thrall recovery" + desc = "Heals all thralls for an amount of brute and burn." + button_icon_state = "heal_veils" + var/heal_amount = 50 + language_output = "Plyn othra" + +/datum/action/cooldown/spell/thrallbuff/heal/empower(mob/living/carbon/human/target) + to_chat(target, span_velvet("You feel healed.")) + target.heal_ordered_damage(heal_amount, list(STAMINA, BURN, BRUTE, TOX, OXY, CLONE, BRAIN), BODYPART_ANY) + +////////////////////////////Temporary speed boost////////////////////////// +/datum/action/cooldown/spell/thrallbuff/speed + name = "Thrall envigorate" + desc = "Give all thralls a temporary movespeed bonus." + button_icon_state = "speedboost_veils" + language_output = "Vyzthun" + +/datum/action/cooldown/spell/thrallbuff/speed/empower(mob/living/carbon/human/target) + to_chat(target, span_velvet("You feel fast.")) + target.apply_status_effect(STATUS_EFFECT_SPEEDBOOST, -0.5, 15 SECONDS, type) + +////////////////////////////////////////////////////////////////////////// +//----------------Single target global ally giga buff-------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/elucidate + name = "Elucidate" + desc = "Channel significant power through an ally, greatly healing them, cleansing all CC and providing a speed boost." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "elucidate" + cast_range = INFINITY //lol + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_CASTABLE_AS_BRAIN + cooldown_time = 5 MINUTES //it's REALLY strong + sound = 'sound/magic/staff_healing.ogg' + psi_cost = 100 //it's REALLY strong + invocation_type = INVOCATION_SHOUT + invocation = "CKKREM!" + +/datum/action/cooldown/spell/pointed/elucidate/is_valid_target(atom/cast_on) + if(!iscarbon(cast_on)) + return FALSE + var/mob/living/carbon/target = cast_on + if(!is_team_darkspawn(target)) + return FALSE + if(target.stat == DEAD && get_dist(target, owner) > 2) + to_chat(owner, span_velvet("This one is beyond our help at such a range")) + return FALSE + return ..() + +/datum/action/cooldown/spell/pointed/elucidate/cast(atom/cast_on) + . = ..() + if(!iscarbon(cast_on)) + return FALSE + var/mob/living/carbon/target = cast_on + target.grab_ghost() + target.revive(TRUE) + target.SetAllImmobility(0, TRUE) + target.resting = FALSE + target.apply_status_effect(STATUS_EFFECT_SPEEDBOOST, -0.5, 15 SECONDS, type) + target.visible_message(span_danger("Streaks of velvet light crack out of [target]'s skin."), span_velvet("Power roars through you like a raging storm, pushing you to your absolute limits.")) + var/obj/item/cuffs = target.get_item_by_slot(ITEM_SLOT_HANDCUFFED) + var/obj/item/legcuffs = target.get_item_by_slot(ITEM_SLOT_LEGCUFFED) + if(target.handcuffed || target.legcuffed) + target.clear_cuffs(cuffs, TRUE, TRUE) + target.clear_cuffs(legcuffs, TRUE, TRUE) + playsound(get_turf(target),'yogstation/sound/creatures/darkspawn_death.ogg', 80, 1) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + if(darkspawn) + darkspawn.block_psi(1 MINUTES, type) + +////////////////////////////////////////////////////////////////////////// +//--------------------Places a camera for panopticon use----------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/darkspawn_build/thrall_eye + name = "Opticial" + desc = "Places a floating watchful eye." + psi_cost = 20 + object_type = /obj/machinery/camera/darkspawn + language_final = "Ixnce" + cast_time = 1 SECONDS + +////////////////////////////////////////////////////////////////////////// +//----------------------Abilities that thralls get----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/seize/lesser + psi_cost = 0 //thralls don't have psi + cooldown_time = 45 SECONDS + stun_duration = 5 SECONDS + +/datum/action/cooldown/spell/toggle/nightvision + name = "Nightvision" + desc = "Grants sight in the dark." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "glare" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_CASTABLE_AS_BRAIN + +/datum/action/cooldown/spell/toggle/nightvision/Remove(mob/living/remove_from) + Disable() + return ..() + +/datum/action/cooldown/spell/toggle/nightvision/Enable() + var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES) + if(eyes && istype(eyes)) + eyes.color_cutoffs = list(12, 0, 50) + eyes.lighting_cutoff = LIGHTING_CUTOFF_HIGH + owner.update_sight() + else + owner.lighting_cutoff = LIGHTING_CUTOFF_HIGH + +/datum/action/cooldown/spell/toggle/nightvision/Disable() + var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES) + if(eyes && istype(eyes)) + eyes.color_cutoffs = list(0, 0, 0) + eyes.lighting_cutoff = 0 + owner.update_sight() + else + owner.lighting_cutoff = 0 + +/datum/action/cooldown/spell/pointed/darkspawn_build/thrall_eye/thrall/thrall + desc = "Places a floating watchful eye for your masters to observe through." + psi_cost = 0 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/time_dilation.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/time_dilation.dm deleted file mode 100644 index 87a5a930090d..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/time_dilation.dm +++ /dev/null @@ -1,22 +0,0 @@ -//Greatly speeds up reflexes and recovery, at a massive Psi cost. -/datum/action/innate/darkspawn/time_dilation - name = "Time Dilation" - id = "time_dilation" - desc = "Greatly increases reaction times and action speed, and provides immunity to slowdown. This lasts for 1 minute. Costs 75 Psi." - button_icon_state = "time_dilation" - check_flags = AB_CHECK_CONSCIOUS - psi_cost = 75 - lucidity_price = 3 - -/datum/action/innate/darkspawn/time_dilation/IsAvailable(feedback = FALSE) - if(..() && !istype(owner, /mob/living/simple_animal/hostile/crawling_shadows)) - var/mob/living/L = owner - return !L.has_status_effect(STATUS_EFFECT_TIME_DILATION) - -/datum/action/innate/darkspawn/time_dilation/Activate() - var/mob/living/L = owner - L.apply_status_effect(STATUS_EFFECT_TIME_DILATION) - L.visible_message(span_warning("[L] howls as their body moves at wild speeds!"), \ - span_velvet("ckppw ck bwop
Your sigils howl out light as your body moves at incredible speed!")) - playsound(L, 'yogstation/sound/creatures/darkspawn_howl.ogg', 50, TRUE) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/veil_mind.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/veil_mind.dm deleted file mode 100644 index a26fff650ceb..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/veil_mind.dm +++ /dev/null @@ -1,45 +0,0 @@ -//Converts people within three tiles of the caster into veils. Also confuses noneligible targets and stuns silicons. -/datum/action/innate/darkspawn/veil_mind - name = "Veil Mind" - id = "veil_mind" - desc = "Converts nearby eligible targets into veils. To be eligible, they must be alive and recently drained by Devour Will." - button_icon_state = "veil_mind" - check_flags = AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS - psi_cost = 60 //since this is only useful when cast directly after a succ it should be pretty expensive - lucidity_price = 2 - -/datum/action/innate/darkspawn/veil_mind/Activate() - var/mob/living/carbon/human/H = owner - if(!H.can_speak_vocal()) - to_chat(H, span_warning("You can't speak!")) - return - owner.visible_message(span_warning("[owner]'s sigils flare as they inhale..."), "dawn kqn okjc...
\ - [span_notice("You take a deep breath...")]") - playsound(owner, 'yogstation/sound/ambience/antag/veil_mind_gasp.ogg', 25) - if(!do_after(owner, 1 SECONDS, owner)) - return - owner.visible_message(span_boldwarning("[owner] lets out a chilling cry!"), "...wjz oanra
\ - [span_notice("You veil the minds of everyone nearby.")]") - playsound(owner, 'yogstation/sound/ambience/antag/veil_mind_scream.ogg', 100) - for(var/mob/living/L in view(3, owner)) - if(L == owner) - continue - if(issilicon(L)) - to_chat(L, span_ownerdanger("$@!) ERR: RECEPTOR OVERLOAD ^!")) - SEND_SOUND(L, sound('sound/misc/interference.ogg', volume = 50)) - L.emote("alarm") - L.Stun(20) - L.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static) - L.clear_fullscreen("flash", 10) - else - if(HAS_TRAIT(L, TRAIT_DEAF)) - to_chat(L, span_warning("...but you can't hear it!")) - else - if(L.has_status_effect(STATUS_EFFECT_BROKEN_WILL)) - if(L.add_veil()) - to_chat(owner, span_velvet("[L.real_name] has become a veil!")) - else - to_chat(L, span_boldwarning("...and it scrambles your thoughts!")) - L.dir = pick(GLOB.cardinals) - L.adjust_confusion(2 SECONDS) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/warlock_abilities.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/warlock_abilities.dm new file mode 100644 index 000000000000..f058c211330e --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_abilities/warlock_abilities.dm @@ -0,0 +1,696 @@ +////////////////////////////////////////////////////////////////////////// +//-------------------------Warlock basic staff--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/toggle/dark_staff + name = "Channeling Staff" + desc = "Pull darkness from the void, knitting it into a staff." + panel = "Darkspawn" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "shadow_staff" + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + /// Staff object stored for the ability + var/obj/item/gun/magic/darkspawn/staff + /// Flags used for different effects that apply when a projectile hits something + var/effect_flags + +/datum/action/cooldown/spell/toggle/dark_staff/link_to(Target) + . = ..() + if(istype(target, /datum/mind)) + RegisterSignal(target, COMSIG_DARKSPAWN_UPGRADE_ABILITY, PROC_REF(handle_upgrade)) + RegisterSignal(target, COMSIG_DARKSPAWN_DOWNGRADE_ABILITY, PROC_REF(handle_downgrade)) + +/datum/action/cooldown/spell/toggle/dark_staff/proc/handle_upgrade(atom/source, flag) + effect_flags |= flag + if(staff) + staff.effect_flags = effect_flags + if(effect_flags & STAFF_UPGRADE_LIGHTEATER) + staff.LoadComponent(/datum/component/light_eater) + +/datum/action/cooldown/spell/toggle/dark_staff/proc/handle_downgrade(atom/source, flag) + effect_flags -= flag + if(staff) + staff.effect_flags = effect_flags + if(flag & STAFF_UPGRADE_LIGHTEATER) + qdel(staff.GetComponent(/datum/component/light_eater)) + +/datum/action/cooldown/spell/toggle/dark_staff/process() + active = owner.is_holding_item_of_type(/obj/item/gun/magic/darkspawn) + return ..() + +/datum/action/cooldown/spell/toggle/dark_staff/can_cast_spell(feedback) + if(!owner.get_empty_held_indexes() && !active) + if(feedback) + to_chat(owner, span_warning("You need an empty hand for this!")) + return FALSE + return ..() + +/datum/action/cooldown/spell/toggle/dark_staff/Enable() + owner.balloon_alert(owner, "Shhouna") + owner.visible_message(span_warning("[owner] knits shadows together into a staff!"), span_velvet("You summon your staff.")) + playsound(owner, 'yogstation/sound/magic/pass_create.ogg', 50, 1) + if(!staff) + staff = new (owner) + if(effect_flags & STAFF_UPGRADE_LIGHTEATER) + staff.LoadComponent(/datum/component/light_eater) + staff.effect_flags = effect_flags + owner.put_in_hands(staff) + +/datum/action/cooldown/spell/toggle/dark_staff/Disable() + owner.balloon_alert(owner, "Haoo") + owner.visible_message(span_warning("[owner]'s staff dissipates!"), span_velvet("You dispel the staff.")) + playsound(owner, 'yogstation/sound/magic/pass_dispel.ogg', 50, 1) + staff.moveToNullspace() + +////////////////////////////////////////////////////////////////////////// +//---------------------Warlock light eater ability----------------------// little bit of anti-fire too +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/extinguish + name = "Extinguish" + desc = "Extinguish all light around you." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "extinguish" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 60 + cooldown_time = 30 SECONDS + sound = 'yogstation/sound/ambience/antag/veil_mind_gasp.ogg' + aoe_radius = 6 + ///Secret item stored in the ability to hit things with to trigger light eater + var/obj/item/darkspawn_extinguish/bopper + ///List of objects seen at cast + var/list/seen_things + +/datum/action/cooldown/spell/aoe/extinguish/Grant(mob/grant_to) + . = ..() + bopper = new(src) + +/datum/action/cooldown/spell/aoe/extinguish/Remove(mob/living/remove_from) + QDEL_NULL(bopper) + return ..() + +/datum/action/cooldown/spell/aoe/extinguish/cast(atom/cast_on) + seen_things = view(owner) //cash all things you can see + . = ..() + owner.balloon_alert(owner, "Shwooh") + to_chat(owner, span_velvet("You extinguish all lights.")) + +/datum/action/cooldown/spell/aoe/extinguish/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(isturf(victim)) //no turf hitting + return + if(!seen_things) + return + if(!(victim in seen_things))//no putting out on the other side of walls + return + if(ishuman(victim))//put out any + var/mob/living/carbon/human/target = victim + if(target.can_block_magic(antimagic_flags, charge_cost = 1)) + return + target.extinguish_mob() + if(isobj(victim))//put out any items too + var/obj/target = victim + target.extinguish() + SEND_SIGNAL(bopper, COMSIG_ITEM_AFTERATTACK, victim, owner, TRUE) //just use a light eater attack on everyone + +/obj/item/darkspawn_extinguish + name = "extinguish" + desc = "you shouldn't be seeing this, it's just used for the spell and nothing else" + +/obj/item/darkspawn_extinguish/Initialize(mapload) + . = ..() + AddComponent(/datum/component/light_eater) + +////////////////////////////////////////////////////////////////////////// +//---------------------Mess up an APC pretty badly----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/touch/null_charge + name = "Null Charge" + desc = "Meddle with the powergrid via a functioning APC, causing a temporary stationwide power outage. Breaks the APC in the process." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "null_charge" + + antimagic_flags = NONE + panel = "Darkspawn" + check_flags = AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + invocation_type = INVOCATION_NONE + cooldown_time = 10 MINUTES + psi_cost = 200 + hand_path = /obj/item/melee/touch_attack/darkspawn + +/datum/action/cooldown/spell/touch/null_charge/is_valid_target(atom/cast_on) + if(!istype(cast_on, /obj/machinery/power/apc)) + return FALSE + var/obj/machinery/power/apc/target = cast_on + if(target.stat & BROKEN) + to_chat(owner, span_danger("This [target] no longer functions enough for access to the power grid.")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/touch/null_charge/cast_on_hand_hit(obj/item/melee/touch_attack/hand, obj/machinery/power/apc/target, mob/living/carbon/human/caster) + if(!target || !istype(target))//sanity check + return FALSE + + //Turn it off for the time being + owner.balloon_alert(owner, "Xlahwa") + target.set_light(0) + target.visible_message(span_warning("The [target] flickers and begins to grow dark.")) + + to_chat(caster, span_velvet("You dim the APC's screen and carefully begin siphoning its power into the void.")) + if(!do_after(caster, 5 SECONDS, target)) + //Whoops! The APC's light turns back on + to_chat(caster, span_velvet("Your concentration breaks and the APC suddenly repowers!")) + target.set_light(2) + target.visible_message(span_warning("The [target] begins glowing brightly!")) + return FALSE + + if(target.stat & BROKEN) + to_chat(owner, span_danger("This [target] no longer functions enough for access to the power grid.")) + return FALSE + + //We did it + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + if(darkspawn) + darkspawn.block_psi(60 SECONDS, type) + owner.balloon_alert(owner, "...SHWOOH!") + priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", ANNOUNCER_POWEROFF) + power_fail(30, 40) + to_chat(caster, span_velvet("You return the APC's power to the void, destroying it and disabling all others.")) + target.set_broken() + return TRUE + +////////////////////////////////////////////////////////////////////////// +//-----------------------Drain enemy, heal ally-------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/extract + name = "Extract" + desc = "Drain a target's life force or bestow it to an ally." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "extract" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED | AB_CHECK_LYING + spell_requirements = SPELL_REQUIRES_HUMAN + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + cast_range = 7 + ///The mob being targeted by the ability + var/mob/living/channeled + ///The beam visual drawn by the ability + var/datum/beam/visual + ///The antag datum that psi is being drawn from + var/datum/antagonist/darkspawn/cost + ///How much psi is taken every process tick (5 times a second) + var/upkeep_cost = 2 + ///How much damage or healing happens every process tick + var/damage_amount = 2 + ///The cooldown duration, only applied when the ability ends + var/actual_cooldown = 15 SECONDS + ///Boolean, whether or not the spell is healing the target + var/healing = FALSE + ///counts up each process tick, when reaching 5 prints a balloon alert and resets + var/balloon_counter = 0 + +/datum/action/cooldown/spell/pointed/extract/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/action/cooldown/spell/pointed/extract/Grant(mob/grant_to) + . = ..() + START_PROCESSING(SSfastprocess, src) + if(isdarkspawn(owner)) + cost = isdarkspawn(owner) + +/datum/action/cooldown/spell/pointed/extract/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + var/mob/living/living_cast_on = cast_on + if(living_cast_on.can_block_magic(antimagic_flags, charge_cost = 1)) + return FALSE + if(living_cast_on.stat == DEAD) + to_chat(owner, span_warning("[cast_on] is dead!")) + return FALSE + if(cast_on == owner) + to_chat(owner, span_warning("can't cast it on yourself!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/extract/process() + if(channeled) + balloon_counter++ + if(!visual || QDELETED(visual)) + cancel() + return + if(!healing && channeled.stat == DEAD) + cancel() + return + if(healing && channeled.health >= channeled.maxHealth) + cancel() + return + if(get_dist(owner, channeled) > cast_range) + cancel() + return + if(cost && (!cost.use_psi(upkeep_cost))) + cancel() + return + if(balloon_counter >= 5) + balloon_counter = 0 + owner.balloon_alert(owner, "...thum...") + if(healing) + channeled.heal_ordered_damage(damage_amount, list(STAMINA, BURN, BRUTE, TOX, OXY, CLONE), BODYPART_ANY) + else + channeled.apply_damage(damage_amount, BURN) + if(isliving(owner)) + var/mob/living/healed = owner + healed.heal_ordered_damage(damage_amount, list(STAMINA, BURN, BRUTE, TOX, OXY, CLONE), BODYPART_ANY) + build_all_button_icons(UPDATE_BUTTON_STATUS) + +/datum/action/cooldown/spell/pointed/extract/Trigger(trigger_flags, atom/target) + if(cancel()) + return FALSE + . = ..() + +/obj/effect/ebeam/darkspawn + name = "void beam" + +/datum/action/cooldown/spell/pointed/extract/cast(mob/living/cast_on) + . = ..() + owner.balloon_alert(owner, "Qokxlez") + visual = owner.Beam(cast_on, "slingbeam", 'yogstation/icons/mob/darkspawn.dmi' , INFINITY, cast_range) + channeled = cast_on + healing = is_team_darkspawn(channeled) + +/datum/action/cooldown/spell/pointed/extract/proc/cancel() + balloon_counter = 0 + if(visual) + qdel(visual) + if(channeled) + channeled = null + StartCooldown(actual_cooldown) + owner.balloon_alert(owner, "...qokshe") + return TRUE + return FALSE + +////////////////////////////////////////////////////////////////////////// +//------------------Literally just goliath tendrils---------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/darkspawn_build/abyssal_call + name = "Abyssal Call" + desc = "Summon abyssal tendrils from beyond the veil to grasp an enemy." + button_icon_state = "abyssal_call" + cast_range = 10 + cast_time = 0 + object_type = /obj/effect/temp_visual/goliath_tentacle/darkspawn/original + cooldown_time = 10 SECONDS + can_density = TRUE + language_final = "Xylt'he kkxla'thamara" + +////////////////////////////////////////////////////////////////////////// +//--------------Gives everyone nearby a random hallucination------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/aoe/mass_hallucination + name = "Mass Hallucination" + desc = "Forces brief delirium on all nearby enemies." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "mass_hallucination" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 30 + cooldown_time = 30 SECONDS + sound = 'yogstation/sound/ambience/antag/veil_mind_scream.ogg' + aoe_radius = 7 + ///how many times it hallucinates (1 per second) + var/hallucination_triggers = 3 + +/datum/action/cooldown/spell/aoe/mass_hallucination/cast(atom/cast_on) + . = ..() + owner.balloon_alert(owner, "H'ellekth'ele") + +/datum/action/cooldown/spell/aoe/mass_hallucination/cast_on_thing_in_aoe(atom/victim, atom/caster) + if(!isliving(victim)) + return + var/mob/living/target = victim + if(is_team_darkspawn(target)) //don't fuck with allies + return + if(target.can_block_magic(antimagic_flags, charge_cost = 1)) + return + hallucinate(target) + +/datum/action/cooldown/spell/aoe/mass_hallucination/proc/hallucinate(mob/living/target, times = hallucination_triggers) + if(times <= 0) + return + var/datum/hallucination/picked_hallucination = pick(GLOB.hallucination_list)//not using weights + target.cause_hallucination(picked_hallucination, "mass hallucination") + addtimer(CALLBACK(src, PROC_REF(hallucinate), target, times--), 1 SECONDS, TIMER_UNIQUE) + +////////////////////////////////////////////////////////////////////////// +//---------------------Detain and capture ability-----------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/seize //Stuns and mutes a human target for 10 seconds + name = "Seize" + desc = "Restrain a target's mental faculties, preventing speech and actions of any kind for a moderate duration." + panel = "Darkspawn" + button_icon_state = "seize" + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED | AB_CHECK_LYING + spell_requirements = SPELL_CASTABLE_AS_BRAIN + psi_cost = 35 + cooldown_time = 30 SECONDS + cast_range = 10 + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + ///duration of stun when used at close range + var/stun_duration = 10 SECONDS + +/datum/action/cooldown/spell/pointed/seize/before_cast(atom/cast_on) + . = ..() + if(!cast_on || !isliving(cast_on)) + return . | SPELL_CANCEL_CAST + var/mob/living/carbon/target = cast_on + if(istype(target) && target.stat) + to_chat(owner, span_warning("[target] must be conscious!")) + return . | SPELL_CANCEL_CAST + if(is_team_darkspawn(target)) + to_chat(owner, span_warning("You cannot seize allies!")) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/pointed/seize/cast(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return + + if(iscarbon(owner)) + var/mob/living/carbon/user = owner + if(!(user.check_obscured_slots() & ITEM_SLOT_EYES)) //only show if the eyes are visible + user.visible_message(span_warning("[user]'s eyes flash a deep purple")) + + owner.balloon_alert(owner, "Sskr'aya") + + var/mob/living/target = cast_on + if(target.can_block_magic(antimagic_flags, charge_cost = 1)) + return + + var/distance = get_dist(target, owner) + if (distance <= 2) + target.visible_message(span_danger("[target] suddenly collapses...")) + to_chat(target, span_userdanger("A purple light flashes through your mind, and you lose control of your movements!")) + target.Paralyze(stun_duration) + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.silent += 10 + else //Distant glare + var/loss = max(120 - (distance * 10), 0) + target.adjustStaminaLoss(loss) + target.adjust_stutter(loss) + to_chat(target, span_userdanger("A purple light flashes through your mind, and exhaustion floods your body...")) + +////////////////////////////////////////////////////////////////////////// +//----------------------Basically a fancy jaunt-------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/erase_time/darkspawn + name = "Quantum disruption" + desc = "Disrupt the flow of possibilities, where you are, where you could be." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + button_icon_state = "quantum_disruption" + panel = "Darkspawn" + antimagic_flags = NONE + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + psi_cost = 80 + cooldown_time = 60 SECONDS + length = 5 SECONDS + +/datum/action/cooldown/spell/erase_time/darkspawn/cast(mob/living/user) + . = ..() + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + if(. && darkspawn) + owner.balloon_alert(owner, "KSH SHOL'NAXHAR!") + darkspawn.block_psi(20 SECONDS, type) + + +////////////////////////////////////////////////////////////////////////// +//----------------I stole blood beam from blood cultists----------------// (and made it better) +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/shadow_beam + name = "Void beam" + desc = "After a short delay, fire a huge beam of void terrain across the entire station." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "shadow_beam" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + cooldown_time = 90 SECONDS + psi_cost = 100 //big fuckin layzer + sound = null + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + cast_range = INFINITY //lol + ///boolean, whether or not the spell is being charged + var/charging = FALSE + ///how many times the charge sound effect plays, also affects delay, sorta like a cast time + var/charge_ticks = 2 //1 second per tick + ///turf of the caster at the moment of casting starting + var/turf/targets_from + ///turf targeted for the center of the beam + var/turf/targets_to + +/datum/action/cooldown/spell/pointed/shadow_beam/can_cast_spell(feedback) + if(charging) + return + return ..() + +/datum/action/cooldown/spell/pointed/shadow_beam/cast(atom/cast_on) + . = ..() + if(charging) + return + + targets_from = get_turf(owner) + targets_to = get_turf(cast_on) + + owner.balloon_alert(owner, "Qwo...") + to_chat(owner, span_velvet("You start building up psionic energy.")) + charging = TRUE + INVOKE_ASYNC(src, PROC_REF(start_beam), owner) //so the reticle doesn't continue to show even after clicking + +/datum/action/cooldown/spell/pointed/shadow_beam/proc/start_beam(mob/user) + charging = TRUE + INVOKE_ASYNC(src, PROC_REF(charge), user) //visual effect + if(do_after(user, charge_ticks SECONDS, user)) + INVOKE_ASYNC(src, PROC_REF(fire_beam), user) + charging = FALSE + +/datum/action/cooldown/spell/pointed/shadow_beam/proc/charge(mob/user, times = charge_ticks, first = TRUE) + if(!charging) + return + if(times <= 0) + return + var/power = charge_ticks - times //grow in sound volume and added sound range as it charges + var/volume = min(10 + (power * 20), 60) + playsound(user, 'sound/effects/magic.ogg', volume, TRUE, power) + playsound(user, 'yogstation/sound/magic/devour_will_begin.ogg', volume, TRUE, power) + if(first) + new /obj/effect/temp_visual/cult/rune_spawn/rune1(user.loc, 2 SECONDS, "#21007F") + else + new /obj/effect/temp_visual/cult/rune_spawn/rune1/reverse(user.loc, 2 SECONDS, "#21007F") + addtimer(CALLBACK(src, PROC_REF(charge), user, times - 1, !first), 1 SECONDS) + +/obj/effect/temp_visual/cult/rune_spawn/rune1/reverse //spins the other way, that's it + turnedness = 179 + +/datum/action/cooldown/spell/pointed/shadow_beam/proc/fire_beam(mob/user) + if(!targets_from || !targets_to) //sanity check + return + user.balloon_alert(user, "...GX'KSHA!") + if(isdarkspawn(user)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(user) + darkspawn.block_psi(30 SECONDS, type) + + playsound(user, 'yogstation/sound/magic/devour_will_end.ogg', 100, FALSE, 30) + //split in two so the targeted tile is always in the center of the beam + for(var/turf/step_target in getline(targets_from, targets_to)) + spawn_ground(step_target) + + //extrapolate a new end target along the line using an angle + var/turf/distant_target = get_turf_in_angle(get_angle(targets_from, targets_to), targets_to, 100) //100 tiles past the end point, roughly following the angle of the original line + + for(var/turf/step_target in getline(targets_to, distant_target)) + spawn_ground(step_target) + +/datum/action/cooldown/spell/pointed/shadow_beam/proc/spawn_ground(turf/target) + for(var/turf/realtile in RANGE_TURFS(1, target)) //bit of aoe around the line (probably super fucking intensive lol) + var/obj/effect/temp_visual/darkspawn/chasm/effect = locate() in realtile.contents + if(!effect) //to prevent multiple visuals from appearing on the same tile and doing more damage than intended + effect = new(realtile) + +/obj/effect/temp_visual/darkspawn + name = "echoing void" + icon = 'yogstation/icons/effects/effects.dmi' + icon_state = "nothing" + +/obj/effect/temp_visual/darkspawn/chasm //a slow field that eventually explodes + icon_state = "consuming" + duration = 4 SECONDS //functions as the delay until the explosion, just make sure it's not shorter than 1.1 seconds or it fucks up the animation + plane = WALL_PLANE + layer = ABOVE_OPEN_TURF_LAYER + alpha = 0 + +/obj/effect/temp_visual/darkspawn/chasm/Initialize(mapload) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered) + ) + AddElement(/datum/element/connect_loc, loc_connections) + animate(src, 1.1 SECONDS, alpha = 255) //fade into existence + +/obj/effect/temp_visual/darkspawn/chasm/proc/on_entered(datum/source, atom/movable/AM, ...) + if(isliving(AM)) + var/mob/living/target = AM + if(!is_team_darkspawn(target)) + target.apply_status_effect(STATUS_EFFECT_SPEEDBOOST, 3, 1 SECONDS, type) //slow field, makes it harder to escape + +/obj/effect/temp_visual/darkspawn/chasm/Destroy() + new/obj/effect/temp_visual/darkspawn/detonate(get_turf(src)) + return ..() + +/obj/effect/temp_visual/darkspawn/detonate //the explosion effect, applies damage when it disappears + icon_state = "detonate" + plane = WALL_PLANE + layer = ABOVE_OPEN_TURF_LAYER + duration = 2 + +/obj/effect/temp_visual/darkspawn/detonate/Destroy() + var/turf/tile_location = get_turf(src) + for(var/mob/living/victim in tile_location.contents) + if(is_team_darkspawn(victim)) + victim.heal_ordered_damage(90, list(BURN, BRUTE, TOX, OXY, CLONE, STAMINA), BODYPART_ANY) + else if(!victim.can_block_magic(MAGIC_RESISTANCE_MIND)) + victim.take_overall_damage(10, 50, 200) //skill issue if you don't dodge it (won't crit if you're full hp) + victim.emote("scream") + return ..() + +////////////////////////////////////////////////////////////////////////// +//-------------------I stole heirophant's burst ability-----------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/null_burst + name = "Null Burst" + desc = "After a short delay, create an explosion of void terrain at the targeted location." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "null_burst" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + panel = "Darkspawn" + antimagic_flags = MAGIC_RESISTANCE_MIND + check_flags = AB_CHECK_CONSCIOUS + spell_requirements = SPELL_REQUIRES_HUMAN + cooldown_time = 90 SECONDS + psi_cost = 100 //big fuckin boom + sound = null + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + cast_range = INFINITY //lol + ///boolean, whether or not the ability is actively being casted + var/charging = FALSE + ///how many times the charge sound effect plays, also affects delay, sorta like a cast time + var/charge_ticks = 2 //1 second per tick + ///the targeted location for the burst + var/turf/targets_to + ///radius of the burst aoe + var/burst_range = 4 + ///modifies the delay between waves in the burst + var/spread_speed = 1 + +/datum/action/cooldown/spell/pointed/null_burst/can_cast_spell(feedback) + if(charging) + return + return ..() + +/datum/action/cooldown/spell/pointed/null_burst/is_valid_target(atom/cast_on) //can target yourself if you really want to + return TRUE + +/datum/action/cooldown/spell/pointed/null_burst/cast(atom/cast_on) + . = ..() + if(charging) + return + + targets_to = get_turf(cast_on) + + owner.balloon_alert(owner, "Qwo...") + to_chat(owner, span_velvet("You start building up psionic energy.")) + charging = TRUE + INVOKE_ASYNC(src, PROC_REF(start_beam), owner) //so the reticle doesn't continue to show even after clicking + +/datum/action/cooldown/spell/pointed/null_burst/proc/start_beam(mob/user) + charging = TRUE + INVOKE_ASYNC(src, PROC_REF(charge), user) //visual effect + if(do_after(user, charge_ticks SECONDS, user)) + INVOKE_ASYNC(src, PROC_REF(burst), user) + charging = FALSE + +/datum/action/cooldown/spell/pointed/null_burst/proc/charge(mob/user, times = charge_ticks, first = TRUE) + if(!charging) + return + if(times <= 0) + return + var/power = charge_ticks - times //grow in sound volume and added sound range as it charges + var/volume = min(10 + (power * 20), 60) + playsound(user, 'sound/effects/magic.ogg', volume, TRUE, power) + playsound(user, 'yogstation/sound/magic/devour_will_begin.ogg', volume, TRUE, power) + if(first) + new /obj/effect/temp_visual/cult/rune_spawn/rune1(user.loc, 2 SECONDS, "#21007F") + else + new /obj/effect/temp_visual/cult/rune_spawn/rune1/reverse(user.loc, 2 SECONDS, "#21007F") + addtimer(CALLBACK(src, PROC_REF(charge), user, times - 1, !first), 1 SECONDS) + +/datum/action/cooldown/spell/pointed/null_burst/proc/burst(mob/user) + if(!targets_to) + return + + user.balloon_alert(user, "...GWO'KSHA!") + if(isdarkspawn(user)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(user) + darkspawn.block_psi(30 SECONDS, type) + + playsound(user, 'yogstation/sound/magic/devour_will_end.ogg', 100, FALSE, 30) + playsound(targets_to,'yogstation/sound/magic/divulge_end.ogg', 70, TRUE, burst_range) + + var/last_dist = 0 + var/real_delay = 0 + for(var/t in spiral_range_turfs(burst_range, targets_to)) + var/turf/T = t + if(!T) + continue + var/dist = get_dist(targets_to, T) + if(dist > last_dist) + last_dist = dist + real_delay += (0.1 SECONDS) + (min(burst_range - last_dist, 1.2 SECONDS) * spread_speed) //gets faster as it gets further out + addtimer(CALLBACK(src, PROC_REF(spawn_ground), T), real_delay) //spawns turf with a callback to avoid using sleep() in a loop like heiro does + +/datum/action/cooldown/spell/pointed/null_burst/proc/spawn_ground(turf/target) + new /obj/effect/temp_visual/darkspawn/chasm(target) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_ability.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_ability.dm deleted file mode 100644 index 06b5a9a63697..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_ability.dm +++ /dev/null @@ -1,53 +0,0 @@ -/datum/action/innate/darkspawn - name = "darkspawn ability" - var/id //The ability's ID, for giving, taking and such - desc = "This probably shouldn't exist." - button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' - background_icon_state = "bg_alien" - overlay_icon_state = "bg_alien_border" - buttontooltipstyle = "alien" - - var/psi_cost = 0 //How much psi the ability costs to use - var/psi_addendum = "" //If applicable, descriptive text shown after the cost - var/lucidity_price = 0 //How much lucidity the ability costs to buy; if this is 0, it isn't listed on the catalog - var/blacklisted = FALSE //If the ability can't be gained from the psi web - var/in_use = FALSE //For channeled/cast-time abilities - var/datum/antagonist/darkspawn/darkspawn //Linked antag datum for drawing lucidity and psi - -/datum/action/innate/darkspawn/New() - ..() - START_PROCESSING(SSfastprocess, src) - -/datum/action/innate/darkspawn/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/datum/action/innate/darkspawn/process() - build_all_button_icons() //so as to be consistent with psi costs and situational requirements, keep the button updated - -/datum/action/innate/darkspawn/Trigger() - var/activated = FALSE - if(!IsAvailable(feedback = TRUE)) - return - if(!active) - activated = Activate() - else - activated = Deactivate() - if(darkspawn) - darkspawn.use_psi(psi_cost * activated) - -/datum/action/innate/darkspawn/IsAvailable(feedback = FALSE) - if(!darkspawn) - return - if(!darkspawn.has_psi(psi_cost)) - if (feedback) - owner.balloon_alert(owner, "not enough psi!") - return - if(in_use) - if (feedback) - owner.balloon_alert(owner, "already in use!") - return - . = ..() - -/datum/action/innate/darkspawn/proc/reset() - in_use = FALSE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_antag.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_antag.dm new file mode 100644 index 000000000000..37871a16c805 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_antag.dm @@ -0,0 +1,693 @@ +/datum/antagonist/darkspawn + name = "Darkspawn" + roundend_category = "darkspawn" + antagpanel_category = "Darkspawn" + job_rank = ROLE_DARKSPAWN + antag_hud_name = "darkspawn" + ui_name = "AntagInfoDarkspawn" + antag_moodlet = /datum/mood_event/sling + + //team used for all the darkspawns, thralls, and the objective + var/datum/team/darkspawn/team + ///name of the player character before the divulge + var/disguise_name + ///name of the player character after the divulge + var/darkspawn_name + ///keeps track of where the darkspawn player is in progression + var/darkspawn_state = DARKSPAWN_MUNDANE //0 for normal crew, 1 for divulged, and 2 for progenitor + ///Component that keeps track of all the spells a darkspawn can learn + var/datum/component/darkspawn_class/picked_class + + //Psi variables + //Psi is the resource used for darkspawn powers + ///Currently available psi + var/psi = 100 + ///Maximum amount of psi + var/psi_cap = 100 + ///How long before psi starts regenerating + var/psi_regen_delay = 9 SECONDS + ///how much psi is regenerated per second once it does start regenerating + var/psi_per_second = 20 + ///When this finishes it's cooldown, regenerate Psi and restart + COOLDOWN_DECLARE(psi_cooldown) + ///Used to prevent duplicate regen proc calls + var/psi_regenerating = FALSE + + ///Willpower is used to buy abilities and is gained by using Devour Will + var/willpower = 6 + + ///Default amount healed in darkness + var/dark_healing = 8 + ///Default amount of damage taken in light + var/light_burning = 7 + ///These three variables + ///multiplies brute damage taken + var/brute_mod = 1 + ///multiplies burn damage taken + var/burn_mod = 1 + ///multiplies stamina damage taken + var/stam_mod = 1 + + ///Boolean, if the player has been notified that they are being revived by undying sigils + var/revive_notice = FALSE + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------------Gain and loss stuff---------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/on_gain() + START_PROCESSING(SSprocessing, src) + owner.special_role = "darkspawn" + for (var/T in GLOB.antagonist_teams) + if (istype(T, /datum/team/darkspawn)) + team = T + if(!team) + team = new + team.add_member(owner) + return ..() + +/datum/antagonist/darkspawn/on_removal() + STOP_PROCESSING(SSprocessing, src) + owner.special_role = null + if(team) + team.remove_member(owner) + owner.current.hud_used.psi_counter.invisibility = initial(owner.current.hud_used.psi_counter.invisibility) + owner.current.hud_used.psi_counter.maptext = "" + QDEL_NULL(picked_class) + return ..() + +/datum/antagonist/darkspawn/apply_innate_effects(mob/living/mob_override) + var/mob/living/current_mob = mob_override || owner.current + if(!current_mob) + return + handle_clown_mutation(current_mob, mob_override ? null : "Our powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.") + add_team_hud(current_mob) + current_mob.grant_language(/datum/language/darkspawn) + + //psi stuff + if(current_mob?.hud_used?.psi_counter) + current_mob.hud_used.psi_counter.invisibility = 0 + update_psi_hud() + + current_mob.faction |= ROLE_DARKSPAWN + + //for panopticon + if(current_mob) + current_mob.AddComponent(/datum/component/internal_cam, list(ROLE_DARKSPAWN)) + var/datum/component/internal_cam/cam = current_mob.GetComponent(/datum/component/internal_cam) + if(cam) + cam.change_cameranet(GLOB.thrallnet) + + //divulge + if(darkspawn_state == DARKSPAWN_MUNDANE) + var/datum/action/cooldown/spell/divulge/action = locate() in current_mob.actions + if(!action) + action = new(owner) + action.Grant(current_mob) + addtimer(CALLBACK(src, PROC_REF(begin_force_divulge)), 20 MINUTES) //this won't trigger if they've divulged when the proc runs + +/datum/antagonist/darkspawn/remove_innate_effects() + owner.current.remove_language(/datum/language/darkspawn) + owner.current.faction -= ROLE_DARKSPAWN + if(owner.current) + qdel(owner.current.GetComponent(/datum/component/internal_cam)) + + for(var/datum/action/cooldown/spell/spells in owner.current.actions) //remove divulge if they haven't yet + if(istype(spells, /datum/action/cooldown/spell/divulge)) + spells.Remove(owner.current) + qdel(spells) + +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------------Antag hud---------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/add_team_hud(mob/target, antag_to_check) + QDEL_NULL(team_hud_ref) + + team_hud_ref = WEAKREF(target.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/has_antagonist, + "antag_team_hud_[REF(src)]", + hud_image_on(target), + antag_to_check || type, + )) + + // Add HUDs that they couldn't see before + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if (is_team_darkspawn(owner.current)) //needs to change this line so both the darkspawn and thrall sees it + antag_hud.show_to(owner.current) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------------Greet---------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/greet() + var/mob/user = owner.current + if(!user) //sanity check + return + + user.playsound_local(get_turf(user), 'yogstation/sound/ambience/antag/darkspawn.ogg', 50, FALSE) + + var/list/report = list() + report += span_progenitor("You are a darkspawn!") + report += span_notice("Add :[MODE_KEY_DARKSPAWN] or .[MODE_KEY_DARKSPAWN] before your message to silently speak with any other darkspawn.") + report += "When you are ready, retreat to a hidden location and Divulge to shed your human skin." + report += "Remember that this will make you die in the light and heal in the dark - keep to the shadows." + report += span_boldwarning("If you do not do this within twenty five minutes, this will happen involuntarily. Prepare quickly.") + to_chat(user, report.Join("
")) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Antag Team stuff----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/get_team() + return team + +/datum/antagonist/darkspawn/create_team(datum/team/darkspawn/new_team) + if(!new_team) + return + if(!istype(new_team)) + stack_trace("Wrong team type passed to [type] initialization.") + team = new_team + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------------UI and Psi web stuff--------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/ui_data(mob/user) + var/list/data = list() + + data["willpower"] = willpower + data["objectives"] = get_objectives() + data["divulged"] = (darkspawn_state > DARKSPAWN_MUNDANE) + data["ascended"] = (darkspawn_state == DARKSPAWN_PROGENITOR) + data["has_class"] = picked_class + if(team) + data["lucidity_drained"] = team.lucidity + data["max_thralls"] = team.max_thralls + data["current_thralls"] = LAZYLEN(team.thralls) + if(LAZYLEN(team.thralls)) + var/list/thrall_names = list() + for(var/datum/mind/dude in team.thralls) + thrall_names += dude.name + data["thrall_names"] += list(thrall_names) + + var/list/categories = list(STORE_OFFENSE, STORE_UTILITY, STORE_PASSIVE) + for(var/category in categories) + var/list/category_data = list() + category_data["name"] = category + + var/list/paths = list() + + if(picked_class) + for(var/datum/psi_web/knowledge as anything in picked_class.get_purchasable_abilities()) + if(category != initial(knowledge.menu_tab)) + continue + + var/list/knowledge_data = list() + knowledge_data["path"] = knowledge + knowledge_data["name"] = initial(knowledge.name) + knowledge_data["desc"] = initial(knowledge.desc) + knowledge_data["lore_description"] = initial(knowledge.lore_description) + knowledge_data["cost"] = initial(knowledge.willpower_cost) + knowledge_data["disabled"] = (initial(knowledge.willpower_cost) > willpower) + knowledge_data["infinite"] = (initial(knowledge.infinite)) + if(initial(knowledge.icon_state)) //only include an icon if one actually exists + knowledge_data["icon"] = icon2base64(icon(initial(knowledge.icon), initial(knowledge.icon_state))) + + paths += list(knowledge_data) + + category_data["knowledgeData"] = paths + data["categories"] += list(category_data) + + return data + +/datum/antagonist/darkspawn/ui_static_data(mob/user) + var/list/data = list() + + data["antag_name"] = name + + for(var/datum/component/darkspawn_class/class as anything in subtypesof(/datum/component/darkspawn_class)) + if(!initial(class.choosable)) + continue + var/list/class_data = list() + class_data["path"] = class + class_data["name"] = initial(class.name) + class_data["color"] = initial(class.class_color) + class_data["description"] = initial(class.description) + class_data["long_description"] = initial(class.long_description) + + data["classData"] += list(class_data) + + return data + +/datum/antagonist/darkspawn/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("purchase") + var/upgrade_path = text2path(params["upgrade_path"]) + if(!ispath(upgrade_path, /datum/psi_web)) + return FALSE + SEND_SIGNAL(owner, COMSIG_DARKSPAWN_PURCHASE_POWER, upgrade_path) + if("select") + if(picked_class) + return FALSE + var/class_path = text2path(params["class_path"]) + if(!ispath(class_path, /datum/component/darkspawn_class)) + return FALSE + picked_class = owner.AddComponent(class_path) + var/processed_message = span_velvet("\[Mindlink\] [owner.current] has selected [picked_class.name] as their class.") + for(var/T in GLOB.alive_mob_list) + var/mob/M = T + if(is_darkspawn_or_thrall(M)) + to_chat(M, processed_message) + +/datum/antagonist/darkspawn/ui_status(mob/user, datum/ui_state/state) + if(user.stat == DEAD) + return UI_CLOSE + return ..() + +//////////////////////////////////////////////////////////////////////////////////// +//---------------------------------Process proc-----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/process(delta_time) + psi = min(psi, psi_cap) + if(psi < psi_cap && COOLDOWN_FINISHED(src, psi_cooldown) && !psi_regenerating) + if(HAS_TRAIT(src, TRAIT_DARKSPAWN_PSIBLOCK)) + return //prevent regeneration + regenerate_psi() + update_psi_hud() + + //low probability because i want it to be super rare and a "wait what the FUCK they can do that!?" type moment + //if it becomes too common, then people stop putting darkspawn brains in mmi (which is metagaming, but whatever) + if((rand(0, 10000) == 0) && owner.current && (isbrain(owner.current) || issilicon(owner.current)))//who in their RIGHT mind would put the brain of the PSIONIC antag into an mmi after you kill them + addtimer(CALLBACK(src, PROC_REF(grant_reform)), rand(1, (20 MINUTES)), TIMER_UNIQUE) //give it a random delay before granting the ability, so it's luck squared to get it immediately + + if((owner?.current?.stat == DEAD) && HAS_TRAIT(src, TRAIT_DARKSPAWN_UNDYING) && ishuman(owner.current) && !QDELETED(owner.current)) + var/mob/living/carbon/human/deadguy = owner.current + var/turf/location = get_turf(owner.current) + var/light_amount = location.get_lumcount() + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) + if(!revive_notice) + deadguy.visible_message(span_notice("[deadguy]'s body twitches."), span_progenitor("Your body lurches as it refuses to be stopped by death.")) + revive_notice = TRUE + deadguy.heal_ordered_damage(10, list(STAMINA, BURN, BRUTE, TOX, OXY, CLONE, BRAIN), BODYPART_ANY) + if(deadguy.health >= deadguy.maxHealth) + deadguy.grab_ghost() + deadguy.revive(TRUE) + revive_notice = FALSE + deadguy.visible_message(span_progenitor("[deadguy]'s sigils flare brightly as they are once again in the realm of the living!"), span_progenitor("You rise once more!")) + playsound(deadguy, 'yogstation/sound/magic/demented_outburst_scream.ogg', 40, FALSE) + else if(revive_notice) + revive_notice = FALSE + deadguy.visible_message(span_notice("[deadguy]'s body stills."), span_velvet("Your body stills once more.")) + + if(owner.current && ishuman(owner.current) && !isshadowperson(owner.current)) + var/datum/action/cooldown/spell/divulge/action = locate() in owner.current.actions + if(!action) + action = new(owner) + action.Grant(owner.current) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Psi regen and usage-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/proc/has_psi(amt) + return psi >= amt + +/datum/antagonist/darkspawn/proc/use_psi(amt) + if(!has_psi(amt)) + return + if(psi_regen_delay) + COOLDOWN_START(src, psi_cooldown, psi_regen_delay) + psi -= amt + psi = round(psi, 0.1) + update_psi_hud() + return TRUE + +/datum/antagonist/darkspawn/proc/regenerate_psi() + set waitfor = FALSE + psi_regenerating = TRUE + var/regen_amount = max(1, (psi_per_second/20))//max speed is 20 ticks per second, regenerate extra per tick if regen speed is over 20 (only encountered when admemes mess with numbers) + psi = min(psi + regen_amount, psi_cap) + psi = round(psi, 0.1) //keep it at reasonable numbers rather than ridiculous decimals + update_psi_hud() + if(psi >= psi_cap || !COOLDOWN_FINISHED(src, psi_cooldown)) + psi_regenerating = FALSE + return + var/delay = (1/psi_per_second) SECONDS + addtimer(CALLBACK(src, PROC_REF(regenerate_psi)), delay, TIMER_UNIQUE) //tick it up very quickly + +///temporarily block psi regeneration +/datum/antagonist/darkspawn/proc/block_psi(duration = 5 SECONDS, identifier) + if(!identifier) + return + ADD_TRAIT(src, TRAIT_DARKSPAWN_PSIBLOCK, identifier) + if(owner.current) + owner.current.throw_alert("psiblock", /atom/movable/screen/alert/psiblock) + addtimer(CALLBACK(src, PROC_REF(unblock_psi), identifier), duration, TIMER_UNIQUE | TIMER_OVERRIDE) + +/datum/antagonist/darkspawn/proc/unblock_psi(identifier) + REMOVE_TRAIT(src, TRAIT_DARKSPAWN_PSIBLOCK, identifier) + if(!HAS_TRAIT(src, TRAIT_DARKSPAWN_PSIBLOCK) && owner.current) + owner.current.clear_alert("psiblock") + +/datum/antagonist/darkspawn/proc/update_psi_hud() + if(!owner.current || !owner.current.hud_used) + return + var/atom/movable/screen/counter = owner.current.hud_used.psi_counter + counter.maptext = ANTAG_MAPTEXT(psi, COLOR_DARKSPAWN_PSI) + +//////////////////////////////////////////////////////////////////////////////////// +//-----------------------------------Divulge--------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/proc/divulge(forced = FALSE) + if(darkspawn_state >= DARKSPAWN_DIVULGED) + return FALSE + + var/mob/living/carbon/human/user = owner.current + + if(!user || !istype(user))//sanity check + return + + if(!picked_class) //you didn't pick, now it gets forced on you + var/list/classes = list() + for(var/datum/component/darkspawn_class/class as anything in subtypesof(/datum/component/darkspawn_class)) + if(initial(class.choosable)) + classes |= class + + var/chosen = pick(classes) + + picked_class = owner.AddComponent(chosen) + + owner.assigned_role = picked_class.name //they stop being whatever job they were the moment they divulge + + if(forced) + owner.current.visible_message( + span_boldwarning("[owner.current]'s skin sloughs off, revealing warping black flesh covered in symbols!"), + span_userdanger("You have forcefully divulged!")) + + for(var/datum/action/cooldown/spell/spells in user.actions) //remove the ability that triggers this + if(istype(spells, /datum/action/cooldown/spell/divulge)) + spells.Remove(user) + qdel(spells) + + user.fully_heal() + disguise_name = user.real_name //keep track of the old name + user.set_species(/datum/species/shadow/darkspawn) + darkspawn_name = user.real_name //keep track of the new name + user.update_appearance(UPDATE_OVERLAYS) + + show_to_ghosts = TRUE + var/processed_message = span_velvet("\[Mindlink\] [disguise_name] has removed their human disguise and is now [darkspawn_name].") + for(var/mob/M as anything in GLOB.alive_mob_list) + if(M == user) + continue + if(is_team_darkspawn(M)) + to_chat(M, processed_message) + deadchat_broadcast(processed_message, null, user) + + darkspawn_state = DARKSPAWN_DIVULGED + to_chat(user, span_velvet("Your mind has expanded. Avoid the light. Keep to the shadows. Your time will come.")) + to_chat(user, span_progenitor("Access to the Psi Web store has been unlocked in the antag menu.")) + to_chat(user, span_progenitor("Spend your [willpower] willpower to purchase abilities and upgrades.")) + return TRUE + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Forced Divulge------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/proc/begin_force_divulge() + if(darkspawn_state != DARKSPAWN_MUNDANE) + return + if(owner.current.stat == DEAD) + return + to_chat(owner.current, span_userdanger("You feel the skin you're wearing crackling like paper - you will forcefully divulge soon! Get somewhere hidden and dark!")) + owner.current.playsound_local(owner.current, 'yogstation/sound/magic/divulge_01.ogg', 50, FALSE, pressure_affected = FALSE) + addtimer(CALLBACK(src, PROC_REF(force_divulge), 5 MINUTES)) + +/datum/antagonist/darkspawn/proc/force_divulge() + if(darkspawn_state != DARKSPAWN_MUNDANE) + return + if(owner.current.stat == DEAD) + return + var/mob/living/carbon/C = owner.current + if(C && !ishuman(C)) + C.humanize() + var/mob/living/carbon/human/H = owner.current + if(!H) + owner.current.gib(TRUE) + H.visible_message(span_boldwarning("[H]'s skin begins to slough off in sheets!"), \ + span_userdanger("You can't maintain your disguise any more! It begins sloughing off!")) + playsound(H, 'yogstation/sound/creatures/darkspawn_force_divulge.ogg', 50, FALSE) + H.do_jitter_animation(1000) + var/processed_message = span_progenitor("\[Mindlink\] [H.real_name] has not divulged in time and is now forcefully divulging.") + for(var/mob/M in GLOB.player_list) + if(M.stat != DEAD && is_team_darkspawn(M)) + to_chat(M, processed_message) + deadchat_broadcast(processed_message, null, H) + addtimer(CALLBACK(src, PROC_REF(divulge), TRUE), 2.5 SECONDS) + +//////////////////////////////////////////////////////////////////////////////////// +//-----------------------------------Sacrament------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/proc/sacrament() + var/mob/living/carbon/human/user = owner.current + + if(!user || !istype(user))//sanity check + return + + user.status_flags |= GODMODE + + if(!GLOB.sacrament_done) + GLOB.sacrament_done = TRUE + team.upon_sacrament() + SSsecurity_level.set_level(SEC_LEVEL_DELTA) + shatter_lights() + addtimer(CALLBACK(src, PROC_REF(sacrament_shuttle_call)), 5 SECONDS) + set_starlight(COLOR_VELVET) //i wanna change power and range, but that causes immense lag + to_chat(world, span_velvet("Reality begins to quake and crack at the seams.")) + addtimer(CALLBACK(src, PROC_REF(start_overlay)), 15 SECONDS) + SEND_GLOBAL_SIGNAL(COMSIG_DARKSPAWN_ASCENSION) + + SSachievements.unlock_achievement(/datum/achievement/greentext/darkspawn, user.client) + + for(var/datum/action/cooldown/spell/spells in user.actions) //they'll have progenitor specific abilities + spells.Remove(user) + qdel(spells) + + var/class_color = COLOR_DARKSPAWN_PSI + var/datum/component/darkspawn_class/class = user.GetComponent(/datum/component/darkspawn_class) + if(class && istype(class) && class.class_color) + class_color = class.class_color //this line actually kinda hurts me + + // Spawn the progenitor + var/mob/living/simple_animal/hostile/darkspawn_progenitor/progenitor = new(get_turf(user), user.real_name, class_color) + user.mind.transfer_to(progenitor) + + psi = 9999 + psi_cap = 9999 + psi_per_second = 9999 + psi_regen_delay = 0 + update_psi_hud() + + darkspawn_state = DARKSPAWN_PROGENITOR + QDEL_IN(user, 1) + +///get rid of all lights by calling the light eater proc +/datum/antagonist/darkspawn/proc/shatter_lights() + for(var/obj/machinery/light/L in GLOB.machines) + addtimer(CALLBACK(L, TYPE_PROC_REF(/obj/machinery/light, on_light_eater)), rand(1, 50)) //stagger the "shatter" to reduce lag + +///call a shuttle +/datum/antagonist/darkspawn/proc/sacrament_shuttle_call() + SSshuttle.emergency.request(null, 0, null, 0.1) + +/datum/antagonist/darkspawn/proc/start_overlay() + to_chat(world, span_progenitor("SOMETHING IS WRONG.")) + for(var/mob/living/person in GLOB.player_list) + person.AddComponent(/datum/component/shadowlands) + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------------Reform body from brain------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +///proc used to delay the granting of the reform body ability +/datum/antagonist/darkspawn/proc/grant_reform() + if(!owner.current || !(isbrain(owner.current) || issilicon(owner.current))) + return + var/datum/action/cooldown/spell/reform_body/recreance = locate() in owner.current.actions + if(recreance) + return + recreance = new(owner) + recreance.Grant(owner.current) + +///creates a new human body for the darkspawn player and transfers their mind to it +/datum/antagonist/darkspawn/proc/reform_body() + if(!owner.current) + return + + for(var/datum/action/cooldown/spell/spells in owner.current.actions) //remove the ability that triggers this + if(istype(spells, /datum/action/cooldown/spell/reform_body)) + spells.Remove(owner.current) + qdel(spells) + + if(owner.current && !(isbrain(owner.current) || issilicon(owner.current))) + return + + var/mob/living/old_body = owner.current + var/mob/living/carbon/human/returner = new(get_turf(old_body)) + + if(darkspawn_state >= DARKSPAWN_DIVULGED)//set them back to being a darkspawn + returner.set_species(/datum/species/shadow/darkspawn) + + var/new_name = darkspawn_name || darkspawn_name() + returner.fully_replace_character_name(null, new_name) + owner.transfer_to(returner) + returner.update_appearance(UPDATE_OVERLAYS) + + if(picked_class) //should bring back all powers, might be something i'm overlooking + picked_class.refresh_powers() + + playsound(returner, 'yogstation/sound/magic/divulge_end.ogg', 50, 0) + playsound(returner, 'yogstation/sound/creatures/darkspawn_death.ogg', 50, 0) + + var/processed_message = span_progenitor("\[Mindlink\] [returner] has reformed their body.") + for(var/T in GLOB.alive_mob_list) + var/mob/M = T + if(is_team_darkspawn(M)) + to_chat(M, processed_message) + deadchat_broadcast(processed_message, null, returner) + + if(isbrain(old_body)) + var/mob/living/brain/thinker = old_body + if(thinker.container) + qdel(thinker.container) + return + + for(var/thing in old_body) + qdel(thing) + old_body.gib(TRUE, TRUE, TRUE) + +//////////////////////////////////////////////////////////////////////////////////// +//-------------------------------Preview icon-------------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/get_preview_icon() + var/icon/darkspawn_icon = icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_head") + darkspawn_icon.Blend(icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_chest"), ICON_OVERLAY) + darkspawn_icon.Blend(icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_r_arm"), ICON_OVERLAY) + darkspawn_icon.Blend(icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_l_arm"), ICON_OVERLAY) + darkspawn_icon.Blend(icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_r_leg"), ICON_OVERLAY) + darkspawn_icon.Blend(icon('yogstation/icons/mob/human_parts.dmi', "darkspawn_l_leg"), ICON_OVERLAY) + + var/class_color + var/class_icon + switch(rand(1,3)) + if(1) + class_color = COLOR_YELLOW + class_icon = "scout_sigils" + if(2) + class_color = COLOR_RED + class_icon = "fighter_sigils" + if(3) + class_color = COLOR_STRONG_VIOLET + class_icon = "warlock_sigils" + + var/icon/eyes = icon('yogstation/icons/mob/darkspawn.dmi', "eyes") + eyes.Blend(class_color, ICON_MULTIPLY) + darkspawn_icon.Blend(eyes, ICON_OVERLAY) + + var/icon/sigil = icon('yogstation/icons/mob/darkspawn.dmi', class_icon) + sigil.Blend(class_color, ICON_MULTIPLY) + darkspawn_icon.Blend(sigil, ICON_OVERLAY) + + return finish_preview_icon(darkspawn_icon) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Admin panel stuff---------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/antagonist/darkspawn/get_admin_commands() + . = ..() + if(darkspawn_state == DARKSPAWN_MUNDANE) + .["Force Divulge"] = CALLBACK(src, PROC_REF(divulge), TRUE) + .["Set Lucidity"] = CALLBACK(src, PROC_REF(set_lucidity)) + .["Set Willpower"] = CALLBACK(src, PROC_REF(set_shop)) + .["Set Psi Values"] = CALLBACK(src, PROC_REF(set_psi)) + .["Set Max Thralls"] = CALLBACK(src, PROC_REF(set_max_thralls)) + if(picked_class) + .["Refund Ability"] = CALLBACK(src, PROC_REF(refund_ability)) + .["Set Class"] = CALLBACK(src, PROC_REF(set_class)) + +/datum/antagonist/darkspawn/proc/set_lucidity(mob/admin) + var/lucid = input(admin, "How much lucidity should all darkspawns have?") as null|num + if(lucid && team) + team.lucidity = lucid + +/datum/antagonist/darkspawn/proc/set_shop(mob/admin) + var/will = input(admin, "How much willpower should [owner] have?") as null|num + if(will) + willpower = will + +/datum/antagonist/darkspawn/proc/set_psi(mob/admin) + var/max = input(admin, "What should the psi cap be?") as null|num + if(max) + psi_cap = max + var/regen = input(admin, "How much psi should be regenerated per second?") as null|num + if(regen) + psi_per_second = regen + var/delay = input(admin, "What should the delay to psi regeneration be?") as null|num + if(delay) + psi_regen_delay = delay + +/datum/antagonist/darkspawn/proc/set_max_thralls(mob/admin) + var/thrall = input(admin, "How many thralls should the darkspawn team be able to get?") as null|num + if(thrall && team) + team.max_thralls = thrall + +/datum/antagonist/darkspawn/proc/refund_ability(mob/admin) + if(!picked_class) + return + + var/list/abilities = list() + for(var/datum/psi_web/ability as anything in picked_class.learned_abilities) + if(initial(ability.willpower_cost)) //if there's even a cost to refund + abilities |= ability + + if(!LAZYLEN(abilities)) + to_chat(admin, span_warning("There are no abilities to refund")) + return + + var/datum/psi_web/picked_ability = tgui_input_list(admin, "Select which ability to refund.", "Select Ability", abilities) + if(!picked_ability || !istype(picked_ability, /datum/psi_web)) + return + + if(QDELETED(src) || QDELETED(owner.current) || QDELETED(picked_class)) + return + + picked_class.lose_power(picked_ability, TRUE) + +/datum/antagonist/darkspawn/proc/set_class(mob/admin) + var/list/classes = list() + for(var/datum/component/darkspawn_class/class as anything in subtypesof(/datum/component/darkspawn_class)) + if(initial(class.choosable)) + classes |= class + classes |= "vvv Not regularly selectible vvv" + classes |= subtypesof(/datum/component/darkspawn_class) + + var/chosen = tgui_input_list(admin, "Select which class to force on the target.", "Select Class", classes) + if(!chosen || !ispath(chosen, /datum/component/darkspawn_class)) + return + + if(QDELETED(src) || QDELETED(owner.current)) + return + + picked_class = owner.AddComponent(chosen) + if(darkspawn_state >= DARKSPAWN_DIVULGED) + owner.assigned_role = picked_class.name //they stop being whatever job they were the moment they divulge + +/datum/antagonist/darkspawn/antag_panel_data() + if(team) + . += "Lucidity: [team.lucidity ? team.lucidity : "0"] / [team.required_succs ? team.required_succs : "0"]
" + . += "Willpower: [willpower ? willpower : "0"]
" + . += "Psi Cap: [psi_cap]. Psi per second: [psi_per_second]. Psi regen delay: [psi_regen_delay ? "[psi_regen_delay/10] seconds" : "no delay"]
" + if(team) + . += "Max Thralls: [team.max_thralls ? team.max_thralls : "0"]
" + + var/datum/component/darkspawn_class/class = owner.GetComponent(/datum/component/darkspawn_class) + if(class && istype(class) && class.learned_abilities) + . += "Upgrades:
" + for(var/datum/psi_web/ability as anything in class.learned_abilities) + . += "[ability.name]
" diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_classes/_class.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_classes/_class.dm new file mode 100644 index 000000000000..a6a23b04ae3e --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_classes/_class.dm @@ -0,0 +1,196 @@ +/datum/component/darkspawn_class + dupe_type = /datum/component/darkspawn_class //prevents multiclassing + ///Class name + var/name = "Debug class" + ///Class short description + var/description = "This is a debug class, you shouldn't see this short description." + ///Class long description. This will be shown in TGUI to explain to players with more depth of what to expect from the class + var/long_description = "This is a debug class, you shouldn't see this long and in-depth description that i'll probably write at some point." + ///The flag of the classtype. Used to determine which psi_web options are available to the class + var/specialization_flag = NONE + ///The darkspawn who this class belongs to + var/datum/mind/owner + var/choosable = TRUE + + var/datum/antagonist/darkspawn/d + ///Abilities our class will start with. Granted to the owning darkspawn on initialization + var/list/datum/psi_web/starting_abilities = list() + ///Abilities the darkspawn has learned from the psi_web + var/list/datum/psi_web/learned_abilities = list() + ///The color of their aura outline + var/class_color = COLOR_SILVER + + var/icon_file = 'yogstation/icons/mob/darkspawn.dmi' + var/eye_icon = "eyes" + var/class_icon = "classless" + +/datum/component/darkspawn_class/Initialize() + if(!istype(parent, /datum/mind)) + return COMPONENT_INCOMPATIBLE + owner = parent + if(!owner.has_antag_datum(/datum/antagonist/darkspawn)) + return COMPONENT_INCOMPATIBLE + +/datum/component/darkspawn_class/Destroy() + . = ..() + owner = null + +/datum/component/darkspawn_class/RegisterWithParent() + RegisterSignal(parent, COMSIG_DARKSPAWN_PURCHASE_POWER, PROC_REF(gain_power)) + RegisterSignal(parent, COMSIG_MIND_TRANSFERRED, PROC_REF(update_overlays_target)) + if(istype(parent, /datum/mind)) + var/datum/mind/thinker = parent + if(thinker.current && isliving(thinker.current)) + var/mob/living/thinkmob = thinker.current + RegisterSignal(thinkmob, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(update_owner_overlay)) + thinkmob.update_appearance(UPDATE_OVERLAYS) + + for(var/datum/psi_web/power as anything in starting_abilities) + gain_power(power_typepath = power) + +/datum/component/darkspawn_class/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_DARKSPAWN_PURCHASE_POWER) + UnregisterSignal(parent, COMSIG_MIND_TRANSFERRED) + if(istype(parent, /datum/mind)) + var/datum/mind/thinker = parent + if(thinker.current && isliving(thinker.current)) + var/mob/living/thinkmob = thinker.current + UnregisterSignal(thinkmob, COMSIG_ATOM_UPDATE_OVERLAYS) + thinkmob.update_appearance(UPDATE_OVERLAYS) + + for(var/datum/psi_web/power in learned_abilities) + lose_power(power, TRUE) //if the component is removed, refund them + +////////////////////////////////////////////////////////////////////////// +//---------------------------Overlay handlers---------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/component/darkspawn_class/proc/update_overlays_target(datum/mind/source, mob/old_current) + SIGNAL_HANDLER + + UnregisterSignal(old_current, COMSIG_ATOM_UPDATE_OVERLAYS) + old_current.update_appearance(UPDATE_OVERLAYS) + + if(source.current) + RegisterSignal(source.current, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(update_owner_overlay)) + source.current.update_appearance(UPDATE_OVERLAYS) + + +/datum/component/darkspawn_class/proc/update_owner_overlay(atom/source, list/overlays) + SIGNAL_HANDLER + + if(!isshadowperson(source)) + return //so they only get the overlay when divulged + + //draw both the overlay itself and the emissive overlay + var/mutable_appearance/eyes = mutable_appearance(icon_file, eye_icon, -HANDCUFF_LAYER) + eyes.color = class_color + overlays += eyes + + overlays += emissive_appearance(icon_file, eye_icon, source) //the emissive overlay for the eyes + + var/mutable_appearance/class_sigil = mutable_appearance(icon_file, class_icon, -HANDCUFF_LAYER) + class_sigil.color = class_color + overlays += class_sigil + + overlays += emissive_appearance(icon_file, class_icon, source) //the emissive overlay for the sigil + +////////////////////////////////////////////////////////////////////////// +//---------------------------Abilities procs----------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/component/darkspawn_class/proc/get_purchasable_abilities() //todo, add buying multiples in this thing + var/list/datum/psi_web/available_abilities = list() + for(var/datum/psi_web/ability as anything in subtypesof(/datum/psi_web)) + if(!(initial(ability.willpower_cost))) //if it's free for some reason, don't show it, it's probably a bug + continue + if(!(initial(ability.shadow_flags) & specialization_flag) || (!initial(ability.infinite) && locate(ability) in learned_abilities)) + continue + available_abilities += ability + return available_abilities + +/datum/component/darkspawn_class/proc/gain_power(atom/source, var/datum/psi_web/power_typepath, silent = FALSE) + if(!ispath(power_typepath)) + CRASH("[owner] tried to gain [power_typepath] which is not a valid darkspawn ability") + if(!(initial(power_typepath.shadow_flags) & specialization_flag)) + CRASH("[owner] tried to gain [power_typepath] which is not allowed by their specialization") + if(!initial(power_typepath.infinite) && (locate(power_typepath) in learned_abilities)) + return + + var/datum/psi_web/new_power = new power_typepath() + if(new_power.on_purchase(owner, silent)) + learned_abilities += new_power + else + qdel(new_power) + +/datum/component/darkspawn_class/proc/lose_power(datum/psi_web/power, refund = FALSE) + if(!locate(power) in learned_abilities) + CRASH("[owner] tried to lose [power] which they haven't learned") + + learned_abilities -= power + power.remove(refund) + +/datum/component/darkspawn_class/proc/refresh_powers() + for(var/datum/psi_web/power in learned_abilities) + var/power_type = power.type + lose_power(power, TRUE) //full refund + gain_power(power_typepath = power_type, silent = TRUE) //then just rebuy it + +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------The Classes in Question-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/component/darkspawn_class/classless + name = "Deprived" + description = "You've yet to peep the horror." + long_description = "You can probably do this with just a club and loincloth anyway." + specialization_flag = NONE + class_color = COLOR_SILVER + starting_abilities = list(/datum/psi_web/innate_darkspawn) + choosable = FALSE + +/datum/component/darkspawn_class/fighter + name = "Fighter" + description = "An unstoppable wall of darkness." + long_description = "Act as a physical threat to crewmembers. Simple and messy." + specialization_flag = DARKSPAWN_FIGHTER + class_color = COLOR_RED + starting_abilities = list(/datum/psi_web/innate_darkspawn, /datum/psi_web/fighter) + class_icon = "fighter_sigils" + +/datum/component/darkspawn_class/scout + name = "Scout" + description = "Stir the shadows in unnatural ways." + long_description = "Deter crewmembers from venturing into the darkness by being everywhere at once. Good at running away." + specialization_flag = DARKSPAWN_SCOUT + class_color = COLOR_YELLOW + starting_abilities = list(/datum/psi_web/innate_darkspawn, /datum/psi_web/scout) + class_icon = "scout_sigils" + +/datum/component/darkspawn_class/warlock + name = "Warlock" + description = "Psionic dominance." + long_description = "Cast spells, thrall minds, and support allies from across the station. Rather complex." + specialization_flag = DARKSPAWN_WARLOCK + class_color = COLOR_STRONG_VIOLET + starting_abilities = list(/datum/psi_web/innate_darkspawn, /datum/psi_web/warlock) + class_icon = "warlock_sigils" + +/datum/component/darkspawn_class/admin + name = "Admeme" + description = "Can do everything." + long_description = "Yeah, you're fucked buddy." + specialization_flag = ALL_DARKSPAWN_CLASSES + class_color = LIGHT_COLOR_ELECTRIC_GREEN + choosable = FALSE + starting_abilities = list(/datum/psi_web/innate_darkspawn, /datum/psi_web/fighter, /datum/psi_web/scout, /datum/psi_web/warlock) + eye_icon = "admeme_eyes" + class_icon = "admeme_sigils" + var/last_colour = 0 + var/list/hsv + +/datum/component/darkspawn_class/admin/update_owner_overlay(atom/source, list/overlays) + if(!hsv) + hsv = RGBtoHSV(rgb(255, 0, 0)) + hsv = RotateHue(hsv, (world.time - last_colour) * 15) + last_colour = world.time + class_color = HSVtoRGB(hsv) //rainbow + addtimer(CALLBACK(owner, TYPE_PROC_REF(/atom, update_appearance), UPDATE_OVERLAYS), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //regularly refresh the overlays + return ..() diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_illusions.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_illusions.dm new file mode 100644 index 000000000000..1af6f7790eb4 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_illusions.dm @@ -0,0 +1,96 @@ +//////////Darkspawn specific illusions////////////// +/mob/living/simple_animal/hostile/illusion/darkspawn //simulacrum version + desc = "They have a weird shimmering to them." + maxHealth = 100 + health = 100 + pressure_resistance = INFINITY + 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 + + speed = -1 + pass_flags = PASSTABLE | PASSMOB | PASSDOOR | PASSMACHINES | PASSMECH | PASSCOMPUTER | PASSGRILLE | PASSGLASS + ventcrawler = TRUE + + attack_sound = 'sound/magic/voidblink.ogg' + deathsound = 'yogstation/sound/magic/devour_will_victim.ogg' + attacktext = "gores" + bubble_icon = BUBBLE_DARKSPAWN + + lighting_cutoff_red = 12 + lighting_cutoff_green = 0 + lighting_cutoff_blue = 50 + lighting_cutoff = LIGHTING_CUTOFF_HIGH + faction = list(ROLE_DARKSPAWN) + +/mob/living/simple_animal/hostile/illusion/darkspawn/Initialize(mapload) + . = ..() + AddComponent(/datum/component/light_eater) + grant_language(/datum/language/darkspawn) + +/mob/living/simple_animal/hostile/illusion/darkspawn/Life(seconds_per_tick, times_fired) + . = ..() + var/turf/T = get_turf(src) + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) + adjustHealth(-2) + +/mob/living/simple_animal/hostile/illusion/darkspawn/psyche //sentient version + +/mob/living/simple_animal/hostile/illusion/darkspawn/psyche/Copy_Parent(mob/living/original, life, hp, damage, replicate) + . = ..() + life_span = INFINITY //doesn't actually despawn + +/mob/living/simple_animal/hostile/illusion/darkspawn/psyche/Login() + . = ..() + if(mind && !ispsyche(src)) + mind.add_antag_datum(/datum/antagonist/psyche) + +///special antagonist used to give an internal camera and antag hud to non-thrall darkspawn teammates +/datum/antagonist/psyche + name = "Darkspawn Psyche" + job_rank = ROLE_DARKSPAWN + antag_hud_name = "thrall" + roundend_category = "thralls" + antagpanel_category = "Darkspawn" + antag_moodlet = /datum/mood_event/thrall + +/datum/antagonist/psyche/apply_innate_effects(mob/living/mob_override) + var/mob/living/current_mob = mob_override || owner.current + if(!current_mob) + return //sanity check + + add_team_hud(current_mob, /datum/antagonist/darkspawn) + + current_mob.grant_language(/datum/language/darkspawn) + current_mob.faction |= ROLE_DARKSPAWN + + current_mob.AddComponent(/datum/component/internal_cam, list(ROLE_DARKSPAWN)) + var/datum/component/internal_cam/cam = current_mob.GetComponent(/datum/component/internal_cam) + if(cam) + cam.change_cameranet(GLOB.thrallnet) + +/datum/antagonist/psyche/remove_innate_effects(mob/living/mob_override) + var/mob/living/current_mob = mob_override || owner.current + if(!current_mob) + return //sanity check + + current_mob.remove_language(/datum/language/darkspawn) + current_mob.faction -= ROLE_DARKSPAWN + qdel(current_mob.GetComponent(/datum/component/internal_cam)) + +/datum/antagonist/psyche/add_team_hud(mob/target, antag_to_check) + QDEL_NULL(team_hud_ref) + + team_hud_ref = WEAKREF(target.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/has_antagonist, + "antag_team_hud_[REF(src)]", + hud_image_on(target), + antag_to_check || type, + )) + + // Add HUDs that they couldn't see before + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if (is_team_darkspawn(owner.current)) //needs to change this line so both the darkspawn and thrall sees it + antag_hud.show_to(owner.current) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/crawling_shadows.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/crawling_shadows.dm new file mode 100644 index 000000000000..27279f81eadc --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/crawling_shadows.dm @@ -0,0 +1,70 @@ +/mob/living/simple_animal/hostile/crawling_shadows + //appearance variables + name = "crawling shadows" + desc = "A formless mass of nothingness with piercing white eyes." + icon = 'yogstation/icons/mob/darkspawn.dmi' //Placeholder sprite + icon_state = "crawling_shadows" + icon_living = "crawling_shadows" + + //survival variables + maxHealth = 10 + health = 10 + pressure_resistance = INFINITY + 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 + + //movement variables + movement_type = FLYING + speed = 0 + ventcrawler = TRUE + pass_flags = PASSTABLE | PASSMOB | PASSDOOR | PASSMACHINES | PASSMECH | PASSCOMPUTER + + //combat variables + harm_intent_damage = 5 + melee_damage_lower = 5 + melee_damage_upper = 5 + + //sight variables + lighting_cutoff_red = 12 + lighting_cutoff_green = 0 + lighting_cutoff_blue = 50 + lighting_cutoff = LIGHTING_CUTOFF_HIGH + + //death variables + del_on_death = TRUE + deathmessage = "trembles, form rapidly dispersing." + deathsound = 'yogstation/sound/magic/devour_will_victim.ogg' + + //attack flavour + speak_emote = list("whispers") + attacktext = "assails" + attack_sound = 'sound/magic/voidblink.ogg' + response_help = "disturbs" + response_harm = "flails at" + + var/move_count = 0 //For spooky sound effects + var/knocking_out = FALSE + +/mob/living/simple_animal/hostile/crawling_shadows/Initialize(mapload) + . = ..() + AddComponent(/datum/component/light_eater) + +/mob/living/simple_animal/hostile/crawling_shadows/Move() + . = ..() + update_light_speed() + move_count++ + if(move_count >= 4) + playsound(get_turf(src), "crawling_shadows_walk", 25, 0) + move_count = 0 + +/mob/living/simple_animal/hostile/crawling_shadows/proc/update_light_speed() + var/turf/T = get_turf(src) + var/lums = T.get_lumcount() + if(lums < SHADOW_SPECIES_BRIGHT_LIGHT) + speed = -1 //Faster, too + alpha = max(alpha - ((SHADOW_SPECIES_BRIGHT_LIGHT - lums) * 60), 0) //Rapidly becomes more invisible in the dark + else + speed = 0 + alpha = min(alpha + (lums * 30), 255) //Slowly becomes more visible in brighter light + update_simplemob_varspeed() diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/dark_bead.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/dark_bead.dm deleted file mode 100644 index 48b4bc757cc9..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/dark_bead.dm +++ /dev/null @@ -1,114 +0,0 @@ -//Formed by the Devour Will ability. -/obj/item/dark_bead - name = "dark bead" - desc = "A glowing black orb. It's fading fast." - icon = 'yogstation/icons/obj/darkspawn_items.dmi' - icon_state = "dark_bead" - item_state = "disintegrate" - resistance_flags = FIRE_PROOF | LAVA_PROOF | UNACIDABLE | INDESTRUCTIBLE - item_flags = DROPDEL - w_class = 5 - light_color = "#21007F" - light_power = 0.3 - light_range = 2 - var/eating = FALSE //If we're devouring someone's will - var/datum/action/innate/darkspawn/devour_will/linked_ability //The ability that keeps data for us - var/full_restore = TRUE - -/obj/item/dark_bead/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - animate(src, alpha = 50, time = 5 SECONDS) - QDEL_IN(src, 5 SECONDS) - -/obj/item/dark_bead/Destroy(force) - if(isliving(loc) && !eating && !force) - to_chat(loc, span_warning("You were too slow! [src] faded away...")) - if(!eating || force) - . = ..() - else - return QDEL_HINT_LETMELIVE - -/obj/item/dark_bead/attack(mob/living/carbon/L, mob/living/user) - var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(user) - if(!darkspawn || eating || L == user) //no eating urself ;))))))) - return - if(!istype(L, /mob/living/carbon)) - to_chat(user, "[L]'s mind is not powerful enough to be of use.") - return - linked_ability = darkspawn.has_ability("devour_will") - if(!linked_ability) //how did you even get this? - qdel(src) - return - if(!L.mind || isdarkspawn(L)) - to_chat(user, span_warning("You cannot drain allies or the mindless.")) - return - if(!L.health || L.stat) - to_chat(user, span_warning("[L] is too weak to drain.")) - return - if(linked_ability.victims[L]) - to_chat(user, span_warning("[L] must be given time to recover from their last draining.")) - return - if(linked_ability.last_victim == L.ckey) - to_chat(user, span_warning("[L]'s mind is still too scrambled. Drain someone else first.")) - return - if(isveil(L)) - full_restore = FALSE - to_chat(user, span_warning("[L] has been veiled and will not produce as much psi as an unmodified victim.")) - eating = TRUE - L.Stun(5 SECONDS) - user.Immobilize(1 SECONDS) // So they don't accidentally move while beading - ADD_TRAIT(L, TRAIT_PARALYSIS, "bead-trait") - if(user.loc != L) - user.visible_message(span_warning("[user] grabs [L] and leans in close..."), "cera qo...
\ - [span_danger("You begin siphoning [L]'s mental energy...")]") - to_chat(L, span_userdanger("AAAAAAAAAAAAAA-")) - L.silent += 4 - playsound(L, 'yogstation/sound/magic/devour_will.ogg', 65, FALSE) //T A S T Y S O U L S - if(!do_after(user, 3 SECONDS, L)) - REMOVE_TRAIT(L, TRAIT_PARALYSIS, "bead-trait") - user.Knockdown(3 SECONDS) - to_chat(L, span_boldwarning("All right. You're all right.")) - L.Knockdown(3 SECONDS) - qdel(src, force = TRUE) - return - else - L.visible_message("[L] suddenly howls and clutches as their face as violet light screams from their eyes!", \ - "AAAAAAAAAAAAAAA-") - to_chat(user, span_velvet("cera qo...
You begin siphoning [L]'s will...")) - playsound(L, 'yogstation/sound/magic/devour_will_long.ogg', 65, FALSE) - if(!do_after(user, 5 SECONDS, L)) - REMOVE_TRAIT(L, TRAIT_PARALYSIS, "bead-trait") - user.Knockdown(5 SECONDS) - to_chat(L, span_boldwarning("All right. You're all right.")) - L.Knockdown(5 SECONDS) - qdel(src, force = TRUE) - return - REMOVE_TRAIT(L, TRAIT_PARALYSIS, "bead-trait") - user.visible_message(span_warning("[user] gently lowers [L] to the ground..."), "...aranupdejc
\ - You devour [L]'s will. Your Psi has been [!full_restore ? "partially restored." : "fully restored.\n\ - Additionally, you have gained one lucidity. Use it to purchase and upgrade abilities."]
\ - [span_warning("[L] is now severely weakened and will take some time to recover.")] \ - [span_warning("Additionally, you can not drain them again without first draining someone else.")]") - playsound(L, 'yogstation/sound/magic/devour_will_victim.ogg', 50, FALSE) - if(full_restore) - darkspawn.psi = darkspawn.psi_cap - else //no getting free lucidity from veils that wouldn't be fun. They'll still count towards winning though. - darkspawn.psi += 20 - if(linked_ability.victims[L] == FALSE) - to_chat(user, " You have already drained this individual previously, and their lucidity will not contribute any more to the sacrament!") - else - to_chat(user, " This individual's lucidity brings you one step closer to the sacrament...") - darkspawn.lucidity++ - darkspawn.lucidity_drained++ - darkspawn.update_psi_hud() - linked_ability.victims[L] = TRUE - linked_ability.last_victim = L.ckey - to_chat(L, span_userdanger("You suddenly feel... empty. Thoughts try to form, but flit away. You slip into a deep, deep slumber...")) - L.playsound_local(L, 'yogstation/sound/magic/devour_will_end.ogg', 75, FALSE) - L.Unconscious(15) - L.apply_effect(EFFECT_STUTTER, 20) - L.apply_status_effect(STATUS_EFFECT_BROKEN_WILL) - addtimer(CALLBACK(linked_ability, TYPE_PROC_REF(/datum/action/innate/darkspawn/devour_will, make_eligible), L), 600) - qdel(src, force = TRUE) - return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/darkspawn_chems.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/darkspawn_chems.dm new file mode 100644 index 000000000000..9e2bd2496b15 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/darkspawn_chems.dm @@ -0,0 +1,51 @@ +////////////////////////////////////////////////////////////////////////// +//--------------------------Used for icy veins--------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/reagent/shadowfrost + name = "Shadowfrost" + description = "A dark liquid that seems to slow down anything that comes into contact with it." + color = "#000000" //Complete black (RGB: 0, 0, 0) + +/datum/reagent/shadowfrost/on_mob_metabolize(mob/living/L) + . = ..() + L.add_movespeed_modifier(type, update=TRUE, priority=100, multiplicative_slowdown=2) + +/datum/reagent/shadowfrost/on_mob_end_metabolize(mob/living/L) + L.remove_movespeed_modifier(type) + return ..() + +////////////////////////////////////////////////////////////////////////// +//-----------------------Used for darkness smoke------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/reagent/darkspawn_darkness_smoke + name = "odd black liquid" + description = "<::ERROR::> CANNOT ANALYZE REAGENT <::ERROR::>" + color = "#000000" //Complete black (RGB: 0, 0, 0) + +/datum/reagent/darkspawn_darkness_smoke/on_mob_add(mob/living/L) + . = ..() + var/datum/antagonist/darkspawn/dude = isdarkspawn(L) + if(dude) + ADD_TRAIT(dude, TRAIT_DARKSPAWN_CREEP, type) + +/datum/reagent/darkspawn_darkness_smoke/on_mob_delete(mob/living/L) + var/datum/antagonist/darkspawn/dude = isdarkspawn(L) + if(dude) + REMOVE_TRAIT(dude, TRAIT_DARKSPAWN_CREEP, type) + return ..() + +/datum/reagent/darkspawn_darkness_smoke/reaction_mob(mob/living/M, methods, reac_volume, show_message, permeability) + . = ..() + if(is_team_darkspawn(M) && M.reagents) //since darkspawns don't breathe, let's do this + M.reagents.add_reagent(type, 5) + +/datum/reagent/darkspawn_darkness_smoke/on_mob_life(mob/living/M) + if(!is_team_darkspawn(M)) + to_chat(M, span_warning("The pitch black smoke irritates your eyes horribly!")) + M.blind_eyes(2 SECONDS) + if(prob(25)) + M.visible_message("[M] claws at their eyes!") + M.Stun(3) + + holder.remove_reagent(type, 1)//tick down at 1u at a time + volume = clamp(volume, 0, 2)//have at most 2u at any time diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/goliath_tendril.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/goliath_tendril.dm new file mode 100644 index 000000000000..23203f8d7d4d --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/goliath_tendril.dm @@ -0,0 +1,38 @@ +/obj/effect/temp_visual/goliath_tentacle/darkspawn + name = "darkspawn tendril" + desc = "OOOOOOOOOOOOOOOO spooky" + light_power = -1 + light_range = 1 + light_color = COLOR_VELVET + light_system = MOVABLE_LIGHT //it's not movable, but the new system looks nicer for this purpose + +/obj/effect/temp_visual/goliath_tentacle/darkspawn/Initialize(mapload, mob/living/new_spawner) + . = ..() + add_atom_colour(COLOR_VELVET, FIXED_COLOUR_PRIORITY) + +/obj/effect/temp_visual/goliath_tentacle/darkspawn/original/Initialize(mapload, mob/living/new_spawner) + . = ..() + var/list/turf/turfs = circle_range_turfs(get_turf(src), 2) + for(var/i in 1 to 9) + if(!LAZYLEN(turfs)) //sanity check + break + var/turf/T = pick_n_take(turfs) + new /obj/effect/temp_visual/goliath_tentacle/darkspawn(T, spawner) + +/obj/effect/temp_visual/goliath_tentacle/darkspawn/trip() + var/latched = 0 + for(var/mob/living/L in loc) + if(is_darkspawn_or_thrall(L) || L.stat == DEAD) + continue + visible_message(span_danger("[src] grabs hold of [L]!")) + if(!L.IsStun()) + L.Stun(8 SECONDS) + else + L.AdjustStun(2 SECONDS) + latched = L.AmountStun() //hold on until the stun applied by the tentacle ends + L.adjustBruteLoss(rand(15,25)) + if(!latched) + retract() + else + deltimer(timerid) + timerid = addtimer(CALLBACK(src, PROC_REF(retract)), latched, TIMER_STOPPABLE) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/psionic_barrier.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/psionic_barrier.dm index da12b75b8c62..3b118c5b4e2e 100644 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/psionic_barrier.dm +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/psionic_barrier.dm @@ -10,14 +10,12 @@ opacity = FALSE density = TRUE mouse_opacity = MOUSE_OPACITY_OPAQUE - light_color = "#21007F" - light_power = 0.3 - light_range = 2 /obj/structure/psionic_barrier/Initialize(mapload, time = 500) . = ..() START_PROCESSING(SSprocessing, src) QDEL_IN(src, time) + update_appearance(UPDATE_OVERLAYS) /obj/structure/psionic_barrier/Destroy() if(!atom_integrity) @@ -29,3 +27,7 @@ /obj/structure/psionic_barrier/process() update_integrity(max(0, min(max_integrity, atom_integrity + 1))) + +/obj/structure/psionic_barrier/update_overlays() + . = ..() + . += emissive_appearance(icon, "shieldsparkles", src) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/scout_traps.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/scout_traps.dm new file mode 100644 index 000000000000..db54b7e9dc49 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/scout_traps.dm @@ -0,0 +1,114 @@ +////////////////////////////////////////////////////////////////////////// +/obj/item/restraints/legcuffs/beartrap/dark + name = "dark snare" + armed = 1 + icon_state = "e_snare" + trap_damage = 0 + breakouttime = 30 + item_flags = DROPDEL + flags_1 = NONE + break_strength = 2 + slowdown = 4 + gender = FEMALE //lol examine text? (this actually matters, don't change it) + trap_damage = 5 + +/obj/item/restraints/legcuffs/beartrap/dark/Initialize(mapload) + . = ..() + add_atom_colour(COLOR_VELVET, FIXED_COLOUR_PRIORITY) + +/obj/item/restraints/legcuffs/beartrap/dark/attack_hand(mob/user) + spring_trap(user) //no picking it up + +/obj/item/restraints/legcuffs/beartrap/dark/spring_trap(AM as mob|obj) + if(isliving(AM)) + var/mob/living/target = AM + if(is_darkspawn_or_thrall(target)) + return + return ..() + +////////////////////////////////////////////////////////////////////////// +//---------------------------Recharging trap----------------------------// +////////////////////////////////////////////////////////////////////////// +/obj/structure/trap/darkspawn + name = "void rune" + desc = "strange ephemeral shadows, knit together in the form of usual symbols." + antimagic_flags = MAGIC_RESISTANCE_MIND + max_integrity = 75 + time_between_triggers = 1 MINUTES + sparks = FALSE + can_reveal = FALSE + mouse_opacity = MOUSE_OPACITY_OPAQUE //nothing draws under them and they really SHOULD be easier to click + var/examine_text + +/obj/structure/trap/darkspawn/examine(mob/user) + . = ..() + if(examine_text && is_darkspawn_or_thrall(user)) + . += span_velvet("The runes denote [examine_text].") + +/obj/structure/trap/darkspawn/Initialize(mapload) + . = ..() + add_atom_colour(COLOR_VELVET, FIXED_COLOUR_PRIORITY) + +/obj/structure/trap/darkspawn/on_trap_entered(datum/source, atom/movable/AM, ...) + if(isliving(AM)) + var/mob/living/target = AM + if(is_darkspawn_or_thrall(target)) + return + if(isprojectile(AM)) //if it's flying above the trap, don't trigger it + return + return ..() + +/////////////////////////////Makes people sick//////////////////////////// +/obj/structure/trap/darkspawn/nausea + time_between_triggers = 15 SECONDS + examine_text = "sickness" + +/obj/structure/trap/darkspawn/nausea/flare() + . = ..() + playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1) + playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 50, TRUE) + +/obj/structure/trap/darkspawn/nausea/trap_effect(mob/living/L) + L.adjust_disgust(30) + L.adjust_confusion(6 SECONDS) + +///////////////////teleport somewhere random on the station//////////////// +/obj/structure/trap/darkspawn/teleport + charges = 1 + examine_text = "transposition" + +/obj/structure/trap/darkspawn/teleport/flare() + . = ..() + playsound(get_turf(src), 'sound/effects/phasein.ogg', 60, 1) + +/obj/structure/trap/darkspawn/teleport/trap_effect(mob/living/L) + L.forceMove(get_safe_random_station_turf()) + playsound(get_turf(L), 'sound/effects/phasein.ogg', 60, 1) + +///////////////////////basic damage trap/////////////////////////////////// +/obj/structure/trap/darkspawn/damage + max_integrity = 15 + time_between_triggers = 15 SECONDS + examine_text = "injury" + +/obj/structure/trap/darkspawn/damage/flare() + . = ..() + playsound(get_turf(src), 'sound/effects/snap.ogg', 30, TRUE, -1) + playsound(get_turf(src), 'sound/weapons/bladeslice.ogg', 60, TRUE, -1) + +/obj/structure/trap/darkspawn/damage/trap_effect(mob/living/L) + L.apply_damage(30, BRUTE) + L.Knockdown(2 SECONDS) + +///////////////////////bear traps target/////////////////////////////////// +/obj/structure/trap/darkspawn/legcuff + charges = 1 + examine_text = "restrain" + +/obj/structure/trap/darkspawn/legcuff/flare() + . = ..() + playsound(get_turf(src), 'sound/effects/snap.ogg', 50, TRUE) + +/obj/structure/trap/darkspawn/legcuff/trap_effect(mob/living/L) + var/obj/item/restraints/legcuffs/beartrap/dark/trap = new(get_turf(src)) + trap.spring_trap(L) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm new file mode 100644 index 000000000000..8f75a666973f --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm @@ -0,0 +1,67 @@ +/obj/item/gun/ballistic/bow/energy/shadow_caster + name = "shadow caster" + desc = "A bow made of solid darkness. The arrows it shoots seem to suck light out of the surroundings." + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "shadow_caster" + item_state = "shadow_caster" + lefthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi' + righthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi' + mag_type = /obj/item/ammo_box/magazine/internal/bow/shadow + no_pin_required = TRUE + recharge_time = 2 SECONDS + trigger_guard = TRIGGER_GUARD_ALLOW_ALL + +/obj/item/gun/ballistic/bow/energy/shadow_caster/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + +// the thing that holds the ammo inside the bow +/obj/item/ammo_box/magazine/internal/bow/shadow + ammo_type = /obj/item/ammo_casing/reusable/arrow/shadow + +//the object that appears when the arrow finishes flying +/obj/item/ammo_casing/reusable/arrow/shadow + name = "shadow arrow" + desc = "it seem to suck light out of the surroundings." + icon = 'yogstation/icons/obj/darkspawn_projectiles.dmi' + icon_state = "caster_arrow" + item_state = "caster_arrow" + light_system = MOVABLE_LIGHT + light_power = -1 + light_color = COLOR_VELVET + light_range = 2 + embedding = list("embed_chance" = 100, "embedded_fall_chance" = 0) //always embeds if it hits someone + projectile_type = /obj/projectile/bullet/reusable/arrow/shadow + +/obj/item/ammo_casing/reusable/arrow/shadow/on_land(obj/projectile/old_projectile) + . = ..() + addtimer(CALLBACK(src, PROC_REF(dissipate)), 10 SECONDS, TIMER_UNIQUE) + +/obj/item/ammo_casing/reusable/arrow/shadow/proc/dissipate() + if(QDELETED(src)) + return + if(iscarbon(loc)) //if it's embedded, remove the embedding properly or it'll cause funkiness + var/mob/living/carbon/holder = loc + if(holder.get_embedded_part(src)) + holder.remove_embedded_object(src, get_turf(holder), TRUE, TRUE, FALSE) + qdel(src) + +//the projectile being shot from the bow +/obj/projectile/bullet/reusable/arrow/shadow + name = "shadow arrow" + icon = 'yogstation/icons/obj/darkspawn_projectiles.dmi' + icon_state = "caster_arrow" + damage = 25 //reduced damage per arrow compared to regular ones + light_system = MOVABLE_LIGHT + light_power = -1 + light_color = COLOR_VELVET + light_range = 2 + embed_chance = 1 //always embeds if it hits someone + +/obj/projectile/bullet/reusable/arrow/shadow/Initialize(mapload) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/projectile/bullet/reusable/arrow/shadow/update_overlays() + . = ..() + . += emissive_appearance(icon, "[icon_state]_emissive", src) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_step.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_step.dm new file mode 100644 index 000000000000..e09d10d7562b --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_step.dm @@ -0,0 +1,27 @@ +/datum/component/shadow_step + var/speedboost = -0.7 + var/mob/living/carbon/human/owner + +/datum/component/shadow_step/Initialize() + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + owner = parent + +/datum/component/shadow_step/Destroy(force, silent) + . = ..() + owner = null + +/datum/component/shadow_step/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(apply_darkness_speed)) + +/datum/component/shadow_step/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_MOB_CLIENT_PRE_MOVE) + owner.remove_movespeed_modifier(type) + +/datum/component/shadow_step/proc/apply_darkness_speed() + var/turf/T = get_turf(owner) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_BRIGHT_LIGHT) + owner.add_movespeed_modifier(type, update=TRUE, priority=100, override = TRUE, multiplicative_slowdown=speedboost) + else + owner.remove_movespeed_modifier(type) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/simulacrum.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/simulacrum.dm deleted file mode 100644 index 1334b50eb7f4..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/simulacrum.dm +++ /dev/null @@ -1,37 +0,0 @@ -//Created from the Simulacrum ability. Runs in a straight line until destroyed. -/obj/effect/simulacrum - name = "an illusion!" - desc = "What are you hiding?!" - icon_state = "static" - density = TRUE - uses_integrity = TRUE - max_integrity = 25 - var/mob/living/mimicking - -/obj/effect/simulacrum/Initialize(mapload) - . = ..() - START_PROCESSING(SSfastprocess, src) - QDEL_IN(src, 100) - -/obj/effect/simulacrum/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/*/obj/effect/simulacrum/examine(mob/user) //I can't currently get this to work properly - if(mimicking) - mimicking.examine(user) - return - . = ..() */ - -/obj/effect/simulacrum/process() - var/turf/T = get_step(src, dir) - Move(T) - -/obj/effect/simulacrum/proc/mimic(mob/living/L) - mimicking = L - name = L.name - desc = "A lifelike illusion of [L]." - icon = L.icon - icon_state = L.icon_state - overlays = L.overlays - setDir(L.dir) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/thrall_tumor.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/thrall_tumor.dm new file mode 100644 index 000000000000..ac5d460eebf5 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/thrall_tumor.dm @@ -0,0 +1,77 @@ +//Snowflake spot for putting sling organ related stuff +/obj/item/organ/shadowtumor + name = "black tumor" + desc = "A tiny black mass with red tendrils trailing from it. It seems to shrivel in the light." + icon_state = "blacktumor" + w_class = 1 + zone = BODY_ZONE_HEAD + slot = ORGAN_SLOT_BRAIN_TUMOR + ///How many process ticks the organ can be in light before it evaporates + var/organ_health = 3 + ///adds a cooldown to the resist so a thrall ipc or preternis can't weaponize it + COOLDOWN_DECLARE(resist_cooldown) + ///How long the resist cooldown is + var/cooldown_length = 15 SECONDS + +/obj/item/organ/shadowtumor/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/organ/shadowtumor/Destroy() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/item/organ/shadowtumor/process() + if(!isthrall(owner)) + qdel(src) + return + if(isturf(loc)) + var/turf/T = loc + var/light_count = T.get_lumcount() + if(light_count > SHADOW_SPECIES_DIM_LIGHT && organ_health > 0) //Die in the light + organ_health-- + else if(light_count < SHADOW_SPECIES_DIM_LIGHT && organ_health < 3) //Heal in the dark + organ_health = min(organ_health + 1, 3) + if(organ_health <= 0) + visible_message(span_warning("[src] collapses in on itself!")) + qdel(src) + else + organ_health = min(organ_health+0.5, 3) + +/obj/item/organ/shadowtumor/on_find(mob/living/finder) + . = ..() + finder.visible_message(span_danger("[finder] opens up [owner]'s skull, revealing a pulsating black mass, with red tendrils attaching it to [owner.p_their()] brain.")) + +/obj/item/organ/shadowtumor/proc/resist(mob/living/carbon/M) + if(QDELETED(src)) + return FALSE + if(!(M.stat == CONSCIOUS))//Thralls cannot be deconverted while awake + return FALSE + if(!isthrall(M))//non thralls don't resist + return FALSE + if(!COOLDOWN_FINISHED(src, resist_cooldown))//adds a cooldown to the resist so a thrall ipc or preternis can't weaponize it + return FALSE + COOLDOWN_START(src, resist_cooldown, cooldown_length) + + playsound(M,'sound/effects/tendril_destroyed.ogg', 80, 1) + to_chat(M, span_progenitor("NOT LIKE THIS!")) + M.visible_message(span_danger("[M] suddenly slams upward and knocks everyone back!")) + M.resting = FALSE //Remove all stuns + M.SetAllImmobility(0, TRUE) + for(var/mob/living/user as anything in range(2, get_turf(src))) + if(!istype(user)) + continue + if(is_darkspawn_or_thrall(user)) + continue + var/turf/target = get_ranged_target_turf(user, get_dir(M, user)) + user.throw_at(target, 2, 2, M) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.Knockdown(4 SECONDS) + C.adjustBruteLoss(20) + if(issilicon(user)) + var/mob/living/silicon/S = user + S.Knockdown(8 SECONDS) + S.adjustBruteLoss(20) + playsound(S, 'sound/effects/bang.ogg', 50, 1) + return TRUE diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm index e9eee70f82dd..7656e1995416 100644 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm @@ -2,7 +2,6 @@ /obj/item/umbral_tendrils name = "umbral tendrils" desc = "A mass of pulsing, chitonous tendrils with exposed violet flesh." - force = 15 icon = 'yogstation/icons/obj/darkspawn_items.dmi' icon_state = "umbral_tendrils" item_state = "umbral_tendrils" @@ -11,100 +10,81 @@ hitsound = 'yogstation/sound/magic/pass_attack.ogg' attack_verb = list("impaled", "tentacled", "torn") item_flags = ABSTRACT | DROPDEL + sharpness = SHARP_EDGED + force = 25 + block_chance = 25 + wound_bonus = -60 //no wounding + bare_wound_bonus = 20 var/datum/antagonist/darkspawn/darkspawn var/obj/item/umbral_tendrils/twin + COOLDOWN_DECLARE(grab_cooldown) + var/cooldown_length = 1 SECONDS //just to prevent accidentally wasting all your psi /obj/item/umbral_tendrils/Initialize(mapload, new_darkspawn) . = ..() ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + AddComponent(/datum/component/light_eater) darkspawn = new_darkspawn for(var/obj/item/umbral_tendrils/U in loc) if(U != src) twin = U U.twin = src - force = 12 - U.force = 12 + force *= 0.75 + U.force *= 0.75 + block_chance *= 0.75 + U.block_chance *= 0.75 /obj/item/umbral_tendrils/Destroy() if(!QDELETED(twin)) qdel(twin) + return ..() + +/obj/item/umbral_tendrils/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type != PROJECTILE_ATTACK) + final_block_chance = 0 //only give defense against shooting + return ..() + +/obj/item/umbral_tendrils/worn_overlays(mutable_appearance/standing, isinhands, icon_file) //this doesn't work and i have no clue why . = ..() + if(isinhands) + . += emissive_appearance(icon_file, "[item_state]_emissive", src) /obj/item/umbral_tendrils/examine(mob/user) . = ..() if(isobserver(user) || isdarkspawn(user)) - to_chat(user, "Functions:") - to_chat(user, span_velvet("Help intent: Click on an open tile within seven tiles to jump to it for 10 Psi.")) - to_chat(user, span_velvet("Disarm intent: Click on an airlock to force it open for 15 Psi (or 30 if it's bolted.)")) - to_chat(user, span_velvet("Harm intent: Fire a projectile that travels up to five tiles, knocking down[twin ? " and pulling forwards" : ""] the first creature struck.")) - to_chat(user, span_velvet("The tendrils will break any lights hit in melee,")) - to_chat(user, span_velvet("The tendrils will shatter light fixtures instantly, as opposed to in several attacks.")) - to_chat(user, span_velvet("Also functions to pry open depowered airlocks on any intent other than harm.")) + . += span_velvet("Functions:") + . += span_velvet("Disarm intent: Click on an airlock to force it open for 15 Psi (or 30 if it's bolted.)") + . += span_velvet("Grab intent: Consume 30 psi to a projectile that travels up to five tiles, knocking down[twin ? " and pulling forwards" : ""] the first creature struck.") + . += span_velvet("The tendrils will devour any lights hit.") + . += span_velvet("Also functions to pry open depowered airlocks on any intent other than harm.") /obj/item/umbral_tendrils/attack(mob/living/target, mob/living/user, twinned_attack = TRUE) set waitfor = FALSE ..() - sleep(0.1 SECONDS) + sleep(0.2 SECONDS) if(twin && twinned_attack && user.Adjacent(target)) twin.attack(target, user, FALSE) /obj/item/umbral_tendrils/afterattack(atom/target, mob/living/user, proximity) + . = ..() if(!darkspawn) return - if(proximity) - if(istype(target, /obj/structure/glowshroom)) - visible_message(span_warning("[src] tears [target] to shreds!")) - qdel(target) - if(isliving(target)) - var/mob/living/L = target - if(isethereal(target)) - target.emp_act(EMP_LIGHT) - for(var/obj/item/O in target.get_all_contents()) - if(O.light_range && O.light_power) - disintegrate(O) - if(L.pulling && L.pulling.light_range && isitem(L.pulling)) - disintegrate(L.pulling) - else if(isitem(target)) - var/obj/item/I = target - if(I.light_range && I.light_power) - disintegrate(I) - // Double hit structures if duality - else if(!QDELETED(target) && (isstructure(target) || ismachinery(target)) && twin && user.get_active_held_item() == src) - target.attackby(twin, user) + if(twin && proximity && !QDELETED(target) && (isstructure(target) || ismachinery(target)) && user.get_active_held_item() == src) + target.attackby(twin, user) switch(user.a_intent) //Note that airlock interactions can be found in airlock.dm. - if(INTENT_HELP) - if(isopenturf(target)) - tendril_jump(user, target) - if(INTENT_HARM) + if(INTENT_GRAB) tendril_swing(user, target) -/obj/item/umbral_tendrils/proc/disintegrate(obj/item/O) - if(istype(O, /obj/item/pda)) - var/obj/item/pda/PDA = O - PDA.set_light_on(FALSE) - PDA.update_appearance(UPDATE_ICON) - visible_message(span_danger("The light in [PDA] shorts out!")) - else - visible_message(span_danger("[O] is disintegrated by [src]!")) - O.burn() - playsound(src, 'sound/items/welder.ogg', 50, 1) - -/obj/item/umbral_tendrils/proc/tendril_jump(mob/living/user, turf/open/target) //throws the user towards the target turf - if(!darkspawn.has_psi(10)) - to_chat(user, span_warning("You need at least 10 Psi to jump!")) +/obj/item/umbral_tendrils/proc/tendril_swing(mob/living/user, mob/living/target) //swing the tendrils to knock someone down + if(!COOLDOWN_FINISHED(src, grab_cooldown)) return - if(!(target in view(7, user))) - to_chat(user, span_warning("You can't access that area, or it's too far away!")) + if(darkspawn && !darkspawn.has_psi(30)) return - to_chat(user, span_velvet("You pull yourself towards [target].")) - playsound(user, 'sound/magic/tail_swing.ogg', 10, TRUE) - user.throw_at(target, 5, 3) - darkspawn.use_psi(10) - -/obj/item/umbral_tendrils/proc/tendril_swing(mob/living/user, mob/living/target) //swing the tendrils to knock someone down if(isliving(target) && target.lying) to_chat(user, span_warning("[target] is already knocked down!")) return + darkspawn.use_psi(30) + COOLDOWN_START(src, grab_cooldown, cooldown_length) user.visible_message(span_warning("[user] draws back [src] and swings them towards [target]!"), \ span_velvet("opehhjaoo
You swing your tendrils towards [target]!")) playsound(user, 'sound/magic/tail_swing.ogg', 50, TRUE) @@ -113,7 +93,6 @@ T.twinned = twin T.firer = user T.fire() - qdel(src) /obj/projectile/umbral_tendrils name = "umbral tendrils" @@ -122,7 +101,6 @@ layer = LARGE_MOB_LAYER damage = 0 nodamage = TRUE - knockdown = 40 speed = 1 range = 5 var/twinned = FALSE @@ -142,26 +120,28 @@ . = TRUE if(isliving(target)) var/mob/living/L = target - if(!iscyborg(target)) + if(is_team_darkspawn(L)) + return BULLET_ACT_FORCE_PIERCE //ignore allies + if(iscarbon(target)) playsound(target, 'yogstation/sound/magic/pass_attack.ogg', 50, TRUE) + L.Knockdown(6 SECONDS) if(!twinned) target.visible_message(span_warning("[firer]'s [name] slam into [target], knocking them off their feet!"), \ span_userdanger("You're knocked off your feet!")) - L.Knockdown(6 SECONDS) else L.Immobilize(0.15 SECONDS) // so they cant cancel the throw by moving target.throw_at(get_step_towards(firer, target), 7, 2) //pull them towards us! target.visible_message(span_warning("[firer]'s [name] slam into [target] and drag them across the ground!"), \ span_userdanger("You're suddenly dragged across the floor!")) - L.Knockdown(8 SECONDS) //these can't hit people who are already on the ground but they can be spammed to all shit addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), target, 'yogstation/sound/magic/pass_attack.ogg', 50, TRUE), 1) - else + else if(issilicon(target)) var/mob/living/silicon/robot/R = target - R.toggle_headlamp(TRUE) //disable headlamps target.visible_message(span_warning("[firer]'s [name] smashes into [target]'s chassis!"), \ span_userdanger("Heavy percussive impact detected. Recalibrating motor input.")) R.playsound_local(target, 'sound/misc/interference.ogg', 25, FALSE) playsound(R, 'sound/effects/bang.ogg', 50, TRUE) - R.Paralyze(40) //this is the only real anti-borg spell get + R.Paralyze(40) R.adjustBruteLoss(10) + else + return BULLET_ACT_FORCE_PIERCE //ignore things that don't matter diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/veil_camera.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/veil_camera.dm new file mode 100644 index 000000000000..0abb2a10fa8a --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/veil_camera.dm @@ -0,0 +1,116 @@ +GLOBAL_DATUM_INIT(thrallnet, /datum/cameranet/darkspawn, new) +////////////////////////////////////////////////////////////////////////// +//-------------------------Access the veilnet---------------------------// +////////////////////////////////////////////////////////////////////////// +/obj/machinery/computer/camera_advanced/darkspawn + name = "dark orb" + desc = "An unsettling swirling mass of darkness. Gazing into it seems to reveal forbidden knowledge." + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "panopticon" + special_appearance = TRUE + use_power = NO_POWER_USE + flags_1 = NODECONSTRUCT_1 + max_integrity = 200 + integrity_failure = 0 + light_power = -1 + light_color = COLOR_VELVET + light_system = MOVABLE_LIGHT //it's not movable, but the new system looks nicer for this purpose + networks = list(ROLE_DARKSPAWN) + clicksound = "crawling_shadows_walk" + jump_action = /datum/action/innate/camera_jump/darkspawn + +/obj/machinery/computer/camera_advanced/darkspawn/Initialize(mapload) + . = ..() + camnet = GLOB.thrallnet + +/obj/machinery/computer/camera_advanced/darkspawn/update_overlays() + . = ..() + . += emissive_appearance(icon, "[icon_state]_emissive", src) + +/obj/machinery/computer/camera_advanced/darkspawn/emp_act(severity) + return + +/obj/machinery/computer/camera_advanced/darkspawn/remove_eye_control(mob/living/user) + . = ..() + playsound(src, "crawling_shadows_walk", 35, FALSE) + +//special ability to replace sound effects +/datum/action/innate/camera_jump/darkspawn + name = "Jump To Ally" + +/datum/action/innate/camera_jump/darkspawn/Activate() + if(!owner || !isliving(owner)) + return + var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control + var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin + + var/list/L = list() + + for (var/obj/machinery/camera/cam as anything in origin.camnet.cameras) + if(length(origin.z_lock) && !(cam.z in origin.z_lock)) + continue + L.Add(cam) + + camera_sort(L) + + var/list/T = list() + + for (var/obj/machinery/camera/netcam in L) + var/list/tempnetwork = netcam.network & origin.networks + if (length(tempnetwork)) + if(!netcam.c_tag) + continue + T["[netcam.c_tag][netcam.can_use() ? null : " (Deactivated)"]"] = netcam + + playsound(origin, "crawling_shadows_walk", 25, FALSE) + var/camera = tgui_input_list(usr, "Ally to view", "Allies", T) + if(isnull(camera)) + return + if(isnull(T[camera])) + return + var/obj/machinery/camera/final = T[camera] + playsound(origin, "crawling_shadows_walk", 25, FALSE) + if(final) + remote_eye.setLoc(get_turf(final)) + owner.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static) + owner.clear_fullscreen("flash", 1) //Shorter flash than normal since it's an ~~advanced~~ console! + +////////////////////////////////////////////////////////////////////////// +//-------------------------Expand the veilnet---------------------------// +////////////////////////////////////////////////////////////////////////// +/obj/machinery/camera/darkspawn + name = "void eye" + use_power = NO_POWER_USE + max_integrity = 20 + integrity_failure = 20 + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "camera" + special_camera = TRUE + internal_light = FALSE + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0) + flags_1 = NODECONSTRUCT_1 + +/obj/machinery/camera/darkspawn/Initialize(mapload, obj/structure/camera_assembly/CA) + . = ..() + network = list(ROLE_DARKSPAWN) + change_camnet(GLOB.thrallnet) + setViewRange(10) + +/obj/machinery/camera/darkspawn/emp_act(severity) + return + +/obj/machinery/camera/darkspawn/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/machinery/camera/darkspawn/wirecutter_act(mob/living/user, obj/item/I) + return + +/obj/machinery/camera/darkspawn/multitool_act(mob/living/user, obj/item/I) + return + +/obj/machinery/camera/darkspawn/welder_act(mob/living/user, obj/item/I) + return + +/obj/machinery/camera/darkspawn/update_overlays() + . = ..() + . += emissive_appearance(icon, "[icon_state]_emissive", src) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/warlock_staff.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/warlock_staff.dm new file mode 100644 index 000000000000..844f6b87bb81 --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_objects/warlock_staff.dm @@ -0,0 +1,120 @@ +////////////////////////////////////////////////////////////////////////// +//---------------------Upgradeable warlock staff------------------------// +////////////////////////////////////////////////////////////////////////// +/obj/item/gun/magic/darkspawn + name = "channeling staff" + desc = "This staff is boring to watch because even though it came first you've seen everything it can do in other staves for years." + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "shadow_staff" + item_state = "shadow_staff0" + base_icon_state = "shadow_staff" + lefthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi' + righthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi' + + fire_sound = 'sound/weapons/emitter2.ogg' + trigger_guard = TRIGGER_GUARD_ALLOW_ALL + slot_flags = NONE + + antimagic_flags = MAGIC_RESISTANCE_MIND + ammo_type = /obj/item/ammo_casing/magic/darkspawn + ///the psi cost to shoot the staff + var/psi_cost = 40 + /// Flags used for different effects that apply when a projectile hits something + var/effect_flags + +/obj/item/gun/magic/darkspawn/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + RegisterSignal(src, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit)) + AddComponent(/datum/component/two_handed, \ + wield_callback = CALLBACK(src, PROC_REF(on_wield)), \ + unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \ + ) + +/obj/item/gun/magic/darkspawn/worn_overlays(mutable_appearance/standing, isinhands, icon_file) //this doesn't work and i have no clue why + . = ..() + if(isinhands) + . += emissive_appearance(icon_file, "[item_state]_emissive", src) + +///////////////////FANCY PROJECTILE EFFECTS////////////////////////// +/obj/item/gun/magic/darkspawn/proc/on_projectile_hit(datum/source, atom/movable/firer, atom/target, angle) + if(isliving(target)) + var/mob/living/M = target + if(is_darkspawn_or_thrall(M)) + if(effect_flags & STAFF_UPGRADE_HEAL) + M.heal_ordered_damage(30, list(STAMINA, BURN, BRUTE, TOX, OXY, CLONE)) + if(effect_flags & STAFF_UPGRADE_EXTINGUISH) + M.extinguish_mob() + else + M.apply_damage(35, STAMINA) + if(effect_flags & STAFF_UPGRADE_CONFUSION) + M.adjust_confusion(4 SECONDS) + +////////////////////////TWO-HANDED BLOCKING////////////////////////// +/obj/item/gun/magic/darkspawn/proc/on_wield() //guns do weird things to some of the icon procs probably, and i can't find which ones, so i need to do this all again + block_chance = 50 + item_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" + if(ishuman(loc)) + var/mob/living/carbon/human/C = loc + C.update_inv_hands() + +/obj/item/gun/magic/darkspawn/proc/on_unwield() + block_chance = 0 + item_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" + if(ishuman(loc)) + var/mob/living/carbon/human/C = loc + C.update_inv_hands() + +/obj/item/gun/magic/darkspawn/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type != PROJECTILE_ATTACK) + final_block_chance = 0 //don't bring a staff to a knife fight + return ..() + +////////////////////////INFINITE AMMO////////////////////////// (some psi required) +/obj/item/gun/magic/darkspawn/can_shoot() + psi_cost = initial(psi_cost) + if(effect_flags & STAFF_UPGRADE_EFFICIENCY) + psi_cost *= 0.5 + if(isliving(src.loc)) + var/mob/living/dude = src.loc + if(isdarkspawn(dude)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(dude) + if(darkspawn && !darkspawn.has_psi(psi_cost)) + return FALSE + return ..() + +/obj/item/gun/magic/darkspawn/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) + . = ..() + if(. && isdarkspawn(user)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(user) + if(darkspawn) + darkspawn.use_psi(psi_cost) + +/obj/item/gun/magic/darkspawn/process_chamber() + . = ..() + charges = max_charges //infinite charges + +////////////////////////OTHER STUFF////////////////////////// +/obj/item/ammo_casing/magic/darkspawn + projectile_type = /obj/projectile/magic/darkspawn + firing_effect_type = null + +/obj/projectile/magic/darkspawn + name = "bolt of nothingness" + icon = 'yogstation/icons/obj/darkspawn_projectiles.dmi' + icon_state = "staff_blast" + damage = 0 + pass_flags = PASSTABLE | PASSMACHINES | PASSCOMPUTER + damage_type = STAMINA + nodamage = FALSE + antimagic_flags = MAGIC_RESISTANCE_MIND + speed = 2 //watch out, it fucks you up + +/obj/projectile/magic/darkspawn/Initialize(mapload) + . = ..() + add_atom_colour(COLOR_VELVET, FIXED_COLOUR_PRIORITY) + update_appearance(UPDATE_OVERLAYS) + +/obj/projectile/magic/darkspawn/update_overlays() + . = ..() + . += emissive_appearance(icon, icon_state, src) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_progenitor.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_progenitor.dm index 7b6d346e107e..80e286c9dfb3 100644 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_progenitor.dm +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_progenitor.dm @@ -1,62 +1,144 @@ /mob/living/simple_animal/hostile/darkspawn_progenitor - name = "cosmic progenitor" + name = "void progenitor" desc = "..." + + //icons icon = 'yogstation/icons/mob/darkspawn_progenitor.dmi' icon_state = "darkspawn_progenitor" icon_living = "darkspawn_progenitor" - health = INFINITY - maxHealth = INFINITY - attacktext = "rips apart" - attack_sound = 'yogstation/sound/creatures/progenitor_attack.ogg' - friendly = "stares down" - speak_emote = list("roars") - armour_penetration = 100 + health_doll_icon = "smolgenitor" + pixel_x = -48 //offset so the mob collision is roughly the middle of the sprite + pixel_y = -32 + layer = LARGE_MOB_LAYER + + //combat stats + health = 100000 //functionally immortal, but still killable + maxHealth = 100000 melee_damage_lower = 40 melee_damage_upper = 40 - move_to_delay = 10 - speed = 1 - pixel_x = -48 - pixel_y = -32 - sentience_type = SENTIENCE_BOSS + armour_penetration = 100 + obj_damage = INFINITY environment_smash = ENVIRONMENT_SMASH_RWALLS - obj_damage = 100 - light_range = 15 - light_color = "#21007F" weather_immunities = list("lava", "ash") - move_force = MOVE_FORCE_OVERPOWERING - move_resist = MOVE_FORCE_OVERPOWERING - pull_force = MOVE_FORCE_OVERPOWERING - mob_size = MOB_SIZE_LARGE - layer = LARGE_MOB_LAYER + status_flags = NONE + 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) //Leaving something at 0 means it's off - has no maximum + unsuitable_atmos_damage = 0 + + //movement stats + speed = 0 //slower than a person, RUN RUN RUN RUN movement_type = FLYING + move_force = INFINITY + move_resist = INFINITY //hmm yes, surely this won't cause bugs *clueless* + pull_force = INFINITY + mob_size = MOB_SIZE_HUGE + + //night vision + lighting_cutoff_red = 12 + lighting_cutoff_green = 0 + lighting_cutoff_blue = 50 + lighting_cutoff = (LIGHTING_CUTOFF_HIGH + 10) //could set it at 40, but i explicitly want it to be higher than the highest lighting cutoff + sight = SEE_MOBS | SEE_TURFS | SEE_OBJS + + //flavour + attack_sound = 'yogstation/sound/creatures/progenitor_attack.ogg' + attacktext = "rends" + friendly = "stares down" + speak_emote = list("roars") + speech_span = SPAN_PROGENITOR //pretty sure this is how i'd go about doing this, hopefully + faction = list("darkspawn") + + //progenitor specific variables var/time_to_next_roar = 0 + var/roar_cooldown = 30 SECONDS + var/list/roar_text = list( //picks a random line each time the target is stunned by a roar (a rough mix of psychotic ramblings, poetic waxings, and white noise) + "You stand paralyzed in the shadow of the cold as it descends from on high.", + "The end times the end times the end times the end times the end times", + "Its in your mind, your mind? it's mind? who's mind?", + "You legs refuse to move as you hear the cry of the unknown.", + "You have barely yet stared into the abyss, yet it gazes back in full force", + "I can't i can't i can't i can't i can't i can't i can't i can't i can't i can't", + "Your brain begins to turn on your body as there is naught else it can do.", + "You fail to muster the strength to move as the weight of your circumstances crush you.", + "This is all a dream, that has to be it, I've just fallen asleep on the job.", + "AHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAH", + "________________________________________________________________________________", + "Let me out. Let me out. Let me out. Let me out. Let me out. Let me out. Let me out.", + "^$#%*^&($^%&($^^!#%#@~!$#@*)&)_*_*)&+(&+*@#$%!@#$!~@%&^%*^&)(&*_((_+*(!#$@!@!))", + "********************************************************************************", + "The beauty of the end, it captivates you, but only for a moment.", + "To once again live in blissful ignorance, what a treat that would be.", + "They've returned... who?", + "Never left, unseen, ancient, original, unending, fleeting, have always been here.", + "The height of hubris to believe one was invited, mere stowaways.", + "How quick the turn from serene to chaos, a thin divide between.", + "The cacophony of sounds assault you from all directions." + ) -/mob/living/simple_animal/hostile/darkspawn_progenitor/Initialize(mapload) + ///Innate spells that are added when a progenitor is created + var/list/actions_to_add = list( //can be modified for each progenitor + /datum/action/cooldown/spell/pointed/progenitor_curse + ) + +/mob/living/simple_animal/hostile/darkspawn_progenitor/Initialize(mapload, darkspawn_name, class_colour = COLOR_DARKSPAWN_PSI) . = ..() - ADD_TRAIT(src, TRAIT_HOLY, "ohgodohfuck") //sorry no magic - alpha = 0 - animate(src, alpha = 255, time = 1 SECONDS) - var/obj/item/radio/headset/silicon/ai/radio = new(src) //so the progenitor can hear people's screams over radio + //add_atom_colour(class_colour, FIXED_COLOUR_PRIORITY) + + //give them all the class specific spells + for(var/spell in actions_to_add) + var/datum/action/cooldown/spell/new_spell = new spell(src) + new_spell.Grant(src) + + //add passive traits, elements, and components + ADD_TRAIT(src, TRAIT_HOLY, INNATE_TRAIT) //sorry no magic + ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) //so people can actually look at the sprite without the weird bobbing up and down + AddElement(/datum/element/death_explosion, 20, 20, 20) //with INFINITY health, they're not really able to die, but IF THEY DO + AddComponent(/datum/component/light_eater) + + //so the progenitor can hear people's screams over radio + var/obj/item/radio/headset/silicon/ai/radio = new(src) radio.wires.cut(WIRE_TX) //but not talk over it + //give them a fancy name (and help the darkspawn tell where they are in the dark) + var/prefix = pick("Ancestral", "Void", "Cosmic", "Shadow", "Darkspawn", "Veil") + var/suffix = pick("Progenitor", "Ascended") + if(rand(0, 10000) == 0) + prefix = "Vxtrin" + name = "[prefix] [suffix][darkspawn_name ? " [darkspawn_name]":""]" + + //have them fade into existence and play a sound cry when they finish fading in + alpha = 0 + animate(src, alpha = 255, time = 4 SECONDS) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(sound_to_playing_players), 'yogstation/sound/magic/sacrament_complete.ogg', 50), 4 SECONDS, TIMER_UNIQUE) + time_to_next_roar = world.time + roar_cooldown //prevent immediate roaring causing sound overlap + update_appearance(UPDATE_OVERLAYS) + +/mob/living/simple_animal/hostile/darkspawn_progenitor/update_overlays() + . = ..() + . += emissive_appearance(icon, icon_state, src) + /mob/living/simple_animal/hostile/darkspawn_progenitor/AttackingTarget() if(istype(target, /obj/machinery/door) || istype(target, /obj/structure/door_assembly)) playsound(target, 'yogstation/sound/magic/pass_smash_door.ogg', 100, FALSE) - obj_damage = 60 . = ..() +////////////////////////////////////////////////////////////////////////// +//-------------------------------Smol-----------------------------------// +////////////////////////////////////////////////////////////////////////// /mob/living/simple_animal/hostile/darkspawn_progenitor/Login() ..() - var/image/I = image(icon = 'yogstation/icons/mob/mob.dmi' , icon_state = "smol_progenitor", loc = src) + var/image/I = image(icon = 'yogstation/icons/mob/darkspawn.dmi' , icon_state = "smol_progenitor", loc = src) I.override = 1 I.pixel_x -= pixel_x I.pixel_y -= pixel_y add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smolgenitor", I) - time_to_next_roar = world.time + 30 SECONDS + time_to_next_roar = world.time + roar_cooldown +////////////////////////////////////////////////////////////////////////// +//-------------------------------Roar-----------------------------------// +////////////////////////////////////////////////////////////////////////// /mob/living/simple_animal/hostile/darkspawn_progenitor/Life(seconds_per_tick = SSMOBS_DT, times_fired) ..() - if(time_to_next_roar + 10 SECONDS <= world.time) //gives time to roar manually if you like want to do that + if(time_to_next_roar + roar_cooldown <= world.time) //gives time to roar manually if you want to roar() /mob/living/simple_animal/hostile/darkspawn_progenitor/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) @@ -64,40 +146,26 @@ if(time_to_next_roar <= world.time) roar() -/mob/living/simple_animal/hostile/darkspawn_progenitor/Process_Spacemove() - return TRUE - /mob/living/simple_animal/hostile/darkspawn_progenitor/proc/roar() - playsound(src, 'yogstation/sound/creatures/progenitor_roar.ogg', 50, TRUE) + playsound(src, 'yogstation/sound/creatures/progenitor_roar.ogg', 70, TRUE) for(var/mob/M in GLOB.player_list) if(get_dist(M, src) > 7) - M.playsound_local(src, 'yogstation/sound/creatures/progenitor_distant.ogg', 25, FALSE, falloff_exponent = 5) + M.playsound_local(src, 'yogstation/sound/creatures/progenitor_distant.ogg', 35, FALSE, falloff_exponent = 5) + else if(is_darkspawn_or_thrall(M) || M==src) //the progenitor is PROBABLY a darkspawn, but just in case + continue else if(isliving(M)) var/mob/living/L = M - if(L != src) //OH GOD OH FUCK I'M SCARING MYSELF - to_chat(M, span_boldannounce("You stand paralyzed in the shadow of the cold as it descends from on high.")) - L.Stun(20) - time_to_next_roar = world.time + 30 SECONDS - -/datum/action/cooldown/spell/list_target/progenitor_curse - name = "Viscerate Mind" - desc = "Unleash a powerful psionic barrage into the mind of the target." - button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' - button_icon_state = "veil_mind" - background_icon_state = "bg_alien" - - cooldown_time = 5 SECONDS - spell_requirements = NONE + if(prob(1) && isethereal(L)) + to_chat(M, span_boldannounce("They weren't just a story to keep us in line...")) + else + to_chat(M, span_boldannounce(pick(roar_text))) + L.Immobilize(3 SECONDS) + time_to_next_roar = world.time + roar_cooldown -/datum/action/cooldown/spell/list_target/progenitor_curse/cast(atom/target_atom) - . = ..() - if(!.) - return FALSE - var/mob/living/target = target_atom - var/zoinks = pick(0.1, 0.5, 1)//like, this isn't even my final form! - usr.visible_message(span_warning("[usr]'s sigils flare as it glances at [target]!"), \ - span_velvet("You direct [zoinks]% of your psionic power into [target]'s mind!.")) - target.apply_status_effect(STATUS_EFFECT_PROGENITORCURSE) +////////////////////////////////////////////////////////////////////////// +//--------------------------Ignoring physics----------------------------// +////////////////////////////////////////////////////////////////////////// +/mob/living/simple_animal/hostile/darkspawn_progenitor/Process_Spacemove() return TRUE /mob/living/simple_animal/hostile/darkspawn_progenitor/narsie_act() @@ -111,3 +179,39 @@ /mob/living/simple_animal/hostile/darkspawn_progenitor/ex_act() //sorry no bombs return + +/mob/living/simple_animal/hostile/darkspawn_progenitor/gib() //no shuttlegib either + return + +////////////////////////////////////////////////////////////////////////// +//--------------------------Progenitor attack---------------------------// +////////////////////////////////////////////////////////////////////////// +/datum/action/cooldown/spell/pointed/progenitor_curse + name = "Viscerate Mind" + desc = "Unleash a powerful psionic barrage into the mind of the target." + button_icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' + button_icon_state = "sacrament(old)" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + buttontooltipstyle = "alien" + ranged_mousepointer = 'icons/effects/mouse_pointers/visor_reticule.dmi' + + panel = "Darkspawn" + spell_requirements = NONE + cast_range = 10 + +/datum/action/cooldown/spell/pointed/progenitor_curse/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + return ..() + +/datum/action/cooldown/spell/pointed/progenitor_curse/cast(atom/cast_on) + . = ..() + if(!isliving(cast_on)) //sanity check + return + var/mob/living/target = cast_on + if(is_darkspawn_or_thrall(target)) + return + var/zoinks = rand(1, 50) / 100 //like, this isn't even my final form! + owner.visible_message(span_warning("[owner]'s sigils flare as it glances at [target]!"), span_velvet("You direct [zoinks]% of your psionic power into [target]'s mind!.")) + target.apply_status_effect(STATUS_EFFECT_PROGENITORCURSE) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_team.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_team.dm new file mode 100644 index 000000000000..a0adf012626b --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_team.dm @@ -0,0 +1,165 @@ +//the team itself +/datum/team/darkspawn + name = "Darkspawns" + member_name = "darkspawn" + ///The name of the "converted" players + var/thrall_name = "thrall" + ///The list containing all converted players + var/list/datum/mind/thralls = list() + ///The number of drains required to perform the sacrament + var/required_succs = 10 //How many succs are needed (this is changed in pre_setup, so it scales based on pop) + ///How many drains have happened so far + var/lucidity = 0 + ///The max number of people that can be actively converted + var/max_thralls = 0 + ///Boolean, Whether or not the darkspawn have been al;erted that they can perform the sacrament + var/announced = FALSE + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Basic Team Stuff----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/team/darkspawn/New(starting_members) + . = ..() + var/datum/objective/darkspawn/O = new + objectives += O + if(SSticker?.mode) + required_succs = clamp(round(SSticker.mode.num_players() / 2), 5, 25) //half the players, 5 at minimum, scaling up to 25 at max + update_objectives() + addtimer(CALLBACK(src, PROC_REF(enable_validhunt)), 75 MINUTES) //allow for validhunting after a duration + +/datum/team/darkspawn/add_member(datum/mind/new_member) + . = ..() + new_member.announce_objectives() + +/datum/team/darkspawn/remove_member(datum/mind/member) //also try to remove them from the thralls list just in case + . = ..() + thralls -= member + +/datum/team/darkspawn/proc/add_thrall(datum/mind/new_member) //thralls are treated differently than darkspawns + thralls |= new_member + +/datum/team/darkspawn/proc/remove_thrall(datum/mind/member) + thralls -= member + +//////////////////////////////////////////////////////////////////////////////////// +//-------------------------------Round end Stuff----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/team/darkspawn/roundend_report() + var/list/report = list() + + if(GLOB.sacrament_done) + report += span_progenitor("The Darkspawn have ascended once again!
The station has forever been lost beyond the veil.") + else + report += span_header("[name]:") //only have a regular header if it's a loss + if(check_darkspawn_death()) + report += span_redtext("The Darkspawn have been killed by the crew!") + else if(EMERGENCY_ESCAPED_OR_ENDGAMED) + report += span_redtext("The crew escaped the station before the Darkspawn could complete the Sacrament!") + else //fallback in case the round ends weirdly + report += span_redtext("The Darkspawn have failed!") + + report += "The [member_name]s were:" + report += printplayerlist(members) + + if(LAZYLEN(thralls)) + report += "The [thrall_name]s were:" + report += printplayerlist(thralls) + + return "
")]
"))) + + to_chat(owner, span_notice("Use .[MODE_KEY_DARKSPAWN] before your messages to speak over the Mindlink.")) + to_chat(owner, span_notice("Blending in with regular crewmembers will generate willpower for your masters.")) + to_chat(owner, span_notice("Ask for help from your masters or fellows if you're new to this role.")) + SEND_SOUND(owner.current, sound ('yogstation/sound/ambience/antag/become_veil.ogg', volume = 50)) + flash_color(owner, flash_color = COLOR_VELVET, flash_time = 10 SECONDS) + +/datum/antagonist/thrall/roundend_report() + return "[printplayer(owner)]" diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrade.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrade.dm deleted file mode 100644 index e30405cb0a53..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrade.dm +++ /dev/null @@ -1,21 +0,0 @@ -//Passive upgrades. These are applied as soon as they're purchased and then delete themselves. -/datum/darkspawn_upgrade - var/name = "darkspawn upgrade" - var/desc = "This is an upgrade." - var/id - var/lucidity_price = 0 //How much lucidity an upgrade costs to buy - var/datum/antagonist/darkspawn/darkspawn //The datum buying this upgrade - -/datum/darkspawn_upgrade/New(darkspawn_datum) - ..() - darkspawn = darkspawn_datum - -/datum/darkspawn_upgrade/proc/unlock() - if(!darkspawn) - return - apply_effects() - qdel(src) - return TRUE - -/datum/darkspawn_upgrade/proc/apply_effects() - return diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/dark_healing.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/dark_healing.dm deleted file mode 100644 index f77c20eaf20a..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/dark_healing.dm +++ /dev/null @@ -1,7 +0,0 @@ -//Increases healing in darkness by 25%. -//Check species_types/darkspawn.dm for effects. -/datum/darkspawn_upgrade/dark_healing - name = "\'Mending\' Sigil" - id = "dark_healing" - desc = "The Naykranu sigil, representing perseverence, is etched onto the back. Unlocking this sigil increases your healing in darkness by 25%." - lucidity_price = 1 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/light_resistance.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/light_resistance.dm deleted file mode 100644 index e4e60b9f1c6f..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/light_resistance.dm +++ /dev/null @@ -1,7 +0,0 @@ -//Halves lightburn damage and gives resistance to dim light. -//Check species_types/darkspawn.dm for effects. -/datum/darkspawn_upgrade/light_resistance - name = "\'Lightward\' Sigil" - id = "light_resistance" - desc = "The Lnkpayp sigil, representing imperviousness, is etched onto the abdomen. Unlocking this sigil halves light damage taken and protects from dim light." - lucidity_price = 2 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/offensive_upgrades.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/offensive_upgrades.dm new file mode 100644 index 000000000000..ab83a590c68b --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/offensive_upgrades.dm @@ -0,0 +1,227 @@ +//////////////////////////////////////////////////////////////////////////////////// +//---------------------------Fighter only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/shadow_crash + name = "Shadow Crash" + desc = "Charge in a direction, knock back and briefly paralyze anyone you collide with." + lore_description = "" + icon_state = "shadow_crash" + willpower_cost = 1 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/shadow_crash) + +/datum/psi_web/demented_outburst + name = "Demented Outburst" + desc = "Deafens and confuses listeners after a five-second charge period, knocking away everyone nearby.." + lore_description = "" + icon_state = "demented_outburst" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/aoe/demented_outburst) + +/datum/psi_web/taunt + name = "Incite" + desc = "Force everyone nearby to walk towards you, but disables your ability to attack for a time." + lore_description = "" + icon_state = "incite" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/aoe/taunt) + +//Using shadow tendrils will now form two tendrils if possible. +//Attacking with one set of tendrils will attack with the other. +//This also speeds up most actions they have. +//Check fighter_abilities.dm for the effect. +/datum/psi_web/ability_upgrade/twin_tendrils + name = "Duality Sigils" + desc = "Unlocking these sigils causes tendrils to form in both hands if possible, empowering both." + lore_description = "The Kqx'xpk sigils, representing duality, are etched onto the arms." + icon_state = "duality" + willpower_cost = 4 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_OFFENSE + flag_to_add = TENDRIL_UPGRADE_TWIN + +/datum/psi_web/ability_upgrade/cleaving_tendrils + name = "Cleaving Sigils" + desc = "Unlocking these sigils causes tendrils to cleave through enemies." + lore_description = "The Akvryt sigils, representing pierce, are etched onto the arms." + icon_state = "cleave" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_OFFENSE + flag_to_add = TENDRIL_UPGRADE_CLEAVE + +//////////////////////////////////////////////////////////////////////////////////// +//-----------------------------Scout only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/permafrost + name = "Permafrost" + desc = "Banish heat from the surrounding terrain, freezing it instantly." + lore_description = "Opens a pinhole to the veil, rapidly draining heat from the surrounding area." + icon_state = "permafrost" + willpower_cost = 3 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/aoe/permafrost) + +/datum/psi_web/shadow_caster + name = "Shadow Caster" + desc = "Twists an active arm into a bow that shoots arrows made of solid darkness." + lore_description = "" + icon_state = "shadow_caster" + willpower_cost = 4 //easily one of the strongest and most reliable abilities + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/toggle/shadow_caster) + +/datum/psi_web/damage_Trap + name = "Psi Trap (damage)" + desc = "Place a trap that deals damage to non-ally that crosses it." + lore_description = "The Shash sigil, representing danger, is carved on the ground." + icon_state = "psi_trap_damage" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/darkspawn_build/trap/damage) + +/datum/psi_web/cuff_trap + name = "Psi Trap (restrain)" + desc = "Place a trap that restrains the legs of any non-ally that crosses it." + lore_description = "The Hh'rt sigil, representing restraint, is carved on the ground." + icon_state = "psi_trap_bear" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/darkspawn_build/trap/legcuff) + +/datum/psi_web/nausea_trap + name = "Psi Trap (nausea)" + desc = "Place a trap that makes any non-ally that crosses it sick to their stomach." + lore_description = "The Vxb'rt sigil, representing disruption, is carved on the ground." + icon_state = "psi_trap_nausea" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/darkspawn_build/trap/nausea) + +/datum/psi_web/teleport_trap + name = "Psi Trap (teleport)" + desc = "Place a trap that teleports any non-ally to a random location on the station." + lore_description = "The Vxb'xki sigil, representing displacement, is carved on the ground." + icon_state = "psi_trap_teleport" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/darkspawn_build/trap/teleport) + +//////////////////////////////////////////////////////////////////////////////////// +//---------------------------Warlock only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/seize + name = "Seize" + desc = "Restrain a target's mental faculties, preventing speech and actions of any kind for a moderate duration." + lore_description = "" + icon_state = "seize" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/seize) + +//staff upgrades +/datum/psi_web/ability_upgrade/staff_confusion + name = "Confusion Sign" + desc = "Empower your staff with the ability to confuse any enemy shot." + lore_description = "The Vxb'rt sigil, representing disruption, is etched onto the staff." + icon_state = "confusion_sign" + willpower_cost = 1 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + flag_to_add = STAFF_UPGRADE_CONFUSION + +/datum/psi_web/ability_upgrade/staff_light_eater + name = "Light Eater Sign" + desc = "Empower your staff with the ability to consume the light of anything shot." + lore_description = "The Aaah'ryt sigil, representing consumption, is etched onto the staff." + icon_state = "lighteater_sign" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + flag_to_add = STAFF_UPGRADE_LIGHTEATER + +//no more staff upgrades +/datum/psi_web/mass_hallucination + name = "Mass Hallucination" + desc = "Forces brief delirium on all nearby enemies." + lore_description = "" + icon_state = "mass_hallucination" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/aoe/mass_hallucination) + +/datum/psi_web/mindblast + name = "Mind blast" + desc = "Focus your psionic energy into a blast that deals physical damage. Can also be projected from the minds of allies." + lore_description = "" + icon_state = "mind_blast" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/mindblast) + +/datum/psi_web/extract + name = "Extract" + desc = "Drain a target's life force or bestow it to an ally." + lore_description = "" + icon_state = "extract" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/extract) + +/datum/psi_web/abyssal_call + name = "Abyssal Call" + desc = "Call a tendril at a targeted location to grasp an enemy." + lore_description = "Summon abyssal tendrils from beyond the veil." + icon_state = "abyssal_call" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/darkspawn_build/abyssal_call) + +/datum/psi_web/shadow_beam + name = "Void Beam" + desc = "After a short delay, fire a huge beam of void terrain across the entire station." + lore_description = "Tears a strip of reality into the void for a short duration." + icon_state = "shadow_beam" + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/shadow_beam) + +/datum/psi_web/null_burst + name = "Null Burst" + desc = "After a short delay, create an explosion of void terrain at the targeted location." + lore_description = "Tears a portion of reality into the void for a short duration." + icon_state = "null_burst" //needs an icon + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/pointed/null_burst) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Mixed abilities-----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/icyveins + name = "Icy Veins" + desc = "Instantly freezes the blood of nearby people, slowing them and rapidly chilling their body." + lore_description = "" + icon_state = "icy_veins" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT | DARKSPAWN_WARLOCK + menu_tab = STORE_OFFENSE + learned_abilities = list(/datum/action/cooldown/spell/aoe/icyveins) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/passive_upgrades.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/passive_upgrades.dm new file mode 100644 index 000000000000..7b63caa44f7d --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/passive_upgrades.dm @@ -0,0 +1,311 @@ +//mixed is at the top here because mixed abilities are probably more versatile and wanted before the specialized ones +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Mixed abilities-----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +//medhud, duh +/datum/psi_web/medhud + name = "Medical Sigil" + desc = "Provides you with a medical hud." + lore_description = "The Vxt sigils, representing knowledge, are etched underneath the eyes." + icon_state = "medical" + willpower_cost = 1 + menu_tab = STORE_PASSIVE + shadow_flags = ALL_DARKSPAWN_CLASSES + +/datum/psi_web/medhud/on_gain() + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + H.show_to(shadowhuman) + +/datum/psi_web/medhud/on_loss() + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + H.hide_from(shadowhuman) + +//xray vision, duh +/datum/psi_web/xray + name = "X-Ray Sigil" + desc = "Allows you to see through solid objects." + lore_description = "The Akvryt sigils, representing pierce, are etched underneath the eyes." + icon_state = "xray" + willpower_cost = 3 + menu_tab = STORE_PASSIVE + shadow_flags = ALL_DARKSPAWN_CLASSES + var/obj/item/organ/eyes/eyes + +/datum/psi_web/xray/on_gain() + eyes = shadowhuman.getorganslot(ORGAN_SLOT_EYES) + if(eyes && istype(eyes)) + eyes.sight_flags |= SEE_OBJS | SEE_TURFS + shadowhuman.update_sight() + +/datum/psi_web/xray/on_loss() + if(eyes) + eyes.sight_flags &= ~(SEE_OBJS | SEE_TURFS) + shadowhuman.update_sight() + +//Increases max Psi by 100. +/datum/psi_web/psi_cap + name = "Psionic Reserve Sigils" + desc = "Unlocking these sigils increases your maximum Psi by 100." + lore_description = "The Trin sigil, representing mind, is etched onto the forehead." + icon_state = "psi_reserve" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = ALL_DARKSPAWN_CLASSES + infinite = TRUE + +/datum/psi_web/psi_cap/on_gain() + darkspawn.psi_cap += 100 + +/datum/psi_web/psi_cap/on_loss() + darkspawn.psi_cap -= 100 + +/datum/psi_web/stamina_res + name = "Vigor Sigils" + desc = "Unlocking this sigil halves stamina damage taken." + lore_description = "The Kalak sigils, representing eternity, are etched onto the legs." + icon_state = "vigor" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_SCOUT | DARKSPAWN_FIGHTER + +/datum/psi_web/stamina_res/on_gain() + darkspawn.stam_mod *= 0.5 + +/datum/psi_web/stamina_res/on_loss() + darkspawn.stam_mod /= 0.5 + +//Increases healing in darkness. +/datum/psi_web/dark_healing + name = "Mending Sigil" + desc = "Unlocking this sigil increases your healing in darkness." + lore_description = "The Kalak sigil, representing eternity, is etched onto the back." + icon_state = "mending" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER | DARKSPAWN_SCOUT + infinite = TRUE + +/datum/psi_web/dark_healing/on_gain() + darkspawn.dark_healing *= 1.25 + +/datum/psi_web/dark_healing/on_gain() + darkspawn.dark_healing /= 1.25 + +//gives resistance to dim light +/datum/psi_web/low_light_resistance + name = "Lightward Sigil" + desc = "Unlocking this sigil protects from dim light." + lore_description = "The Vvkatkz sigil, representing warding, is etched onto the back." + icon_state = "light_ward" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER | DARKSPAWN_SCOUT + +/datum/psi_web/low_light_resistance/on_gain() + ADD_TRAIT(darkspawn, TRAIT_DARKSPAWN_LIGHTRES, src) + +/datum/psi_web/low_light_resistance/on_loss() + REMOVE_TRAIT(darkspawn, TRAIT_DARKSPAWN_LIGHTRES, src) + +/datum/psi_web/noslip + name = "Stability Sigil" + desc = "Unlocking this sigil prevents loss of footing." + lore_description = "The Tr'bxv sigils, representing stability, are etched onto the legs." + icon_state = "stability" + willpower_cost = 1 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER | DARKSPAWN_SCOUT + +/datum/psi_web/noslip/on_gain() + ADD_TRAIT(shadowhuman, TRAIT_NO_SLIP_ALL, type) + +/datum/psi_web/noslip/on_loss() + REMOVE_TRAIT(shadowhuman, TRAIT_NO_SLIP_ALL, type) + +//reduces spell cooldowns +/datum/psi_web/fast_cooldown + name = "Storm Sigil" + desc = "Unlocking this sigil causes your spells to have shorter cooldowns." + lore_description = "The Zeras sigil, representing storm, is etched onto the forehead." + icon_state = "storm" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_WARLOCK | DARKSPAWN_SCOUT + +/datum/psi_web/fast_cooldown/on_gain() + ADD_TRAIT(shadowhuman, TRAIT_FAST_COOLDOWNS, type) + +/datum/psi_web/fast_cooldown/on_loss() + REMOVE_TRAIT(shadowhuman, TRAIT_FAST_COOLDOWNS, type) + +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------Fighter Passive Upgrades------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/sunglasses + name = "Lightblind Sigil" + desc = "Protects you from strong flashes of light." + lore_description = "The Vvkatkz sigils, representing warding, are etched underneath the eyes." + icon_state = "light_blind" + willpower_cost = 1 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER + +/datum/psi_web/sunglasses/on_gain() + ADD_TRAIT(shadowhuman, TRAIT_NOFLASH, type) + +/datum/psi_web/sunglasses/on_loss() + REMOVE_TRAIT(shadowhuman, TRAIT_NOFLASH, type) + +//Halves lightburn damage. +/datum/psi_web/light_resistance + name = "Shadowskin Sigil" + desc = "Unlocking this sigil reduces light damage taken." + lore_description = "The Xlynsh sigil, representing refraction, is etched onto the abdomen." + icon_state = "shadow_skin" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER + infinite = TRUE + +/datum/psi_web/light_resistance/on_gain() + darkspawn.light_burning *= 0.8 + +/datum/psi_web/light_resistance/on_loss() + darkspawn.light_burning /= 0.8 + +/datum/psi_web/brute_res + name = "Callous Sigil" + desc = "Unlocking this sigil reduces brute damage taken." + lore_description = "The Hh'sha sigil, representing perserverance, is etched onto the abdomen." + icon_state = "callous" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER + infinite = TRUE + +/datum/psi_web/brute_res/on_gain() + darkspawn.brute_mod *= 0.8 + +/datum/psi_web/brute_res/on_loss() + darkspawn.brute_mod /= 0.8 + +/datum/psi_web/burn_res + name = "Stifle Sigil" + desc = "Unlocking this sigil reduces burn damage taken." + lore_description = "The Khophg sigil, representing suffocation, is etched onto the abdomen." + icon_state = "stifle" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER + infinite = TRUE + +/datum/psi_web/burn_res/on_gain() + darkspawn.burn_mod *= 0.85 + +/datum/psi_web/burn_res/on_loss() + darkspawn.burn_mod /= 0.85 + +/datum/psi_web/undying + name = "Undying Sigils" + desc = "Unlocking this sigil will revive you upon death after some time spent in darkness." + lore_description = "The Kalak sigil, representing eternity, is etched onto the abdomen." + icon_state = "undying" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_FIGHTER + +/datum/psi_web/undying/on_gain() + ADD_TRAIT(darkspawn, TRAIT_DARKSPAWN_UNDYING, type) + +/datum/psi_web/undying/on_loss() + REMOVE_TRAIT(darkspawn, TRAIT_DARKSPAWN_UNDYING, type) + +//////////////////////////////////////////////////////////////////////////////////// +//----------------------------Scout Passive Upgrades------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/shadow_walk + name = "Shadowwalk Sigils" + desc = "Unlocking this sigil greatly increases speed in the dark." + lore_description = "The Mehlak sigils, representing journey, are etched onto the legs." + icon_state = "shadow_walk" + willpower_cost = 3 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_SCOUT + +/datum/psi_web/shadow_walk/on_gain() + shadowhuman.AddComponent(/datum/component/shadow_step) + +/datum/psi_web/shadow_walk/on_loss() + qdel(shadowhuman.GetComponent(/datum/component/shadow_step)) + +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------Warlock Passive Upgrades------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +//Decreases the delay before psi starts regenerating +/datum/psi_web/psi_regen_delay + name = "Psionic Relief Sigil" + desc = "Unlocking this sigil causes your Psi to start regenerating 5 seconds sooner." + lore_description = "The Kalak sigil, representing eternity, is etched onto the forehead." + icon_state = "psi_relief" + willpower_cost = 2 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_WARLOCK + +/datum/psi_web/psi_regen_delay/on_gain() + darkspawn.psi_regen_delay -= 5 SECONDS + +/datum/psi_web/psi_regen_delay/on_loss() + darkspawn.psi_regen_delay += 5 SECONDS + +//increase the speed at which psi regenerates when it does start +/datum/psi_web/psi_regen_speed + name = "Psionic Recovery Sigil" + desc = "Unlocking this sigil causes your Psi to regenerate twice as quickly." + lore_description = "The Ieaxki sigil, representing swiftness, is etched onto the forehead." + icon_state = "psi_recovery" + willpower_cost = 1 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_WARLOCK + infinite = TRUE + +/datum/psi_web/psi_regen_speed/on_gain() + darkspawn.psi_per_second *= 2 + +/datum/psi_web/psi_regen_speed/on_loss() + darkspawn.psi_per_second /= 2 + +//adds an additional thrall +/datum/psi_web/more_thralls + name = "Control Sigil" + desc = "Unlocking this sigil allows control of two additional thralls." + lore_description = "The Gryxah sigils, representing control, are etched onto the arms." + icon_state = "control" + willpower_cost = 3 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_WARLOCK + +/datum/psi_web/more_thralls/on_gain() + var/datum/team/darkspawn/team = darkspawn.get_team() + if(team) + team.max_thralls += 2 + +/datum/psi_web/more_thralls/on_loss() + var/datum/team/darkspawn/team = darkspawn.get_team() + if(team) + team.max_thralls += 2 + +//buff allied darkspawns +/datum/psi_web/buff_allies + name = "Unity Sigil" + desc = "Unlocking this sigil allows your thrall support abilities to also affect allied darkspawns." + lore_description = "The Ahwelhe sigils, representing unity, are etched onto the hands." + icon_state = "unity" + willpower_cost = 1 + menu_tab = STORE_PASSIVE + shadow_flags = DARKSPAWN_WARLOCK + +/datum/psi_web/buff_allies/on_gain() + ADD_TRAIT(darkspawn, TRAIT_DARKSPAWN_BUFFALLIES, type) + +/datum/psi_web/buff_allies/on_loss() + REMOVE_TRAIT(darkspawn, TRAIT_DARKSPAWN_BUFFALLIES, type) diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_cap.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_cap.dm deleted file mode 100644 index c85e8bda23e7..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_cap.dm +++ /dev/null @@ -1,9 +0,0 @@ -//Increases max Psi by 25. -/datum/darkspawn_upgrade/psi_cap - name = "\'Psi\' Sigils" - id = "psi_cap" - desc = "The Atlwjz sigils, representing Psi, are etched onto the forehead. Unlocking these sigils increases your maximum Psi by 25." - lucidity_price = 2 - -/datum/darkspawn_upgrade/psi_cap/apply_effects() - darkspawn.psi_cap += 25 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_regen.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_regen.dm deleted file mode 100644 index 6aead6117cc3..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/psi_regen.dm +++ /dev/null @@ -1,10 +0,0 @@ -//Decreases the Psi regeneration delay by 3 ticks and increases Psi regeneration threshold to 25. -/datum/darkspawn_upgrade/psi_regen - name = "\'Recovery\' Sigil" - id = "psi_regen" - desc = "The Mqeygjao sigil, representing swiftness, is etched onto the forehead. Unlocking this sigil causes your Psi to regenerate 3 ticks sooner, and you will regenerate up to 25 Psi instead of 20." - lucidity_price = 1 - -/datum/darkspawn_upgrade/psi_regen/apply_effects() - darkspawn.psi_regen = 25 - darkspawn.psi_regen_delay -= 3 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/spacewalking.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/spacewalking.dm deleted file mode 100644 index f8bd4ef55073..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/spacewalking.dm +++ /dev/null @@ -1,6 +0,0 @@ -//Provides immunity to starlight. -/datum/darkspawn_upgrade/spacewalking - name = "\'Starlight\' Sigils" - id = "spacewalking" - desc = "The Jaxqhw sigils, representing the void, are etched multiple times across the body. Unlocking these sigils provides the ability to walk freely in space without fear of starlight." - lucidity_price = 3 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/twin_tendrils.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/twin_tendrils.dm deleted file mode 100644 index 4bf95bf71d7d..000000000000 --- a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/twin_tendrils.dm +++ /dev/null @@ -1,9 +0,0 @@ -//Using Pass will now form two tendrils if possible. -//Attacking with one set of tendrils will attack with the other. -//This also speeds up most actions they have. -//Check pass.dm and umbral_tendrils.dm for effects. -/datum/darkspawn_upgrade/twin_tendrils - name = "\'Duality\' Sigils" - id = "twin_tendrils" - desc = "The Zkqxha sigils, representing duality, are etched onto the arms. Unlocking these sigils causes Pass to form tendrils in both hands if possible, which empowers both." - lucidity_price = 1 diff --git a/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/utility_upgrades.dm b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/utility_upgrades.dm new file mode 100644 index 000000000000..ae3f2725c97d --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/darkspawn_upgrades/utility_upgrades.dm @@ -0,0 +1,221 @@ +//////////////////////////////////////////////////////////////////////////////////// +//---------------------------Fighter only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/deluge + name = "Deluge" + desc = "Douses all nearby creatures with water, extinguishing them and protecting from fire." + lore_description = "Calls upon the endless depths to douse all with the beyond." + icon_state = "deluge" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/aoe/deluge) + +/datum/psi_web/creep + name = "Encroach" + desc = "Grants immunity to lightburn while active. Can be toggled on and off." + lore_description = "Mold the darkness into an ephemeral cloak using psionic power." + icon_state = "encroach" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/toggle/creep) + +/datum/psi_web/time_dilation + name = "Time Dilation" + desc = "Greatly increases reaction times and action speed, and provides immunity to slowdown." + lore_description = "Directly control your physical form using psionic power rather than relying on brutish biology." + icon_state = "time_dilation" + willpower_cost = 4 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/time_dilation) + +/datum/psi_web/indomitable + name = "Indomitable" + desc = "Grants immunity to all CC effects, but locks the user into walking." + lore_description = "Stitch yourself to the ground using shadows themselves." + icon_state = "indomitable" + willpower_cost = 2 + shadow_flags = DARKSPAWN_FIGHTER + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/toggle/indomitable) + +//////////////////////////////////////////////////////////////////////////////////// +//-----------------------------Scout only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/void_jump + name = "Void Jump" + desc = "A short range targeted teleport." + lore_description = "Take a single step through the veil." + icon_state = "shadow_jump" + willpower_cost = 2 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/pointed/phase_jump/void_jump) + +/datum/psi_web/void_jaunt + name = "Void Jaunt" + desc = "Move through the veil for a time, avoiding mortal eyes and lights." + lore_description = "The veil can be treacherous for those unprepared, however it can give reprieve." + icon_state = "void_jaunt" + willpower_cost = 3 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/jaunt/ethereal_jaunt/void_jaunt) + +/datum/psi_web/darkness_smoke + name = "Blinding Miasma" + desc = "Spews a cloud of smoke which will blind enemies and provide cover from light." + lore_description = "Release a pocket of darkness nestled within your body." + icon_state = "blinding_miasma" + willpower_cost = 3 + shadow_flags = DARKSPAWN_SCOUT + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/darkness_smoke) + +//////////////////////////////////////////////////////////////////////////////////// +//---------------------------Warlock only abilities-------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/ability_upgrade/efficiency + name = "Efficiency Sign" + desc = "Optimize your staff to reduce the Psi cost to shoot." + lore_description = "The Akylia sigil, representing efficiency, is etched onto the staff." + icon_state = "efficiency_sign" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + flag_to_add = STAFF_UPGRADE_EFFICIENCY + +/datum/psi_web/ability_upgrade/heal + name = "Recovery Sign" + desc = "Empower your staff with the ability to heal allies shot." + lore_description = "The Kalak sigil, representing eternity, is etched onto the staff." + icon_state = "recovery_sign" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + flag_to_add = STAFF_UPGRADE_HEAL + +/datum/psi_web/ability_upgrade/extinguish + name = "Stifle Sign" + desc = "Empower your staff with the ability to extinguish the fire on allies shot." + lore_description = "The Khophg sigil, representing suffocation, is etched onto the staff." + icon_state = "extinguish_sign" + willpower_cost = 1 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + flag_to_add = STAFF_UPGRADE_EXTINGUISH + +/datum/psi_web/extinguish + name = "Extinguish" + desc = "Extinguish all light around you." + lore_description = "Remind all that glows, that it is but a small part of reality." + icon_state = "extinguish" + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/aoe/extinguish) + +/datum/psi_web/thrall_heal + name = "Thrall Recovery" + desc = "Heals all vthralls for an amount of brute and burn." + lore_description = "While thralls are expendable, they do have their use." + icon_state = "heal_veils" + willpower_cost = 1 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/thrallbuff/heal) + +/datum/psi_web/thrall_speed + name = "Thrall Envigorate" + desc = "Give all thralls a temporary movespeed bonus." + lore_description = "Thralls are expendable, push them until they break." + icon_state = "speedboost_veils" + willpower_cost = 1 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/thrallbuff/speed) + +/datum/psi_web/elucidate + name = "Elucidate" + desc = "Channel significant power through an ally, greatly healing them, cleansing all CC and providing a speed boost." + lore_description = "" + icon_state = "elucidate" + willpower_cost = 2 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/pointed/elucidate) + +/datum/psi_web/null_charge + name = "Null Charge" + desc = "Meddle with the powergrid via a functioning APC, causing a temporary stationwide power outage. Breaks the APC in the process." + lore_description = "" + icon_state = "null_charge" + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/touch/null_charge) + +/datum/psi_web/quantum_disruption //basically just a worse jaunt + name = "Quantum Disruption" + desc = "Bend the laws of reality to allow free passage all through-out spacetime for a short duration." + lore_description = "Disrupt the flow of possibilities, where you are, where you could be." + icon_state = "quantum_disruption" + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/erase_time/darkspawn) + +/datum/psi_web/fray_self + name = "Fray self" + desc = "Attemps to split a piece of your psyche into a sentient copy of yourself that lasts until destroyed." + lore_description = "" + icon_state = "fray_self" + willpower_cost = 3 + shadow_flags = DARKSPAWN_WARLOCK + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/fray_self) + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Mixed abilities-----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/psi_web/umbral_trespass + name = "Umbral Trespass" + desc = "Melds with a target's shadow, causing you to invisibly follow them." + lore_description = "" + icon_state = "umbral_trespass" + willpower_cost = 2 + shadow_flags = ALL_DARKSPAWN_CLASSES + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/touch/umbral_trespass) + +/datum/psi_web/simulacrum + name = "Simulacrum" + desc = "Creates an illusion that closely resembles you. The illusion will fight nearby enemies in your stead for 10 seconds." + lore_description = "" + icon_state = "simulacrum" + willpower_cost = 2 + shadow_flags = ALL_DARKSPAWN_CLASSES + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/simulacrum) + +/datum/psi_web/crawling_shadows + name = "Crawling Shadows" + desc = "Assumes a shadowy form that can crawl through vents and squeeze through the cracks in doors." + lore_description = "" + icon_state = "crawling_shadows" + willpower_cost = 3 + shadow_flags = ALL_DARKSPAWN_CLASSES + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/shapeshift/crawling_shadows) + +/datum/psi_web/silver_tongue + name = "Silver Tongue" + desc = "When used near a communications console, allows you to forcefully transmit a message to Central Command, initiating a shuttle recall." + lore_description = "" + icon_state = "silver_tongue" + willpower_cost = 2 + shadow_flags = ALL_DARKSPAWN_CLASSES + menu_tab = STORE_UTILITY + learned_abilities = list(/datum/action/cooldown/spell/touch/silver_tongue) diff --git a/yogstation/code/modules/antagonists/darkspawn/shadowlands_component.dm b/yogstation/code/modules/antagonists/darkspawn/shadowlands_component.dm new file mode 100644 index 000000000000..b253477bf03e --- /dev/null +++ b/yogstation/code/modules/antagonists/darkspawn/shadowlands_component.dm @@ -0,0 +1,37 @@ +//adds and removes the shadowlands overlay based on Z level +/datum/component/shadowlands + dupe_mode = COMPONENT_DUPE_UNIQUE + ///The mob that gets the overlay applied to it + var/mob/living/owner + +/datum/component/shadowlands/Initialize() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + owner = parent + +/datum/component/shadowlands/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(update_fullscreen)) + update_fullscreen() + +/datum/component/shadowlands/UnregisterFromParent() + if(owner) + owner.clear_fullscreen("shadowlands") + UnregisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED) + return ..() + +/datum/component/shadowlands/proc/update_fullscreen() + if(!owner) + return + + var/turf/location = get_turf(owner) + if(!(is_centcom_level(location.z) || is_reserved_level(location.z))) + owner.overlay_fullscreen("shadowlands", /atom/movable/screen/fullscreen/shadowlands) + else + owner.clear_fullscreen("shadowlands") + +//the fullscreen in question +/atom/movable/screen/fullscreen/shadowlands + icon_state = "shadowlands" + layer = CURSE_LAYER + plane = FULLSCREEN_PLANE diff --git a/yogstation/code/modules/antagonists/shadowling/ascendant_shadowling.dm b/yogstation/code/modules/antagonists/shadowling/ascendant_shadowling.dm deleted file mode 100644 index b97ea0ee1262..000000000000 --- a/yogstation/code/modules/antagonists/shadowling/ascendant_shadowling.dm +++ /dev/null @@ -1,48 +0,0 @@ -/mob/living/simple_animal/ascendant_shadowling - name = "ascendant shadowling" - desc = "HOLY SHIT RUN THE FUCK AWAY- RAAAAAAA!" - icon = 'yogstation/icons/mob/mob.dmi' - icon_state = "shadowling_ascended" - icon_living = "shadowling_ascended" - verb_say = "telepathically thunders" - verb_ask = "telepathically thunders" - verb_exclaim = "telepathically thunders" - verb_yell = "telepathically thunders" - force_threshold = INFINITY //Can't die by normal means - health = 9999 - maxHealth = 9999 - speed = 0 - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_MINIMUM - response_help = "pokes" - response_disarm = "flails at" - response_harm = "flails at" - harm_intent_damage = 0 - melee_damage_lower = 160 //Was 60, buffed - melee_damage_upper = 160 - attacktext = "rends" - attack_sound = 'sound/weapons/slash.ogg' - minbodytemp = 0 - maxbodytemp = INFINITY - environment_smash = 3 - faction = list("faithless") - speech_span = SPAN_REALLYBIG //screw it someone else can figure out how to put both SPAN_YELL and SPAN_REALLYBIG on a speech_span later - -/mob/living/simple_animal/ascendant_shadowling/Initialize(mapload) - . = ..() - LoadComponent(/datum/component/walk) - -/mob/living/simple_animal/ascendant_shadowling/Process_Spacemove(movement_dir = 0) - return TRUE //copypasta from carp code - -/mob/living/simple_animal/ascendant_shadowling/ex_act(severity) - return FALSE //You think an ascendant can be hurt by bombs? HA - -/mob/living/simple_animal/ascendant_shadowling/singularity_act() - to_chat(src, "