Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.
Merged
14 changes: 12 additions & 2 deletions code/game/objects/effects/effect_system/effects_sparks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@
light_power = 0.5
light_color = LIGHT_COLOR_FIRE
light_flags = LIGHT_NO_LUMCOUNT
var/duration = 2 SECONDS
var/volume = 100
var/sound = "sparks"

/obj/effect/particle_effect/sparks/Initialize(mapload)
..()
return INITIALIZE_HINT_LATELOAD

/obj/effect/particle_effect/sparks/LateInitialize()
flick(icon_state, src) // replay the animation
playsound(src, "sparks", 100, TRUE)
playsound(src, sound, volume, TRUE)
var/turf/T = loc
if(isturf(T))
T.hotspot_expose(1000,100)
QDEL_IN(src, 2 SECONDS)
QDEL_IN(src, duration)

/obj/effect/particle_effect/sparks/Destroy()
var/turf/T = loc
Expand All @@ -63,6 +66,13 @@
name = "lightning"
icon_state = "electricity"

/obj/effect/particle_effect/sparks/electricity/short //used for wirecrawling
name = "lightning"
icon_state = "electricity"
duration = 8
volume = 40
sound = "softsparks"

/obj/effect/particle_effect/sparks/quantum
name = "quantum sparks"
icon_state = "quantum_sparks"
Expand Down
33 changes: 33 additions & 0 deletions code/game/objects/items/granters/martial_arts/racial.dm
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,36 @@
var/obj/item/reagent_containers/glass/bottle/vial/empty = new(get_turf(user))
qdel(src)
user.put_in_active_hand(empty)


/obj/item/book/granter/action/wirecrawl
name = "modified yellow slime extract"
desc = "An experimental yellow slime extract that when absorbed by an Ethereal, grants control over electrical powers."
icon = 'icons/mob/slimes.dmi'
icon_state = "yellow slime extract"
granted_action = /datum/action/cooldown/spell/jaunt/wirecrawl
action_name = "Wirecrawling"
drop_sound = null
pickup_sound = null
remarks = list("Shock...", "Zap...", "High Voltage...", "Dissolve...", "Dissipate...", "Disperse...", "Red Hot...", "Spiral...", "Electro-magnetic...", "Turbo...")
book_sounds = list('sound/effects/sparks1.ogg','sound/effects/sparks2.ogg','sound/effects/sparks3.ogg')
var/admin = FALSE

/obj/item/book/granter/action/wirecrawl/on_reading_start(mob/user)
to_chat(user, span_notice("You hold \the [src] directly to your chest..."))

/obj/item/book/granter/action/wirecrawl/can_learn(mob/user)
if(isethereal(user) || admin)
return ..()
to_chat(user, span_warning("Yup, that's a slime extract alright."))
return FALSE

/obj/item/book/granter/action/wirecrawl/on_reading_finished(mob/living/carbon/user)
..()
if(!uses)
name = "grey slime extract"
desc = "Goo extracted from a slime. Legends claim these to have \"magical powers\"."
icon_state = "grey slime extract"

/obj/item/book/granter/action/wirecrawl/admin //if someone wants to spawn it in
admin = TRUE
2 changes: 2 additions & 0 deletions code/game/sound.dm
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ distance_multiplier - Can be used to multiply the distance at which the sound is
soundin = pick('sound/effects/creak1.ogg', 'sound/effects/creak2.ogg', 'sound/effects/creak3.ogg')
if ("sparks")
soundin = pick('sound/effects/sparks1.ogg','sound/effects/sparks2.ogg','sound/effects/sparks3.ogg','sound/effects/sparks4.ogg')
if ("softsparks")//doesn't have the cracking sound of sparks 4
soundin = pick('sound/effects/sparks1.ogg','sound/effects/sparks2.ogg','sound/effects/sparks3.ogg')
if ("rustle")
soundin = pick('sound/effects/rustle1.ogg','sound/effects/rustle2.ogg','sound/effects/rustle3.ogg','sound/effects/rustle4.ogg','sound/effects/rustle5.ogg')
if ("bodyfall")
Expand Down
1 change: 1 addition & 0 deletions code/modules/mob/living/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@
else
clear_fullscreen("remote_view", 0)
update_pipe_vision()
update_wire_vision()

