diff --git a/_maps/shuttles/ferry_meat.dmm b/_maps/shuttles/ferry_meat.dmm index 3a5be5bebf01..56eb6ac34b5f 100644 --- a/_maps/shuttles/ferry_meat.dmm +++ b/_maps/shuttles/ferry_meat.dmm @@ -66,7 +66,7 @@ name = "podperson meat" }, /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow{ - name = "shadowling meat" + name = "nightmare meat" }, /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime{ name = "slimeperson meat" diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index c8e102b28568..57b924379f28 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -197,6 +197,7 @@ #define ORGAN_SLOT_LUNGS "lungs" #define ORGAN_SLOT_HEART "heart" #define ORGAN_SLOT_ZOMBIE "zombie_infection" +#define ORGAN_SLOT_BRAIN_TUMOR "brain_tumor" #define ORGAN_SLOT_LIVER "liver" #define ORGAN_SLOT_TONGUE "tongue" #define ORGAN_SLOT_VOICE "vocal_cords" diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 7e566a6a2ab9..debaf5f2bd14 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -312,6 +312,7 @@ #define COLOR_CHANGELING_CHEMICALS "#DD66DD" #define COLOR_DARKSPAWN_PSI "#7264FF" +#define COLOR_VELVET "#21007F" #define COLOR_CARP_PURPLE "#aba2ff" #define COLOR_CARP_PINK "#da77a8" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index b751f86bf679..4d03a0d0acc1 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -82,3 +82,5 @@ #define COMSIG_NARSIE_SUMMON_UPDATE "!narsie_summon_update" /// Global signal when starlight color is changed (old_star, new_star) #define COMSIG_STARLIGHT_COLOR_CHANGED "!starlight_color_changed" +/// Global signal sent when darkspawns ascend: (No arguments) +#define COMSIG_DARKSPAWN_ASCENSION "!darkspawn_ascension" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm index d9d114e4c1d3..a735e6a659d0 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm @@ -72,6 +72,8 @@ #define COMSIG_LIVING_STATUS_PARALYZE "living_paralyze" ///from base of mob/living/Immobilize() (amount, ignore_canstun) #define COMSIG_LIVING_STATUS_IMMOBILIZE "living_immobilize" +///from base of mob/living/Daze() (amount, ignore_canstun) +#define COMSIG_LIVING_STATUS_DAZE "living_daze" ///from base of mob/living/Unconscious() (amount, ignore_canstun) #define COMSIG_LIVING_STATUS_UNCONSCIOUS "living_unconscious" ///from base of mob/living/Sleeping() (amount, ignore_canstun) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 273ceaeeded4..2328dbe5b781 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -459,3 +459,19 @@ /// from /obj/item/detective_scanner/scan(): (mob/user, list/extra_data) #define COMSIG_DETECTIVE_SCANNED "det_scanned" + +// /datum/element/light_eater +///from base of [/datum/element/light_eater/proc/table_buffet]: (list/light_queue, datum/light_eater) +#define COMSIG_LIGHT_EATER_QUEUE "light_eater_queue" +///from base of [/datum/element/light_eater/proc/devour]: (datum/light_eater) +#define COMSIG_LIGHT_EATER_ACT "light_eater_act" + ///Prevents the default light eater behavior from running in case of immunity or custom behavior + #define COMPONENT_BLOCK_LIGHT_EATER (1<<0) +///from base of [/datum/element/light_eater/proc/devour]: (atom/eaten_light) +#define COMSIG_LIGHT_EATER_DEVOUR "light_eater_devour" + + +/// Flag for when /afterattack potentially acts on an item. +/// Used for the swap hands/drop tutorials to know when you might just be trying to do something normally. +/// Does not necessarily imply success, or even that it did hit an item, just intent. +#define COMPONENT_AFTERATTACK_PROCESSED_ITEM (1<<0) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index c6e89f697d68..c0b045af5c06 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -15,7 +15,7 @@ #define isnan(x) ( isnum((x)) && ((x) != (x)) ) -#define isinternalorgan(A) (istype(A, /obj/item/organ/internal)) +//#define isinternalorgan(A) (istype(A, /obj/item/organ/internal)) uncomment if we port tg organ code //Turfs //#define isturf(A) (istype(A, /turf)) This is actually a byond built-in. Added here for completeness sake. @@ -93,6 +93,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isinsurgent(A) (is_species(A, /datum/species/ipc/self/insurgent)) #define issnail(A) (is_species(A, /datum/species/snail)) #define isandroid(A) (is_species(A, /datum/species/android)) +#define isshadowperson(A) (is_species(A, /datum/species/shadow)) #define is_synth(A) (is_species(A,/datum/species/wy_synth)) #define isdummy(A) (istype(A, /mob/living/carbon/human/dummy)) diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm index 34ec4b6659b6..a83ebbd87cee 100644 --- a/code/__DEFINES/magic.dm +++ b/code/__DEFINES/magic.dm @@ -60,6 +60,7 @@ #define SPELL_REQUIRES_MIND (1 << 6) /// Whether the spell requires the caster have a mime vow (mindless mobs will succeed this check regardless). #define SPELL_REQUIRES_MIME_VOW (1 << 7) + /// Whether the spell can be cast, even if the caster is unable to speak the invocation /// (effectively making the invocation flavor, instead of required). #define SPELL_CASTABLE_WITHOUT_INVOCATION (1 << 8) @@ -87,7 +88,7 @@ DEFINE_BITFIELD(spell_requirements, list( // Bitflags for magic resistance types /// Default magic resistance that blocks normal magic (wizard, spells, magical staff projectiles) #define MAGIC_RESISTANCE (1<<0) -/// Tinfoil hat magic resistance that blocks mental magic (telepathy / mind links, mind curses, abductors) +/// Tinfoil hat magic resistance that blocks mental magic (telepathy / mind links, mind curses, abductors, darkspawns) #define MAGIC_RESISTANCE_MIND (1<<1) /// Holy magic resistance that blocks unholy magic (revenant, vampire, voice of god) #define MAGIC_RESISTANCE_HOLY (1<<2) diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index be6ebb9eb03e..a42388275161 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -189,8 +189,8 @@ while(pixel_y < -16) pixel_y += 32 new_y-- - new_x = clamp(new_x, 0, world.maxx) - new_y = clamp(new_y, 0, world.maxy) + new_x = clamp(new_x, 1, world.maxx) + new_y = clamp(new_y, 1, world.maxy) return locate(new_x, new_y, starting.z) // Returns a list where [1] is all x values and [2] is all y values that overlap between the given pair of rectangles diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 17975df42978..c6200da0d716 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -281,7 +281,10 @@ #define MEGAFAUNA_DEFAULT_RECOVERY_TIME 5 -#define SHADOW_SPECIES_LIGHT_THRESHOLD 0.2 +#define SHADOW_SPECIES_DIM_LIGHT 0.2 //light of this intensity suppresses healing and causes very slow burn damage +#define SHADOW_SPECIES_BRIGHT_LIGHT 0.6 //light of this intensity causes rapid burn damage (high number because movable lights are weird) +//so the problem is that movable lights ALWAYS have a luminosity of 0.5, regardless of power or distance, so even at the edge of the overlay they still do damage +//at 0.6 being bright they'll still do damage and disable some abilities, but it won't be weaponized // Offsets defines diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index d835e5a0fea7..93f2c9a80542 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -41,7 +41,6 @@ #define ROLE_LAVALAND "Lavaland" #define ROLE_INTERNAL_AFFAIRS "Internal Affairs Agent" #define ROLE_FUGITIVE "Fugitive" -#define ROLE_SHADOWLING "Shadowling" // Yogs #define ROLE_VAMPIRE "Vampire" // Yogs #define ROLE_GANG "gangster" // Yogs #define ROLE_DARKSPAWN "darkspawn" // Yogs @@ -91,7 +90,6 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_OBSESSED = /datum/antagonist/obsessed, ROLE_INTERNAL_AFFAIRS = /datum/antagonist/traitor/internal_affairs, ROLE_FUGITIVE = /datum/antagonist/fugitive, - ROLE_SHADOWLING = /datum/antagonist/shadowling, // Yogs ROLE_VAMPIRE = /datum/antagonist/vampire, // Yogs ROLE_GANG = /datum/antagonist/gang, // Yogs ROLE_DARKSPAWN = /datum/antagonist/darkspawn, // Yogs diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 33ed99d829aa..3471e336dc88 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -81,6 +81,7 @@ #define SPAN_SINGING "singing" #define SPAN_CULTLARGE "cultlarge" #define SPAN_HELIUM "small" +#define SPAN_PROGENITOR "progenitor" //bitflag #defines for return value of the radio() proc. #define ITALICS (1<<0) diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 3baac161ae41..7de0686c86a5 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -121,6 +121,7 @@ #define span_phobia(str) ("" + str + "") #define span_prefix(str) ("" + str + "") #define span_priority(str) ("" + str + "") +#define span_progenitor(str) ("" + str + "") #define span_purple(str) ("" + str + "") #define span_radio(str) ("" + str + "") #define span_ratvar(str) ("" + str + "") @@ -147,7 +148,6 @@ #define span_servradio(str) ("" + str + "") #define span_sevtug(str) ("" + str + "") #define span_sevtug_small(str) ("" + str + "") -#define span_shadowling(str) ("" + str + "") #define span_singing(str) ("" + str + "") #define span_slime(str) ("" + str + "") #define span_small(str) ("" + str + "") diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 359711f46144..52563c7e3c15 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -54,8 +54,6 @@ #define STATUS_EFFECT_ANTIMAGIC /datum/status_effect/antimagic //grants antimagic (and reapplies if lost) for the duration -#define STATUS_EFFECT_CREEP /datum/status_effect/creep //Provides immunity to lightburn for darkspawn, does nothing to anyone else //Yogs - #define STATUS_EFFECT_TIME_DILATION /datum/status_effect/time_dilation //Provides immunity to slowdown and halves click-delay/action times //Yogs #define STATUS_EFFECT_DETERMINED /datum/status_effect/determined //currently in a combat high from being seriously wounded @@ -72,6 +70,8 @@ #define STATUS_EFFECT_HOLYLIGHT_HEALBOOST /datum/status_effect/holylight_healboost //short-term heal boost that grants the chaplain favor +#define STATUS_EFFECT_SPEEDBOOST /datum/status_effect/speedboost //applies a speed boost + ///////////// // DEBUFFS // ///////////// @@ -84,6 +84,8 @@ #define STATUS_EFFECT_PARALYZED /datum/status_effect/incapacitating/paralyzed //the affected is unable to move, use items, or stand up. +#define STATUS_EFFECT_DAZED /datum/status_effect/incapacitating/dazed //the affected is unable use items but can otherwise do everything else + #define STATUS_EFFECT_UNCONSCIOUS /datum/status_effect/incapacitating/unconscious //the affected is unconscious #define STATUS_EFFECT_SLEEPING /datum/status_effect/incapacitating/sleeping //the affected is asleep @@ -141,7 +143,9 @@ #define STATUS_EFFECT_STASIS /datum/status_effect/incapacitating/stasis //Halts biological functions like bleeding, chemical processing, blood regeneration, walking, etc -#define STATUS_EFFECT_BROKEN_WILL /datum/status_effect/broken_will //A 30-second sleep effect reduced by 1 second for every point of damage the target takes. //Yogs +#define STATUS_EFFECT_BROKEN_WILL /datum/status_effect/broken_will //A 30-second sleep effect, ends instantly upon taking enough damage in a single hit. //Yogs + +#define STATUS_EFFECT_DEVOURED_WILL /datum/status_effect/devoured_will //A 3 minute long status effect that prevents using devour will on the owner #define STATUS_EFFECT_AMOK /datum/status_effect/amok //Makes the target automatically strike out at adjecent non-heretics. @@ -157,6 +161,8 @@ #define STATUS_EFFECT_VOID_CHILL /datum/status_effect/void_chill +#define STATUS_EFFECT_TAUNT /datum/status_effect/taunt //forced movement towards the person applying + ///////////// // NEUTRAL // ///////////// diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 1ef42bdbb560..212d327e7ace 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -146,7 +146,8 @@ #define TRAIT_NOINTERACT "no_interact" //Not allowed to touch anything (even with TK) or use things in hand #define TRAIT_POWERHUNGRY "power_hungry" // uses electricity instead of food - +///Prevent species from changing while they have the trait +#define TRAIT_SPECIESLOCK "species_lock" #define TRAIT_NOSLIPICE "noslip_ice" #define TRAIT_NOSLIPWATER "noslip_water" #define TRAIT_NOSLIPALL "noslip_all" @@ -268,4 +269,19 @@ #define STATION_TRAIT_MOONSCORCH "station_trait_moonscorch" - +///Darkspawn traits +///lets darkspawns walk through weak light +#define TRAIT_DARKSPAWN_LIGHTRES "darkspawn_lightres" +///lets darkspawns walk through any light +#define TRAIT_DARKSPAWN_CREEP "darkspawn_creep" +///permanently reduces the lucidity gained from future succs +#define TRAIT_DARKSPAWN_DEVOURED "darkspawn_devoured" +///disable psi regeneration (make sure to remove it after some time) +#define TRAIT_DARKSPAWN_PSIBLOCK "darkspawn_psiblock" +///make aoe ally buff abilities also affect allied darkspawns +#define TRAIT_DARKSPAWN_BUFFALLIES "darkspawn_allybuff" +///revives the darkspawn if they're dead and in the dark +#define TRAIT_DARKSPAWN_UNDYING "darkspawn_undying" + +///reduces the cooldown of all used /datum/action/cooldown by 25% +#define TRAIT_FAST_COOLDOWNS "short_spell_cooldowns" diff --git a/code/__DEFINES/{yogs_defines}/antagonists.dm b/code/__DEFINES/{yogs_defines}/antagonists.dm index 6007a5e48b67..afd0f96a62cb 100644 --- a/code/__DEFINES/{yogs_defines}/antagonists.dm +++ b/code/__DEFINES/{yogs_defines}/antagonists.dm @@ -1,12 +1,10 @@ #define ANTAG_DATUM_VAMPIRE /datum/antagonist/vampire -#define ANTAG_DATUM_THRALL /datum/antagonist/thrall -#define ANTAG_DATUM_SLING /datum/antagonist/shadowling #define ANTAG_DATUM_DARKSPAWN /datum/antagonist/darkspawn -#define ANTAG_DATUM_VEIL /datum/antagonist/veil +#define ANTAG_DATUM_THRALL /datum/antagonist/thrall #define ANTAG_DATUM_INFILTRATOR /datum/antagonist/infiltrator #define ANTAG_DATUM_HIJACKEDAI /datum/antagonist/hijacked_ai #define ANTAG_DATUM_SINFULDEMON /datum/antagonist/sinfuldemon #define NOT_DOMINATING -1 #define MAX_LEADERS_GANG 3 -#define INITIAL_DOM_ATTEMPTS 3 \ No newline at end of file +#define INITIAL_DOM_ATTEMPTS 3 diff --git a/code/__DEFINES/{yogs_defines}/darkspawn.dm b/code/__DEFINES/{yogs_defines}/darkspawn.dm new file mode 100644 index 000000000000..b807f9f5a50a --- /dev/null +++ b/code/__DEFINES/{yogs_defines}/darkspawn.dm @@ -0,0 +1,31 @@ +//Defines specifically for the darkspawn game mode +#define DARKSPAWN_MUNDANE 0 +#define DARKSPAWN_DIVULGED 1 +#define DARKSPAWN_PROGENITOR 2 + +#define DARKSPAWN_SCOUT (1<<0) +#define DARKSPAWN_FIGHTER (1<<1) +#define DARKSPAWN_WARLOCK (1<<2) +#define ALL_DARKSPAWN_CLASSES (DARKSPAWN_SCOUT | DARKSPAWN_FIGHTER | DARKSPAWN_WARLOCK) + +/// things that you use and it fucks someone up +#define STORE_OFFENSE "offense" +///things that you use and it does something less straightforward +#define STORE_UTILITY "utility" +///things that always happen all the time +#define STORE_PASSIVE "passives" + +///used by the antag datum and class component to handle power purchasing +#define COMSIG_DARKSPAWN_PURCHASE_POWER "purchase_power" +///used by the psi_web to handle upgrading existing abilities +#define COMSIG_DARKSPAWN_UPGRADE_ABILITY "upgrade_ability" +///used by the psi_web to handle refunding ability upgrades +#define COMSIG_DARKSPAWN_DOWNGRADE_ABILITY "downgrade_ability" + +#define STAFF_UPGRADE_CONFUSION (1<<0) +#define STAFF_UPGRADE_HEAL (1<<1) +#define STAFF_UPGRADE_LIGHTEATER (1<<2) +#define STAFF_UPGRADE_EXTINGUISH (1<<3) +#define TENDRIL_UPGRADE_TWIN (1<<4) +#define TENDRIL_UPGRADE_CLEAVE (1<<5) +#define STAFF_UPGRADE_EFFICIENCY (1<<6) diff --git a/code/__DEFINES/{yogs_defines}/is_helpers.dm b/code/__DEFINES/{yogs_defines}/is_helpers.dm index f405ac2380b3..c7f9e2042008 100644 --- a/code/__DEFINES/{yogs_defines}/is_helpers.dm +++ b/code/__DEFINES/{yogs_defines}/is_helpers.dm @@ -1,10 +1,8 @@ -#define is_thrall(M) (istype(M, /mob/living) && M.mind?.has_antag_datum(/datum/antagonist/thrall)) -#define is_shadow(M) (istype(M, /mob/living) && M.mind?.has_antag_datum(/datum/antagonist/shadowling)) -#define is_shadow_or_thrall(M) (is_thrall(M) || is_shadow(M)) - #define isdarkspawn(A) (A?.mind?.has_antag_datum(/datum/antagonist/darkspawn)) -#define isveil(A) (A?.mind?.has_antag_datum(/datum/antagonist/veil)) -#define is_darkspawn_or_veil(A) (A.mind && isdarkspawn(A) || isveil(A)) +#define isthrall(A) (A?.mind?.has_antag_datum(/datum/antagonist/thrall)) +#define ispsyche(A) (A?.mind?.has_antag_datum(/datum/antagonist/psyche)) //non thrall teammates +#define is_darkspawn_or_thrall(A) (A.mind && isdarkspawn(A) || isthrall(A)) +#define is_team_darkspawn(A) ((A.mind && isdarkspawn(A) || isthrall(A)) || ispsyche(A) || (ROLE_DARKSPAWN in A.faction)) //also checks factions, so things can be immune to darkspawn spells without needing an antag datum #define is_clockcult(M) (istype(M, /mob/living) && M.mind && M.mind.has_antag_datum(/datum/antagonist/clockcult)) diff --git a/code/__DEFINES/{yogs_defines}/mobs.dm b/code/__DEFINES/{yogs_defines}/mobs.dm index ef4f0e0aaf6f..816023c8161a 100644 --- a/code/__DEFINES/{yogs_defines}/mobs.dm +++ b/code/__DEFINES/{yogs_defines}/mobs.dm @@ -7,10 +7,4 @@ #define REGEN_BLOOD_REQUIREMENT 40 // The amount of "blood" that a slimeperson consumes when regenerating a single limb. -#define DARKSPAWN_DIM_LIGHT 0.2 //light of this intensity suppresses healing and causes very slow burn damage -#define DARKSPAWN_BRIGHT_LIGHT 0.3 //light of this intensity causes rapid burn damage - - #define DARKSPAWN_DARK_HEAL 5 //how much damage of each type (with fire damage half rate) is healed in the dark -#define DARKSPAWN_LIGHT_BURN 7 //how much damage the darkspawn receives per tick in lit areas - #define MONKIFY_BLOOD_COEFFICIENT (BLOOD_VOLUME_MONKEY/BLOOD_VOLUME_GENERIC) //the ratio of monkey to human blood volume so a 100% blood volume monkey will not instantly die when you turn it into a human with ~58% blood volume diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index 441a88b36fe9..3ec17ced31da 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -1,5 +1,5 @@ -///Calculate the angle between two movables and the west|east coordinate -/proc/get_angle(atom/movable/start, atom/movable/end)//For beams. +///Calculate the angle between two atoms and the west|east coordinate +/proc/get_angle(atom/start, atom/end)//For beams. if(!start || !end) return 0 var/dy =(32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index 39957009ad00..9f80401ceff1 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -28,13 +28,15 @@ if(1) //Adj + Noun return "[pick(GLOB.adjectives)] [pick(GLOB.forge_name)]" if(2) //Vxtrin name - switch(rand(1,3)) + switch(rand(1,4)) if(1) //Name return pick(GLOB.preternis_names) if(2) //Caste + Name return "[pick(GLOB.preternis_class)]'[pick(GLOB.preternis_names)]" if(3) //Caste + Name + Home - return "[pick(GLOB.preternis_class)]'[pick(GLOB.preternis_names)]'[pick(GLOB.preternis_home)]" + return "[pick(GLOB.preternis_class)]'[pick(GLOB.preternis_names)]-[capitalize(pick(GLOB.preternis_home))]" + if(4) //Name + Home + return "[pick(GLOB.preternis_names)]-[capitalize(pick(GLOB.preternis_home))]" if(3) //Robotic name return ipc_name() @@ -55,6 +57,35 @@ /proc/ipc_name() return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" +/proc/nightmare_name() //they have one segment to the name because they're shells of what a vxtrin once was + switch(rand(1,3)) + if(1) //space and capital last name + return "[pick(GLOB.preternis_class)]" + if(2) //dash and lowercase last name + return "[pick(GLOB.preternis_names)]" + if(3) //apostrophe and lowercase last name + return "[pick(GLOB.preternis_home)]" + +/proc/darkspawn_name() //they have three segments to their name, but have lost sane ordering + var/list/order = list("name", "class", "home") + var/name = "" + for(var/i in 1 to 3) + var/selection = pick_n_take(order) + switch(selection) + if("name") + name += "[capitalize(pick(GLOB.preternis_names))]" + if("class") + name += "[capitalize(pick(GLOB.preternis_class))]" + if("home") + name += "[capitalize(pick(GLOB.preternis_home))]" + switch(i) + if(1) //apostrophe after the first name + name += "'" + if(2) //dash after the second + name = capitalize(name) + name += "-" + return name + GLOBAL_VAR(command_name) /proc/command_name() if (GLOB.command_name) diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index 6fea72a2d2ae..151d85825512 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -26,7 +26,6 @@ GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt") GLOBAL_LIST_INIT(constellations, world.file2list("strings/names/constellations.txt")) GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) -GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) GLOBAL_LIST_INIT(horror_names, world.file2list("strings/names/horror.txt")) GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm index 75b1b9a9f2c1..8fc71c221b80 100644 --- a/code/_globalvars/lists/poll_ignore.dm +++ b/code/_globalvars/lists/poll_ignore.dm @@ -22,6 +22,7 @@ #define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support" #define POLL_IGNORE_RAGINMAGES "raging_mages" #define POLL_IGNORE_HERETIC_MONSTER "heretic_monsters" +#define POLL_IGNORE_DARKSPAWN_PSYCHE "darkspawn_psyche" GLOBAL_LIST_INIT(poll_ignore_desc, list( diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 7da11d599b81..fae9bfd3cfa2 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -744,3 +744,8 @@ so as to remain in compliance with the most up-to-date laws." master_ref = null mob_viewer = null screen_loc = "" + +/atom/movable/screen/alert/psiblock + name = "Psiblock" + desc = "You have pushed your psionic powers beyond your capabilities and need time to recover." + icon_state = "shadow_mend" diff --git a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm index 63206afdaa93..9a25456bdb2d 100644 --- a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm +++ b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm @@ -86,9 +86,15 @@ // You aren't the source? don't change yourself return RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_increase)) + RegisterSignal(SSdcs, COMSIG_NARSIE_SUMMON_UPDATE, PROC_REF(narsie_modified)) if(GLOB.narsie_summon_count >= 1) narsie_start_midway(GLOB.narsie_effect_last_modified) // We assume we're on the start, so we can use this number + + RegisterSignal(SSdcs, COMSIG_DARKSPAWN_ASCENSION, PROC_REF(darkspawn_ascension)) + if(GLOB.sacrament_done) //so no runtimes prior to the round starting + darkspawn_ascension(0) + offset_increase(0, SSmapping.max_plane_offset) /atom/movable/screen/plane_master/parallax/proc/on_offset_increase(datum/source, old_offset, new_offset) @@ -160,6 +166,9 @@ /atom/movable/screen/plane_master/parallax/proc/narsie_unsummoned() animate(src, color = null, time = 8 SECONDS) +/atom/movable/screen/plane_master/parallax/proc/darkspawn_ascension(delay = 15 SECONDS) + animate(src, color = "#555555", time = delay) //greatly dimmed stars + /atom/movable/screen/plane_master/gravpulse name = "Gravpulse" documentation = "Ok so this one's fun. Basically, we want to be able to distort the game plane when a grav annom is around.\ diff --git a/code/datums/achievements/achievements.dm b/code/datums/achievements/achievements.dm index c78c87ff3c06..717fe8869d04 100644 --- a/code/datums/achievements/achievements.dm +++ b/code/datums/achievements/achievements.dm @@ -92,9 +92,9 @@ desc = "As a changeling, complete your objectives" id = 13 -/datum/achievement/greentext/slingascend +/datum/achievement/greentext/darkspawn name = "The Dark Shadow" - desc = "As a shadowling, ascend successfully" + desc = "As a darkspawn, ascend successfully" id = 14 /datum/achievement/death diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm index 27ae684aefb6..8de28855963f 100644 --- a/code/datums/actions/cooldown_action.dm +++ b/code/datums/actions/cooldown_action.dm @@ -167,10 +167,14 @@ /// Starts a cooldown time for this ability only /// Will use default cooldown time if an override is not specified /datum/action/cooldown/proc/StartCooldownSelf(override_cooldown_time) + var/cooldown_amount = cooldown_time + if(isnum(override_cooldown_time)) - next_use_time = world.time + override_cooldown_time - else - next_use_time = world.time + cooldown_time + cooldown_amount = override_cooldown_time + if(owner && HAS_TRAIT(owner, TRAIT_FAST_COOLDOWNS)) + cooldown_amount *= 0.66 + + next_use_time = world.time + cooldown_amount build_all_button_icons(UPDATE_BUTTON_STATUS) START_PROCESSING(SSfastprocess, src) diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm index a8920076b464..74b983815a7a 100644 --- a/code/datums/brain_damage/magic.dm +++ b/code/datums/brain_damage/magic.dm @@ -18,7 +18,7 @@ var/turf/T = owner.loc if(istype(T)) var/light_amount = T.get_lumcount() - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying + if(light_amount > SHADOW_SPECIES_DIM_LIGHT) //if there's enough light, start dying if(world.time > next_damage_warning) to_chat(owner, span_warning("The light burns you!")) next_damage_warning = world.time + 100 //Avoid spamming diff --git a/code/datums/components/internal_cam.dm b/code/datums/components/internal_cam.dm new file mode 100644 index 000000000000..b1120964cb28 --- /dev/null +++ b/code/datums/components/internal_cam.dm @@ -0,0 +1,49 @@ +///The static update delay on movement of the camera in a mob we use +#define INTERNAL_CAMERA_BUFFER (0.5 SECONDS) + +/** + * Internal camera component, basically a bodycam component, so it's not tied to an item + */ +/datum/component/internal_cam + ///The camera object used to gather information for the camera net + var/obj/machinery/camera/bodcam + +/datum/component/internal_cam/Initialize(list/networks = list("ss13")) + if (!parent || !isliving(parent)) + return COMPONENT_INCOMPATIBLE + + bodcam = new(parent) + bodcam.c_tag = parent + bodcam.name = parent + bodcam.network = networks + bodcam.setViewRange(10)//standard mob viewrange + bodcam.internal_light = FALSE + ADD_TRAIT(bodcam, TRAIT_EMPPROOF_SELF, type) + +/datum/component/internal_cam/RegisterWithParent() + bodcam.status = TRUE + update_cam() + bodcam.built_in = parent + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(update_cam)) + +/datum/component/internal_cam/UnregisterFromParent() + bodcam.status = FALSE + update_cam() + bodcam.built_in = null + UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) + +/datum/component/internal_cam/Destroy(force, silent) + . = ..() + QDEL_NULL(bodcam) + +///Changes the camera net used by the interal camera, currently only used for the darkspawn cameranet +/datum/component/internal_cam/proc/change_cameranet(var/datum/cameranet/newnet) + if(!newnet) + return + bodcam.change_camnet(newnet) + +///Updates the camera net, telling it that the camera has moved +/datum/component/internal_cam/proc/update_cam() + bodcam.camnet.updatePortableCamera(bodcam, INTERNAL_CAMERA_BUFFER) + + diff --git a/code/datums/components/light_eater.dm b/code/datums/components/light_eater.dm new file mode 100644 index 000000000000..0501af5e0cbb --- /dev/null +++ b/code/datums/components/light_eater.dm @@ -0,0 +1,66 @@ +/** + * Makes anything it attaches to capable of removing something's ability to produce light until it is destroyed + * + * The permanent version of this is [/datum/element/light_eater] + */ +/datum/component/light_eater + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Tracks things this light eater has eaten + var/list/eaten_lights + +/datum/component/light_eater/Initialize(list/_eaten) + if(!isatom(parent) && !istype(parent, /datum/reagent)) + return COMPONENT_INCOMPATIBLE + + . = ..() + if(!LAZYLEN(_eaten)) + return + + LAZYINITLIST(eaten_lights) + var/list/cached_eaten_lights = eaten_lights + for(var/morsel in _eaten) + LAZYSET(cached_eaten_lights, morsel, TRUE) + RegisterSignal(morsel, COMSIG_QDELETING, PROC_REF(deref_eaten_light)) + +/datum/component/light_eater/Destroy(force, silent) + for(var/light in eaten_lights) + var/atom/eaten_light = light + eaten_light.RemoveElement(/datum/element/light_eaten) + UnregisterSignal(eaten_light, COMSIG_QDELETING) + eaten_lights = null + return ..() + +/datum/component/light_eater/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_LIGHT_EATER_DEVOUR, PROC_REF(on_devour)) + parent.AddElement(/datum/element/light_eater) + +/datum/component/light_eater/UnregisterFromParent() + . = ..() + parent.RemoveElement(/datum/element/light_eater) + UnregisterSignal(parent, COMSIG_LIGHT_EATER_DEVOUR) + +/datum/component/light_eater/InheritComponent(datum/component/C, i_am_original, list/_eaten) + . = ..() + if(!LAZYLEN(_eaten)) + return + + LAZYINITLIST(eaten_lights) + var/list/cached_eaten_lights = eaten_lights + for(var/morsel in _eaten) + RegisterSignal(morsel, COMSIG_QDELETING, PROC_REF(deref_eaten_light)) + LAZYSET(cached_eaten_lights, morsel, TRUE) + +/// Handles storing references to lights eaten by the light eater. +/datum/component/light_eater/proc/on_devour(datum/source, atom/morsel) + SIGNAL_HANDLER + LAZYSET(eaten_lights, morsel, TRUE) + RegisterSignal(morsel, COMSIG_QDELETING, PROC_REF(deref_eaten_light)) + return NONE + +/// Handles dereferencing deleted lights. +/datum/component/light_eater/proc/deref_eaten_light(atom/eaten_light, force) + SIGNAL_HANDLER + UnregisterSignal(eaten_light, COMSIG_QDELETING) + LAZYREMOVE(eaten_lights, eaten_light) + return NONE diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm index b8ca41d931eb..04a54e44010e 100644 --- a/code/datums/components/mood.dm +++ b/code/datums/components/mood.dm @@ -135,7 +135,7 @@ if(absmood > highest_absolute_mood) highest_absolute_mood = absmood - if(!conflicting_moodies.len) //no special icons- go to the normal icon states + if(!conflicting_moodies.len && owner.mind) //no special icons- go to the normal icon states var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) //bloodsucker edit if(sanity < 25) screen_obj.icon_state = "mood_insane" diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm index 06a7fdb6f493..8af74e4b4d08 100644 --- a/code/datums/components/overlay_lighting.dm +++ b/code/datums/components/overlay_lighting.dm @@ -116,6 +116,7 @@ RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, PROC_REF(on_light_flags_change)) RegisterSignal(parent, COMSIG_ATOM_USED_IN_CRAFT, PROC_REF(on_parent_crafted)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_moved)) + RegisterSignal(parent, COMSIG_LIGHT_EATER_QUEUE, PROC_REF(on_light_eater)) RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_move)) var/atom/movable/movable_parent = parent if(movable_parent.light_flags & LIGHT_ATTACHED) @@ -140,6 +141,7 @@ COMSIG_ATOM_UPDATE_LIGHT_ON, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, COMSIG_ATOM_USED_IN_CRAFT, + COMSIG_LIGHT_EATER_QUEUE, )) if(directional) UnregisterSignal(parent, COMSIG_ATOM_DIR_CHANGE) @@ -213,15 +215,17 @@ parent_attached_to = new_parent_attached_to if(.) var/atom/movable/old_parent_attached_to = . - UnregisterSignal(old_parent_attached_to, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) + UnregisterSignal(old_parent_attached_to, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_LIGHT_EATER_QUEUE)) if(old_parent_attached_to == current_holder) RegisterSignal(old_parent_attached_to, COMSIG_QDELETING, PROC_REF(on_holder_qdel)) RegisterSignal(old_parent_attached_to, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved)) + RegisterSignal(old_parent_attached_to, COMSIG_LIGHT_EATER_QUEUE, PROC_REF(on_light_eater)) if(parent_attached_to) if(parent_attached_to == current_holder) - UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) + UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_LIGHT_EATER_QUEUE)) RegisterSignal(parent_attached_to, COMSIG_QDELETING, PROC_REF(on_parent_attached_to_qdel)) RegisterSignal(parent_attached_to, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_attached_to_moved)) + RegisterSignal(parent_attached_to, COMSIG_LIGHT_EATER_QUEUE, PROC_REF(on_light_eater)) check_holder() @@ -231,7 +235,7 @@ return if(current_holder) if(current_holder != parent && current_holder != parent_attached_to) - UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) + UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_LIGHT_EATER_QUEUE)) if(directional) UnregisterSignal(current_holder, COMSIG_ATOM_DIR_CHANGE) if(overlay_lighting_flags & LIGHTING_ON) @@ -242,6 +246,7 @@ return if(new_holder != parent && new_holder != parent_attached_to) RegisterSignal(new_holder, COMSIG_QDELETING, PROC_REF(on_holder_qdel)) + RegisterSignal(new_holder, COMSIG_LIGHT_EATER_QUEUE, PROC_REF(on_light_eater)) if(overlay_lighting_flags & LIGHTING_ON) RegisterSignal(new_holder, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved)) if(directional) @@ -341,7 +346,7 @@ turn_off() range = clamp(CEILING(new_range, 0.5), 1, 6) var/pixel_bounds = ((range - 1) * 64) + 32 - lumcount_range = CEILING(range, 1) + lumcount_range = CEILING(range - 1, 1) //yog change -- added a -1 to range -- makes the visual fit the lumcount range more closely for darkspawn gameplay if(current_holder && overlay_lighting_flags & LIGHTING_ON) current_holder.underlays -= visible_mask visible_mask.icon = light_overlays["[pixel_bounds]"] diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index efb42ce2ad03..89adb46af4b4 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -209,7 +209,7 @@ if(isturf(M.loc)) //else, there's considered to be no light var/turf/T = M.loc light_amount = min(1,T.get_lumcount()) - 0.5 - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) return power /datum/symptom/heal/darkness/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) diff --git a/code/datums/dna.dm b/code/datums/dna.dm index f389781297ce..bc7d33758e9f 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -483,6 +483,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) /mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + if(HAS_TRAIT(src, TRAIT_SPECIESLOCK))//can't swap species + return if(mrace && has_dna()) var/datum/species/new_race if(ispath(mrace)) diff --git a/code/datums/elements/frozen.dm b/code/datums/elements/frozen.dm index eb7da45e8f6f..fabcb5aedd7a 100644 --- a/code/datums/elements/frozen.dm +++ b/code/datums/elements/frozen.dm @@ -18,12 +18,14 @@ target_obj.add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) target_obj.alpha -= 25 + /* uncomment if we end up porting tg organ code if (isinternalorgan(target)) var/obj/item/organ/internal/organ = target organ.organ_flags |= ORGAN_FROZEN else if (isbodypart(target)) for(var/obj/item/organ/internal/organ in target_obj.contents) organ.organ_flags |= ORGAN_FROZEN + */ RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(target, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(shatter_on_throw)) @@ -38,12 +40,14 @@ obj_source.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) obj_source.alpha += 25 + /* uncomment if we end up porting tg organ code if (isinternalorgan(source)) var/obj/item/organ/internal/organ = source organ.organ_flags &= ~ORGAN_FROZEN else if (isbodypart(source)) for(var/obj/item/organ/internal/organ in obj_source.contents) organ.organ_flags &= ~ORGAN_FROZEN + */ return ..() diff --git a/code/datums/elements/light_eaten.dm b/code/datums/elements/light_eaten.dm new file mode 100644 index 000000000000..5fe3208cc256 --- /dev/null +++ b/code/datums/elements/light_eaten.dm @@ -0,0 +1,72 @@ +/** + * Makes anything that it attaches to incapable of producing light + */ +/datum/element/light_eaten + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY // Detach for turfs + +/datum/element/light_eaten/Attach(atom/target) + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + + . = ..() + var/atom/atom_target = target + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_POWER, PROC_REF(block_light_power)) + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_RANGE, PROC_REF(block_light_range)) + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_ON, PROC_REF(block_light_on)) + RegisterSignal(atom_target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + + /// Because the lighting system does not like movable lights getting set_light() called. + switch(atom_target.light_system) + if(STATIC_LIGHT) + target.set_light(0, 0, null) + else + target.set_light_power(0) + target.set_light_range(0) + target.set_light_on(FALSE) + +/datum/element/light_eaten/Detach(datum/source) + UnregisterSignal(source, list( + COMSIG_ATOM_SET_LIGHT_POWER, + COMSIG_ATOM_SET_LIGHT_RANGE, + COMSIG_ATOM_SET_LIGHT_ON, + COMSIG_ATOM_EXAMINE, + )) + return ..() + +/// Prevents the light power of the target atom from exceeding 0 or increasing. +/datum/element/light_eaten/proc/block_light_power(atom/eaten_light, new_power) + SIGNAL_HANDLER + if(new_power > 0) + return COMPONENT_BLOCK_LIGHT_UPDATE + if(new_power > eaten_light.light_power) + return COMPONENT_BLOCK_LIGHT_UPDATE + return NONE + +/// Prevents the light range of the target atom from exceeding 0 while the light power is greater than 0. +/datum/element/light_eaten/proc/block_light_range(atom/eaten_light, new_range) + SIGNAL_HANDLER + if(new_range <= 0) + return NONE + if(eaten_light.light_power <= 0) + return NONE + return COMPONENT_BLOCK_LIGHT_UPDATE + +/// Prevents the light from turning on while the light power is greater than 0. +/datum/element/light_eaten/proc/block_light_on(atom/eaten_light, new_on) + SIGNAL_HANDLER + if(!new_on) + return NONE + if(eaten_light.light_power <= 0) + return NONE + return COMPONENT_BLOCK_LIGHT_UPDATE + +/// Signal handler for light eater flavortext +/datum/element/light_eaten/proc/on_examine(atom/eaten_light, mob/examiner, list/examine_text) + SIGNAL_HANDLER + examine_text += span_warning("It's dark and empty...") + if(isliving(examiner) && prob(20)) + var/mob/living/target = examiner + examine_text += span_userdanger("You can feel something in [eaten_light.p_them()] gnash at your eyes!") + target.blind_eyes(5) + target.adjust_eye_blur(10) + return NONE diff --git a/code/datums/elements/light_eater.dm b/code/datums/elements/light_eater.dm new file mode 100644 index 000000000000..45be1034e31d --- /dev/null +++ b/code/datums/elements/light_eater.dm @@ -0,0 +1,177 @@ +/** + * Makes anything it attaches to capable of permanently removing something's ability to produce light. + * + * The temporary equivalent is [/datum/component/light_eater] + */ +/datum/element/light_eater + var/static/list/blacklisted_areas = typecacheof(list( + /turf/open/space, + /turf/open/lava, + )) + +/datum/element/light_eater/Attach(datum/target) + if(isatom(target)) + if(ismovable(target)) + if(ismachinery(target) || isstructure(target)) + RegisterSignal(target, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit)) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact)) + if(isitem(target)) + if(isgun(target)) + RegisterSignal(target, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit)) + RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_afterattack)) + RegisterSignal(target, COMSIG_ITEM_HIT_REACT, PROC_REF(on_hit_reaction)) + else if(isprojectile(target)) + RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_projectile_self_hit)) + else if(isprojectilespell(target)) + RegisterSignal(target, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit)) + else + return ELEMENT_INCOMPATIBLE + + return ..() + +/datum/element/light_eater/Detach(datum/source) + UnregisterSignal(source, list( + COMSIG_MOVABLE_IMPACT, + COMSIG_ITEM_AFTERATTACK, + COMSIG_ITEM_HIT_REACT, + COMSIG_PROJECTILE_ON_HIT, + )) + return ..() + +/** + * Makes the light eater consume all of the lights attached to the target atom. + * + * Arguments: + * - [food][/atom]: The atom to start the search for lights at. + * - [eater][/datum]: The light eater being used in this case. + */ +/datum/element/light_eater/proc/eat_lights(atom/food, datum/eater) + var/list/buffet = table_buffet(food) + if(!LAZYLEN(buffet)) + return 0 + + . = 0 + for(var/morsel in buffet) + . += devour(morsel, eater) + + if(!.) + return + + food.visible_message( + span_danger("Something dark in [eater] lashes out at [food] and [food.p_their()] light goes out in an instant!"), + span_userdanger("You feel something dark in [eater] lash out and gnaw through your light in an instant! It recedes just as fast, but you can feel that [eater.p_theyve()] left something hungry behind."), + span_danger("You feel a gnawing pulse eat at your sight.") + ) + +/** + * Aggregates a list of the light sources attached to the target atom. + * + * Arguments: + * - [comissary][/atom]: The origin node of all of the light sources to search through. + * - [devourer][/datum]: The light eater this element is attached to. Since the element is compatible with reagents this needs to be a datum. + */ +/datum/element/light_eater/proc/table_buffet(atom/commisary, datum/devourer) + . = list() + SEND_SIGNAL(commisary, COMSIG_LIGHT_EATER_QUEUE, ., devourer) + for(var/datum/light_source/morsel as anything in commisary.light_sources) + .[morsel.source_atom] = TRUE + +/** + * Consumes the light on the target, permanently rendering it incapable of producing light + * + * Arguments: + * - [morsel][/atom]: The light-producing thing we are eating + * - [eater][/datum]: The light eater eating the morsel. This is the datum that the element is attached to that started this chain. + */ +/datum/element/light_eater/proc/devour(atom/morsel, datum/eater) + if(is_type_in_typecache(morsel, blacklisted_areas)) + return FALSE + if(istransparentturf(morsel)) + return FALSE + if(morsel.light_power <= 0 || morsel.light_range <= 0 || !morsel.light_on) + return FALSE + if(SEND_SIGNAL(morsel, COMSIG_LIGHT_EATER_ACT, eater) & COMPONENT_BLOCK_LIGHT_EATER) + return FALSE // Either the light eater can't eat it or it had special behaviors. + + morsel.AddElement(/datum/element/light_eaten) + SEND_SIGNAL(src, COMSIG_LIGHT_EATER_DEVOUR, morsel) + return TRUE + +///////////////////// +// SIGNAL HANDLERS // +///////////////////// + +/** + * Called when a movable source is thrown and strikes a target + * + * Arugments: + * - [source][/atom/movable]: The movable atom that was thrown + * - [hit_atom][/atom]: The target atom that was struck by the source in flight + * - [thrownthing][/datum/thrownthing]: A datum containing the information for the throw + */ +/datum/element/light_eater/proc/on_throw_impact(atom/movable/source, atom/hit_atom, datum/thrownthing/thrownthing) + SIGNAL_HANDLER + eat_lights(hit_atom, source) + return NONE + +/** + * Called when a target is attacked with a source item + * + * Arguments: + * - [source][/obj/item]: The item what was used to strike the target + * - [target][/atom]: The atom being struck by the user with the source + * - [user][/mob/living]: The mob using the source to strike the target + * - proximity: Whether the strike was in melee range so you can't eat lights from cameras + */ +/datum/element/light_eater/proc/on_afterattack(obj/item/source, atom/target, mob/living/user, proximity) + SIGNAL_HANDLER + if(!proximity) + return NONE + eat_lights(target, source) + return COMPONENT_AFTERATTACK_PROCESSED_ITEM + +/** + * Called when a source object is used to block a thrown object, projectile, or attack + * + * Arguments: + * - [source][/obj/item]: The item what was used to block the target + * - [owner][/mob/living/carbon/human]: The mob that blocked the target with the source + * - [hitby][/atom/movable]: The movable that was blocked by the owner with the source + * - attack_text: The text tring that will be used to report that the target was blocked + * - final_block_chance: The probability of blocking the target with the source + * - attack_type: The type of attack that was blocked + */ +/datum/element/light_eater/proc/on_hit_reaction(obj/item/source, mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type, damage_type) + SIGNAL_HANDLER + if(prob(final_block_chance)) + eat_lights(hitby, source) + return NONE + +/** + * Called when a produced projectile strikes a target atom + * + * Arguments: + * - [source][/datum]: The thing that created the projectile + * - [firer][/atom/movable]: The movable atom that fired the projectile + * - [target][/atom]: The atom that was struck by the projectile + * - angle: The angle the target was struck at + */ +/datum/element/light_eater/proc/on_projectile_hit(datum/source, atom/movable/firer, atom/target, angle) + SIGNAL_HANDLER + eat_lights(target, source) + return NONE + +/** + * Called when a source projectile strikes a target atom + * + * Arguments: + * - [source][/obj/projectile]: The projectile striking the target atom + * - [firer][/atom/movable]: The movable atom that fired the projectile + * - [target][/atom]: The atom that was struck by the projectile + * - angle: The angle the target was struck at + * - hit_limb: The limb that was hit, if the target was a carbon + */ +/datum/element/light_eater/proc/on_projectile_self_hit(obj/projectile/source, atom/movable/firer, atom/target, angle, hit_limb) + SIGNAL_HANDLER + eat_lights(target, source) + return NONE diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 266f5c82a8c5..20800284facb 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -201,7 +201,6 @@ var/glow_power = 3.5 var/glow_range = 2.5 var/glow_color - var/current_nullify_timer // For veil yogstation\code\modules\antagonists\shadowling\shadowling_abilities.dm var/obj/effect/dummy/lighting_obj/moblight/glow @@ -212,17 +211,28 @@ glow_color = owner.dna.features["mcolor"] glow = owner.mob_light() modify() + RegisterSignal(glow, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) // Override modify here without a parent call, because we don't actually give an action. /datum/mutation/human/glow/modify() if(!glow) return glow.set_light_range_power_color(glow_range * GET_MUTATION_POWER(src), glow_power, glow_color) + glow.set_light_on(TRUE) + +/datum/mutation/human/glow/proc/on_light_eater(mob/living/carbon/human/source, datum/light_eater) + SIGNAL_HANDLER + if(!glow) + return + glow.set_light_on(FALSE) + addtimer(CALLBACK(src, PROC_REF(modify)), 20 SECONDS * GET_MUTATION_SYNCHRONIZER(src), TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds (reduced by sychronizer) + return COMPONENT_BLOCK_LIGHT_EATER /datum/mutation/human/glow/on_losing(mob/living/carbon/human/owner) . = ..() if(.) return + UnregisterSignal(glow, COMSIG_LIGHT_EATER_ACT) QDEL_NULL(glow) /datum/mutation/human/glow/anti diff --git a/code/datums/mutations/radiantburst.dm b/code/datums/mutations/radiantburst.dm index 37586972bb0e..168f1df464ca 100644 --- a/code/datums/mutations/radiantburst.dm +++ b/code/datums/mutations/radiantburst.dm @@ -21,9 +21,8 @@ if(GET_MUTATION_SYNCHRONIZER(src) < 1) to_modify.safe = TRUE //don't blind yourself - to_modify.cooldown_time *= GET_MUTATION_ENERGY(src) //blind more often if(GET_MUTATION_POWER(src) > 1) - to_modify.aoe_radius += 2 //bigger blind + to_modify.strong = TRUE //damages darkspawns more and bypasses blindness check /datum/action/cooldown/spell/aoe/radiantburst name = "Radiant Burst" @@ -32,19 +31,20 @@ button_icon_state = "Kindle" active_icon_state = "Kindle" base_icon_state = "Kindle" - aoe_radius = 3 + aoe_radius = 5 antimagic_flags = NONE spell_requirements = NONE school = SCHOOL_EVOCATION cooldown_time = 15 SECONDS sound = 'sound/magic/blind.ogg' var/safe = FALSE + var/strong = FALSE /datum/action/cooldown/spell/aoe/radiantburst/cast(atom/cast_on) . = ..() if(!safe && iscarbon(owner)) var/mob/living/carbon/dummy = owner - dummy.flash_act(3) //it's INSIDE you, it's gonna blind + dummy.flash_act(3, strong) //it's INSIDE you, it's gonna blind owner.visible_message(span_warning("[owner] releases a blinding light from within themselves."), span_notice("You release all the light within you.")) owner.color = LIGHT_COLOR_HOLY_MAGIC animate(owner, 0.5 SECONDS, color = null) @@ -52,4 +52,8 @@ /datum/action/cooldown/spell/aoe/radiantburst/cast_on_thing_in_aoe(atom/victim, atom/caster) if(ishuman(victim)) var/mob/living/carbon/human/hurt = victim - hurt.flash_act()//only strength of 1, so sunglasses protect from it + hurt.flash_act(1, strong)//only strength of 1, so sunglasses protect from it unless strengthened + if(isdarkspawn(hurt)) + hurt.adjustFireLoss(strong? (-30) : (-20)) + to_chat(hurt, span_userdanger("The blinding light sears you!")) + playsound(hurt, 'sound/weapons/sear.ogg', 75, TRUE) diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm index 9684ae51d8b2..bd1641fe2c23 100644 --- a/code/datums/saymode.dm +++ b/code/datums/saymode.dm @@ -148,19 +148,13 @@ var/datum/mind = user.mind if(!mind) return TRUE - if(is_darkspawn_or_veil(user)) + if(is_team_darkspawn(user)) user.log_talk(message, LOG_SAY, tag="darkspawn") var/msg = span_velvet("\[Mindlink\] [user.real_name]: \"[message]\"") for(var/mob/M in GLOB.player_list) if(M in GLOB.dead_mob_list) var/link = FOLLOW_LINK(M, user) to_chat(M, "[link] [msg]") - else if(is_darkspawn_or_veil(M)) - var/turf/receiver = get_turf(M) - var/turf/sender = get_turf(user) - if(receiver.z != sender.z) - if(prob(25)) - to_chat(M, span_warning("Your mindlink trembles with words, but they are too far to make out...")) - continue + else if(is_team_darkspawn(M)) to_chat(M, msg) return FALSE //yogs end diff --git a/code/datums/status_effects/buffs/buffs.dm b/code/datums/status_effects/buffs/buffs.dm index 9e080c8b88fe..4ddc7684180d 100644 --- a/code/datums/status_effects/buffs/buffs.dm +++ b/code/datums/status_effects/buffs/buffs.dm @@ -545,36 +545,6 @@ REMOVE_TRAIT(owner, TRAIT_ANTIMAGIC, MAGIC_TRAIT) owner.visible_message(span_warning("[owner]'s dull aura fades away...")) -/datum/status_effect/creep //allows darkspawn to move through lights without lightburn damage //yogs start: darkspawn - id = "creep" - duration = -1 - alert_type = /atom/movable/screen/alert/status_effect/creep - examine_text = span_warning("SUBJECTPRONOUN is surrounded by velvety, gently-waving black shadows!") - var/datum/antagonist/darkspawn/darkspawn - -/datum/status_effect/creep/on_creation(mob/living/owner, datum/antagonist/darkspawn) - . = ..() - if(!.) - return - src.darkspawn = darkspawn - -/datum/status_effect/creep/tick() - if(!darkspawn) - qdel(src) - return - if(!darkspawn.has_psi(1)) //ticks 5 times per second, 5 Psi lost per second - to_chat(owner, span_warning("Without the Psi to maintain it, your protective aura vanishes!")) - qdel(src) - return - darkspawn.use_psi(1) - -/atom/movable/screen/alert/status_effect/creep - name = "Creep" - desc = "You are immune to lightburn. Drains 1 Psi per second." - icon = 'yogstation/icons/mob/actions/actions_darkspawn.dmi' - icon_state = "creep" - - /datum/status_effect/time_dilation //used by darkspawn; greatly increases action times etc id = "time_dilation" duration = 600 diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 6089eb84b98a..dea517a568d0 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -83,20 +83,18 @@ owner.remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED, TRAIT_HANDS_BLOCKED), TRAIT_STATUS_EFFECT(id)) return ..() -//INCAPACITATED +//DAZED /// This status effect represents anything that leaves a character unable to perform basic tasks (interrupting do-afters, for example), but doesn't incapacitate them further than that (no stuns etc..) -/datum/status_effect/incapacitating/incapacitated - id = "incapacitated" +/datum/status_effect/incapacitating/dazed + id = "dazed" -// What happens when you get the incapacitated status. You get TRAIT_INCAPACITATED added to you for the duration of the status effect. -/datum/status_effect/incapacitating/incapacitated/on_apply() +/datum/status_effect/incapacitating/dazed/on_apply() . = ..() if(!.) return ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) -// When the status effect runs out, your TRAIT_INCAPACITATED is removed. -/datum/status_effect/incapacitating/incapacitated/on_remove() +/datum/status_effect/incapacitating/dazed/on_remove() REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) return ..() @@ -741,8 +739,8 @@ deltimer(timerid) /datum/status_effect/progenitor_curse - duration = 200 - tick_interval = 5 + tick_interval = 1.5 SECONDS //how often a hand is shot + duration = 30 SECONDS /datum/status_effect/progenitor_curse/tick() if(owner.stat == DEAD) @@ -755,7 +753,7 @@ /datum/status_effect/progenitor_curse/proc/grasp(turf/spawn_turf) set waitfor = FALSE new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir) - playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, 1, -1) + playsound(spawn_turf, pick('sound/effects/curse1.ogg','sound/effects/curse2.ogg','sound/effects/curse3.ogg'), 80, 1, -1) var/obj/projectile/curse_hand/progenitor/C = new (spawn_turf) C.preparePixelProjectile(owner, spawn_turf) C.fire() @@ -1040,34 +1038,62 @@ msg_stage++ -//Broken Will: Applied by Devour Will, and functions similarly to Kindle. Induces sleep for 30 seconds, going down by 1 second for every point of damage the target takes. //yogs start: darkspawn +//Broken Will: Applied by Devour Will, and functions similarly to Kindle. Induces sleep for 30 seconds, broken instantly by taking more than a certain amount of damage. //yogs start: darkspawn /datum/status_effect/broken_will id = "broken_will" status_type = STATUS_EFFECT_UNIQUE tick_interval = 5 - duration = 300 + duration = 30 SECONDS examine_text = span_deadsay("SUBJECTPRONOUN is in a deep, deathlike sleep, with no signs of awareness to anything around them.") alert_type = /atom/movable/screen/alert/status_effect/broken_will - var/old_health + ///how much damage taken in one hit will wake the holder + var/wake_threshold = 5 + +/datum/status_effect/broken_will/on_apply() + if(owner) + RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) + ADD_TRAIT(owner, TRAIT_NOCRITDAMAGE, type) + return ..() + +/datum/status_effect/broken_will/on_remove() + if(owner) + UnregisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE) + REMOVE_TRAIT(owner, TRAIT_NOCRITDAMAGE, type) + owner.SetUnconscious(0) //wake them up + return ..() /datum/status_effect/broken_will/tick() + if(is_darkspawn_or_thrall(owner) || owner.stat == DEAD) + qdel(src) + return owner.Unconscious(15) - if(!old_health) - old_health = owner.health - var/health_difference = old_health - owner.health - if(!health_difference) + if(owner.health <= HEALTH_THRESHOLD_CRIT) + owner.heal_ordered_damage(3, list(BURN, BRUTE), BODYPART_ANY) //so if they're left to bleed out, they'll survive, probably? + if(prob(10)) + to_chat(owner, span_velvet("sleep... bliss...")) //give a notice that they're probably healing because of the sleep + +/datum/status_effect/broken_will/proc/on_take_damage(datum/source, damage, damagetype) + if(damage < wake_threshold) return - owner.visible_message(span_warning("[owner] jerks in their sleep as they're harmed!")) - to_chat(owner, span_boldannounce("Something hits you, pulling you towards wakefulness!")) - health_difference *= 10 //1 point of damage = 1 second = 10 deciseconds - duration -= health_difference - old_health = owner.health + owner.visible_message(span_warning("[owner] is jolted awake by the impact!") , span_boldannounce("Something hits you, pulling you towards wakefulness!")) + ADD_TRAIT(owner, TRAIT_NOSOFTCRIT, type) + addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_NOSOFTCRIT, type), 20 SECONDS) + ADD_TRAIT(owner, TRAIT_RESISTDAMAGESLOWDOWN, type) + addtimer(TRAIT_CALLBACK_REMOVE(owner, TRAIT_RESISTDAMAGESLOWDOWN, type), 20 SECONDS) + qdel(src) /atom/movable/screen/alert/status_effect/broken_will name = "Broken Will" desc = "..." icon_state = "broken_will" - alerttooltipstyle = "alien" //yogs end + alerttooltipstyle = "alien" + +//used to prevent the use of devour will on the target +/datum/status_effect/devoured_will + id = "devoured_will" + status_type = STATUS_EFFECT_UNIQUE + duration = 3 MINUTES + alert_type = null /datum/status_effect/eldritch duration = 15 SECONDS @@ -1690,3 +1716,29 @@ /datum/status_effect/eldritch/knock/on_remove() REMOVE_TRAIT(owner, TRAIT_ALWAYS_NO_ACCESS, STATUS_EFFECT_TRAIT) return ..() + +/datum/status_effect/taunt + id = "taunt" + alert_type = /atom/movable/screen/alert/status_effect/star_mark + duration = 5 SECONDS + tick_interval = CLICK_CD_MELEE + var/mob/living/taunter + +/datum/status_effect/taunt/on_creation(mob/living/new_owner, mob/living/taunter) + src.taunter = taunter + return ..() + +/datum/status_effect/taunt/on_apply() + . = ..() + if(HAS_TRAIT(owner, TRAIT_STUNIMMUNE)) + return FALSE + if(!taunter) + return FALSE + owner.SetImmobilized(5 SECONDS) + +/datum/status_effect/taunt/tick(delta_time, times_fired) + step_towards(owner, taunter) + owner.SetImmobilized(5 SECONDS) + +/datum/status_effect/taunt/on_remove() + owner.SetImmobilized(0) diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 78d592e69a37..0708fd8321aa 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -145,7 +145,6 @@ /datum/status_effect/tagalong //applied to darkspawns while they accompany someone //yogs start: darkspawn id = "tagalong" - duration = 3000 tick_interval = 1 //as fast as possible alert_type = /atom/movable/screen/alert/status_effect/tagalong var/mob/living/shadowing @@ -172,18 +171,14 @@ qdel(src) return cached_location = get_turf(shadowing) - if(cached_location.get_lumcount() < DARKSPAWN_DIM_LIGHT) + if(cached_location.get_lumcount() < SHADOW_SPECIES_DIM_LIGHT) owner.forceMove(cached_location) shadowing.visible_message(span_warning("[owner] suddenly appears from the dark!")) to_chat(owner, span_warning("You are forced out of [shadowing]'s shadow!")) - owner.Knockdown(3 SECONDS) qdel(src) var/obj/item/I = owner.get_active_held_item() if(I) to_chat(owner, span_userdanger("Equipping an item forces you out!")) - if(istype(I, /obj/item/dark_bead)) - to_chat(owner, span_userdanger("[I] crackles with feedback, briefly disorienting you!")) - owner.Stun(5) //short delay so they can't click as soon as they're out qdel(src) /atom/movable/screen/alert/status_effect/tagalong diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index a7fbb26d6382..60112254cb50 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -188,7 +188,7 @@ /datum/quirk/nyctophobia/on_process() var/mob/living/carbon/human/H = quirk_holder - if((H.dna.species.id in list("shadow", "nightmare", "darkspawn")) || (H.mind && (H.mind.has_antag_datum(ANTAG_DATUM_THRALL) || H.mind.has_antag_datum(ANTAG_DATUM_SLING) || H.mind.has_antag_datum(ANTAG_DATUM_DARKSPAWN) || H.mind.has_antag_datum(ANTAG_DATUM_VEIL)))) //yogs - thrall & sling check + if((H.dna.species.id in list("shadow", "nightmare", "darkspawn")) || is_darkspawn_or_thrall(H)) return //we're tied with the dark, so we don't get scared of it; don't cleanse outright to avoid cheese var/turf/T = get_turf(quirk_holder) var/lums = T.get_lumcount() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 07d907f2f6d5..ebc29e8ef3f0 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -483,7 +483,7 @@ for(var/X in GLOB.xeno_spawn) var/turf/T = X var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) spawn_locs += T if(!spawn_locs.len) return FALSE @@ -640,7 +640,7 @@ for(var/X in GLOB.xeno_spawn) var/turf/T = X var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) spawn_locs += T if(!spawn_locs.len) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index d4a9188b23df..44a96ac1a64d 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -840,50 +840,6 @@ spawn_meteors(ramp_up_final, wavetype) -////////////////////////////////////////////// -// // -// SHADOWLINGS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/shadowling - name = "Shadowling" - antag_flag = ROLE_SHADOWLING - antag_datum = /datum/antagonist/shadowling - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Research Director", "Chief Engineer", "Chief Medical Officer", "Brig Physician") - restricted_roles = list("Cyborg", "AI", "Synthetic") - required_candidates = 3 - weight = 3 - cost = 30 - requirements = list(90,80,80,70,60,40,30,30,20,10) - flags = HIGH_IMPACT_RULESET - minimum_players = 30 - antag_cap = 3 - minimum_players = 32 - -/datum/dynamic_ruleset/roundstart/shadowling/ready(population, forced = FALSE) - required_candidates = get_antag_cap(population) - . = ..() - -/datum/dynamic_ruleset/roundstart/shadowling/pre_execute(population) /// DON'T BREAK PLEASE - Xoxeyos 3/13/2021 - . = ..() - var/shadowlings = get_antag_cap(population) - for(var/shadowling_number = 1 to shadowlings) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.special_role = ROLE_SHADOWLING - M.mind.restricted_roles = restricted_roles - log_game("[key_name(M)] has been selected as a Shadowling") - return TRUE - -/datum/dynamic_ruleset/roundstart/shadowling/proc/check_shadow_death() - return FALSE - -//Xoxeyos Here, I've added this Shadowling shit in, I have no idea what I'm doing, if there were mistakes made -//feel free to make changes, if it crashes, or just doesn't give anyone roles. - ////////////////////////////////////////////// // // // VAMPIRE // @@ -995,24 +951,25 @@ // // ////////////////////////////////////////////// -/datum/dynamic_ruleset/roundstart/darkspawn +/datum/dynamic_ruleset/roundstart/darkspawn //i don't entirely know how dynamic works, i hope i've set this up correctly name = "Darkspawn" antag_flag = ROLE_DARKSPAWN - antag_datum = /datum/antagonist/darkspawn/ - minimum_required_age = 20 + antag_datum = /datum/antagonist/darkspawn protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Research Director", "Chief Engineer", "Chief Medical Officer", "Brig Physician") restricted_roles = list("AI", "Cyborg", "Synthetic") - required_candidates = 3 - weight = 3 + minimum_players = 25 + required_candidates = 2 + minimum_required_age = 24 //reasonably complicated antag + antag_cap = 4 + weight = 4 + flags = HIGH_IMPACT_RULESET cost = 20 - scaling_cost = 20 - antag_cap = 3 requirements = list(80,75,70,65,50,30,30,30,25,20) - minimum_players = 32 + var/datum/team/darkspawn/team /datum/dynamic_ruleset/roundstart/darkspawn/pre_execute(population) - . = ..() - var/num_darkspawn = get_antag_cap(population) * (scaled_times + 1) + var/num_darkspawn = clamp(round((population+5)/15), required_enemies, get_antag_cap(population)) + for (var/i = 1 to num_darkspawn) if(candidates.len <= 0) break @@ -1020,7 +977,12 @@ assigned += M.mind M.mind.special_role = ROLE_DARKSPAWN M.mind.restricted_roles = restricted_roles - log_game("[key_name(M)] has been selected as a Darkspawn") + log_game("[key_name(M)] (ckey) has been selected as a Darkspawn") + + team = new + team.update_objectives() + GLOB.thrallnet.name = "Thrall net" + return TRUE ////////////////////////////////////////////// diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index c3ff23b9892a..a14463ffa251 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -140,6 +140,16 @@ replacementmode.make_antag_chance(character) return +//replace someone that's job banned +/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 + to_chat(M, "You have been expelled from your body! Appeal your job ban if you want to avoid this in the future!") + M.ghostize(0) + if(candidates.len) + theghost = pick(candidates) + message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbaned player.") + M.key = theghost.key /// Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. /datum/game_mode/proc/convert_roundtype() diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index e3092c3429ad..f6eb65ae0d1d 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -22,6 +22,8 @@ var/status = TRUE var/start_active = FALSE //If it ignores the random chance to start broken on round start var/invuln = null + ///Boolean, if this camera uses special icon states rather than the default + var/special_camera = FALSE var/obj/item/camera_bug/bug = null var/obj/item/radio/alertradio = null var/obj/structure/camera_assembly/assembly = null @@ -29,6 +31,8 @@ //OTHER + var/datum/cameranet/camnet + var/view_range = 7 var/short_range = 2 @@ -78,8 +82,9 @@ else assembly = new(src) assembly.state = 4 //STATE_FINISHED - GLOB.cameranet.cameras += src - GLOB.cameranet.addCamera(src) + camnet = GLOB.cameranet + camnet.cameras += src + camnet.addCamera(src) if (isturf(loc)) myarea = get_area(src) LAZYADD(myarea.cameras, src) @@ -102,10 +107,19 @@ network -= i network += "[port.shuttle_id]_[i]" +/obj/machinery/camera/proc/change_camnet(datum/cameranet/newnet) + if(newnet && istype(newnet)) + camnet.cameras -= src + camnet.removeCamera(src) + camnet = newnet + camnet.cameras += src + camnet.addCamera(src) + /obj/machinery/camera/Destroy() if(can_use()) toggle_cam(null, 0) //kick anyone viewing out and remove from the camera chunks - GLOB.cameranet.cameras -= src + camnet.cameras -= src + camnet = null if(isarea(myarea)) LAZYREMOVE(myarea.cameras, src) @@ -153,7 +167,7 @@ update_appearance() var/list/previous_network = network network = list() - GLOB.cameranet.removeCamera(src) + camnet.removeCamera(src) stat |= EMPED set_light(0) emped = emped+1 //Increase the number of consecutive EMP's @@ -167,7 +181,7 @@ stat &= ~EMPED update_appearance() if(can_use()) - GLOB.cameranet.addCamera(src) + camnet.addCamera(src) emped = 0 //Resets the consecutive EMP count addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), severity SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) for(var/i in GLOB.player_list) @@ -184,7 +198,7 @@ /obj/machinery/camera/proc/setViewRange(num = 7) src.view_range = num - GLOB.cameranet.updateVisibility(src, 0) + camnet.updateVisibility(src, 0) /obj/machinery/camera/proc/shock(mob/living/user) if(!istype(user)) @@ -359,6 +373,8 @@ /obj/machinery/camera/update_icon_state() //TO-DO: Make panel open states, xray camera, and indicator lights overlays instead. . = ..() + if(special_camera) + return var/xray_module if(isXRay(TRUE)) xray_module = "xray" @@ -372,7 +388,7 @@ /obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE) status = !status if(can_use()) - GLOB.cameranet.addCamera(src) + camnet.addCamera(src) if (isturf(loc)) myarea = get_area(src) LAZYADD(myarea.cameras, src) @@ -380,12 +396,12 @@ myarea = null else set_light(0) - GLOB.cameranet.removeCamera(src) + camnet.removeCamera(src) if (isarea(myarea)) LAZYREMOVE(myarea.cameras, src) // We are not guarenteed that the camera will be on a turf. account for that var/turf/our_turf = get_turf(src) - GLOB.cameranet.updateChunk(our_turf.x, our_turf.y, our_turf.z) + camnet.updateChunk(our_turf.x, our_turf.y, our_turf.z) var/change_msg = "deactivates" if(status) change_msg = "reactivates" diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm index 9a0c3e44dc81..6edf09c723da 100644 --- a/code/game/machinery/computer/_computer.dm +++ b/code/game/machinery/computer/_computer.dm @@ -10,6 +10,8 @@ integrity_failure = 100 armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 40, ACID = 20) clicksound = "keyboard" + /// Boolean, Doesn't have standard overlays or appearance, but has the same effects + var/special_appearance = FALSE /// How bright we are when turned on. var/brightness_on = 1 /// Icon_state of the keyboard overlay. @@ -45,7 +47,7 @@ return TRUE /obj/machinery/computer/ratvar_act() - if(!clockwork) + if(!clockwork && !special_appearance) clockwork = TRUE icon_screen = "ratvar[rand(1, 4)]" icon_keyboard = "ratvar_key[rand(1, 6)]" @@ -62,6 +64,8 @@ /obj/machinery/computer/update_appearance(updates) . = ..() + if(special_appearance) //always centered + return //Prevents fuckery with subtypes that are meant to be pixel shifted or map shifted shit if(pixel_x == 0 && pixel_y == 0) // this bit of code makes the computer hug the wall its next to @@ -91,6 +95,8 @@ /obj/machinery/computer/update_overlays() . = ..() + if(special_appearance) + return if(icon_keyboard) if(keyboard_change_icon && (stat & NOPOWER)) . += "[icon_keyboard]_off" diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index b0339d21b12e..0bfee5b7bf8f 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -21,6 +21,8 @@ /// List of all actions to give to a user when they're well, granted actions var/list/actions = list() + var/datum/cameranet/camnet //the net it's looking at + ///Should we supress any view changes? var/should_supress_view_changes = TRUE @@ -49,6 +51,8 @@ //Camera action button to move down a Z level if(move_down_action) actions += new move_down_action(src) + + camnet = GLOB.cameranet //the default cameranet /obj/machinery/computer/camera_advanced/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) for(var/i in networks) @@ -64,6 +68,7 @@ /obj/machinery/computer/camera_advanced/proc/CreateEye() eyeobj = new() eyeobj.origin = src + eyeobj.networks = networks /obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/user) for(var/datum/action/to_grant as anything in actions) @@ -135,10 +140,10 @@ var/camera_location var/turf/myturf = get_turf(src) if(eyeobj.use_static != FALSE) - if((!length(z_lock) || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf)) + if((!length(z_lock) || (myturf.z in z_lock)) && camnet.checkTurfVis(myturf)) camera_location = myturf else - for(var/obj/machinery/camera/C as anything in GLOB.cameranet.cameras) + for(var/obj/machinery/camera/C as anything in camnet.cameras) if(!C.can_use() || length(z_lock) && !(C.z in z_lock)) continue var/list/network_overlap = networks & C.network @@ -186,7 +191,7 @@ var/cooldown = 0 var/acceleration = 1 var/mob/living/eye_user = null - var/obj/machinery/origin + var/obj/machinery/computer/camera_advanced/origin var/eye_initialized = 0 var/visible_icon = 0 var/image/user_image = null @@ -219,7 +224,7 @@ update_ai_detect_hud() if(use_static) - GLOB.cameranet.visibility(src, GetViewerClient(), null, use_static) + origin.camnet.visibility(src, GetViewerClient(), null, use_static) if(visible_icon) if(eye_user.client) @@ -271,7 +276,7 @@ var/list/L = list() - for (var/obj/machinery/camera/cam as anything in GLOB.cameranet.cameras) + 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) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 86efd3423d60..c0fc583bbd65 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1221,12 +1221,10 @@ if(T.twin) if(!do_after(user, rand(4, 6), src)) T.darkspawn.use_psi(30) - qdel(T) return else if(!do_after(user, rand(8, 10), src)) T.darkspawn.use_psi(30) - qdel(T) return playsound(src, 'yogstation/sound/magic/pass_smash_door.ogg', 50, TRUE) take_damage(max_integrity / rand(8, 15)) @@ -1234,7 +1232,8 @@ ex_act(EXPLODE_DEVASTATE) user.visible_message(span_boldwarning("[user] slams down [src]!"), "KLAJ.") T.darkspawn.use_psi(30) - qdel(T) + else + return ..() else return ..() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 77988134caf9..298a6ab2449a 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -461,6 +461,8 @@ /obj/machinery/door/proc/update_freelook_sight() if(!glass && GLOB.cameranet) GLOB.cameranet.updateVisibility(src, 0) + if(!glass && GLOB.thrallnet) + GLOB.thrallnet.updateVisibility(src, 0) /obj/machinery/door/BlockThermalConductivity() // All non-glass airlocks block heat, this is intended. if(heat_proof && density) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 0c998e467b27..9d9b5449e494 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -171,7 +171,16 @@ diag_hud_set_mechhealth() diag_hud_set_mechcell() diag_hud_set_mechstat() - + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + +/// Special light eater handling +/obj/mecha/proc/on_light_eater(obj/vehicle/sealed/source, datum/light_eater) + SIGNAL_HANDLER + visible_message(span_danger("[src]'s lights burn out!")) + set_light_on(FALSE) + lights_action.Remove(occupant) + return COMPONENT_BLOCK_LIGHT_EATER + /obj/mecha/update_icon_state() . = ..() if (silicon_pilot && silicon_icon_state) diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm index 3839457dc634..5cfcebe08b0a 100644 --- a/code/game/objects/effects/effect_system/effect_system.dm +++ b/code/game/objects/effects/effect_system/effect_system.dm @@ -15,9 +15,11 @@ would spawn and follow the beaker, even if it is carried or thrown. /obj/effect/particle_effect/Initialize(mapload) . = ..() GLOB.cameranet.updateVisibility(src) + GLOB.thrallnet.updateVisibility(src) /obj/effect/particle_effect/Destroy() GLOB.cameranet.updateVisibility(src) + GLOB.thrallnet.updateVisibility(src) return ..() /obj/effect/particle_effect/newtonian_move() // Prevents effects from getting registered for SSspacedrift diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index f02dfc5396a7..a8d5d9f300d8 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -465,3 +465,10 @@ /datum/effect_system/fluid_spread/smoke/chem/quick effect_type = /obj/effect/particle_effect/fluid/smoke/chem/quick + +/datum/effect_system/fluid_spread/smoke/chem/darkspawn + effect_type = /obj/effect/particle_effect/fluid/smoke/chem/darkspawn + +/obj/effect/particle_effect/fluid/smoke/chem/darkspawn + lifetime = 10 SECONDS + opacity = FALSE diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index 58f9a8c51c59..dc0c422877a7 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -115,6 +115,13 @@ . = ..() if(!ismob(loc)) return INITIALIZE_HINT_QDEL + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + +//always block light eater if it tries to apply directly to this +//moblights should be handled in special ways by everything that grants them +/obj/effect/dummy/lighting_obj/moblight/proc/on_light_eater(atom/source, datum/light_eater) + SIGNAL_HANDLER + return COMPONENT_BLOCK_LIGHT_EATER /obj/effect/dummy/lighting_obj/moblight/species name = "species lighting" diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm index d3f3f94d52f2..0817a2639bcb 100644 --- a/code/game/objects/items/cardboard_cutouts.dm +++ b/code/game/objects/items/cardboard_cutouts.dm @@ -22,7 +22,6 @@ "Clockwork Cultist" = image(icon = src.icon, icon_state = "cutout_servant"), "Revolutionary" = image(icon = src.icon, icon_state = "cutout_viva"), "Wizard" = image(icon = src.icon, icon_state = "cutout_wizard"), - "Shadowling" = image(icon = src.icon, icon_state = "cutout_shadowling"), "Xenomorph" = image(icon = src.icon, icon_state = "cutout_fukken_xeno"), "Xenomorph Maid" = image(icon = src.icon, icon_state = "cutout_lusty"), "Swarmer" = image(icon = src.icon, icon_state = "cutout_swarmer"), @@ -152,10 +151,6 @@ name = "[pick(GLOB.wizard_first)], [pick(GLOB.wizard_second)]" desc = "A cardboard cutout of a wizard." icon_state = "cutout_wizard" - if("Shadowling") - name = "Unknown" - desc = "A cardboard cutout of a shadowling." - icon_state = "cutout_shadowling" if("Xenomorph") name = "alien hunter ([rand(1, 999)])" desc = "A cardboard cutout of a xenomorph." @@ -196,6 +191,10 @@ name = "Private Security Officer" desc = "A cardboard cutout of a private security officer." icon_state = "cutout_ntsec" + if("Shadowling") + name = "Unknown" + desc = "A cardboard cutout of a shadowling." + icon_state = "cutout_shadowling" else return FALSE return TRUE diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index dd31e5b34328..8b5f624f0256 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -361,6 +361,7 @@ force = on_damage damtype = BURN update_brightness() + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) /obj/item/flashlight/flare/toggle_light() if(light_on || !fuel) @@ -444,6 +445,13 @@ /obj/item/flashlight/flare/is_hot() return light_on * heat +//fire isn't one light source, it's several constantly appearing and disappearing... or something +/obj/item/flashlight/flare/proc/on_light_eater(atom/source, datum/light_eater) + SIGNAL_HANDLER + if(light_on) + visible_message("The enduring flickering of \the [src] refuses to fade.") + return COMPONENT_BLOCK_LIGHT_EATER + /obj/item/flashlight/flare/emergency name = "safety flare" desc = "A flare issued to Nanotrasen employees for emergencies. There are instructions on the side, it reads 'pull cord, make light, obey Nanotrasen'." diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 18824aa5da65..a1c4a621be82 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -65,16 +65,6 @@ playsound(loc, cuffsound, 30, 1, -2) - // Yogs start: Prevents darkspawn from cheesing their bead sleep to cuff and kill - if(is_darkspawn_or_veil(user) && C.has_status_effect(STATUS_EFFECT_BROKEN_WILL) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) - to_chat(user, span_boldannounce("Restraining [C] will wake them up! Are you sure you want to do this?")) - C.visible_message(span_warning("[C] jerks in their sleep as they are restrained!")) - to_chat(C, span_boldannounce("Someone handles your arms roughly, pulling you towards wakefulness!")) - if(do_after(user, 1.5 SECONDS, C, progress = FALSE)) // No progress bar - C.remove_status_effect(STATUS_EFFECT_BROKEN_WILL) - C.SetUnconscious(0) - // Yogs end - if(do_after(user, 3 SECONDS, C) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) if(iscyborg(user)) apply_cuffs(C, user, TRUE) diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 3cf8adbfd8f2..6cccb7423b99 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -308,6 +308,15 @@ menutab = MENU_WEAPON additional_desc = "An exceptionally large sword, glows brightly from an unknown power within." +/obj/item/nullrod/glowing/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + +/obj/item/nullrod/glowing/proc/on_light_eater(atom/source, datum/light_eater) + SIGNAL_HANDLER + visible_message("The undying glow of \the [src] refuses to fade.") + return COMPONENT_BLOCK_LIGHT_EATER + /obj/item/nullrod/Hypertool name = "hypertool" desc = "A tool so powerful even you cannot perfectly use it." @@ -950,7 +959,7 @@ // Everyone else still takes damage but less real damage // Average DPS is 5|15 or 10|10 if unholy (burn|stam) // Should be incredibly difficult to metacheck with this due to RNG and fast processing - if(iscultist(L) || is_clockcult(L) || iswizard(L) || isvampire(L) || IS_BLOODSUCKER(L) || IS_VASSAL(L) || IS_HERETIC(L) || IS_HERETIC_MONSTER(L)) + if(iscultist(L) || is_clockcult(L) || iswizard(L) || isvampire(L) || IS_BLOODSUCKER(L) || IS_VASSAL(L) || IS_HERETIC(L) || IS_HERETIC_MONSTER(L) || isshadowperson(L)) if(damage) L.adjustFireLoss(rand(3,5) * 0.5) // 1.5-2.5 AVG 2.0 if(L.getStaminaLoss() < 65) diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm index 153448b4ba7d..440ae58340f8 100644 --- a/code/game/objects/items/implants/implant_mindshield.dm +++ b/code/game/objects/items/implants/implant_mindshield.dm @@ -39,7 +39,7 @@ target.visible_message(span_warning("[target] seems to resist the implant!"), span_warning("You feel something interfering with your mental conditioning, but you resist it!")) removed(target, TRUE) return FALSE - if(target.mind.has_antag_datum(/datum/antagonist/gang/boss) || is_shadow_or_thrall(target) || target.mind.has_antag_datum(/datum/antagonist/mindslave)) + if(target.mind.has_antag_datum(/datum/antagonist/gang/boss) || target.mind.has_antag_datum(/datum/antagonist/mindslave)) if(!silent) target.visible_message(span_warning("[target] seems to resist the implant!"), span_warning("You feel something interfering with your mental conditioning, but you resist it!")) removed(target, TRUE) @@ -77,8 +77,6 @@ rev.remove_revolutionary(FALSE, user) if(target.mind.has_antag_datum(/datum/antagonist/gang)) target.mind.remove_antag_datum(/datum/antagonist/gang) - if(target.mind.has_antag_datum(/datum/antagonist/veil)) - target.mind.remove_antag_datum(/datum/antagonist/veil) if(!silent) if(target.mind in SSticker.mode.cult) to_chat(target, span_warning("You feel something interfering with your mental conditioning, but you resist it!")) diff --git a/code/game/objects/items/implants/implant_mindshieldtot.dm b/code/game/objects/items/implants/implant_mindshieldtot.dm index e593dc5dac3a..ff0ae38108bf 100644 --- a/code/game/objects/items/implants/implant_mindshieldtot.dm +++ b/code/game/objects/items/implants/implant_mindshieldtot.dm @@ -68,8 +68,6 @@ return FALSE if(target.mind.has_antag_datum(/datum/antagonist/gang)) target.mind.remove_antag_datum(/datum/antagonist/gang) - if(target.mind.has_antag_datum(/datum/antagonist/veil)) - target.mind.remove_antag_datum(/datum/antagonist/veil) if(!silent) if(target.mind in SSticker.mode.cult) to_chat(target, span_warning("You feel something interfering with your mental conditioning, but you resist it!")) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 5583f0894af6..6d1311eca88b 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -19,9 +19,11 @@ if(smoothing_flags & SMOOTH_CORNERS) icon_state = "" GLOB.cameranet.updateVisibility(src) + GLOB.thrallnet.updateVisibility(src) /obj/structure/Destroy() GLOB.cameranet.updateVisibility(src) + GLOB.thrallnet.updateVisibility(src) if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)) QUEUE_SMOOTH_NEIGHBORS(src) return ..() diff --git a/code/game/objects/structures/traps.dm b/code/game/objects/structures/traps.dm index 3c7487decdb6..c8bbb9e1bb92 100644 --- a/code/game/objects/structures/traps.dm +++ b/code/game/objects/structures/traps.dm @@ -10,6 +10,8 @@ var/time_between_triggers = 1 MINUTES //takes a minute to recharge var/charges = INFINITY var/antimagic_flags = MAGIC_RESISTANCE + var/sparks = TRUE + var/can_reveal = TRUE var/list/static/ignore_typecache var/list/mob/immune_minds = list() @@ -28,7 +30,6 @@ /mob/dead)) var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), COMSIG_ATOM_ENTERED = PROC_REF(on_trap_entered) ) AddElement(/datum/element/connect_loc, loc_connections) @@ -44,7 +45,7 @@ return if(user.mind && (user.mind in immune_minds)) return - if(get_dist(user, src) <= 1) + if(can_reveal && get_dist(user, src) <= 1) . += span_notice("You reveal [src]!") flare() @@ -52,7 +53,8 @@ // Makes the trap visible, and starts the cooldown until it's // able to be triggered again. visible_message(span_warning("[src] flares brightly!")) - spark_system.start() + if(sparks) + spark_system.start() alpha = 200 last_trigger = world.time charges-- @@ -63,6 +65,7 @@ animate(src, alpha = initial(alpha), time = time_between_triggers) /obj/structure/trap/proc/on_trap_entered(datum/source, atom/movable/AM, ...) + SIGNAL_HANDLER if(last_trigger + time_between_triggers > world.time) return // Don't want the traps triggered by sparks, ghosts or projectiles. @@ -72,7 +75,7 @@ var/mob/M = AM if(M.mind in immune_minds) return - if(M.can_block_magic()) + if(M.can_block_magic(antimagic_flags)) flare() return if(charges <= 0) @@ -146,23 +149,3 @@ /obj/structure/trap/ward/Initialize(mapload) . = ..() QDEL_IN(src, time_between_triggers) - -/obj/structure/trap/proc/on_entered(datum/source, atom/movable/victim) - SIGNAL_HANDLER - if(last_trigger + time_between_triggers > world.time) - return - // Don't want the traps triggered by sparks, ghosts or projectiles. - if(is_type_in_typecache(victim, ignore_typecache)) - return - if(ismob(victim)) - var/mob/mob_victim = victim - if(mob_victim.mind in immune_minds) - return - if(mob_victim.can_block_magic(antimagic_flags)) - flare() - return - if(charges <= 0) - return - flare() - if(isliving(victim)) - trap_effect(victim) diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 5d0e14f7648b..45742ff12420 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -359,6 +359,16 @@ light_color = "#33CCFF" color = "#33CCFF" +/turf/open/floor/grass/fairy/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + +/turf/open/floor/grass/fairy/proc/on_light_eater(obj/machinery/light/source, datum/light_eater) + SIGNAL_HANDLER + visible_message("Dark energies lash out and corrupt [src].") + TerraformTurf(/turf/open/floor/grass/fairy/dark) + return COMPONENT_BLOCK_LIGHT_EATER + /turf/open/floor/grass/fairy/white name = "white fairygrass patch" floor_tile = /obj/item/stack/tile/fairygrass/white @@ -402,10 +412,12 @@ /turf/open/floor/grass/fairy/dark name = "dark fairygrass patch" floor_tile = /obj/item/stack/tile/fairygrass/dark - light_power = -0.15 - light_range = 2 - light_color = "#AAD84B" - color = "#53003f" + light_power = -1 + light_color = "#21007F" + color = "#21007F" + +/turf/open/floor/grass/fairy/dark/on_light_eater(obj/machinery/light/source, datum/light_eater) + return /turf/open/floor/grass/fairy/Initialize(mapload) . = ..() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index ea66adeaa38c..b0956aab2ad2 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -604,6 +604,7 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/proc/visibilityChanged() GLOB.cameranet.updateVisibility(src) + GLOB.thrallnet.updateVisibility(src) /turf/proc/burn_tile() return diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index bcbd9a5afcf3..0052dd7eb292 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -628,6 +628,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) if(robeless) new_spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB + new_spell.psi_cost = 0 //breaks balance, but allows non darkspawns to use darkspawn abilities new_spell.Grant(spell_recipient) diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index da6408ac1485..2d2ffef95445 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -300,7 +300,7 @@ ROLE_REV, ROLE_REVENANT, ROLE_SINFULDEMON, ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE, ROLE_TRAITOR, ROLE_WIZARD, ROLE_GANG, ROLE_VAMPIRE, - ROLE_SHADOWLING, ROLE_DARKSPAWN, ROLE_ZOMBIE, ROLE_HERETIC)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV + ROLE_DARKSPAWN, ROLE_ZOMBIE, ROLE_HERETIC)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV for(var/department in long_job_lists) output += "
" break_counter = 0 diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ebb2c6618ce3..bfa4c3a4b3b6 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -206,13 +206,6 @@ else message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") log_admin("[key_name(usr)] failed to create a revenant.") - if("shadowling") - if(makeShadowling()) - message_admins("[key_name(usr)] created a shadowling.") - log_admin("[key_name(usr)] created a shadowling.") - else - message_admins("[key_name_admin(usr)] tried to create a shadowling. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a shadowling.") if("darkspawn") if(makeDarkspawn()) message_admins("[key_name(usr)] created a darkspawn.") diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index 2aee88e9a327..a95ab8a4750b 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -24,7 +24,6 @@ Make CentCom Response Team (Requires Ghosts)
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 world.time && stun_absorption[i]["examine_message"]) msg += "[t_He] [t_is][stun_absorption[i]["examine_message"]]\n" - if(!glasses && mind && mind.has_antag_datum(ANTAG_DATUM_THRALL)) - if(getorganslot(ORGAN_SLOT_EYES)) - msg += "[t_His] eyes seem unnaturally dark and soulless.\n" // I'VE BECOME SO NUMB, I CAN'T FEEL YOU THERE - else - msg += "The pair of holes where [t_His] eyes would be seem unnaturally dark and soulless.\n" - - if((!glasses || !wear_suit) && mind?.has_antag_datum(ANTAG_DATUM_VEIL)) + if((!wear_suit && !w_uniform) && mind?.has_antag_datum(ANTAG_DATUM_THRALL)) msg += "[t_His] whole body is covered in sigils!\n" if(!appears_dead) @@ -618,10 +612,6 @@ else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) msg += "[t_He] [p_do()]n't seem all there.\n" - - if(!glasses && mind && mind.has_antag_datum(ANTAG_DATUM_THRALL)) - msg += "[t_His] eyes seem unnaturally dark and soulless.\n" // I'VE BECOME SO NUMB, I CAN'T FEEL YOU THERE - if (length(msg)) . += span_warning("[msg.Join("")]") diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index b1193b2b63b6..7c62e342622a 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -42,7 +42,9 @@ var/max_power = 2 var/current_color var/EMPeffect = FALSE + var/EMP_timer = null var/emageffect = FALSE + var/emag_timer = null var/emag_speed = 4 //how many deciseconds between each colour cycle var/r1 var/g1 @@ -69,6 +71,7 @@ setup_color(ethereal) ethereal_light = ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species) + RegisterSignal(ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) spec_updatehealth(ethereal) var/obj/item/organ/heart/ethereal/ethereal_heart = ethereal.getorganslot(ORGAN_SLOT_HEART) @@ -79,8 +82,11 @@ ethereal_eyes.ethereal_color = default_color /datum/species/ethereal/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + UnregisterSignal(C, COMSIG_LIGHT_EATER_ACT) QDEL_NULL(ethereal_light) C.set_light(0) + deltimer(EMP_timer) + deltimer(emag_timer) return ..() /datum/species/ethereal/random_name(gender,unique,lastname) @@ -131,13 +137,19 @@ fixed_mut_color = rgb(128,128,128) ethereal.update_body() +/// Special handling for getting hit with a light eater +/datum/species/ethereal/proc/on_light_eater(mob/living/carbon/human/source, datum/light_eater) + SIGNAL_HANDLER + spec_emp_act(source, EMP_LIGHT) + return COMPONENT_BLOCK_LIGHT_EATER + /datum/species/ethereal/spec_emp_act(mob/living/carbon/human/H, severity) .=..() if(!EMPeffect) to_chat(H, span_notice("You feel the light of your body leave you.")) EMPeffect = TRUE spec_updatehealth(H) - addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 20 * severity, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 2 to 20 seconds depending on severity + EMP_timer = addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 20 * severity, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //We're out for 2 to 20 seconds depending on severity /datum/species/ethereal/proc/stop_emp(mob/living/carbon/human/H) EMPeffect = FALSE @@ -154,7 +166,7 @@ current_color = rgb(255,0,0) emageffect = TRUE handle_emag(H) - addtimer(CALLBACK(src, PROC_REF(stop_emag), H), 30 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //Disco mode for 30 seconds! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass. + emag_timer = addtimer(CALLBACK(src, PROC_REF(stop_emag), H), 30 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //Disco mode for 30 seconds! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass. return TRUE /datum/species/ethereal/proc/handle_emag(mob/living/carbon/human/H)//please change this to use animate() if you ever figure out how to animate light colours diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 64ff9876d585..75f4e7625a5d 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -1389,6 +1389,7 @@ active_msg = span_notice("You start channeling your telecrystal core....") deactive_msg = span_notice("You stop channeling your telecrystal core.") spell_requirements = NONE + var/beam_icon = "tentacle" /datum/action/cooldown/spell/pointed/phase_jump/InterceptClickOn(mob/living/user, params, atom/target) . = ..() @@ -1400,7 +1401,7 @@ var/obj/spot1 = new phaseout(get_turf(user), user.dir) owner.forceMove(target_turf) var/obj/spot2 = new phasein(get_turf(user), user.dir) - spot1.Beam(spot2, "tentacle", time=2 SECONDS) + spot1.Beam(spot2, beam_icon, time=2 SECONDS) user.visible_message(span_danger("[user] phase shifts away!"), span_warning("You shift around the space around you.")) return TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 4aca5549e3d8..8ce5cc1307cb 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -1,12 +1,18 @@ #define HEART_RESPAWN_THRESHHOLD 40 #define HEART_SPECIAL_SHADOWIFY 2 +///cooldown between charges of the projectile absorb +#define DARKSPAWN_REFLECT_COOLDOWN 15 SECONDS +//////////////////////////////////////////////////////////////////////////////////// +//--------------------------Basic shadow person species---------------------------// +//////////////////////////////////////////////////////////////////////////////////// /datum/species/shadow // Humans cursed to stay in the darkness, lest their life forces drain. They regain health in shadow and die in light. name = "???" plural_form = "???" id = "shadow" sexes = FALSE + bubble_icon = BUBBLE_DARKSPAWN ignored_by = list(/mob/living/simple_animal/hostile/faithless) meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow species_traits = list(NOBLOOD,NOEYESPRITES,NOFLASH, AGENDER) @@ -14,17 +20,61 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC mutanteyes = /obj/item/organ/eyes/shadow + species_language_holder = /datum/language_holder/darkspawn + ///If the darkness healing heals all damage types, not just brute and burn + var/powerful_heal = FALSE + ///How much damage is healed each life tick + var/dark_healing = 1 + ///How much burn damage is taken each life tick, reduced to 20% for dim light + var/light_burning = 1 + ///How many charges their projectile protection has + var/shadow_charges = 0 + +/datum/species/shadow/bullet_act(obj/projectile/P, mob/living/carbon/human/H) + var/turf/T = get_turf(H) + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_DIM_LIGHT && shadow_charges > 0) + H.visible_message(span_danger("The shadows around [H] ripple as they absorb \the [P]!")) + playsound(H, "bullet_miss", 75, 1) + shadow_charges = min(shadow_charges - 1, 0) + addtimer(CALLBACK(src, PROC_REF(regen_shadow)), DARKSPAWN_REFLECT_COOLDOWN)//so they regen on different timers + return BULLET_ACT_BLOCK + return ..() +/datum/species/shadow/proc/regen_shadow() + shadow_charges = min(shadow_charges++, initial(shadow_charges)) /datum/species/shadow/spec_life(mob/living/carbon/human/H) var/turf/T = H.loc if(istype(T)) var/light_amount = T.get_lumcount() - - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying - H.take_overall_damage(1,1, 0, BODYPART_ORGANIC) - else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark - H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) + switch(light_amount) + if(0 to SHADOW_SPECIES_DIM_LIGHT) + var/list/healing_types = list(CLONE, BURN, BRUTE) + if(powerful_heal) + healing_types |= list(STAMINA, TOX, OXY, BRAIN) //heal additional damage types + H.AdjustAllImmobility(-dark_healing * 40) + H.SetSleeping(0) + H.heal_ordered_damage(dark_healing, healing_types, BODYPART_ANY) + if(SHADOW_SPECIES_DIM_LIGHT to SHADOW_SPECIES_BRIGHT_LIGHT) //not bright, but still dim + var/datum/antagonist/darkspawn/dude = isdarkspawn(H) + if(dude) + if(HAS_TRAIT(dude, TRAIT_DARKSPAWN_LIGHTRES)) + return + if(HAS_TRAIT(dude, TRAIT_DARKSPAWN_CREEP)) + return + to_chat(H, span_userdanger("The light singes you!")) + H.playsound_local(H, 'sound/weapons/sear.ogg', max(30, 40 * light_amount), TRUE) + H.adjustCloneLoss(light_burning * 0.2) + if(SHADOW_SPECIES_BRIGHT_LIGHT to INFINITY) //but quick death in the light + var/datum/antagonist/darkspawn/dude = isdarkspawn(H) + if(dude) + if(HAS_TRAIT(dude, TRAIT_DARKSPAWN_CREEP)) + return + to_chat(H, span_userdanger("The light burns you!")) + H.playsound_local(H, 'sound/weapons/sear.ogg', max(40, 65 * light_amount), TRUE) + H.adjustCloneLoss(light_burning) /datum/species/shadow/check_roundstart_eligible() if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) @@ -32,24 +82,13 @@ return ..() /datum/species/shadow/get_species_description() - return "Victims of a long extinct space alien. Their flesh is a sickly \ - seethrough filament, their tangled insides in clear view. Their form \ - is a mockery of life, leaving them mostly unable to work with others under \ - normal circumstances." + return "Their flesh is a sickly seethrough filament,\ + their tangled insides in clear view. Their form is a mockery of life, \ + leaving them mostly unable to work with others under normal circumstances." /datum/species/shadow/get_species_lore() return list( - "Long ago, the Spinward Sector used to be inhabited by terrifying aliens aptly named \"Shadowlings\" \ - after their control over darkness, and tendancy to kidnap victims into the dark maintenance shafts. \ - Around 2558, the long campaign Nanotrasen waged against the space terrors ended with the full extinction of the Shadowlings.", - - "Victims of their kidnappings would become brainless thralls, and via surgery they could be freed from the Shadowling's control. \ - Those more unlucky would have their entire body transformed by the Shadowlings to better serve in kidnappings. \ - Unlike the brain tumors of lesser control, these greater thralls could not be reverted.", - - "With Shadowlings long gone, their will is their own again. But their bodies have not reverted, burning in exposure to light. \ - Nanotrasen has assured the victims that they are searching for a cure. No further information has been given, even years later. \ - Most shadowpeople now assume Nanotrasen has long since shelfed the project.", + "WIP.", ) /datum/species/shadow/create_pref_unique_perks() @@ -80,6 +119,9 @@ return to_add +//////////////////////////////////////////////////////////////////////////////////// +//------------------------Midround antag exclusive species------------------------// +//////////////////////////////////////////////////////////////////////////////////// /datum/species/shadow/nightmare name = "Nightmare" plural_form = null @@ -92,7 +134,8 @@ mutanteyes = /obj/item/organ/eyes/shadow mutant_organs = list(/obj/item/organ/heart/nightmare) mutantbrain = /obj/item/organ/brain/nightmare - + shadow_charges = 1 + var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." @@ -100,23 +143,108 @@ . = ..() to_chat(C, "[info_text]") - C.fully_replace_character_name("[C.real_name]","[pick(GLOB.nightmare_names)]") // Yogs -- fixes nightmares not having special spooky names. this proc takes the old name first, and *THEN* the new name! - -/datum/species/shadow/nightmare/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - H.visible_message(span_danger("[H] dances in the shadows, evading [P]!")) - playsound(T, "bullet_miss", 75, 1) - return BULLET_ACT_FORCE_PIERCE - return ..() + C.fully_replace_character_name("[C.real_name]", nightmare_name()) /datum/species/shadow/nightmare/check_roundstart_eligible() return FALSE -//Organs +//////////////////////////////////////////////////////////////////////////////////// +//----------------------Roundstart antag exclusive species------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/datum/species/shadow/darkspawn + name = "Darkspawn" + id = "darkspawn" + limbs_id = "darkspawn" + sexes = FALSE + nojumpsuit = TRUE + changesource_flags = MIRROR_BADMIN //never put this in the pride pool because they look super valid and can never be changed off of + siemens_coeff = 0 + armor = 10 + burnmod = 1.2 + heatmod = 1.5 + no_equip = list( + ITEM_SLOT_MASK, + ITEM_SLOT_OCLOTHING, + ITEM_SLOT_GLOVES, + ITEM_SLOT_FEET, + ITEM_SLOT_ICLOTHING, + ITEM_SLOT_SUITSTORE, + ITEM_SLOT_HEAD, + ITEM_SLOT_EYES + ) + species_traits = list( + NOBLOOD, + NO_UNDERWEAR, + NO_DNA_COPY, + NOTRANSSTING, + NOEYESPRITES, + NOHUSK + ) + inherent_traits = list( + TRAIT_NOGUNS, + TRAIT_RESISTCOLD, + TRAIT_RESISTHIGHPRESSURE, + TRAIT_RESISTLOWPRESSURE, + TRAIT_NOBREATH, + TRAIT_RADIMMUNE, + TRAIT_VIRUSIMMUNE, + TRAIT_PIERCEIMMUNE, + TRAIT_NODISMEMBER, + TRAIT_NOHUNGER, + TRAIT_NOSLIPICE, + TRAIT_GENELESS, + TRAIT_NOCRITDAMAGE, + TRAIT_NOGUNS, + TRAIT_SPECIESLOCK //never let them swap off darkspawn, it can cause issues + ) + mutanteyes = /obj/item/organ/eyes/darkspawn + mutantears = /obj/item/organ/ears/darkspawn + + powerful_heal = TRUE + shadow_charges = 3 + +/datum/species/shadow/darkspawn/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.fully_replace_character_name("[C.real_name]", darkspawn_name()) + +/datum/species/shadow/darkspawn/spec_updatehealth(mob/living/carbon/human/H) + var/datum/antagonist/darkspawn/antag = isdarkspawn(H) + if(antag) + dark_healing = antag.dark_healing + light_burning = antag.light_burning + if(H.physiology) + H.physiology.brute_mod = antag.brute_mod + H.physiology.burn_mod = antag.burn_mod + H.physiology.stamina_mod = antag.stam_mod + +/datum/species/shadow/darkspawn/spec_death(gibbed, mob/living/carbon/human/H) + playsound(H, 'yogstation/sound/creatures/darkspawn_death.ogg', 50, FALSE) + +/datum/species/shadow/darkspawn/check_roundstart_eligible() + return FALSE +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Darkspawn organs----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// +/obj/item/organ/eyes/darkspawn //special eyes that innately have night vision without having a toggle that adds action clutter + name = "darkspawn eyes" + desc = "It turned out they had them after all!" + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD //far more durable eyes than most + healing_factor = 2 * STANDARD_ORGAN_HEALING + lighting_cutoff = LIGHTING_CUTOFF_HIGH + color_cutoffs = list(12, 0, 50) + sight_flags = SEE_MOBS + +/obj/item/organ/ears/darkspawn //special ears that are a bit tankier and have innate sound protection + name = "darkspawn ears" + desc = "It turned out they had them after all!" + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD //far more durable ears than most + healing_factor = 2 * STANDARD_ORGAN_HEALING + bang_protect = 1 + +//////////////////////////////////////////////////////////////////////////////////// +//------------------------------Nightmare organs----------------------------------// +//////////////////////////////////////////////////////////////////////////////////// /obj/item/organ/brain/nightmare name = "tumorous mass" desc = "A fleshy growth that was dug out of the skull of a Nightmare." @@ -168,6 +296,8 @@ ..() if(special != HEART_SPECIAL_SHADOWIFY) blade = new/obj/item/light_eater + blade.force = 25 + blade.armour_penetration = 35 M.put_in_hands(blade) START_PROCESSING(SSobj, src) @@ -190,12 +320,12 @@ if(istype(T)) var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + if(light_amount < SHADOW_SPECIES_DIM_LIGHT) respawn_progress++ playsound(owner,'sound/effects/singlebeat.ogg',40,1) if(respawn_progress >= HEART_RESPAWN_THRESHHOLD) owner.revive(full_heal = TRUE) - if(!(owner.dna.species.id == "shadow" || owner.dna.species.id == "nightmare")) + if(!(isshadowperson(owner))) var/mob/living/carbon/old_owner = owner Remove(owner, HEART_SPECIAL_SHADOWIFY) old_owner.set_species(/datum/species/shadow) @@ -207,66 +337,32 @@ respawn_progress = 0 //Weapon - /obj/item/light_eater name = "light eater" //as opposed to heavy eater - icon = 'icons/obj/changeling.dmi' - icon_state = "arm_blade" - item_state = "arm_blade" - force = 25 - armour_penetration = 35 - lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' - righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' - item_flags = ABSTRACT | DROPDEL - tool_behaviour = TOOL_MINING + icon = 'yogstation/icons/obj/darkspawn_items.dmi' + icon_state = "light_eater" + item_state = "light_eater" + force = 18 + lefthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi' + righthand_file = 'yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi' + hitsound = 'sound/weapons/bladeslice.ogg' + item_flags = ABSTRACT w_class = WEIGHT_CLASS_HUGE sharpness = SHARP_EDGED - wound_bonus = -30 - bare_wound_bonus = 20 + wound_bonus = -40 resistance_flags = ACID_PROOF /obj/item/light_eater/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) AddComponent(/datum/component/butchering, 80, 70) + AddComponent(/datum/component/light_eater) -/obj/item/light_eater/afterattack(atom/movable/AM, mob/user, proximity) +/obj/item/light_eater/worn_overlays(mutable_appearance/standing, isinhands, icon_file) //this doesn't work and i have no clue why . = ..() - if(!proximity) - return - if(isopenturf(AM)) //So you can actually melee with it - return - if(isliving(AM)) - var/mob/living/L = AM - if(isethereal(AM)) - AM.emp_act(EMP_LIGHT) - - else if(iscyborg(AM)) - var/mob/living/silicon/robot/borg = AM - if(borg.lamp_enabled) - borg.smash_headlamp() - else if(ishuman(AM)) - for(var/obj/item/O in AM.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(AM)) - var/obj/item/I = AM - if(I.light_range && I.light_power) - disintegrate(I) - -/obj/item/light_eater/proc/disintegrate(obj/item/O) - if(istype(O, /obj/item/pda)) - var/obj/item/pda/PDA = O - PDA.set_light_on(FALSE) - PDA.set_light_range(0) //It won't be turning on again. - 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) + if(isinhands) + . += emissive_appearance(icon, "[item_state]_emissive", src) +#undef DARKSPAWN_REFLECT_COOLDOWN #undef HEART_SPECIAL_SHADOWIFY #undef HEART_RESPAWN_THRESHHOLD diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 658d1791f789..9dd30825e4cc 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1586,8 +1586,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) /datum/antagonist/cult, /datum/antagonist/darkspawn, /datum/antagonist/rev, - /datum/antagonist/shadowling, - /datum/antagonist/veil + /datum/antagonist/thrall, ) for(var/antagcheck in bad_antags) if(mind?.has_antag_datum(antagcheck)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index f28a509cf4fa..32855c1f0af2 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -447,7 +447,7 @@ //called when the mob receives a bright flash /mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash) - if(get_eye_protection() < intensity && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND)))) + if(get_eye_protection() < intensity && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND))) && !HAS_TRAIT(src, TRAIT_NOFLASH)) overlay_fullscreen("flash", type) addtimer(CALLBACK(src, PROC_REF(clear_fullscreen), "flash", 25), 25) return TRUE diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index c5a94b99a552..0fcf444417b8 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -23,8 +23,7 @@ if(changeling) changeling.regain_powers() - var/datum/antagonist/darkspawn/darkspawn = mind.has_antag_datum(/datum/antagonist/darkspawn) - if(darkspawn) - darkspawn.regain_abilities() - src.client.init_verbs() + + if(GLOB.sacrament_done) + AddComponent(/datum/component/shadowlands) diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index a9ad9884045d..375e47263e28 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -17,6 +17,13 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) /// List of images cloned by all chunk static images put onto turfs cameras cant see /// Indexed by the plane offset to use var/list/image/obscured_images + ///If defined, only cameras with matching network flags will be used by chunks + ///The cameras list is only used for updating chunks, not for actual vision + var/list/networks + +//worst 6 hours of my life i spent trying to figure out how to best split a cameranet, this is what i've settled on +/datum/cameranet/darkspawn + networks = list(ROLE_DARKSPAWN) /datum/cameranet/New() obscured_images = list() @@ -54,7 +61,7 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) var/key = "[x],[y],[lowest.z]" . = chunks[key] if(!.) - chunks[key] = . = new /datum/camerachunk(x, y, lowest.z) + chunks[key] = . = new /datum/camerachunk(x, y, lowest.z, src) /// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. /datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = TRUE) diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index a4f63e82a4fd..1ed146f015eb 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -20,6 +20,7 @@ var/list/seenby = list() ///images currently in use on obscured turfs. var/list/active_static_images = list() + var/datum/cameranet/camnet var/changed = FALSE var/x = 0 @@ -90,7 +91,7 @@ var/list/newly_obscured_turfs = visibleTurfs - updated_visible_turfs for(var/mob/camera/ai_eye/client_eye as anything in seenby) - var/client/client = client_eye.ai?.client || client_eye.client + var/client/client = client_eye.GetViewerClient() if(!client) continue @@ -120,7 +121,7 @@ changed = FALSE for(var/mob/camera/ai_eye/client_eye as anything in seenby) - var/client/client = client_eye.ai?.client || client_eye.client + var/client/client = client_eye.GetViewerClient() if(!client) continue @@ -128,9 +129,10 @@ /// Create a new camera chunk, since the chunks are made as they are needed. -/datum/camerachunk/New(x, y, lower_z) +/datum/camerachunk/New(x, y, lower_z, cameranet) x = GET_CHUNK_COORD(x) y = GET_CHUNK_COORD(y) + camnet = cameranet src.x = x src.y = y @@ -141,11 +143,11 @@ for(var/z_level in lower_z to upper_z) var/list/local_cameras = list() for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level))) - if(camera.can_use()) + if(camera.can_use() && (!camnet.networks || LAZYLEN(camera.network & camnet.networks))) local_cameras += camera for(var/mob/living/silicon/sillycone in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level))) - if(sillycone.builtInCamera?.can_use()) + if(sillycone.builtInCamera?.can_use() && (!camnet.networks || LAZYLEN(sillycone.builtInCamera.network & camnet.networks))) local_cameras += sillycone.builtInCamera // for(var/obj/vehicle/sealed/mecha/mech in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level))) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index b1b93d0bfd93..f7fade0fc34e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -16,6 +16,7 @@ var/static_visibility_range = 16 var/ai_detector_visible = TRUE var/ai_detector_color = COLOR_RED + var/list/networks = list("ss13", "mine") /mob/camera/ai_eye/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 6a3957353f67..6a2a00f37b6f 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -123,6 +123,7 @@ wires = new /datum/wires/robot(src) ADD_TRAIT(src, TRAIT_EMPPROOF_CONTENTS, "innate_empproof") + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge)) robot_modules_background = new() @@ -343,6 +344,14 @@ if(thruster_button) thruster_button.icon_state = "ionpulse[ionpulse_on]" +/// Special handling for getting hit with a light eater +/mob/living/silicon/robot/proc/on_light_eater(mob/living/silicon/robot/source, datum/light_eater) + SIGNAL_HANDLER + if(lamp_enabled) + smash_headlamp() + return COMPONENT_BLOCK_LIGHT_EATER + + /mob/living/silicon/robot/get_status_tab_items() . = ..() . += "" diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 9936019d6959..b23d8ce8d159 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -217,12 +217,66 @@ P = apply_status_effect(STATUS_EFFECT_PARALYZED, amount, updating) return P +///////////////////////////////// DAZED ////////////////////////////////// +/mob/living/proc/IsDazed() //If we're dazed + return has_status_effect(STATUS_EFFECT_DAZED) + +/mob/living/proc/AmountDazed() //How many deciseconds remain in our dazed status effect + var/datum/status_effect/incapacitating/dazed/D = IsDazed(FALSE) + if(D) + return D.duration - world.time + return 0 + +/mob/living/proc/Daze(amount, updating = TRUE, ignore_canstun = FALSE) //Can't go below remaining duration + if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN) + return + if(((status_flags & CANKNOCKDOWN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) || ignore_canstun) + if(absorb_stun(amount, ignore_canstun)) + return + var/datum/status_effect/incapacitating/dazed/D = IsDazed(FALSE) + if(D) + D.duration = max(world.time + amount, D.duration) + else if(amount > 0) + D = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating) + return D + +/mob/living/proc/SetDaze(amount, updating = TRUE, ignore_canstun = FALSE) //Sets remaining duration + if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN) + return + if(((status_flags & CANKNOCKDOWN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) || ignore_canstun) + var/datum/status_effect/incapacitating/dazed/D = IsDazed(FALSE) + if(amount <= 0) + if(D) + qdel(D) + else + if(absorb_stun(amount, ignore_canstun)) + return + if(D) + D.duration = world.time + amount + else + D = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating) + return D + +/mob/living/proc/AdjustDaze(amount, updating = TRUE, ignore_canstun = FALSE) //Adds to remaining duration + if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_DAZE, amount, updating, ignore_canstun) & COMPONENT_NO_STUN) + return + if(((status_flags & CANKNOCKDOWN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) || ignore_canstun) + if(absorb_stun(amount, ignore_canstun)) + return + var/datum/status_effect/incapacitating/dazed/D = IsDazed(FALSE) + if(D) + D.duration += amount + else if(amount > 0) + D = apply_status_effect(STATUS_EFFECT_DAZED, amount, updating) + return D + //Blanket /mob/living/proc/AllImmobility(amount, updating) Paralyze(amount, FALSE) Knockdown(amount, FALSE) Stun(amount, FALSE) Immobilize(amount, FALSE) + Daze(amount, FALSE) if(updating) update_mobility() @@ -231,6 +285,7 @@ SetKnockdown(amount, FALSE) SetStun(amount, FALSE) SetImmobilized(amount, FALSE) + SetDaze(amount, FALSE) if(updating) update_mobility() @@ -239,6 +294,7 @@ AdjustKnockdown(amount, FALSE) AdjustStun(amount, FALSE) AdjustImmobilized(amount, FALSE) + AdjustDaze(amount, FALSE) if(updating) update_mobility() diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index fe62eb47fa82..ff568b8561e0 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -369,6 +369,8 @@ break_light_tube(TRUE) update(trigger = FALSE) + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + /obj/machinery/light/Destroy() GLOB.lights.Remove(src) var/area/A = get_area(src) @@ -694,6 +696,13 @@ if(BURN) playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + +/obj/machinery/light/proc/on_light_eater(obj/machinery/light/source, datum/light_eater) + SIGNAL_HANDLER + if(status != LIGHT_EMPTY) + var/obj/item/light/tube = drop_light_tube() + tube?.burn() + return COMPONENT_BLOCK_LIGHT_EATER // returns if the light has power /but/ is manually turned off // if a light is turned off, it won't activate emergency power /obj/machinery/light/proc/turned_off() diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 9a6620ced03f..b51cd56b4ee8 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -159,7 +159,7 @@ qdel(src) /proc/wabbajack(mob/living/M, randomize) - if(!istype(M) || M.stat == DEAD || M.notransform || (GODMODE & M.status_flags)) + if(!istype(M) || M.stat == DEAD || M.notransform || (GODMODE & M.status_flags) || HAS_TRAIT(M, TRAIT_SPECIESLOCK)) return M.notransform = TRUE diff --git a/code/modules/projectiles/projectile/reusable/arrow.dm b/code/modules/projectiles/projectile/reusable/arrow.dm index 69f1038d2d27..cfd76800d514 100644 --- a/code/modules/projectiles/projectile/reusable/arrow.dm +++ b/code/modules/projectiles/projectile/reusable/arrow.dm @@ -14,7 +14,12 @@ ..() var/turf/open/target_turf = get_turf(target) if(istype(target_turf)) - target_turf.ignite_turf(rand(8, 16)) + if(istype(ammo_type, /obj/item/ammo_casing/reusable/arrow)) + var/obj/item/ammo_casing/reusable/arrow/arrow = ammo_type + if(arrow.flaming) + target_turf.ignite_turf(rand(8, 16)) + else + target_turf.ignite_turf(rand(8, 16)) if(!isliving(target) || (blocked == 100)) return diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index e296440797f2..468aed5c4df2 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -50,7 +50,7 @@ QDEL_NULL(arm) if(CHECK_BITFIELD(movement_type, PHASING)) playsound(src, 'sound/effects/curse3.ogg', 25, TRUE, -1) - var/turf/T = get_step(src, dir) + var/turf/T = get_turf(src) var/obj/effect/temp_visual/dir_setting/curse/hand/leftover = new(T, dir) leftover.icon_state = icon_state for(var/obj/effect/temp_visual/dir_setting/curse/grasp_portal/G in starting) @@ -58,8 +58,7 @@ if(!T) //T can be in nullspace when src is set to QDEL return new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(starting, dir) - var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm) - animate(D.visuals, alpha = 0, time = 3.2 SECONDS) + starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm) /obj/projectile/curse_hand/on_range() finale() @@ -75,3 +74,10 @@ damage_type = BRAIN paralyze = 0 +/obj/projectile/curse_hand/progenitor/on_hit(atom/target, blocked) + if(isliving(target)) + var/mob/living/victim = target + if(is_darkspawn_or_thrall(victim)) + return BULLET_ACT_FORCE_PIERCE + return ..() + \ No newline at end of file diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 063ca42a963a..2bbd35ed0428 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -257,26 +257,25 @@ taste_description = "mint" /datum/reagent/consumable/frostoil/on_mob_life(mob/living/carbon/M) - var/cooling = 0 + var/cooling = -10 * TEMPERATURE_DAMAGE_COEFFICIENT switch(current_cycle) if(1 to 15) - cooling = -10 * TEMPERATURE_DAMAGE_COEFFICIENT if(holder.has_reagent(/datum/reagent/consumable/capsaicin)) holder.remove_reagent(/datum/reagent/consumable/capsaicin, 5) if(isslime(M)) cooling = -rand(5,20) if(15 to 25) - cooling = -20 * TEMPERATURE_DAMAGE_COEFFICIENT + cooling *= 2 if(isslime(M)) cooling = -rand(10,20) if(25 to 35) - cooling = -30 * TEMPERATURE_DAMAGE_COEFFICIENT + cooling *= 3 if(prob(1) && !HAS_TRAIT(M, TRAIT_RESISTCOLD)) M.emote("shiver") if(isslime(M)) cooling = -rand(15,20) if(35 to INFINITY) - cooling = -40 * TEMPERATURE_DAMAGE_COEFFICIENT + cooling *= 4 if(prob(5) && !HAS_TRAIT(M, TRAIT_RESISTCOLD)) M.emote("shiver") if(isslime(M)) diff --git a/code/modules/research/nanites/nanite_programs/buffing.dm b/code/modules/research/nanites/nanite_programs/buffing.dm index 38da771f9098..a5acb19f2675 100644 --- a/code/modules/research/nanites/nanite_programs/buffing.dm +++ b/code/modules/research/nanites/nanite_programs/buffing.dm @@ -112,9 +112,7 @@ return if(host_mob.mind.has_antag_datum(/datum/antagonist/gang, TRUE)) return - if(is_shadow_or_thrall(host_mob)) - return - if(host_mob.mind.has_antag_datum(/datum/antagonist/veil) || host_mob.mind.has_antag_datum(/datum/antagonist/darkspawn)) + if(is_darkspawn_or_thrall(host_mob)) return ADD_TRAIT(host_mob, TRAIT_MINDSHIELD, "nanites") host_mob.sec_hud_set_implants() diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index b3a73046b8cb..d19617017e00 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -85,6 +85,9 @@ /// The amount of smoke to create on cast. This is a range, so a value of 5 will create enough smoke to cover everything within 5 steps. var/smoke_amt = 0 + ///used for darkspawn, if there is a psi cost, it will try to check for a darkspawn datum + var/psi_cost = 0 //can var edit psi_cost to 0 to make darkspawn abilities usable by non-darkspawns + /datum/action/cooldown/spell/Grant(mob/grant_to) // If our spell is mind-bound, we only wanna grant it to our mind if(istype(target, /datum/mind)) @@ -171,6 +174,19 @@ to_chat(owner, span_warning("You must dedicate yourself to silence first!")) return FALSE + //used for darkspawn spells + if(psi_cost) + if(isdarkspawn(owner)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + if(!darkspawn.has_psi(psi_cost)) + if(feedback) + owner.balloon_alert(owner, span_warning("Not enough psi!")) + return FALSE + else + if(feedback) + to_chat(owner, span_warning("Your mind is incapable of comprehending this ability!")) + return FALSE + // If the spell requires the user has no antimagic equipped, and they're holding antimagic // that corresponds with the spell's antimagic, then they can't actually cast the spell if((spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC) && !owner.can_cast_magic(antimagic_flags)) @@ -248,7 +264,7 @@ var/precast_result = before_cast(cast_on) if(precast_result & SPELL_CANCEL_CAST) return FALSE - + // Spell is officially being cast if(!(precast_result & SPELL_NO_FEEDBACK)) // We do invocation and sound effects here, before actual cast @@ -261,6 +277,7 @@ if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) // The entire spell is done, start the actual cooldown at its set duration StartCooldown() + consume_resource() //a resource cost is basically the same as a cooldown // And then proceed with the aftermath of the cast // Final effects that happen after all the casting is done can go here @@ -329,6 +346,13 @@ SEND_SIGNAL(owner, COMSIG_MOB_AFTER_SPELL_CAST, src, cast_on) SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on) +/// Called after the effect happens, whether that's after the button press or after hitting someone with a touch ability +/datum/action/cooldown/spell/proc/consume_resource() //to-do: rework vampire blood use into using this proc + if(psi_cost && isdarkspawn(owner)) + var/datum/antagonist/darkspawn/darkspawn = isdarkspawn(owner) + darkspawn.use_psi(psi_cost) + + /// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation /datum/action/cooldown/spell/proc/spell_feedback() if(!owner) diff --git a/code/modules/spells/spell_types/jaunt/shadow_walk.dm b/code/modules/spells/spell_types/jaunt/shadow_walk.dm index 1b36a0e69501..4b2a1d8b908d 100644 --- a/code/modules/spells/spell_types/jaunt/shadow_walk.dm +++ b/code/modules/spells/spell_types/jaunt/shadow_walk.dm @@ -24,7 +24,7 @@ if(is_jaunting(owner)) return TRUE var/turf/cast_turf = get_turf(owner) - if(cast_turf.get_lumcount() >= SHADOW_SPECIES_LIGHT_THRESHOLD) + if(cast_turf.get_lumcount() >= SHADOW_SPECIES_DIM_LIGHT) if(feedback) to_chat(owner, span_warning("It isn't dark enough here!")) return FALSE @@ -112,7 +112,7 @@ /obj/effect/dummy/phased_mob/shadow/proc/check_light_level(location_to_check) var/turf/T = get_turf(location_to_check) var/light_amount = T.get_lumcount() - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) // jaunt ends + if(light_amount > SHADOW_SPECIES_DIM_LIGHT) // jaunt ends return TRUE /** diff --git a/code/modules/spells/spell_types/toggle/_toggle.dm b/code/modules/spells/spell_types/toggle/_toggle.dm new file mode 100644 index 000000000000..6fa127377805 --- /dev/null +++ b/code/modules/spells/spell_types/toggle/_toggle.dm @@ -0,0 +1,32 @@ +///basically just a copy of how part of /datum/action/innate works, but as a spell typepath +/datum/action/cooldown/spell/toggle + /// Whether we're active or not + var/active = FALSE + +/datum/action/cooldown/spell/toggle/New() + ..() + START_PROCESSING(SSfastprocess, src) + +/datum/action/cooldown/spell/toggle/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/action/cooldown/spell/toggle/process() + build_all_button_icons(ALL) //so as to be consistent with situational requirements, keep the button updated + +/datum/action/cooldown/spell/toggle/cast(atom/cast_on) + . = ..() + active = !active + if(active) + Enable() + else + Disable() + +/datum/action/cooldown/spell/toggle/is_action_active(atom/movable/screen/movable/action_button/current_button) + return active + +/datum/action/cooldown/spell/toggle/proc/Enable() + return + +/datum/action/cooldown/spell/toggle/proc/Disable() + return diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm index b4f1b74c859c..9777c0e21442 100644 --- a/code/modules/spells/spell_types/touch/_touch.dm +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -120,6 +120,7 @@ reset_spell_cooldown() else StartCooldown() + consume_resource() build_all_button_icons() /// Registers all signal procs for the hand. diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index c790f4900488..add44d04d896 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -958,12 +958,6 @@ limb.icon_state = "[species_id]_[body_zone]_[icon_gender]" else limb.icon_state = "[species_id]_[body_zone]" - if(should_draw_yogs) //yogs start - limb.icon = 'yogstation/icons/mob/mutant_bodyparts.dmi' - if(should_draw_gender) - limb.icon_state = "[species_id]_[body_zone]_[icon_gender]" - else - limb.icon_state = "[species_id]_[body_zone]" //yogs end if(aux_zone) aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir) . += aux diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm index bc220c737a14..119e8a02d5d3 100644 --- a/code/modules/surgery/limb_augmentation.dm +++ b/code/modules/surgery/limb_augmentation.dm @@ -66,6 +66,9 @@ if(isgolem(target)) // no armor stacking to_chat(user, span_warning("[target]'s exterior is too strong already!")) return FALSE + else if(isshadowperson(target)) // no augmenting the species made of shadows + to_chat(user, span_warning("[target]'s body refuses to be augmented!")) + return FALSE else return TRUE diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm index 44e3a64a6017..643db6fd73f7 100644 --- a/code/modules/surgery/organ_manipulation.dm +++ b/code/modules/surgery/organ_manipulation.dm @@ -170,6 +170,10 @@ H.leave_victim() return FALSE if(I && I.owner == target) + if(istype(I, /obj/item/organ/shadowtumor))//Thralls resist deconversion + var/obj/item/organ/shadowtumor/tumor = I + if(tumor.resist(target)) + return FALSE display_results(user, target, span_notice("You successfully extract [I] from [target]'s [parse_zone(target_zone)]."), "[user] successfully extracts [I] from [target]'s [parse_zone(target_zone)]!", "[user] successfully extracts something from [target]'s [parse_zone(target_zone)]!") diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index 87bdd6505488..1472567fb8fc 100644 --- a/code/modules/surgery/organs/augments_internal.dm +++ b/code/modules/surgery/organs/augments_internal.dm @@ -106,6 +106,7 @@ COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_LIVING_STATUS_IMMOBILIZE, COMSIG_LIVING_STATUS_PARALYZE, + COMSIG_LIVING_STATUS_DAZE, ) var/stun_cap_amount = 4 SECONDS diff --git a/config/game_options.txt b/config/game_options.txt index 1b5cbf0dd834..0e1dab8bdae7 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -104,7 +104,6 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o ## Set to 0 to disable that mode. # New -PROBABILITY DARKSPAWN 2 PROBABILITY HERESY 6 PROBABILITY INFILTRATION 0 PROBABILITY BLOODSUCKER 5 @@ -120,13 +119,13 @@ PROBABILITY CHANGELING 3 PROBABILITY VAMPIRE 0 # Group antags +PROBABILITY DARKSPAWN 9 PROBABILITY REVOLUTION 8 PROBABILITY GANG 0 PROBABILITY CULT 5 PROBABILITY CLOCKWORK_CULT 5 # Snowflakes -PROBABILITY SHADOWLING 0 PROBABILITY WIZARD 8 PROBABILITY NUCLEAR 9 PROBABILITY MALF 0 @@ -162,10 +161,10 @@ CONTINUOUS CHANGELING CONTINUOUS WIZARD CONTINUOUS HIVEMIND #CONTINUOUS MALF -CONTINUOUS SHADOWLING CONTINUOUS VAMPIRE CONTINUOUS DYNAMIC CONTINUOUS HERESY +CONTINUOUS DARKSPAWN ##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately! @@ -188,6 +187,7 @@ MIDROUND_ANTAG CHANGELING MIDROUND_ANTAG WIZARD #MIDROUND_ANTAG MONKEY #MIDROUND_ANTAG MALF +MIDROUND_ANTAG DARKSPAWN ## Uncomment these for overrides of the minimum / maximum number of players in a round type. ## If you set any of these occasionally check to see if you still need them as the modes diff --git a/html/browser/roundend.css b/html/browser/roundend.css index 2558d97ad62a..7bcf48fa08de 100644 --- a/html/browser/roundend.css +++ b/html/browser/roundend.css @@ -96,3 +96,9 @@ body { .tooltip_container:hover .tooltip_hover { visibility: visible; } + +.progenitor { + color: #7264FF; + font-size: 24px; + font-weight: bold; +} diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index aff9f0685c57..9ac330d0690d 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/screen_full.dmi b/icons/mob/screen_full.dmi index 4fa520c7d9c3..f90ffb4ae402 100644 Binary files a/icons/mob/screen_full.dmi and b/icons/mob/screen_full.dmi differ diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi index 8cc452f225d2..280f811dda6d 100644 Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ diff --git a/icons/title_cards.dmi b/icons/title_cards.dmi index 9191e1539594..156b924b95f9 100644 Binary files a/icons/title_cards.dmi and b/icons/title_cards.dmi differ diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index f182c158438d..2299f9613a18 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -94,7 +94,6 @@ h1.alert, h2.alert {color: #000000;} .suicide {color: #ff5050; font-style: italic;} .green {color: #03ff39;} .nicegreen {color: #14a833;} -.shadowling {color: #3b2769;} .velvet {color: #21007F;} .cult {color: #973e3b;} .cultlarge {color: #973e3b; font-weight: bold; font-size: 3;} diff --git a/strings/accents/accent_brooklyn.json b/strings/accents/accent_brooklyn.json index 476566bea47a..36d76f143f73 100644 --- a/strings/accents/accent_brooklyn.json +++ b/strings/accents/accent_brooklyn.json @@ -17,7 +17,6 @@ "\bClockwork\b": "Clockwawhk", "\bRevolution\b": "Revolushun", "\bGangster\b": "Gangstuh", - "\bShadowling\b": "Shadowlin'", "\bling\b": "lin'", "\bsling\b": "slin'", "\bMutineer\b": "Mutineuh", @@ -153,4 +152,4 @@ "\bsupport\b": "suppawht", "\bmotion\b": "moshun" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_chef_muppet.json b/strings/accents/accent_chef_muppet.json index c9e0cc78258f..16b1ddb80761 100644 --- a/strings/accents/accent_chef_muppet.json +++ b/strings/accents/accent_chef_muppet.json @@ -55,7 +55,6 @@ "\bWizard\b": "vizardrden", "\bMagic\b": "magiccen", "\bShadow\b": "shadowwen", - "\bShadowling\b": "shadowlingngen", "\bling\b": "lingngen", "\bsling\b": "slingngen", "\bMutineer\b": "mootinenner", @@ -992,4 +991,4 @@ "\bshell\b": "shellllen", "\bneck\b": "neckcken" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_french.json b/strings/accents/accent_french.json index 4cff06774de8..657ac8baab97 100644 --- a/strings/accents/accent_french.json +++ b/strings/accents/accent_french.json @@ -40,7 +40,6 @@ "\bClockwork\b": "clokwairk", "\bRevolution\b": "revulushé-on", "\bGangster\b": "gangstair", - "\bShadowling\b": "shadowleng", "\bling\b": "léng", "\bsling\b": "sleng", "\bMutineer\b": "mutineair", @@ -460,4 +459,4 @@ "\bshell\b": "shéll", "\bneck\b": "nek" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_germanlight.json b/strings/accents/accent_germanlight.json index 5fce13cb2581..921aa7de9eee 100644 --- a/strings/accents/accent_germanlight.json +++ b/strings/accents/accent_germanlight.json @@ -16,7 +16,6 @@ "\brevolution\b": "R-r-refolution", "\bwizard\b": "Vizart", "\bshadow\b": "Schadow", - "\bshadowling\b": "Schadowlingkt", "\bling\b": "lingkt", "\bsling\b": "slingkt", "\bvampire\b": "fampire", diff --git a/strings/accents/accent_kraut.json b/strings/accents/accent_kraut.json index 9f3b67ea4030..283a253d72f3 100644 --- a/strings/accents/accent_kraut.json +++ b/strings/accents/accent_kraut.json @@ -41,7 +41,6 @@ "\bGangster\b": "Kangster", "\bWizard\b": "Visard", "\bShadow\b": "Schatow", - "\bShadowling\b": "Schatovling", "\bvampire\b": "fambire", "\bbrothers\b": "prothers", "\bguardian\b": "guartian", @@ -593,4 +592,4 @@ "\bquotient\b": "guodient", "\bshell\b": "schell" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_pirate.json b/strings/accents/accent_pirate.json index 9a3905b23b23..28a8996da6f9 100644 --- a/strings/accents/accent_pirate.json +++ b/strings/accents/accent_pirate.json @@ -5,9 +5,7 @@ "\bOfficer\b": "bosun", "\bBartender\b": "barkeep", "\bchangeling\b": "changelin'", - "\bShadowling\b": "shadowlin'", "\bling\b": "lin'", - "\bsling\b": "slin'", "\bPirate\b": "corsair", "\bthe\b": "th'", "\bof\b": "o'", @@ -140,4 +138,4 @@ "\bdrink\b": "barrel o' rum", "\bliquid\b": "grog" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_piratelight.json b/strings/accents/accent_piratelight.json index 7f49cb27600a..4617c828fff6 100644 --- a/strings/accents/accent_piratelight.json +++ b/strings/accents/accent_piratelight.json @@ -3,7 +3,6 @@ "\bCaptain\b": "Cap'n", "\bOfficer\b": "bosun", "\bchangeling\b": "changelin'", - "\bShadowling\b": "Shadowlin'", "\bling\b": "lin'", "\bsling\b": "slin'", "\bPirate\b": "Corsair", @@ -80,4 +79,4 @@ "\bevening\b": "evenin'", "\bdrink\b": "barrel o' rum" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_shakespearean.json b/strings/accents/accent_shakespearean.json index 40ee83147e45..868ce3ac3459 100644 --- a/strings/accents/accent_shakespearean.json +++ b/strings/accents/accent_shakespearean.json @@ -574,4 +574,4 @@ "\blog\b": "logeth", "\bmeant\b": "meanteth" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_skaven.json b/strings/accents/accent_skaven.json index 8d2d1d3d5a5d..19e21e7b3cbd 100644 --- a/strings/accents/accent_skaven.json +++ b/strings/accents/accent_skaven.json @@ -55,9 +55,7 @@ "\bwizard\b": "clan-warp", "\bmagic\b": "warp-wielder", "\bshadow\b": "dark", - "\bshadowling\b": "dark-ling", "\bling\b": "thing", - "\bsling\b": "d-ling", "\bmutineer\b": "mutiny-thing", "\bvampire\b": "blood-sucker", "\bbrothers\b": "kin", @@ -274,4 +272,4 @@ "\bneck\b": "neck-neck", "\bsec\b": "clan" } -} \ No newline at end of file +} diff --git a/strings/accents/accent_valspeak.json b/strings/accents/accent_valspeak.json index bd3f616ba8a9..fe91c64f73c3 100644 --- a/strings/accents/accent_valspeak.json +++ b/strings/accents/accent_valspeak.json @@ -1,7 +1,6 @@ { "Valley": { "\bchangeling\b": "changelin'", - "\bShadowling\b": "Shadowlin'", "\bling\b": "lin'", "\bsling\b": "slin'", "\bthe\b": "thuh", @@ -44,4 +43,4 @@ "\bstring\b": "strin'", "\bevening\b": "evenin'" } -} \ No newline at end of file +} diff --git a/strings/names/nightmare.txt b/strings/names/nightmare.txt deleted file mode 100644 index 431a81d78690..000000000000 --- a/strings/names/nightmare.txt +++ /dev/null @@ -1,18 +0,0 @@ -U'ruan -Y`shej -Nex -Hel-uae -Noaey'gief -Mii`mahza -Amerziox -Gyrg-mylin -Kanet'pruunance -Vigistaezian -Mbux'iqguuh -Xulefux -Odmixe-wuxxe -Yix`sruaj -Ghvebi -Ywaown-yeldan -Upnoathyl -Zohkvdspun \ No newline at end of file diff --git a/tgui/packages/tgui-panel/chat/constants.js b/tgui/packages/tgui-panel/chat/constants.js index dab3172cef66..0c3a949d7e67 100644 --- a/tgui/packages/tgui-panel/chat/constants.js +++ b/tgui/packages/tgui-panel/chat/constants.js @@ -59,7 +59,7 @@ export const MESSAGE_TYPES = [ type: MESSAGE_TYPE_RADIO, name: 'Radio', description: 'All departments of radio messages', - selector: '.alert, .syndradio, .centradio, .airadio, .entradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .supradio, .srvradio, .expradio, .radio, .deptradio, .newscaster, .commonradio, .cultitalic, .shadowling, .aiprivradio', + selector: '.alert, .syndradio, .centradio, .airadio, .entradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .supradio, .srvradio, .expradio, .radio, .deptradio, .newscaster, .commonradio, .cultitalic, .aiprivradio', }, { type: MESSAGE_TYPE_INFO, diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss index 591480fb2443..cd6e1189991d 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss @@ -453,7 +453,13 @@ em { } .velvet { - color: #dfafb4; + color: #7264FF; +} + +.progenitor { + color: #7264FF; + font-weight: bold; + font-size: 185%; } .info { @@ -525,9 +531,6 @@ em { .nicegreen { color: #059223; } -.shadowling{ - color: #8e8a99; -} .cult { color: #973e3b; } diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/goon/chat-light.scss index 71e4f5d2f4e3..c77fb5b8f010 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-light.scss @@ -492,7 +492,13 @@ h1.alert, h2.alert { } .velvet { - color: #631f3a; + color: #21007F; +} + +.progenitor { + color: #21007F; + font-weight: bold; + font-size: 185%; } .info { @@ -608,9 +614,6 @@ h1.alert, h2.alert { font-weight: bold; font-size: 370%; } -.shadowling{ - color: #3b2769;; -} .brass { color: #BE8700; } diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index cceffd9529d6..e441ce1ac03f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -475,6 +475,16 @@ em { color: #ff5050; } +.velvet { + color: #7264FF; +} + +.progenitor { + color: #7264FF; + font-weight: bold; + font-size: 185%; +} + .info { color: #9ab0ff; } diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index d5861bfa6d0a..b8fb7f327c82 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -507,6 +507,16 @@ h2.alert { color: #ff5050; } +.velvet { + color: #21007F; +} + +.progenitor { + color: #21007F; + font-weight: bold; + font-size: 185%; +} + .info { color: #0000cc; } diff --git a/tgui/packages/tgui/interfaces/AntagInfoDarkspawn.tsx b/tgui/packages/tgui/interfaces/AntagInfoDarkspawn.tsx new file mode 100644 index 000000000000..c20a9036a55c --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoDarkspawn.tsx @@ -0,0 +1,437 @@ +import { capitalize } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Section, Stack, Box, Tabs, Button, BlockQuote, Flex } from '../components'; +import { Window } from '../layouts'; +import { BooleanLike } from 'common/react'; +import { ObjectivePrintout, Objective, ReplaceObjectivesButton } from './common/Objectives'; + +const Velvet = { + fontWeight: 'bold', + color: '#7264FF', +}; + +const Lucidity = { + fontWeight: 'bold', + color: '#00AAFF', +}; + +type Data = { + categories: Category[]; +}; + +type Category = { + name: String; + knowledgeData: Knowledge[]; +}; + +type Knowledge = { + path: string; + name: string; + desc: string; + lore_description: string; + cost: number; + disabled: boolean; + menutab: string; + infinite: boolean; + icon: string; +}; + +type Classes = { + classData: Class[]; +} + +type Class = { + path: string; + name: string; + description: string; + long_description: string; + color: string; +} + +type Info = { + willpower: number; + lucidity_drained: number; + divulged: BooleanLike; + ascended: BooleanLike; + max_thralls: number; + current_thralls: number; + has_class: BooleanLike; + objectives: Objective[]; + categories: Category[]; + thrall_names: string[]; +}; + +export const AntagInfoDarkspawn = (props, context) => { + const { data } = useBackend(context); + const { divulged, has_class } = data; + + const [currentTab, setTab] = useLocalState(context, 'currentTab', 0); + + return ( + + + + + setTab(0)}> + Information + + {!!divulged && !!has_class && ( + setTab(1)}> + Research + + )} + {!has_class && ( + setTab(2)}> + Class Selection + + )} + + + {(currentTab === 0 && ) || (currentTab === 1 && ) || } + + + + + ); +}; + +const IntroductionSection = (props, context) => { + const { data, act } = useBackend(context); + const { objectives, ascended } = data; + + return ( + + +
+ + + + + + + + {!ascended && ( + + + + )} + +
+
+
+ ); +}; + +const FlavorSection = () => { + return ( + + + + Eternity spent dormant, floating in an endless void. + + + Ripples herald chaos as a bright blue light tears you from your slumber.  + + + As you plunge into normalspace, you violently curse the being that caused such an event.  + + + You quickly fashion a crude facsimile of the lifeforms nearby. + + + It won't last long, but it's enough to trick these fools. + + + + + Nullspace awaits your return. + + + + ); +}; + +const GuideSection = (props, context) => { + const { data } = useBackend(context); + const { has_class } = data; + + return ( + + + - Collaborate with fellow darkspawns, use .w to converse using the mindlink. + + {!has_class && ( + + - Select a class in the selection tab to decide what kind of gameplay you want. + + )} + + - Incapacitate crewmembers and devour their will to gain lucidity and willpower. + + + - Spend willpower in the research tab to unlock new abilities and passive skills. + + + - Once a certain amount lucidity has been drained, perform the sacrament and ascend as a progenitor. + + + ); +}; + +const InformationSection = (props, context) => { + const { data } = useBackend(context); + const { willpower, lucidity_drained, ascended, current_thralls, max_thralls, thrall_names } = data; + return ( + + + {!!ascended && ( + + + You have + + + ASCENDED + + ! + + + + )} + + You have {willpower || 0}  + + willpower + + . + + + You have drained a total of  + {lucidity_drained || 0}  + lucidity. + + {!!max_thralls && ( + + You currently have {current_thralls || 0}/{max_thralls || 0}  + + thralls + + . + + )} + {!!current_thralls && !!thrall_names && ( + + They are: + {thrall_names.map(thrall => ( + {capitalize(thrall)} + ))} + + )} + + + ); +}; + +// Research tab +const ResearchInfo = (props, context) => { + const { act, data } = useBackend(context); + const { willpower, categories = [] } = data; + + const [selectedKnowledge, setSelectedKnowledge] = useLocalState(context, "knowledge", null); + + return ( + + + + + You have {willpower || 0}  + + willpower + {' '} + to spend. + + + + + + + + + + + + + + {selectedKnowledge && ( +
" - -/datum/objective/ascend - explanation_text = "Ascend to your true form by use of the Ascendance ability. This may only be used with 15 or more collective thralls, while hatched, and is unlocked with the Collective Mind ability." - -/datum/objective/ascend/check_completion() - if(..()) - return TRUE - return (SSticker && SSticker.mode && SSticker.mode.shadowling_ascended) - -/datum/objective/ascend/update_explanation_text() - explanation_text = "Ascend to your true form by use of the Ascendance ability. This may only be used with [SSticker.mode.required_thralls] or more collective thralls, while hatched, and is unlocked with the Collective Mind ability." - -/mob/living/carbon/human/get_status_tab_items() - . = ..() - if((dna && dna.species) && istype(dna.species, /datum/species/shadow/ling)) - var/datum/species/shadow/ling/SL = dna.species - . += "Shadowy Shield Charges: [SL.shadow_charges]" - -/datum/antagonist/shadowling/get_preview_icon() - var/icon/shadowling_icon = icon('yogstation/icons/mob/mob.dmi', "shadowling_ascended") - - shadowling_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE) - - return shadowling_icon diff --git a/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm b/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm deleted file mode 100644 index b9fa6625d1b2..000000000000 --- a/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm +++ /dev/null @@ -1,1199 +0,0 @@ -#define EMPOWERED_THRALL_LIMIT 3 - -/datum/action/cooldown/spell/proc/shadowling_check(mob/living/carbon/human/H) //what the fuck was this shit - if(!H || !istype(H)) - return FALSE - if(H.dna && H.dna.species && H.dna.species.id == "shadowling" && is_shadow(H)) - return TRUE - if(H.dna && H.dna.species && H.dna.species.id == "l_shadowling" && is_thrall(H)) - return TRUE - if(!is_shadow_or_thrall(owner)) - to_chat(owner, span_warning("You can't wrap your head around how to do this.")) - - if(is_thrall(owner)) - to_chat(owner, span_warning("You aren't powerful enough to do this.")) - - if(is_shadow(owner)) - to_chat(owner, span_warning("Your telepathic ability is suppressed. Hatch or use Rapid Re-Hatch first.")) - return FALSE - -/datum/action/cooldown/spell/pointed/sling - ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' - var/mob/living/user - -/datum/action/cooldown/spell/pointed/sling/InterceptClickOn(mob/living/caller, params, atom/t) - . = ..() - if(!.) - return FALSE - if(!isliving(t)) - to_chat(caller, span_warning("You may only use this ability on living things!")) - return FALSE - user = caller - target = t - if(!shadowling_check(user)) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/sling/proc/revert_cast() - unset_click_ability(owner) - -/datum/action/cooldown/spell/pointed/sling/proc/start_recharge() - build_all_button_icons() - -/datum/action/cooldown/spell/pointed/sling/glare //Stuns and mutes a human target for 10 seconds - name = "Glare" - desc = "Disrupts the target's motor and speech abilities. Much more effective within two meters." - panel = "Shadowling Abilities" - button_icon_state = "glare" - button_icon = 'yogstation/icons/mob/actions.dmi' - - cooldown_time = 30 SECONDS - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/sling/glare/InterceptClickOn(mob/living/caller, params, atom/target_atom) - . = ..() - if(!.) - return FALSE - if(!target_atom || !iscarbon(target_atom)) - revert_cast() - return FALSE - var/mob/living/carbon/target = target_atom - if(!caller.getorganslot(ORGAN_SLOT_EYES)) - to_chat(owner, span_warning("You need eyes to glare!")) - revert_cast() - return FALSE - if(target.stat) - to_chat(owner, span_warning("[target] must be conscious!")) - revert_cast() - return FALSE - if(is_shadow_or_thrall(target)) - to_chat(owner, span_warning("You cannot glare at allies!")) - revert_cast() - return FALSE - var/mob/living/carbon/human/M = target - usr.visible_message(span_warning("[owner]'s eyes flash a purpleish-red!")) - var/distance = get_dist(target, owner) - if (distance <= 2) - target.visible_message(span_danger("[target] suddendly collapses...")) - to_chat(target, span_userdanger("A purple light flashes across your vision, and you lose control of your movements!")) - target.Paralyze(10 SECONDS) - M.silent += 10 - else //Distant glare - var/loss = 100 - (distance * 10) - target.adjustStaminaLoss(loss) - if(iscarbon(target)) - target.adjust_stutter(loss) - else if(issilicon(target)) - target.adjust_stutter(distance) - to_chat(target, span_userdanger("A purple light flashes across your vision, and exhaustion floods your body...")) - target.visible_message(span_danger("[target] looks very tired...")) - start_recharge() - unset_click_ability(owner) - return TRUE - -/datum/action/cooldown/spell/aoe/veil //Puts out most nearby lights except for flares and yellow slime cores - name = "Veil" - desc = "Extinguishes most nearby light sources." - panel = "Shadowling Abilities" - button_icon_state = "veil" - button_icon = 'yogstation/icons/mob/actions.dmi' - - cooldown_time = 12 SECONDS //Short cooldown because people can just turn the lights back on - aoe_radius = 5 - var/admin_override = FALSE //Requested by Shadowlight213. Allows anyone to cast the spell, not just shadowlings. - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/aoe/proc/extinguishItem(obj/item/I, cold = FALSE) //Does not darken items held by mobs due to mobs having separate luminosity, use extinguish_mob() or write your own proc. - var/blacklisted_lights = list(/obj/item/flashlight/flare, /obj/item/flashlight/slime) - if(istype(I, /obj/item/flashlight)) - var/obj/item/flashlight/F = I - if(F.light_on) - if(cold) - if(is_type_in_list(F, blacklisted_lights)) - F.visible_message(span_warning("The sheer cold shatters [F]!")) - qdel(F) - else - return - if(is_type_in_list(I, blacklisted_lights)) - I.visible_message(span_danger("[I] dims slightly before scattering the shadows around it.")) - return F.light_power //Necessary because flashlights become 0-luminosity when held. I don't make the rules of lightcode. - F.light_on = FALSE - F.set_light_on(FALSE) - F.update_brightness() - else if(istype(I, /obj/item/pda)) - var/obj/item/pda/P = I - P.set_light_on(FALSE) - I.set_light_on(FALSE) - return I.luminosity - -/datum/action/cooldown/spell/aoe/proc/extinguish_mob(mob/living/H, cold = FALSE) - for(var/obj/item/F in H) - if(cold) - extinguishItem(F, TRUE) - extinguishItem(F) - if(iscarbon(H)) - var/mob/living/carbon/M = H - var/datum/mutation/human/glow/G = M.dna.get_mutation(GLOWY) - if(G) - G.glow.set_light_range_power_color(range = 0, power = 0) // Set glowy to no light - if(G.current_nullify_timer) - deltimer(G.current_nullify_timer) // Stacks - G.current_nullify_timer = addtimer(CALLBACK(src, PROC_REF(give_glow_back), M), 40 SECONDS, TIMER_STOPPABLE) - -/datum/action/cooldown/spell/aoe/proc/give_glow_back(mob/living/carbon/M) - if(!M) - return - var/datum/mutation/human/glow/G = M.dna.get_mutation(GLOWY) - if(G) - G.modify() // Re-sets glowy - G.current_nullify_timer = null - -/datum/action/cooldown/spell/aoe/veil/cast_on_thing_in_aoe(atom/target, atom/user) - if(!shadowling_check(owner) && !admin_override) - return - to_chat(owner, span_shadowling("You silently disable all nearby lights.")) - //var/turf/T = get_turf(owner) - for(var/datum/light_source/LS in target.light_sources) - var/atom/LO = LS.source_atom - if(isitem(LO)) - extinguishItem(LO) - continue - if(istype(LO, /obj/machinery/light)) - var/obj/machinery/light/L = LO - L.on = FALSE - L.visible_message(span_warning("[L] flickers and falls dark.")) - L.update(0) - L.set_light(0) - continue - if(istype(LO, /obj/machinery/computer) || istype(LO, /obj/machinery/power/apc)) - LO.set_light(0) - LO.visible_message(span_warning("[LO] grows dim, its screen barely readable.")) - continue - if(ismob(LO)) - extinguish_mob(LO) - if(istype(LO, /mob/living/silicon/robot)) - var/mob/living/silicon/robot/borg = LO - if(!borg.lamp_cooldown) - borg.smash_headlamp() - if(istype(LO, /obj/machinery/camera)) - LO.set_light(0) - if(prob(10)) - LO.emp_act(2) - continue - if(istype(LO, /obj/mecha)) - var/obj/mecha/M = LO - M.set_light(0) - M.lights = FALSE - if(istype(LO, /obj/machinery/power/floodlight)) - var/obj/machinery/power/floodlight/FL = LO - FL.change_setting(2) // Set floodlight to lowest setting - if(istype(LO, /obj/structure/light_prism)) - qdel(LO) - - for(var/obj/structure/glowshroom/G in orange(7, user)) //High radius because glowshroom spam wrecks shadowlings - if(G.light_power > 0) - var/obj/structure/glowshroom/shadowshroom/S = new /obj/structure/glowshroom/shadowshroom(G.loc) //I CAN FEEL THE WARP OVERTAKING ME! IT IS A GOOD PAIN! - S.generation = G.generation - G.visible_message(span_warning("[G] suddenly turns dark!")) - qdel(G) - for(var/turf/open/floor/grass/fairy/F in view(7, user)) - if(F.light_power > 0) - F.visible_message(span_warning("[F] suddenly turns dark!")) - F.ChangeTurf(/turf/open/floor/grass/fairy/dark, flags = CHANGETURF_INHERIT_AIR) - for(var/obj/structure/marker_beacon/M in view(7, user)) - M.deconstruct() - -/datum/action/cooldown/spell/aoe/flashfreeze //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, stunning them and causing burn damage while hampering their movement." - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "icy_veins" - - panel = "Shadowling Abilities" - sound = 'sound/effects/ghost2.ogg' - aoe_radius = 3 - cooldown_time = 1 MINUTES - var/special_lights = list(/obj/item/flashlight/flare, /obj/item/flashlight/slime) - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/aoe/flashfreeze/cast_on_thing_in_aoe(atom/target, atom/user) - if(!shadowling_check(owner)) - return - to_chat(owner, span_shadowling("You freeze the nearby air.")) - if(isturf(target)) - var/turf/T = target - for(var/mob/living/carbon/M in T.contents) - if(is_shadow_or_thrall(M)) - if(M == user) //No message for the user, of course - continue - else - to_chat(M, span_danger("You feel a blast of paralyzingly cold air wrap around you and flow past, but you are unaffected!")) - continue - to_chat(M, span_userdanger("A wave of shockingly cold air engulfs you!")) - M.Stun(2) - M.apply_damage(5, BURN) - if(M.bodytemperature) - M.adjust_bodytemperature(-100, 50) - if(M.reagents) - M.reagents.add_reagent(/datum/reagent/consumable/frostoil, 5) //some amount of a cryo sting fucked if I care - M.reagents.add_reagent(/datum/reagent/shadowfrost, 5) - extinguish_mob(M, TRUE) - for(var/obj/item/F in T.contents) - extinguishItem(F, TRUE) - -/datum/action/cooldown/spell/pointed/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties - name = "Enthrall" - desc = "Allows you to enslave a conscious, non-braindead, non-catatonic human to your will. This takes some time to cast." - panel = "Shadowling Abilities" - button_icon_state = "enthrall" - button_icon = 'yogstation/icons/mob/actions.dmi' - - cast_range = 1 //Adjacent to user - var/enthralling = FALSE - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/enthrall/InterceptClickOn(mob/living/caller, params, atom/target_atom) - . = ..() - if(!.) - return FALSE - var/mob/living/carbon/human/user = owner - listclearnulls(SSticker.mode.thralls) - if(!(user.mind in SSticker.mode.shadows)) return - if(user.dna.species.id != "shadowling") - if(SSticker.mode.thralls.len >= 5) - return FALSE - var/mob/living/target = target_atom - if(!target.key || !target.mind) - to_chat(user, span_warning("The target has no mind!")) - return FALSE - if(target.stat) - to_chat(user, span_warning("The target must be conscious!")) - return FALSE - if(is_shadow_or_thrall(target)) - to_chat(user, span_warning("You can not enthrall allies!")) - return FALSE - if(!ishuman(target)) - to_chat(user, span_warning("You can only enthrall humans!")) - return FALSE - if(enthralling) - to_chat(user, span_warning("You are already enthralling!")) - return FALSE - if(!target.client) - to_chat(user, span_warning("[target]'s mind is vacant of activity.")) - enthralling = TRUE - for(var/progress = 0, progress <= 3, progress++) - switch(progress) - if(1) - to_chat(user, span_notice("You place your hands to [target]'s head...")) - user.visible_message(span_warning("[user] places their hands onto the sides of [target]'s head!")) - if(2) - to_chat(user, span_notice("You begin preparing [target]'s mind as a blank slate...")) - user.visible_message(span_warning("[user]'s palms flare a bright red against [target]'s temples!")) - to_chat(target, span_danger("A terrible red light floods your mind. You collapse as conscious thought is wiped away.")) - target.Knockdown(12 SECONDS) - if(HAS_TRAIT(target, TRAIT_MINDSHIELD)) - if(ispreternis(target)) - to_chat(user, span_notice("Your servant's mind has been corrupted by other machinery. You begin to shut down the implant preventing your command - this will take some time...")) - user.visible_message(span_warning("[user] growls in frustration, then dips their head with determination!")) - else - to_chat(user, span_notice("They are protected by an implant. You begin to shut down the nanobots in their brain - this will take some time...")) - user.visible_message(span_warning("[user] pauses, then dips their head in concentration!")) - to_chat(target, span_boldannounce("You feel your mental protection faltering!")) - if(!do_after(user, 65 SECONDS, target)) //65 seconds to remove a loyalty implant. yikes! - to_chat(user, span_warning("The enthralling has been interrupted - your target's mind returns to its previous state.")) - to_chat(target, span_userdanger("You wrest yourself away from [user]'s hands and compose yourself!")) - enthralling = FALSE - return - to_chat(user, span_notice("The nanobots composing the mindshield implant have been rendered inert. Now to continue.")) - user.visible_message(span_warning("[user] relaxes again.")) - for(var/obj/item/implant/mindshield/L in target) - if(L) - qdel(L) - to_chat(target, span_boldannounce("Your mental protection unexpectedly falters, dims, dies.")) - if(3) - to_chat(user, span_notice("You begin planting the tumor that will control the new thrall...")) - user.visible_message(span_warning("A strange energy passes from [user]'s hands into [target]'s head!")) - to_chat(target, span_boldannounce("You feel your memories twisting, morphing. A sense of horror dominates your mind.")) - if(!do_after(user, 7 SECONDS, target)) //around 21 seconds total for enthralling, 86 for someone with a loyalty implant - to_chat(user, span_warning("The enthralling has been interrupted - your target's mind returns to its previous state.")) - to_chat(target, span_userdanger("You wrest yourself away from [user]'s hands and compose yourself!")) - enthralling = FALSE - return - enthralling = FALSE - to_chat(user, span_shadowling("You have enthralled [target.real_name]!")) - target.visible_message(span_big("[target] looks to have experienced a revelation!"), \ - span_warning("False faces all dark not real not real not--")) - target.setOxyLoss(0) //In case the shadowling was choking them out - if(iscarbon(target)) - var/mob/living/carbon/M = target - var/datum/mutation/human/glow/G = M.dna.get_mutation(GLOWY) - if(G) - M.dna.remove_mutation(GLOWY) - target.mind.special_role = "thrall" - var/obj/item/organ/internal/shadowtumor/ST = new - ST.Insert(target, FALSE, FALSE) - target.add_thrall() - if(target.reagents.has_reagent(/datum/reagent/consumable/frostoil)) //Stabilize body temp incase the sling froze them earlier - target.reagents.remove_reagent(/datum/reagent/consumable/frostoil) - to_chat(target, span_notice("You feel warmer... It feels good.")) - target.bodytemperature = 310 - - return TRUE - -/datum/action/cooldown/spell/shadowling_hivemind //Lets a shadowling talk to its allies - name = "Hivemind Commune" - desc = "Allows you to silently communicate with all other shadowlings and thralls." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "commune" - - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/shadowling_hivemind/cast(atom/cast_on) - . = ..() - if(!.) - return FALSE - if(!is_shadow(owner)) - to_chat(owner, span_warning("You must be a shadowling to do that!")) - return - var/text = sanitize(tgui_input_text(owner, "What do you want to say your thralls and fellow shadowlings?.", "Hive Chat", "")) - if(!text) - return - var/my_message = "\[Shadowling\] [owner.real_name]: [text]" - for(var/mob/M in GLOB.mob_list) - if(is_shadow_or_thrall(M)) - to_chat(M, my_message) - if(M in GLOB.dead_mob_list) - to_chat(M, "(F) [my_message]") - log_say("[owner.real_name]/[owner.key] : [text]") - - return TRUE - -/datum/action/cooldown/spell/shadowling_regenarmor //Resets a shadowling's species to normal, removes genetic defects, and re-equips their armor - name = "Rapid Re-Hatch" - desc = "Re-forms protective chitin that may be lost during cloning or similar processes." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "regen_armor" - - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/shadowling_regenarmor/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - if(!is_shadow(user)) - to_chat(user, span_warning("You must be a shadowling to do this!")) - return - user.visible_message(span_warning("[user]'s skin suddenly bubbles and shifts around their body!"), \ - span_shadowling("You regenerate your protective armor and cleanse your form of defects.")) - user.setCloneLoss(0) - user.equip_to_slot_or_del(new /obj/item/clothing/suit/space/shadowling(user), ITEM_SLOT_OCLOTHING) - user.equip_to_slot_or_del(new /obj/item/clothing/head/shadowling(user), ITEM_SLOT_HEAD) - user.set_species(/datum/species/shadow/ling) - - return TRUE - -/datum/action/cooldown/spell/collective_mind //Lets a shadowling bring together their thralls' strength, granting new abilities and a headcount - name = "Collective Hivemind" - desc = "Gathers the power of all of your thralls and compares it to what is needed for ascendance. Also gains you new abilities." - panel = "Shadowling Abilities" - button_icon_state = "collective_mind" - button_icon = 'yogstation/icons/mob/actions.dmi' - - cooldown_time = 10 SECONDS //10 second cooldown to prevent spam - spell_requirements = SPELL_REQUIRES_HUMAN - - var/blind_smoke_acquired = FALSE - var/screech_acquired = FALSE - var/reviveThrallAcquired = FALSE - var/null_charge_acquired = FALSE - -/datum/action/cooldown/spell/collective_mind/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - var/thralls = 0 - var/victory_threshold = SSticker.mode.required_thralls - var/mob/M - to_chat(user, span_shadowling("You focus your telepathic energies abound, harnessing and drawing together the strength of your thralls.")) - for(M in GLOB.alive_mob_list) - if(is_thrall(M)) - thralls++ - to_chat(M, span_shadowling("You feel hooks sink into your mind and pull.")) - if(!do_after(user, 3 SECONDS, user)) - to_chat(user, span_warning("Your concentration has been broken. The mental hooks you have sent out now retract into your mind.")) - return - if(thralls >= CEILING(3 * SSticker.mode.thrall_ratio, 1) && !screech_acquired) - screech_acquired = TRUE - to_chat(user, span_shadowling("The power of your thralls has granted you the Sonic Screech ability. This ability will shatter nearby windows and deafen enemies, plus stunning silicon lifeforms.")) - var/datum/action/cooldown/spell/aoe/unearthly_screech/screech = new(M) - screech.Grant(M) - if(thralls >= CEILING(5 * SSticker.mode.thrall_ratio, 1) && !blind_smoke_acquired) - blind_smoke_acquired = TRUE - to_chat(user, "The power of your thralls has granted you the Blinding Smoke ability. It will create a choking cloud that will blind any non-thralls who enter. \ - ") - to_chat(user, "The power of your thralls has granted you the Dark Acceleration ability. This will allow you to turn one of your thralls into a weaker shadowling. \ - ") - var/datum/action/cooldown/spell/pointed/empower_thrall/empower = new(M) - empower.Grant(M) - var/datum/action/cooldown/spell/blindness_smoke/smoke = new(M) - smoke.Grant(M) - if(thralls >= CEILING(7 * SSticker.mode.thrall_ratio, 1) && !null_charge_acquired) - null_charge_acquired = TRUE - to_chat(user, "The power of your thralls has granted you the Null Charge ability. This ability will drain an APC's contents to the void, preventing it from recharging \ - or sending power until repaired.") - var/datum/action/cooldown/spell/null_charge/null_charge = new(M) - null_charge.Grant(M) - if(thralls >= CEILING(9 * SSticker.mode.thrall_ratio, 1) && !reviveThrallAcquired) - reviveThrallAcquired = TRUE - to_chat(user, "The power of your thralls has granted you the Black Recuperation ability. This will, after a short time, bring a dead thrall completely back to life \ - with no bodily defects.") - var/datum/action/cooldown/spell/pointed/revive_thrall/revive = new(M) - revive.Grant(M) - if(thralls < victory_threshold) - to_chat(user, span_shadowling("You do not have the power to ascend. You require [victory_threshold] thralls, but only [thralls] living thralls are present.")) - else if(thralls >= victory_threshold) - to_chat(user, span_shadowling("You are now powerful enough to ascend. Use the Ascendance ability when you are ready.")) - to_chat(user, span_shadowling("You may find Ascendance in the Shadowling Evolution tab.")) - for(M in GLOB.alive_mob_list) - if(is_shadow(M)) - var/datum/action/cooldown/spell/collective_mind/CM - if(CM in M.actions) - M.actions -= CM - qdel(CM) - for(var/datum/action/cooldown/spell/shadowling_hatch/hatch in M.actions) - LAZYREMOVE(M.actions, hatch) - var/datum/action/cooldown/spell/shadowling_ascend/ascend = new(M) - ascend.Grant(M) - if(M == user) - to_chat(user, span_shadowling("You project this power to the rest of the shadowlings.")) - else - to_chat(M, span_shadowling("[user.real_name] has coalesced the strength of the thralls. You can draw upon it at any time to ascend. (Shadowling Evolution Tab)")) //Tells all the other shadowlings - - return TRUE - -/datum/action/cooldown/spell/null_charge - name = "Null Charge" - desc = "Empties an APC, preventing it from recharging until fixed." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "null_charge" - - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/null_charge/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - - var/list/local_objs = view(1, user) - var/obj/machinery/power/apc/target_apc - for(var/object in local_objs) - if(istype(object, /obj/machinery/power/apc)) - target_apc = object - break - - if(!target_apc) - to_chat(user, span_warning("You must stand next to an APC to drain it!")) - return - - //Free veil since you have to stand next to the thing for a while to depower it. - target_apc.set_light(0) - target_apc.visible_message(span_warning("The [target_apc] flickers and begins to grow dark.")) - - to_chat(user, span_shadowling("You dim the APC's screen and carefully begin siphoning its power into the void.")) - if(!do_after(user, 15 SECONDS, target_apc)) - //Whoops! The APC's light turns back on - to_chat(user, span_shadowling("Your concentration breaks and the APC suddenly repowers!")) - target_apc.set_light(2) - target_apc.visible_message(span_warning("The [target_apc] begins glowing brightly!")) - else - //We did it - to_chat(user, span_shadowling("You return the APC's power to the void, disabling it.")) - target_apc.cell?.charge = 0 //Sent to the shadow realm - target_apc.chargemode = 0 //Won't recharge either until an engineer hits the button - target_apc.charging = 0 - target_apc.update_appearance(UPDATE_ICON) - - return TRUE - -/datum/action/cooldown/spell/blindness_smoke //Spawns a cloud of smoke that blinds non-thralls/shadows and grants slight healing to shadowlings and their allies - name = "Blindness Smoke" - desc = "Spews a cloud of smoke which will blind enemies." - panel = "Shadowling Abilities" - button_icon_state = "black_smoke" - button_icon = 'yogstation/icons/mob/actions.dmi' - - sound = 'sound/effects/bamf.ogg' - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/blindness_smoke/cast(mob/living/carbon/human/user) //Extremely hacky - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - user.visible_message(span_warning("[user] bends over and coughs out a cloud of black smoke!")) - to_chat(user, span_shadowling("You regurgitate a vast cloud of blinding smoke.")) - var/obj/item/reagent_containers/glass/beaker/large/B = new /obj/item/reagent_containers/glass/beaker/large(user.loc) //hacky - B.reagents.clear_reagents() //Just in case! - B.invisibility = INFINITY //This ought to do the trick - B.reagents.add_reagent(/datum/reagent/shadowling_blindness_smoke, 10) - var/datum/effect_system/fluid_spread/smoke/chem/S = new - S.attach(B) - if(S) - S.set_up(4, location = B.loc, carry = B.reagents) - S.start() - qdel(B) - return TRUE - -/datum/action/cooldown/spell/aoe/unearthly_screech //Damages nearby windows, confuses nearby carbons, and outright stuns silly cones - name = "Sonic Screech" - desc = "Deafens, stuns, and confuses nearby people. Also shatters windows." - panel = "Shadowling Abilities" - button_icon_state = "screech" - button_icon = 'yogstation/icons/mob/actions.dmi' - - sound = 'sound/effects/screech.ogg' - aoe_radius = 7 - cooldown_time = 30 SECONDS - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/aoe/unearthly_screech/cast_on_thing_in_aoe(atom/target_atom, mob/living/user) - if(!shadowling_check(user)) - return - user.audible_message(span_warning("[user] lets out a horrible scream!")) - if(isturf(target)) - var/turf/T = target_atom - for(var/mob/target in T.contents) - if(is_shadow_or_thrall(target)) - if(target == user) //No message for the user, of course - continue - else - continue - if(iscarbon(target)) - var/mob/living/carbon/M = target - to_chat(M, span_danger("A spike of pain drives into your head and scrambles your thoughts!")) - M.adjust_confusion(10 SECONDS) - M.adjustEarDamage(0, 30)//as bad as a changeling shriek - else if(issilicon(target)) - var/mob/living/silicon/S = target - to_chat(S, span_warning("ERROR $!(@ ERROR )#^! SENSORY OVERLOAD \[$(!@#")) - playsound(S, 'sound/machines/warning-buzzer.ogg', 50, 1) - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread - sp.set_up(5, 1, S) - sp.start() - S.Paralyze(5 SECONDS) - for(var/obj/structure/window/W in T.contents) - W.take_damage(rand(80, 100)) - -/datum/action/cooldown/spell/pointed/empower_thrall //turns a thrall into a lesser shadowling - name = "Dark Acceleration" - desc = "Empowers a thrall. You can only have 3 empowered thralls at a time. Empowered thralls become lesser versions of yourself, gaining a small selection of your abilities as well as your healing in the dark and aversion to light." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "darksight" - - cast_range = 1 - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/empower_thral/InterceptClickOn(mob/living/user, params, atom/target) - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - if(!ishuman(target)) - return - var/mob/living/carbon/human/thrallToEmpower = target - if(!is_thrall(thrallToEmpower)) - to_chat(user, span_warning("[thrallToEmpower] is not a thrall.")) - return - if(thrallToEmpower.stat != CONSCIOUS) - to_chat(user, span_warning("[thrallToEmpower] must be conscious to become empowered.")) - return - if(thrallToEmpower.dna.species.id == "l_shadowling") - to_chat(user, span_warning("[thrallToEmpower] is already empowered.")) - return - var/empowered_thralls = 0 - for(var/datum/mind/M in SSticker.mode.thralls) - if(!ishuman(M.current)) - return - var/mob/living/carbon/human/H = M.current - if(H.dna.species.id == "l_shadowling") - empowered_thralls++ - if(empowered_thralls >= EMPOWERED_THRALL_LIMIT) - to_chat(user, span_warning("You cannot spare this much energy. There are too many empowered thralls.")) - return - user.visible_message(span_danger("[user] places their hands over [thrallToEmpower]'s face, red light shining from beneath."), \ - span_shadowling("You place your hands on [thrallToEmpower]'s face and begin gathering energy...")) - to_chat(thrallToEmpower, span_userdanger("[user] places their hands over your face. You feel energy gathering. Stand still...")) - if(!do_after(user, 8 SECONDS, thrallToEmpower)) - to_chat(user, span_warning("Your concentration snaps. The flow of energy ebbs.")) - return - to_chat(user, span_shadowling("You release a massive surge of power into [thrallToEmpower]!")) - user.visible_message(span_boldannounce("Red lightning surges into [thrallToEmpower]'s face!")) - playsound(thrallToEmpower, 'sound/weapons/Egloves.ogg', 50, 1) - playsound(thrallToEmpower, 'sound/machines/defib_zap.ogg', 50, 1) - user.Beam(thrallToEmpower,icon_state="red_lightning",time=1) - thrallToEmpower.Knockdown(5) - thrallToEmpower.visible_message(span_warning("[thrallToEmpower] collapses, their skin and face distorting!"), \ - span_userdanger("AAAAAAAAAAAAAAAAAAAGH-")) - if (!do_after(user, 5, thrallToEmpower)) - thrallToEmpower.Unconscious(1 MINUTES) - thrallToEmpower.visible_message(span_warning("[thrallToEmpower] gasps, and passes out!"), span_warning("That... feels nice....")) - to_chat(user, span_warning("We have been interrupted! [thrallToEmpower] will need to rest to recover.")) - return - thrallToEmpower.visible_message(span_warning("[thrallToEmpower] slowly rises, no longer recognizable as human."), \ - "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \ - darkness but wither slowly in light. In addition, Lesser Glare has been upgraded into it's true form, and you've been given the ability to turn off nearby lights.") - thrallToEmpower.set_species(/datum/species/shadow/ling/lesser) - for(var/datum/action/cooldown/spell/pointed/lesser_glare/lglare in thrallToEmpower.actions) - LAZYREMOVE(thrallToEmpower.actions, lglare) - - var/datum/action/cooldown/spell/pointed/sling/glare/sglare = new(thrallToEmpower) - sglare.Grant(thrallToEmpower) - - var/datum/action/cooldown/spell/aoe/veil/veil = new(thrallToEmpower) - veil.Grant(thrallToEmpower) - - var/datum/action/cooldown/spell/jaunt/void_jaunt/jaunt = new(thrallToEmpower) - jaunt.Grant(thrallToEmpower) - -/datum/action/cooldown/spell/pointed/revive_thrall //Completely revives a dead thrall - name = "Black Recuperation" - desc = "Revives or empowers a thrall." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "revive_thrall" - - cast_range = 1 - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/revive_thrall/InterceptClickOn(mob/living/user, params, atom/target) - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - if(!ishuman(target)) - return - var/mob/living/carbon/human/thrallToRevive = target - if(!is_thrall(thrallToRevive)) - to_chat(user, span_warning("[thrallToRevive] is not a thrall.")) - return - if(thrallToRevive.stat != DEAD) - to_chat(user, span_warning("[thrallToRevive] is not dead.")) - return - if(HAS_TRAIT(thrallToRevive, TRAIT_BADDNA)) - to_chat(user, span_warning("[thrallToRevive] is too far gone.")) - return - - user.visible_message(span_danger("[user] kneels over [thrallToRevive], placing their hands on \his chest."), \ - span_shadowling("You crouch over the body of your thrall and begin gathering energy...")) - thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive) - if(!do_after(user, 3 SECONDS, thrallToRevive)) - to_chat(user, span_warning("Your concentration snaps. The flow of energy ebbs.")) - return - to_chat(user, span_shadowling("You release a massive surge of power into [thrallToRevive]!")) - user.visible_message(span_boldannounce("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!")) - playsound(thrallToRevive, 'sound/weapons/Egloves.ogg', 50, 1) - playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1) - user.Beam(thrallToRevive,icon_state="red_lightning",time=1) - if(thrallToRevive.revive(full_heal = 1)) - thrallToRevive.visible_message( - span_boldannounce("[thrallToRevive] heaves in breath, dim red light shining in their eyes."), \ - span_shadowling("You have returned. One of your masters has brought you from the darkness beyond."), \ - ) - thrallToRevive.Knockdown(4) - thrallToRevive.emote("gasp") - playsound(thrallToRevive, "bodyfall", 50, 1) - if (!do_after(user, 2 SECONDS, thrallToRevive)) - thrallToRevive.Knockdown(50) - thrallToRevive.Unconscious(500) - thrallToRevive.visible_message(span_boldannounce("[thrallToRevive] collapses in exhaustion."), \ - span_warning("You collapse in exhaustion... nap..... dark.")) - - return TRUE - -/datum/action/cooldown/spell/pointed/shadowling_extend_shuttle - name = "Destroy Engines" - desc = "Sacrifice a thrall to extend the time of the emergency shuttle's arrival by fifteen minutes. This can only be used once." - panel = "Shadowling Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "extend_shuttle" - - cast_range = 1 - cooldown_time = 1 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/shadowling_extend_shuttle/InterceptClickOn(mob/living/user, params, atom/target_atom) - . = ..() - if(!.) - return FALSE - if(!shadowling_check(user)) - return - if(!ishuman(target_atom)) - return - var/mob/living/carbon/human/target = target_atom - if(target.stat) - return - if(!is_thrall(target)) - to_chat(user, span_warning("[target] must be a thrall.")) - return - if(SSshuttle.emergency.mode != SHUTTLE_CALL) - to_chat(user, "span class='warning'>The shuttle must be inbound only to the station.") - return - var/mob/living/carbon/human/M = target - user.visible_message(span_warning("[user]'s eyes flash a bright red!"), \ - span_notice("You begin to draw [M]'s life force.")) - M.visible_message(span_warning("[M]'s face falls slack, their jaw slightly distending."), \ - span_boldannounce("You are suddenly transported... far, far away...")) - if(!do_after(user, 5 SECONDS, M)) - to_chat(M, span_warning("You are snapped back to reality, your haze dissipating!")) - to_chat(user, span_warning("You have been interrupted. The draw has failed.")) - return - to_chat(user, span_notice("You project [M]'s life force toward the approaching shuttle, extending its arrival duration!")) - M.visible_message(span_warning("[M]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing."), \ - span_warning("...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...")) - M.dust() - - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/more_minutes = 9000 - var/timer = SSshuttle.emergency.timeLeft() - timer += more_minutes - priority_announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 15 minutes...", "System Failure", 'sound/misc/notice1.ogg') - SSshuttle.emergency.setTimer(timer) - SSshuttle.emergency_no_recall = TRUE - user.actions.Remove(src) //Can only be used once! - qdel(src) - - return TRUE - -//Loosely adapted from the Nightmare's Shadow Walk, but different enough that -//inheriting would have been more hacky code. -//Unlike Shadow Walk, jaunting shadowlings can move through lit areas unmolested, -//but take a constant stamina penalty while jaunting. -/datum/action/cooldown/spell/jaunt/void_jaunt - name = "Void Jaunt" - desc = "Move through the void for a time, avoiding mortal eyes and lights." - panel = "Shadowling Abilities" - button_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "jaunt" - - cooldown_time = 80 SECONDS - spell_requirements = SPELL_REQUIRES_HUMAN - var/apply_damage = TRUE - -/datum/action/cooldown/spell/jaunt/void_jaunt/cast(mob/living/user) - . = ..() - if(!.) - return FALSE - if(iscarbon(user)) //If we're not an ascendant sling - var/mob/living/carbon/C = user - if(C.on_fire) - user.visible_message(span_boldwarning("[user]'s body shudders and flickers into darkness for a moment!"), - span_shadowling("The void rejects the flames engulfing your body, throwing you back into the burning light!")) - return - if(!shadowling_check(user) && !istype(user, /mob/living/simple_animal/ascendant_shadowling)) - return - if(is_jaunting(user)) - exit_jaunt() - return - else - playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) - if(apply_damage) - user.visible_message(span_boldwarning("[user] melts into the shadows!"), - span_shadowling("Steeling yourself, you dive into the void.")) - else - user.visible_message(span_boldwarning("[user] melts into the shadows!"), - span_shadowling("You allow yourself to fall into the void.")) - user.SetAllImmobility(0) - user.setStaminaLoss(0, 0) - var/obj/effect/dummy/phased_mob/shadowling/S2 = new(get_turf(user.loc)) - S2.apply_damage = apply_damage - user.forceMove(S2) - S2.jaunter = user - S2.jaunt_spell = src - - return TRUE - -//Both have to be high to cancel out natural regeneration -#define VOIDJAUNT_STAM_PENALTY_DARK 10 -#define VOIDJAUNT_STAM_PENALTY_LIGHT 35 - -/obj/effect/dummy/phased_mob/shadowling - name = "darkness" - icon = 'icons/effects/effects.dmi' - icon_state = "nothing" - var/canmove = TRUE - density = FALSE - anchored = TRUE - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - - var/apply_damage = TRUE - var/move_delay = 0 //Time until next move allowed - var/move_speed = 2 //Deciseconds per move - - var/datum/action/cooldown/spell/jaunt/void_jaunt/jaunt_spell //what spell we actually came from (for forced cooldown) - -/obj/effect/dummy/phased_mob/shadowling/relaymove(mob/user, direction) - if(move_delay > world.time && apply_damage) //Ascendants get no slowdown - return - - move_delay = world.time + move_speed - var/turf/newLoc = get_step(src,direction) - forceMove(newLoc) - -/obj/effect/dummy/phased_mob/shadowling/proc/check_light_level() - var/turf/T = get_turf(src) - var/light_amount = T.get_lumcount() - if(!isliving(jaunter)) - return - var/mob/living/jaunter_living = jaunter - if(light_amount > LIGHT_DAM_THRESHOLD) //Increased penalty - jaunter_living.apply_damage(VOIDJAUNT_STAM_PENALTY_LIGHT, STAMINA) - else - jaunter_living.apply_damage(VOIDJAUNT_STAM_PENALTY_DARK, STAMINA) - -/obj/effect/dummy/phased_mob/shadowling/eject_jaunter(mob/living/unjaunter, turf/loc_override, forced) - if(unjaunter) - unjaunter.forceMove(get_turf(src)) - if(forced) - unjaunter.visible_message(span_boldwarning("A dark shape stumbles from a hole in the air and collapses!"), - span_shadowling("Straining, you use the last of your energy to force yourself from the void.")) - else - unjaunter.visible_message(span_boldwarning("A dark shape tears itself from nothingness!"), - span_shadowling("You exit the void.")) - - playsound(get_turf(jaunter), 'sound/magic/ethereal_exit.ogg', 50, 1, -1) - jaunt_spell?.StartCooldown() - jaunter = null - qdel(src) - -/obj/effect/dummy/phased_mob/shadowling/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/effect/dummy/phased_mob/shadowling/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/effect/dummy/phased_mob/shadowling/process() - if(!jaunter) - qdel(src) - if(jaunter.loc != src) - qdel(src) - - if(apply_damage) - check_light_level() - - //True if jaunter entered stamcrit - var/mob/living/jaunter_living = jaunter - if(jaunter_living.IsParalyzed()) - eject_jaunter(forced = TRUE) - return - -/obj/effect/dummy/phased_mob/shadowling/ex_act() - return - -/obj/effect/dummy/phased_mob/shadowling/bullet_act() - return BULLET_ACT_FORCE_PIERCE - -/obj/effect/dummy/phased_mob/shadowling/singularity_act() - return - -#undef VOIDJAUNT_STAM_PENALTY_DARK -#undef VOIDJAUNT_STAM_PENALTY_LIGHT - - -// THRALL ABILITIES BEYOND THIS POINT // -/datum/action/cooldown/spell/pointed/lesser_glare //a defensive ability, nothing else. can't be used to stun people, steal tasers, etc. Just good for escaping - name = "Lesser Glare" - desc = "Makes a single target dizzy for a bit." - panel = "Thrall Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "glare" - ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' - - cooldown_time = 45 SECONDS - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/pointed/lesser_glare/InterceptClickOn(mob/living/carbon/user, params, atom/target) - . = ..() - if(!.) - return FALSE - if(!user.getorganslot(ORGAN_SLOT_EYES)) - to_chat(user, span_warning("You need eyes to glare!")) - return - if(!ishuman(target) || !target) - to_chat(user, span_warning("You may only glare at humans!")) - return - var/mob/living/carbon/human/M = target - if(M.stat) - to_chat(user, span_warning("[target] must be conscious!")) - return - if(is_shadow_or_thrall(M)) - to_chat(user, span_warning("You cannot glare at allies!")) - return - user.visible_message(span_warning("[user]'s eyes flash a bright red!")) - target.visible_message(span_danger("[target] suddendly looks dizzy and nauseous...")) - if(in_range(target, user)) - to_chat(target, span_userdanger("Your gaze is forcibly drawn into [user]'s eyes, and you suddendly feel dizzy and nauseous...")) - else //Only alludes to the thrall if the target is close by - to_chat(target, span_userdanger("Red lights suddenly dance in your vision, and you suddendly feel dizzy and nauseous...")) - M.adjust_confusion(25 SECONDS) - M.adjust_jitter(50 SECONDS) - if(prob(25)) - M.vomit(10) - - return TRUE - -/datum/action/cooldown/spell/lesser_shadow_walk //Thrall version of Shadow Walk, only works in darkness, doesn't grant phasing, but gives near-invisibility - name = "Guise" - desc = "Wraps your form in shadows, making you harder to see." - panel = "Thrall Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "shadow_walk" - - cooldown_time = 2 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/lesser_shadow_walk/proc/reappear(mob/living/carbon/human/user) - user.visible_message(span_warning("[user] appears from nowhere!"), span_shadowling("Your shadowy guise slips away.")) - user.alpha = initial(user.alpha) - -/datum/action/cooldown/spell/lesser_shadow_walk/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - user.visible_message(span_warning("[user] suddenly fades away!"), span_shadowling("You veil yourself in darkness, making you harder to see.")) - user.alpha = 10 - addtimer(CALLBACK(src, PROC_REF(reappear), user), 10 SECONDS) - - return TRUE - -/datum/action/cooldown/spell/thrall_night_vision //Toggleable night vision for thralls - name = "Thrall Darksight" - desc = "Allows you to see in the dark!" - button_icon_state = "darksight" - button_icon = 'yogstation/icons/mob/actions.dmi' - - spell_requirements = NONE - -// /datum/action/cooldown/spell/thrall_night_vision/cast(mob/living/carbon/human/user) -// . = ..() -// if(!.) -// return FALSE -// if(!is_shadow_or_thrall(user)) -// return -// var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) -// if(!eyes) -// return -// eyes.sight_flags = initial(eyes.sight_flags) -// switch(eyes.lighting_alpha) -// if (LIGHTING_PLANE_ALPHA_VISIBLE) -// eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE -// eyes.see_in_dark = 8 -// if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) -// eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE -// if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) -// eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE -// else -// eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE -// eyes.see_in_dark = 2 //default -// user.update_sight() - -// return TRUE - -/datum/action/cooldown/spell/lesser_shadowling_hivemind //Lets a thrall talk with their allies - name = "Lesser Commune" - desc = "Allows you to silently communicate with all other shadowlings and thralls." - panel = "Thrall Abilities" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "commune" - - cooldown_time = 5 SECONDS - spell_requirements = SPELL_REQUIRES_HUMAN - -/datum/action/cooldown/spell/lesser_shadowling_hivemind/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - if(!is_shadow_or_thrall(user)) - to_chat(user, span_warning("As you attempt to commune with the others, an agonizing spike of pain drives itself into your head!")) - user.apply_damage(10, BRUTE, "head") - return - var/text = stripped_input(user, "What do you want to say your masters and fellow thralls?.", "Lesser Commune", "") - if(!text) - return - text = span_shadowling("\[Thrall\] [user.real_name]: [text]") - for(var/T in GLOB.alive_mob_list) - var/mob/M = T - if(is_shadow_or_thrall(M)) - to_chat(M, text) - if(isobserver(M)) - to_chat(M, "(F) [text]") - log_say("[user.real_name]/[user.key] : [text]") - - return TRUE - -// ASCENDANT ABILITIES BEYOND THIS POINT // -// YES THEY'RE OP, BUT THEY'VE WON AT THE POINT WHERE THEY HAVE THIS, SO WHATEVER. // -/datum/action/cooldown/spell/pointed/sling/annihilate //Gibs someone instantly. - name = "Annihilate" - desc = "Gibs someone instantly." - panel = "Ascendant" - button_icon_state = "annihilate" - button_icon = 'yogstation/icons/mob/actions.dmi' - - sound = 'sound/magic/Staff_Chaos.ogg' - spell_requirements = NONE - -/datum/action/cooldown/spell/pointed/sling/annihilate/InterceptClickOn(mob/living/caller, params, atom/target) - . = ..() - if(!.) - return FALSE - var/mob/living/boom = target - if(user.incorporeal_move) - to_chat(user, span_warning("You are not in the same plane of existence. Unphase first.")) - revert_cast() - return - if(is_shadow(boom)) //Used to not work on thralls. Now it does so you can PUNISH THEM LIKE THE WRATHFUL GOD YOU ARE. - to_chat(user, "Making an ally explode seems unwise.") - revert_cast() - return - if(istype(boom, /mob/living/simple_animal/pet/dog/corgi)) - to_chat(user, "Not even we are that bad of monsters..") - revert_cast() - return - if (!boom.is_holding(/obj/item/storage/backpack/holding)) //so people actually have a chance to kill ascended slings without being insta-sploded - user.visible_message(span_warning("[user]'s markings flare as they gesture at [boom]!"), \ - span_shadowling("You direct a lance of telekinetic energy into [boom].")) - if(iscarbon(boom)) - playsound(boom, 'sound/magic/Disintegrate.ogg', 100, 1) - boom.visible_message(span_userdanger("[boom] explodes!")) - boom.gib() - else - to_chat(user, "The telekinetic energy is absorbed by the bluespace portal in [boom]'s hand!") - to_chat(boom, "You feel a slight recoil from the bag of holding!") - -/datum/action/cooldown/spell/pointed/sling/hypnosis //Enthralls someone instantly. Nonlethal alternative to Annihilate - name = "Hypnosis" - desc = "Instantly enthralls a human." - panel = "Ascendant" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "enthrall" - - spell_requirements = NONE - -/datum/action/cooldown/spell/pointed/sling/hypnosis/InterceptClickOn(mob/living/caller, params, atom/target_atom) - . = ..() - if(!.) - return - if(!iscarbon(target_atom)) - return - var/mob/living/carbon/target = target_atom - if(user.incorporeal_move) - revert_cast() - to_chat(user, span_warning("You are not in the same plane of existence. Unphase first.")) - return - if(is_shadow_or_thrall(target)) - to_chat(user, span_warning("You cannot enthrall an ally.")) - revert_cast() - return - if(!target.ckey || !target.mind) - to_chat(user, span_warning("The target has no mind.")) - revert_cast() - return - if(target.stat) - to_chat(user, span_warning("The target must be conscious.")) - revert_cast() - return - if(!ishuman(target)) - to_chat(user, span_warning("You can only enthrall humans.")) - revert_cast() - return - to_chat(user, span_shadowling("You instantly rearrange [target]'s memories, hyptonitizing them into a thrall.")) - to_chat(target, span_userdanger("An agonizing spike of pain drives into your mind, and--")) - target.mind.special_role = "thrall" - target.add_thrall() - -/datum/action/cooldown/spell/aoe/ascendant_storm //Releases bolts of lightning to everyone nearby - name = "Lightning Storm" - desc = "Shocks everyone nearby." - panel = "Ascendant" - button_icon_state = "lightning_storm" - button_icon = 'yogstation/icons/mob/actions.dmi' - - sound = 'sound/magic/lightningbolt.ogg' - aoe_radius = 6 - cooldown_time = 10 SECONDS - spell_requirements = NONE - -/datum/action/cooldown/spell/aoe/ascendant_storm/cast_on_thing_in_aoe(turf/victim, mob/living/user) - if(user.incorporeal_move) - to_chat(user, span_warning("You are not in the same plane of existence. Unphase first.")) - return - user.visible_message(span_warning("A massive ball of lightning appears in [user]'s hands and flares out!"), \ - span_shadowling("You conjure a ball of lightning and release it.")) - for(var/mob/living/carbon/human/target in view(aoe_radius)) - if(is_shadow_or_thrall(target)) - continue - to_chat(target, span_userdanger("You're struck by a bolt of lightning!")) - target.apply_damage(10, BURN) - playsound(target, 'sound/magic/LightningShock.ogg', 50, 1) - target.Knockdown(8 SECONDS) - user.Beam(target,icon_state="red_lightning",time=10) - -/datum/action/cooldown/spell/shadowling_hivemind_ascendant //Large, all-caps text in shadowling chat - name = "Ascendant Commune" - desc = "Allows you to LOUDLY communicate with all other shadowlings and thralls." - panel = "Ascendant" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "commune" - - spell_requirements = NONE - -/datum/action/cooldown/spell/shadowling_hivemind_ascendant/cast(mob/living/carbon/human/user) - . = ..() - if(!.) - return FALSE - var/text = sanitize(tgui_input_text(user, "What do you want to say to fellow thralls and shadowlings?.", "Hive Chat", "")) - if(!text) - return - text = "\[Ascendant\] [user.real_name]: [text]" - for(var/mob/M in GLOB.mob_list) - if(is_shadow_or_thrall(M)) - to_chat(M, text) - if(isobserver(M)) - to_chat(M, "(F) [text]") - log_say("[user.real_name]/[user.key] : [text]") - -/datum/action/cooldown/spell/pointed/sling/instant_enthrall //Enthralls someone instantly. Nonlethal alternative to Annihilate - name = "Subjugate" - desc = "Instantly enthrall a weakling." - panel = "Ascendant" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "gore" - - spell_requirements = NONE - -/datum/action/cooldown/spell/jaunt/void_jaunt/ascendant - name = "Void Walk" - desc = "Move invisibly through the void between worlds, shielded from mortal eyes." - panel = "Ascendant" - - apply_damage = FALSE diff --git a/yogstation/code/modules/antagonists/shadowling/shadowling_items.dm b/yogstation/code/modules/antagonists/shadowling/shadowling_items.dm deleted file mode 100644 index c1f40312c7d6..000000000000 --- a/yogstation/code/modules/antagonists/shadowling/shadowling_items.dm +++ /dev/null @@ -1,42 +0,0 @@ -/obj/item/clothing/suit/space/shadowling - name = "chitin shell" - desc = "A dark, semi-transparent shell. Protects against vacuum, but not against the light of the stars." //Still takes damage from spacewalking but is immune to space itself - mob_overlay_icon = 'yogstation/icons/mob/clothing/suit/suit.dmi' - icon = 'yogstation/icons/obj/clothing/suits.dmi' - icon_state = "sl_shell" - item_state = "sl_shell" - body_parts_covered = FULL_BODY //Shadowlings are immune to space - cold_protection = FULL_BODY - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEGLOVES | HIDESHOES | HIDEJUMPSUIT - slowdown = 0 - heat_protection = null //You didn't expect a light-sensitive creature to have heat resistance, did you? - max_heat_protection_temperature = null - armor = list(MELEE = 30, BULLET = 30, LASER = 0, ENERGY = 0, BOMB = 25, BIO = 100, RAD = 100) - item_flags = ABSTRACT | DROPDEL - clothing_flags = THICKMATERIAL | STOPSPRESSUREDAMAGE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/clothing/suit/space/shadowling/Initialize(mapload) - .=..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/obj/item/clothing/head/shadowling - name = "chitin helm" - desc = "A helmet-like enclosure of the head." - mob_overlay_icon = 'yogstation/icons/mob/clothing/suit/suit.dmi' - icon = 'yogstation/icons/obj/clothing/hats.dmi' - icon_state = "sl_head" - item_state = "sl_head" - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - armor = list(MELEE = 30, BULLET = 30, LASER = 0, ENERGY = 0, BOMB = 25, BIO = 100, RAD = 100) - clothing_flags = STOPSPRESSUREDAMAGE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - item_flags = ABSTRACT | DROPDEL - -/obj/item/clothing/head/shadowling/Initialize(mapload) - .=..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) diff --git a/yogstation/code/modules/antagonists/shadowling/special_shadowling_abilities.dm b/yogstation/code/modules/antagonists/shadowling/special_shadowling_abilities.dm deleted file mode 100644 index 191d4c687865..000000000000 --- a/yogstation/code/modules/antagonists/shadowling/special_shadowling_abilities.dm +++ /dev/null @@ -1,228 +0,0 @@ -//In here: Hatch and Ascendance -/datum/action/cooldown/spell/shadowling_hatch - name = "Hatch" - desc = "Casts off your disguise." - panel = "Shadowling Evolution" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "hatch" - - cooldown_time = 5 MINUTES - spell_requirements = SPELL_REQUIRES_HUMAN - -/obj/structure/alien/resin/wall/shadowling //For chrysalis - name = "chrysalis wall" - desc = "Some sort of purple substance in an egglike shape. It pulses and throbs from within and seems impenetrable." - max_integrity = INFINITY - -/datum/action/cooldown/spell/shadowling_hatch/cast(mob/living/user) - . = ..() - if(!.) - return FALSE - if(user.stat || !ishuman(user) || !user || !is_shadow(user) || user.isinspace()) - return - var/mob/living/carbon/human/H = user - var/hatch_or_no = tgui_alert(H,"Are you sure you want to hatch? You cannot undo this!","Ello",list("Yes","No")) - switch(hatch_or_no) - if("No") - to_chat(H, span_warning("You decide against hatching for now.")) - return - if("Yes") - H.notransform = TRUE - H.visible_message(span_warning("[H]'s things suddenly slip off. They hunch over and vomit up a copious amount of purple goo which begins to shape around them!"), \ - span_shadowling("You remove any equipment which would hinder your hatching and begin regurgitating the resin which will protect you.")) - var/temp_flags = H.status_flags - H.status_flags |= GODMODE //Can't die while hatching - H.unequip_everything() - if(!do_after(H, 5 SECONDS)) - return - var/turf/shadowturf = get_turf(user) - for(var/turf/open/floor/F in orange(1, user)) - new /obj/structure/alien/resin/wall/shadowling(F) - for(var/obj/structure/alien/resin/wall/shadowling/R in shadowturf) //extremely hacky - qdel(R) - new /obj/structure/alien/weeds/node(shadowturf) //Dim lighting in the chrysalis -- removes itself afterwards - H.visible_message(span_warning("A chrysalis forms around [H], sealing them inside."), \ - span_shadowling("You create your chrysalis and begin to contort within.")) - if(!do_after(H, 10 SECONDS)) - return - H.visible_message(span_warning("The skin on [H]'s back begins to split apart. Black spines slowly emerge from the divide."), \ - span_shadowling("Spines pierce your back. Your claws break apart your fingers. You feel excruciating pain as your true form begins its exit.")) - if(!do_after(H, 9 SECONDS)) - return - H.visible_message(span_warning("[H], skin shifting, begins tearing at the walls around them."), \ - span_shadowling("Your false skin slips away. You begin tearing at the fragile membrane protecting you.")) - if(!do_after(H, 8 SECONDS)) - return - playsound(H.loc, 'sound/weapons/slash.ogg', 25, 1) - to_chat(H, "You rip and slice.") - if(!do_after(H, 1 SECONDS)) - return - playsound(H.loc, 'sound/weapons/slashmiss.ogg', 25, 1) - to_chat(H, "The chrysalis falls like water before you.") - if(!do_after(H, 1 SECONDS)) - return - playsound(H.loc, 'sound/weapons/slice.ogg', 25, 1) - to_chat(H, "You are free!") - H.status_flags = temp_flags - if(!do_after(H, 1 SECONDS)) - return - playsound(H.loc, 'sound/effects/ghost.ogg', 100, 1) - var/newNameId = pick(GLOB.nightmare_names) - var/oldName = H.real_name - GLOB.nightmare_names.Remove(newNameId) - H.real_name = newNameId - H.name = user.real_name - H.notransform = FALSE - to_chat(H, "YOU LIVE!!!") - var/hatchannounce = "[oldName] has hatched into the Shadowling [newNameId]!" - for(var/T in GLOB.alive_mob_list) - var/mob/M = T - if(is_shadow_or_thrall(M)) - to_chat(M, hatchannounce) - for(var/T in GLOB.dead_mob_list) - var/mob/M = T - to_chat(M, "(F) [hatchannounce]") - for(var/obj/structure/alien/resin/wall/shadowling/W in orange(1, H)) - playsound(W, 'sound/effects/splat.ogg', 50, 1) - qdel(W) - for(var/obj/structure/alien/weeds/node/N in shadowturf) - qdel(N) - H.visible_message(span_warning("The chrysalis explodes in a shower of purple flesh and fluid!")) - H.underwear = "Nude" - H.undershirt = "Nude" - H.socks = "Nude" - H.faction |= "faithless" - for(var/datum/antagonist/shadowling/antag_datum in H.mind.antag_datums) - antag_datum.show_to_ghosts = TRUE - H.LoadComponent(/datum/component/walk/shadow) - - H.equip_to_slot_or_del(new /obj/item/clothing/suit/space/shadowling(H), ITEM_SLOT_OCLOTHING) - H.equip_to_slot_or_del(new /obj/item/clothing/head/shadowling(H), ITEM_SLOT_HEAD) - H.set_species(/datum/species/shadow/ling) //can't be a shadowling without being a shadowling - H.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), TRUE) - Remove(H) - if(!do_after(H, 10 SECONDS)) - return - to_chat(H, span_shadowling("Your powers are awoken. You may now live to your fullest extent. Remember your goal. Cooperate with your thralls and allies.")) - - var/datum/action/cooldown/spell/pointed/enthrall/enthrall = new(H) - enthrall.Grant(H) - - var/datum/action/cooldown/spell/pointed/sling/glare/glare = new(H) - glare.Grant(H) - - var/datum/action/cooldown/spell/aoe/veil/veil = new(H) - veil.Grant(H) - - var/datum/action/cooldown/spell/jaunt/void_jaunt/void_jaunt = new(H) - void_jaunt.Grant(H) - - var/datum/action/cooldown/spell/jaunt/shadow_walk/shadow_walk = new(H) - shadow_walk.Grant(H) - - var/datum/action/cooldown/spell/aoe/flashfreeze/flashfreeze = new(H) - flashfreeze.Grant(H) - - var/datum/action/cooldown/spell/collective_mind/mind = new(H) - mind.Grant(H) - - var/datum/action/cooldown/spell/shadowling_regenarmor/regen_armor = new(H) - regen_armor.Grant(H) - - var/datum/action/cooldown/spell/pointed/shadowling_extend_shuttle/shuttle_extend = new(H) - shuttle_extend.Grant(H) - - return TRUE - -/datum/action/cooldown/spell/shadowling_ascend - name = "Ascend" - desc = "Enters your true form." - panel = "Shadowling Evolution" - button_icon = 'yogstation/icons/mob/actions.dmi' - button_icon_state = "ascend" - - cooldown_time = 5 MINUTES - spell_requirements = NONE - -/datum/action/cooldown/spell/shadowling_ascend/before_cast(mob/living/user) - . = ..() - if(!.) - return - var/mob/living/carbon/human/H = user - if(!shadowling_check(H)) - return - var/hatch_or_no = tgui_alert(H,"It is time to ascend. Are you sure about this?",,list("Yes","No")) - switch(hatch_or_no) - if("No") - to_chat(H, span_warning("You decide against ascending for now.")) - return - if("Yes") - H.notransform = 1 - H.visible_message(span_warning("[H]'s things suddenly slip off. They gently rise into the air, red light glowing in their eyes."), \ - span_shadowling("You rise into the air and get ready for your transformation.")) - for(var/obj/item/I in H) //drops all items - H.unequip_everything(I) - if(!do_after(H, 5 SECONDS)) - return - H.visible_message(span_warning("[H]'s skin begins to crack and harden."), \ - span_shadowling("Your flesh begins creating a shield around yourself.")) - if(!do_after(H, 10 SECONDS)) - return - H.visible_message(span_warning("The small horns on [H]'s head slowly grow and elongate."), \ - span_shadowling("Your body continues to mutate. Your telepathic abilities grow.")) //y-your horns are so big, senpai...!~ - if(!do_after(H, 9 SECONDS)) - return - H.visible_message(span_warning("[H]'s body begins to violently stretch and contort."), \ - span_shadowling("You begin to rend apart the final barriers to godhood.")) - if(!do_after(H, 4 SECONDS)) - return - to_chat(H, "Yes!") - if(!do_after(H, 1 SECONDS)) - return - to_chat(H, "[span_big("YES!!")]") - if(!do_after(H, 1 SECONDS)) - return - to_chat(H, "[span_reallybig("YE--")]") - if(!do_after(H, 0.1 SECONDS)) - return - for(var/mob/living/M in orange(7, H)) - M.Knockdown(10) - to_chat(M, span_userdanger("An immense pressure slams you onto the ground!")) - send_to_playing_players("\"VYSHA NERADA YEKHEZET U'RUU!!\"") - sound_to_playing_players('sound/hallucinations/veryfar_noise.ogg') - for(var/obj/machinery/power/apc/A in GLOB.apcs_list) - A.overload_lighting() - SSachievements.unlock_achievement(/datum/achievement/greentext/slingascend, H.client) - var/mob/A = new /mob/living/simple_animal/ascendant_shadowling(H.loc) - for(var/datum/action/spell in H.actions) - if(spell == src) - continue - spell.Remove(H) - H.mind.transfer_to(A) - A.name = H.real_name - if(A.real_name) - A.real_name = H.real_name - H.invisibility = 60 //This is pretty bad, but is also necessary for the shuttle call to function properly - H.forceMove(A) - if(!SSticker.mode.shadowling_ascended) - SSsecurity_level.set_level(SEC_LEVEL_GAMMA) - SSshuttle.emergency_call_time = 1800 - SSshuttle.emergency.request(null, 0.3) - SSshuttle.emergency_no_recall = TRUE - SSticker.mode.shadowling_ascended = TRUE - var/datum/action/cooldown/spell/pointed/sling/annihilate/annihilate = new(A) - annihilate.Grant(A) - - var/datum/action/cooldown/spell/pointed/sling/hypnosis/hypnosis = new(A) - hypnosis.Grant(A) - - var/datum/action/cooldown/spell/aoe/ascendant_storm/storm = new(A) - storm.Grant(A) - - var/datum/action/cooldown/spell/jaunt/void_jaunt/ascendant/jaunt = new(A) - jaunt.Grant(A) - - var/datum/action/cooldown/spell/shadowling_hivemind_ascendant/hivemind = new(A) - hivemind.Grant(A) - Remove(A) - qdel(H) diff --git a/yogstation/code/modules/antagonists/shadowling/thrall.dm b/yogstation/code/modules/antagonists/shadowling/thrall.dm deleted file mode 100644 index c25d821af63a..000000000000 --- a/yogstation/code/modules/antagonists/shadowling/thrall.dm +++ /dev/null @@ -1,71 +0,0 @@ -GLOBAL_LIST_INIT(thrall_spell_types, typecacheof(list(/datum/action/cooldown/spell/lesser_shadowling_hivemind, /datum/action/cooldown/spell/pointed/lesser_glare, /datum/action/cooldown/spell/lesser_shadow_walk, /datum/action/cooldown/spell/thrall_night_vision))) - -/datum/antagonist/thrall - name = "Shadowling Thrall" - job_rank = ROLE_SHADOWLING - antag_hud_name = "thrall" - roundend_category = "thralls" - antagpanel_category = "Shadowlings" - antag_moodlet = /datum/mood_event/thrall - -/datum/antagonist/thrall/can_be_owned(datum/mind/new_owner) - . = ..() - if(.) - var/list/no_team_antag = list( - /datum/antagonist/rev, - /datum/antagonist/clockcult, - /datum/antagonist/darkspawn, - /datum/antagonist/cult, - /datum/antagonist/zombie - ) - for(var/datum/antagonist/NTA in new_owner.antag_datums) - if(NTA.type in no_team_antag) - return FALSE - -/datum/antagonist/thrall/on_gain() - . = ..() - SSticker.mode.thralls += owner - owner.special_role = "thrall" - message_admins("[key_name_admin(owner.current)] was enthralled by a shadowling!") - log_game("[key_name(owner.current)] was enthralled by a shadowling!") - for(var/datum/action/cooldown/spell/spells as anything in GLOB.thrall_spell_types) - spells = new(owner.current) - spells.Grant(owner.current) - -/datum/antagonist/thrall/apply_innate_effects(mob/living/mob_override) - . = ..() - var/mob/living/current_mob = mob_override || owner.current - add_team_hud(current_mob, /datum/antagonist/shadowling) - -/datum/antagonist/thrall/on_removal() - SSticker.mode.thralls -= owner - message_admins("[key_name_admin(owner.current)] was dethralled!") - log_game("[key_name(owner.current)] was dethralled!") - owner.special_role = null - for(var/datum/action/cooldown/spell/spells in owner.current.actions) - if(is_type_in_typecache(spells, GLOB.thrall_spell_types)) //only remove thrall spells! - spells.Remove(owner.current) - 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 shadowlings or your time under their command...")) - M.update_sight() - return ..() - -/datum/antagonist/thrall/greet() - to_chat(owner, span_shadowling("You see the truth. Reality has been torn away and you realize what a fool you've been.")) - if(ispreternis(owner.current)) - to_chat(owner, span_shadowling("The shadowlings- your creators, have returned to become gods. Serve them above all else and ensure they complete their goals.")) - else - to_chat(owner, span_shadowling("The shadowlings are your masters. Serve them above all else and ensure they complete their goals.")) - to_chat(owner, span_shadowling("You may not harm other thralls or the shadowlings. However, you do not need to obey other thralls.")) - to_chat(owner, span_shadowling("Your body has been irreversibly altered. The attentive can see this - you may conceal it by wearing a mask.")) - to_chat(owner, span_shadowling("Though not nearly as powerful as your masters, you possess some weak powers. These can be found in the Thrall Abilities tab.")) - to_chat(owner, span_shadowling("You may communicate with your allies by using the Lesser Commune ability.")) - SEND_SOUND(owner.current, sound('yogstation/sound/ambience/antag/thrall.ogg')) - -/datum/antagonist/thrall/roundend_report() - return "[printplayer(owner)]" diff --git a/yogstation/code/modules/guardian/abilities/major/time.dm b/yogstation/code/modules/guardian/abilities/major/time.dm index d87f4218e9a4..6f666745be75 100644 --- a/yogstation/code/modules/guardian/abilities/major/time.dm +++ b/yogstation/code/modules/guardian/abilities/major/time.dm @@ -27,9 +27,14 @@ spell_requirements = NONE var/length = 10 SECONDS +/datum/action/cooldown/spell/erase_time/can_cast_spell(feedback) + if(!isturf(owner.loc)) + return FALSE + return ..() + /datum/action/cooldown/spell/erase_time/cast(mob/living/user) . = ..() - if (!isturf(user.loc) || !isguardian(user)) + if (!isturf(user.loc)) return var/list/immune = list(user) if (isguardian(user)) diff --git a/yogstation/code/modules/mob/living/carbon/human/species.dm b/yogstation/code/modules/mob/living/carbon/human/species.dm index 612355a461d1..336d9795cecb 100644 --- a/yogstation/code/modules/mob/living/carbon/human/species.dm +++ b/yogstation/code/modules/mob/living/carbon/human/species.dm @@ -1,14 +1,6 @@ //////////////////// /////BODYPARTS///// //////////////////// -/obj/item/bodypart - var/should_draw_yogs = FALSE - -/mob/living/carbon/proc/draw_yogs_parts(do_it) - for(var/O in bodyparts) - var/obj/item/bodypart/B = O - B.should_draw_yogs = do_it - /datum/species var/yogs_draw_robot_hair = FALSE //DAMN ROBOTS STEALING OUR HAIR AND AIR var/yogs_virus_infect_chance = 100 diff --git a/yogstation/code/modules/mob/living/carbon/human/species_types/darkspawn.dm b/yogstation/code/modules/mob/living/carbon/human/species_types/darkspawn.dm deleted file mode 100644 index f6aa1ad55a31..000000000000 --- a/yogstation/code/modules/mob/living/carbon/human/species_types/darkspawn.dm +++ /dev/null @@ -1,89 +0,0 @@ -#define DARKSPAWN_REFLECT_COOLDOWN 15 SECONDS - -/datum/species/darkspawn - name = "Darkspawn" - id = "darkspawn" - limbs_id = "darkspawn" - bubble_icon = BUBBLE_DARKSPAWN - sexes = FALSE - nojumpsuit = TRUE - changesource_flags = MIRROR_BADMIN | MIRROR_MAGIC | WABBAJACK | ERT_SPAWN //never put this in the pride pool because they look super valid - siemens_coeff = 0 - brutemod = 0.9 - heatmod = 1.5 - no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE, ITEM_SLOT_HEAD) - 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, TRAIT_NODISMEMBER, TRAIT_NOHUNGER) - mutanteyes = /obj/item/organ/eyes/alien - var/list/upgrades = list() - COOLDOWN_DECLARE(reflect_cd_1) - COOLDOWN_DECLARE(reflect_cd_2) - COOLDOWN_DECLARE(reflect_cd_3) - -/datum/species/darkspawn/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - if(prob(50) && (COOLDOWN_FINISHED(src, reflect_cd_1) || COOLDOWN_FINISHED(src, reflect_cd_2) || COOLDOWN_FINISHED(src, reflect_cd_3))) - if(COOLDOWN_FINISHED(src, reflect_cd_1)) - COOLDOWN_START(src, reflect_cd_1, DARKSPAWN_REFLECT_COOLDOWN) - else if(COOLDOWN_FINISHED(src, reflect_cd_2)) - COOLDOWN_START(src, reflect_cd_2, DARKSPAWN_REFLECT_COOLDOWN) - else if(COOLDOWN_FINISHED(src, reflect_cd_3)) - COOLDOWN_START(src, reflect_cd_3, DARKSPAWN_REFLECT_COOLDOWN) - H.visible_message(span_danger("The shadows around [H] ripple as they absorb \the [P]!")) - playsound(H, "bullet_miss", 75, 1) - return -1 - return 0 - -/datum/species/darkspawn/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.real_name = "[pick(GLOB.nightmare_names)]" - C.name = C.real_name - if(C.mind) - C.mind.name = C.real_name - C.dna.real_name = C.real_name - -/datum/species/darkspawn/on_species_loss(mob/living/carbon/C) - . = ..() - C.bubble_icon = initial(C.bubble_icon) - -/datum/species/darkspawn/spec_life(mob/living/carbon/human/H) - handle_upgrades(H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < DARKSPAWN_DIM_LIGHT) //rapid healing and stun reduction in the darkness - var/healing_amount = DARKSPAWN_DARK_HEAL - if(upgrades["dark_healing"]) - healing_amount *= 1.25 - H.adjustBruteLoss(-healing_amount) - H.adjustFireLoss(-healing_amount) - H.adjustToxLoss(-healing_amount) - H.adjustStaminaLoss(-healing_amount * 20) - H.AdjustStun(-healing_amount * 40) - H.AdjustKnockdown(-healing_amount * 40) - H.AdjustUnconscious(-healing_amount * 40) - H.SetSleeping(0) - H.setOrganLoss(ORGAN_SLOT_BRAIN,0) - H.setCloneLoss(0) - else if(light_amount < DARKSPAWN_BRIGHT_LIGHT && !upgrades["light_resistance"]) //not bright, but still dim - H.adjustFireLoss(1) - else if(light_amount > DARKSPAWN_BRIGHT_LIGHT && !H.has_status_effect(STATUS_EFFECT_CREEP)) //but quick death in the light - if(upgrades["spacewalking"] && isspaceturf(T)) - return - else if(!upgrades["light_resistance"]) - to_chat(H, span_userdanger("The light burns you!")) - H.playsound_local(H, 'sound/weapons/sear.ogg', max(40, 65 * light_amount), TRUE) - H.adjustFireLoss(DARKSPAWN_LIGHT_BURN) - else - to_chat(H, span_userdanger("The light singes you!")) - H.playsound_local(H, 'sound/weapons/sear.ogg', max(30, 50 * light_amount), TRUE) - H.adjustFireLoss(DARKSPAWN_LIGHT_BURN * 0.5) - -/datum/species/darkspawn/spec_death(gibbed, mob/living/carbon/human/H) - playsound(H, 'yogstation/sound/creatures/darkspawn_death.ogg', 50, FALSE) - -/datum/species/darkspawn/proc/handle_upgrades(mob/living/carbon/human/H) - var/datum/antagonist/darkspawn/darkspawn - if(H.mind) - darkspawn = H.mind.has_antag_datum(/datum/antagonist/darkspawn) - if(darkspawn) - upgrades = darkspawn.upgrades diff --git a/yogstation/code/modules/mob/living/carbon/human/species_types/plantpeople.dm b/yogstation/code/modules/mob/living/carbon/human/species_types/plantpeople.dm index f13418885c8c..293ab0774576 100644 --- a/yogstation/code/modules/mob/living/carbon/human/species_types/plantpeople.dm +++ b/yogstation/code/modules/mob/living/carbon/human/species_types/plantpeople.dm @@ -62,7 +62,7 @@ return BUTT_SPRITE_FLOWERPOT /datum/species/pod/spec_life(mob/living/carbon/human/H) - if(H.stat == DEAD || H.stat == UNCONSCIOUS || (H.mind && H.mind.has_antag_datum(ANTAG_DATUM_THRALL))) + if(H.stat == DEAD || H.stat == UNCONSCIOUS) return if(IS_BLOODSUCKER(H) && !HAS_TRAIT(H, TRAIT_MASQUERADE)) return diff --git a/yogstation/code/modules/mob/living/silicon/robot/login.dm b/yogstation/code/modules/mob/living/silicon/robot/login.dm index 781a72d168e0..279698547d22 100644 --- a/yogstation/code/modules/mob/living/silicon/robot/login.dm +++ b/yogstation/code/modules/mob/living/silicon/robot/login.dm @@ -1,6 +1,4 @@ /mob/living/silicon/robot/Login() ..() if(mind) - remove_thrall() - remove_sling() - remove_vampire() \ No newline at end of file + remove_vampire() diff --git a/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm b/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm index 50221e2a2d83..21fd26add6ed 100644 --- a/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -22,37 +22,3 @@ /datum/reagent/cluwnification/reaction_mob(mob/living/L, methods=TOUCH, reac_volume, show_message = 1, permeability = 1) if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*permeability))) L.ForceContractDisease(new /datum/disease/cluwnification(), FALSE, TRUE) - -/datum/reagent/shadowling_blindness_smoke - name = "odd black liquid" - description = "<::ERROR::> CANNOT ANALYZE REAGENT <::ERROR::>" - color = "#000000" //Complete black (RGB: 0, 0, 0) - metabolization_rate = 100 //lel - -/datum/reagent/shadowling_blindness_smoke/on_mob_life(mob/living/M) - if(!is_shadow_or_thrall(M)) - to_chat(M, span_warning("You breathe in the black smoke, and your eyes burn horribly!")) - M.blind_eyes(5) - if(prob(25)) - M.visible_message("[M] claws at their eyes!") - M.Stun(3, 0) - . = 1 - else - to_chat(M, span_notice("You breathe in the black smoke, and you feel revitalized!")) - M.adjustOxyLoss(-2, 0) - M.adjustToxLoss(-2, 0) - . = 1 - return ..() || . - -/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) - ..() diff --git a/yogstation/code/modules/surgery/organs/shadowling_organs.dm b/yogstation/code/modules/surgery/organs/shadowling_organs.dm deleted file mode 100644 index 1182b11f6ed6..000000000000 --- a/yogstation/code/modules/surgery/organs/shadowling_organs.dm +++ /dev/null @@ -1,70 +0,0 @@ -//Snowflake spot for putting sling organ related stuff -/obj/item/organ/eyes/alien/sling - name = "shadowling eyes" - desc = "The eyes of a spooky shadowling!" - -/obj/item/organ/internal/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 = "head" - slot = "brain_tumor" - var/organ_health = 3 - -/obj/item/organ/internal/shadowtumor/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/organ/internal/shadowtumor/Destroy() - STOP_PROCESSING(SSobj, src) - ..() - -/obj/item/organ/internal/shadowtumor/process() - if(isturf(loc)) - var/turf/T = loc - var/light_count = T.get_lumcount() - if(light_count > LIGHT_DAM_THRESHOLD && organ_health > 0) //Die in the light - organ_health-- - else if(light_count < LIGHT_HEAL_THRESHOLD && organ_health < 3) //Heal in the dark - organ_health++ - 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/internal/shadowtumor/Insert(mob/living/carbon/M, special, drop_if_replaced) - . = ..() - M.add_thrall() - -/obj/item/organ/internal/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/internal/shadowtumor/Remove(mob/living/carbon/M, special) - if(M.dna.species.id == "l_shadowling") //Empowered thralls cannot be deconverted - to_chat(M, span_shadowling("NOT LIKE THIS!")) - M.visible_message(span_danger("[M] suddenly slams upward and knocks down everyone!")) - M.resting = FALSE //Remove all stuns - M.SetStun(0, 0) - M.SetKnockdown(0) - M.SetUnconscious(0) - for(var/mob/living/user in range(2, src)) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.Knockdown(6) - C.adjustBruteLoss(20) - else if(issilicon(user)) - var/mob/living/silicon/S = user - S.Knockdown(8) - S.adjustBruteLoss(20) - playsound(S, 'sound/effects/bang.ogg', 50, 1) - return FALSE - var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) - eyes.lighting_cutoff = LIGHTING_CUTOFF_VISIBLE - eyes.sight_flags = initial(eyes.sight_flags) - M.update_sight() - M.remove_thrall() - M.visible_message(span_warning("A strange black mass falls from [M]'s head!")) - return ..() diff --git a/yogstation/icons/effects/effects.dmi b/yogstation/icons/effects/effects.dmi index 62119d5f1558..5637d48a3634 100644 Binary files a/yogstation/icons/effects/effects.dmi and b/yogstation/icons/effects/effects.dmi differ diff --git a/yogstation/icons/mob/actions/actions_darkspawn.dmi b/yogstation/icons/mob/actions/actions_darkspawn.dmi index b2534e1d1699..1e6b7a67c98a 100644 Binary files a/yogstation/icons/mob/actions/actions_darkspawn.dmi and b/yogstation/icons/mob/actions/actions_darkspawn.dmi differ diff --git a/yogstation/icons/mob/antag_hud.dmi b/yogstation/icons/mob/antag_hud.dmi index 49361b308a6d..471349be02e6 100644 Binary files a/yogstation/icons/mob/antag_hud.dmi and b/yogstation/icons/mob/antag_hud.dmi differ diff --git a/yogstation/icons/mob/darkspawn.dmi b/yogstation/icons/mob/darkspawn.dmi new file mode 100644 index 000000000000..cea5bc7a74f9 Binary files /dev/null and b/yogstation/icons/mob/darkspawn.dmi differ diff --git a/yogstation/icons/mob/human_parts.dmi b/yogstation/icons/mob/human_parts.dmi index e3988bf4407c..23f74b53d3a4 100644 Binary files a/yogstation/icons/mob/human_parts.dmi and b/yogstation/icons/mob/human_parts.dmi differ diff --git a/yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi b/yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi index a7da23303f58..04b6b977ec87 100644 Binary files a/yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi and b/yogstation/icons/mob/inhands/antag/darkspawn_lefthand.dmi differ diff --git a/yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi b/yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi index 7c327f39eb91..d16d51e45d35 100644 Binary files a/yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi and b/yogstation/icons/mob/inhands/antag/darkspawn_righthand.dmi differ diff --git a/yogstation/icons/mob/mob.dmi b/yogstation/icons/mob/mob.dmi deleted file mode 100644 index 9b1427dec03b..000000000000 Binary files a/yogstation/icons/mob/mob.dmi and /dev/null differ diff --git a/yogstation/icons/mob/mutant_bodyparts.dmi b/yogstation/icons/mob/mutant_bodyparts.dmi deleted file mode 100644 index 24e542bbaad4..000000000000 Binary files a/yogstation/icons/mob/mutant_bodyparts.dmi and /dev/null differ diff --git a/yogstation/icons/mob/sling.dmi b/yogstation/icons/mob/sling.dmi deleted file mode 100644 index c873421dc0c2..000000000000 Binary files a/yogstation/icons/mob/sling.dmi and /dev/null differ diff --git a/yogstation/icons/obj/darkspawn_items.dmi b/yogstation/icons/obj/darkspawn_items.dmi index f5cff3fae449..12a61c13021a 100644 Binary files a/yogstation/icons/obj/darkspawn_items.dmi and b/yogstation/icons/obj/darkspawn_items.dmi differ diff --git a/yogstation/icons/obj/darkspawn_projectiles.dmi b/yogstation/icons/obj/darkspawn_projectiles.dmi new file mode 100644 index 000000000000..956793bc5fc5 Binary files /dev/null and b/yogstation/icons/obj/darkspawn_projectiles.dmi differ diff --git a/yogstation/sound/magic/divulge_01.ogg b/yogstation/sound/magic/divulge_01.ogg index 280ce9b2db0b..b89f8e0bd087 100644 Binary files a/yogstation/sound/magic/divulge_01.ogg and b/yogstation/sound/magic/divulge_01.ogg differ diff --git a/yogstation/sound/magic/divulge_02.ogg b/yogstation/sound/magic/divulge_02.ogg index 927adb8e6452..5d1acba30ad2 100644 Binary files a/yogstation/sound/magic/divulge_02.ogg and b/yogstation/sound/magic/divulge_02.ogg differ diff --git a/yogstation/sound/magic/divulge_03.ogg b/yogstation/sound/magic/divulge_03.ogg index ec1f2959737f..da42a5213450 100644 Binary files a/yogstation/sound/magic/divulge_03.ogg and b/yogstation/sound/magic/divulge_03.ogg differ