diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index c4933d5260..17bc605ae7 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -56,6 +56,68 @@ proc/isemptylist(list/list) return 1 return 0 +////////////////////////////////////////////////////// +// "typecache" utilities - Making and searching them +////////////////////////////////////////////////////// + +//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') +/proc/is_type_in_typecache(atom/A, list/L) + if(!LAZYLEN(L) || !A) + return FALSE + return L[A.type] + +//returns a new list with only atoms that are in typecache L +/proc/typecache_filter_list(list/atoms, list/typecache) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache[A.type]) + . += A + +/proc/typecache_filter_list_reverse(list/atoms, list/typecache) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(!typecache[A.type]) + . += A + +/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache_include[A.type] && !typecache_exclude[A.type]) + . += A + +//Like typesof() or subtypesof(), but returns a typecache instead of a list +/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) + if(ispath(path)) + var/list/types = list() + if(only_root_path) + types = list(path) + else + types = ignore_root_path ? subtypesof(path) : typesof(path) + var/list/L = list() + for(var/T in types) + L[T] = TRUE + return L + else if(islist(path)) + var/list/pathlist = path + var/list/L = list() + if(ignore_root_path) + for(var/P in pathlist) + for(var/T in subtypesof(P)) + L[T] = TRUE + else + for(var/P in pathlist) + if(only_root_path) + L[P] = TRUE + else + for(var/T in typesof(P)) + L[T] = TRUE + return L + +////////////////////////////////////////////////////// + //Empties the list by setting the length to 0. Hopefully the elements get garbage collected proc/clearlist(list/list) if(istype(list)) diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 5bf9a73954..e52cc857c6 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1094,6 +1094,15 @@ var/global/list/common_tools = list( return 1 return 0 +/proc/is_wire_tool(obj/item/I) + if(istype(I, /obj/item/device/multitool)) + return TRUE + if(istype(I, /obj/item/weapon/wirecutters)) + return TRUE + if(istype(I, /obj/item/device/assembly/signaler)) + return TRUE + return + proc/is_hot(obj/item/W as obj) switch(W.type) if(/obj/item/weapon/weldingtool) diff --git a/code/controllers/subsystems/orbits.dm b/code/controllers/subsystems/orbits.dm new file mode 100644 index 0000000000..80c16c9ce5 --- /dev/null +++ b/code/controllers/subsystems/orbits.dm @@ -0,0 +1,44 @@ +SUBSYSTEM_DEF(orbit) + name = "Orbits" + priority = 8 // FIRE_PRIORITY_ORBIT + wait = 2 + flags = SS_NO_INIT|SS_TICKER + + var/list/currentrun = list() + var/list/processing = list() + +/datum/controller/subsystem/orbit/stat_entry() + ..("P:[processing.len]") + + +/datum/controller/subsystem/orbit/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while (currentrun.len) + var/datum/orbit/O = currentrun[currentrun.len] + currentrun.len-- + if (!O) + processing -= O + if (MC_TICK_CHECK) + return + continue + if (!O.orbiter) + qdel(O) + if (MC_TICK_CHECK) + return + continue + if (O.lastprocess >= world.time) //we already checked recently + if (MC_TICK_CHECK) + return + continue + var/targetloc = get_turf(O.orbiting) + if (targetloc != O.lastloc || O.orbiter.loc != targetloc) + O.Check(targetloc) + if (MC_TICK_CHECK) + return + + diff --git a/code/datums/orbit.dm b/code/datums/orbit.dm new file mode 100644 index 0000000000..5d457ff4ac --- /dev/null +++ b/code/datums/orbit.dm @@ -0,0 +1,132 @@ +/datum/orbit + var/atom/movable/orbiter + var/atom/orbiting + var/lock = TRUE + var/turf/lastloc + var/lastprocess + +/datum/orbit/New(_orbiter, _orbiting, _lock) + orbiter = _orbiter + orbiting = _orbiting + SSorbit.processing += src + if (!orbiting.orbiters) + orbiting.orbiters = list() + orbiting.orbiters += src + + if (orbiter.orbiting) + orbiter.stop_orbit() + orbiter.orbiting = src + Check() + lock = _lock + +//do not qdel directly, use stop_orbit on the orbiter. (This way the orbiter can bind to the orbit stopping) +/datum/orbit/Destroy(force = FALSE) + SSorbit.processing -= src + if (orbiter) + orbiter.orbiting = null + orbiter = null + if (orbiting) + if (orbiting.orbiters) + orbiting.orbiters -= src + if (!orbiting.orbiters.len)//we are the last orbit, delete the list + orbiting.orbiters = null + orbiting = null + return ..() + +/datum/orbit/proc/Check(turf/targetloc, list/checked_already = list()) + //Avoid infinite loops for people who end up orbiting themself through another orbiter + checked_already[src] = TRUE + if (!orbiter) + qdel(src) + return + if (!orbiting) + orbiter.stop_orbit() + return + if (!orbiter.orbiting) //admin wants to stop the orbit. + orbiter.orbiting = src //set it back to us first + orbiter.stop_orbit() + var/atom/movable/AM = orbiting + if(istype(AM) && AM.orbiting && AM.orbiting.orbiting == orbiter) + orbiter.stop_orbit() + return + lastprocess = world.time + if (!targetloc) + targetloc = get_turf(orbiting) + if (!targetloc || (!lock && orbiter.loc != lastloc && orbiter.loc != targetloc)) + orbiter.stop_orbit() + return + orbiter.loc = targetloc + //TODO-LESH-DEL orbiter.update_parallax_contents() + orbiter.update_light() + lastloc = orbiter.loc + for(var/other_orbit in orbiter.orbiters) + var/datum/orbit/OO = other_orbit + //Skip if checked already + if(checked_already[OO]) + continue + OO.Check(targetloc, checked_already) + +/atom/movable/var/datum/orbit/orbiting = null +/atom/var/list/orbiters = null + +//A: atom to orbit +//radius: range to orbit at, radius of the circle formed by orbiting (in pixels) +//clockwise: whether you orbit clockwise or anti clockwise +//rotation_speed: how fast to rotate (how many ds should it take for a rotation to complete) +//rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default. +//pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts +//lockinorbit: Forces src to always be on A's turf, otherwise the orbit cancels when src gets too far away (eg: ghosts) + +/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lockinorbit = FALSE) + if (!istype(A)) + return + + new/datum/orbit(src, A, lockinorbit) + if (!orbiting) //something failed, and our orbit datum deleted itself + return + var/matrix/initial_transform = matrix(transform) + + //Head first! + if (pre_rotation) + var/matrix/M = matrix(transform) + var/pre_rot = 90 + if(!clockwise) + pre_rot = -90 + M.Turn(pre_rot) + transform = M + + var/matrix/shift = matrix(transform) + shift.Translate(0,radius) + transform = shift + + SpinAnimation(rotation_speed, -1, clockwise, rotation_segments) + + //we stack the orbits up client side, so we can assign this back to normal server side without it breaking the orbit + transform = initial_transform + +/atom/movable/proc/stop_orbit() + SpinAnimation(0,0) + qdel(orbiting) + +/atom/Destroy(force = FALSE) + . = ..() + if (orbiters) + for (var/thing in orbiters) + var/datum/orbit/O = thing + if (O.orbiter) + O.orbiter.stop_orbit() + +/atom/movable/Destroy(force = FALSE) + . = ..() + if (orbiting) + stop_orbit() + +/* +/atom/movable/proc/transfer_observers_to(atom/movable/target) + if(orbiters) + for(var/thing in orbiters) + var/datum/orbit/O = thing + if(O.orbiter && isobserver(O.orbiter)) + var/mob/dead/observer/D = O.orbiter + D.ManualFollow(target) +*/ diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm new file mode 100644 index 0000000000..f176b8f139 --- /dev/null +++ b/code/datums/wires/tesla_coil.dm @@ -0,0 +1,18 @@ +/datum/wires/tesla_coil + wire_count = 1 + holder_type = /obj/machinery/power/tesla_coil + +var/const/WIRE_ZAP = 1 + +/datum/wires/tesla_coil/CanUse(mob/living/L) + var/obj/machinery/power/tesla_coil/T = holder + if(T && T.panel_open) + return 1 + return 0 + +/datum/wires/tesla_coil/UpdatePulsed(index) + var/obj/machinery/power/tesla_coil/T = holder + switch(index) + if(WIRE_ZAP) + T.zap() + ..() diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 7e867af9a7..2e881f6583 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -317,6 +317,27 @@ Class Procs: user << " [C.name]" return 1 +// Default behavior for wrenching down machines. Supports both delay and instant modes. +/obj/machinery/proc/default_unfasten_wrench(var/mob/user, var/obj/item/weapon/wrench/W, var/time = 0) + if(!istype(W)) + return FALSE + if(panel_open) + return FALSE // Close panel first! + playsound(loc, W.usesound, 50, 1) + var/actual_time = W.toolspeed * time + if(actual_time != 0) + user.visible_message( \ + "\The [user] begins [anchored ? "un" : ""]securing \the [src].", \ + "You start [anchored ? "un" : ""]securing \the [src].") + if(actual_time == 0 || do_after(user, actual_time, target = src)) + user.visible_message( \ + "\The [user] has [anchored ? "un" : ""]secured \the [src].", \ + "You [anchored ? "un" : ""]secure \the [src].") + anchored = !anchored + power_change() //Turn on or off the machine depending on the status of power in the new area. + update_icon() + return TRUE + /obj/machinery/proc/default_deconstruction_crowbar(var/mob/user, var/obj/item/weapon/crowbar/C) if(!istype(C)) return 0 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 11b61d0a1a..8ce3c2d266 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -517,11 +517,15 @@ E.state = 2 E.connect_to_network() E.active = TRUE - for(var/obj/machinery/field_generator/F in world) if(istype(get_area(F), /area/space)) F.Varedit_start = 1 - + for(var/obj/machinery/power/grounding_rod/GR in world) + GR.anchored = TRUE + GR.update_icon() + for(var/obj/machinery/power/tesla_coil/TC in world) + TC.anchored = TRUE + TC.update_icon() for(var/obj/structure/particle_accelerator/PA in world) PA.anchored = TRUE PA.construction_state = 3 @@ -531,25 +535,6 @@ PA.construction_state = 3 PA.update_icon() - spawn(30) - for(var/obj/machinery/the_singularitygen/G in world) - if(G.anchored) - var/obj/singularity/S = new /obj/singularity(get_turf(G), 50) - spawn(0) - qdel(G) - S.energy = 1750 - S.current_size = 7 - S.icon = 'icons/effects/224x224.dmi' - S.icon_state = "singularity_s7" - S.pixel_x = -96 - S.pixel_y = -96 - S.grav_pull = 0 - //S.consume_range = 3 - S.dissipate = 0 - //S.dissipate_delay = 10 - //S.dissipate_track = 0 - //S.dissipate_strength = 10 - for(var/obj/machinery/power/rad_collector/Rad in world) if(Rad.anchored) if(!Rad.P) diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index ba833d858f..112f5a482e 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -138,6 +138,14 @@ ..() return +// Power machinery should also connect/disconnect from the network. +/obj/machinery/power/default_unfasten_wrench(var/mob/user, var/obj/item/weapon/wrench/W, var/time = 20) + if((. = ..())) + if(anchored) + connect_to_network() + else + disconnect_from_network() + // Used for power spikes by the engine, has specific effects on different machines. /obj/machinery/power/proc/overload(var/obj/machinery/power/source) return diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm index 5de10c12b9..2f9bc83cf4 100644 --- a/code/modules/power/singularity/generator.dm +++ b/code/modules/power/singularity/generator.dm @@ -8,11 +8,12 @@ density = 1 use_power = 0 var/energy = 0 + var/creation_type = /obj/singularity /obj/machinery/the_singularitygen/process() var/turf/T = get_turf(src) if(src.energy >= 200) - new /obj/singularity/(T, 50) + new creation_type(T, 50) if(src) qdel(src) /obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user) diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 46f7102f4c..246dbba6c7 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -29,15 +29,11 @@ var/chained = 0//Adminbus chain-grab -/obj/singularity/New(loc, var/starting_energy = 50, var/temp = 0) +/obj/singularity/New(loc, var/starting_energy = 50) //CARN: admin-alert for chuckle-fuckery. admin_investigate_setup() energy = starting_energy - if (temp) - spawn (temp) - qdel(src) - ..() processing_objects += src for(var/obj/machinery/power/singularity_beacon/singubeacon in machines) diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm new file mode 100644 index 0000000000..07dedf5b2b --- /dev/null +++ b/code/modules/power/tesla/coil.dm @@ -0,0 +1,133 @@ +/obj/machinery/power/tesla_coil + name = "tesla coil" + desc = "For the union!" + icon = 'icons/obj/tesla_engine/tesla_coil.dmi' + icon_state = "coil0" + anchored = FALSE + density = TRUE + + // Executing a traitor caught releasing tesla was never this fun! + can_buckle = TRUE + buckle_lying = FALSE + + circuit = /obj/item/weapon/circuitboard/tesla_coil + + var/power_loss = 2 + var/input_power_multiplier = 1 + var/zap_cooldown = 100 + var/last_zap = 0 + var/datum/wires/tesla_coil/wires = null + +/obj/machinery/power/tesla_coil/New() + ..() + wires = new(src) + +/obj/machinery/power/tesla_coil/initialize() + . = ..() + default_apply_parts() + +/obj/machinery/power/tesla_coil/Destroy() + qdel_null(wires) + return ..() + +/obj/machinery/power/tesla_coil/RefreshParts() + var/power_multiplier = 0 + zap_cooldown = 100 + for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts) + power_multiplier += C.rating + zap_cooldown -= (C.rating * 20) + input_power_multiplier = power_multiplier + + +/obj/machinery/power/tesla_coil/update_icon() + if(panel_open) + icon_state = "coil_open[anchored]" + else + icon_state = "coil[anchored]" + +/obj/machinery/power/tesla_coil/attackby(obj/item/W, mob/user, params) + src.add_fingerprint(user) + + //if(default_deconstruction_screwdriver(user, "coil_open[anchored]", "coil[anchored]", W)) + if(default_deconstruction_screwdriver(user, W)) + return + if(default_part_replacement(user, W)) + return + if(default_unfasten_wrench(user, W)) + return + if(default_deconstruction_crowbar(user, W)) + return + if(is_wire_tool(W)) + return wires.Interact(user) + return ..() + +/obj/machinery/power/tesla_coil/attack_hand(mob/user) + if(user.a_intent == I_GRAB && user_buckle_mob(user.pulling, user)) + return + ..() + +/obj/machinery/power/tesla_coil/tesla_act(var/power) + if(anchored && !panel_open) + being_shocked = TRUE + //don't lose arc power when it's not connected to anything + //please place tesla coils all around the station to maximize effectiveness + var/power_produced = powernet ? power / power_loss : power + add_avail(power_produced*input_power_multiplier) + flick("coilhit", src) + playsound(src.loc, 'sound/effects/lightningshock.ogg', 100, 1, extrarange = 5) + tesla_zap(src, 5, power_produced) + //addtimer(CALLBACK(src, .proc/reset_shocked), 10) + spawn(10) reset_shocked() + else + ..() + +/obj/machinery/power/tesla_coil/proc/zap() + if((last_zap + zap_cooldown) > world.time || !powernet) + return FALSE + last_zap = world.time + var/coeff = (20 - ((input_power_multiplier - 1) * 3)) + coeff = max(coeff, 10) + var/power = (powernet.avail/2) + draw_power(power) + playsound(src.loc, 'sound/effects/lightningshock.ogg', 100, 1, extrarange = 5) + tesla_zap(src, 10, power/(coeff/2)) + +/obj/machinery/power/grounding_rod + name = "grounding rod" + desc = "Keep an area from being fried from Edison's Bane." + icon = 'icons/obj/tesla_engine/tesla_coil.dmi' + icon_state = "grounding_rod0" + anchored = FALSE + density = TRUE + + can_buckle = TRUE + buckle_lying = FALSE + +/obj/machinery/power/grounding_rod/update_icon() + if(panel_open) + icon_state = "grounding_rod_open[anchored]" + else + icon_state = "grounding_rod[anchored]" + +/obj/machinery/power/grounding_rod/attackby(obj/item/W, mob/user, params) + //if(default_deconstruction_screwdriver(user, "grounding_rod_open[anchored]", "grounding_rod[anchored]", W)) + if(default_deconstruction_screwdriver(user, W)) + return + if(default_part_replacement(user, W)) + return + if(default_unfasten_wrench(user, W)) + return + if(default_deconstruction_crowbar(user, W)) + return + return ..() + +/obj/machinery/power/grounding_rod/attack_hand(mob/user) + if(user.a_intent == I_GRAB && user_buckle_mob(user.pulling, user)) + return + ..() + +/obj/machinery/power/grounding_rod/tesla_act(var/power) + if(anchored && !panel_open) + flick("grounding_rodhit", src) + else + ..() diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm new file mode 100644 index 0000000000..9b89e35b77 --- /dev/null +++ b/code/modules/power/tesla/energy_ball.dm @@ -0,0 +1,298 @@ +#define TESLA_DEFAULT_POWER 1738260 +#define TESLA_MINI_POWER 869130 + +/obj/singularity/energy_ball + name = "energy ball" + desc = "An energy ball." + icon = 'icons/obj/tesla_engine/energy_ball.dmi' + icon_state = "energy_ball" + pixel_x = -32 + pixel_y = -32 + current_size = STAGE_TWO + move_self = 1 + grav_pull = 0 + contained = 0 + density = TRUE + energy = 0 + dissipate = 1 + dissipate_delay = 5 + dissipate_strength = 1 + var/list/orbiting_balls = list() + var/miniball = FALSE + var/produced_power + var/energy_to_raise = 32 + var/energy_to_lower = -20 + +/obj/singularity/energy_ball/New(loc, starting_energy = 50, is_miniball = FALSE) + ..() + miniball = is_miniball + +/obj/singularity/energy_ball/initialize() + . = ..() + if(!miniball) + set_light(10, 7, "#EEEEFF") + +/obj/singularity/energy_ball/ex_act(severity, target) + return + +/obj/singularity/energy_ball/Destroy() + if(orbiting && istype(orbiting.orbiting, /obj/singularity/energy_ball)) + var/obj/singularity/energy_ball/EB = orbiting.orbiting + EB.orbiting_balls -= src + + for(var/ball in orbiting_balls) + var/obj/singularity/energy_ball/EB = ball + qdel(EB) + + . = ..() + +/obj/singularity/energy_ball/admin_investigate_setup() + if(miniball) + return //don't annnounce miniballs + ..() + + +/obj/singularity/energy_ball/process(var/wait = 20) + set waitfor = FALSE + if(!orbiting) + handle_energy() + + move_the_basket_ball(max(wait - 5, 4 + orbiting_balls.len * 1.5)) + + playsound(src.loc, 'sound/effects/lightningbolt.ogg', 100, 1, extrarange = 30) + + set_dir(tesla_zap(src.loc, 7, TESLA_DEFAULT_POWER, TRUE)) + + for (var/ball in orbiting_balls) + var/range = rand(1, Clamp(orbiting_balls.len, 3, 7)) + tesla_zap(ball, range, TESLA_MINI_POWER/7*range, TRUE) + else + energy = 0 // ensure we dont have miniballs of miniballs + +/obj/singularity/energy_ball/examine(mob/user) + ..() + if(orbiting_balls.len) + to_chat(user, "The amount of orbiting mini-balls is [orbiting_balls.len].") + + +/obj/singularity/energy_ball/proc/move_the_basket_ball(var/move_amount) + //we face the last thing we zapped, so this lets us favor that direction a bit + var/move_bias = dir + for(var/i in 0 to move_amount) + var/move_dir = pick(global.alldirs + move_bias) //ensures large-ball teslas don't just sit around + if(target && prob(10)) + move_dir = get_dir(src,target) + var/turf/T = get_step(src, move_dir) + if(can_move(T)) + forceMove(T) + set_dir(move_dir) + for(var/mob/living/carbon/C in loc) + dust_mobs(C) + sleep(1) // So movement is smooth + +/obj/singularity/energy_ball/proc/handle_energy() + if(energy >= energy_to_raise) + energy_to_lower = energy_to_raise - 20 + energy_to_raise = energy_to_raise * 1.25 + + playsound(src.loc, 'sound/effects/lightning_chargeup.ogg', 100, 1, extrarange = 30) + //addtimer(CALLBACK(src, .proc/new_mini_ball), 100) + spawn(100) new_mini_ball() + + else if(energy < energy_to_lower && orbiting_balls.len) + energy_to_raise = energy_to_raise / 1.25 + energy_to_lower = (energy_to_raise / 1.25) - 20 + + var/Orchiectomy_target = pick(orbiting_balls) + qdel(Orchiectomy_target) + + else if(orbiting_balls.len) + dissipate() //sing code has a much better system. + +/obj/singularity/energy_ball/proc/new_mini_ball() + if(!loc) + return + var/obj/singularity/energy_ball/EB = new(loc, 0, TRUE) + + EB.transform *= pick(0.3, 0.4, 0.5, 0.6, 0.7) + var/icon/I = icon(icon,icon_state,dir) + + var/orbitsize = (I.Width() + I.Height()) * pick(0.4, 0.5, 0.6, 0.7, 0.8) + orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25) + + EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36)) + + +/obj/singularity/energy_ball/Bump(atom/A) + dust_mobs(A) + +/obj/singularity/energy_ball/Bumped(atom/movable/AM) + dust_mobs(AM) + +/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target) + if (istype(target)) + target.orbiting_balls += src + //TODO-LESH-DEL global.poi_list -= src + target.dissipate_strength = target.orbiting_balls.len + + . = ..() +/obj/singularity/energy_ball/stop_orbit() + if (orbiting && istype(orbiting.orbiting, /obj/singularity/energy_ball)) + var/obj/singularity/energy_ball/orbitingball = orbiting.orbiting + orbitingball.orbiting_balls -= src + orbitingball.dissipate_strength = orbitingball.orbiting_balls.len + ..() + if (!loc && !QDELETED(src)) + qdel(src) + + +/obj/singularity/energy_ball/proc/dust_mobs(atom/A) + if(isliving(A)) + var/mob/living/L = A + if(L.incorporeal_move) + return + if(!iscarbon(A)) + return + for(var/obj/machinery/power/grounding_rod/GR in orange(src, 2)) + if(GR.anchored) + return + var/mob/living/carbon/C = A + // C.dust() - Changing to do fatal elecrocution instead + C.electrocute_act(500, src, def_zone = BP_TORSO) + +/proc/tesla_zap(atom/source, zap_range = 3, power, explosive = FALSE, stun_mobs = TRUE) + . = source.dir + if(power < 1000) + return + + var/closest_dist = 0 + var/closest_atom + var/obj/machinery/power/tesla_coil/closest_tesla_coil + var/obj/machinery/power/grounding_rod/closest_grounding_rod + var/mob/living/closest_mob + var/obj/machinery/closest_machine + var/obj/structure/closest_structure + var/obj/structure/blob/closest_blob + var/static/things_to_shock = typecacheof(list(/obj/machinery, /mob/living, /obj/structure)) + var/static/blacklisted_tesla_types = typecacheof(list( + /obj/machinery/atmospherics, + /obj/machinery/power/emitter, + /obj/machinery/field_generator, + /mob/living/simple_animal, + /obj/machinery/door/blast, + /obj/machinery/particle_accelerator/control_box, + /obj/structure/particle_accelerator/fuel_chamber, + /obj/structure/particle_accelerator/particle_emitter/center, + /obj/structure/particle_accelerator/particle_emitter/left, + /obj/structure/particle_accelerator/particle_emitter/right, + /obj/structure/particle_accelerator/power_box, + /obj/structure/particle_accelerator/end_cap, + /obj/machinery/containment_field, + /obj/structure/disposalpipe, + /obj/structure/sign, + /obj/machinery/gateway, + /obj/structure/lattice, + /obj/structure/grille, + /obj/machinery/the_singularitygen/tesla)) + + for(var/A in typecache_filter_multi_list_exclusion(oview(source, zap_range+2), things_to_shock, blacklisted_tesla_types)) + if(istype(A, /obj/machinery/power/tesla_coil)) + var/dist = get_dist(source, A) + var/obj/machinery/power/tesla_coil/C = A + if(dist <= zap_range && (dist < closest_dist || !closest_tesla_coil) && !C.being_shocked) + closest_dist = dist + + //we use both of these to save on istype and typecasting overhead later on + //while still allowing common code to run before hand + closest_tesla_coil = C + closest_atom = C + + + else if(closest_tesla_coil) + continue //no need checking these other things + + else if(istype(A, /obj/machinery/power/grounding_rod)) + var/dist = get_dist(source, A)-2 + if(dist <= zap_range && (dist < closest_dist || !closest_grounding_rod)) + closest_grounding_rod = A + closest_atom = A + closest_dist = dist + + else if(closest_grounding_rod) + continue + + else if(isliving(A)) + var/dist = get_dist(source, A) + var/mob/living/L = A + if(dist <= zap_range && (dist < closest_dist || !closest_mob) && L.stat != DEAD && !(L.status_flags & GODMODE)) + closest_mob = L + closest_atom = A + closest_dist = dist + + else if(closest_mob) + continue + + else if(istype(A, /obj/machinery)) + var/obj/machinery/M = A + var/dist = get_dist(source, A) + if(dist <= zap_range && (dist < closest_dist || !closest_machine) && !M.being_shocked) + closest_machine = M + closest_atom = A + closest_dist = dist + + else if(closest_mob) + continue + + else if(istype(A, /obj/structure/blob)) + var/obj/structure/blob/B = A + var/dist = get_dist(source, A) + if(dist <= zap_range && (dist < closest_dist || !closest_tesla_coil) && !B.being_shocked) + closest_blob = B + closest_atom = A + closest_dist = dist + + else if(closest_blob) + continue + + else if(istype(A, /obj/structure)) + var/obj/structure/S = A + var/dist = get_dist(source, A) + if(dist <= zap_range && (dist < closest_dist || !closest_tesla_coil) && !S.being_shocked) + closest_structure = S + closest_atom = A + closest_dist = dist + + //Alright, we've done our loop, now lets see if was anything interesting in range + if(closest_atom) + //common stuff + source.Beam(closest_atom, icon_state="lightning[rand(1,12)]", time=5, maxdistance = INFINITY) + var/zapdir = get_dir(source, closest_atom) + if(zapdir) + . = zapdir + + //per type stuff: + if(closest_tesla_coil) + closest_tesla_coil.tesla_act(power, explosive, stun_mobs) + + else if(closest_grounding_rod) + closest_grounding_rod.tesla_act(power, explosive, stun_mobs) + + else if(closest_mob) + var/shock_damage = Clamp(round(power/400), 10, 90) + rand(-5, 5) + closest_mob.electrocute_act(shock_damage, source, 1/*, tesla_shock = 1, stun = stun_mobs*/) + if(issilicon(closest_mob)) + var/mob/living/silicon/S = closest_mob + if(stun_mobs) + S.emp_act(3 /*EMP_LIGHT*/) + tesla_zap(closest_mob, 7, power / 1.5, explosive, stun_mobs) // metallic folks bounce it further + else + tesla_zap(closest_mob, 5, power / 1.5, explosive, stun_mobs) + + else if(closest_machine) + closest_machine.tesla_act(power, explosive, stun_mobs) + + else if(closest_blob) + closest_blob.tesla_act(power, explosive, stun_mobs) + + else if(closest_structure) + closest_structure.tesla_act(power, explosive, stun_mobs) diff --git a/code/modules/power/tesla/generator.dm b/code/modules/power/tesla/generator.dm new file mode 100644 index 0000000000..63bce099fa --- /dev/null +++ b/code/modules/power/tesla/generator.dm @@ -0,0 +1,10 @@ +/obj/machinery/the_singularitygen/tesla + name = "energy ball generator" + desc = "Makes the wardenclyffe look like a child's plaything when shot with a particle accelerator." + icon = 'icons/obj/tesla_engine/tesla_generator.dmi' + icon_state = "TheSingGen" + creation_type = /obj/singularity/energy_ball + +/obj/machinery/the_singularitygen/tesla/tesla_act(power, explosive = FALSE) + if(explosive) + energy += power diff --git a/code/modules/power/tesla/telsa_construction.dm b/code/modules/power/tesla/telsa_construction.dm new file mode 100644 index 0000000000..5cb11b9fe6 --- /dev/null +++ b/code/modules/power/tesla/telsa_construction.dm @@ -0,0 +1,31 @@ +////////////////////////// +// Circuits and Research +////////////////////////// + +// Tesla coils are built as machines using a circuit researchable in RnD +/obj/item/weapon/circuitboard/tesla_coil + name = T_BOARD("tesla coil") + build_path = /obj/machinery/power/tesla_coil + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_MAGNET = 2, TECH_POWER = 4) + req_components = list(/obj/item/weapon/stock_parts/capacitor = 1) + +/datum/design/circuit/tesla_coil + name = "Machine Design (Tesla Coil Board)" + desc = "The circuit board for a tesla coil." + id = "tesla_coil" + build_path = /obj/item/weapon/circuitboard/tesla_coil + req_tech = list(TECH_MAGNET = 2, TECH_POWER = 4) + sort_string = "MAAAC" + +// Grounding rods can be built as machines using a circuit made in an autolathe. +/obj/item/weapon/circuitboard/grounding_rod + name = T_BOARD("grounding rod") + build_path = /obj/machinery/power/grounding_rod + board_type = new /datum/frame/frame_types/machine + matter = list(DEFAULT_WALL_MATERIAL = 50, "glass" = 50) + req_components = list() + +/datum/category_item/autolathe/engineering/grounding_rod + name = "grounding rod electronics" + path = /obj/item/weapon/circuitboard/grounding_rod diff --git a/code/modules/power/tesla/tesla_act.dm b/code/modules/power/tesla/tesla_act.dm new file mode 100644 index 0000000000..8e3f1c6996 --- /dev/null +++ b/code/modules/power/tesla/tesla_act.dm @@ -0,0 +1,68 @@ +//////////////////////////////////////// +// Vars and Default tesla_act behavior +//////////////////////////////////////// + +/obj + var/being_shocked = FALSE + +/obj/proc/tesla_act(var/power) + being_shocked = TRUE + var/power_bounced = power / 2 + tesla_zap(src, 3, power_bounced) + //addtimer(CALLBACK(src, .proc/reset_shocked), 10) + //schedule_task_with_source_in(10, src, .proc/reset_shocked) + spawn(10) reset_shocked() + +/obj/proc/reset_shocked() + being_shocked = FALSE + +// Overrides for behavior on specific types + +/obj/structure/blob/tesla_act(power) + ..() + adjust_integrity(-power/400) + +/obj/machinery/nuclearbomb/tesla_act(power, explosive) + ..() + if(explosive) + qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over + +/obj/machinery/tesla_act(power, explosive = FALSE) + ..() + if(prob(85) && explosive) + explosion(loc, 1, 2, 4, /*flame_range = 2,*/ adminlog = FALSE/*, smoke = FALSE*/) + else if(prob(50)) + emp_act(2) + else + ex_act(2) + +/obj/machinery/camera/tesla_act(var/power)//EMP proof upgrade also makes it tesla immune + if(isEmpProof()) + return + ..() + qdel(src) //to prevent bomb testing camera from exploding over and over forever + +/obj/machinery/light/tesla_act(power, explosive = FALSE) + if(explosive) + explosion(loc, 0, 0, 0/*, flame_range = 5*/, adminlog = FALSE) + qdel(src) + +/obj/structure/closet/tesla_act(var/power) + ..() //extend the zap + visible_message("[src] is blown apart by the bolt of electricity!", "You hear a metallic screeching sound.") + dump_contents() + qdel(src) + +/obj/structure/reagent_dispensers/fueltank/tesla_act() + ..() //extend the zap + explode() + + + + + + + + + + diff --git a/html/changelogs/Leshana-tesla.yml b/html/changelogs/Leshana-tesla.yml new file mode 100644 index 0000000000..8910bebb4b --- /dev/null +++ b/html/changelogs/Leshana-tesla.yml @@ -0,0 +1,6 @@ +author: Leshana +delete-after: True + - soundadd: "Sounds of Tesla engine lighting bolts" + - imageadd: "Sprites for grounding rod and Tesla coil" + - imageadd: "Sprites for lighting bolts" + - rscadd: "The Tesla Engine, Tesla Coils, and Grounding Rods" diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index dc0c7b9c51..e7f61d605d 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/obj/tesla_engine/energy_ball.dmi b/icons/obj/tesla_engine/energy_ball.dmi new file mode 100644 index 0000000000..22866252a9 Binary files /dev/null and b/icons/obj/tesla_engine/energy_ball.dmi differ diff --git a/icons/obj/tesla_engine/tesla_coil.dmi b/icons/obj/tesla_engine/tesla_coil.dmi new file mode 100644 index 0000000000..96a6c636d8 Binary files /dev/null and b/icons/obj/tesla_engine/tesla_coil.dmi differ diff --git a/icons/obj/tesla_engine/tesla_generator.dmi b/icons/obj/tesla_engine/tesla_generator.dmi new file mode 100644 index 0000000000..adf5fbaa8e Binary files /dev/null and b/icons/obj/tesla_engine/tesla_generator.dmi differ diff --git a/polaris.dme b/polaris.dme index da4ed4e6a3..209c8afd15 100644 --- a/polaris.dme +++ b/polaris.dme @@ -176,6 +176,7 @@ #include "code\controllers\subsystems\garbage.dm" #include "code\controllers\subsystems\lighting.dm" #include "code\controllers\subsystems\machines.dm" +#include "code\controllers\subsystems\orbits.dm" #include "code\datums\ai_law_sets.dm" #include "code\datums\ai_laws.dm" #include "code\datums\beam.dm" @@ -191,6 +192,7 @@ #include "code\datums\mixed.dm" #include "code\datums\modules.dm" #include "code\datums\mutable_appearance.dm" +#include "code\datums\orbit.dm" #include "code\datums\organs.dm" #include "code\datums\progressbar.dm" #include "code\datums\recipe.dm" @@ -314,6 +316,7 @@ #include "code\datums\wires\smartfridge.dm" #include "code\datums\wires\smes.dm" #include "code\datums\wires\suit_storage_unit.dm" +#include "code\datums\wires\tesla_coil.dm" #include "code\datums\wires\vending.dm" #include "code\datums\wires\wires.dm" #include "code\defines\gases.dm" @@ -2034,6 +2037,11 @@ #include "code\modules\power\singularity\particle_accelerator\particle_power.dm" #include "code\modules\power\supermatter\setup_supermatter.dm" #include "code\modules\power\supermatter\supermatter.dm" +#include "code\modules\power\tesla\coil.dm" +#include "code\modules\power\tesla\energy_ball.dm" +#include "code\modules\power\tesla\generator.dm" +#include "code\modules\power\tesla\telsa_construction.dm" +#include "code\modules\power\tesla\tesla_act.dm" #include "code\modules\projectiles\ammunition.dm" #include "code\modules\projectiles\dnalocking.dm" #include "code\modules\projectiles\effects.dm" diff --git a/sound/effects/lightning_chargeup.ogg b/sound/effects/lightning_chargeup.ogg new file mode 100644 index 0000000000..0af4449502 Binary files /dev/null and b/sound/effects/lightning_chargeup.ogg differ diff --git a/sound/effects/lightningbolt.ogg b/sound/effects/lightningbolt.ogg new file mode 100644 index 0000000000..e5f6a5731a Binary files /dev/null and b/sound/effects/lightningbolt.ogg differ diff --git a/sound/effects/lightningshock.ogg b/sound/effects/lightningshock.ogg new file mode 100644 index 0000000000..341a2934d6 Binary files /dev/null and b/sound/effects/lightningshock.ogg differ