From 1417993709cab7c855725999bf0d4044d3cc9bf2 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:02:29 +0300 Subject: [PATCH] [MIRROR] The Voidwalker | New Midround Antagonist [MDB IGNORE] (#4546) * The Voidwalker | New Midround Antagonist * Update role_preferences.dm * Update sql_ban_system.dm * Update _bodyparts.dm * Update role_preferences.dm * Update lazy_templates.dm * Update _bodyparts.dm * Update _bodyparts.dm * Grep --------- Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com> Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com> Co-authored-by: SomeRandomOwl <2568378+SomeRandomOwl@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Iajret --- .../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 | 6 +- 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 | 11 +- 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 | 14 +- .../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, 2591 insertions(+), 25 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 1f715778f0d..0da41db2941 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,5 +11,6 @@ "Ninja Holding" = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY, \ "Abductor Ships" = LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS, \ "Heretic Sacrifice Level" = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE, \ - "Outpost of Cogs" = LAZY_TEMPLATE_KEY_OUTPOST_OF_COGS, \ -) // NOVA EDIT ABOVE - OUTPOST OF COGS + "Voidwalker Void" = LAZY_TEMPLATE_KEY_VOIDWALKER_VOID, \ + "Outpost of Cogs" = LAZY_TEMPLATE_KEY_OUTPOST_OF_COGS, /* NOVA EDIT ADDITION */ \ +) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 286b4c02862..0156d0dd315 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -142,6 +142,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" @@ -482,6 +483,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 9301ee04f0b..dcc56215559 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -48,6 +48,7 @@ #define ROLE_MUTANT "Mutated Abomination" #define ROLE_CLOCK_CULTIST "Clock Cultist" // NOVA EDIT ADDITION END +#define ROLE_VOIDWALKER "Voidwalker" // Latejoin roles #define ROLE_HERETIC_SMUGGLER "Heretic Smuggler" @@ -180,6 +181,7 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_DRIFTING_CONTRACTOR = 14, ROLE_MUTANT = 0, // NOVA EDIT ADDITION END + 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 123fcb679db..9306039bc21 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm @@ -973,3 +973,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 7639aa8da53..f7a0bf1c52f 100644 --- a/code/datums/bodypart_overlays/bodypart_overlay.dm +++ b/code/datums/bodypart_overlays/bodypart_overlay.dm @@ -84,3 +84,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 b395d28c203..2d43064d85b 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -888,9 +888,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 @@ -935,7 +936,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) @@ -1432,7 +1434,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 03fa7e09972..ab6460361f8 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -290,7 +290,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 0efae6afd5b..49131a0ae5f 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..ab53099ed34 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,8 @@ 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 f6e7ba849cd..655d24cea16 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 4d45d6be1c2..270907cb1ab 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -433,6 +433,7 @@ ROLE_WIZARD, ROLE_BORER, // NOVA EDIT ADDITION ROLE_ASSAULT_OPERATIVE, // NOVA EDIT ADDITION + ROLE_VOIDWALKER, ), "Nova Ban Options" = list( BAN_PACIFICATION, 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 09fe1bf1cb0..e24f6f58fd0 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 4583f6035d3..9047ade86da 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1176,3 +1176,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 201eb789d4a..79fc567afb1 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 @@ -1100,7 +1108,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) // NOVA EDIT ADDITION BEGIN - MARKINGS CODE var/override_color var/atom/offset_spokesman = owner || src @@ -1156,8 +1165,7 @@ . += accessory_overlay if (emissive) . += emissive - // NOVA EDIT END - MARKINGS CODE END - + // NOVA EDIT ADDITION END - MARKINGS CODE END 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;