From 39a2434c062737df0f4bfc7050074ea9ccfc25bb Mon Sep 17 00:00:00 2001
From: Time-Green <7501474+Time-Green@users.noreply.github.com>
Date: Tue, 1 Jul 2025 13:55:31 +0200
Subject: [PATCH] Voidwalker Rework | Basic Mob Edition + Bonus Antag (#91646)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## About The Pull Request
Reworks the Voidwalker into a basic mob, including a lot of the balance
and mechanics!
Old (left), new (right)

https://github.com/user-attachments/assets/22d6138f-11aa-4f7a-8600-2565e6578bcf
(little outdated)
https://youtu.be/cp1E3qPJGX4 (high res mirror)
**🟨Voidwalker mob changes🟨**
No longer a human species and no more void eater. Instead deals damage
by simple unarmed attacks.
Instead of dealing brute, the voidwalker does oxygen damage (4-5 hits to
knock out) with left-click, and slight brute on right click. Non-human
mobs automatically take brute damage instead of oxygen damage.
150 HP
33% burn armor
Yes it has hands, but it can only pick up and drop stuff
**🟨Window phase🟨**

Instead of needing to smash a window with the void eater, moving through
windows simply leaves them passable for 5 seconds. Makes kidnapping a
lot easier, but also makes it easier for people to chase you
People who used the voided skull also leave windows passable for a short
bit.
**🟩Cosmic Charge🟩**
Standard charge ability, but only lets you charge towards space and
works while dragging people
**🟩The Vomitwalker🟩**
People you kidnapped now occasionally do the nebula vomit, which
voidwalkers can use to dive from and into

Diving into it is very fast but also removes the nebula vomit. You can
also kidnap people into the vomit (this doesn't remove the vomit).
There is also a little UI for tracking this. Clicking it while in space
dive teleports you to the next nebula vomit, if there are any.

**🟨Voided people changes🟨**
Kidnapped people and people that used the cosmic skull are no longer
muted, but take 10% extra brute and occasionally leave behind glass
shards when taking a lot of damage.
Are no longer obliterated on a second encounter with voidwalkers.
Voidwalkers can't hurt people they've already voided (unless they really
want to), but instead just knock them out for 30s
Also the kidnapped do space vomit as I said earlier.
**🟩Sunwalker🟩**
Voidwalker variant made for pure murderbone. Has no camo and kindap
mechanics, but has a fiery charge, loads of damage and area igniting and
people ignition. It's admin, but I might change this later once I've had
some more time to think about it.

Other changes:
- Voidwindows no longer need to be space adjacent
- Unsettle works faster but can't be used in combat anymore
- Space camo now grants complete space invisibility
- Makes a lot of aspects easily moddable, so we can easily mod it into a
moistwalker in-game
- Taking a cosmic skull when you already used one gives you the old
voidwalker void eater arm. Additional uses just gives you more void
eater arms until you run out of hands
- I definitely forgot a lot more
- I made a cool voidwalker hud! It even has a unique space camo toggle
## Why It's Good For The Game
Lot of text
Voidwalker was basically a snowflaked toghether human species because I
didn't know how to sprite, but I was able to work with species and
visual effects. Then I realized I can just commission sprites!
This also let me just cut out a lot of the snowflake code, because it's
no longer a human so half the things I didn't want them to be able to
do, they just literally cannot do.
Voidwalkers were in a bit of strange spot with kidnapping? There was
essentially no incentive, other than "smash spaceman = funny". They also
had issues doing, anything? There's surprisingly little space on a space
station, especially maps such as tram. Making the voided victims have
nebula vomit gives the voidwalker a reason to WANT to kidnap, by giving
them a way to appear basically anywhere on the station. I don't think
it's too overpowered. Voided people don't vomit that much, it's easily
cleanable and diving into it removes them, so they're limited usability.
Replacing the brute damage with oxygen damage also kinda... just makes
sense? I seriously contemplated letting them do stamina damage for the
first iteration, but opted not to do it because stamina damage has so
many hooks attached. Oxygen damage doesn't! It's also just incredibly
thematic, let's them bypass most armor and makes them more suited to
non-letha kidnappings.
Space camo making them completely invisible was also long overdue. It
was literally just urging people to turn up their gamma and turn down
their parallax settings. I thought it was an interesting mechanic, but
it's just straight up unfair and doesn't belong in a multiplayer game.
They now more frequently leave behind little glass shards, leave
particles from nebula vomit they leave from and have more unique sound
effects, so attentive (and lucky) people can still deduce if an area is
safe-ish.
I removed being able to shatter voided people because the mechanic was
deeply misunderstood. It was intended to give them a means of removing
people if they kept incessently bothering the voidwalker, but people
went out of their way to use this to roundremove people they had already
voided. The 30s sleep conveys my intention a lot better, and fits better
now that the voidwalker benefits from having as many voided people
vomitting all over the place.
The cosmic charge gives them some much needed survivability. My
experiences (in observing voidwalkers, I can never get the roll ;_;) is
that they're constantly one mistake away from complete obliteration. The
cosmic charge let's them get out quick despite their slow movement speed
in gravity. It also makes them stronger when fighting in space.
They got 33% burn armor so it's a tiny bit harder to wipe them away in a
single laser salvo, while still giving people ample opportunity to fight
them off . Also they're like glass or something so it fits thematically.
I gave them hands because I thought it was cool, might be a mistake idk
## Changelog
:cl: Time-Green, INFRARED_BACON
add: Voidwalker has been throughly reworked! Now you are even less safe!
admin: Adds admin-only Sunwalker mob
fix: Unsettle doesnt work on yourself anymore
fix: Space camo doesnt stop bobbing anymore
fix: Voidwalker windows now recharge on kidnap
runtime: Fixes healthanalyzers runtiming when scanning mobs without
reagent holders
/:cl:
---
code/__DEFINES/colors.dm | 1 +
code/__DEFINES/combat.dm | 1 +
.../dcs/signals/signals_voidwalker.dm | 2 -
code/__DEFINES/role_preferences.dm | 1 +
code/__DEFINES/traits/declarations.dm | 4 +-
code/_globalvars/phobias.dm | 4 +-
code/_globalvars/traits/_traits.dm | 1 +
code/_onclick/hud/voidwalkerhud.dm | 108 ++++++
.../dynamic/dynamic_ruleset_midround.dm | 8 +-
code/datums/components/debris_bleeder.dm | 36 ++
code/datums/components/glass_passer.dm | 37 +-
code/datums/components/igniter.dm | 10 +-
code/datums/components/regenerator.dm | 13 +
code/datums/components/space_camo.dm | 51 ++-
code/datums/components/space_dive.dm | 45 ++-
code/datums/components/space_kidnap.dm | 72 ----
code/datums/components/vision_hurting.dm | 7 +-
code/datums/elements/glass_pacifist.dm | 2 +-
code/datums/elements/pick_and_drop_only.dm | 45 +++
.../objects/effects/decals/cleanable/misc.dm | 9 +
.../antagonists/voidwalker/sunwalker_mob.dm | 81 ++++
.../antagonists/voidwalker/sunwalkers.dm | 41 +++
.../antagonists/voidwalker/voidwalker.dm | 23 +-
.../voidwalker/voidwalker_abilities.dm | 90 ++++-
.../voidwalker/voidwalker_bodyparts.dm | 147 --------
.../voidwalker/voidwalker_kidnap.dm | 2 +-
.../antagonists/voidwalker/voidwalker_loot.dm | 82 ++++-
.../antagonists/voidwalker/voidwalker_mob.dm | 347 ++++++++++++++++++
.../voidwalker/voidwalker_organs.dm | 135 -------
.../voidwalker/voidwalker_particles.dm | 21 +-
.../voidwalker/voidwalker_species.dm | 74 ----
.../voidwalker/voidwalker_status_effects.dm | 16 -
.../voidwalker/voidwalker_traumas.dm | 44 ++-
.../voidwalker/voidwalker_void_eater.dm | 167 ---------
code/modules/mob/living/carbon/human/human.dm | 3 -
code/modules/mob/living/life.dm | 2 +-
.../screenshot_antag_icons_voidwalker.png | Bin 696 -> 345 bytes
...ot_humanoids__datum_species_voidwalker.png | Bin 1632 -> 0 bytes
icons/effects/effects.dmi | Bin 941106 -> 940287 bytes
icons/effects/particles/voidwalker.dmi | Bin 326 -> 353 bytes
icons/hud/screen_voidwalker.dmi | Bin 0 -> 6817 bytes
icons/mob/actions/actions_items.dmi | Bin 16264 -> 16540 bytes
icons/mob/actions/actions_spells.dmi | Bin 37337 -> 40232 bytes
icons/mob/actions/backgrounds.dmi | Bin 13269 -> 13456 bytes
icons/mob/simple/voidwalker.dmi | Bin 0 -> 16917 bytes
tgstation.dme | 12 +-
.../tgui/interfaces/AntagInfoSunwalker.tsx | 62 ++++
.../tgui/interfaces/AntagInfoVoidwalker.tsx | 46 ++-
48 files changed, 1135 insertions(+), 717 deletions(-)
delete mode 100644 code/__DEFINES/dcs/signals/signals_voidwalker.dm
create mode 100644 code/_onclick/hud/voidwalkerhud.dm
create mode 100644 code/datums/components/debris_bleeder.dm
delete mode 100644 code/datums/components/space_kidnap.dm
create mode 100644 code/datums/elements/pick_and_drop_only.dm
create mode 100644 code/modules/antagonists/voidwalker/sunwalker_mob.dm
create mode 100644 code/modules/antagonists/voidwalker/sunwalkers.dm
delete mode 100644 code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm
create mode 100644 code/modules/antagonists/voidwalker/voidwalker_mob.dm
delete mode 100644 code/modules/antagonists/voidwalker/voidwalker_organs.dm
delete mode 100644 code/modules/antagonists/voidwalker/voidwalker_species.dm
delete mode 100644 code/modules/antagonists/voidwalker/voidwalker_void_eater.dm
delete mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_voidwalker.png
create mode 100644 icons/hud/screen_voidwalker.dmi
create mode 100644 icons/mob/simple/voidwalker.dmi
create mode 100644 tgui/packages/tgui/interfaces/AntagInfoSunwalker.tsx
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index c93613cdb1b..27272eb419c 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -114,6 +114,7 @@
#define COLOR_TRAM_LIGHT_BLUE "#A8A7DA"
#define COLOR_AMETHYST "#822BFF"
#define COLOR_BLUE_LIGHT "#33CCFF"
+#define COLOR_BLUE_VERY_LIGHT "#ccecff"
#define COLOR_NAVY "#000080"
#define COLOR_BLUE_GRAY "#75A2BB"
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 305e7ebcba3..32dd88a6e10 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -156,6 +156,7 @@ DEFINE_BITFIELD(status_flags, list(
#define ATTACK_EFFECT_MECHFIRE "mech_fire"
#define ATTACK_EFFECT_MECHTOXIN "mech_toxin"
#define ATTACK_EFFECT_BOOP "boop" //Honk
+#define ATTACK_EFFECT_VOID "void"
/// Attack animation for sharp items
#define ATTACK_ANIMATION_SLASH "slash"
diff --git a/code/__DEFINES/dcs/signals/signals_voidwalker.dm b/code/__DEFINES/dcs/signals/signals_voidwalker.dm
deleted file mode 100644
index 25878f99dd3..00000000000
--- a/code/__DEFINES/dcs/signals/signals_voidwalker.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/// Called from /datum/component/space_kidnap/successful_kidnap() : (mob/living/carbon/human/voidwalker, mob/living/carbon/human/victim)
-#define COMSIG_VOIDWALKER_SUCCESSFUL_KIDNAP "voidwalker_successful_kidnap"
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index ce911101b70..5f2dda07c56 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -39,6 +39,7 @@
#define ROLE_SPIDER "Spider"
#define ROLE_WIZARD_MIDROUND "Wizard (Midround)"
#define ROLE_VOIDWALKER "Voidwalker"
+#define ROLE_SUNWALKER "Sunwalker"
// Latejoin roles
#define ROLE_HERETIC_SMUGGLER "Heretic Smuggler"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 3c18294e74e..d6aa8c5c0e0 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -1525,7 +1525,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait given to colorblind mobs
#define TRAIT_COLORBLIND "colorblind"
+/// Trait that blocks invisibility (uh as of writing only the space camo but might expand later idk)
+#define TRAIT_INVISIBILITY_BLOCKED "invisibility_blocked"
+
/// Trait that signals to objects on this turf that its open (has UNDERFLOOR_INTERACTIBLE) but still covers them
#define TRAIT_UNCOVERED_TURF "uncovered_turf"
-
// END TRAIT DEFINES
diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm
index d96d363a96c..1e6eba39405 100644
--- a/code/_globalvars/phobias.dm
+++ b/code/_globalvars/phobias.dm
@@ -108,6 +108,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
)),
"skeletons" = typecacheof(list(/mob/living/basic/skeleton)),
"snakes" = typecacheof(list(/mob/living/basic/snake)),
+ "space" = typecacheof(list(/mob/living/basic/voidwalker)),
"the supernatural" = typecacheof(list(
/mob/dead/observer,
/mob/living/basic/bat,
@@ -123,6 +124,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
/mob/living/basic/wizard,
/mob/living/basic/zombie,
/mob/living/simple_animal/bot/mulebot/paranormal,
+ /mob/living/basic/voidwalker,
)),
))
@@ -577,8 +579,6 @@ GLOBAL_LIST_INIT(phobia_species, list(
/datum/species/plasmaman,
/datum/species/skeleton,
)),
- "space" = typecacheof(list(/datum/species/voidwalker)),
- "supernatural" = typecacheof(list(/datum/species/voidwalker))
))
/// Creates a regular expression to match against the given phobia
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 3c5c83a739a..cf0efed5edd 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -339,6 +339,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_IMMUNODEFICIENCY" = TRAIT_IMMUNODEFICIENCY,
"TRAIT_INCAPACITATED" = TRAIT_INCAPACITATED,
"TRAIT_INTROVERT" = TRAIT_INTROVERT,
+ "TRAIT_INVISIBILITY_BLOCKED" = TRAIT_INVISIBILITY_BLOCKED,
"TRAIT_INVISIBLE_MAN" = TRAIT_INVISIBLE_MAN,
"TRAIT_INVISIMIN" = TRAIT_INVISIMIN,
"TRAIT_IN_CALL" = TRAIT_IN_CALL,
diff --git a/code/_onclick/hud/voidwalkerhud.dm b/code/_onclick/hud/voidwalkerhud.dm
new file mode 100644
index 00000000000..736c29d626d
--- /dev/null
+++ b/code/_onclick/hud/voidwalkerhud.dm
@@ -0,0 +1,108 @@
+/datum/hud/dextrous/voidwalker
+ ui_style = 'icons/hud/screen_voidwalker.dmi'
+
+/datum/hud/dextrous/voidwalker/New(mob/living/owner)
+ . = ..()
+
+ floor_change.icon = ui_style
+ floor_change.screen_loc = ui_rest
+ static_inventory += floor_change
+
+ action_intent = new /atom/movable/screen/combattoggle/flashy/voidwalker(null, src)
+ action_intent.icon = ui_style
+ static_inventory += action_intent
+
+ var/atom/movable/screen/space_camo_toggle = new /atom/movable/screen/space_camo(null, src)
+ space_camo_toggle.screen_loc = ui_above_movement
+ static_inventory += space_camo_toggle
+
+ var/atom/movable/screen/vomit_jump/vomit = new /atom/movable/screen/vomit_jump(null, src)
+ vomit.screen_loc = ui_mood
+ static_inventory += vomit
+
+/// This exists because for some reason only the combat indicator screen_loc is constantly set to initial
+/atom/movable/screen/combattoggle/flashy/voidwalker
+ screen_loc = ui_movi
+
+/atom/movable/screen/space_camo
+ name = "space camouflage toggle"
+ icon = 'icons/hud/screen_voidwalker.dmi'
+ icon_state = "camo_toggle"
+
+ /// Wheter or not we're toggled on or off
+ var/invisibility_toggle = TRUE
+
+/atom/movable/screen/space_camo/Click()
+ if(!isliving(usr))
+ return
+
+ invisibility_toggle = !invisibility_toggle
+ update_appearance()
+
+ if(invisibility_toggle)
+ REMOVE_TRAIT(usr, TRAIT_INVISIBILITY_BLOCKED, type)
+ else
+ ADD_TRAIT(usr, TRAIT_INVISIBILITY_BLOCKED, type)
+
+/atom/movable/screen/space_camo/update_icon_state()
+ icon_state = initial(icon_state) + (invisibility_toggle ? "" : "_off")
+ return ..()
+
+/atom/movable/screen/vomit_jump
+ name = "vomit tracker"
+ icon = 'icons/hud/screen_voidwalker.dmi'
+ icon_state = "vomitjump"
+ /// So we can sort of loop through it
+ var/index = 1
+
+/atom/movable/screen/vomit_jump/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+
+ var/icon/vomit_overlay = icon(/obj/effect/decal/cleanable/vomit/nebula::icon, pick(/obj/effect/decal/cleanable/vomit/nebula::random_icon_states))
+ add_overlay(vomit_overlay)
+
+/atom/movable/screen/vomit_jump/examine(mob/user)
+ . = ..()
+
+ var/list/vomits = get_valid_vomits()
+
+ . += span_notice("Tracks all of the nebula vomits generated by the people you have touched.")
+ . += span_notice("Left-click while in space dive to move to a nebula vomit, if there are any.")
+ . += span_notice("Current nebula vomits: [vomits.len]")
+
+
+/atom/movable/screen/vomit_jump/proc/get_valid_vomits()
+ var/list/valid_vomits = list()
+
+ for(var/atom/movable/vomit as anything in GLOB.nebula_vomits)
+ if(!is_station_level(vomit.z))
+ return
+
+ valid_vomits += vomit
+ return valid_vomits
+
+/atom/movable/screen/vomit_jump/Click(location, control, params)
+ var/list/modifiers = params2list(params)
+ if(LAZYACCESS(modifiers, RIGHT_CLICK) || LAZYACCESS(modifiers, SHIFT_CLICK))
+ usr.examinate(src)
+ return
+
+ if(!isliving(usr))
+ return
+
+ if(!istype(usr.loc, /obj/effect/dummy/phased_mob/space_dive))
+ usr.balloon_alert(usr, "must be space diving!")
+ return
+
+ var/list/vomits = get_valid_vomits()
+
+ if(!vomits.len)
+ usr.balloon_alert(usr, "no nebula vomits!")
+ return
+
+ var/obj/effect/dummy/phased_mob/space_dive/holder = usr.loc
+
+ index = (index % vomits.len) + 1
+
+ holder.forceMove(get_step(vomits[index], pick(GLOB.alldirs)))
+ usr.playsound_local(usr, 'sound/effects/phasein.ogg', 50, FALSE)
diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm
index 40e46d4cf0e..2058df26c0e 100644
--- a/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm
+++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm
@@ -715,11 +715,13 @@
name = "Voidwalker"
config_tag = "Voidwalker"
preview_antag_datum = /datum/antagonist/voidwalker
+
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_VOIDWALKER
ruleset_flags = RULESET_INVADER
+
weight = 5
- min_pop = 30 // Ensures there's a lot of people near windows
+ min_pop = 40 // Ensures there's a lot of people near windows
max_antag_cap = 1
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_VOIDWALKER_VOID)
signup_atom_appearance = /obj/item/clothing/head/helmet/skull/cosmic
@@ -727,9 +729,11 @@
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/can_be_selected()
return ..() && !SSmapping.is_planetary() && !isnull(find_space_spawn())
+/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/create_ruleset_body()
+ return new /mob/living/basic/voidwalker
+
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/voidwalker)
- candidate.current.set_species(/datum/species/voidwalker)
candidate.current.forceMove(find_space_spawn())
playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1)
diff --git a/code/datums/components/debris_bleeder.dm b/code/datums/components/debris_bleeder.dm
new file mode 100644
index 00000000000..0167c73a567
--- /dev/null
+++ b/code/datums/components/debris_bleeder.dm
@@ -0,0 +1,36 @@
+/// Drop debris when taking damage
+/datum/component/debris_bleeder
+ /// The type of debrees to spawn at a certain damage threshold. First threshold to hit in the list wins, so usually you'd want to construct from descending damage
+ /// list(/obj/item/toolbox = 30, /obj/item/wire = 20, etc)
+ var/list/debris_to_damage
+ /// Which type of damage to respond to
+ var/damage_type
+ /// The sound to play on hit
+ var/sound
+ /// Minimal damage at which we can play the sound
+ var/sound_threshold
+
+/datum/component/debris_bleeder/Initialize(list/debris_to_damage, damage_type = BRUTE, sound = null, sound_threshold = 0)
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.debris_to_damage = debris_to_damage
+ src.damage_type = damage_type
+ src.sound = sound
+ src.sound_threshold = sound_threshold
+
+ RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_apply_damage))
+
+/datum/component/debris_bleeder/proc/on_apply_damage(mob/living/liver, amount, damage_type)
+ SIGNAL_HANDLER
+
+ if(src.damage_type != damage_type)
+ return
+
+ for(var/key in debris_to_damage)
+ var/value = debris_to_damage[key]
+
+ if(value < amount)
+ new key ((get_turf(liver)))
+ if(sound && sound_threshold < amount)
+ playsound(liver, sound, 60)
diff --git a/code/datums/components/glass_passer.dm b/code/datums/components/glass_passer.dm
index f96300341b0..0a52e5e685f 100644
--- a/code/datums/components/glass_passer.dm
+++ b/code/datums/components/glass_passer.dm
@@ -2,13 +2,17 @@
/datum/component/glass_passer
/// How long does it take us to move into glass?
var/pass_time = 0 SECONDS
+ /// How long does the window stay deformed and passable for others?
+ var/deform_glass = 0 SECONDS
-/datum/component/glass_passer/Initialize(pass_time)
+/datum/component/glass_passer/Initialize(pass_time, deform_glass)
if(!ismob(parent)) //if its not a mob then just directly use passwindow
return COMPONENT_INCOMPATIBLE
src.pass_time = pass_time
+ src.deform_glass = deform_glass
+/datum/component/glass_passer/RegisterWithParent()
if(!pass_time)
passwindow_on(parent, type)
else
@@ -16,14 +20,16 @@
var/mob/mobbers = parent
mobbers.generic_canpass = FALSE
- RegisterSignal(parent, COMSIG_MOVABLE_CROSS_OVER, PROC_REF(cross_over))
+ RegisterSignal(parent, COMSIG_MOVABLE_CROSS_OVER, PROC_REF(on_cross_over))
-/datum/component/glass_passer/Destroy()
- . = ..()
+ if(deform_glass)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+
+/datum/component/glass_passer/UnregisterFromParent()
if(parent)
passwindow_off(parent, type)
-/datum/component/glass_passer/proc/cross_over(mob/passer, atom/crosser)
+/datum/component/glass_passer/proc/on_cross_over(mob/passer, atom/crosser)
SIGNAL_HANDLER
if(istype(crosser, /obj/structure/grille))
@@ -34,6 +40,27 @@
return null
+/datum/component/glass_passer/proc/on_moved(mob/living/phaser, atom/old_loc, dir, forced, list/old_locs)
+ SIGNAL_HANDLER
+
+ for(var/obj/structure/structure as anything in old_loc)
+ if(!structure.density || !istype(structure, /obj/structure/window) && !istype(structure, /obj/structure/grille))
+ continue
+
+ blomperize(structure)
+
+/// Make the window temporarily passable
+/datum/component/glass_passer/proc/blomperize(obj/structure/structure)
+ structure.density = FALSE
+ apply_wibbly_filters(structure)
+
+ addtimer(CALLBACK(src, PROC_REF(unblomperize), structure), deform_glass)
+
+/// Reset the windows passability
+/datum/component/glass_passer/proc/unblomperize(obj/structure/structure)
+ structure.density = initial(structure.density)
+ remove_wibbly_filters(structure, 0.5 SECONDS)
+
/datum/component/glass_passer/proc/bumped(mob/living/owner, atom/bumpee)
SIGNAL_HANDLER
diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm
index f8abd0116da..5d8fbbfbc44 100644
--- a/code/datums/components/igniter.dm
+++ b/code/datums/components/igniter.dm
@@ -3,7 +3,7 @@
var/fire_type
/datum/component/igniter/Initialize(fire_stacks = 1, fire_type = /datum/status_effect/fire_handler/fire_stacks)
- if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isprojectilespell(parent))
+ if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isprojectilespell(parent) && !isbasicmob(parent))
return COMPONENT_INCOMPATIBLE
src.fire_stacks = fire_stacks
@@ -14,8 +14,8 @@
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, PROC_REF(projectile_hit))
else if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(item_afterattack))
- else if(ishostile(parent))
- RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(hostile_attackingtarget))
+ else if(isbasicmob(parent))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(unarmed_attack))
/datum/component/igniter/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_POST_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
@@ -25,11 +25,9 @@
do_igniter(target)
-/datum/component/igniter/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target, success)
+/datum/component/igniter/proc/unarmed_attack(mob/living/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- if(!success)
- return
do_igniter(target)
/datum/component/igniter/proc/projectile_hit(datum/fired_from, atom/movable/firer, atom/target, Angle)
diff --git a/code/datums/components/regenerator.dm b/code/datums/components/regenerator.dm
index a8fd3d0c46c..44c189eaf2f 100644
--- a/code/datums/components/regenerator.dm
+++ b/code/datums/components/regenerator.dm
@@ -25,6 +25,8 @@
var/outline_colour
/// When this timer completes we start restoring health, it is a timer rather than a cooldown so we can do something on its completion
var/regeneration_start_timer
+ /// Callback for adding special checks for whether or not we can start regenning
+ var/datum/callback/regen_check = null
/datum/component/regenerator/Initialize(
regeneration_delay = 6 SECONDS,
@@ -35,6 +37,7 @@
heals_wounds = FALSE,
ignore_damage_types = list(STAMINA),
outline_colour = COLOR_PALE_GREEN,
+ regen_check = null,
)
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
@@ -47,6 +50,7 @@
src.heals_wounds = heals_wounds
src.ignore_damage_types = ignore_damage_types
src.outline_colour = outline_colour
+ src.regen_check = regen_check
/datum/component/regenerator/RegisterWithParent()
. = ..()
@@ -71,6 +75,10 @@
if (damagetype in ignore_damage_types)
return
+
+ reset_regeneration_timer()
+
+/datum/component/regenerator/proc/reset_regeneration_timer()
stop_regenerating()
regeneration_start_timer = addtimer(CALLBACK(src, PROC_REF(start_regenerating)), regeneration_delay, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE)
@@ -129,6 +137,11 @@
/datum/component/regenerator/proc/should_be_regenning(mob/living/who)
if(who.stat == DEAD)
return FALSE
+
+ if(regen_check && !regen_check.Invoke(who))
+ reset_regeneration_timer()
+ return FALSE
+
if(heals_wounds && iscarbon(who))
var/mob/living/carbon/carbon_who = who
if(length(carbon_who.all_wounds) > 0)
diff --git a/code/datums/components/space_camo.dm b/code/datums/components/space_camo.dm
index 50ccab7b181..267bcb89402 100644
--- a/code/datums/components/space_camo.dm
+++ b/code/datums/components/space_camo.dm
@@ -6,22 +6,28 @@
var/non_space_alpha
/// How long we can't enter camo after hitting or being hit
var/reveal_after_combat
+ /// Icon override for our camouflage
+ var/image/camo_icon
/// The world time after we can camo again
VAR_PRIVATE/next_camo
/// Image we show to our jaunter so they can see where they are
var/image/position_indicator
- /// Icon we draw our position indicator from
- var/phased_mob_icon = 'icons/obj/weapons/guns/projectiles.dmi'
- /// Icon state we use for our position indicator
- var/phased_mob_icon_state = "solarflare"
+ /// The alpha we see ourselves at when in camo
+ var/alpha_to_self
+ /// Are we currently camouflaged?
+ var/is_camouflaged = FALSE
+ /// What is the type for the space tile? For easy admin modding
+ var/turf/open/camo_tile = /turf/open/space
-/datum/component/space_camo/Initialize(space_alpha, non_space_alpha, reveal_after_combat)
+/datum/component/space_camo/Initialize(space_alpha, non_space_alpha, alpha_to_self = 120, reveal_after_combat, camo_icon)
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
+ src.camo_icon = camo_icon
+ src.alpha_to_self = alpha_to_self
/datum/component/space_camo/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_ENTERING, PROC_REF(on_atom_entering))
@@ -29,11 +35,18 @@
return
var/mob/living/living_parent = parent
- position_indicator = image(phased_mob_icon, living_parent, phased_mob_icon_state, ABOVE_LIGHTING_PLANE)
+
+ position_indicator = camo_icon ? camo_icon : image(living_parent, ABOVE_LIGHTING_PLANE)
+
+ position_indicator.loc = living_parent
+
SET_PLANE_EXPLICIT(position_indicator, ABOVE_LIGHTING_PLANE, living_parent)
position_indicator.appearance_flags |= RESET_ALPHA
position_indicator.alpha = 0
+ RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_INVISIBILITY_BLOCKED), PROC_REF(added_invisibility_block))
+ RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_INVISIBILITY_BLOCKED), PROC_REF(removed_invisibility_block))
+
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))
RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(show_client_image))
show_client_image(parent)
@@ -61,10 +74,12 @@
exit_camo(parent)
/datum/component/space_camo/proc/attempt_enter_camo()
- if(!isspaceturf(get_turf(parent)) || next_camo > world.time)
+ if(!istype(get_turf(parent), camo_tile) || next_camo > world.time || HAS_TRAIT(parent, TRAIT_INVISIBILITY_BLOCKED))
return FALSE
- enter_camo(parent)
+ if(!is_camouflaged)
+ enter_camo(parent)
+
return TRUE
/datum/component/space_camo/proc/force_exit_camo()
@@ -78,16 +93,20 @@
if(parent.alpha != space_alpha)
animate(parent, alpha = space_alpha, time = 0.5 SECONDS)
if (position_indicator)
- animate(position_indicator, alpha = 255, time = 0.5 SECONDS)
+ animate(position_indicator, alpha = alpha_to_self, time = 0.5 SECONDS)
parent.remove_from_all_data_huds()
- parent.add_atom_colour(SSparallax.get_parallax_color(), TEMPORARY_COLOUR_PRIORITY)
+ is_camouflaged = TRUE
/datum/component/space_camo/proc/exit_camo(atom/movable/parent)
+ if(!is_camouflaged)
+ return
+
animate(parent, alpha = non_space_alpha, time = 0.5 SECONDS)
if (position_indicator)
animate(position_indicator, alpha = 0, time = 0.5 SECONDS)
parent.add_to_all_human_data_huds()
- parent.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
+ DO_FLOATING_ANIM(parent)
+ is_camouflaged = FALSE
/datum/component/space_camo/proc/show_client_image(mob/show_to)
SIGNAL_HANDLER
@@ -96,3 +115,13 @@
/datum/component/space_camo/proc/remove_client_image(mob/remove_from)
SIGNAL_HANDLER
remove_from.client?.images -= position_indicator
+
+/datum/component/space_camo/proc/added_invisibility_block(atom/movable/parent)
+ SIGNAL_HANDLER
+
+ exit_camo(parent)
+
+/datum/component/space_camo/proc/removed_invisibility_block(atom/movable/parent)
+ SIGNAL_HANDLER
+
+ attempt_enter_camo()
diff --git a/code/datums/components/space_dive.dm b/code/datums/components/space_dive.dm
index 3a172307aab..63920e3506e 100644
--- a/code/datums/components/space_dive.dm
+++ b/code/datums/components/space_dive.dm
@@ -8,17 +8,24 @@
var/surface_time = 1 SECONDS
/// Traits added during phasing (and removed after)
var/static/phase_traits = list(TRAIT_MAGICALLY_PHASED, TRAIT_RUNECHAT_HIDDEN, TRAIT_WEATHER_IMMUNE)
+ /// Type of turf that is divable
+ var/diveable_turf = /turf/open/space
+ /// A decal we can dive from, and escape into (but only one enter)
+ var/diveable_decal = /obj/effect/decal/cleanable/vomit/nebula
-/datum/component/space_dive/Initialize(...)
+/datum/component/space_dive/Initialize(jaunt_type)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
+ src.jaunt_type = jaunt_type
+
RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(bump))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
/datum/component/space_dive/proc/bump(mob/living/parent, atom/bumped)
SIGNAL_HANDLER
- if(!isspaceturf(get_turf(parent)))
+ if(!istype(get_turf(parent), diveable_turf))
return
if(ismovable(bumped))
@@ -40,6 +47,9 @@
/datum/component/space_dive/proc/dive(atom/bumped)
var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(get_turf(bumped), parent)
+ var/mob/living/diver = parent
+ diver.drop_all_held_items()
+
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))
@@ -50,7 +60,7 @@
/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))
+ if(!istype(new_turf, diveable_turf) && !(locate(diveable_decal) in new_turf))
return
INVOKE_ASYNC(src, PROC_REF(attempt_surface), parent, new_turf)
@@ -59,6 +69,12 @@
/// 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))))
+ var/decal = locate(diveable_decal) in new_turf
+
+ if(decal)
+ var/obj/particles = new /obj/effect/abstract/particle_holder (decal, /particles/void_vomit)
+ QDEL_IN(particles, 60 SECONDS)
+
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
@@ -80,6 +96,29 @@
// This needs to happen at the end, after all the traits and stuff is handled
SEND_SIGNAL(parent, COMSIG_MOB_AFTER_EXIT_JAUNT, src)
+/datum/component/space_dive/proc/on_unarmed_attack(mob/living/source, atom/target, proximity, modifiers)
+ SIGNAL_HANDLER
+
+ if(istype(target, diveable_decal))
+ INVOKE_ASYNC(src, PROC_REF(try_enter), source, target)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/datum/component/space_dive/proc/try_enter(mob/living/source, atom/movable/decal)
+ if(!do_after(source, 1 SECONDS, decal))
+ return
+
+ dive(decal)
+ new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(decal))
+ qdel(decal)
+
/obj/effect/dummy/phased_mob/space_dive
movespeed = 1
phased_mob_icon_state = "solarflare"
+
+/obj/effect/dummy/phased_mob/space_dive/voidwalker
+ phased_mob_icon = /mob/living/basic/voidwalker::icon
+ phased_mob_icon_state = /mob/living/basic/voidwalker::icon_state + "_stealthed"
+
+/obj/effect/dummy/phased_mob/space_dive/sunwalker
+ phased_mob_icon = /mob/living/basic/voidwalker/sunwalker::icon
+ phased_mob_icon_state = /mob/living/basic/voidwalker/sunwalker::icon_state + "_stealthed"
diff --git a/code/datums/components/space_kidnap.dm b/code/datums/components/space_kidnap.dm
deleted file mode 100644
index 436242ca3c0..00000000000
--- a/code/datums/components/space_kidnap.dm
+++ /dev/null
@@ -1,72 +0,0 @@
-/// 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(src, PROC_REF(check_incapacitated), victim)))
- 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)
- successfully_kidnapped()
-
-/datum/component/space_kidnap/proc/successfully_kidnapped(mob/living/carbon/human/kidnappee)
- SEND_SIGNAL(parent, COMSIG_VOIDWALKER_SUCCESSFUL_KIDNAP, kidnappee)
-
-/datum/component/space_kidnap/proc/check_incapacitated(mob/living/carbon/human/kidnappee)
- return kidnappee.incapacitated
diff --git a/code/datums/components/vision_hurting.dm b/code/datums/components/vision_hurting.dm
index 925395f3d39..3292f708555 100644
--- a/code/datums/components/vision_hurting.dm
+++ b/code/datums/components/vision_hurting.dm
@@ -2,13 +2,16 @@
/datum/component/vision_hurting
var/damage_per_second
var/message
+ /// Do people looking at us make a fuss?
+ var/silent
-/datum/component/vision_hurting/Initialize(damage_per_second=1, message="Your eyes burn as you look at")
+/datum/component/vision_hurting/Initialize(damage_per_second=1, message="Your eyes burn as you look at", silent = FALSE)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
src.damage_per_second = damage_per_second
src.message = message
+ src.silent = silent
START_PROCESSING(SSdcs, src)
@@ -20,6 +23,8 @@
if(!burning_orbs)
continue
burning_orbs.apply_organ_damage(damage_per_second * seconds_per_tick)
+ if(silent)
+ return
if(SPT_PROB(50, seconds_per_tick))
to_chat(viewer, span_userdanger("[message] [parent]!"))
if(SPT_PROB(20, seconds_per_tick))
diff --git a/code/datums/elements/glass_pacifist.dm b/code/datums/elements/glass_pacifist.dm
index dbec37bc434..6d5e608cfcb 100644
--- a/code/datums/elements/glass_pacifist.dm
+++ b/code/datums/elements/glass_pacifist.dm
@@ -7,7 +7,7 @@
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
- RegisterSignal(target, COMSIG_LIVING_ATTACK_ATOM, PROC_REF(check_if_glass))
+ RegisterSignals(target, list(COMSIG_LIVING_ATTACK_ATOM, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(check_if_glass))
/datum/element/glass_pacifist/proc/check_if_glass(mob/living/owner, atom/hit)
SIGNAL_HANDLER
diff --git a/code/datums/elements/pick_and_drop_only.dm b/code/datums/elements/pick_and_drop_only.dm
new file mode 100644
index 00000000000..530fffc51f1
--- /dev/null
+++ b/code/datums/elements/pick_and_drop_only.dm
@@ -0,0 +1,45 @@
+/// Give to a human or other dextrous mob to have them only be able to pick up and drop items, and not use them on anything
+/datum/element/pick_and_drop_only
+ element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
+
+/datum/element/pick_and_drop_only/Attach(datum/target)
+ . = ..()
+
+ if(!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, COMSIG_LIVING_PICKED_UP_ITEM, PROC_REF(on_picked_up))
+
+/datum/element/pick_and_drop_only/proc/on_picked_up(mob/living/liver, obj/item/item)
+ SIGNAL_HANDLER
+
+ RegisterSignal(item, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped))
+
+ RegisterSignals(item, list(COMSIG_ITEM_ATTACK,
+ COMSIG_ITEM_ATTACK_SECONDARY,
+ COMSIG_ITEM_ATTACK_ATOM,
+ COMSIG_ITEM_ATTACK_SELF,
+ COMSIG_ITEM_ATTACK_SELF_SECONDARY,
+ COMSIG_ITEM_INTERACTING_WITH_ATOM,
+ COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY,
+ ), \
+ PROC_REF(on_item_use))
+
+/datum/element/pick_and_drop_only/proc/on_dropped(obj/item/item, mob/user)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(item, list(
+ COMSIG_ITEM_DROPPED,
+ COMSIG_ITEM_ATTACK,
+ COMSIG_ITEM_ATTACK_SECONDARY,
+ COMSIG_ITEM_ATTACK_ATOM,
+ COMSIG_ITEM_ATTACK_SELF,
+ COMSIG_ITEM_ATTACK_SELF_SECONDARY,
+ COMSIG_ITEM_INTERACTING_WITH_ATOM,
+ COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY,
+ ))
+
+/datum/element/pick_and_drop_only/proc/on_item_use()
+ SIGNAL_HANDLER
+ // CANCEL EVERYTHING, WAHAHAHA!!
+ return COMPONENT_CANCEL_ATTACK_CHAIN
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index e95d070d87c..141d7dace9e 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -215,6 +215,9 @@
icon_state = "vomitnanite_1"
random_icon_states = list("vomitnanite_1", "vomitnanite_2", "vomitnanite_3", "vomitnanite_4")
+/// Tracked for voidwalkers to jump to and from
+GLOBAL_LIST_EMPTY(nebula_vomits)
+
/obj/effect/decal/cleanable/vomit/nebula
name = "nebula vomit"
desc = "Gosh, how... beautiful."
@@ -225,11 +228,17 @@
/obj/effect/decal/cleanable/vomit/nebula/Initialize(mapload, list/datum/disease/diseases)
. = ..()
update_appearance(UPDATE_OVERLAYS)
+ GLOB.nebula_vomits += src
/obj/effect/decal/cleanable/vomit/nebula/update_overlays()
. = ..()
. += emissive_appearance(icon, icon_state, src, alpha = src.alpha)
+/obj/effect/decal/cleanable/vomit/nebula/Destroy()
+ . = ..()
+
+ GLOB.nebula_vomits -= src
+
/// Nebula vomit with extra guests
/obj/effect/decal/cleanable/vomit/nebula/worms
diff --git a/code/modules/antagonists/voidwalker/sunwalker_mob.dm b/code/modules/antagonists/voidwalker/sunwalker_mob.dm
new file mode 100644
index 00000000000..3da7c177e80
--- /dev/null
+++ b/code/modules/antagonists/voidwalker/sunwalker_mob.dm
@@ -0,0 +1,81 @@
+/// Voidwalker murderbone variant focused around the sun (wow yet another fire element antag!!)
+/mob/living/basic/voidwalker/sunwalker
+ name = "Heliarch"
+ desc = "A cosmic entity covered in stellar corona. You DEFINITELY shouldn't stare."
+
+ icon_state = "sunwalker"
+
+ melee_damage_type = BURN
+ melee_damage_lower = 25
+ melee_damage_upper = 25
+
+ health = 200
+ maxHealth = 200
+
+ obj_damage = 50
+ speed = 0.2
+
+ maximum_survivable_temperature = INFINITY
+ pressure_resistance = INFINITY
+
+ charge = /datum/action/cooldown/mob_cooldown/charge/sunwalker
+ telepathy = /datum/action/cooldown/spell/list_target/telepathy/voidwalker/sunwalker
+ can_do_abductions = FALSE
+
+ regenerate_colour = COLOR_BLUE
+ light_range = 7
+
+ /// Temperature we move our surroundings towards
+ var/hotspot_temperature = 1000
+ /// Gas volume passively exposed to our temperature
+ var/hotspot_volume = 100
+
+ /// Water damage we take on any exposure
+ var/water_damage = 10
+ /// Below this health threshold, we dont take water damage
+ var/water_damage_cutoff = 10
+
+/mob/living/basic/voidwalker/sunwalker/unique_setup()
+ AddComponent(/datum/component/igniter)
+ AddComponent(/datum/component/vision_hurting, damage_per_second = 0.1, message = null, silent = TRUE)
+ AddComponent(/datum/component/space_dive, /obj/effect/dummy/phased_mob/space_dive/sunwalker)
+
+ create_reagents(1) // Needed for the water reagent interactions to work
+
+ var/static/list/ray_filter = list(type = "rays", size = 32, color = COLOR_BLUE_VERY_LIGHT)
+ add_filter("sunwalker_rays", 3, ray_filter)
+ animate(get_filter("sunwalker_rays"), offset = 1, time = 3 SECONDS, loop = -1, flags = ANIMATION_PARALLEL)
+
+/mob/living/basic/voidwalker/sunwalker/examine(mob/user)
+ . = ..()
+
+ if(!iscarbon(user))
+ return
+
+ // MY EYEESSS!!!
+ var/mob/living/carbon/carbon = user
+ if(carbon.get_eye_protection() < 1)
+ var/obj/item/organ/eyes/burning_orbs = locate() in carbon.organs
+ burning_orbs?.apply_organ_damage(5)
+
+/mob/living/basic/voidwalker/sunwalker/UnarmedAttack(atom/target, proximity_flag, list/modifiers)
+ . = ..()
+
+ do_sparks(rand(1, 4), source = target)
+
+/mob/living/basic/voidwalker/sunwalker/process(seconds_per_tick)
+ . = ..()
+ if(isopenturf(loc))
+ var/turf/location = loc
+ location.hotspot_expose(hotspot_temperature, hotspot_volume)
+
+ if(prob(2))
+ new /obj/effect/hotspot(location)
+
+/mob/living/basic/voidwalker/sunwalker/reagent_expose(datum/reagent/chem, methods, reac_volume, show_message, touch_protection)
+ . = ..()
+
+ if(istype(chem, /datum/reagent/water) && health > water_damage_cutoff)
+ take_overall_damage(water_damage)
+ playsound(src, 'sound/items/tools/welder.ogg', 80, TRUE, 1)
+ do_sparks(rand(4, 8), source = src)
diff --git a/code/modules/antagonists/voidwalker/sunwalkers.dm b/code/modules/antagonists/voidwalker/sunwalkers.dm
new file mode 100644
index 00000000000..3d052319285
--- /dev/null
+++ b/code/modules/antagonists/voidwalker/sunwalkers.dm
@@ -0,0 +1,41 @@
+/// Space antagonist that murders everyone in space and nearby it using the power of loads of fire
+/datum/antagonist/sunwalker
+ name = "\improper Sunwalker"
+ antagpanel_category = ANTAG_GROUP_ABOMINATIONS
+ pref_flag = ROLE_SUNWALKER
+
+ show_in_antagpanel = TRUE
+ antagpanel_category = "Voidwalker"
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
+ ui_name = "AntagInfoSunwalker"
+ suicide_cry = "FOR THE SUN!!"
+
+/datum/antagonist/sunwalker/greet()
+ . = ..()
+ owner.announce_objectives()
+
+/datum/antagonist/sunwalker/on_gain()
+ . = ..()
+
+ forge_objectives()
+
+/datum/antagonist/sunwalker/forge_objectives()
+ var/datum/objective/sunwalker/objective = new
+ objective.owner = owner
+ objectives += objective
+
+/datum/objective/sunwalker
+
+/datum/objective/sunwalker/New()
+ var/list/explanation_texts = list(
+ "Teach them to fear the void. Overhwelm them with your brilliance, then impart the truth of immolation. Seek to destroy, not enlighten.",
+ )
+ explanation_text = pick(explanation_texts)
+
+ if(prob(5))
+ explanation_text = "Man I fucking love glass."
+ ..()
+
+/datum/objective/sunwalker/check_completion()
+ return owner.current.stat != DEAD
diff --git a/code/modules/antagonists/voidwalker/voidwalker.dm b/code/modules/antagonists/voidwalker/voidwalker.dm
index a21d5d777d3..932582b4bd1 100644
--- a/code/modules/antagonists/voidwalker/voidwalker.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker.dm
@@ -3,13 +3,13 @@
name = "\improper Voidwalker"
antagpanel_category = ANTAG_GROUP_ABOMINATIONS
pref_flag = 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()
. = ..()
@@ -18,30 +18,19 @@
/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/get_preview_icon()
+ var/mob/living/basic/voidwalker/walker_type = /mob/living/basic/voidwalker
+ var/icon/icon = icon(walker_type::icon, walker_type::icon_state)
+ icon.Crop(5, 18, 30, 44)
+ return finish_preview_icon(icon)
/datum/antagonist/voidwalker/forge_objectives()
var/datum/objective/voidwalker_objective/objective = new
objective.owner = owner
objectives += objective
-/datum/outfit/voidwalker
- name = "Voidwalker (Preview only)"
-
-/datum/outfit/voidwalker/post_equip(mob/living/carbon/human/human, visuals_only)
- human.set_species(/datum/species/voidwalker)
-
/datum/objective/voidwalker_objective
/datum/objective/voidwalker_objective/New()
diff --git a/code/modules/antagonists/voidwalker/voidwalker_abilities.dm b/code/modules/antagonists/voidwalker/voidwalker_abilities.dm
index 3bf4e1b76c3..a1da3474d9c 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_abilities.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_abilities.dm
@@ -4,18 +4,22 @@
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_void"
+ overlay_icon_state = null
panel = null
spell_requirements = NONE
cooldown_time = 12 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
+ var/stare_time = 6 SECONDS
/// how long we stun someone on successful cast
var/stun_time = 2 SECONDS
/// stamina damage we doooo
var/stamina_damage = 80
+ /// Used briefly for checking if we comitted a combat during our staredown
+ VAR_PRIVATE/in_combat = FALSE
/datum/action/cooldown/spell/pointed/unsettle/is_valid_target(atom/cast_on)
. = ..()
@@ -24,26 +28,34 @@
cast_on.balloon_alert(owner, "cannot be targeted!")
return FALSE
- if(!check_if_in_view(cast_on))
+ if(!check_if_staring(cast_on))
owner.balloon_alert(owner, "cannot see you!")
return FALSE
- return TRUE
+ return .
/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_NO_IMMEDIATE_COOLDOWN
+ RegisterSignals(owner, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_LIVING_ATTACK_ATOM), PROC_REF(is_combatting))
-/datum/action/cooldown/spell/pointed/unsettle/proc/check_if_in_view(mob/living/carbon/human/target)
+ if(do_after(owner, stare_time, cast_on, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE, extra_checks = CALLBACK(src, PROC_REF(check_if_staring), cast_on), hidden = TRUE))
+ spookify(cast_on)
+
+ else
+ . = SPELL_NO_IMMEDIATE_COOLDOWN
+
+ UnregisterSignal(owner, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_LIVING_ATTACK_ATOM))
+
+/datum/action/cooldown/spell/pointed/unsettle/proc/check_if_staring(mob/living/carbon/human/target)
SIGNAL_HANDLER
if(target.is_blind() || !(owner in view(target, world.view)))
+ owner.balloon_alert(owner, "line of sight broken!")
return FALSE
+ if(in_combat)
+ owner.balloon_alert(owner, "interrupted by combat!")
+ in_combat = FALSE
return TRUE
/datum/action/cooldown/spell/pointed/unsettle/proc/spookify(mob/living/carbon/human/target)
@@ -56,12 +68,70 @@
new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(target))
SEND_SIGNAL(owner, COMSIG_ATOM_REVEAL)
+/datum/action/cooldown/spell/pointed/unsettle/proc/is_combatting()
+ SIGNAL_HANDLER
+
+ in_combat = TRUE
+
/obj/effect/temp_visual/circle_wave/unsettle
color = COLOR_PURPLE
/datum/action/cooldown/spell/list_target/telepathy/voidwalker
- name = "Transmit"
+ name = "Cosmic Transmit"
background_icon_state = "bg_void"
button_icon = 'icons/mob/actions/actions_voidwalker.dmi'
button_icon_state = "voidwalker_telepathy"
panel = null
+ overlay_icon_state = null
+
+/datum/action/cooldown/spell/list_target/telepathy/voidwalker/sunwalker
+ name = "Stellar Transmit"
+ background_icon_state = "bg_star"
+
+/datum/action/cooldown/mob_cooldown/charge/sunwalker
+ name = "Stellar Charge"
+ background_icon_state = "bg_star"
+ charge_past = 1
+ charge_damage = 30
+ cooldown_time = 8 SECONDS
+ destroy_objects = FALSE
+
+/datum/action/cooldown/mob_cooldown/charge/sunwalker/on_move(atom/source, atom/new_loc, atom/target)
+ . = ..()
+
+ new /obj/effect/hotspot(get_turf(owner))
+
+/datum/action/cooldown/mob_cooldown/charge/sunwalker/charge_end(datum/source)
+ . = ..()
+
+ for(var/turf/open/tile in range(1, owner))
+ new /obj/effect/hotspot(tile)
+
+/datum/action/cooldown/mob_cooldown/charge/sunwalker/do_charge_indicator(atom/charger, atom/charge_target)
+ return
+
+/datum/action/cooldown/mob_cooldown/charge/voidwalker
+ name = "Cosmic Dash"
+ button_icon_state = "void_dash"
+ charge_past = 0
+ charge_damage = 20
+ cooldown_time = 8 SECONDS
+ destroy_objects = FALSE
+ charge_distance = 40
+
+ /// Turfs that are valid to target
+ var/turf/valid_target_turf = /turf/open/space
+
+/datum/action/cooldown/mob_cooldown/charge/voidwalker/do_charge_indicator(atom/charger, atom/charge_target)
+ playsound(owner, 'sound/effects/curse/curse1.ogg', 100)
+ return
+
+/datum/action/cooldown/mob_cooldown/charge/voidwalker/on_moved(atom/source)
+ return
+
+/datum/action/cooldown/mob_cooldown/charge/voidwalker/PreActivate(atom/target)
+ if(istype(get_turf(target), valid_target_turf))
+ return ..()
+
+ owner.balloon_alert(owner, "must target [initial(valid_target_turf.name)]!")
+ return FALSE
diff --git a/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm b/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm
deleted file mode 100644
index a8e5b8de30f..00000000000
--- a/code/modules/antagonists/voidwalker/voidwalker_bodyparts.dm
+++ /dev/null
@@ -1,147 +0,0 @@
-///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
-
- 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
-
- 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
-
- bodypart_traits = list(TRAIT_CHUNKYFINGERS)
-
- 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
-
- bodypart_traits = list(TRAIT_CHUNKYFINGERS)
-
- 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
-
- 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
-
- 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
index 3b6c1f3a413..cada56a33eb 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm
@@ -93,7 +93,7 @@ GLOBAL_LIST_EMPTY(voidwalker_void)
. = ..()
gone.remove_traits(wisp_driver_traits, REF(src))
- to_chat(gone, span_boldwarning("You feel it would be very bad to get caught again."))
+ to_chat(gone, span_boldwarning("You feel watched."))
qdel(src)
/// Loop us around, maybe, if we're going to bump into a wall
diff --git a/code/modules/antagonists/voidwalker/voidwalker_loot.dm b/code/modules/antagonists/voidwalker/voidwalker_loot.dm
index 8dfa123b42d..1b2d8707f87 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_loot.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_loot.dm
@@ -16,12 +16,11 @@
/obj/item/clothing/head/helmet/skull/cosmic/attack_self(mob/user, modifiers)
. = ..()
- if(!uses || !ishuman(user))
+ if(istype(user, /mob/living/basic/voidwalker))
+ to_chat(user, span_bolddanger("OH GOD NOO!!!! WHYYYYYYYYY??!!! WHO WOULD DO THIS?!!"))
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?!!"))
+ if(!uses || !ishuman(user))
return
to_chat(user, span_purple("You begin staring into \the [src]..."))
@@ -29,14 +28,75 @@
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/curse/curse5.ogg', 60)
-
uses--
if(uses <= 0)
icon_state = drained_icon_state
light_on = FALSE
+
+ var/mob/living/carbon/human/starer = user
+
+ if(starer.has_trauma_type(/datum/brain_trauma/voided/stable))
+ starer.put_in_hands(new /obj/item/void_eater(), TRUE, forced = TRUE)
+ playsound(starer, 'sound/effects/blob/blobattack.ogg', 60, TRUE)
+ else
+ 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/curse/curse5.ogg', 60)
+
+/**
+ * An armblade that pops windows
+ */
+/obj/item/void_eater
+ name = "void eater" //as opposed to full eater
+ desc = "A deformed appendage, capable of shattering any glass and any flesh."
+ icon = 'icons/obj/weapons/voidwalker_items.dmi'
+ icon_state = "tentacle"
+ inhand_icon_state = "tentacle"
+ icon_angle = 180
+ 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/items/weapons/bladeslice.ogg'
+ wound_bonus = -30
+ exposed_wound_bonus = 20
+
+/obj/item/void_eater/Initialize(mapload)
+ . = ..()
+
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
+ AddComponent(/datum/component/temporary_glass_shatterer)
+
+/obj/effect/spawner/random/glass_shards
+ loot = list(/obj/item/shard = 2, /obj/item/shard/plasma = 1, /obj/item/shard/titanium = 1, /obj/item/shard/plastitanium = 1)
+ spawn_random_offset = TRUE
+
+ /// Min shards we generate
+ var/min_spawn = 4
+ /// Max shards we generate
+ var/max_spawn = 6
+
+/obj/effect/spawner/random/glass_shards/Initialize(mapload)
+ spawn_loot_count = rand(min_spawn, max_spawn)
+
+ return ..()
+
+/obj/effect/spawner/random/glass_shards/mini
+ min_spawn = 1
+ max_spawn = 2
+
+/obj/effect/spawner/random/glass_debris
+ /// Weighted list for the debris we spawn
+ loot = list(
+ /obj/effect/decal/cleanable/glass = 2,
+ /obj/effect/decal/cleanable/glass/plasma = 1,
+ /obj/effect/decal/cleanable/glass/titanium = 1,
+ /obj/effect/decal/cleanable/glass/plastitanium = 1,
+ )
diff --git a/code/modules/antagonists/voidwalker/voidwalker_mob.dm b/code/modules/antagonists/voidwalker/voidwalker_mob.dm
new file mode 100644
index 00000000000..7915e85d116
--- /dev/null
+++ b/code/modules/antagonists/voidwalker/voidwalker_mob.dm
@@ -0,0 +1,347 @@
+//Minimum strength to convert a wall into a void window.
+#define WALL_CONVERT_STRENGTH 40
+
+/// Voidwalker mob to void all over the place
+/mob/living/basic/voidwalker
+ name = "voidwalker"
+ desc = "A glass-like entity from the void between stars. You probably shouldn't stare."
+ icon = 'icons/mob/simple/voidwalker.dmi'
+ icon_state = "voidwalker"
+
+ mob_biotypes = MOB_SPECIAL
+ maxHealth = 150
+ health = 150
+ damage_coeff = list(BRUTE = 1, BURN = 0.66, TOX = 1, STAMINA = 1, OXY = 0)
+
+ pressure_resistance = 200
+ combat_mode = TRUE
+
+ obj_damage = 15
+
+ melee_damage_lower = 12
+ melee_damage_upper = 15
+ melee_attack_cooldown = CLICK_CD_MELEE
+
+ melee_damage_type = OXY
+
+ attack_sound = 'sound/items/weapons/shrink_hit.ogg'
+ attack_vis_effect = ATTACK_EFFECT_VOID
+
+ faction = list(FACTION_CARP)
+
+ habitable_atmos = null
+ minimum_survivable_temperature = 0
+ maximum_survivable_temperature = 1500
+
+ basic_mob_flags = DEL_ON_DEATH
+ speed = 0
+
+ hud_type = /datum/hud/dextrous/voidwalker
+ hud_possible = list(ANTAG_HUD)
+ sight = SEE_TURFS | SEE_MOBS
+
+ /// Color of our regen outline
+ var/regenerate_colour = COLOR_GRAY
+
+ /// How long does it take to kidnap them?
+ var/kidnap_time = 6 SECONDS
+ /// Are we kidnapping right now?
+ var/kidnapping = FALSE
+
+ /// Our stare/stun ability
+ var/datum/action/cooldown/spell/pointed/unsettle/unsettle
+ /// Our means of communicating with the world
+ var/datum/action/cooldown/spell/list_target/telepathy/voidwalker/telepathy = /datum/action/cooldown/spell/list_target/telepathy/voidwalker
+ /// Our cool charge ability
+ var/datum/action/cooldown/mob_cooldown/charge/charge = /datum/action/cooldown/mob_cooldown/charge/voidwalker
+
+ /// Alpha we have in space
+ var/space_alpha = 0
+ /// Alpha we have elsewhere
+ var/non_space_alpha = 255
+
+ /// Speed modifier given when in gravity
+ var/datum/movespeed_modifier/speed_modifier = /datum/movespeed_modifier/grounded_voidwalker
+
+ /// Cooldown for converting walls to void windows
+ COOLDOWN_DECLARE(wall_conversion)
+ /// How many wall conversions can we perform before we have to refresh?
+ var/conversions_remaining = 2
+
+ /// Damage type we do for rightclicks
+ var/rclick_damage_type = BRUTE
+ /// Can we speak?
+ var/can_speak = FALSE
+ /// Turf that our abilities rely on. VVing this automatically sets all the components and removes space dependency stuff if the new tile isnt space
+ var/home_turf = /turf/open/space
+ /// Decal that we can kidnap on
+ var/kidnapping_decal = /obj/effect/decal/cleanable/vomit/nebula
+ /// Toggle for abduction interactions, in-case we have non-kidnap subtypes ()
+ var/can_do_abductions = TRUE
+
+/mob/living/basic/voidwalker/Initialize(mapload, mob/tamer)
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) //Need to set before init cause if we init in hyperspace we get dragged before the trait can be added
+ . = ..()
+
+ AddElement(/datum/element/simple_flying)
+ AddElement(/datum/element/glass_pacifist)
+
+ AddComponent(/datum/component/regenerator, brute_per_second = 2, burn_per_second = 2, outline_colour = regenerate_colour, regen_check = CALLBACK(src, PROC_REF(can_regen)))
+ AddComponent(/datum/component/glass_passer, deform_glass = 5 SECONDS)
+ AddComponent(/datum/component/planet_allergy)
+
+ AddElement(/datum/element/dextrous, hud_type = hud_type)
+ AddComponent(/datum/component/basic_inhands, x_offset = -2)
+
+ AddElement(/datum/element/pick_and_drop_only)
+
+ AddComponent(/datum/component/debris_bleeder, \
+ list(/obj/effect/spawner/random/glass_shards = 20, /obj/effect/spawner/random/glass_debris = 0), \
+ BRUTE, SFX_SHATTER, sound_threshold = 20)
+
+ charge = new charge(src)
+ charge.Grant(src)
+
+ // Glass passing is handled by the glass passer component
+ passtable_on(src, type)
+
+ // Voidwalker lore is that radio's actually attracted them, so they should be able to listen to it
+ var/obj/item/radio/internal_radio = new /obj/item/radio(src)
+ internal_radio.keyslot = /obj/item/encryptionkey/heads/captain
+ internal_radio.subspace_transmission = TRUE
+ internal_radio.canhear_range = 0 // anything higher and people in the area will hear it too
+ internal_radio.recalculateChannels()
+
+ telepathy = new telepathy(src)
+ telepathy.Grant(src)
+
+ unique_setup()
+
+/// Stuff you might want different on subtypes
+/mob/living/basic/voidwalker/proc/unique_setup()
+ AddComponent(/datum/component/space_camo, space_alpha, non_space_alpha, 255, 5 SECONDS, image(icon, icon_state + "_stealthed", ABOVE_LIGHTING_PLANE))
+ AddComponent(/datum/component/space_dive, /obj/effect/dummy/phased_mob/space_dive/voidwalker)
+
+ unsettle = new(src)
+ unsettle.Grant(src)
+
+ fully_replace_character_name(null, pick(GLOB.voidwalker_names))
+
+/mob/living/basic/voidwalker/Destroy()
+ QDEL_NULL(unsettle)
+ QDEL_NULL(telepathy)
+ QDEL_NULL(charge)
+
+ return ..()
+
+/// Called on COMSIG_LIVING_UNARMED_ATTACK
+/mob/living/basic/voidwalker/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+
+ if(!. || !can_do_abductions)
+ return
+
+ if(ishuman(target))
+ var/mob/living/carbon/human/hewmon = target
+
+ var/should_attack = try_kidnap(hewmon)
+
+ if(!should_attack)
+ return FALSE
+
+ if(hewmon.stat == HARD_CRIT && !hewmon.has_trauma_type(/datum/brain_trauma/voided))
+ hewmon.balloon_alert(src, "is in crit!")
+ hewmon.Stun(5 SECONDS) // blocks some crit movement mechanics from a bunch of sources
+ return FALSE
+
+ // left click
+ if(LAZYACCESS(modifiers, LEFT_CLICK))
+ melee_damage_type = ishuman(target) ? initial(melee_damage_type) : rclick_damage_type
+
+ // Right click
+ else
+ melee_damage_type = rclick_damage_type
+
+ if(!istype(target, /turf/closed/wall))
+ return
+ INVOKE_ASYNC(src, PROC_REF(try_convert_wall), target)
+ return TRUE
+
+/// Called by the regenerator component so we only regen in space
+/mob/living/basic/voidwalker/proc/can_regen()
+ if(istype(get_turf(src), home_turf))
+ return TRUE
+ return FALSE
+
+/mob/living/basic/voidwalker/can_speak(allow_mimes)
+ return can_speak && ..()
+
+/mob/living/basic/voidwalker/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+
+ if(!isturf(loc) || !speed_modifier)
+ return
+
+ var/turf/new_turf = loc
+
+ //apply debufs for being in gravity
+ var/area/local_area = get_area(new_turf)
+ if(new_turf.has_gravity() && !is_area_nearby_station(local_area))
+ add_movespeed_modifier(speed_modifier)
+ //remove debufs for not being in gravity
+ else
+ remove_movespeed_modifier(speed_modifier)
+
+/mob/living/basic/voidwalker/death()
+ var/turf/spawn_loc = get_turf(src)
+ new /obj/effect/spawner/random/glass_shards(spawn_loc)
+ new /obj/item/clothing/head/helmet/skull/cosmic(spawn_loc)
+ playsound(get_turf(src), SFX_SHATTER, 100)
+
+ return ..()
+
+///
+/// Kidnapping
+///
+
+/// Start the kidnap interactions, including surprises for those who are already voided
+/mob/living/basic/voidwalker/proc/try_kidnap(mob/living/carbon/human/victim)
+ if(victim.has_trauma_type(/datum/brain_trauma/voided))
+ victim.balloon_alert(src, "already voided!")
+ new /obj/effect/temp_visual/circle_wave/unsettle(get_turf(victim))
+ victim.SetSleeping(30 SECONDS)
+ return FALSE
+
+ if(victim.stat == DEAD)
+ victim.balloon_alert(src, "is dead!")
+ return FALSE
+
+ if(victim.stat == CONSCIOUS) //we're still beating them up!!
+ return TRUE
+
+ if(!istype(get_turf(victim), home_turf) && !(locate(kidnapping_decal) in get_turf(victim)))
+ victim.balloon_alert(src, "not in space!")
+ return FALSE
+
+ if(!kidnapping)
+ INVOKE_ASYNC(src, PROC_REF(kidnap), src, victim)
+ return FALSE
+
+/// Start kidnapping the victim
+/mob/living/basic/voidwalker/proc/kidnap(mob/living/parent, mob/living/victim)
+ victim.Paralyze(kidnap_time) //so they don't get up if we already got em
+
+ var/static/list/wave_filter = list(type = "wave", x = 2, size = 4)
+ victim.add_filter("wave_filter_kidnap", 3, wave_filter)
+ animate(victim.get_filter("wave_filter_kidnap"), offset = 32, time = kidnap_time)
+
+ kidnapping = TRUE
+
+ if(do_after(parent, kidnap_time, victim, extra_checks = CALLBACK(src, PROC_REF(check_incapacitated), victim)))
+ take_them(victim)
+
+ victim.remove_filter("wave_filter_kidnap")
+
+ kidnapping = FALSE
+
+/// Woosh! You got takened
+/mob/living/basic/voidwalker/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)
+
+ conversions_remaining++
+
+ var/obj/wisp = new /obj/effect/wisp_mobile (get_turf(pick(GLOB.voidwalker_void)))
+ victim.forceMove(wisp)
+
+/// Check if theyre still incapacitated for the kidnap do_after
+/mob/living/basic/voidwalker/proc/check_incapacitated(mob/living/carbon/human/kidnappee)
+ return kidnappee.incapacitated
+
+/// Modding the voidwalker is funny, so setting the home_turf sets everything right for easy of modding
+/mob/living/basic/voidwalker/vv_edit_var(vname, vval)
+ . = ..()
+ // This is all very snowflakey code but like, it's supposed to be. It's just for helping admins mod it
+ if(vname == NAMEOF(src, home_turf))
+ var/datum/component/space_camo/camo = GetComponent(/datum/component/space_camo)
+ var/datum/component/space_dive/dive = GetComponent(/datum/component/space_dive)
+
+ camo.camo_tile = vval
+ dive.diveable_turf = vval
+ if(istype(charge, /datum/action/cooldown/mob_cooldown/charge/voidwalker))
+ var/datum/action/cooldown/mob_cooldown/charge/voidwalker/charge_voidwalker = charge
+ charge_voidwalker.valid_target_turf = vval
+
+ if(!isspaceturf(vval))
+ qdel(GetComponent(/datum/component/planet_allergy))
+ remove_movespeed_modifier(speed_modifier)
+ speed_modifier = null
+
+///
+/// Wall Conversion
+///
+
+/// Attempt to convert a wall into passable voidwalker windows
+/mob/living/basic/voidwalker/proc/try_convert_wall(turf/closed/wall/our_wall)
+ if(!conversions_remaining)
+ balloon_alert(src, "need more kidnaps!")
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!COOLDOWN_FINISHED(src, wall_conversion))
+ balloon_alert(src, "must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, wall_conversion))]!")
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!check_wall_validity(our_wall, src, silent = FALSE))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ playsound(our_wall, 'sound/effects/magic/blind.ogg', 100, TRUE)
+ new /obj/effect/temp_visual/transmute_tile_flash(our_wall)
+
+ var/obj/particles = new /obj/effect/abstract/particle_holder (our_wall, /particles/void_wall)
+
+ balloon_alert(src, "opening window...")
+ if(!do_after(src, 8 SECONDS, our_wall, hidden = TRUE))
+ qdel(particles)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ if(!conversions_remaining)
+ qdel(particles)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ qdel(particles)
+
+ var/list/target_walls = list()
+ target_walls += our_wall
+ for(var/turf/closed/wall/adjacent_wall in orange(1, our_wall))
+ if(check_wall_validity(adjacent_wall))
+ target_walls += adjacent_wall
+
+ playsound(our_wall, 'sound/effects/magic/blind.ogg', 100, TRUE)
+
+ for(var/turf/closed/wall/targeted_wall in target_walls)
+ new /obj/effect/temp_visual/transmute_tile_flash(targeted_wall)
+ targeted_wall.ScrapeAway()
+ new /obj/structure/window/fulltile/voidwalker(targeted_wall)
+ new /obj/structure/grille(targeted_wall)
+
+ conversions_remaining--
+ COOLDOWN_START(src, wall_conversion, 60 SECONDS)
+
+/// Check if the wall is valid for conversion
+/mob/living/basic/voidwalker/proc/check_wall_validity(turf/closed/wall/wall_to_check, silent = TRUE)
+ if(wall_to_check.hardness < WALL_CONVERT_STRENGTH)
+ if(!silent)
+ balloon_alert(src, "too strong!")
+ return FALSE
+ return TRUE
+
+#undef WALL_CONVERT_STRENGTH
diff --git a/code/modules/antagonists/voidwalker/voidwalker_organs.dm b/code/modules/antagonists/voidwalker/voidwalker_organs.dm
deleted file mode 100644
index e71fe7c2600..00000000000
--- a/code/modules/antagonists/voidwalker/voidwalker_organs.dm
+++ /dev/null
@@ -1,135 +0,0 @@
-/// Voidwalker eyes with nightvision and thermals
-/obj/item/organ/eyes/voidwalker
- name = "blackened orbs"
- desc = "These orbs will withstand the light of the sun, yet still see within the darkest voids."
- icon_state = "eyes_voidwalker"
- eye_icon_state = null
- blink_animation = FALSE
- iris_overlay = 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/brain/voidwalker
- name = "cosmic brain"
- desc = "A mind fully integrated into the cosmic thread."
- icon = 'icons/obj/medical/organs/shadow_organs.dmi'
- can_smoothen_out = FALSE
-
- /// 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
- /// Our brain transmit telepathy spell
- var/datum/action/transmit = /datum/action/cooldown/spell/list_target/telepathy/voidwalker
-
-/obj/item/organ/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, 5 SECONDS)
-
- organ_owner.AddElement(/datum/element/only_pull_living)
- organ_owner.AddElement(/datum/element/glass_pacifist)
- organ_owner.AddElement(/datum/element/no_crit_hitting)
-
- organ_owner.apply_status_effect(regen)
-
- unsettle = new unsettle(organ_owner)
- unsettle.Grant(organ_owner)
-
- transmit = new transmit(organ_owner)
- transmit.Grant(organ_owner)
-
- glass_breaker = new/obj/item/void_eater
- organ_owner.put_in_hands(glass_breaker)
-
-/obj/item/organ/brain/voidwalker/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
- . = ..()
-
- UnregisterSignal(organ_owner, COMSIG_ENTER_AREA)
- alpha = 255
-
- qdel(organ_owner.GetComponent(/datum/component/space_camo))
-
- organ_owner.RemoveElement(/datum/element/only_pull_living)
- organ_owner.RemoveElement(/datum/element/glass_pacifist)
- organ_owner.RemoveElement(/datum/element/no_crit_hitting)
-
- organ_owner.remove_status_effect(regen)
-
- unsettle.Remove(organ_owner)
- unsettle = initial(unsettle)
-
- transmit.Remove(organ_owner)
- transmit = initial(transmit)
-
- QDEL_NULL(glass_breaker)
-
-/obj/item/organ/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
- var/area/local_area = get_area(new_turf)
- if(new_turf.has_gravity() && !is_area_nearby_station(local_area))
- 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/brain/voidwalker/on_death()
- . = ..()
-
- var/turf/spawn_loc = get_turf(owner)
- new /obj/effect/spawner/random/glass_shards(spawn_loc)
- new /obj/item/clothing/head/helmet/skull/cosmic(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/random/glass_shards
- loot = list(/obj/item/shard = 2, /obj/item/shard/plasma = 1, /obj/item/shard/titanium = 1, /obj/item/shard/plastitanium = 1)
- spawn_random_offset = TRUE
-
- /// Min shards we generate
- var/min_spawn = 4
- /// Max shards we generate
- var/max_spawn = 6
-
-/obj/effect/spawner/random/glass_shards/Initialize(mapload)
- spawn_loot_count = rand(min_spawn, max_spawn)
-
- return ..()
-
-/obj/effect/spawner/random/glass_shards/mini
- min_spawn = 1
- max_spawn = 2
-
-/obj/effect/spawner/random/glass_debris
- /// Weighted list for the debris we spawn
- loot = list(
- /obj/effect/decal/cleanable/glass = 2,
- /obj/effect/decal/cleanable/glass/plasma = 1,
- /obj/effect/decal/cleanable/glass/titanium = 1,
- /obj/effect/decal/cleanable/glass/plastitanium = 1,
- )
diff --git a/code/modules/antagonists/voidwalker/voidwalker_particles.dm b/code/modules/antagonists/voidwalker/voidwalker_particles.dm
index 8ffbd4abd44..6b4d0a6b98a 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_particles.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_particles.dm
@@ -1,15 +1,30 @@
-/particles/void_kidnap
+/particles/void_wall
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
+ spawning = 3
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)
+ gravity = list(0, 0.1)
grow = 0.05
+
+/particles/void_vomit
+ icon = 'icons/effects/particles/voidwalker.dmi'
+ icon_state = list("void_1" = 1, "void_2" = 5)
+ width = 100
+ height = 300
+ count = 100
+ spawning = 1
+ lifespan = 3 SECONDS
+ fade = 2 SECONDS
+ velocity = list(0, 0.1, 0)
+ position = generator(GEN_SPHERE, 6, 6, NORMAL_RAND)
+ drift = generator(GEN_SPHERE, 0, 0.05, NORMAL_RAND)
+ friction = 0.1
+ gravity = list(0, 0.50)
diff --git a/code/modules/antagonists/voidwalker/voidwalker_species.dm b/code/modules/antagonists/voidwalker/voidwalker_species.dm
deleted file mode 100644
index 44c2ead61a3..00000000000
--- a/code/modules/antagonists/voidwalker/voidwalker_species.dm
+++ /dev/null
@@ -1,74 +0,0 @@
-/// 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,
- TRAIT_NO_THROWING,
- TRAIT_GENELESS,
- )
- 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 | ITEM_SLOT_EYES
-
- mutantbrain = /obj/item/organ/brain/voidwalker
- mutanteyes = /obj/item/organ/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, regenerate_icons)
- . = ..()
-
- 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)
-
- human_who_gained_species.fully_replace_character_name(null, pick(GLOB.voidwalker_names))
-
-/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/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
index b63e1bea2f8..9de244d5949 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm
@@ -2,22 +2,6 @@
/datum/movespeed_modifier/grounded_voidwalker
multiplicative_slowdown = 1.1
-/// Regenerate in space
-/datum/status_effect/space_regeneration
- id = "space_regeneration"
- duration = STATUS_EFFECT_PERMANENT
- 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 = STATUS_EFFECT_PERMANENT
diff --git a/code/modules/antagonists/voidwalker/voidwalker_traumas.dm b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm
index e2fd5d3ebe8..77f4f42bed3 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_traumas.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_traumas.dm
@@ -12,25 +12,45 @@
/// Color in which we paint the space texture
var/space_color = COLOR_WHITE
///traits we give on gain
- var/list/traits_to_apply = list(TRAIT_MUTE, TRAIT_PACIFISM)
+ var/list/traits_to_apply = list(TRAIT_PACIFISM)
/// Do we ban the person from entering space?
var/ban_from_space = TRUE
+ /// Chance we'll get a color from space_colors
+ var/coloring_chance = 50
/// Statis list of all possible space colors
- var/static/list/space_colors = list("#ffffff", "#00ccff","#b12bff","#ff7f3a","#ff1c55","#ff7597","#28ff94","#0fcfff","#ff8b4c","#ffc425","#2dff96","#1770ff","#ff3f31","#ffba3b")
+ var/static/list/space_colors = list("#00ccff","#b12bff","#ff7f3a","#ff1c55","#ff7597","#28ff94","#0fcfff","#ff8b4c","#ffc425","#2dff96","#1770ff","#ff3f31","#ffba3b")
+ /// Frequency at which we do a space vomit
+ var/vomit_frequency = 2
+ /// We take a little extra damage, cause we're like glass or something
+ var/brute_mod = 1.1
/datum/brain_trauma/voided/on_gain()
. = ..()
- space_color = pick(space_colors)
+
+ if(prob(coloring_chance))
+ space_color = pick(space_colors)
+
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)
+
+ if(!is_on_a_planet(owner))
+ owner.AddComponent(/datum/component/planet_allergy)
+
+ owner.AddComponent(/datum/component/debris_bleeder, \
+ list(/obj/effect/spawner/random/glass_shards = 20, /obj/effect/spawner/random/glass_debris = 0), \
+ BRUTE, SFX_SHATTER, sound_threshold = 20)
+
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)
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human = owner
+ human.physiology.brute_mod *= brute_mod
+
//your underwear is belong to us
if(ishuman(owner))
var/mob/living/carbon/human/human = owner //CARBON WILL NEVER BE REAL!!!!!
@@ -48,11 +68,22 @@
if(ban_from_space)
qdel(owner.GetComponent(/datum/component/banned_from_space))
qdel(owner.GetComponent(/datum/component/planet_allergy))
+ qdel(owner.GetComponent(/datum/component/debris_bleeder))
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human = owner
+ human.physiology.brute_mod /= brute_mod
for(var/obj/item/bodypart/bodypart as anything in owner.bodyparts)
untexture_limb(owner, bodypart)
owner.update_body()
+/datum/brain_trauma/voided/on_life(seconds_per_tick, times_fired)
+ . = ..()
+
+ if(prob(vomit_frequency))
+ owner.vomit(MOB_VOMIT_KNOCKDOWN, vomit_type = /obj/effect/decal/cleanable/vomit/nebula, distance = 0)
+
/// Apply the space texture
/datum/brain_trauma/voided/proc/texture_limb(atom/source, obj/item/bodypart/limb)
SIGNAL_HANDLER
@@ -85,13 +116,14 @@
/// 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)
+ traits_to_apply = list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD)
ban_from_space = FALSE
+ vomit_frequency = 0
/datum/brain_trauma/voided/stable/on_gain()
. = ..()
- owner.AddComponent(/datum/component/glass_passer, 2 SECONDS)
+ owner.AddComponent(/datum/component/glass_passer, 2 SECONDS, 2 SECONDS)
/datum/brain_trauma/voided/stable/on_lose()
. = ..()
diff --git a/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm b/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm
deleted file mode 100644
index 8d04b6a0089..00000000000
--- a/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm
+++ /dev/null
@@ -1,167 +0,0 @@
-//Minimum strength to convert a wall into a void window.
-#define WALL_CONVERT_STRENGTH 40
-
-/**
- * An armblade that pops windows
- */
-/obj/item/void_eater
- name = "void eater" //as opposed to full eater
- desc = "A deformed appendage, capable of shattering any glass and any flesh."
- icon = 'icons/obj/weapons/voidwalker_items.dmi'
- icon_state = "tentacle"
- inhand_icon_state = "tentacle"
- icon_angle = 180
- 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/items/weapons/bladeslice.ogg'
- wound_bonus = -30
- exposed_wound_bonus = 20
-
- /// Damage we loss per hit
- var/damage_loss_per_hit = 0.5
- /// The minimal damage we can reach
- var/damage_minimum = 15
- /// Cooldown for converting walls to void windows
- COOLDOWN_DECLARE(wall_conversion)
- ///How many wall conversions can we perform before we have to refresh?
- var/conversions_remaining = 2
-
-/obj/item/void_eater/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
-
- AddComponent(/datum/component/temporary_glass_shatterer)
-
-/obj/item/void_eater/equipped(mob/user)
- . = ..()
-
- RegisterSignal(user, COMSIG_VOIDWALKER_SUCCESSFUL_KIDNAP, PROC_REF(refresh))
-
-/obj/item/void_eater/dropped(mob/user, silent)
- . = ..()
- UnregisterSignal(user, COMSIG_VOIDWALKER_SUCCESSFUL_KIDNAP)
-
-/obj/item/void_eater/examine(mob/user)
- . = ..()
- . += span_notice("The [name] weakens each hit, recharge it by kidnapping someone!")
- . += span_notice("Sharpness: [round(force)]/[initial(force)]")
- . += span_notice("Wall Conversions remaining: [conversions_remaining ? conversions_remaining : "None! You must resharpen your blade!"]")
-
-/obj/item/void_eater/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers)
- 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/random/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/random/glass_shards (spawnloc)
- var/obj/item/organ/brain = hewmon.get_organ_by_type(/obj/item/organ/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
-
- target_mob.apply_status_effect(/datum/status_effect/void_eatered)
-
- if(force == damage_minimum + damage_loss_per_hit)
- user.balloon_alert(user, "void eater blunted!")
-
- force = max(force - damage_loss_per_hit, damage_minimum)
-
- if(prob(5))
- new /obj/effect/spawner/random/glass_debris (get_turf(user))
- return ..()
-
-/obj/item/void_eater/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
- if(!istype(interacting_with, /turf/closed/wall))
- return ITEM_INTERACT_BLOCKING
-
- if(!conversions_remaining)
- balloon_alert(user, "must refresh void eater!")
- return ITEM_INTERACT_BLOCKING
-
- if(!COOLDOWN_FINISHED(src, wall_conversion))
- balloon_alert(user, "must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, wall_conversion))]!")
- return ITEM_INTERACT_BLOCKING
-
- var/turf/closed/wall/our_wall = interacting_with
- if(!check_wall_validity(our_wall, user, silent = FALSE))
- return ITEM_INTERACT_BLOCKING
- playsound(interacting_with, 'sound/effects/magic/blind.ogg', 100, TRUE)
- new /obj/effect/temp_visual/transmute_tile_flash(interacting_with)
- balloon_alert(user, "opening window...")
- if(!do_after(user, 8 SECONDS, interacting_with, hidden = TRUE))
- return ITEM_INTERACT_BLOCKING
- if(!conversions_remaining)
- return ITEM_INTERACT_BLOCKING
-
- var/list/target_walls = list()
- target_walls += our_wall
- for(var/turf/closed/wall/adjacent_wall in orange(1, interacting_with))
- if(check_wall_validity(adjacent_wall, user))
- target_walls += adjacent_wall
-
- for(var/turf/closed/wall/targeted_wall in target_walls)
- playsound(targeted_wall, 'sound/effects/magic/blind.ogg', 100, TRUE)
- new /obj/effect/temp_visual/transmute_tile_flash(targeted_wall)
- targeted_wall.ScrapeAway()
- new /obj/structure/window/fulltile/voidwalker(targeted_wall)
- new /obj/structure/grille(targeted_wall)
- conversions_remaining--
- COOLDOWN_START(src, wall_conversion, 60 SECONDS)
- return ITEM_INTERACT_SUCCESS
-
-/// Called when the voidwalker kidnapped someone
-/obj/item/void_eater/proc/refresh(mob/living/carbon/human/voidwalker)
- SIGNAL_HANDLER
-
- force = initial(force)
-
- color = "#000000"
- animate(src, color = null, time = 1 SECONDS)//do a color flashy woosh
-
- to_chat(voidwalker, span_boldnotice("Your [name] refreshes!"))
-
-/obj/item/void_eater/proc/check_wall_validity(turf/closed/wall/wall_to_check, mob/living/user, silent = TRUE)
- if(wall_to_check.hardness < WALL_CONVERT_STRENGTH)
- if(!silent)
- balloon_alert(user, "too strong!")
- return FALSE
-
- for(var/turf/nearby_turf in orange(1, wall_to_check))
- var/area/nearby_area = get_area(nearby_turf)
- if(is_area_nearby_station(nearby_area))
- return TRUE
-
- if(!silent)
- balloon_alert(user, "not near space!")
-
- return FALSE
-
-
-
-#undef WALL_CONVERT_STRENGTH
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 8fb3ca204e5..e557caf5104 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1157,6 +1157,3 @@
/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/mob/living/life.dm b/code/modules/mob/living/life.dm
index ebe2f875609..76a501ae7af 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -122,7 +122,7 @@
* * needs_metabolizing (bool) takes into consideration if the chemical is matabolizing when it's checked.
*/
/mob/living/proc/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE)
- return reagents.has_reagent(reagent, amount, needs_metabolizing)
+ return reagents?.has_reagent(reagent, amount, needs_metabolizing)
/mob/living/proc/update_damage_hud()
return
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png
index 08ff8c889d2fe06d9b77ef7c560689eb9b2ca84b..1908a198a2cea9d1d7a56407b4b1ec8bdc7c2917 100644
GIT binary patch
delta 282
zcmV+#0p
zzyJUM0d!JMQvg8b*k%9#0A!IRGk*YbEX}w$Gg33tGfE(w;*!LYR3KAHiHkEOv#1!z
zH00t;D@x2wg|L+sT>V_YCISGS$rvAshC*oo004zaL_t(oh3(W$4nQy%gyBkh0CfWS
z!d_Ab5XJ?B`(H&=#Ban#6!WgvLYCv7pOa$U8_hM|8@R
z9Tvex-CaN?&rXZWT|&jdiaDvQ;r8OJ;_?1^4D(J($orHqcrY+9aeBHqhE&XXduMmy
zAqNq+hg%C7^BO032+m|OchZ%+?xf6jqN9T&^B|*Ua{3vsh9irPN6!50no;5T>-oF5
zdRqk+&q*pUwB-Mb*0@HO{haIP9@)0{lJ7
zGks%nGHv%-h8c`Nfp2c>9pc5hT~jOf-*?WQbd0M(lHm+P!p$#-1^byz3>DGhl8Ie0={$^1t8ir9Yor
zKfT-hP8llRYs!9MZ(V%V{rE7D1^>6^|P(*4NMZ
zbbe>yd;604_rYPT)4qT8O7G0X_rK=!rQ6KD(dKtoUieAyGA>z*nZWeL;OXk;vd$@?
F2>?;!7-;|i
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
deleted file mode 100644
index 78962671d119281aa4e97d11467d1f309ea73475..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1632
zcmV-m2A}zfP)V=-0C=1w$2$&yAP@z>nmxq}_Qqekje!^nxPytSP^bwIy}pIhO@rAkF2Hz4
zkK~f>3yi7lNWE#R5sYyl6(t?9{wZ~>0*2P4wv-DP6RAC~rxY-HYG8wb@s2*4{}>LN
zJdk?;00po~L_t(|oaI|FZ{kQ4{(M~I1nlS{oX$;v#??wI(NXv>q$dp>#g?vDTT@;^
zM*}}(3OcH7uyh4UB+4M26vt!~vA%*GXK)O7W{eS+Pl~XOnVI*^n>X*hdC>?V_{{j@
z*aJ8=K0N_{%Kp%G-H2TiLbQB_lO3H-C+>eR7)bXor2)L~Y4?YQ=XntTRIAmEdl?85
zuua&aNZ^GJf$=hT
z`8i%+UpJmRj2c_-3=2rCqd2EoI-pb={~{YQ@4kjswfG0KCt5qtS?k
z=1J*xyP5hHHiDDMB-490xDjo7XFKI`8NTnMQmMq>EtgB2ot?I(pI&r?ik9*w5nxKkS
zsfByo1Hcddc~aF`OdyKPHejDB|NI$Wejl&Kx!Qo=n(%$sd(0hXYM(Vm5jKL&W^-rb
z$(fhk3X{KM?ebgQK7&Uo-P@1UF0iXpHuJyLRvE0Fq((8rCu;SR*hv6C3t488Td&Fn
zkb$+6h0q`fFc=IpN+~F%nKDIt!{L2`^D}URo7`qT4co%rq!%cXA`=RRfOlia6T|lG
zv{_eihm(ev5TY>;k9T#`o@IrQ4d4gFuG!~!HHK~5AcSay5Y4vjR3SwB;@f0``o13v
z-R*WGQTLZeZV8cky^i^O9^a2fBduPqE4MisjW%XAEd*9EghB*tQ)k<@q3imG?{!@V
z(*#klHhC~FFAa|49DWIG!+bu+bUH=5-HuhT-EK$I>GZ=4X2obU(vofRV7|X>DvUum
zDM|>@($;ly_GJa%)h4jkf^N4PC9}dZvk#nLQj~#td0D)@T{u|+n0?3+@$JIN5|9^!
z9ijglKD`EfdIF@+20NWjRIOIASS$djozcs{^Sp@lt7df!$zipJPAc@gJBo~0K&y0UIwjFYa>^f6+)gM
zbAFicl+t(rc^IUuVAUaE7{d4c^%40mxwfQj0Hstp7FWS?xx~xM3oOgR+uIw)t8uIa
zBFH5eDsuLLwE=v1ctE96!PC=I{9NWVN{9fKemd6HmqfW-hGkiCJ%S8QJ{%6iQ0X{M
zh=AE_7Hfg7>mL%5hGAqXOOZvV(}|M)M;L}1RVkv#Jg+gK-YE5W-|cAZ-ESs0eoJrXs~(&rhT&Y2*Mh`&is>s
zP?7&Q<+^TEuh(IkX1oMV(}YYtf)D}8Gkd-=Tec2B2K+d9`y*42AXOvOY&JKNkVUS4
zejK)K!!%6*!1MDn=JWaC{QyFUc5LCY4l@Kq)P*Na~n*fNhW+
e)5%AJs`wAgMenO`m8%s10000bM_
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 ;vfdJ4tZ6980>
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 |