diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm new file mode 100644 index 000000000000..dfaf6fdb47f2 --- /dev/null +++ b/code/datums/wires/particle_accelerator.dm @@ -0,0 +1,49 @@ +/datum/wires/particle_accelerator/control_box + holder_type = /obj/machinery/particle_accelerator/control_box + proper_name = "Particle Accelerator" + +/datum/wires/particle_accelerator/control_box/New(atom/holder) + wires = list( + WIRE_POWER, WIRE_STRENGTH, WIRE_LIMIT, + WIRE_INTERFACE + ) + add_duds(2) + ..() + +/datum/wires/particle_accelerator/control_box/interactable(mob/user) + . = ..() + var/obj/machinery/particle_accelerator/control_box/C = holder + if(C.construction_state == 2) + return TRUE + +/datum/wires/particle_accelerator/control_box/on_pulse(wire) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + C.toggle_power() + if(WIRE_STRENGTH) + C.add_strength() + if(WIRE_INTERFACE) + C.interface_control = !C.interface_control + if(WIRE_LIMIT) + C.visible_message("[icon2html(C, viewers(holder))][C] makes a large whirring noise.") + +/datum/wires/particle_accelerator/control_box/on_cut(wire, mend) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + if(C.active == !mend) + C.toggle_power() + if(WIRE_STRENGTH) + for(var/i = 1; i < 3; i++) + C.remove_strength() + if(WIRE_INTERFACE) + if(!mend) + C.interface_control = FALSE + if(WIRE_LIMIT) + C.strength_upper_limit = (mend ? 2 : 3) + if(C.strength_upper_limit < C.strength) + C.remove_strength() + +/datum/wires/particle_accelerator/control_box/emp_pulse() // to prevent singulo from pulsing wires + return diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm index d32c6596786e..a958d0d4c38e 100644 --- a/code/game/machinery/computer/arcade/orion.dm +++ b/code/game/machinery/computer/arcade/orion.dm @@ -59,9 +59,7 @@ GLOBAL_LIST_INIT(orion_events, generate_orion_events()) /obj/machinery/computer/arcade/orion_trail/kobayashi name = "Kobayashi Maru control computer" desc = "A test for cadets." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_keyboard = null - icon_screen = null + icon = 'icons/obj/particle_accelerator.dmi' icon_state = "control_boxp" //kobatashi has a smaller list of events, so we copy from the global list and cut whatever isn't here var/list/event_whitelist = list( diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index d1cc5601991e..3dd5f5bfa8a4 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -209,6 +209,12 @@ /obj/item/circuitboard/machine/turbine_compressor name = "Turbine - Inlet Compressor" +/obj/item/circuitboard/machine/pacman/mrs + name = "MRSPACMAN-type Generator (Machine Board)" + build_path = /obj/machinery/power/port_gen/pacman/mrs + +/obj/item/circuitboard/machine/power_compressor + name = "Power Compressor (Machine Board)" greyscale_colors = CIRCUIT_COLOR_ENGINEERING build_path = /obj/machinery/power/turbine/inlet_compressor/constructed req_components = list( diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm new file mode 100644 index 000000000000..8adcf8823f55 --- /dev/null +++ b/code/modules/awaymissions/bluespaceartillery.dm @@ -0,0 +1,55 @@ + + +/obj/machinery/artillerycontrol + var/reload = 120 + var/reload_cooldown = 120 + var/explosiondev = 3 + var/explosionmed = 6 + var/explosionlight = 12 + name = "bluespace artillery control" + icon_state = "control_boxp1" + icon = 'icons/obj/particle_accelerator.dmi' + density = TRUE + +/obj/machinery/artillerycontrol/process(delta_time) + if(reload < reload_cooldown) + reload += delta_time + +/obj/structure/artilleryplaceholder + name = "artillery" + icon = 'icons/obj/machines/artillery.dmi' + anchored = TRUE + density = TRUE + +/obj/structure/artilleryplaceholder/decorative + density = FALSE + +/obj/machinery/artillerycontrol/ui_interact(mob/user) + . = ..() + var/dat = "Bluespace Artillery Control:
" + dat += "Locked on
" + dat += "Charge progress: [reload]/[reload_cooldown]:
" + dat += "Open Fire
" + dat += "Deployment of weapon authorized by
Nanotrasen Naval Command

