From 89cf4c7787037f0c82ccc1152f0a880159dcfa15 Mon Sep 17 00:00:00 2001 From: Time-Green <7501474+Time-Green@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:57:44 +0200 Subject: [PATCH] The Voidwalker | New Midround Antagonist (#84674) ## The Voidwalker Adds a new antagonist, the Voidwalker! It's a rare antag that spawns in space when there's at least 40 people Design doc is here: https://hackmd.io/jE4YScP8RPykXo37rTBV2Q (there's some deviations) No biddle ## Summary Spooky space antag that moves around space, ambushing people either in space or near space. They can move through glass, have an ability to stamina drain you if they remain in your vision for 8 seconds, can temporarily remove glass windows to drag you through them and deal ~~stamina damage~~ incredibly violence. Upon being taken, they can be kidnapped and cursed, muting and pacifying the person and sending them to the void. ## Passive ![image](https://github.com/tgstation/tgstation/assets/7501474/e2bbac33-bac8-4eb9-9042-994904ebec18) ![image](https://github.com/tgstation/tgstation/assets/7501474/b76df700-84d4-4722-bf6f-2e2049c345b3) Passive abilities: - Permanent space flight and indoor flight - Space regen - Slowdown when in gravity - Space camo (very low alpha when in space - Can freely move through unshocked glass - Mute, but can hear all station frequencies (excluding binary) - Will quickly die when on planets or moons (obviously can't roll on icebox) - 10 brute armor and 20 burn armor. They can't wear any form of armor and don't have any get out of jail free cards, so I think it'll help with their survivability a slight bit ## Abilities **Void eater:** Literally just a light-eater but instead of eating light it instantly shatters windows, but restores them after a few seconds **Space Dive:** New ability that lets you move under the station with a 2 second do_after, so they can still get to closed of space spots without being as annoying as heretic space shift **Unsettle:** Remain in view of the target for 8 seconds to give them a short stun, slurring and 80 stamina damage, but announce your presence and location to them. Both you and your target can move, as long as you remain in their view. **Space Kidnap:** When your target is incapacitated and in space, you can drag them into the cosmic void. They'll be returned cursed after undergoing a sort of reverse heretic sacrifice (more on that in the next section). [Showcase of all the above abilities!](https://youtu.be/NJ01H28PV9w) ## Voided Crew A brain trauma received when you get kidnapped. While under it's influence, you are muted and pacified. You will die in planetary gravity and cannot enter space. ![image](https://github.com/tgstation/tgstation/assets/7501474/ea90bf9b-b8d1-4d46-8c69-7b0900e8e50c) It can be cured with a lobotomy or by dying in planetary gravity. You get warned to avoid the Voidwalker. The voidwalker now does extra damage and gets the option to glass gib you if you die, leaving just a brain ## Loot On death, the Voidwalker shatters into glass and drops a cosmic skull. Looking into the skull gives you a stable version of the Voided brain trauma. It doesn't give you pacifism and doesn't ban you from space. It also makes you space immune and gives you the ability to walk through unshocked glass after 2 seconds do_after. Sprites for the cosmic skull by Justice12354 and Rex9001! I'll throw up a video showcasing death and their sprites on Sunday ## Why It's Good For The Game We don't have any space centered antagonists. The closest we have are Space Dragons, but they have to go deep into the station anyway. I'm also quite fond of simpler antagonists, like revenant and nightmare. Give people the tools, and they'll make the fun themselves. I've been comparing the Voidwalker with the Nightmare. Both are goalles, simple antagonists, but where the Nightmare's gimmick is darkness, the Voidwalker's gimmick is space. They also get a dark tentacle arm so they can murderize people, but it only gets them to crit. After that they can drag people into the "void" to kidnap them. This is mostly to encourage the Voidwalker not to just space them cause that's kinda lame. They still can though, just in-case it would be very funny ## Changelog :cl: Time-Green, Justice12354, Rex9001 add: Adds the Voidwalker, a new rare space based midround antagonist! code: Adds the ability to texture limbs and bodypart_overlays Sprite: Cosmic skull sprites by Justice12354 and Rex9001 /:cl: - [x] Add a better kidnapping mechanic, instead of just teleporting someone to a station turf - [x] Add an armblade or weapon or something, unarmed combat is kinda ass for this - [x] Fix the antag preview not rendering textures - [x] Prevent them from space phasing when in combat so they can actually be killed in space combat, even if really fucking hard - [x] Nerf visibility for people with space parallax disabled, probably also something to improve camo with colored parallax - [x] Replace the stamina damage stuff - [x] Cool rework of space phase idea I have - [x] Update/implement vidual effects - [x] Implement names ## Considerations There's a few things that I might change or implement later, depending on how it actually plays. It's a space focused antag, but there might not be enough people near space in a given round. An ability to let them "capture" area's or let them turn into a meteor or something might be needed later if they turn out to be too passive. They might also be _too_ oppressive in space. I designed them for near-space combat, but may've made them insanely overpowered in in raw space combat. Probably wont do biddle, but might add more powers that you can reroll every few minutes or something (like blob). Either wat it wont be this PR --------- Co-authored-by: carlarctg <53100513+carlarctg@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Jacquerel --- .../lazy_templates/voidwalker_void.dmm | 1101 +++++++++++++++++ .../signals/signals_atom/signals_atom_main.dm | 5 +- .../signals_atom/signals_atom_movable.dm | 3 + .../signals_atom/signals_atom_movement.dm | 7 +- code/__DEFINES/dcs/signals/signals_spell.dm | 4 + code/__DEFINES/lazy_templates.dm | 2 + code/__DEFINES/mobs.dm | 4 + code/__DEFINES/role_preferences.dm | 2 + code/__byond_version_compat.dm | 2 +- code/_globalvars/lists/names.dm | 1 + .../dynamic/dynamic_rulesets_midround.dm | 39 + code/controllers/subsystem/parallax.dm | 8 + .../bodypart_overlays/bodypart_overlay.dm | 4 + .../markings_bodypart_overlay.dm | 2 +- .../simple_bodypart_overlay.dm | 2 +- .../texture_bodypart_overlay.dm | 23 + code/datums/components/banned_from_space.dm | 37 + code/datums/components/glass_passer.dm | 50 + code/datums/components/space_allaergy.dm | 14 + code/datums/components/space_camo.dm | 54 + code/datums/components/space_dive.dm | 84 ++ code/datums/components/space_kidnap.dm | 65 + .../components/temporary_glass_shatter.dm | 30 + code/datums/lazy_template.dm | 4 + code/game/atoms_movable.dm | 20 +- code/game/data_huds.dm | 2 +- code/game/machinery/doors/door.dm | 2 + .../anomalies/anomalies_bioscrambler.dm | 12 +- code/game/objects/effects/phased_mob.dm | 4 + code/game/objects/structures/grille.dm | 27 + code/game/objects/structures/window.dm | 32 + code/modules/admin/sql_ban_system.dm | 1 + .../antagonists/heretic/magic/space_crawl.dm | 3 +- .../antagonists/voidwalker/voidwalker.dm | 61 + .../voidwalker/voidwalker_abilities.dm | 61 + .../voidwalker/voidwalker_bodyparts.dm | 161 +++ .../voidwalker/voidwalker_kidnap.dm | 112 ++ .../antagonists/voidwalker/voidwalker_loot.dm | 39 + .../voidwalker/voidwalker_organs.dm | 109 ++ .../voidwalker/voidwalker_particles.dm | 15 + .../voidwalker/voidwalker_species.dm | 75 ++ .../voidwalker/voidwalker_status_effects.dm | 27 + .../voidwalker/voidwalker_traumas.dm | 91 ++ .../voidwalker/voidwalker_void_eater.dm | 58 + .../jobs/job_types/antagonists/voidwalker.dm | 2 + .../mob/living/carbon/carbon_update_icons.dm | 1 + code/modules/mob/living/carbon/human/human.dm | 3 + code/modules/research/experimentor.dm | 4 +- .../spells/spell_types/pointed/terrorize.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 11 +- .../screenshot_antag_icons_voidwalker.png | Bin 0 -> 696 bytes ...ot_humanoids__datum_species_voidwalker.png | Bin 0 -> 1632 bytes icons/area/areas_centcom.dmi | Bin 5633 -> 5743 bytes icons/effects/particles/voidwalker.dmi | Bin 0 -> 326 bytes icons/mob/human/species/voidwalker.dmi | Bin 0 -> 954 bytes icons/mob/human/textures.dmi | Bin 0 -> 504 bytes .../mob/inhands/antag/voidwalker_lefthand.dmi | Bin 0 -> 2701 bytes .../inhands/antag/voidwalker_righthand.dmi | Bin 0 -> 2709 bytes icons/obj/weapons/voidwalker_items.dmi | Bin 0 -> 5731 bytes strings/names/voidwalker.txt | 19 + tgstation.dme | 20 + .../tgui/interfaces/AntagInfoVoidwalker.tsx | 74 ++ .../antagonists/antagonists/voidwalker.ts | 15 + 63 files changed, 2589 insertions(+), 21 deletions(-) create mode 100644 _maps/templates/lazy_templates/voidwalker_void.dmm create mode 100644 code/datums/bodypart_overlays/texture_bodypart_overlay.dm create mode 100644 code/datums/components/banned_from_space.dm create mode 100644 code/datums/components/glass_passer.dm create mode 100644 code/datums/components/space_allaergy.dm create mode 100644 code/datums/components/space_camo.dm create mode 100644 code/datums/components/space_dive.dm create mode 100644 code/datums/components/space_kidnap.dm create mode 100644 code/datums/components/temporary_glass_shatter.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_abilities.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_kidnap.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_loot.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_organs.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_particles.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_species.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_status_effects.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_traumas.dm create mode 100644 code/modules/antagonists/voidwalker/voidwalker_void_eater.dm create mode 100644 code/modules/jobs/job_types/antagonists/voidwalker.dm create mode 100644 code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_voidwalker.png create mode 100644 icons/effects/particles/voidwalker.dmi create mode 100644 icons/mob/human/species/voidwalker.dmi create mode 100644 icons/mob/human/textures.dmi create mode 100644 icons/mob/inhands/antag/voidwalker_lefthand.dmi create mode 100644 icons/mob/inhands/antag/voidwalker_righthand.dmi create mode 100644 icons/obj/weapons/voidwalker_items.dmi create mode 100644 strings/names/voidwalker.txt create mode 100644 tgui/packages/tgui/interfaces/AntagInfoVoidwalker.tsx create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/voidwalker.ts diff --git a/_maps/templates/lazy_templates/voidwalker_void.dmm b/_maps/templates/lazy_templates/voidwalker_void.dmm new file mode 100644 index 00000000000..2f4dadf7600 --- /dev/null +++ b/_maps/templates/lazy_templates/voidwalker_void.dmm @@ -0,0 +1,1101 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/floor/black, +/area/centcom/voidwalker_void) +"z" = ( +/obj/effect/wisp_food, +/turf/open/floor/black, +/area/centcom/voidwalker_void) +"W" = ( +/obj/effect/landmark/voidwalker_void, +/turf/open/floor/black, +/area/centcom/voidwalker_void) + +(1,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +W +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +z +a +W +a +a +a +"} +(5,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(6,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(7,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(8,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(9,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(10,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(11,1,1) = {" +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(12,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(13,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(14,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(15,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(16,1,1) = {" +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(17,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(18,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(19,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(20,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +z +a +a +a +a +a +a +a +a +a +"} +(21,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +W +a +"} +(22,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(23,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(24,1,1) = {" +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +"} +(25,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(26,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(27,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(28,1,1) = {" +a +a +a +W +a +a +a +z +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(29,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(30,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +W +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(31,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(32,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index c612d174ac3..3282c9387a1 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -68,7 +68,7 @@ #define COMSIG_ATOM_EXITED "atom_exited" ///from base of atom/movable/Moved(): (atom/movable/gone, direction) #define COMSIG_ATOM_ABSTRACT_EXITED "atom_abstract_exited" -///from base of atom/Bumped(): (/atom/movable) +///from base of atom/Bumped(): (/atom/movable) (the one that gets bumped) #define COMSIG_ATOM_BUMPED "atom_bumped" ///from base of atom/has_gravity(): (turf/location, list/forced_gravities) #define COMSIG_ATOM_HAS_GRAVITY "atom_has_gravity" @@ -140,3 +140,6 @@ /// From /obj/effect/particle_effect/sparks/proc/sparks_touched(datum/source, atom/movable/singed) #define COMSIG_ATOM_TOUCHED_SPARKS "atom_touched_sparks" #define COMSIG_ATOM_TOUCHED_HAZARDOUS_SPARKS "atom_touched_hazardous_sparks" + +/// From whoever has been revealed (atom/revealed) +#define COMSIG_ATOM_REVEAL "atom_reveal" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index bc73dbf3291..717a8aab27f 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -9,10 +9,12 @@ #define COMSIG_MOVABLE_MOVED "movable_moved" ///from base of atom/movable/Cross(): (/atom/movable) #define COMSIG_MOVABLE_CROSS "movable_cross" + #define COMPONENT_BLOCK_CROSS (1<<0) ///from base of atom/movable/Move(): (/atom/movable) #define COMSIG_MOVABLE_CROSS_OVER "movable_cross_am" ///from base of atom/movable/Bump(): (/atom) #define COMSIG_MOVABLE_BUMP "movable_bump" + #define COMPONENT_INTERCEPT_BUMPED (1<<0) ///from base of atom/movable/newtonian_move(): (inertia_direction, start_delay) #define COMSIG_MOVABLE_NEWTONIAN_MOVE "movable_newtonian_move" #define COMPONENT_MOVABLE_NEWTONIAN_BLOCK (1<<0) @@ -123,3 +125,4 @@ #define COMSIG_MOVABLE_BUMP_PUSHED "movable_bump_pushed" /// Stop it from moving #define COMPONENT_NO_PUSH (1<<0) + diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index 5836a0be65a..63ad7655be7 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -19,7 +19,12 @@ #define COMSIG_ATOM_START_PULL "movable_start_pull" /// called on /atom when something attempts to pass through it (atom/movable/source, atom/movable/passing, dir) #define COMSIG_ATOM_TRIED_PASS "atom_tried_pass" - #define COMSIG_COMPONENT_PERMIT_PASSAGE (1 << 0) +/// called on /movable when something attempts to pass through it (atom/movable/source, atom/movable/passing, dir) AND WHEN general_movement = FALSE for some fucking reason +#define COMSIG_MOVABLE_CAN_PASS_THROUGH "movable_can_pass_through" +/// If given, we permit passage through +#define COMSIG_COMPONENT_PERMIT_PASSAGE (1 << 0) +/// If given, we DONT permit passage through +#define COMSIG_COMPONENT_REFUSE_PASSAGE (1 << 1) ///called on /living when someone starts pulling (atom/movable/pulled, state, force) #define COMSIG_LIVING_START_PULL "living_start_pull" ///called on /living when someone is pulled (mob/living/puller) diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm index 45029974385..d9ef98527e2 100644 --- a/code/__DEFINES/dcs/signals/signals_spell.dm +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -74,6 +74,10 @@ #define COMSIG_MOB_EJECTED_FROM_JAUNT "spell_mob_eject_jaunt" /// Sent from datum/action/cooldown/spell/jaunt/exit_jaunt, after the mob exited jaunt: (datum/action/cooldown/spell/spell) #define COMSIG_MOB_AFTER_EXIT_JAUNT "spell_mob_after_exit_jaunt" +/// Sent from /obj/effect/dummy/phased_mob/proc/phased_check when moving to the holder object: (/obj/effect/dummy/phased_mob, mob/living/phaser, turf/newloc) +#define COMSIG_MOB_PHASED_CHECK "mob_phased_check" + /// Return this to cancel the phased move + #define COMPONENT_BLOCK_PHASED_MOVE (1 << 0) /// Sent from/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt, /// to any unconscious / critical mobs being dragged when the jaunter enters blood: diff --git a/code/__DEFINES/lazy_templates.dm b/code/__DEFINES/lazy_templates.dm index 8a469af4dee..1e8dca9a7a1 100644 --- a/code/__DEFINES/lazy_templates.dm +++ b/code/__DEFINES/lazy_templates.dm @@ -3,6 +3,7 @@ #define LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY "LT_NINJAHOLDING" #define LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS "LT_ABDUCTORSHIPS" #define LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE "LT_HERETICSACRIFICE" +#define LAZY_TEMPLATE_KEY_VOIDWALKER_VOID "LT_VOIDWALKERVOID" #define LAZY_TEMPLATE_KEY_LIST_ALL(...) list( \ "Nukie Base" = LAZY_TEMPLATE_KEY_NUKIEBASE, \ @@ -10,4 +11,5 @@ "Ninja Holding" = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY, \ "Abductor Ships" = LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS, \ "Heretic Sacrifice Level" = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE, \ + "Voidwalker Void" = LAZY_TEMPLATE_KEY_VOIDWALKER_VOID, \ ) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index f690304fd5f..78e48277d00 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -130,6 +130,7 @@ #define SPECIES_ZOMBIE "zombie" #define SPECIES_ZOMBIE_INFECTIOUS "memezombie" #define SPECIES_ZOMBIE_KROKODIL "krokodil_zombie" +#define SPECIES_VOIDWALKER "voidwalker" // Like species IDs, but not specifically attached a species. #define BODYPART_ID_ALIEN "alien" @@ -466,6 +467,9 @@ #define ROBOTIC_BRUTE_EXAMINE_TEXT "denting" #define ROBOTIC_BURN_EXAMINE_TEXT "charring" +#define GLASSY_BRUTE_EXAMINE_TEXT "cracking" +#define GLASSY_BURN_EXAMINE_TEXT "deformation" + #define GRAB_PIXEL_SHIFT_PASSIVE 6 #define GRAB_PIXEL_SHIFT_AGGRESSIVE 12 #define GRAB_PIXEL_SHIFT_NECK 16 diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 88e64164acd..bc8987b4539 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -37,6 +37,7 @@ #define ROLE_SPACE_DRAGON "Space Dragon" #define ROLE_SPIDER "Spider" #define ROLE_WIZARD_MIDROUND "Wizard (Midround)" +#define ROLE_VOIDWALKER "Voidwalker" // Latejoin roles #define ROLE_HERETIC_SMUGGLER "Heretic Smuggler" @@ -153,6 +154,7 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_SPACE_DRAGON = 0, ROLE_SPIDER = 0, ROLE_WIZARD_MIDROUND = 14, + ROLE_VOIDWALKER = 0, // Latejoin ROLE_HERETIC_SMUGGLER = 0, diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm index 87d4348580e..0f19332934d 100644 --- a/code/__byond_version_compat.dm +++ b/code/__byond_version_compat.dm @@ -16,7 +16,7 @@ // We use wrappers for this in case some part of the api ever changes, and to make their function more clear // For the record: GLOBAL_VERB_REF would be useless as verbs can't be global. -/// Call by name proc references, checks if the proc exists on either this type or as a global proc. +/// Call by name proc references, checks if the proc exists on either this type () (AND ONLY THIS TYPE) or as a global proc. #define PROC_REF(X) (nameof(.proc/##X)) /// Call by name verb references, checks if the verb exists on either this type or as a global verb. #define VERB_REF(X) (nameof(.verb/##X)) diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index dce2dc69a50..f9f70a2c706 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -31,6 +31,7 @@ GLOBAL_LIST_INIT(guardian_first_names, world.file2list("strings/names/guardian_d GLOBAL_LIST_INIT(guardian_tech_surnames, world.file2list("strings/names/guardian_gamepieces.txt")) GLOBAL_LIST_INIT(guardian_fantasy_surnames, world.file2list("strings/names/guardian_tarot.txt")) GLOBAL_LIST_INIT(operative_aliases, world.file2list("strings/names/operative_alias.txt")) +GLOBAL_LIST_INIT(voidwalker_names, world.file2list("strings/names/voidwalker.txt")) GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm index 857a1babd32..7d91ca6b565 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm @@ -949,3 +949,42 @@ #undef MALF_ION_PROB #undef REPLACE_LAW_WITH_ION_PROB + +/// Midround Voidwalker Ruleset (From Ghosts) +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker + name = "Voidwalker" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT + antag_datum = /datum/antagonist/voidwalker + antag_flag = ROLE_VOIDWALKER + antag_flag_override = ROLE_VOIDWALKER + ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS + required_enemies = list(2,2,1,1,1,1,1,0,0,0) + required_candidates = 1 + weight = 2 + cost = 5 + minimum_players = 40 + repeatable = TRUE + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_VOIDWALKER_VOID) + /// The space turf we find in acceptable(), cached for ease + var/space_turf + +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/acceptable(population = 0, threat_level = 0) + space_turf = find_space_spawn() + if(!space_turf) + return FALSE + return ..() + +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/generate_ruleset_body(mob/applicant) + var/datum/mind/player_mind = new /datum/mind(applicant.key) + player_mind.active = TRUE + + var/mob/living/carbon/human/voidwalker = new (space_turf) + player_mind.transfer_to(voidwalker) + player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/voidwalker)) + player_mind.special_role = antag_flag + player_mind.add_antag_datum(antag_datum) + + playsound(voidwalker, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + message_admins("[ADMIN_LOOKUPFLW(voidwalker)] has been made into a Voidwalker by the midround ruleset.") + log_dynamic("[key_name(voidwalker)] was spawned as a Voidwalker by the midround ruleset.") + return voidwalker diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm index 212af9076bd..28ebd80560f 100644 --- a/code/controllers/subsystem/parallax.dm +++ b/code/controllers/subsystem/parallax.dm @@ -97,4 +97,12 @@ SUBSYSTEM_DEF(parallax) /datum/controller/subsystem/parallax/proc/post_station_setup() random_layer?.apply_global_effects() +/// Return the most dominant color, if we have a colored background (mostly nebula gas) +/datum/controller/subsystem/parallax/proc/get_parallax_color() + var/atom/movable/screen/parallax_layer/random/space_gas/gas = random_layer + if(!istype(gas)) + return + + return gas.parallax_color + #undef PARALLAX_NONE diff --git a/code/datums/bodypart_overlays/bodypart_overlay.dm b/code/datums/bodypart_overlays/bodypart_overlay.dm index 22f2b15f2cc..960a1a55029 100644 --- a/code/datums/bodypart_overlays/bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/bodypart_overlay.dm @@ -68,3 +68,7 @@ ///Generate a unique identifier to cache with. If you change something about the image, but the icon cache stays the same, it'll simply pull the unchanged image out of the cache /datum/bodypart_overlay/proc/generate_icon_cache() return list() + +/// Additionally color or texture the limb +/datum/bodypart_overlay/proc/modify_bodypart_appearance(datum/appearance) + return diff --git a/code/datums/bodypart_overlays/markings_bodypart_overlay.dm b/code/datums/bodypart_overlays/markings_bodypart_overlay.dm index c2c6f54d861..5c11fe9f703 100644 --- a/code/datums/bodypart_overlays/markings_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/markings_bodypart_overlay.dm @@ -15,7 +15,7 @@ /datum/bodypart_overlay/simple/body_marking/get_image(layer, obj/item/bodypart/limb) var/gender_string = (use_gender && limb.is_dimorphic) ? (limb.gender == MALE ? MALE : FEMALE + "_") : "" //we only got male and female sprites - return image(icon, gender_string + icon_state + "_" + limb.body_zone, layer = layer) + return mutable_appearance(icon, gender_string + icon_state + "_" + limb.body_zone, layer = layer) /datum/bodypart_overlay/simple/body_marking/moth dna_feature_key = "moth_markings" diff --git a/code/datums/bodypart_overlays/simple_bodypart_overlay.dm b/code/datums/bodypart_overlays/simple_bodypart_overlay.dm index 7f52d21de53..6c9eb4240ec 100644 --- a/code/datums/bodypart_overlays/simple_bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/simple_bodypart_overlay.dm @@ -9,7 +9,7 @@ var/draw_color /datum/bodypart_overlay/simple/get_image(layer, obj/item/bodypart/limb) - return image(icon, icon_state, layer = layer) + return mutable_appearance(icon, icon_state, layer = layer) /datum/bodypart_overlay/simple/color_image(image/overlay, layer, obj/item/bodypart/limb) diff --git a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm new file mode 100644 index 00000000000..e8d578bb16c --- /dev/null +++ b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm @@ -0,0 +1,23 @@ +/// Bodypart overlays focused on texturing limbs +/datum/bodypart_overlay/texture + /// icon file for the texture + var/texture_icon + /// icon state for the texture + var/texture_icon_state + /// Cache the icon so we dont have to make a new one each time + var/cached_texture_icon + +/datum/bodypart_overlay/texture/New() + . = ..() + + cached_texture_icon = icon(texture_icon, texture_icon_state) + +/datum/bodypart_overlay/texture/modify_bodypart_appearance(datum/appearance) + appearance.add_filter("bodypart_texture_[texture_icon_state]", 1, layering_filter(icon = cached_texture_icon,blend_mode = BLEND_INSET_OVERLAY)) + +/datum/bodypart_overlay/texture/generate_icon_cache() + return "[type]" + +/datum/bodypart_overlay/texture/spacey + texture_icon_state = "spacey" + texture_icon = 'icons/mob/human/textures.dmi' diff --git a/code/datums/components/banned_from_space.dm b/code/datums/components/banned_from_space.dm new file mode 100644 index 00000000000..ae1d6701dd7 --- /dev/null +++ b/code/datums/components/banned_from_space.dm @@ -0,0 +1,37 @@ +/// Following recent tomfoolery, we've decided to ban you from space. +/datum/component/banned_from_space + /// List of recent tiles we walked on that aren't space + var/list/tiles = list() + /// The max amount of tiles we store + var/max_tile_list_size = 4 + +/datum/component/banned_from_space/Initialize(...) + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_ATOM_ENTERING, PROC_REF(check_if_space)) + +/datum/component/banned_from_space/proc/check_if_space(atom/source, atom/new_location) + SIGNAL_HANDLER + + if(!isturf(new_location)) + return + + if(isspaceturf(new_location)) + send_back(parent) + + else + tiles.Add(new_location) + if(tiles.len > max_tile_list_size) + tiles.Cut(1, 2) + +/datum/component/banned_from_space/proc/send_back(atom/movable/parent) + var/new_turf + + if(tiles.len) + new_turf = tiles[1] + new /obj/effect/temp_visual/portal_animation(parent.loc, new_turf, parent) + else + new_turf = get_random_station_turf() + + parent.forceMove(new_turf) diff --git a/code/datums/components/glass_passer.dm b/code/datums/components/glass_passer.dm new file mode 100644 index 00000000000..f96300341b0 --- /dev/null +++ b/code/datums/components/glass_passer.dm @@ -0,0 +1,50 @@ +/// Allows us to move through glass but not electrified glass. Can also do a little slowdown before passing through +/datum/component/glass_passer + /// How long does it take us to move into glass? + var/pass_time = 0 SECONDS + +/datum/component/glass_passer/Initialize(pass_time) + if(!ismob(parent)) //if its not a mob then just directly use passwindow + return COMPONENT_INCOMPATIBLE + + src.pass_time = pass_time + + if(!pass_time) + passwindow_on(parent, type) + else + RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(bumped)) + + var/mob/mobbers = parent + mobbers.generic_canpass = FALSE + RegisterSignal(parent, COMSIG_MOVABLE_CROSS_OVER, PROC_REF(cross_over)) + +/datum/component/glass_passer/Destroy() + . = ..() + if(parent) + passwindow_off(parent, type) + +/datum/component/glass_passer/proc/cross_over(mob/passer, atom/crosser) + SIGNAL_HANDLER + + if(istype(crosser, /obj/structure/grille)) + var/obj/structure/grille/grillefriend = crosser + if(grillefriend.is_shocked()) //prevent passage of shocked + crosser.balloon_alert(passer, "is shocked!") + return COMPONENT_BLOCK_CROSS + + return null + +/datum/component/glass_passer/proc/bumped(mob/living/owner, atom/bumpee) + SIGNAL_HANDLER + + if(!istype(bumpee, /obj/structure/window)) + return + + INVOKE_ASYNC(src, PROC_REF(phase_through_glass), owner, bumpee) + +/datum/component/glass_passer/proc/phase_through_glass(mob/living/owner, atom/bumpee) + if(!do_after(owner, pass_time, bumpee)) + return + passwindow_on(owner, type) + try_move_adjacent(owner, get_dir(owner, bumpee)) + passwindow_off(owner, type) diff --git a/code/datums/components/space_allaergy.dm b/code/datums/components/space_allaergy.dm new file mode 100644 index 00000000000..d1bc334ce1f --- /dev/null +++ b/code/datums/components/space_allaergy.dm @@ -0,0 +1,14 @@ +/// Slowly kill the thing when iuts on a planet +/datum/component/planet_allergy/Initialize(...) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_ENTER_AREA, PROC_REF(entered_area)) + +/datum/component/planet_allergy/proc/entered_area(mob/living/parent, area/new_area) + SIGNAL_HANDLER + + if(is_on_a_planet(parent) && parent.has_gravity()) + parent.apply_status_effect(/datum/status_effect/planet_allergy) //your gamer body cant stand real gravity + else + parent.remove_status_effect(/datum/status_effect/planet_allergy) diff --git a/code/datums/components/space_camo.dm b/code/datums/components/space_camo.dm new file mode 100644 index 00000000000..08b6c106494 --- /dev/null +++ b/code/datums/components/space_camo.dm @@ -0,0 +1,54 @@ +/// Camouflage us when we enter space by increasing alpha and or changing color +/datum/component/space_camo + /// Alpha we have in space + var/space_alpha + /// Alpha we have elsewhere + var/non_space_alpha + /// How long we can't enter camo after hitting or being hit + var/reveal_after_combat + /// The world time after we can camo again + VAR_PRIVATE/next_camo + +/datum/component/space_camo/Initialize(space_alpha, non_space_alpha, reveal_after_combat) + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + src.space_alpha = space_alpha + src.non_space_alpha = non_space_alpha + src.reveal_after_combat = reveal_after_combat + + RegisterSignal(parent, COMSIG_ATOM_ENTERING, PROC_REF(on_atom_entering)) + + if(isliving(parent)) + RegisterSignals(parent, list(COMSIG_ATOM_WAS_ATTACKED, COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_REVEAL), PROC_REF(force_exit_camo)) + +/datum/component/space_camo/proc/on_atom_entering(atom/movable/entering, atom/entered) + SIGNAL_HANDLER + + if(!attempt_enter_camo()) + exit_camo(parent) + +/datum/component/space_camo/proc/attempt_enter_camo() + if(!isspaceturf(get_turf(parent)) || next_camo > world.time) + return FALSE + + enter_camo(parent) + return TRUE + +/datum/component/space_camo/proc/force_exit_camo() + SIGNAL_HANDLER + + exit_camo(parent) + next_camo = world.time + reveal_after_combat + addtimer(CALLBACK(src, PROC_REF(attempt_enter_camo)), reveal_after_combat, TIMER_OVERRIDE | TIMER_UNIQUE) + +/datum/component/space_camo/proc/enter_camo(atom/movable/parent) + if(parent.alpha != space_alpha) + animate(parent, alpha = space_alpha, time = 0.5 SECONDS) + parent.remove_from_all_data_huds() + parent.add_atom_colour(SSparallax.get_parallax_color(), TEMPORARY_COLOUR_PRIORITY) + +/datum/component/space_camo/proc/exit_camo(atom/movable/parent) + animate(parent, alpha = non_space_alpha, time = 0.5 SECONDS) + parent.add_to_all_human_data_huds() + parent.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) diff --git a/code/datums/components/space_dive.dm b/code/datums/components/space_dive.dm new file mode 100644 index 00000000000..870adbc22d4 --- /dev/null +++ b/code/datums/components/space_dive.dm @@ -0,0 +1,84 @@ +/// Lets us dive under the station from space +/datum/component/space_dive + /// holder we use when we're in dive + var/jaunt_type = /obj/effect/dummy/phased_mob/space_dive + /// time it takes to enter the dive + var/dive_time = 2 SECONDS + /// the time it takes to exit our space dive + var/surface_time = 2 SECONDS + /// Traits added during phasing (and removed after) + var/static/phase_traits = list(TRAIT_MAGICALLY_PHASED, TRAIT_RUNECHAT_HIDDEN, TRAIT_WEATHER_IMMUNE) + +/datum/component/space_dive/Initialize(...) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(bump)) + +/datum/component/space_dive/proc/bump(mob/living/parent, atom/bumped) + SIGNAL_HANDLER + + if(!isspaceturf(get_turf(parent))) + return + + if(ismovable(bumped)) + if(istype(bumped, /obj/machinery/door))//door check is kinda lame but it just plays better + return + + var/atom/movable/mover = bumped + if(!mover.anchored) + return + + INVOKE_ASYNC(src, PROC_REF(attempt_dive), parent, bumped) + +/datum/component/space_dive/proc/attempt_dive(mob/living/parent, atom/bumped) + if(!do_after(parent, dive_time, bumped)) + return + + dive(bumped) + +/datum/component/space_dive/proc/dive(atom/bumped) + var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(get_turf(bumped), parent) + + RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(surface)) + RegisterSignal(jaunt, COMSIG_MOB_PHASED_CHECK, PROC_REF(move_check)) + parent.add_traits(phase_traits, REF(src)) + + // This needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(parent, COMSIG_MOB_ENTER_JAUNT, src, jaunt) + +/datum/component/space_dive/proc/move_check(obj/effect/dummy/phased_mob/jaunt, mob/living/parent, turf/new_turf) + SIGNAL_HANDLER + + if(!isspaceturf(new_turf)) + return + + INVOKE_ASYNC(src, PROC_REF(attempt_surface), parent, new_turf) + return COMPONENT_BLOCK_PHASED_MOVE + +/// try and surface by doing a do_after +/datum/component/space_dive/proc/attempt_surface(mob/living/parent, turf/new_turf) + if(do_after(parent, surface_time, new_turf, extra_checks = CALLBACK(src, PROC_REF(check_if_moved), parent, get_turf(parent)))) + surface(null, parent, new_turf) + +// we check if we moved for the do_after, since relayed movements arent caught that well by the do_after +/datum/component/space_dive/proc/check_if_moved(mob/living/parent, turf/do_after_turf) + return get_turf(parent) == do_after_turf + +/datum/component/space_dive/proc/surface(atom/holder, mob/living/parent, turf/target) + SIGNAL_HANDLER + + var/obj/effect/dummy/phased_mob/jaunt = parent.loc + if(!istype(jaunt)) + return FALSE + + parent.remove_traits(phase_traits, REF(src)) + + parent.forceMove(target || get_turf(parent)) + qdel(jaunt) + + // This needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(parent, COMSIG_MOB_AFTER_EXIT_JAUNT, src) + +/obj/effect/dummy/phased_mob/space_dive + movespeed = 1 diff --git a/code/datums/components/space_kidnap.dm b/code/datums/components/space_kidnap.dm new file mode 100644 index 00000000000..adf1e6b5728 --- /dev/null +++ b/code/datums/components/space_kidnap.dm @@ -0,0 +1,65 @@ +/// Component that lets us space kidnap people as the voidwalker with our HAAAADS +/datum/component/space_kidnap + /// How long does it take to kidnap them? + var/kidnap_time = 6 SECONDS + /// Are we kidnapping right now? + var/kidnapping = FALSE + +/datum/component/space_kidnap/Initialize(...) + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_kidnap)) + +/datum/component/space_kidnap/proc/try_kidnap(mob/living/parent, atom/target) + SIGNAL_HANDLER + + if(!isliving(target)) + return + + var/mob/living/victim = target + + if(victim.stat == DEAD) + target.balloon_alert(parent, "is dead!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(!victim.incapacitated()) + return + + if(!isspaceturf(get_turf(target))) + target.balloon_alert(parent, "not in space!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(!kidnapping) + INVOKE_ASYNC(src, PROC_REF(kidnap), parent, target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/space_kidnap/proc/kidnap(mob/living/parent, mob/living/victim) + victim.Paralyze(kidnap_time) //so they don't get up if we already got em + var/obj/particles = new /obj/effect/abstract/particle_holder (victim, /particles/void_kidnap) + kidnapping = TRUE + + if(do_after(parent, kidnap_time, victim, extra_checks = CALLBACK(victim, TYPE_PROC_REF(/mob, incapacitated)))) + take_them(victim) + + qdel(particles) + kidnapping = FALSE + +/datum/component/space_kidnap/proc/take_them(mob/living/victim) + if(ishuman(victim)) + var/mob/living/carbon/human/hewmon = victim + hewmon.gain_trauma(/datum/brain_trauma/voided) + + victim.flash_act(INFINITY, override_blindness_check = TRUE, visual = TRUE, type = /atom/movable/screen/fullscreen/flash/black) + new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(victim)) + + if(!SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_VOIDWALKER_VOID) || !GLOB.voidwalker_void.len) + victim.forceMove(get_random_station_turf()) + victim.heal_overall_damage(brute = 80, burn = 20) + CRASH("[victim] was instantly dumped after being voidwalker kidnapped due to a missing landmark!") + else + victim.heal_and_revive(90) + victim.adjustOxyLoss(-100, FALSE) + + var/obj/wisp = new /obj/effect/wisp_mobile (get_turf(pick(GLOB.voidwalker_void))) + victim.forceMove(wisp) diff --git a/code/datums/components/temporary_glass_shatter.dm b/code/datums/components/temporary_glass_shatter.dm new file mode 100644 index 00000000000..c2db33190e6 --- /dev/null +++ b/code/datums/components/temporary_glass_shatter.dm @@ -0,0 +1,30 @@ +/// Component to make an item temporarily break glass +/datum/component/temporary_glass_shatterer/Initialize(...) + . = ..() + + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_tap)) + +/datum/component/temporary_glass_shatterer/proc/on_tap(obj/item/parent, mob/tapper, atom/target) + SIGNAL_HANDLER + + if(istype(target, /obj/structure/window)) + var/obj/structure/grille/grille = locate(/obj/structure/grille) in get_turf(target) + if(grille?.is_shocked()) + target.balloon_alert(tapper, "is shocked!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + var/obj/structure/window/window = target + window.temporary_shatter() + else if(istype(target, /obj/structure/grille)) + var/obj/structure/grille/grille = target + if(grille.is_shocked()) + target.balloon_alert(tapper, "is shocked!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + grille.temporary_shatter() + else + return + return COMPONENT_CANCEL_ATTACK_CHAIN diff --git a/code/datums/lazy_template.dm b/code/datums/lazy_template.dm index 7b18ff7225f..3faefc0cc78 100644 --- a/code/datums/lazy_template.dm +++ b/code/datums/lazy_template.dm @@ -130,3 +130,7 @@ /datum/lazy_template/heretic_sacrifice_room key = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE map_name = "heretic_sacrifice" + +/datum/lazy_template/voidwalker_void + key = LAZY_TEMPLATE_KEY_VOIDWALKER_VOID + map_name = "voidwalker_void" diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 8135a3af593..ae987cb668f 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -879,9 +879,10 @@ // Make sure you know what you're doing if you call this // You probably want CanPass() /atom/movable/Cross(atom/movable/crossed_atom) - . = TRUE - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, crossed_atom) - SEND_SIGNAL(crossed_atom, COMSIG_MOVABLE_CROSS_OVER, src) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, crossed_atom) & COMPONENT_BLOCK_CROSS) + return FALSE + if(SEND_SIGNAL(crossed_atom, COMSIG_MOVABLE_CROSS_OVER, src) & COMPONENT_BLOCK_CROSS) + return FALSE return CanPass(crossed_atom, get_dir(src, crossed_atom)) ///default byond proc that is deprecated for us in lieu of signals. do not call @@ -926,7 +927,8 @@ /atom/movable/Bump(atom/bumped_atom) if(!bumped_atom) CRASH("Bump was called with no argument.") - SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) & COMPONENT_INTERCEPT_BUMPED) + return . = ..() if(!QDELETED(throwing)) throwing.finalize(hit = TRUE, target = bumped_atom) @@ -1420,7 +1422,15 @@ /atom/movable/proc/CanPassThrough(atom/blocker, movement_dir, blocker_opinion) SHOULD_CALL_PARENT(TRUE) SHOULD_BE_PURE(TRUE) - return blocker_opinion + + var/blocking_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_PASS_THROUGH, blocker, movement_dir) + if(!blocking_signal) + return blocker_opinion + + if(blocking_signal & COMSIG_COMPONENT_PERMIT_PASSAGE) + return TRUE + else //we have a COMSIG_COMPONENT_REFUSE_PASSAGE but like its either this or that, unlike someone wanna adds half-passing through but fuck you + return FALSE /// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. /atom/movable/proc/on_exit_storage(datum/storage/master_storage) diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index a4d59d668ae..91c500fd8f1 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -282,7 +282,7 @@ Security HUDs! Basic mode shows only the job. /mob/living/proc/sec_hud_set_implants() var/image/holder - for(var/i in list(IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD)) + for(var/i in (list(IMPSEC_FIRST_HUD, IMPLOYAL_HUD, IMPSEC_SECOND_HUD) & hud_list)) holder = hud_list[i] holder.icon_state = null set_hud_image_inactive(i) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index c6ed9f94a99..601ad67a6e9 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -102,6 +102,8 @@ else flags_1 &= ~PREVENT_CLICK_UNDER_1 + if(glass) + passwindow_on(src, INNATE_TRAIT) //doors only block while dense though so we have to use the proc real_explosion_block = explosion_block update_explosive_block() diff --git a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm index 2e2039dfaec..27c02fb9d18 100644 --- a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm +++ b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm @@ -24,7 +24,7 @@ if(!COOLDOWN_FINISHED(src, pulse_cooldown)) return - new /obj/effect/temp_visual/bioscrambler_wave(get_turf(src)) + new /obj/effect/temp_visual/circle_wave/bioscrambler(get_turf(src)) playsound(src, 'sound/magic/cosmic_energy.ogg', vol = 50, vary = TRUE) COOLDOWN_START(src, pulse_cooldown, pulse_delay) for(var/mob/living/carbon/nearby in hearers(range, src)) @@ -81,7 +81,7 @@ return /// Visual effect spawned when the bioscrambler scrambles your bio -/obj/effect/temp_visual/bioscrambler_wave +/obj/effect/temp_visual/circle_wave icon = 'icons/effects/64x64.dmi' icon_state = "circle_wave" pixel_x = -16 @@ -90,7 +90,7 @@ color = COLOR_LIME var/max_alpha = 255 -/obj/effect/temp_visual/bioscrambler_wave/Initialize(mapload) +/obj/effect/temp_visual/circle_wave/Initialize(mapload) transform = matrix().Scale(0.1) animate(src, transform = matrix().Scale(2), time = duration, flags = ANIMATION_PARALLEL) animate(src, alpha = max_alpha, time = duration * 0.6, flags = ANIMATION_PARALLEL) @@ -98,5 +98,9 @@ apply_wibbly_filters(src) return ..() -/obj/effect/temp_visual/bioscrambler_wave/light +/obj/effect/temp_visual/circle_wave/bioscrambler + color = COLOR_LIME + +/obj/effect/temp_visual/circle_wave/bioscrambler/light max_alpha = 128 + diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm index dcd4e39189c..f48f0979083 100644 --- a/code/game/objects/effects/phased_mob.dm +++ b/code/game/objects/effects/phased_mob.dm @@ -84,6 +84,10 @@ return var/area/destination_area = newloc.loc movedelay = world.time + movespeed + + if(SEND_SIGNAL(src, COMSIG_MOB_PHASED_CHECK, user, newloc) & COMPONENT_BLOCK_PHASED_MOVE) + return null + if(newloc.turf_flags & NOJAUNT) to_chat(user, span_warning("Some strange aura is blocking the way.")) return diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 3bd3e00cc27..aa9af66868f 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -17,6 +17,8 @@ integrity_failure = 0.4 var/rods_type = /obj/item/stack/rods var/rods_amount = 2 + /// Whether or not we're disappearing but dramatically + var/dramatically_disappearing = FALSE /datum/armor/structure_grille melee = 50 @@ -365,6 +367,31 @@ /obj/structure/grille/get_dumping_location() return null +/obj/structure/grille/proc/temporary_shatter(time_to_go = 0 SECONDS, time_to_return = 4 SECONDS) + if(dramatically_disappearing) + return + + //dissapear in 1 second + dramatically_disappearing = TRUE + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, moveToNullspace)), time_to_go) //woosh + + // come back in 1 + 4 seconds + addtimer(VARSET_CALLBACK(src, atom_integrity, atom_integrity), time_to_go + time_to_return) //set the health back (icon is updated on move) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, forceMove), loc), time_to_go + time_to_return) //we back boys + addtimer(VARSET_CALLBACK(src, dramatically_disappearing, FALSE), time_to_go + time_to_return) //also set the var back + +/// Do some very specific checks to see if we *would* get shocked. Returns TRUE if it's shocked +/obj/structure/grille/proc/is_shocked() + var/turf/turf = get_turf(src) + var/obj/structure/cable/cable = turf.get_cable_node() + var/list/powernet_info = get_powernet_info_from_source(cable) + + if(!powernet_info) + return FALSE + + var/datum/powernet/powernet = powernet_info["powernet"] + return !!powernet.get_electrocute_damage() + /obj/structure/grille/broken // Pre-broken grilles for map placement icon_state = "brokengrille" density = FALSE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 6c41b2f8b15..b2b3ee50df6 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -35,6 +35,8 @@ var/bloodied = FALSE ///Datum that the shard and debris type is pulled from for when the glass is broken. var/datum/material/glass_material_datum = /datum/material/glass + /// Whether or not we're disappearing but dramatically + var/dramatically_disappearing = FALSE /datum/armor/structure_window melee = 50 @@ -428,6 +430,36 @@ return TRUE +/obj/structure/window/proc/temporary_shatter(time_to_go = 1 SECONDS, time_to_return = 4 SECONDS, take_grill = TRUE) + if(dramatically_disappearing) + return + + // do a cute breaking animation + var/static/time_interval = 2 DECISECONDS //per how many steps should we do damage? + for(var/damage_step in 1 to (floor(time_to_go / time_interval) - 1)) //10 ds / 2 ds = 5 damage steps, minus 1 so we dont actually break it + // slowly drain our total health for the illusion of shattering + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, take_damage), floor(atom_integrity / (time_to_go / time_interval))), time_interval * damage_step) + + //dissapear in 1 second + dramatically_disappearing = TRUE + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), loc, break_sound, 70, TRUE), time_to_go) //SHATTER SOUND + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, moveToNullspace)), time_to_go) //woosh + + // come back in 1 + 4 seconds + addtimer(VARSET_CALLBACK(src, atom_integrity, atom_integrity), time_to_go + time_to_return) //set the health back (icon is updated on move) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, forceMove), loc), time_to_go + time_to_return) //we back boys + addtimer(VARSET_CALLBACK(src, dramatically_disappearing, FALSE), time_to_go + time_to_return) //also set the var back + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), time_to_go + time_to_return) + + var/obj/structure/grille/grill = take_grill ? (locate(/obj/structure/grille) in loc) : null + if(grill) + grill.temporary_shatter(time_to_go, time_to_return) + +/obj/structure/window/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(loc) + update_nearby_icons() + MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/spawner, 0) /obj/structure/window/unanchored diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 9a713588abd..74955324dff 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -396,6 +396,7 @@ ROLE_SYNDICATE, ROLE_TRAITOR, ROLE_WIZARD, + ROLE_VOIDWALKER, ), ) for(var/department in long_job_lists) diff --git a/code/modules/antagonists/heretic/magic/space_crawl.dm b/code/modules/antagonists/heretic/magic/space_crawl.dm index 49677e3bb50..90f74a37047 100644 --- a/code/modules/antagonists/heretic/magic/space_crawl.dm +++ b/code/modules/antagonists/heretic/magic/space_crawl.dm @@ -84,8 +84,8 @@ RegisterSignal(jaunter, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost)) RegisterSignal(jaunter, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change)) - our_turf.visible_message(span_warning("[jaunter] sinks into [our_turf]!")) playsound(our_turf, 'sound/magic/cosmic_energy.ogg', 50, TRUE, -1) + our_turf.visible_message(span_warning("[jaunter] sinks into [our_turf]!")) new /obj/effect/temp_visual/space_explosion(our_turf) jaunter.extinguish_mob() @@ -102,7 +102,6 @@ if(!exit_jaunt(jaunter, our_turf)) return FALSE - our_turf.visible_message(span_boldwarning("[jaunter] rises out of [our_turf]!")) return TRUE diff --git a/code/modules/antagonists/voidwalker/voidwalker.dm b/code/modules/antagonists/voidwalker/voidwalker.dm new file mode 100644 index 00000000000..08fe2873f5f --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker.dm @@ -0,0 +1,61 @@ +/// Space antagonist that harasses people near space and cursed them if they get the chance +/datum/antagonist/voidwalker + name = "\improper Voidwalker" + antagpanel_category = ANTAG_GROUP_ABOMINATIONS + job_rank = ROLE_VOIDWALKER + show_in_antagpanel = TRUE + antagpanel_category = "Voidwalker" + show_name_in_check_antagonists = TRUE + show_to_ghosts = TRUE + ui_name = "AntagInfoVoidwalker" + suicide_cry = "FOR THE VOID!!" + preview_outfit = /datum/outfit/voidwalker + +/datum/antagonist/voidwalker/greet() + . = ..() + owner.announce_objectives() + +/datum/antagonist/voidwalker/on_gain() + . = ..() + + var/mob/living/carbon/human/body = owner.current + if(ishuman(body)) + body.set_species(/datum/species/voidwalker) + + forge_objectives() + +/datum/antagonist/voidwalker/on_removal() + var/mob/living/carbon/human/body = owner.current + if(ishuman(body)) + body.set_species(/datum/species/human) + + return ..() + +/datum/antagonist/voidwalker/forge_objectives() + var/datum/objective/voidwalker_fluff/objective = new + objective.owner = owner + objectives += objective + +/datum/outfit/voidwalker + name = "Voidwalker (Preview only)" + +/datum/outfit/voidwalker/post_equip(mob/living/carbon/human/human, visualsOnly) + human.set_species(/datum/species/voidwalker) + +/datum/objective/voidwalker_fluff + +/datum/objective/voidwalker_fluff/New() + var/list/explanation_texts = list( + "Show them the beauty of the void.", + "They must see what you have seen. They must walk where you have walked.", + "Recover what you have lost.", + "Obliterate the tyranny of matter.", + "Make them all just like you." + ) + if(prob(20)) + explanation_text += "Man I fucking love glass." + explanation_text = pick(explanation_texts) + ..() + +/datum/objective/voidwalker_fluff/check_completion() + return owner.current.stat != DEAD diff --git a/code/modules/antagonists/voidwalker/voidwalker_abilities.dm b/code/modules/antagonists/voidwalker/voidwalker_abilities.dm new file mode 100644 index 00000000000..01fb25cb920 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_abilities.dm @@ -0,0 +1,61 @@ +/// Remain in someones view without breaking line of sight +/datum/action/cooldown/spell/pointed/unsettle + name = "Unsettle" + desc = "Stare directly into someone who doesn't see you. Remain in their view for a bit to stun them for 2 seconds and announce your presence to them. " + button_icon_state = "terrify" + background_icon_state = "bg_alien" + overlay_icon_state = "bg_alien_border" + panel = null + spell_requirements = NONE + cooldown_time = 8 SECONDS + cast_range = 9 + active_msg = "You prepare to stare down a target..." + deactive_msg = "You refocus your eyes..." + /// how long we need to stare at someone to unsettle them (woooooh) + var/stare_time = 8 SECONDS + /// how long we stun someone on succesful cast + var/stun_time = 2 SECONDS + /// stamina damage we doooo + var/stamina_damage = 80 + +/datum/action/cooldown/spell/pointed/unsettle/is_valid_target(atom/cast_on) + . = ..() + + if(!isliving(cast_on)) + cast_on.balloon_alert(owner, "cannot be targeted!") + return FALSE + + if(!check_if_in_view(cast_on)) + owner.balloon_alert(owner, "cannot see you!") + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/unsettle/cast(mob/living/carbon/human/cast_on) + . = ..() + + if(do_after(owner, stare_time, cast_on, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE, extra_checks = CALLBACK(src, PROC_REF(check_if_in_view), cast_on), hidden = TRUE)) + spookify(cast_on) + return + owner.balloon_alert(owner, "line of sight broken!") + return SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/pointed/unsettle/proc/check_if_in_view(mob/living/carbon/human/target) + SIGNAL_HANDLER + + if(target.is_blind() || !(owner in viewers(target, world.view))) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/unsettle/proc/spookify(mob/living/carbon/human/target) + target.Paralyze(stun_time) + target.adjustStaminaLoss(stamina_damage) + target.apply_status_effect(/datum/status_effect/speech/slurring/generic) + target.emote("scream") + + new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(owner)) + new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(target)) + SEND_SIGNAL(owner, COMSIG_ATOM_REVEAL) + +/obj/effect/temp_visual/circle_wave/unsettle + color = COLOR_PURPLE diff --git a/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm b/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm new file mode 100644 index 00000000000..370a273d733 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm @@ -0,0 +1,161 @@ +///Turn the damage overlays glassy +#define GLASSY_OVERLAY_MATRIX list(\ + 1, 2, 2, 0, \ + 0, 1, 0, 0, \ + 0, 0, 1, 0, \ + 0, 0, 0, 1, \ + 0, 0, 0, 0) + +/obj/item/bodypart/head/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + bodypart_traits = list(TRAIT_MUTE) + head_flags = NONE + blocks_emissive = EMISSIVE_BLOCK_NONE + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + brute_modifier = 0.9 + burn_modifier = 0.8 + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +/obj/item/bodypart/chest/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + blocks_emissive = EMISSIVE_BLOCK_NONE + + brute_modifier = 0.9 + burn_modifier = 0.8 + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +/obj/item/bodypart/arm/left/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + blocks_emissive = EMISSIVE_BLOCK_NONE + + brute_modifier = 0.9 + burn_modifier = 0.8 + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +/obj/item/bodypart/arm/right/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + blocks_emissive = EMISSIVE_BLOCK_NONE + + brute_modifier = 0.9 + burn_modifier = 0.8 + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +/obj/item/bodypart/leg/left/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + blocks_emissive = EMISSIVE_BLOCK_NONE + + brute_modifier = 0.9 + burn_modifier = 0.8 + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +/obj/item/bodypart/leg/right/voidwalker + texture_bodypart_overlay = /datum/bodypart_overlay/texture/spacey + icon_greyscale = 'icons/mob/human/species/voidwalker.dmi' + limb_id = SPECIES_VOIDWALKER + is_dimorphic = FALSE + blocks_emissive = EMISSIVE_BLOCK_NONE + + brute_modifier = 0.9 + burn_modifier = 0.8 + + damage_overlay_color = GLASSY_OVERLAY_MATRIX + + light_brute_msg = "splintered" + medium_brute_msg = "cracked" + heavy_brute_msg = "shattered" + + light_burn_msg = "bent" + medium_burn_msg = "deformed" + heavy_burn_msg = "warped" + + damage_examines = list( + BRUTE = GLASSY_BRUTE_EXAMINE_TEXT, + BURN = GLASSY_BURN_EXAMINE_TEXT, + ) + +#undef GLASSY_OVERLAY_MATRIX diff --git a/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm b/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm new file mode 100644 index 00000000000..ce654218c2c --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm @@ -0,0 +1,112 @@ +/// A global assoc list for the drop of point +GLOBAL_LIST_EMPTY(voidwalker_void) + +/// Lardmarks meant to designate where voidwalker kidnapees are sent +/obj/effect/landmark/voidwalker_void + name = "default voidwalker void landmark" + icon_state = "x" + +/obj/effect/landmark/voidwalker_void/Initialize(mapload) + . = ..() + GLOB.voidwalker_void += src + +/obj/effect/landmark/voidwalker_void/Destroy() + GLOB.voidwalker_void -= src + return ..() + +/// Voidwalker void where the people go +/area/centcom/voidwalker_void + name = "Voidwalker void" + icon_state = "voidwalker" + has_gravity = STANDARD_GRAVITY + ambience_index = AMBIENCE_SPOOKY + sound_environment = SOUND_ENVIRONMENT_CAVE + area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE + +/// Mini car where people drive around in in their mangled corpse to heal a bit before they get dumped back on station +/obj/effect/wisp_mobile + name = "wisp" + + icon = 'icons/obj/weapons/voidwalker_items.dmi' + icon_state = "wisp" + + light_system = OVERLAY_LIGHT + light_color = COLOR_WHITE + light_range = 4 + light_power = 1 + light_on = TRUE + + /// Delay between movements + var/move_delay = 0.5 SECONDS + /// when can we move again? + var/can_move + /// what do we eatt? + var/food_type = /obj/effect/wisp_food + /// how much do we heal per food? + var/heal_per_food = 15 + /// Traits given to the wisp driver + var/wisp_driver_traits = list(TRAIT_STASIS, TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_HANDS_BLOCKED) + +/obj/effect/wisp_mobile/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + + if(!isliving(arrived)) + return + + var/mob/living/driver = arrived + driver.forceMove(src) + driver.add_traits(wisp_driver_traits, REF(src)) + add_atom_colour(random_color(), FIXED_COLOUR_PRIORITY) + + addtimer(CALLBACK(driver, TYPE_PROC_REF(/atom/movable, forceMove), get_random_station_turf()), 60 SECONDS) + +/obj/effect/wisp_mobile/relaymove(mob/living/user, direction) + if(can_move >= world.time) + return + can_move = world.time + move_delay + + if(isturf(loc)) + can_move = world.time + move_delay + try_step_multiz(direction) + +/obj/effect/wisp_mobile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + + var/obj/food = locate(food_type) in loc + if(!food) + return + + qdel(food) + + // make new food + var/area/our_area = get_area(src) + new food_type(pick(get_area_turfs(our_area))) + + var/mob/living/driver = locate(/mob/living) in contents + if(driver) + driver.heal_ordered_damage(heal_per_food, list(BRUTE, BURN, OXY)) + playsound(src, 'sound/misc/server-ready.ogg', 50, TRUE, -1) + +/obj/effect/wisp_mobile/Exited(atom/movable/gone, direction) + . = ..() + + gone.remove_traits(wisp_driver_traits, REF(src)) + to_chat(gone, span_boldwarning("You feel it would be very bad to get caught again.")) + qdel(src) + +/// we only exist to be eaten by wisps for food 😔👊 +/obj/effect/wisp_food + name = "wisp" + icon = 'icons/obj/weapons/voidwalker_items.dmi' + icon_state = "wisp" + + color = COLOR_YELLOW + + light_system = OVERLAY_LIGHT + light_color = COLOR_WHITE + light_range = 4 + light_power = 1 + light_on = TRUE + +/obj/item/restraints/handcuffs/energy/void + breakouttime = INFINITY diff --git a/code/modules/antagonists/voidwalker/voidwalker_loot.dm b/code/modules/antagonists/voidwalker/voidwalker_loot.dm new file mode 100644 index 00000000000..8d3420d0a52 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_loot.dm @@ -0,0 +1,39 @@ +/// Gives someone the stable voided trauma and then self destructs +/obj/item/cosmic_skull + name = "cosmic skull" + desc = "You can see and feel the surrounding space pulsing through it..." + + icon = 'icons/obj/weapons/voidwalker_items.dmi' + icon_state = "cosmic_skull_charged" + + /// Icon state for when drained + var/drained_icon_state = "cosmic_skull_drained" + /// How many uses does it have left? + var/uses = 1 + +/obj/item/cosmic_skull/attack_self(mob/user, modifiers) + . = ..() + + if(!uses || !ishuman(user)) + return + + var/mob/living/carbon/human/hewmon = user + if(is_species(hewmon, /datum/species/voidwalker)) + to_chat(user, span_bolddanger("OH GOD NOO!!!! WHYYYYYYYYY!!!!! WHO WOULD DO THIS?!!")) + return + + to_chat(user, span_purple("You begin staring into the [name]...")) + + if(!do_after(user, 10 SECONDS, src)) + return + + var/mob/living/carbon/human/starer = user + starer.cure_trauma_type(/datum/brain_trauma/voided) //this wouldn't make much sense to have anymore + + starer.gain_trauma(/datum/brain_trauma/voided/stable) + to_chat(user, span_purple("And a whole world opens up to you.")) + playsound(get_turf(user), 'sound/effects/curse5.ogg', 60) + + uses-- + if(uses <= 0 ) + icon_state = drained_icon_state diff --git a/code/modules/antagonists/voidwalker/voidwalker_organs.dm b/code/modules/antagonists/voidwalker/voidwalker_organs.dm new file mode 100644 index 00000000000..234e2a85910 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_organs.dm @@ -0,0 +1,109 @@ +/// Voidwalker eyes with nightvision and thermals +/obj/item/organ/internal/eyes/voidwalker + name = "blackened orbs" + desc = "These orbs will withstand the light of the sun, yet still see within the darkest voids." + eye_icon_state = null + pepperspray_protect = TRUE + flash_protect = FLASH_PROTECTION_WELDER + color_cutoffs = list(20, 10, 40) + sight_flags = SEE_MOBS + +/// Voidwalker brain stacked with a lot of the abilities +/obj/item/organ/internal/brain/voidwalker + name = "cosmic brain" + desc = "A mind fully integrated into the cosmic thread." + icon = 'icons/obj/medical/organs/shadow_organs.dmi' + + /// Alpha we have in space + var/space_alpha = 15 + /// Alpha we have elsewhere + var/non_space_alpha = 255 + /// We settle the un + var/datum/action/unsettle = /datum/action/cooldown/spell/pointed/unsettle + /// Regen effect we have in space + var/datum/status_effect/regen = /datum/status_effect/space_regeneration + /// Speed modifier given when in gravity + var/datum/movespeed_modifier/speed_modifier = /datum/movespeed_modifier/grounded_voidwalker + /// The void eater weapon + var/obj/item/glass_breaker = /obj/item/void_eater + +/obj/item/organ/internal/brain/voidwalker/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + + RegisterSignal(organ_owner, COMSIG_ATOM_ENTERING, PROC_REF(on_atom_entering)) + + organ_owner.AddComponent(/datum/component/space_camo, space_alpha, non_space_alpha, 2 SECONDS) + organ_owner.apply_status_effect(regen) + + unsettle = new unsettle(organ_owner) + unsettle.Grant(organ_owner) + + glass_breaker = new/obj/item/void_eater + organ_owner.put_in_hands(glass_breaker) + +/obj/item/organ/internal/brain/voidwalker/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + + UnregisterSignal(organ_owner, COMSIG_ENTER_AREA) + alpha = 255 + + qdel(organ_owner.GetComponent(/datum/component/space_camo)) + organ_owner.remove_status_effect(regen) + + unsettle.Remove(organ_owner) + unsettle = initial(unsettle) + + if(glass_breaker) + qdel(glass_breaker) + +/obj/item/organ/internal/brain/voidwalker/proc/on_atom_entering(mob/living/carbon/organ_owner, atom/entering) + SIGNAL_HANDLER + + if(!isturf(entering)) + return + + var/turf/new_turf = entering + + //apply debufs for being in gravity + if(new_turf.has_gravity()) + organ_owner.add_movespeed_modifier(speed_modifier) + //remove debufs for not being in gravity + else + organ_owner.remove_movespeed_modifier(speed_modifier) + +/obj/item/organ/internal/brain/voidwalker/on_death() + . = ..() + + var/turf/spawn_loc = get_turf(owner) + new /obj/effect/spawner/glass_shards (spawn_loc) + new /obj/item/cosmic_skull (spawn_loc) + playsound(get_turf(owner), SFX_SHATTER, 100) + + qdel(owner) + +/obj/item/implant/radio/voidwalker + radio_key = /obj/item/encryptionkey/heads/captain + actions_types = null + +/obj/effect/spawner/glass_shards + /// Weighted list for the shards we spawn + var/list/shards = list(/obj/item/shard = 2, /obj/item/shard/plasma = 1, /obj/item/shard/titanium = 1, /obj/item/shard/plastitanium = 1) + /// Min shards we generate + var/min_spawn = 4 + /// Max shards we generate + var/max_spawn = 6 + /// The we can apply when generating + var/pixel_offset = 16 + +/obj/effect/spawner/glass_shards/Initialize(mapload) + . = ..() + + for(var/i in 1 to rand(min_spawn, max_spawn)) + var/shard_type = pick_weight(shards) + var/obj/shard = new shard_type (loc) + shard.pixel_x = rand(-pixel_offset, pixel_offset) + shard.pixel_y = rand(-pixel_offset, pixel_offset) + +/obj/effect/spawner/glass_shards/mini + min_spawn = 1 + max_spawn = 2 diff --git a/code/modules/antagonists/voidwalker/voidwalker_particles.dm b/code/modules/antagonists/voidwalker/voidwalker_particles.dm new file mode 100644 index 00000000000..8ffbd4abd44 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_particles.dm @@ -0,0 +1,15 @@ +/particles/void_kidnap + icon = 'icons/effects/particles/voidwalker.dmi' + icon_state = list("kidnap_1" = 1, "kidnap_2" = 1, "kidnap_3" = 2) + width = 100 + height = 300 + count = 1000 + spawning = 20 + lifespan = 1.5 SECONDS + fade = 1 SECONDS + velocity = list(0, 0.4, 0) + position = generator(GEN_SPHERE, 12, 12, NORMAL_RAND) + drift = generator(GEN_SPHERE, 0, 1, NORMAL_RAND) + friction = 0.2 + gravity = list(0.95, 0) + grow = 0.05 diff --git a/code/modules/antagonists/voidwalker/voidwalker_species.dm b/code/modules/antagonists/voidwalker/voidwalker_species.dm new file mode 100644 index 00000000000..865d10697a7 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_species.dm @@ -0,0 +1,75 @@ +/// Species for the voidwalker antagonist +/datum/species/voidwalker + name = "\improper Voidling" + id = SPECIES_VOIDWALKER + sexes = FALSE + inherent_traits = list( + TRAIT_NOBREATH, + TRAIT_NO_UNDERWEAR, + TRAIT_RADIMMUNE, + TRAIT_VIRUSIMMUNE, + TRAIT_NOBLOOD, + TRAIT_NODISMEMBER, + TRAIT_NEVER_WOUNDED, + TRAIT_MOVE_FLYING, + TRAIT_RESISTCOLD, + TRAIT_RESISTHIGHPRESSURE, + TRAIT_RESISTLOWPRESSURE, + TRAIT_NOHUNGER, + TRAIT_FREE_HYPERSPACE_MOVEMENT, + TRAIT_ADVANCEDTOOLUSER, + TRAIT_NO_BLOOD_OVERLAY, + ) + changesource_flags = MIRROR_BADMIN + + bodypart_overrides = list( + BODY_ZONE_HEAD = /obj/item/bodypart/head/voidwalker, + BODY_ZONE_CHEST = /obj/item/bodypart/chest/voidwalker, + BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/voidwalker, + BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/voidwalker, + BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/voidwalker, + BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/voidwalker, + ) + + no_equip_flags = ITEM_SLOT_OCLOTHING | ITEM_SLOT_ICLOTHING | ITEM_SLOT_GLOVES | ITEM_SLOT_MASK | ITEM_SLOT_HEAD | ITEM_SLOT_FEET | ITEM_SLOT_BACK | ITEM_SLOT_EARS + + mutantbrain = /obj/item/organ/internal/brain/voidwalker + mutanteyes = /obj/item/organ/internal/eyes/voidwalker + mutantheart = null + mutantlungs = null + mutanttongue = null + + siemens_coeff = 0 + +/datum/species/voidwalker/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) + . = ..() + + human_who_gained_species.AddComponent(/datum/component/glass_passer) + human_who_gained_species.AddComponent(/datum/component/space_dive) + human_who_gained_species.AddComponent(/datum/component/space_kidnap) + + var/obj/item/implant/radio = new /obj/item/implant/radio/voidwalker (human_who_gained_species) + radio.implant(human_who_gained_species, null, TRUE, TRUE) + + human_who_gained_species.AddComponent(/datum/component/planet_allergy) + +/datum/species/voidwalker/on_species_loss(mob/living/carbon/human/human, datum/species/new_species, pref_load) + . = ..() + + qdel(human.GetComponent(/datum/component/glass_passer)) + qdel(human.GetComponent(/datum/component/space_dive)) + qdel(human.GetComponent(/datum/component/space_kidnap)) + + var/obj/item/implant/radio = locate(/obj/item/implant/radio/voidwalker) in human + if(radio) + qdel(radio) + + qdel(human.GetComponent(/datum/component/planet_allergy)) + +/datum/species/voidwalker/on_species_gain(mob/living/carbon/nameless, datum/species/old_species) + . = ..() + + nameless.fully_replace_character_name(null, pick(GLOB.voidwalker_names)) // nightmare names as placeholder, need to think of something :/ + +/datum/species/voidwalker/check_roundstart_eligible() + return FALSE diff --git a/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm b/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm new file mode 100644 index 00000000000..845be059800 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm @@ -0,0 +1,27 @@ +/// THE GRAVITY!!! IT WEIGHS!!! +/datum/movespeed_modifier/grounded_voidwalker + multiplicative_slowdown = 1.1 + +/// Regenerate in space +/datum/status_effect/space_regeneration + id = "space_regeneration" + duration = INFINITE + alert_type = null + // How much do we heal per tick? + var/healing = 1.5 + +/datum/status_effect/space_regeneration/tick(effect) + heal_owner() + +/// Regenerate health whenever this status effect is applied or reapplied +/datum/status_effect/space_regeneration/proc/heal_owner() + if(isspaceturf(get_turf(owner))) + owner.heal_ordered_damage(healing, list(BRUTE, BURN, OXY, STAMINA, TOX, BRAIN)) + +/datum/status_effect/planet_allergy + id = "planet_allergy" + duration = INFINITE + alert_type = /atom/movable/screen/alert/veryhighgravity + +/datum/status_effect/planet_allergy/tick() + owner.adjustBruteLoss(1) diff --git a/code/modules/antagonists/voidwalker/voidwalker_traumas.dm b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm new file mode 100644 index 00000000000..cf4f389a0d6 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm @@ -0,0 +1,91 @@ +/// Curse brain trauma that makes someone space textured, mute, pacifist and forbids them from entering space +/datum/brain_trauma/voided + name = "Voided" + desc = "They've seen the secrets of the cosmos, in exchange for a curse that keeps them chained." + scan_desc = "cosmic neural pattern" + gain_text = "" + lose_text = "" + resilience = TRAUMA_RESILIENCE_LOBOTOMY + random_gain = FALSE + /// Type for the bodypart texture we add + var/bodypart_overlay_type = /datum/bodypart_overlay/texture/spacey + ///traits we give on gain + var/list/traits_to_apply = list(TRAIT_MUTE, TRAIT_PACIFISM) + /// Do we ban the person from entering space? + var/ban_from_space = TRUE + +/datum/brain_trauma/voided/on_gain() + . = ..() + + owner.add_traits(traits_to_apply, REF(src)) + if(ban_from_space) + owner.AddComponent(/datum/component/banned_from_space) + owner.AddComponent(/datum/component/planet_allergy) + RegisterSignal(owner, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(texture_limb)) //also catch new limbs being attached + RegisterSignal(owner, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(untexture_limb)) //and remove it from limbs if they go away + + for(var/obj/item/bodypart as anything in owner.bodyparts) + texture_limb(owner, bodypart) + + //your underwear is belong to us + if(ishuman(owner)) + var/mob/living/carbon/human/human = owner //CARBON WILL NEVER BE REAL!!!!! + human.underwear = "Nude" + human.undershirt = "Nude" + human.socks = "Nude" + + owner.update_body() + +/datum/brain_trauma/voided/on_lose() + . = ..() + + owner.remove_traits(traits_to_apply, REF(src)) + UnregisterSignal(owner, list(COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) + if(ban_from_space) + qdel(owner.GetComponent(/datum/component/banned_from_space)) + qdel(owner.GetComponent(/datum/component/planet_allergy)) + + for(var/obj/item/bodypart/bodypart as anything in owner.bodyparts) + untexture_limb(owner, bodypart) + +/// Apply the space texture +/datum/brain_trauma/voided/proc/texture_limb(atom/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + + limb.add_bodypart_overlay(new bodypart_overlay_type) + if(istype(limb, /obj/item/bodypart/head)) + var/obj/item/bodypart/head/head = limb + head.head_flags &= ~HEAD_EYESPRITES + +/datum/brain_trauma/voided/proc/untexture_limb(atom/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + + var/overlay = locate(bodypart_overlay_type) in limb.bodypart_overlays + if(overlay) + limb.remove_bodypart_overlay(overlay) + + if(istype(limb, /obj/item/bodypart/head)) + var/obj/item/bodypart/head/head = limb + head.head_flags = initial(head.head_flags) + +/datum/brain_trauma/voided/on_death() + . = ..() + + if(is_on_a_planet(owner)) + qdel(src) + +/// Positive version of the previous. Get space immunity and the ability to slowly move through glass (but you still get muted) +/datum/brain_trauma/voided/stable + scan_desc = "stable cosmic neural pattern" + traits_to_apply = list(TRAIT_MUTE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD) + ban_from_space = FALSE + +/datum/brain_trauma/voided/stable/on_gain() + . = ..() + + owner.AddComponent(/datum/component/glass_passer, 2 SECONDS) + +/datum/brain_trauma/voided/stable/on_lose() + . = ..() + + qdel(owner.GetComponent(/datum/component/glass_passer)) diff --git a/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm b/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm new file mode 100644 index 00000000000..e46946d38a8 --- /dev/null +++ b/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm @@ -0,0 +1,58 @@ +/** + * An armblade that pops windows + */ +/obj/item/void_eater + name = "void eater" //as opposed to full eater + icon = 'icons/obj/weapons/voidwalker_items.dmi' + icon_state = "tentacle" + inhand_icon_state = "tentacle" + force = 25 + armour_penetration = 35 + lefthand_file = 'icons/mob/inhands/antag/voidwalker_lefthand.dmi' + righthand_file = 'icons/mob/inhands/antag/voidwalker_righthand.dmi' + blocks_emissive = EMISSIVE_BLOCK_NONE + item_flags = ABSTRACT | DROPDEL + resistance_flags = INDESTRUCTIBLE | ACID_PROOF | FIRE_PROOF | LAVA_PROOF | UNACIDABLE + w_class = WEIGHT_CLASS_HUGE + tool_behaviour = TOOL_MINING + hitsound = 'sound/weapons/bladeslice.ogg' + wound_bonus = -30 + bare_wound_bonus = 20 + +/obj/item/void_eater/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + + AddComponent(/datum/component/temporary_glass_shatterer) + +/obj/item/void_eater/attack(mob/living/target_mob, mob/living/user, params) + if(!ishuman(target_mob)) + return ..() + + var/mob/living/carbon/human/hewmon = target_mob + + if(hewmon.has_trauma_type(/datum/brain_trauma/voided)) + var/turf/spawnloc = get_turf(hewmon) + + if(hewmon.stat != DEAD) + hewmon.balloon_alert(user, "already voided!") + playsound(hewmon, SFX_SHATTER, 60) + new /obj/effect/spawner/glass_shards/mini (spawnloc) + hewmon.adjustBruteLoss(10) // BONUS DAMAGE + else + hewmon.balloon_alert(user, "shattering...") + if(do_after(user, 4 SECONDS, hewmon)) + new /obj/effect/spawner/glass_shards (spawnloc) + var/obj/item/organ/brain = hewmon.get_organ_by_type(/obj/item/organ/internal/brain) + if(brain) + brain.Remove(hewmon) + brain.forceMove(spawnloc) + brain.balloon_alert(user, "shattered!") + playsound(hewmon, SFX_SHATTER, 100) + qdel(hewmon) + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(hewmon.stat == HARD_CRIT && !hewmon.has_trauma_type(/datum/brain_trauma/voided)) + target_mob.balloon_alert(user, "is in crit!") + return COMPONENT_CANCEL_ATTACK_CHAIN + return ..() diff --git a/code/modules/jobs/job_types/antagonists/voidwalker.dm b/code/modules/jobs/job_types/antagonists/voidwalker.dm new file mode 100644 index 00000000000..c29d165efb8 --- /dev/null +++ b/code/modules/jobs/job_types/antagonists/voidwalker.dm @@ -0,0 +1,2 @@ +/datum/job/voidwalker + title = ROLE_VOIDWALKER diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 00e502bad04..4c98419ef41 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -306,6 +306,7 @@ continue if(isnull(damage_overlay) && (iter_part.brutestate || iter_part.burnstate)) damage_overlay = mutable_appearance('icons/mob/effects/dam_mob.dmi', "blank", -DAMAGE_LAYER, appearance_flags = KEEP_TOGETHER) + damage_overlay.color = iter_part.damage_overlay_color if(iter_part.brutestate) damage_overlay.add_overlay("[iter_part.dmg_overlay_type]_[iter_part.body_zone]_[iter_part.brutestate]0") //we're adding icon_states of the base image as overlays if(iter_part.burnstate) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 94df3d3c433..612801695fd 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1099,3 +1099,6 @@ /mob/living/carbon/human/species/zombie/infectious race = /datum/species/zombie/infectious + +/mob/living/carbon/human/species/voidwalker + race = /datum/species/voidwalker diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 8f7787c4394..9268dd90b80 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -740,12 +740,12 @@ // Scrambles your organs. 33% chance to delete after use. /obj/item/relic/proc/tummy_ache(mob/user) - new /obj/effect/temp_visual/bioscrambler_wave/light(get_turf(src)) + new /obj/effect/temp_visual/circle_wave/bioscrambler/light(get_turf(src)) to_chat(user, span_notice("Your stomach starts growling...")) addtimer(CALLBACK(src, PROC_REF(scrambliticus), user), rand(1 SECONDS, 3 SECONDS)) // throw it away! /obj/item/relic/proc/scrambliticus(mob/user) - new /obj/effect/temp_visual/bioscrambler_wave(get_turf(src)) + new /obj/effect/temp_visual/circle_wave/bioscrambler/light(get_turf(src)) playsound(src, 'sound/magic/cosmic_energy.ogg', vol = 50, vary = TRUE) for(var/mob/living/carbon/nearby in hearers(2, src)) nearby.bioscramble(name) diff --git a/code/modules/spells/spell_types/pointed/terrorize.dm b/code/modules/spells/spell_types/pointed/terrorize.dm index 33465b1d353..699410b9a3c 100644 --- a/code/modules/spells/spell_types/pointed/terrorize.dm +++ b/code/modules/spells/spell_types/pointed/terrorize.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/pointed/terrorize name = "Terrorize" - desc = "Project yourself into a victim's mind, inflictng them with terror buildup. \ + desc = "Project yourself into a victim's mind, inflicting them with terror buildup. \ Prey will become increasingly terrified. Swatting terrified prey with an open hand will \ scare and disorient them." button_icon_state = "terrify" diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 06599c2fb48..5d6228d0772 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -115,6 +115,8 @@ ///the type of damage overlay (if any) to use when this bodypart is bruised/burned. var/dmg_overlay_type = "human" + ///a color (optionally matrix) for the damage overlays to give the limb + var/damage_overlay_color /// If we're bleeding, which icon are we displaying on this part var/bleed_overlay_icon @@ -199,6 +201,8 @@ var/any_existing_wound_can_mangle_our_interior /// get_damage() / total_damage must surpass this to allow our limb to be disabled, even temporarily, by an EMP. var/robotic_emp_paralyze_damage_percent_threshold = 0.3 + /// A potential texturing overlay to put on the limb + var/datum/bodypart_overlay/texture/texture_bodypart_overlay /obj/item/bodypart/apply_fantasy_bonuses(bonus) . = ..() @@ -224,6 +228,10 @@ RegisterSignal(src, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle)) + if(texture_bodypart_overlay) + texture_bodypart_overlay = new texture_bodypart_overlay() + add_bodypart_overlay(texture_bodypart_overlay) + if(!IS_ORGANIC_LIMB(src)) grind_results = null @@ -1059,7 +1067,8 @@ for(var/external_layer in overlay.all_layers) if(overlay.layers & external_layer) . += overlay.get_overlay(external_layer, src) - + for(var/datum/layer in .) + overlay.modify_bodypart_appearance(layer) return . /obj/item/bodypart/proc/huskify_image(image/thing_to_husk, draw_blood = TRUE) diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png new file mode 100644 index 0000000000000000000000000000000000000000..08ff8c889d2fe06d9b77ef7c560689eb9b2ca84b GIT binary patch literal 696 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoSydqsB`&GO$wiq3C7Jno3=9=> zg2M`mO22;zF8KKMiI%sn*10q1gExd4Tr__0Nawtd=E;B!p&LvdKRV-a*5|xNV?md< zkav)1j_8yrJ1l~Yy1Rf(o}Ct#yM&5^6?0Nq!|lac#pC_;80MXnkoPHJ@L*tI;`DTJ z45^s&_Rj9YLk=Qt54RRF<~2_65S+m97*~TN!x@Hzn_mtK_A{Hv zF+;d)48{x*W^Zqpx(4QYmhW!2*Sh_73sV9c!|8=4$1OhPHoark23lso+#vY){*UB; zzuilJKDT~)xA~niRJ_-e{leb5_^SKyVIcFJc^&!rEl)rF^rvF?-EW(WVyA2dGK+6r zng4Fj|1}V&O}l$`r)AIYjg>Rz%jX|DDsy3`D99P}b6=?RmpwK!Zdtwd+1~ZF8Szn! zK#L_Aawb?Vc=|o+>-#fV?;_5IO?~<`v-W-TT;^pb7=SKS?qhbF&)l_WSIq8{-?ig3 z(w{zGdp^_3jpH2SSCl|UDiIbXOT{`7J8JH?o|3j3DB3-0HG!WQWNN6%vZbVcm_+H^<6^|P( z*4NMZbbe>yd;604_rU?I)4qT8O7G0X_rK=!rQ6KD(dKtoUieAyGA>z*nZTsT;OXk; Jvd$@?2>{*PE~@|l literal 0 HcmV?d00001 diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_voidwalker.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_voidwalker.png new file mode 100644 index 0000000000000000000000000000000000000000..78962671d119281aa4e97d11467d1f309ea73475 GIT binary patch literal 1632 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 literal 0 HcmV?d00001 diff --git a/icons/obj/weapons/voidwalker_items.dmi b/icons/obj/weapons/voidwalker_items.dmi new file mode 100644 index 0000000000000000000000000000000000000000..1179d1953003f429adab291ebee23b8adc02f12a GIT binary patch literal 5731 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 { + return ( + + + + +
+ + You are a Voidwalker. + +
+ You are a creature from the void between stars. You were + attracted to the radio signals being broadcasted by this + station. +
+
+ + + Survive:  + You have unrivaled freedom. Remain in space and no one can + stop you. You can move through windows, so stay near them to + always have a way out. +
+ Hunt:  + Pick unfair fights. Look for inattentive targets and strike at + them when they don't expect you. +
+ Abduct:  + Your Unsettle ability stuns and drains your targets. Finish + them with your void window and use it to pop a window, drag + them into space and use an empty hand to kidnap them. +
+
+
+
+ +
+ + + You can move under the station from space, use this to hunt + and get to isolated sections of space. + + + Your divine appendage; it allows you to incapacitate the loud + ones and instantly break windows. + + + Your natural camouflage makes you nearly invisible in space, + as well as mending any wounds your body might have sustained. + You can move through glass freely, but are slowed in gravity. + + + Target a victim while remaining only partially in their view + to stun and weaken them, but also announce them your presence. + + +
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/voidwalker.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/voidwalker.ts new file mode 100644 index 00000000000..2a8d22a1f67 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/voidwalker.ts @@ -0,0 +1,15 @@ +import { Antagonist, Category } from '../base'; + +const Voidwalker: Antagonist = { + key: 'voidwalker', + name: 'Voidwalker', + description: [ + ` + Move through space and pull people into the void. + Declare solars an independent nation. + `, + ], + category: Category.Midround, +}; + +export default Voidwalker;