/mob/living/vv_edit_var(var_name, var_value)
switch(var_name)
Expand Down
1 change: 1 addition & 0 deletions code/modules/mob/living/living_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
var/has_limbs = 0 //does the mob have distinct limbs?(arms,legs, chest,head)

var/list/pipes_shown = list()
var/list/wires_shown = list()
var/last_played_vent

var/smoke_delay = FALSE //used to prevent spam with smoke reagent reaction on mob.
Expand Down
4 changes: 4 additions & 0 deletions code/modules/power/cable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ By design, d1 is the smallest direction and d2 is the highest
pipe_group = "cable-[cable_color]"\
)

var/image/wire_vision_img //specifically for wirecrawling

/obj/structure/cable/yellow
cable_color = "yellow"
color = "#ffff00"
Expand Down Expand Up @@ -96,6 +98,8 @@ By design, d1 is the smallest direction and d2 is the highest
if(powernet)
cut_cable_from_powernet() // update the powernets
GLOB.cable_list -= src //remove it from global cable list
if(wire_vision_img)
qdel(wire_vision_img)
return ..() // then go ahead and delete the cable

/obj/structure/cable/deconstruct(disassembled = TRUE)
Expand Down
8 changes: 8 additions & 0 deletions code/modules/power/power.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
idle_power_usage = 0
active_power_usage = 0

var/image/wire_vision_img //specifically for wirecrawling

/obj/machinery/power/Destroy()
if(wire_vision_img)
qdel(wire_vision_img)
disconnect_from_network()
return ..()

///////////////////////////////
// General procedures
//////////////////////////////
Expand Down
252 changes: 252 additions & 0 deletions code/modules/spells/spell_types/jaunt/wirecrawl.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/**
* ### Blood Crawl
*
* Lets the caster enter and exit pools of blood.
*/
/datum/action/cooldown/spell/jaunt/wirecrawl
name = "Wire Crawl"
desc = "Allows you to break your body down into electricity and travel through wires."

button_icon = 'icons/effects/effects.dmi'
button_icon_state = "electricity2"

spell_requirements = NONE
antimagic_flags = NONE
panel = null
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED

/// The time it takes to enter blood
var/enter_blood_time = 3 SECONDS
/// The time it takes to exit blood
var/exit_blood_time = 0 SECONDS
/// The radius around us that we look for wires in
var/enter_radius = 1
/// If TRUE, we equip "wire crawl" hands to the jaunter to prevent using items
var/equip_wire_hands = TRUE

jaunt_type = /obj/effect/dummy/phased_mob/wirecrawl

/datum/action/cooldown/spell/jaunt/wirecrawl/Grant(mob/grant_to)
. = ..()
RegisterSignal(grant_to, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal))

/datum/action/cooldown/spell/jaunt/wirecrawl/Remove(mob/remove_from)
. = ..()
UnregisterSignal(remove_from, COMSIG_MOVABLE_MOVED)

/datum/action/cooldown/spell/jaunt/wirecrawl/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
return FALSE
if(find_nearby_power(get_turf(owner)))
return TRUE
if(feedback)
to_chat(owner, span_warning("There must be a nearby power outlet!"))
return FALSE

/datum/action/cooldown/spell/jaunt/wirecrawl/cast(mob/living/cast_on)
. = ..()
// Should always return something because we checked that in can_cast_spell before arriving here
var/obj/structure/cable/wire = find_nearby_power(get_turf(cast_on))
do_wirecrawl(wire, cast_on)

/// Returns a nearby blood decal, or null if there aren't any
/datum/action/cooldown/spell/jaunt/wirecrawl/proc/find_nearby_power(turf/origin)
var/machinery = FALSE
for(var/obj/machinery/power/thing in range(enter_radius, origin))
machinery = TRUE
for(var/obj/structure/cable/thing in range(enter_radius, origin))
if(machinery || !thing.invisibility)//only enter it if it's not covered by a tile or is connected to a power machinery
return thing
return null