Remember, friendly fire is grounds for termination of your contract and life.
" + user << browse(dat, "window=scroll") + onclose(user, "scroll") + +/obj/machinery/artillerycontrol/Topic(href, href_list) + if(..()) + return + var/A + A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs + var/area/thearea = GLOB.teleportlocs[A] + if(usr.stat != CONSCIOUS || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED)) + return + if(reload < reload_cooldown) + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + priority_announce("Bluespace artillery fire detected. Brace for impact.") + message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.") + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + L+=T + var/loc = pick(L) + explosion(loc, explosiondev, explosionmed, explosionlight) + reload = 0 diff --git a/code/modules/cargo/bounties/engineering.dm b/code/modules/cargo/bounties/engineering.dm index 9da865ec7533..723d77074ae4 100644 --- a/code/modules/cargo/bounties/engineering.dm +++ b/code/modules/cargo/bounties/engineering.dm @@ -1,3 +1,58 @@ +/datum/bounty/item/engineering/gas + name = "Full Tank of Pluoxium" + description = "CentCom RnD is researching extra compact internals. Ship us a tank full of Pluoxium and you'll be compensated." + reward = CARGO_CRATE_VALUE * 15 + wanted_types = list(/obj/item/tank) + var/moles_required = 20 // A full tank is 28 moles, but CentCom ignores that fact. + var/gas_type = /datum/gas/pluoxium + +/datum/bounty/item/engineering/gas/applies_to(obj/O) + if(!..()) + return FALSE + var/obj/item/tank/T = O + var/datum/gas_mixture/our_mix = T.return_air() + if(!our_mix.gases[gas_type]) + return FALSE + return our_mix.gases[gas_type][MOLES] >= moles_required + +/datum/bounty/item/engineering/gas/nitryl_tank + name = "Full Tank of Nitryl" + description = "The non-human staff of Station 88 has been volunteered to test performance enhancing drugs. Ship them a tank full of Nitryl so they can get started. (20 Moles)" + gas_type = /datum/gas/nitryl + +/datum/bounty/item/engineering/gas/freon_tank + name = "Full Tank of Freon" + description = "The Supermatter of station 33 has started the delamination process. Deliver a tank of Freon gas to help them stop it! (20 Moles)" + gas_type = /datum/gas/freon + +/datum/bounty/item/engineering/gas/tritium_tank + name = "Full Tank of Tritium" + description = "Station 49 is looking to kickstart their research program. Ship them a tank full of Tritium. (20 Moles)" + gas_type = /datum/gas/tritium + +/datum/bounty/item/engineering/gas/hydrogen_tank + name = "Full Tank of Hydrogen" + description = "Our R&D department is working on the development of more efficient electrical batteries using hydrogen as a catalyst. Ship us a tank full of it. (20 Moles)" + gas_type = /datum/gas/hydrogen + +/datum/bounty/item/engineering/gas/zauker_tank + name = "Full Tank of Zauker" + description = "The main planet of \[REDACTED] has been chosen as testing grounds for the new weapon that uses Zauker gas. Ship us a tank full of it. (20 Moles)" + reward = CARGO_CRATE_VALUE * 20 + gas_type = /datum/gas/zauker + +/datum/bounty/item/engineering/energy_ball + name = "Contained Tesla Ball" + description = "Station 24 is being overrun by hordes of angry Mothpeople. They are requesting the ultimate bug zapper." + reward = CARGO_CRATE_VALUE * 375 + wanted_types = list(/obj/singularity/energy_ball) + +/datum/bounty/item/engineering/energy_ball/applies_to(obj/O) + if(!..()) + return FALSE + var/obj/singularity/energy_ball/T = O + return !T.miniball + /datum/bounty/item/engineering/emitter name = "Emitter" description = "We think there may be a defect in your station's emitter designs, based on the sheer number of delaminations your sector seems to see. Ship us one of yours." diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index 1871d69eb452..a340955a5431 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -81,6 +81,16 @@ unit_name = "tesla coil" export_types = list(/obj/machinery/power/energy_accumulator/tesla_coil) +/datum/export/large/pa + cost = CARGO_CRATE_VALUE * 2 + unit_name = "particle accelerator part" + export_types = list(/obj/structure/particle_accelerator) + +/datum/export/large/pa/controls + cost = CARGO_CRATE_VALUE * 2.5 + unit_name = "particle accelerator control console" + export_types = list(/obj/machinery/particle_accelerator/control_box) + /datum/export/large/supermatter cost = CARGO_CRATE_VALUE * 16 unit_name = "supermatter shard" @@ -91,6 +101,17 @@ unit_name = "grounding rod" export_types = list(/obj/machinery/power/energy_accumulator/grounding_rod) +/datum/export/large/tesla_gen + cost = CARGO_CRATE_VALUE * 16 + unit_name = "energy ball generator" + export_types = list(/obj/machinery/the_singularitygen/tesla) + +/datum/export/large/singulo_gen + cost = CARGO_CRATE_VALUE * 16 + unit_name = "gravitational singularity generator" + export_types = list(/obj/machinery/the_singularitygen) + include_subtypes = FALSE + /datum/export/large/iv cost = CARGO_CRATE_VALUE * 0.25 unit_name = "iv drip" diff --git a/code/modules/cargo/exports/tools.dm b/code/modules/cargo/exports/tools.dm index 94e9c2643653..f22e141fca43 100644 --- a/code/modules/cargo/exports/tools.dm +++ b/code/modules/cargo/exports/tools.dm @@ -136,6 +136,26 @@ unit_name = "rapid pipe dispenser" export_types = list(/obj/item/pipe_dispenser) +/datum/export/singulo //failsafe in case someone decides to ship a live singularity to CentCom without the corresponding bounty + cost = 1 + unit_name = "singularity" + export_types = list(/obj/singularity) + include_subtypes = FALSE + +/datum/export/singulo/total_printout(datum/export_report/ex, notes = TRUE) + . = ..() + if(. && notes) + . += " ERROR: Invalid object detected." + +/datum/export/singulo/tesla //see above + unit_name = "energy ball" + export_types = list(/obj/singularity/energy_ball) + +/datum/export/singulo/tesla/total_printout(datum/export_report/ex, notes = TRUE) + . = ..() + if(. && notes) + . += " ERROR: Unscheduled energy ball delivery detected." + //artisanal exports for the mom and pops /datum/export/soap cost = CARGO_CRATE_VALUE * 0.375 diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index 5400a48422c4..2b6e998ac778 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -277,5 +277,20 @@ power_gen = 15000 sheet_path = /obj/item/stack/sheet/mineral/uranium +/obj/machinery/power/port_gen/pacman/super/overheat() + explosion(src, devastation_range = 3, heavy_impact_range = 3, light_impact_range = 3, flash_range = -1) + +/obj/machinery/power/port_gen/pacman/mrs + name = "\improper M.R.S.P.A.C.M.A.N.-type portable generator" + base_icon = "portgen2" + icon_state = "portgen2_0" + circuit = /obj/item/circuitboard/machine/pacman/mrs + sheet_path = /obj/item/stack/sheet/mineral/diamond + power_gen = 40000 + time_per_sheet = 80 + +/obj/machinery/power/port_gen/pacman/mrs/overheat() + explosion(src.loc, 4, 4, 4, -1) + /obj/machinery/power/port_gen/pacman/pre_loaded sheets = 15 diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm new file mode 100644 index 000000000000..f5e3bbc1417b --- /dev/null +++ b/code/modules/power/singularity/generator.dm @@ -0,0 +1,35 @@ +/////SINGULARITY SPAWNER +/obj/machinery/the_singularitygen + name = "Gravitational Singularity Generator" + desc = "An odd device which produces a Gravitational Singularity when set up." + icon = 'icons/obj/singularity.dmi' + icon_state = "TheSingGen" + anchored = FALSE + density = TRUE + use_power = NO_POWER_USE + resistance_flags = FIRE_PROOF + + // You can buckle someone to the singularity generator, then start the engine. Fun! + can_buckle = TRUE + buckle_lying = FALSE + buckle_requires_restraints = TRUE + + var/energy = 0 + var/creation_type = /obj/singularity + +/obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + default_unfasten_wrench(user, W, 0) + else + return ..() + +/obj/machinery/the_singularitygen/process() + if(energy > 0) + if(energy >= 200) + var/turf/T = get_turf(src) + SSblackbox.record_feedback("tally", "engine_started", 1, type) + var/obj/singularity/S = new creation_type(T, 50) + transfer_fingerprints_to(S) + qdel(src) + else + energy -= 1 diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm new file mode 100644 index 000000000000..6dcc0a6aae2a --- /dev/null +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -0,0 +1,63 @@ +/obj/effect/accelerated_particle + name = "Accelerated Particles" + desc = "Small things moving very fast." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "particle" + anchored = TRUE + density = FALSE + var/movement_range = 20 + var/energy = 10 + var/speed = 1 + +/obj/effect/accelerated_particle/weak + movement_range = 15 + energy = 5 + +/obj/effect/accelerated_particle/strong + movement_range = 25 + energy = 15 + +/obj/effect/accelerated_particle/powerful + movement_range = 30 + energy = 50 + + +/obj/effect/accelerated_particle/New(loc) + ..() + + addtimer(CALLBACK(src, .proc/move), 1) + + +/obj/effect/accelerated_particle/Bump(atom/A) + if(A) + if(isliving(A)) + toxmob(A) + else if(istype(A, /obj/machinery/the_singularitygen)) + var/obj/machinery/the_singularitygen/S = A + S.energy += energy + else if(istype(A, /obj/singularity)) + var/obj/singularity/S = A + S.energy += energy + else if(istype(A, /obj/structure/blob)) + var/obj/structure/blob/B = A + B.take_damage(energy*0.6) + movement_range = 0 + +/obj/effect/accelerated_particle/ex_act(severity, target) + qdel(src) + +/obj/effect/accelerated_particle/singularity_pull() + return + +/obj/effect/accelerated_particle/proc/toxmob(mob/living/M) + M.rad_act(energy*6) + +/obj/effect/accelerated_particle/proc/move() + if(!step(src,dir)) + forceMove(get_step(src,dir)) + movement_range-- + if(movement_range == 0) + qdel(src) + else + sleep(speed) + move() diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm new file mode 100644 index 000000000000..4518f4654307 --- /dev/null +++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm @@ -0,0 +1,166 @@ +#define PA_CONSTRUCTION_UNSECURED 0 +#define PA_CONSTRUCTION_UNWIRED 1 +#define PA_CONSTRUCTION_PANEL_OPEN 2 +#define PA_CONSTRUCTION_COMPLETE 3 + +/obj/structure/particle_accelerator + name = "Particle Accelerator" + desc = "Part of a Particle Accelerator." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "none" + anchored = FALSE + density = TRUE + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80) + + var/obj/machinery/particle_accelerator/control_box/master = null + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/reference = null + var/powered = 0 + var/strength = null + +/obj/structure/particle_accelerator/examine(mob/user) + . = ..() + + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + . += "Looks like it's not attached to the flooring." + if(PA_CONSTRUCTION_UNWIRED) + . += "It is missing some cables." + if(PA_CONSTRUCTION_PANEL_OPEN) + . += "The panel is open." + +/obj/structure/particle_accelerator/Destroy() + construction_state = PA_CONSTRUCTION_UNSECURED + if(master) + master.connected_parts -= src + master.assembled = 0 + master = null + return ..() + +/obj/structure/particle_accelerator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) + + +/obj/structure/particle_accelerator/set_anchored(anchorvalue) + . = ..() + if(isnull(.)) + return + construction_state = anchorvalue ? PA_CONSTRUCTION_UNWIRED : PA_CONSTRUCTION_UNSECURED + update_state() + update_icon() + +/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params) + var/did_something = FALSE + + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + if(W.tool_behaviour == TOOL_WRENCH && !isinspace()) + W.play_tool_sound(src, 75) + set_anchored(TRUE) + user.visible_message("[user.name] secures the [name] to the floor.", \ + "You secure the external bolts.") + user.changeNext_move(CLICK_CD_MELEE) + return //set_anchored handles the rest of the stuff we need to do. + if(PA_CONSTRUCTION_UNWIRED) + if(W.tool_behaviour == TOOL_WRENCH) + W.play_tool_sound(src, 75) + set_anchored(FALSE) + user.visible_message("[user.name] detaches the [name] from the floor.", \ + "You remove the external bolts.") + user.changeNext_move(CLICK_CD_MELEE) + return //set_anchored handles the rest of the stuff we need to do. + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(CC.use(1)) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + if(PA_CONSTRUCTION_PANEL_OPEN) + if(W.tool_behaviour == TOOL_WIRECUTTER)//TODO:Shock user if its on? + user.visible_message("[user.name] removes some wires from the [name].", \ + "You remove some wires.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] closes the [name]'s access panel.", \ + "You close the access panel.") + construction_state = PA_CONSTRUCTION_COMPLETE + did_something = TRUE + if(PA_CONSTRUCTION_COMPLETE) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] opens the [name]'s access panel.", \ + "You open the access panel.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + + if(did_something) + user.changeNext_move(CLICK_CD_MELEE) + update_state() + update_icon() + return + + return ..() + + +/obj/structure/particle_accelerator/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/iron (loc, 5) + qdel(src) + +/obj/structure/particle_accelerator/Move() + . = ..() + if(master && master.active) + master.toggle_power() + investigate_log("was moved whilst active; it powered down.", INVESTIGATE_SINGULO) + + +/obj/structure/particle_accelerator/update_icon_state() + . = ..() + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED,PA_CONSTRUCTION_UNWIRED) + icon_state="[reference]" + if(PA_CONSTRUCTION_PANEL_OPEN) + icon_state="[reference]w" + if(PA_CONSTRUCTION_COMPLETE) + if(powered) + icon_state="[reference]p[strength]" + else + icon_state="[reference]c" + +/obj/structure/particle_accelerator/proc/update_state() + if(master) + master.update_state() + +/obj/structure/particle_accelerator/proc/connect_master(obj/O) + if(O.dir == dir) + master = O + return 1 + return 0 + +/////////// +// PARTS // +/////////// + + +/obj/structure/particle_accelerator/end_cap + name = "Alpha Particle Generation Array" + desc = "This is where Alpha particles are generated from \[REDACTED\]." + icon_state = "end_cap" + reference = "end_cap" + +/obj/structure/particle_accelerator/power_box + name = "Particle Focusing EM Lens" + desc = "This uses electromagnetic waves to focus the Alpha particles." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "power_box" + reference = "power_box" + +/obj/structure/particle_accelerator/fuel_chamber + name = "EM Acceleration Chamber" + desc = "This is where the Alpha particles are accelerated to radical speeds." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "fuel_chamber" + reference = "fuel_chamber" diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm new file mode 100644 index 000000000000..dd2be56d282d --- /dev/null +++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm @@ -0,0 +1,329 @@ +/obj/machinery/particle_accelerator/control_box + name = "Particle Accelerator Control Console" + desc = "This controls the density of the particles." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "control_box" + anchored = FALSE + density = TRUE + use_power = NO_POWER_USE + idle_power_usage = 500 + active_power_usage = 10000 + dir = NORTH + mouse_opacity = MOUSE_OPACITY_OPAQUE + var/strength_upper_limit = 2 + var/interface_control = TRUE + var/list/obj/structure/particle_accelerator/connected_parts + var/assembled = FALSE + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/active = FALSE + var/strength = 0 + var/powered = FALSE + +/obj/machinery/particle_accelerator/control_box/Initialize() + . = ..() + wires = new /datum/wires/particle_accelerator/control_box(src) + connected_parts = list() + +/obj/machinery/particle_accelerator/control_box/Destroy() + if(active) + toggle_power() + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.master = null + connected_parts.Cut() + QDEL_NULL(wires) + return ..() + +/obj/machinery/particle_accelerator/control_box/multitool_act(mob/living/user, obj/item/I) + . = ..() + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + return TRUE + +/obj/machinery/particle_accelerator/control_box/proc/update_state() + if(construction_state < PA_CONSTRUCTION_COMPLETE) + use_power = NO_POWER_USE + assembled = FALSE + active = FALSE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = null + part.powered = FALSE + part.update_icon() + connected_parts.Cut() + return + if(!part_scan()) + use_power = IDLE_POWER_USE + active = FALSE + connected_parts.Cut() + +/obj/machinery/particle_accelerator/control_box/update_icon_state() + . = ..() + if(active) + icon_state = "control_boxp1" + else + if(use_power) + if(assembled) + icon_state = "control_boxp" + else + icon_state = "ucontrol_boxp" + else + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED, PA_CONSTRUCTION_UNWIRED) + icon_state = "control_box" + if(PA_CONSTRUCTION_PANEL_OPEN) + icon_state = "control_boxw" + else + icon_state = "control_boxc" + +/obj/machinery/particle_accelerator/control_box/proc/strength_change() + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = strength + part.update_icon() + +/obj/machinery/particle_accelerator/control_box/proc/add_strength(s) + if(assembled && (strength < strength_upper_limit)) + strength++ + strength_change() + + message_admins("PA Control Computer increased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer increased to [strength] by [key_name(usr)] in [AREACOORD(src)]") + investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + +/obj/machinery/particle_accelerator/control_box/proc/remove_strength(s) + if(assembled && (strength > 0)) + strength-- + strength_change() + + message_admins("PA Control Computer decreased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in [AREACOORD(src)]") + investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + +/obj/machinery/particle_accelerator/control_box/power_change() + . = ..() + if(machine_stat & NOPOWER) + active = FALSE + use_power = NO_POWER_USE + else if(!machine_stat && construction_state == PA_CONSTRUCTION_COMPLETE) + use_power = IDLE_POWER_USE + +/obj/machinery/particle_accelerator/control_box/process() + if(active) + //a part is missing! + if(connected_parts.len < 6) + investigate_log("lost a connected part; It powered down.", INVESTIGATE_SINGULO) + toggle_power() + update_icon() + return + //emit some particles + for(var/obj/structure/particle_accelerator/particle_emitter/PE in connected_parts) + PE.emit_particle(strength) + +/obj/machinery/particle_accelerator/control_box/proc/part_scan() + var/ldir = turn(dir,-90) + var/rdir = turn(dir,90) + var/odir = turn(dir,180) + var/turf/T = loc + + assembled = FALSE + critical_machine = FALSE + + var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) + if(!F) + return FALSE + + setDir(F.dir) + connected_parts.Cut() + + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) + return FALSE + T = get_step(T,odir) + if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) + return FALSE + T = get_step(T,dir) + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/power_box)) + return FALSE + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) + return FALSE + T = get_step(T,ldir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) + return FALSE + T = get_step(T,rdir) + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) + return FALSE + + assembled = TRUE + critical_machine = TRUE //Only counts if the PA is actually assembled. + return TRUE + +/obj/machinery/particle_accelerator/control_box/proc/check_part(turf/T, type) + var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T + if(istype(PA, type) && (PA.construction_state == PA_CONSTRUCTION_COMPLETE)) + if(PA.connect_master(src)) + connected_parts.Add(PA) + return TRUE + return FALSE + +/obj/machinery/particle_accelerator/control_box/proc/toggle_power() + active = !active + investigate_log("turned [active?"ON":"OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + message_admins("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? ADMIN_LOOKUPFLW(usr) : "outside forces"] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? "[key_name(usr)]" : "outside forces"] at [AREACOORD(src)]") + if(active) + use_power = ACTIVE_POWER_USE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = strength + part.powered = TRUE + part.update_icon() + else + use_power = IDLE_POWER_USE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = null + part.powered = FALSE + part.update_icon() + return TRUE + +/obj/machinery/particle_accelerator/control_box/examine(mob/user) + . = ..() + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + . += "Looks like it's not attached to the flooring." + if(PA_CONSTRUCTION_UNWIRED) + . += "It is missing some cables." + if(PA_CONSTRUCTION_PANEL_OPEN) + . += "The panel is open." + +/obj/machinery/particle_accelerator/control_box/set_anchored(anchorvalue) + . = ..() + if(isnull(.)) + return + construction_state = anchorvalue ? PA_CONSTRUCTION_UNWIRED : PA_CONSTRUCTION_UNSECURED + update_state() + update_icon() + +/obj/machinery/particle_accelerator/control_box/attackby(obj/item/W, mob/user, params) + var/did_something = FALSE + + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + if(W.tool_behaviour == TOOL_WRENCH && !isinspace()) + W.play_tool_sound(src, 75) + set_anchored(TRUE) + user.visible_message("[user.name] secures the [name] to the floor.", \ + "You secure the external bolts.") + user.changeNext_move(CLICK_CD_MELEE) + return //set_anchored handles the rest of the stuff we need to do. + if(PA_CONSTRUCTION_UNWIRED) + if(W.tool_behaviour == TOOL_WRENCH) + W.play_tool_sound(src, 75) + set_anchored(FALSE) + user.visible_message("[user.name] detaches the [name] from the floor.", \ + "You remove the external bolts.") + user.changeNext_move(CLICK_CD_MELEE) + return //set_anchored handles the rest of the stuff we need to do. + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(CC.use(1)) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + if(PA_CONSTRUCTION_PANEL_OPEN) + if(W.tool_behaviour == TOOL_WIRECUTTER)//TODO:Shock user if its on? + user.visible_message("[user.name] removes some wires from the [name].", \ + "You remove some wires.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] closes the [name]'s access panel.", \ + "You close the access panel.") + construction_state = PA_CONSTRUCTION_COMPLETE + did_something = TRUE + if(PA_CONSTRUCTION_COMPLETE) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] opens the [name]'s access panel.", \ + "You open the access panel.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + + if(did_something) + user.changeNext_move(CLICK_CD_MELEE) + update_state() + update_icon() + return + + return ..() + +/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B) + if(prob(50)) + qdel(src) + +/obj/machinery/particle_accelerator/control_box/interact(mob/user) + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + else + ..() + +/obj/machinery/particle_accelerator/control_box/proc/is_interactive(mob/user) + if(!interface_control) + to_chat(user, "ERROR: Request timed out. Check wire contacts.") + return FALSE + if(construction_state != PA_CONSTRUCTION_COMPLETE) + return FALSE + return TRUE + +/obj/machinery/particle_accelerator/control_box/ui_status(mob/user) + if(is_interactive(user)) + return ..() + return UI_CLOSE + +/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ParticleAccelerator", name) + ui.open() + +/obj/machinery/particle_accelerator/control_box/ui_data(mob/user) + var/list/data = list() + data["assembled"] = assembled + data["power"] = active + data["strength"] = strength + return data + +/obj/machinery/particle_accelerator/control_box/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + if(wires.is_cut(WIRE_POWER)) + return + toggle_power() + . = TRUE + if("scan") + part_scan() + . = TRUE + if("add_strength") + if(wires.is_cut(WIRE_STRENGTH)) + return + add_strength() + . = TRUE + if("remove_strength") + if(wires.is_cut(WIRE_STRENGTH)) + return + remove_strength() + . = TRUE + + update_icon() + +#undef PA_CONSTRUCTION_UNSECURED +#undef PA_CONSTRUCTION_UNWIRED +#undef PA_CONSTRUCTION_PANEL_OPEN +#undef PA_CONSTRUCTION_COMPLETE diff --git a/code/modules/power/singularity/particle_accelerator/particle_emitter.dm b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm new file mode 100644 index 000000000000..d754b7e62d31 --- /dev/null +++ b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm @@ -0,0 +1,43 @@ +/obj/structure/particle_accelerator/particle_emitter + name = "EM Containment Grid" + desc = "This launches the Alpha particles, might not want to stand near this end." + icon = 'icons/obj/particle_accelerator.dmi' + icon_state = "none" + var/fire_delay = 50 + var/last_shot = 0 + +/obj/structure/particle_accelerator/particle_emitter/center + icon_state = "emitter_center" + reference = "emitter_center" + +/obj/structure/particle_accelerator/particle_emitter/left + icon_state = "emitter_left" + reference = "emitter_left" + +/obj/structure/particle_accelerator/particle_emitter/right + icon_state = "emitter_right" + reference = "emitter_right" + +/obj/structure/particle_accelerator/particle_emitter/proc/set_delay(delay) + if(delay >= 0) + fire_delay = delay + return 1 + return 0 + +/obj/structure/particle_accelerator/particle_emitter/proc/emit_particle(strength = 0) + if((last_shot + fire_delay) <= world.time) + last_shot = world.time + var/turf/T = get_turf(src) + var/obj/effect/accelerated_particle/P + switch(strength) + if(0) + P = new/obj/effect/accelerated_particle/weak(T) + if(1) + P = new/obj/effect/accelerated_particle(T) + if(2) + P = new/obj/effect/accelerated_particle/strong(T) + if(3) + P = new/obj/effect/accelerated_particle/powerful(T) + P.setDir(dir) + return 1 + return 0 diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index de6a36e43c0c..dab0054c316e 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -7,49 +7,44 @@ anchored = TRUE density = TRUE move_resist = INFINITY - plane = MASSIVE_OBJ_PLANE - plane = ABOVE_LIGHTING_PLANE + layer = MASSIVE_OBJ_PLANE light_range = 6 - appearance_flags = LONG_GLIDE - - /// The singularity component itself. - /// A weak ref in case an admin removes the component to preserve the functionality. - var/datum/weakref/singularity_component + appearance_flags = 0 ///Current singularity size, from 1 to 6 var/current_size = 1 ///Current allowed size for the singulo var/allowed_size = 1 - ///How strong are we? - var/energy = 100 - ///Do we lose energy over time? - var/dissipate = TRUE - /// How long should it take for us to dissipate in seconds? - var/dissipate_delay = 20 - /// How much energy do we lose every dissipate_delay? - var/dissipate_strength = 1 - /// How long its been (in seconds) since the last dissipation - var/time_since_last_dissipiation = 0 - ///Prob for event each tick - var/event_chance = 10 - ///Can i move by myself? - var/move_self = TRUE - ///If the singularity has eaten a supermatter shard and can go to stage six - var/consumed_supermatter = FALSE - /// How long it's been since the singulo last acted, in seconds - var/time_since_act = 0 - - flags_1 = SUPERMATTER_IGNORES_1 + var/contained = 1 //Is it gonna move around? + var/energy = 100 //How strong are we? + var/dissipate = 1 //Do we lose energy over time? + var/dissipate_delay = 10 //How long does it take to dissipate in seconds? + var/dissipate_track = 0 //Tracks the time before dissipation starts + var/dissipate_strength = 1 //How much energy do we lose? + var/move_self = 1 //Do we move on our own? + var/grav_pull = 4 //How many tiles out do we pull? + var/consume_range = 0 //How many tiles out do we eat + var/event_chance = 10 //Prob for event each tick + var/target = null //its target. moves towards the target if it has one + var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing + var/last_warning + var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six + var/drifting_dir = 0 // Chosen direction to drift in resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION /obj/singularity/Initialize(mapload, starting_energy = 50) + //In case Urist McShitler is feeling quirky for once. + admin_investigate_setup() + + src.energy = starting_energy . = ..() energy = starting_energy START_PROCESSING(SSsinguloprocess, src) SSpoints_of_interest.make_point_of_interest(src) + GLOB.poi_list |= src var/datum/component/singularity/new_component = AddComponent( /datum/component/singularity, \ @@ -77,10 +72,57 @@ ) + AddElement(/datum/element/bsa_blocker) + RegisterSignal(src, COMSIG_ATOM_BSA_BEAM, .proc/bluespace_reaction) + /obj/singularity/Destroy() STOP_PROCESSING(SSsinguloprocess, src) + GLOB.poi_list.Remove(src) + GLOB.singularities.Remove(src) return ..() +/obj/singularity/Move(atom/newloc, direct) + var/turf/T = get_turf(src) + for(var/dir in GLOB.cardinals) + if(direct & dir) + T = get_step(T, dir) + if(!T) + break + // eat the stuff if we're going to move into it so it doesn't mess up our movement + for(var/atom/A in T.contents) + consume(A) + consume(T) + + if(current_size >= STAGE_FIVE || check_turfs_in(direct)) + last_failed_movement = 0//Reset this because we moved + return ..() + else + last_failed_movement = direct + return 0 + +/obj/singularity/attack_hand(mob/user) + consume(user) + return TRUE + +/obj/singularity/attack_paw(mob/user) + consume(user) + +/obj/singularity/attack_alien(mob/user) + consume(user) + +/obj/singularity/attack_animal(mob/user) + consume(user) + +/obj/singularity/attackby(obj/item/W, mob/user, params) + consume(user) + return 1 + +/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man! + return 0 + +/obj/singularity/blob_act(obj/structure/blob/B) + return + /obj/singularity/attack_tk(mob/user) if(!iscarbon(user)) return @@ -154,13 +196,53 @@ time_since_act = 0 if(current_size >= STAGE_TWO) if(prob(event_chance)) + +/obj/singularity/bullet_act(obj/projectile/P) + qdel(P) + return BULLET_ACT_HIT //Will there be an impact? Who knows. Will we see it? No. + + +/obj/singularity/Bump(atom/A) + consume(A) + if(QDELETED(A)) // don't keep moving into objects that weren't destroyed infinitely + step(src, drifting_dir) + return + + +/obj/singularity/Bumped(atom/movable/AM) + consume(AM) + + +/obj/singularity/process() + if(current_size >= STAGE_TWO) + move() + radiation_pulse(src, min(5000, (energy*4.5)+1000), RAD_DISTANCE_COEFFICIENT*0.5) + if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit event() - dissipate(delta_time) + eat() + dissipate() check_energy() + return -/obj/singularity/proc/dissipate(delta_time) - if (!dissipate) +/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one. + return + +/obj/singularity/proc/admin_investigate_setup() + var/turf/T = get_turf(src) + last_warning = world.time + var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1) + if(!count) + message_admins("A singularity has been created without containment fields active at [ADMIN_VERBOSEJMP(T)].") + investigate_log("was created at [AREACOORD(T)]. [count?"":"No containment fields were active"]", INVESTIGATE_SINGULO) + +/obj/singularity/proc/dissipate() + if(!dissipate) return + if(dissipate_track >= dissipate_delay) + src.energy -= dissipate_strength + dissipate_track = 0 + else + dissipate_track++ time_since_last_dissipiation += delta_time @@ -174,13 +256,9 @@ if(force_size) temp_allowed_size = force_size - - if(temp_allowed_size >= STAGE_SIX && !consumed_supermatter) + if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter) temp_allowed_size = STAGE_FIVE - var/new_grav_pull - var/new_consume_range - switch(temp_allowed_size) if(STAGE_ONE) current_size = STAGE_ONE @@ -188,10 +266,10 @@ icon_state = "singularity_s1" pixel_x = 0 pixel_y = 0 - new_grav_pull = 4 - new_consume_range = 0 + grav_pull = 4 + consume_range = 0 dissipate_delay = 10 - time_since_last_dissipiation = 0 + dissipate_track = 0 dissipate_strength = 1 if(STAGE_TWO) if(check_cardinals_range(1, TRUE)) @@ -200,10 +278,10 @@ icon_state = "singularity_s3" pixel_x = -32 pixel_y = -32 - new_grav_pull = 6 - new_consume_range = 1 + grav_pull = 6 + consume_range = 1 dissipate_delay = 5 - time_since_last_dissipiation = 0 + dissipate_track = 0 dissipate_strength = 5 if(STAGE_THREE) if(check_cardinals_range(2, TRUE)) @@ -212,10 +290,10 @@ icon_state = "singularity_s5" pixel_x = -64 pixel_y = -64 - new_grav_pull = 8 - new_consume_range = 2 + grav_pull = 8 + consume_range = 2 dissipate_delay = 4 - time_since_last_dissipiation = 0 + dissipate_track = 0 dissipate_strength = 20 if(STAGE_FOUR) if(check_cardinals_range(3, TRUE)) @@ -224,10 +302,10 @@ icon_state = "singularity_s7" pixel_x = -96 pixel_y = -96 - new_grav_pull = 10 - new_consume_range = 3 + grav_pull = 10 + consume_range = 3 dissipate_delay = 10 - time_since_last_dissipiation = 0 + dissipate_track = 0 dissipate_strength = 10 if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything current_size = STAGE_FIVE @@ -235,26 +313,18 @@ icon_state = "singularity_s9" pixel_x = -128 pixel_y = -128 - new_grav_pull = 10 - new_consume_range = 4 - dissipate = FALSE //It cant go smaller due to e loss + grav_pull = 10 + consume_range = 4 + dissipate = 0 //It cant go smaller due to e loss if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard. current_size = STAGE_SIX icon = 'icons/effects/352x352.dmi' icon_state = "singularity_s11" pixel_x = -160 pixel_y = -160 - new_grav_pull = 15 - new_consume_range = 5 - dissipate = FALSE - - var/datum/component/singularity/resolved_singularity = singularity_component.resolve() - if (!isnull(resolved_singularity)) - resolved_singularity.consume_range = new_consume_range - resolved_singularity.grav_pull = new_grav_pull - resolved_singularity.disregard_failed_movements = current_size >= STAGE_FIVE - resolved_singularity.roaming = move_self && current_size >= STAGE_TWO - resolved_singularity.singularity_size = current_size + grav_pull = 15 + consume_range = 5 + dissipate = 0 if(current_size == allowed_size) investigate_log("grew to size [current_size].", INVESTIGATE_ENGINE) @@ -262,13 +332,13 @@ else if(current_size < (--temp_allowed_size)) expand(temp_allowed_size) else - return FALSE + return 0 /obj/singularity/proc/check_energy() if(energy <= 0) investigate_log("collapsed.", INVESTIGATE_ENGINE) qdel(src) - return FALSE + return 0 switch(energy)//Some of these numbers might need to be changed up later -Mport if(1 to 199) allowed_size = STAGE_ONE @@ -279,22 +349,57 @@ if(1000 to 1999) allowed_size = STAGE_FOUR if(2000 to INFINITY) - if(energy >= 3000 && consumed_supermatter) + if(energy >= 3000 && consumedSupermatter) allowed_size = STAGE_SIX else allowed_size = STAGE_FIVE if(current_size != allowed_size) expand() - return TRUE + return 1 + +/obj/singularity/proc/eat() + for(var/tile in spiral_range_turfs(grav_pull, src)) + var/turf/T = tile + if(!T || !isturf(loc)) + continue + if(get_dist(T, src) > consume_range) + T.singularity_pull(src, current_size) + else + consume(T) + for(var/thing in T) + if(isturf(loc) && thing != src) + var/atom/movable/X = thing + if(get_dist(X, src) > consume_range) + X.singularity_pull(src, current_size) + else + consume(X) + CHECK_TICK + return + -/obj/singularity/proc/consume(atom/thing) - var/gain = thing.singularity_act(current_size, src) - energy += gain - if(istype(thing, /obj/machinery/power/supermatter_crystal) && !consumed_supermatter) +/obj/singularity/proc/consume(atom/A) + var/gain = A.singularity_act(current_size, src) + src.energy += gain + if(istype(A, /obj/machinery/power/supermatter_crystal) && !consumedSupermatter) desc = "[initial(desc)] It glows fiercely with inner fire." name = "supermatter-charged [initial(name)]" - consumed_supermatter = TRUE + consumedSupermatter = 1 set_light(10) + return + +/obj/singularity/proc/move(force_move = 0) + if(!move_self) + return 0 + + var/drifting_dir = pick(GLOB.alldirs - last_failed_movement) + + if(force_move) + drifting_dir = force_move + + if(target && prob(60)) + drifting_dir = get_dir(src,target) //moves to a singulo beacon, if there is one + + step(src, drifting_dir) /obj/singularity/proc/check_cardinals_range(steps, retry_with_move = FALSE) . = length(GLOB.cardinals) //Should be 4. @@ -305,11 +410,11 @@ if(step(src, i)) //Move in each direction. if(check_cardinals_range(steps, FALSE)) //New location passes, return true. return TRUE - return !. + . = !. /obj/singularity/proc/check_turfs_in(direction = 0, step = 0) if(!direction) - return FALSE + return 0 var/steps = 0 if(!step) switch(current_size) @@ -426,6 +531,7 @@ /obj/singularity/proc/emp_area() empulse(src, 8, 10) + return /obj/singularity/singularity_act() var/gain = (energy/2) @@ -454,3 +560,7 @@ /obj/singularity/deadchat_controlled/Initialize(mapload, starting_energy) . = ..() deadchat_plays(mode = DEMOCRACY_MODE) + +/obj/singularity/proc/bluespace_reaction() + investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_SINGULO) + qdel(src) diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm index 34c6b551344d..30e5ba8675cc 100644 --- a/code/modules/power/tesla/energy_ball.dm +++ b/code/modules/power/tesla/energy_ball.dm @@ -10,6 +10,176 @@ #define BLOB (STRUCTURE + 1) #define STRUCTURE (1) +/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/Initialize(mapload, starting_energy = 50, is_miniball = FALSE) + miniball = is_miniball + . = ..() + if(!is_miniball) + set_light(10, 7, "#EEEEFF") + +/obj/singularity/energy_ball/ex_act(severity, target) + return + +/obj/singularity/energy_ball/consume(severity, target) + return + +/obj/singularity/energy_ball/Destroy() + if(orbiting && istype(orbiting.parent, /obj/singularity/energy_ball)) + var/obj/singularity/energy_ball/EB = orbiting.parent + EB.orbiting_balls -= src + + for(var/ball in orbiting_balls) + var/obj/singularity/energy_ball/EB = ball + QDEL_NULL(EB) + + . = ..() + +/obj/singularity/energy_ball/admin_investigate_setup() + if(miniball) + return //don't annnounce miniballs + ..() + + +/obj/singularity/energy_ball/process() + if(!orbiting) + handle_energy() + + move_the_basket_ball(4 + orbiting_balls.len * 1.5) + + playsound(src.loc, 'sound/magic/lightningbolt.ogg', 100, TRUE, extrarange = 30) + + pixel_x = 0 + pixel_y = 0 + + tesla_zap(src, 7, TESLA_DEFAULT_POWER) + + pixel_x = -32 + pixel_y = -32 + 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) + else + energy = 0 // ensure we dont have miniballs of miniballs + +/obj/singularity/energy_ball/examine(mob/user) + . = ..() + if(orbiting_balls.len) + . += "There are [orbiting_balls.len] mini-balls orbiting it." + + +/obj/singularity/energy_ball/proc/move_the_basket_ball(move_amount) + //we face the last thing we zapped, so this lets us favor that direction a bit + var/move_bias = pick(GLOB.alldirs) + for(var/i in 0 to move_amount) + var/move_dir = pick(GLOB.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) + setDir(move_dir) + for(var/mob/living/carbon/C in loc) + dust_mobs(C) + + +/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/magic/lightning_chargeup.ogg', 100, TRUE, extrarange = 30) + addtimer(CALLBACK(src, .proc/new_mini_ball), 100) + + 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/attack_tk(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + to_chat(C, "That was a shockingly dumb idea.") + var/obj/item/organ/brain/rip_u = locate(/obj/item/organ/brain) in C.internal_organs + C.ghostize(0) + qdel(rip_u) + C.death() + +/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target) + if (istype(target)) + target.orbiting_balls += src + GLOB.poi_list -= src + target.dissipate_strength = target.orbiting_balls.len + . = ..() + +/obj/singularity/energy_ball/stop_orbit() + if (orbiting && istype(orbiting.parent, /obj/singularity/energy_ball)) + var/obj/singularity/energy_ball/orbitingball = orbiting.parent + orbitingball.orbiting_balls -= src + orbitingball.dissipate_strength = orbitingball.orbiting_balls.len + . = ..() + if (!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 || L.status_flags & GODMODE) + 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() + /// The Tesla engine /obj/energy_ball name = "energy ball" diff --git a/code/modules/power/tesla/generator.dm b/code/modules/power/tesla/generator.dm new file mode 100644 index 000000000000..a63e11b4914a --- /dev/null +++ b/code/modules/power/tesla/generator.dm @@ -0,0 +1,12 @@ +/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/zap_act(power, zap_flags) + if(zap_flags & ZAP_MACHINE_EXPLOSIVE) + energy += power + zap_flags &= ~(ZAP_MACHINE_EXPLOSIVE | ZAP_OBJ_DAMAGE) // Don't blow yourself up yeah? + return ..() diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index f5a7099cfbea..a49c6e5965ca 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -21,6 +21,22 @@ ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING +/datum/design/board/circulator + name = "Machine Design (Circulator Board)" + desc = "The circuit board for a circulator." + id = "circulator" + build_path = /obj/item/circuitboard/machine/circulator + category = list ("Engineering Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/board/teg + name = "Machine Design (TEG Board)" + desc = "The circuit board for a TEG." + id = "teg" + build_path = /obj/item/circuitboard/machine/generator + category = list ("Engineering Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + /datum/design/board/announcement_system name = "Automated Announcement System Board" desc = "The circuit board for an automated announcement system." diff --git a/code/modules/research/designs/power_designs.dm b/code/modules/research/designs/power_designs.dm index 89377a8e1d4e..3f4e99afd794 100644 --- a/code/modules/research/designs/power_designs.dm +++ b/code/modules/research/designs/power_designs.dm @@ -127,3 +127,16 @@ RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_TURBINE ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING +/datum/design/board/pacman/super + name = "Machine Design (SUPERPACMAN-type Generator Board)" + desc = "The circuit board that for a SUPERPACMAN-type portable generator." + id = "superpacman" + build_path = /obj/item/circuitboard/machine/pacman/super + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/board/pacman/mrs + name = "Machine Design (MRSPACMAN-type Generator Board)" + desc = "The circuit board that for a MRSPACMAN-type portable generator." + id = "mrspacman" + build_path = /obj/item/circuitboard/machine/pacman/mrs + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index c33156361018..577c86a50a08 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -662,6 +662,10 @@ "turbine_compressor", "turbine_rotor", "turbine_stator", + "circulator", + "teg", + "superpacman", + "mrspacman", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) discount_experiments = list(/datum/experiment/scanning/points/machinery_pinpoint_scan/tier2_capacitors = 2500) diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 8f87770b4d33..b5e1d125b73f 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -32,7 +32,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) return FALSE /obj/machinery/bsa - icon = 'icons/obj/machines/particle_accelerator.dmi' + icon = 'icons/obj/particle_accelerator.dmi' density = TRUE anchored = TRUE @@ -260,7 +260,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) name = "bluespace artillery control" use_power = NO_POWER_USE circuit = /obj/item/circuitboard/computer/bsa_control - icon = 'icons/obj/machines/particle_accelerator.dmi' + icon = 'icons/obj/particle_accelerator.dmi' icon_state = "control_boxp" icon_keyboard = null icon_screen = null diff --git a/icons/obj/engine/singularity.dmi b/icons/obj/engine/singularity.dmi index e231032f5768..9d11f886accf 100644 Binary files a/icons/obj/engine/singularity.dmi and b/icons/obj/engine/singularity.dmi differ diff --git a/icons/obj/particle_accelerator.dmi b/icons/obj/particle_accelerator.dmi new file mode 100644 index 000000000000..c601b8d1b6ad Binary files /dev/null and b/icons/obj/particle_accelerator.dmi differ diff --git a/tgstation.dme b/tgstation.dme index a3de34d3c63e..6e8e50a4fe45 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1422,6 +1422,7 @@ #include "code\datums\wires\microwave.dm" #include "code\datums\wires\mod.dm" #include "code\datums\wires\mulebot.dm" +#include "code\datums\wires\particle_accelerator.dm" #include "code\datums\wires\r_n_d.dm" #include "code\datums\wires\radio.dm" #include "code\datums\wires\robot.dm" @@ -4322,8 +4323,13 @@ #include "code\modules\power\singularity\containment_field.dm" #include "code\modules\power\singularity\emitter.dm" #include "code\modules\power\singularity\field_generator.dm" +#include "code\modules\power\singularity\generator.dm" #include "code\modules\power\singularity\narsie.dm" #include "code\modules\power\singularity\singularity.dm" +#include "code\modules\power\singularity\particle_accelerator\particle.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_accelerator.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_control.dm" +#include "code\modules\power\singularity\particle_accelerator\particle_emitter.dm" #include "code\modules\power\supermatter\supermatter.dm" #include "code\modules\power\supermatter\supermatter_extra_effects.dm" #include "code\modules\power\supermatter\supermatter_gas.dm" @@ -4336,6 +4342,7 @@ #include "code\modules\power\supermatter\supermatter_delamination\delamination_effects.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\turbine\turbine.dm" #include "code\modules\power\turbine\turbine_computer.dm" #include "code\modules\power\turbine\turbine_parts.dm" diff --git a/tgui/packages/tgui/interfaces/ParticleAccelerator.js b/tgui/packages/tgui/interfaces/ParticleAccelerator.js new file mode 100644 index 000000000000..04f8ba48104e --- /dev/null +++ b/tgui/packages/tgui/interfaces/ParticleAccelerator.js @@ -0,0 +1,64 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const ParticleAccelerator = (props, context) => { + const { act, data } = useBackend(context); + const { + assembled, + power, + strength, + } = data; + return ( + + +
+ + act('scan')} /> + )}> + + {assembled + ? "Ready - All parts in place" + : "Unable to detect all parts"} + + + +
+
+ + +
+
+
+ ); +}; \ No newline at end of file