diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm index c2bdd2f18bd..80216c7e1aa 100644 --- a/code/datums/components/crafting/recipes.dm +++ b/code/datums/components/crafting/recipes.dm @@ -1140,10 +1140,18 @@ name = "Elder Atmosian Statue" result = /obj/structure/statue/elder_atmosian time = 6 SECONDS - reqs = list(/obj/item/stack/sheet/mineral/metal_hydrogen = 10, - /obj/item/grenade/gas_crystal/healium_crystal = 1, - /obj/item/grenade/gas_crystal/proto_nitrate_crystal = 1, - /obj/item/grenade/gas_crystal/zauker_crystal = 1 + reqs = list(/obj/item/stack/sheet/mineral/metal_hydrogen = 20, + /obj/item/stack/sheet/mineral/zaukerite = 15, + /obj/item/stack/sheet/iron = 30, + ) + category = CAT_MISC + +/datum/crafting_recipe/elder_atmosian_fireaxe + name = "Elder Axe" + result = /obj/item/fireaxe/elder_atmosian_fireaxe + time = 6 SECONDS + reqs = list(/obj/item/fireaxe/metal_h2_axe = 1, + /obj/item/stack/sheet/mineral/zaukerite = 10, ) category = CAT_MISC diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index 774fab28b28..4cad06fe31c 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -497,3 +497,18 @@ Unless you know what you're doing, only use the first three numbers. They're in beauty_modifier = 0.2 turf_sound_override = FOOTSTEP_WOOD texture_layer_icon_state = "bamboo" + +/datum/material/zaukerite + name = "zaukerite" + desc = "A light absorbing crystal" + color = COLOR_ALMOST_BLACK + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + sheet_type = /obj/item/stack/sheet/mineral/zaukerite + value_per_unit = 0.45 + armor_modifiers = list(MELEE = 0.9, BULLET = 0.9, LASER = 1.75, ENERGY = 1.75, BOMB = 0.5, BIO = 1, RAD = 1.75, FIRE = 0.1, ACID = 1) + beauty_modifier = 0.001 + +/datum/material/zaukerite/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) + victim.apply_damage(30, BURN, BODY_ZONE_HEAD, wound_bonus = 5) + source_item?.reagents?.add_reagent(/datum/reagent/toxin/plasma, source_item.reagents.total_volume*5) + return TRUE diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 37b38b302f5..6caa5a4447a 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -338,6 +338,15 @@ /obj/item/stack/sheet/glass = 10, /obj/item/stack/sheet/plasteel = 5) +/obj/item/circuitboard/machine/crystallizer + name = "Crystallizer (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/binary/crystallizer + req_components = list( + /obj/item/stack/cable_coil = 10, + /obj/item/stack/sheet/glass = 10, + /obj/item/stack/sheet/plasteel = 5) + //Generic /obj/item/circuitboard/machine/circuit_imprinter diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm index 6122f3dccac..87eef81c77e 100644 --- a/code/game/objects/items/fireaxe.dm +++ b/code/game/objects/items/fireaxe.dm @@ -88,3 +88,14 @@ /obj/item/fireaxe/metal_h2_axe/ComponentInitialize() . = ..() AddComponent(/datum/component/two_handed, force_unwielded=5, force_wielded=23, icon_wielded="[base_icon_state]1") + +/obj/item/fireaxe/elder_atmosian_fireaxe + icon_state = "elder_axe0" + base_icon_state = "elder_axe" + name = "eldest fireaxe" + desc = "Upgrade of the metallic hydrogen axe; created by some madman with mysterious powers" + +/obj/item/fireaxe/elder_atmosian_fireaxe/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_unwielded=7, force_wielded=25, icon_wielded="[base_icon_state]1") + AddComponent(/datum/component/lifesteal, 5) diff --git a/code/game/objects/items/grenades/atmos_grenades.dm b/code/game/objects/items/grenades/atmos_grenades.dm index b1dcdbe955b..c2b8e171d8f 100644 --- a/code/game/objects/items/grenades/atmos_grenades.dm +++ b/code/game/objects/items/grenades/atmos_grenades.dm @@ -6,38 +6,6 @@ inhand_icon_state = "flashbang" resistance_flags = FIRE_PROOF - -/obj/item/grenade/gas_crystal/healium_crystal - name = "Healium crystal" - desc = "A crystal made from the Healium gas, it's cold to the touch." - icon_state = "healium_crystal" - ///Amount of stamina damage mobs will take if in range - var/stamina_damage = 30 - ///Range of the grenade that will cool down and affect mobs - var/freeze_range = 4 - ///Amount of gas released if the state is optimal - var/gas_amount = 250 - -/obj/item/grenade/gas_crystal/proto_nitrate_crystal - name = "Proto Nitrate crystal" - desc = "A crystal made from the Proto Nitrate gas, you can see the liquid gases inside." - icon_state = "proto_nitrate_crystal" - ///Range of the grenade air refilling - var/refill_range = 5 - ///Amount of Nitrogen gas released (close to the grenade) - var/n2_gas_amount = 400 - ///Amount of Oxygen gas released (close to the grenade) - var/o2_gas_amount = 100 - -/obj/item/grenade/gas_crystal/zauker_crystal - name = "Zauker crystal" - desc = "A crystal made from the Zauker Gas, you can see the liquid plasma inside." - icon_state = "zauker_crystal" - ex_dev = 1 - ex_heavy = 2 - ex_light = 4 - ex_flame = 2 - /obj/item/grenade/gas_crystal/arm_grenade(mob/user, delayoverride, msg = TRUE, volume = 60) var/turf/turf_loc = get_turf(src) log_grenade(user, turf_loc) //Inbuilt admin procs already handle null users @@ -54,6 +22,17 @@ SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) addtimer(CALLBACK(src, .proc/detonate), isnull(delayoverride)? det_time : delayoverride) +/obj/item/grenade/gas_crystal/healium_crystal + name = "Healium crystal" + desc = "A crystal made from the Healium gas, it's cold to the touch." + icon_state = "healium_crystal" + ///Amount of stamina damage mobs will take if in range + var/stamina_damage = 30 + ///Range of the grenade that will cool down and affect mobs + var/freeze_range = 4 + ///Amount of gas released if the state is optimal + var/gas_amount = 70 + /obj/item/grenade/gas_crystal/healium_crystal/detonate(mob/living/lanced_by) . = ..() update_mob() @@ -70,12 +49,22 @@ floor_loc.MakeSlippery(TURF_WET_PERMAFROST, (5 / distance_from_center) MINUTES) if(floor_loc.air.gases[/datum/gas/plasma]) floor_loc.air.gases[/datum/gas/plasma][MOLES] -= floor_loc.air.gases[/datum/gas/plasma][MOLES] * 0.5 / distance_from_center - floor_loc.air_update_turf(FALSE, FALSE) for(var/mob/living/carbon/live_mob in turf_loc) live_mob.adjustStaminaLoss(stamina_damage / distance_from_center) live_mob.adjust_bodytemperature(-150 / distance_from_center) qdel(src) +/obj/item/grenade/gas_crystal/proto_nitrate_crystal + name = "Proto Nitrate crystal" + desc = "A crystal made from the Proto Nitrate gas, you can see the liquid gases inside." + icon_state = "proto_nitrate_crystal" + ///Range of the grenade air refilling + var/refill_range = 5 + ///Amount of Nitrogen gas released (close to the grenade) + var/n2_gas_amount = 60 + ///Amount of Oxygen gas released (close to the grenade) + var/o2_gas_amount = 30 + /obj/item/grenade/gas_crystal/proto_nitrate_crystal/detonate(mob/living/lanced_by) . = ..() update_mob() @@ -86,10 +75,25 @@ var/distance_from_center = max(get_dist(turf_loc, loc), 1) var/turf/open/floor_loc = turf_loc floor_loc.atmos_spawn_air("n2=[n2_gas_amount / distance_from_center];o2=[o2_gas_amount / distance_from_center];TEMP=273") - floor_loc.air_update_turf(FALSE, FALSE) qdel(src) -/obj/item/grenade/gas_crystal/zauker_crystal/detonate(mob/living/lanced_by) +/obj/item/grenade/gas_crystal/nitrous_oxide_crystal + name = "N2O crystal" + desc = "A crystal made from the N2O gas, you can see the liquid gases inside." + icon_state = "n2o_crystal" + ///Range of the grenade air refilling + var/fill_range = 1 + ///Amount of n2o gas released (close to the grenade) + var/n2o_gas_amount = 10 + +/obj/item/grenade/gas_crystal/nitrous_oxide_crystal/detonate(mob/living/lanced_by) . = ..() update_mob() + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + for(var/turf/turf_loc in view(fill_range, loc)) + if(!isopenturf(turf_loc)) + continue + var/distance_from_center = max(get_dist(turf_loc, loc), 1) + var/turf/open/floor_loc = turf_loc + floor_loc.atmos_spawn_air("n2o=[n2o_gas_amount / distance_from_center];TEMP=273") qdel(src) diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index f6a8f00e1d0..44e961dee96 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -490,3 +490,14 @@ GLOBAL_LIST_INIT(metalhydrogen_recipes, list( /obj/item/stack/sheet/mineral/metal_hydrogen/get_main_recipes() . = ..() . += GLOB.metalhydrogen_recipes + +/obj/item/stack/sheet/mineral/zaukerite + name = "zaukerite" + icon_state = "zaukerite" + inhand_icon_state = "sheet-zaukerite" + singular_name = "zaukerite crystal" + w_class = WEIGHT_CLASS_NORMAL + point_value = 120 + mats_per_unit = list(/datum/material/zaukerite = MINERAL_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/sheet/mineral/zaukerite + material_type = /datum/material/zaukerite diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 3e10add9dc7..c3d08296d89 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -520,45 +520,6 @@ nobiliumsuppression = INFINITY air.temperature = max(((temperature * old_heat_capacity + energy_released) / new_heat_capacity), TCMB) return REACTING -/datum/gas_reaction/metalhydrogen - priority = 20 - name = "Metal Hydrogen formation" - id = "metalhydrogen" - -/datum/gas_reaction/metalhydrogen/init_reqs() - min_requirements = list( - /datum/gas/hydrogen = 100, - /datum/gas/bz = 5, - "TEMP" = METAL_HYDROGEN_MINIMUM_HEAT - ) - -/datum/gas_reaction/metalhydrogen/react(datum/gas_mixture/air, datum/holder) - var/list/cached_gases = air.gases - var/temperature = air.temperature - var/old_heat_capacity = air.heat_capacity() - if(!isturf(holder)) - return NO_REACTION - var/turf/open/location = holder - ///the more heat you use the higher is this factor - var/increase_factor = min((temperature / METAL_HYDROGEN_MINIMUM_HEAT), 5) - ///the more moles you use and the higher the heat, the higher is the efficiency - var/heat_efficency = cached_gases[/datum/gas/hydrogen][MOLES] * 0.01 * increase_factor - var/pressure = air.return_pressure() - var/energy_used = heat_efficency * METAL_HYDROGEN_FORMATION_ENERGY - - if(pressure >= METAL_HYDROGEN_MINIMUM_PRESSURE && temperature >= METAL_HYDROGEN_MINIMUM_HEAT) - cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.01 - if (prob(20 * increase_factor)) - cached_gases[/datum/gas/hydrogen][MOLES] -= heat_efficency * 3.5 - if (prob(100 / increase_factor)) - new /obj/item/stack/sheet/mineral/metal_hydrogen(location) - - if(energy_used > 0) - var/new_heat_capacity = air.heat_capacity() - if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) - air.temperature = max(((temperature * old_heat_capacity - energy_used) / new_heat_capacity), TCMB) - return REACTING - /datum/gas_reaction/freonformation priority = 5 name = "Freon formation" diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm new file mode 100644 index 00000000000..ce143132c85 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/atmos_machines_recipes.dm @@ -0,0 +1,150 @@ +///Global list of recipes for atmospheric machines to use +GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list()) +///Defines for the recipes var +#define ENDOTHERMIC_REACTION "endothermic" +#define EXOTHERMIC_REACTION "exothermic" + +/* + * Global proc to build the gas recipe global list + */ +/proc/gas_recipes_list() + . = list() + for(var/recipe_path in subtypesof(/datum/gas_recipe)) + var/datum/gas_recipe/recipe = new recipe_path() + + .[recipe.id] = recipe + +/datum/gas_recipe + ///Id of the recipe for easy identification in the code + var/id = "" + ///What machine the recipe is for + var/machine_type = "" + ///Displayed name of the recipe + var/name = "" + ///Minimum temperature for the recipe + var/min_temp = TCMB + ///Maximum temperature for the recipe + var/max_temp = INFINITY + ///Type of reaction (either endothermic or exothermic) + var/reaction_type = "" + ///Amount of energy released/consumed by the reaction (always positive) + var/energy_release = 0 + var/dangerous = FALSE + ///Gas required for the recipe to work + var/list/requirements + ///Products made from the recipe + var/list/products + +/datum/gas_recipe/crystallizer + machine_type = "Crystallizer" + +/datum/gas_recipe/crystallizer/metallic_hydrogen + id = "metal_h" + name = "Metallic hydrogen" + min_temp = 50000 + max_temp = 150000 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 250000 + requirements = list(/datum/gas/hydrogen = 60, /datum/gas/bz = 20) + products = list(/obj/item/stack/sheet/mineral/metal_hydrogen = 1) + +/datum/gas_recipe/crystallizer/healium_grenade + id = "healium_g" + name = "Healium crystal" + min_temp = 200 + max_temp = 400 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 100000 + requirements = list(/datum/gas/healium = 50, /datum/gas/freon = 100, /datum/gas/plasma = 10) + products = list(/obj/item/grenade/gas_crystal/healium_crystal = 1) + +/datum/gas_recipe/crystallizer/proto_nitrate_grenade + id = "proto_nitrate_g" + name = "Proto nitrate crystal" + min_temp = 200 + max_temp = 400 + reaction_type = EXOTHERMIC_REACTION + energy_release = 150000 + requirements = list(/datum/gas/proto_nitrate = 50, /datum/gas/nitrogen = 80, /datum/gas/oxygen = 80) + products = list(/obj/item/grenade/gas_crystal/proto_nitrate_crystal = 1) + +/datum/gas_recipe/crystallizer/hot_ice + id = "hot_ice" + name = "Hot ice" + min_temp = 15 + max_temp = 35 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 300000 + requirements = list(/datum/gas/freon = 50, /datum/gas/plasma = 40, /datum/gas/oxygen = 30) + products = list(/obj/item/stack/sheet/hot_ice = 1) + +/datum/gas_recipe/crystallizer/ammonia_crystal + id = "ammonia_crystal" + name = "Ammonia crystal" + min_temp = 200 + max_temp = 240 + reaction_type = EXOTHERMIC_REACTION + energy_release = 15000 + requirements = list(/datum/gas/hydrogen = 50, /datum/gas/nitrogen = 40) + products = list(/obj/item/stack/ammonia_crystals = 2) + +/datum/gas_recipe/crystallizer/shard + id = "crystal_shard" + name = "Supermatter crystal shard" + min_temp = TCMB + max_temp = 5 + reaction_type = EXOTHERMIC_REACTION + energy_release = 1500000 + dangerous = TRUE + requirements = list(/datum/gas/hypernoblium = 1500, /datum/gas/antinoblium = 1500, /datum/gas/bz = 2000, /datum/gas/plasma = 5000, /datum/gas/oxygen = 4500) + products = list(/obj/machinery/power/supermatter_crystal/shard = 1) + +/datum/gas_recipe/crystallizer/n2o_crystal + id = "n2o_crystal" + name = "Nitrous oxide crystal" + min_temp = 50 + max_temp = 350 + reaction_type = EXOTHERMIC_REACTION + energy_release = 350000 + requirements = list(/datum/gas/nitrous_oxide = 100, /datum/gas/bz = 5) + products = list(/obj/item/grenade/gas_crystal/nitrous_oxide_crystal = 1) + +/datum/gas_recipe/crystallizer/diamond + id = "diamond" + name = "Diamond" + min_temp = 10000 + max_temp = 30000 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 650000 + requirements = list(/datum/gas/carbon_dioxide = 10000) + products = list(/obj/item/stack/sheet/mineral/diamond = 1) + +/datum/gas_recipe/crystallizer/plasma_sheet + id = "plasma_sheet" + name = "Plasma sheet" + min_temp = 100 + max_temp = 140 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 15000 + requirements = list(/datum/gas/plasma = 25) + products = list(/obj/item/stack/sheet/mineral/plasma = 1) + +/datum/gas_recipe/crystallizer/crystal_cell + id = "crystal_cell" + name = "Crystal Cell" + min_temp = 50 + max_temp = 90 + reaction_type = ENDOTHERMIC_REACTION + energy_release = 80000 + requirements = list(/datum/gas/plasma = 800, /datum/gas/helium = 100, /datum/gas/bz = 50) + products = list(/obj/item/stock_parts/cell/crystal_cell = 1) + +/datum/gas_recipe/crystallizer/zaukerite + id = "zaukerite" + name = "Zaukerite sheet" + min_temp = 5 + max_temp = 20 + reaction_type = EXOTHERMIC_REACTION + energy_release = 29000 + requirements = list(/datum/gas/antinoblium = 100, /datum/gas/zauker = 500, /datum/gas/bz = 75) + products = list(/obj/item/stack/sheet/mineral/zaukerite = 2) diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm new file mode 100644 index 00000000000..c59a2d6bd42 --- /dev/null +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm @@ -0,0 +1,333 @@ +#define MIN_PROGRESS_AMOUNT 3 +#define MIN_DEVIATION_RATE 0.90 +#define MAX_DEVIATION_RATE 1.1 +#define HIGH_CONDUCTIVITY_RATIO 0.95 + +/obj/machinery/atmospherics/components/binary/crystallizer + icon = 'icons/obj/atmospherics/components/machines.dmi' + icon_state = "crystallizer-off" + name = "crystallizer" + desc = "Used to crystallize or solidify gases." + layer = ABOVE_MOB_LAYER + density = TRUE + max_integrity = 300 + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 80, ACID = 30) + circuit = /obj/item/circuitboard/machine/crystallizer + pipe_flags = PIPING_ONE_PER_TURF + + ///Base icon state for the machine to be used in update_icon() + var/base_icon = "crystallizer" + ///Internal Gas mix used for processing the gases that have been put in + var/datum/gas_mixture/internal + ///Var that controls how much gas gets injected in moles/S + var/gas_input = 0 + ///Saves the progress during the processing of the items + var/progress_bar = 0 + ///Stores the amount of lost quality + var/quality_loss = 0 + ///Stores the recipe selected by the user in the GUI + var/datum/gas_recipe/selected_recipe = null + ///Stores the total amount of moles needed for the current recipe + var/total_recipe_moles = 0 + +/obj/machinery/atmospherics/components/binary/crystallizer/attackby(obj/item/I, mob/user, params) + if(!on) + if(default_deconstruction_screwdriver(user, "[base_icon]-open", "[base_icon]-off", I)) + return + if(default_change_direction_wrench(user, I)) + return + if(default_deconstruction_crowbar(I)) + return + return ..() + +/obj/machinery/atmospherics/components/binary/crystallizer/default_change_direction_wrench(mob/user, obj/item/I) + . = ..() + if(!.) + return FALSE + SetInitDirections() + var/obj/machinery/atmospherics/node1 = nodes[1] + var/obj/machinery/atmospherics/node2 = nodes[2] + if(node1) + if(src in node1.nodes) //Only if it's actually connected. On-pipe version would is one-sided. + node1.disconnect(src) + nodes[1] = null + if(node2) + if(src in node2.nodes) //Only if it's actually connected. On-pipe version would is one-sided. + node2.disconnect(src) + nodes[2] = null + + if(parents[1]) + nullifyPipenet(parents[1]) + if(parents[2]) + nullifyPipenet(parents[2]) + + atmosinit() + node1 = nodes[1] + if(node1) + node1.atmosinit() + node1.addMember(src) + node2 = nodes[2] + if(node2) + node2.atmosinit() + node2.addMember(src) + SSair.add_to_rebuild_queue(src) + return TRUE + +/obj/machinery/atmospherics/components/binary/crystallizer/update_overlays() + . = ..() + cut_overlays() + add_overlay(getpipeimage(icon, "pipe", dir, COLOR_LIME, piping_layer)) + add_overlay(getpipeimage(icon, "pipe", turn(dir, 180), COLOR_MOSTLY_PURE_RED, piping_layer)) + +/obj/machinery/atmospherics/components/binary/crystallizer/update_icon_state() + . = ..() + if(panel_open) + icon_state = "[base_icon]-open" + else if(on && is_operational) + icon_state = "[base_icon]-on" + else + icon_state = "[base_icon]-off" + +/obj/machinery/atmospherics/components/binary/crystallizer/attackby_secondary(mob/user) + if(!can_interact(user)) + return + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) + update_icon() + +///Checks if the gases in the input are the ones needed by the recipe +/obj/machinery/atmospherics/components/binary/crystallizer/proc/check_gas_requirements() + var/datum/gas_mixture/contents = airs[2] + for(var/gas_type in selected_recipe.requirements) + if(!contents.gases[gas_type] || !contents.gases[gas_type][MOLES]) + return FALSE + return TRUE + +///Checks if the reaction temperature is inside the range of temperature + a little deviation +/obj/machinery/atmospherics/components/binary/crystallizer/proc/check_temp_requirements() + var/datum/gas_mixture/contents = airs[2] + if(contents.temperature >= selected_recipe.min_temp * MIN_DEVIATION_RATE && contents.temperature <= selected_recipe.max_temp * MAX_DEVIATION_RATE) + return TRUE + return FALSE + +///Injects the gases from the input inside the internal gasmix, the amount is dependant on the gas_input var +/obj/machinery/atmospherics/components/binary/crystallizer/proc/inject_gases(delta_time) + var/datum/gas_mixture/contents = airs[2] + for(var/gas_type in selected_recipe.requirements) + internal.merge(contents.remove_specific(gas_type, contents.gases[gas_type][MOLES] * (gas_input * delta_time))) + +///Checks if the gases required are all inside +/obj/machinery/atmospherics/components/binary/crystallizer/proc/internal_check() + var/gas_check = 0 + for(var/gas_type in selected_recipe.requirements) + if(internal.gases[gas_type][MOLES] >= selected_recipe.requirements[gas_type]) + gas_check++ + if(gas_check == selected_recipe.requirements.len) + return TRUE + return FALSE + +///Calculation for the heat of the various gas mixes and controls the quality of the item +/obj/machinery/atmospherics/components/binary/crystallizer/proc/heat_calculations() + var/datum/gas_mixture/cooling_port = airs[1] + if(cooling_port.total_moles() * 0.25 > MINIMUM_MOLE_COUNT) + var/datum/gas_mixture/cooling_remove = cooling_port.remove_ratio(0.25) + + if(internal.total_moles() > 0) + var/coolant_temperature_delta = cooling_remove.temperature - internal.temperature + var/cooling_heat_capacity = cooling_remove.heat_capacity() + var/internal_heat_capacity = internal.heat_capacity() + var/cooling_heat_amount = HIGH_CONDUCTIVITY_RATIO * coolant_temperature_delta * (cooling_heat_capacity * internal_heat_capacity / (cooling_heat_capacity + internal_heat_capacity)) + cooling_remove.temperature = max(cooling_remove.temperature - cooling_heat_amount / cooling_heat_capacity, TCMB) + internal.temperature = max(internal.temperature + cooling_heat_amount / internal_heat_capacity, TCMB) + cooling_port.merge(cooling_remove) + + if( (internal.temperature >= (selected_recipe.min_temp * MIN_DEVIATION_RATE) && internal.temperature <= selected_recipe.min_temp) || \ + (internal.temperature >= selected_recipe.max_temp && internal.temperature <= (selected_recipe.max_temp * MAX_DEVIATION_RATE))) + quality_loss = min(quality_loss + 1.5, 100) + + var/median_temperature = (selected_recipe.max_temp - selected_recipe.min_temp) * 0.5 + if(internal.temperature >= (median_temperature * MIN_DEVIATION_RATE) && internal.temperature <= (median_temperature * MAX_DEVIATION_RATE)) + quality_loss = max(quality_loss - 5.5, 100) + + if(selected_recipe.reaction_type == "endothermic") + internal.temperature = max(internal.temperature - (selected_recipe.energy_release / internal.heat_capacity()), TCMB) + else if(selected_recipe.reaction_type == "exothermic") + internal.temperature = max(internal.temperature + (selected_recipe.energy_release / internal.heat_capacity()), TCMB) + +///Calculate the total moles needed for the recipe +/obj/machinery/atmospherics/components/binary/crystallizer/proc/moles_calculations() + var/amounts = 0 + for(var/gas_type in selected_recipe.requirements) + amounts += selected_recipe.requirements[gas_type] + total_recipe_moles = amounts + +///Removes the gases from the internal gasmix when the recipe is changed +/obj/machinery/atmospherics/components/binary/crystallizer/proc/dump_gases() + var/datum/gas_mixture/remove = internal.remove(internal.total_moles()) + airs[2].merge(remove) + internal.garbage_collect() + +/obj/machinery/atmospherics/components/binary/crystallizer/process_atmos(delta_time) + if(!on || !is_operational || selected_recipe == null) + return + + if(!check_gas_requirements()) + return + + inject_gases(delta_time) + + if(!internal.total_moles()) + update_parents() + return + + if(internal_check()) + if(check_temp_requirements()) + heat_calculations() + progress_bar = min(progress_bar + (MIN_PROGRESS_AMOUNT * 5 / (round(log(10, total_recipe_moles * 0.1), 0.01))), 100) + else + quality_loss = min(quality_loss + 0.5, 100) + progress_bar = max(progress_bar - 1, 0) + if(progress_bar != 100) + update_parents() + return + progress_bar = 0 + + for(var/gas_type in selected_recipe.requirements) + var/amount_consumed = selected_recipe.requirements[gas_type] + quality_loss * 5 + if(internal.gases[gas_type][MOLES] < amount_consumed) + quality_loss = min(quality_loss + 10, 100) + internal.remove_specific(gas_type, amount_consumed) + + var/total_quality = clamp(50 - quality_loss, 0, 100) + var/quality_control + switch(total_quality) + if(100) + quality_control = "Masterwork" + if(95 to 99) + quality_control = "Supreme" + if(75 to 94) + quality_control = "Good" + if(65 to 74) + quality_control = "Decent" + if(55 to 64) + quality_control = "Average" + if(35 to 54) + quality_control = "Ok" + if(15 to 34) + quality_control = "Poor" + if(5 to 14) + quality_control = "Ugly" + if(1 to 4) + quality_control = "Cracked" + if(0) + quality_control = "Oh God why" + + for(var/path in selected_recipe.products) + var/amount_produced = selected_recipe.products[path] + for(var/i in 1 to amount_produced) + var/obj/creation = new path(get_step(src, SOUTH)) + creation.name = "[quality_control] [creation.name]" + if(selected_recipe.dangerous) + investigate_log("has been created in the crystallizer.", INVESTIGATE_SUPERMATTER) + message_admins("[src] has been created in the crystallizer [ADMIN_JMP(src)].") + + + quality_loss = 0 + update_parents() + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Crystallizer", name) + ui.open() + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_static_data() + var/data = list() + data["selected_recipes"] = list(list("name" = "Nothing", "id" = "")) + for(var/path in GLOB.gas_recipe_meta) + var/datum/gas_recipe/recipe = GLOB.gas_recipe_meta[path] + if(recipe.machine_type != "Crystallizer") + continue + data["selected_recipes"] += list(list("name" = recipe.name, "id" = recipe.id)) + return data + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_data() + var/data = list() + data["on"] = on + + if(selected_recipe) + data["selected_recipe"] = selected_recipe.id + else + data["selected_recipe"] = null + + var/list/internal_gas_data = list() + if(internal.total_moles()) + for(var/gasid in internal.gases) + internal_gas_data.Add(list(list( + "name"= internal.gases[gasid][GAS_META][META_GAS_NAME], + "amount" = round(internal.gases[gasid][MOLES], 0.01), + ))) + else + for(var/gasid in internal.gases) + internal_gas_data.Add(list(list( + "name"= internal.gases[gasid][GAS_META][META_GAS_NAME], + "amount" = 0, + ))) + data["internal_gas_data"] = internal_gas_data + + var/list/requirements + if(!selected_recipe) + requirements = list("Select a recipe to see the requirements") + else + requirements = list("To create [selected_recipe.name] you will need:") + for(var/gas_type in selected_recipe.requirements) + var/datum/gas/gas_required = gas_type + var/amount_consumed = selected_recipe.requirements[gas_type] + requirements += "-[amount_consumed] moles of [initial(gas_required.name)]" + requirements += "In a temperature range between [selected_recipe.min_temp] K and [selected_recipe.max_temp] K" + requirements += "The crystallization reaction will be [selected_recipe.reaction_type]" + data["requirements"] = requirements.Join("\n") + + var/temperature + if(internal.total_moles()) + temperature = internal.temperature + else + temperature = 0 + data["internal_temperature"] = temperature + data["progress_bar"] = progress_bar + data["gas_input"] = gas_input + return data + +/obj/machinery/atmospherics/components/binary/crystallizer/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("power") + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("recipe") + selected_recipe = null + var/recipe_name = "nothing" + var/datum/gas_recipe/recipe = GLOB.gas_recipe_meta[params["mode"]] + if(internal.total_moles()) + dump_gases() + quality_loss = 0 + progress_bar = 0 + if(selected_recipe) + selected_recipe = recipe + recipe_name = recipe.name + update_parents() //prevent the machine from stopping because of the recipe change and the pipenet not updating + moles_calculations() + investigate_log("was set to recipe [recipe_name] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("gas_input") + var/_gas_input = params["gas_input"] + gas_input = clamp(_gas_input, 0, 50) + update_icon() + +#undef MIN_PROGRESS_AMOUNT +#undef MIN_DEVIATION_RATE +#undef MAX_DEVIATION_RATE +#undef HIGH_CONDUCTIVITY_RATIO diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index d5c6e62ac69..a3ddc6c0dd9 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -385,6 +385,20 @@ if(!A.lightswitch || !A.light_power) charge = 0 //For naturally depowered areas, we start with no power +/obj/item/stock_parts/cell/crystal_cell + name = "crystal power cell" + desc = "A very high power cell made from crystallized plasma" + icon_state = "crystal_cell" + maxcharge = 50000 + chargerate = 0 + custom_materials = null + grind_results = null + rating = 5 + +/obj/item/stock_parts/cell/crystal_cell/Initialize() + . = ..() + charge = 50000 + /obj/item/stock_parts/cell/inducer_supply maxcharge = 5000 charge = 5000 diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 293f9c2ccad..04417982ada 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -720,3 +720,11 @@ build_path = /obj/item/circuitboard/machine/HFR_interface category = list ("Engineering Machinery") departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/board/crystallizer + name = "Machine Design (Crystallizer)" + desc = "The circuit board for a crystallizer." + id = "crystallizer" + build_path = /obj/item/circuitboard/machine/crystallizer + category = list ("Engineering Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 120c120c678..f783088bbf9 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -149,7 +149,7 @@ design_ids = list("solarcontrol", "recharger", "powermonitor", "rped", "pacman", "adv_capacitor", "adv_scanning", "w-recycler" , "emitter", "high_cell", "adv_matter_bin", "scanner_gate", "atmosalerts", "atmos_control", "recycler", "autolathe", "high_micro_laser", "nano_mani", "mesons", "welding_goggles", "thermomachine", "rad_collector", "tesla_coil", "grounding_rod", "apc_control", "cell_charger", "power control", "airlock_board", "firelock_board", "airalarm_electronics", "firealarm_electronics", "cell_charger", "stack_console", "stack_machine", - "oxygen_tank", "plasma_tank", "emergency_oxygen", "emergency_oxygen_engi", "plasmaman_tank_belt", "electrolyzer", "adv_electrolite", "pneumatic_seal") + "oxygen_tank", "plasma_tank", "emergency_oxygen", "emergency_oxygen_engi", "plasmaman_tank_belt", "electrolyzer", "adv_electrolite", "pneumatic_seal", "crystallizer") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500) discount_experiments = list(/datum/experiment/scanning/random/material/easy = 7500) diff --git a/icons/mob/inhands/misc/sheets_lefthand.dmi b/icons/mob/inhands/misc/sheets_lefthand.dmi index 8a543121770..7e8eaf9a12f 100644 Binary files a/icons/mob/inhands/misc/sheets_lefthand.dmi and b/icons/mob/inhands/misc/sheets_lefthand.dmi differ diff --git a/icons/mob/inhands/misc/sheets_righthand.dmi b/icons/mob/inhands/misc/sheets_righthand.dmi index 24256299f2e..433a2ea7fb1 100644 Binary files a/icons/mob/inhands/misc/sheets_righthand.dmi and b/icons/mob/inhands/misc/sheets_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/axes_lefthand.dmi b/icons/mob/inhands/weapons/axes_lefthand.dmi index 8f8831d4d4a..24ee8f3e153 100644 Binary files a/icons/mob/inhands/weapons/axes_lefthand.dmi and b/icons/mob/inhands/weapons/axes_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/axes_righthand.dmi b/icons/mob/inhands/weapons/axes_righthand.dmi index eb38a774fa3..cc4d53bdfba 100644 Binary files a/icons/mob/inhands/weapons/axes_righthand.dmi and b/icons/mob/inhands/weapons/axes_righthand.dmi differ diff --git a/icons/obj/atmospherics/components/machines.dmi b/icons/obj/atmospherics/components/machines.dmi new file mode 100644 index 00000000000..baf01a3e9cb Binary files /dev/null and b/icons/obj/atmospherics/components/machines.dmi differ diff --git a/icons/obj/grenade.dmi b/icons/obj/grenade.dmi index 2180a1644c7..cab93d34235 100644 Binary files a/icons/obj/grenade.dmi and b/icons/obj/grenade.dmi differ diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi index 72776578aa7..4fd32b6999a 100644 Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ diff --git a/icons/obj/power.dmi b/icons/obj/power.dmi index 33fbdacd91d..015d0c84e6d 100644 Binary files a/icons/obj/power.dmi and b/icons/obj/power.dmi differ diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi index 1fb26cfbb12..4e297c7fc8d 100644 Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 455fde9be10..9259da105d2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1776,6 +1776,8 @@ #include "code\modules\atmospherics\machinery\components\fusion\hfr_main_processes.dm" #include "code\modules\atmospherics\machinery\components\fusion\hfr_parts.dm" #include "code\modules\atmospherics\machinery\components\fusion\hfr_procs.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\atmos_machines_recipes.dm" +#include "code\modules\atmospherics\machinery\components\gas_recipe_machines\crystallizer.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\filter.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\mixer.dm" #include "code\modules\atmospherics\machinery\components\trinary_devices\trinary_devices.dm" diff --git a/tgui/packages/tgui/interfaces/Crystallizer.js b/tgui/packages/tgui/interfaces/Crystallizer.js new file mode 100644 index 00000000000..2a7d48a0644 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Crystallizer.js @@ -0,0 +1,97 @@ +import { useBackend } from '../backend'; +import { AnimatedNumber, Button, Flex, Input, LabeledList, ProgressBar, Section, Table, NumberInput, Box } from '../components'; +import { getGasColor, getGasLabel } from '../constants'; +import { toFixed } from 'common/math'; +import { Window } from '../layouts'; + +export const Crystallizer = (props, context) => { + const { act, data } = useBackend(context); + const recipeTypes = data.recipe_types || []; + const gasTypes = data.internal_gas_data || []; + const { + requirements, + internal_temperature, + progress_bar, + gas_input, + selected_recipe, + } = data; + return ( + + +
+ + +
+
+ + + + + + + {requirements} + + + +
+
+ + {gasTypes.map(gas => ( + + + {toFixed(gas.amount, 2) + ' moles'} + + + ))} + +
+
+
+ ); +};