/**
* Attempts to enter or exit the passed blood pool.
* Returns TRUE if we successfully entered or exited said pool, FALSE otherwise
*/
/datum/action/cooldown/spell/jaunt/wirecrawl/proc/do_wirecrawl(obj/structure/cable/wire, mob/living/jaunter)
if(is_jaunting(jaunter))
. = try_exit_jaunt(wire, jaunter)
else
. = try_enter_jaunt(wire, jaunter)

if(!.)
reset_spell_cooldown()
to_chat(jaunter, span_warning("You are unable to wire crawl!"))

/**
* Attempts to enter the passed blood pool.
* If forced is TRUE, it will override enter_blood_time.
*/
/datum/action/cooldown/spell/jaunt/wirecrawl/proc/try_enter_jaunt(obj/structure/cable/wire, mob/living/jaunter, forced = FALSE)
if(!forced)
if(enter_blood_time > 0 SECONDS)
do_sparks(5, FALSE, jaunter)
wire.visible_message(span_warning("[jaunter] starts to sink into [wire]!"))
if(!do_after(jaunter, enter_blood_time, wire))
return FALSE

// The actual turf we enter
var/turf/jaunt_turf = get_turf(wire)

// Begin the jaunt
jaunter.notransform = TRUE
var/obj/effect/dummy/phased_mob/wirecrawl/holder = enter_jaunt(jaunter, jaunt_turf)
if(!holder)
jaunter.notransform = FALSE
return FALSE

RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal))
if(equip_wire_hands && iscarbon(jaunter))
jaunter.drop_all_held_items()
// Give them some bloody hands to prevent them from doing things
var/obj/item/wirecrawl/left_hand = new(jaunter)
var/obj/item/wirecrawl/right_hand = new(jaunter)
jaunter.put_in_hands(left_hand)
jaunter.put_in_hands(right_hand)

wire.visible_message(span_warning("[jaunter] zips into [wire]!"))
jaunter.extinguish_mob()

jaunter.sight |= (SEE_TURFS|BLIND)
jaunter.add_wirevision(wire)
holder.travelled = wire.powernet
do_sparks(10, FALSE, jaunter)

jaunter.notransform = FALSE
return TRUE

/**
* Attempts to Exit the passed blood pool.
* If forced is TRUE, it will override exit_blood_time, and if we're currently consuming someone.
*/
/datum/action/cooldown/spell/jaunt/wirecrawl/proc/try_exit_jaunt(obj/structure/cable/wire, mob/living/jaunter, forced = FALSE)
if(!forced)
if(jaunter.notransform)
to_chat(jaunter, span_warning("You cannot exit yet!!"))
return FALSE

if(exit_blood_time > 0 SECONDS)
wire.visible_message(span_warning("[wire] starts to crackle..."))
do_sparks(5, FALSE, jaunter)
if(!do_after(jaunter, exit_blood_time, wire))
return FALSE

if(!exit_jaunt(jaunter, get_turf(wire)))
return FALSE

jaunter.sight &= ~(SEE_TURFS|BLIND)
jaunter.remove_wirevision()
do_sparks(10, FALSE, jaunter)

wire.visible_message(span_boldwarning("[jaunter] zips out of [wire]!"))
return TRUE

/datum/action/cooldown/spell/jaunt/wirecrawl/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter)
UnregisterSignal(jaunt, COMSIG_MOVABLE_MOVED)
if(equip_wire_hands && iscarbon(unjaunter))
for(var/obj/item/wirecrawl/wire_hand in unjaunter.held_items)
unjaunter.temporarilyRemoveItemFromInventory(wire_hand, force = TRUE)
qdel(wire_hand)
return ..()

/obj/effect/dummy/phased_mob/wirecrawl
name = "wire"
///keep track of the powernet we're in
var/datum/powernet/travelled

