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. +