diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm new file mode 100644 index 0000000000..35d99a982d --- /dev/null +++ b/code/__DEFINES/research/anomalies.dm @@ -0,0 +1,21 @@ +// Anomaly core types +/// Bluespace cores +#define ANOMALY_CORE_BLUESPACE /obj/item/assembly/signaler/anomaly/bluespace +/// Gravitational cores +#define ANOMALY_CORE_GRAVITATIONAL /obj/item/assembly/signaler/anomaly/grav +/// Flux +#define ANOMALY_CORE_FLUX /obj/item/assembly/signaler/anomaly/flux +/// Vortex +#define ANOMALY_CORE_VORTEX /obj/item/assembly/signaler/anomaly/vortex +/// Pyro +#define ANOMALY_CORE_PYRO /obj/item/assembly/signaler/anomaly/pyro + +// Max amounts of cores you can make +#define MAX_CORES_BLUESPACE 8 +#define MAX_CORES_GRAVITATIONAL 8 +#define MAX_CORES_FLUX 8 +#define MAX_CORES_VORTEX 8 +#define MAX_CORES_PYRO 8 + +/// chance supermatter anomalies drop real cores +#define SUPERMATTER_ANOMALY_DROP_CHANCE 20 diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 60c5dd5b99..ac9db60ccc 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -294,6 +294,17 @@ SUBSYSTEM_DEF(research) //[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min] //Around 450000 points max??? + /// The global list of raw anomaly types that have been refined, for hard limits. + var/list/created_anomaly_types = list() + /// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements. + var/list/anomaly_hard_limit_by_type = list( + ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE, + ANOMALY_CORE_PYRO = MAX_CORES_PYRO, + ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL, + ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX, + ANOMALY_CORE_FLUX = MAX_CORES_FLUX + ) + /datum/controller/subsystem/research/Initialize() point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES initialize_all_techweb_designs() diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 2b7411d03f..b82d932e6d 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -551,4 +551,4 @@ Class Procs: AM.pixel_y = -8 + (round( . / 3)*8) /obj/machinery/rust_heretic_act() - take_damage(500, BRUTE, "melee", 1) + take_damage(500, BRUTE, "melee", 1) diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index 871fe838d7..7e3a5205de 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -17,11 +17,15 @@ var/countdown_colour var/obj/effect/countdown/anomaly/countdown -/obj/effect/anomaly/Initialize(mapload, new_lifespan) + /// chance we drop a core when neutralized + var/core_drop_chance = 100 + +/obj/effect/anomaly/Initialize(mapload, new_lifespan, core_drop_chance = 100) . = ..() GLOB.poi_list |= src START_PROCESSING(SSobj, src) impact_area = get_area(src) + src.core_drop_chance = core_drop_chance if (!impact_area) return INITIALIZE_HINT_QDEL @@ -54,6 +58,8 @@ GLOB.poi_list.Remove(src) STOP_PROCESSING(SSobj, src) qdel(countdown) + if(aSignal) + QDEL_NULL(aSignal) return ..() /obj/effect/anomaly/proc/anomalyEffect() @@ -70,12 +76,12 @@ /obj/effect/anomaly/proc/anomalyNeutralize() new /obj/effect/particle_effect/smoke/bad(loc) - for(var/atom/movable/O in src) - O.forceMove(drop_location()) + if(prob(core_drop_chance)) + aSignal.forceMove(drop_location()) + aSignal = null qdel(src) - /obj/effect/anomaly/attackby(obj/item/I, mob/user, params) if(I.tool_behaviour == TOOL_ANALYZER) //revert if runtimed to_chat(user, "Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(aSignal.frequency)], code [aSignal.code].") diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 806aed92ed..8e59e91e38 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1146,3 +1146,8 @@ build_path = /obj/machinery/atmospherics/components/unary/shuttle/heater req_components = list(/obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/matter_bin = 1) + +/obj/item/circuitboard/machine/explosive_compressor + name = "Explosive Compressor (Machine Board)" + build_path = /obj/machinery/research/explosive_compressor + req_components = list(/obj/item/stock_parts/matter_bin = 3) diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 32d9c02a27..8b0d46be44 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -289,3 +289,9 @@ . = TRUE update_icon() + +/** + * Returns if this is ready to be detonated. Checks if both tanks are in place. + */ +/obj/item/transfer_valve/proc/ready() + return tank_one && tank_two diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm index fd6fee362d..125bfe2034 100644 --- a/code/modules/cargo/packs/science.dm +++ b/code/modules/cargo/packs/science.dm @@ -192,3 +192,49 @@ crate_type = /obj/structure/closet/crate/secure/science dangerous = TRUE +//////// RAW ANOMALY CORES + +/datum/supply_pack/science/raw_flux_anomaly + name = "Raw Flux Anomaly" + desc = "The raw core of a flux anomaly, ready to be implosion-compressed into a powerful artifact." + cost = 5000 + access = ACCESS_TOX + contains = list(/obj/item/raw_anomaly_core/flux) + crate_name = "raw flux anomaly" + crate_type = /obj/structure/closet/crate/secure/science + +/datum/supply_pack/science/raw_grav_anomaly + name = "Raw Gravitational Anomaly" + desc = "The raw core of a gravitational anomaly, ready to be implosion-compressed into a powerful artifact." + cost = 5000 + access = ACCESS_TOX + contains = list(/obj/item/raw_anomaly_core/grav) + crate_name = "raw pyro anomaly" + crate_type = /obj/structure/closet/crate/secure/science + +/datum/supply_pack/science/raw_vortex_anomaly + name = "Raw Vortex Anomaly" + desc = "The raw core of a vortex anomaly, ready to be implosion-compressed into a powerful artifact." + cost = 5000 + access = ACCESS_TOX + contains = list(/obj/item/raw_anomaly_core/vortex) + crate_name = "raw vortex anomaly" + crate_type = /obj/structure/closet/crate/secure/science + +/datum/supply_pack/science/raw_bluespace_anomaly + name = "Raw Bluespace Anomaly" + desc = "The raw core of a bluespace anomaly, ready to be implosion-compressed into a powerful artifact." + cost = 5000 + access = ACCESS_TOX + contains = list(/obj/item/raw_anomaly_core/bluespace) + crate_name = "raw bluespace anomaly" + crate_type = /obj/structure/closet/crate/secure/science + +/datum/supply_pack/science/raw_pyro_anomaly + name = "Raw Pyro Anomaly" + desc = "The raw core of a pyro anomaly, ready to be implosion-compressed into a powerful artifact." + cost = 5000 + access = ACCESS_TOX + contains = list(/obj/item/raw_anomaly_core/pyro) + crate_name = "raw pyro anomaly" + crate_type = /obj/structure/closet/crate/secure/science diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index d4e3f6ad0d..e3fdbd3654 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -639,7 +639,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(produces_gas) env.merge(removed) air_update_turf() - + /********* END CITADEL CHANGES *********/ @@ -1033,12 +1033,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(L) switch(type) if(FLUX_ANOMALY) - var/obj/effect/anomaly/flux/A = new(L, 300, FALSE) + var/obj/effect/anomaly/flux/A = new(L, 300, SUPERMATTER_ANOMALY_DROP_CHANCE) A.explosive = FALSE if(GRAVITATIONAL_ANOMALY) - new /obj/effect/anomaly/grav(L, 250, FALSE) + new /obj/effect/anomaly/grav(L, 250, SUPERMATTER_ANOMALY_DROP_CHANCE) if(PYRO_ANOMALY) - new /obj/effect/anomaly/pyro(L, 200, FALSE) + new /obj/effect/anomaly/pyro(L, 200, SUPERMATTER_ANOMALY_DROP_CHANCE) /obj/machinery/power/supermatter_crystal/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 4000, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list()) if(QDELETED(zapstart)) diff --git a/code/modules/research/anomaly/explosive_compressor.dm b/code/modules/research/anomaly/explosive_compressor.dm new file mode 100644 index 0000000000..23983e31dc --- /dev/null +++ b/code/modules/research/anomaly/explosive_compressor.dm @@ -0,0 +1,152 @@ +#define MAX_RADIUS_REQUIRED 8000 //tritbomb +#define MIN_RADIUS_REQUIRED 20 //maxcap +/** + * # Explosive compressor machines + * + * The explosive compressor machine used in anomaly core production. + * + * Uses the standard toxins/tank explosion scaling to compress raw anomaly cores into completed ones. The required explosion radius increases as more cores of that type are created. + */ +/obj/machinery/research/explosive_compressor + name = "implosion compressor" + desc = "An advanced machine capable of implosion-compressing raw anomaly cores into finished artifacts." + icon = 'icons/obj/machines/research.dmi' + icon_state = "explosive_compressor" + density = TRUE + circuit = /obj/item/circuitboard/machine/explosive_compressor + + /// The raw core inserted in the machine. + var/obj/item/raw_anomaly_core/inserted_core + /// The TTV inserted in the machine. + var/obj/item/transfer_valve/inserted_bomb + /// The last time we did say_requirements(), because someone will inevitably click spam this. + var/last_requirements_say = 0 + +/obj/machinery/research/explosive_compressor/examine(mob/user) + . = ..() + . += "Ctrl-Click to remove an inserted core." + . += "Click with an empty hand to gather information about the required radius of an inserted core. Insert a ready TTV to start the implosion process if a core is inserted." + +/obj/machinery/research/explosive_compressor/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!inserted_core) + to_chat(user, "There is no core inserted.") + return + if(last_requirements_say + 3 SECONDS > world.time) + return + last_requirements_say = world.time + say_requirements(inserted_core) + +/obj/machinery/research/explosive_compressor/CtrlClick(mob/living/user) + . = ..() + if(!istype(user) || !user.Adjacent(src) || !(user.mobility_flags & MOBILITY_USE)) + return + if(!inserted_core) + to_chat(user, "There is no core inserted.") + return + inserted_core.forceMove(get_turf(user)) + to_chat(user, "You remove [inserted_core] from [src].") + user.put_in_hands(inserted_core) + inserted_core = null + +/** + * Says (no, literally) the data of required explosive power for a certain anomaly type. + */ +/obj/machinery/research/explosive_compressor/proc/say_requirements(obj/item/raw_anomaly_core/C) + var/required = get_required_radius(C.anomaly_type) + if(isnull(required)) + say("Unfortunately, due to diminishing supplies of condensed anomalous matter, [C] and any cores of its type are no longer of a sufficient quality level to be compressed into a working core.") + else + say("[C] requires a minimum of a theoretical radius of [required] to successfully implode into a charged anomaly core.") + +/** + * Determines how much explosive power (last value, so light impact theoretical radius) is required to make a certain anomaly type. + * + * Returns null if the max amount has already been reached. + * + * Arguments: + * * anomaly_type - anomaly type define + */ +/obj/machinery/research/explosive_compressor/proc/get_required_radius(anomaly_type) + var/already_made = SSresearch.created_anomaly_types[anomaly_type] + var/hard_limit = SSresearch.anomaly_hard_limit_by_type[anomaly_type] + if(already_made >= hard_limit) + return //return null + // my crappy autoscale formula + // linear scaling. + var/radius_span = MAX_RADIUS_REQUIRED - MIN_RADIUS_REQUIRED + var/radius_increase_per_core = radius_span / hard_limit + var/radius = clamp(round(MIN_RADIUS_REQUIRED + radius_increase_per_core * already_made, 1), MIN_RADIUS_REQUIRED, MAX_RADIUS_REQUIRED) + return radius + +/obj/machinery/research/explosive_compressor/attackby(obj/item/I, mob/living/user, params) + if(default_unfasten_wrench(user, I)) + return + if(default_deconstruction_screwdriver(user, "explosive_compressor", "explosive_compressor", I)) + return + if(default_deconstruction_crowbar(I)) + return + . = ..() + if(istype(I, /obj/item/raw_anomaly_core)) + if(inserted_core) + to_chat(user, "There is already a core in [src].") + return + if(!user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand.") + return + inserted_core = I + to_chat(user, "You insert [I] into [src].") + return + if(istype(I, /obj/item/transfer_valve)) + // If they don't have a bomb core inserted, don't let them insert this. If they do, insert and do implosion. + if(!inserted_core) + to_chat(user, "There is no core inserted in [src]. What would be the point of detonating an implosion without a core?") + return + var/obj/item/transfer_valve/valve = I + if(!valve.ready()) + to_chat(user, "[valve] is incomplete.") + return + if(!user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand.") + return + inserted_bomb = I + to_chat(user, "You insert [I] and press the start button.") + do_implosion() + +/** + * The ""explosion"" proc. + */ +/obj/machinery/research/explosive_compressor/proc/do_implosion() + var/required_radius = get_required_radius(inserted_core.anomaly_type) + // By now, we should be sure that we have a core, a TTV, and that the TTV has both tanks in place. + var/datum/gas_mixture/mix1 = inserted_bomb.tank_one.air_contents + var/datum/gas_mixture/mix2 = inserted_bomb.tank_two.air_contents + // Snowflaked tank explosion + var/datum/gas_mixture/mix = new(70) // Standard tank volume, 70L + mix.merge(mix1) + mix.merge(mix2) + mix.react() + if(mix.return_pressure() < TANK_FRAGMENT_PRESSURE) + // They failed so miserably we're going to give them their bomb back. + inserted_bomb.forceMove(drop_location()) + inserted_bomb = null + inserted_core.forceMove(drop_location()) + inserted_core = null + say("Transfer valve resulted in negligible explosive power. Items ejected.") + return + mix.react() // build more pressure + var/pressure = mix.return_pressure() + var/range = (pressure - TANK_FRAGMENT_PRESSURE) / TANK_FRAGMENT_SCALE + QDEL_NULL(inserted_bomb) // bomb goes poof + if(range < required_radius) + inserted_bomb.forceMove(src) + say("Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Core ejected.") + return + inserted_core.create_core(drop_location(), TRUE, TRUE) + inserted_core = null + say("Success. Resultant detonation has theoretical range of [range]. Required radius was [required_radius]. Core production complete.") + +#undef MAX_RADIUS_REQUIRED +#undef MIN_RADIUS_REQUIRED diff --git a/code/modules/research/anomaly/raw_anomaly.dm b/code/modules/research/anomaly/raw_anomaly.dm new file mode 100644 index 0000000000..2342edd28f --- /dev/null +++ b/code/modules/research/anomaly/raw_anomaly.dm @@ -0,0 +1,73 @@ +/** + * # Raw Anomaly Cores + * + * The current precursor to anomaly cores, these are manufactured into 'finished' anomaly cores for use in research, items, and more. + * + * The current amounts created is stored in SSresearch.created_anomaly_types[ANOMALY_CORE_TYPE_DEFINE] = amount + * The hard limits are in code/__DEFINES/anomalies.dm + */ +/obj/item/raw_anomaly_core + name = "raw anomaly core" + desc = "You shouldn't be seeing this. Someone screwed up." + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "broken_state" + + /// Anomaly type + var/anomaly_type + +/obj/item/raw_anomaly_core/bluespace + name = "raw bluespace core" + desc = "The raw core of a bluespace anomaly, glowing and full of potential." + anomaly_type = /obj/item/assembly/signaler/anomaly/bluespace + icon_state = "rawcore_bluespace" + +/obj/item/raw_anomaly_core/vortex + name = "raw vortex core" + desc = "The raw core of a vortex anomaly. Feels heavy to the touch." + anomaly_type = /obj/item/assembly/signaler/anomaly/vortex + icon_state = "rawcore_vortex" + +/obj/item/raw_anomaly_core/grav + name = "raw gravity core" + desc = "The raw core of a gravity anomaly. The air seems attracted to it." + anomaly_type = /obj/item/assembly/signaler/anomaly/grav + icon_state = "rawcore_grav" + +/obj/item/raw_anomaly_core/pyro + desc = "The raw core of a pyro anomaly. It is warm to the touch." + name = "raw pyro core" + anomaly_type = /obj/item/assembly/signaler/anomaly/pyro + icon_state = "rawcore_pyro" + +/obj/item/raw_anomaly_core/flux + name = "raw flux core" + desc = "The raw core of a flux anomaly, faintly crackling with energy." + anomaly_type = /obj/item/assembly/signaler/anomaly/flux + icon_state = "rawcore_flux" + +/obj/item/raw_anomaly_core/random + name = "random raw core" + desc = "You should not see this!" + icon_state = "rawcore_bluespace" + +/obj/item/raw_anomaly_core/random/Initialize(mapload) + . = ..() + var/path = pick(subtypesof(/obj/item/raw_anomaly_core)) + new path(loc) + return INITIALIZE_HINT_QDEL + +/** + * Created the resulting core after being "made" into it. + * + * Arguments: + * * newloc - Where the new core will be created + * * del_self - should we qdel(src) + * * count_towards_limit - should we increment the amount of created cores on SSresearch + */ +/obj/item/raw_anomaly_core/proc/create_core(newloc, del_self = FALSE, count_towards_limit = FALSE) + . = new anomaly_type(newloc) + if(count_towards_limit) + var/existing = SSresearch.created_anomaly_types[anomaly_type] || 0 + SSresearch.created_anomaly_types[anomaly_type] = existing + 1 + if(del_self) + qdel(src) diff --git a/tgstation.dme b/tgstation.dme index 2b31b68243..aeb3ba1d54 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -142,6 +142,7 @@ #include "code\__DEFINES\mapping\maploader.dm" #include "code\__DEFINES\material\worth.dm" #include "code\__DEFINES\mobs\slowdowns.dm" +#include "code\__DEFINES\research\anomalies.dm" #include "code\__DEFINES\research\stock_parts.dm" #include "code\__DEFINES\skills\defines.dm" #include "code\__DEFINES\skills\helpers.dm" @@ -3143,6 +3144,8 @@ #include "code\modules\research\server.dm" #include "code\modules\research\stock_parts.dm" #include "code\modules\research\anomaly\anomaly_core.dm" +#include "code\modules\research\anomaly\explosive_compressor.dm" +#include "code\modules\research\anomaly\raw_anomaly.dm" #include "code\modules\research\designs\AI_module_designs.dm" #include "code\modules\research\designs\autobotter_designs.dm" #include "code\modules\research\designs\autoylathe_designs.dm"