/obj/effect/dummy/phased_mob/wirecrawl/Initialize(mapload, atom/movable/jaunter)
. = ..()
START_PROCESSING(SSprocessing, src)

/obj/effect/dummy/phased_mob/wirecrawl/Destroy()
STOP_PROCESSING(SSprocessing, src)
return ..()

/obj/effect/dummy/phased_mob/wirecrawl/phased_check(mob/living/user, direction)
var/turf/oldloc = get_turf(user)
var/obj/structure/cable/current = locate() in oldloc
if(!current || !istype(current) || !travelled)//if someone snips the wire you're currently in, or it gets destroyed in some way, get out
eject_jaunter()
return

var/turf/newloc = ..()
for(var/obj/structure/cable/wire in newloc)
if(travelled != wire.powernet) //no jumping to a different powernet
continue
user.update_wire_vision(wire)
new /obj/effect/particle_effect/sparks/electricity/short(get_turf(user))
return newloc

/obj/effect/dummy/phased_mob/wirecrawl/process(delta_time)//so if the existing wire is destroyed, they are forced out
. = ..()
var/turf/currentloc = get_turf(jaunter)
var/obj/structure/cable/current = locate() in currentloc
if(!current || !istype(current) || !travelled)//if someone snips the wire you're currently in, or it gets destroyed in some way, get out
eject_jaunter()
return

/obj/effect/dummy/phased_mob/wirecrawl/eject_jaunter()
var/mob/living/ejected = jaunter
ejected.remove_wirevision()
. = ..()

//vision
/mob/living/proc/update_wire_vision(var/obj/structure/cable/wire)
if(!wire) //if there's no wire, grab a random one in the location
wire = locate() in get_turf(src)
remove_wirevision()
if(!wire) //if there's STILL no wire, just give up
return
add_wirevision(wire)

/mob/living/proc/add_wirevision(obj/structure/cable/wire)
if(!wire || !istype(wire) || !wire.powernet)
return
var/list/totalMembers = list()

var/datum/powernet/P = wire.powernet
if(P)
totalMembers += P.nodes
totalMembers += P.cables

if(!totalMembers.len)
return

if(client)
for(var/object in totalMembers)//cables and power machinery are not the same unfortunately
if(in_view_range(client.mob, object))
if(istype(object, /obj/structure/cable))
var/obj/structure/cable/display = object
if(!display.wire_vision_img)
display.wire_vision_img = image(display, display.loc, layer = ABOVE_HUD_LAYER, dir = display.dir)
display.wire_vision_img.plane = ABOVE_HUD_PLANE
client.images += display.wire_vision_img
wires_shown += display.wire_vision_img

else if(istype(object, /obj/machinery/power))
var/obj/machinery/power/display = object
if(!display.wire_vision_img)
display.wire_vision_img = image(display, display.loc, layer = ABOVE_HUD_LAYER, dir = display.dir)
display.wire_vision_img.plane = ABOVE_HUD_PLANE
client.images += display.wire_vision_img
wires_shown += display.wire_vision_img

/mob/living/proc/remove_wirevision()
if(client)
for(var/image/current_image in wires_shown)
client.images -= current_image
wires_shown.len = 0


/obj/item/wirecrawl
name = "wire crawl"
desc = "You are unable to hold anything while in this form."
icon = 'icons/effects/effects.dmi'
icon_state = "electricity2"
item_flags = ABSTRACT | DROPDEL

/obj/item/wirecrawl/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)

1 change: 1 addition & 0 deletions yogstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3534,6 +3534,7 @@
#include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm"
#include "code\modules\spells\spell_types\jaunt\ethereal_jaunt.dm"
#include "code\modules\spells\spell_types\jaunt\shadow_walk.dm"
#include "code\modules\spells\spell_types\jaunt\wirecrawl.dm"
#include "code\modules\spells\spell_types\list_target\_list_target.dm"
#include "code\modules\spells\spell_types\list_target\telepathy.dm"
#include "code\modules\spells\spell_types\pointed\_pointed.dm"
Expand Down
Loading