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 08ff8c889d2..1908a198a2c 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_voidwalker.png differ
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 78962671d11..00000000000
Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_voidwalker.png and /dev/null differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 6440b24b839..830a006527e 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/particles/voidwalker.dmi b/icons/effects/particles/voidwalker.dmi
index d7f94c98797..621e468ac99 100644
Binary files a/icons/effects/particles/voidwalker.dmi and b/icons/effects/particles/voidwalker.dmi differ
diff --git a/icons/hud/screen_voidwalker.dmi b/icons/hud/screen_voidwalker.dmi
new file mode 100644
index 00000000000..68a625786d3
Binary files /dev/null and b/icons/hud/screen_voidwalker.dmi differ
diff --git a/icons/mob/actions/actions_items.dmi b/icons/mob/actions/actions_items.dmi
index 3887804e55d..4477d75b30b 100644
Binary files a/icons/mob/actions/actions_items.dmi and b/icons/mob/actions/actions_items.dmi differ
diff --git a/icons/mob/actions/actions_spells.dmi b/icons/mob/actions/actions_spells.dmi
index 335229641fa..33459d6280e 100644
Binary files a/icons/mob/actions/actions_spells.dmi and b/icons/mob/actions/actions_spells.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 0c82afeb05c..e937c49c4a9 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/simple/voidwalker.dmi b/icons/mob/simple/voidwalker.dmi
new file mode 100644
index 00000000000..d6096589edb
Binary files /dev/null and b/icons/mob/simple/voidwalker.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 4c6854dce16..6244095c2b8 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -382,7 +382,6 @@
#include "code\__DEFINES\dcs\signals\signals_turf.dm"
#include "code\__DEFINES\dcs\signals\signals_twohand.dm"
#include "code\__DEFINES\dcs\signals\signals_vehicle.dm"
-#include "code\__DEFINES\dcs\signals\signals_voidwalker.dm"
#include "code\__DEFINES\dcs\signals\signals_wash.dm"
#include "code\__DEFINES\dcs\signals\signals_wizard.dm"
#include "code\__DEFINES\dcs\signals\signals_xeno_control.dm"
@@ -623,6 +622,7 @@
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\screentip.dm"
#include "code\_onclick\hud\soulscythe.dm"
+#include "code\_onclick\hud\voidwalkerhud.dm"
#include "code\_onclick\hud\parallax\parallax.dm"
#include "code\_onclick\hud\parallax\random_layer.dm"
#include "code\_onclick\hud\rendering\plane_master_controller.dm"
@@ -1133,6 +1133,7 @@
#include "code\datums\components\dart_insert.dm"
#include "code\datums\components\deadchat_control.dm"
#include "code\datums\components\death_linked.dm"
+#include "code\datums\components\debris_bleeder.dm"
#include "code\datums\components\dejavu.dm"
#include "code\datums\components\deployable.dm"
#include "code\datums\components\direct_explosive_trap.dm"
@@ -1279,7 +1280,6 @@
#include "code\datums\components\space_allergy.dm"
#include "code\datums\components\space_camo.dm"
#include "code\datums\components\space_dive.dm"
-#include "code\datums\components\space_kidnap.dm"
#include "code\datums\components\spawner.dm"
#include "code\datums\components\speechmod.dm"
#include "code\datums\components\spill.dm"
@@ -1586,6 +1586,7 @@
#include "code\datums\elements\pet_bonus.dm"
#include "code\datums\elements\pet_collar.dm"
#include "code\datums\elements\pet_cult.dm"
+#include "code\datums\elements\pick_and_drop_only.dm"
#include "code\datums\elements\plant_backfire.dm"
#include "code\datums\elements\point_of_interest.dm"
#include "code\datums\elements\poster_tearer.dm"
@@ -3456,17 +3457,16 @@
#include "code\modules\antagonists\valentines\heartbreaker.dm"
#include "code\modules\antagonists\valentines\valentine.dm"
#include "code\modules\antagonists\venus_human_trap\venus_human_trap.dm"
+#include "code\modules\antagonists\voidwalker\sunwalker_mob.dm"
+#include "code\modules\antagonists\voidwalker\sunwalkers.dm"
#include "code\modules\antagonists\voidwalker\voidwalker.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_abilities.dm"
-#include "code\modules\antagonists\voidwalker\voidwalker_bodyparts.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_kidnap.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_loot.dm"
-#include "code\modules\antagonists\voidwalker\voidwalker_organs.dm"
+#include "code\modules\antagonists\voidwalker\voidwalker_mob.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_particles.dm"
-#include "code\modules\antagonists\voidwalker\voidwalker_species.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_status_effects.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_traumas.dm"
-#include "code\modules\antagonists\voidwalker\voidwalker_void_eater.dm"
#include "code\modules\antagonists\voidwalker\voidwalker_window.dm"
#include "code\modules\antagonists\wishgranter\wishgranter.dm"
#include "code\modules\antagonists\wizard\imp_antag.dm"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoSunwalker.tsx b/tgui/packages/tgui/interfaces/AntagInfoSunwalker.tsx
new file mode 100644
index 00000000000..8e2607e4c9c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AntagInfoSunwalker.tsx
@@ -0,0 +1,62 @@
+import { BlockQuote, LabeledList, Section, Stack } from 'tgui-core/components';
+
+import { Window } from '../layouts';
+
+const tipstyle = {
+ color: 'white',
+};
+
+const noticestyle = {
+ color: 'lightblue',
+};
+
+export const AntagInfoSunwalker = (props) => {
+ return (
+
+
+
+
+
+
+ You are the Sunwalker
+
+
+ You are an ancient voidwalker, having been caught in a
+ supernova. You are altered, and hateful.
+
+
+ There will be no lessons or enlightenment, they wont survive
+ to learn from it.
+
+
+
+
+
+
+
+
+
+
+ You can move under the station from space, use this to hunt
+ and get to isolated sections of space.
+
+
+ Your attacks deal great burn damage and ignite those hit.
+
+
+ Your very skin heats the air around you, while the vacuum of
+ space mends any wounds your body might have sustained. You can
+ move through glass freely, but are slowed in gravity.
+
+
+ With an exploding burning speed, charge forwards, dealing
+ damage and burning the surroundings.
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AntagInfoVoidwalker.tsx b/tgui/packages/tgui/interfaces/AntagInfoVoidwalker.tsx
index 6bf20f74cf1..a37932ec22b 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoVoidwalker.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoVoidwalker.tsx
@@ -12,13 +12,13 @@ const noticestyle = {
export const AntagInfoVoidwalker = (props) => {
return (
-
+
-
+
- You are a Voidwalker.
+ You are the Voidwalker
You are a creature from the void between stars. You were
@@ -33,39 +33,57 @@ export const AntagInfoVoidwalker = (props) => {
stop you. You can move through windows, so stay near them to
always have a way out.
+
Hunt:
Pick unfair fights. Look for inattentive targets and strike at
them when they don't expect you.
+
Abduct:
- Your Unsettle ability stuns and drains your targets. Finish
- them with your void window and use it to pop a window, drag
- them into space and use an empty hand to kidnap them.
+ Your Unsettle ability stuns and drains your targets. Knock
+ them out with your draining slash, take them to space (or
+ nebula vomit) and enlighten them.
+
+
+ Reap:
+ Our students expell our essence regularly. We can use this to
+ go where we could otherwise not, but we reabsorb this when we
+ use it to leave. (You can dive in space vomit.)
-
+
You can move under the station from space, use this to hunt
and get to isolated sections of space.
-
- Your divine appendage; it allows you to incapacitate the loud
- ones and instantly break windows, or convert soft walls into
- windows for you to pass through.
+
+ You take the breath right from their lungs and quickly take
+ down even the strongest opponents. Should they resist,
+ right-click allows you to do some raw damage. Your arms are
+ otherwise not very sophisticated, and are not good for much
+ more than just grabbing things.
- Your natural camouflage makes you nearly invisible in space,
- as well as mending any wounds your body might have sustained.
- You can move through glass freely, but are slowed in gravity.
+ Your natural camouflage makes you invisible in space, as well
+ as mending any wounds your body might have sustained. You can
+ move through glass freely, but are slowed in gravity.
Target a victim while remaining only partially in their view
to stun and weaken them, but also announce them your presence.
+
+ With a short range and negligible damage, it makes for a poor
+ offensive tool, but great for quick escapes and repositions.
+
+
+ For every lesson we teach, we grow in power. We can convert
+ walls into glass to allow us to reach even further.
+