diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index b9b478fc0b4..b03d49fa14d 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -13680,7 +13680,6 @@ /obj/effect/turf_decal/tile/neutral{ dir = 4 }, -/obj/machinery/atmospherics/pipe/smart/manifold4w/general/visible, /obj/machinery/airalarm/directional/east, /obj/structure/cable, /turf/open/floor/iron, @@ -16441,7 +16440,6 @@ /obj/effect/turf_decal/tile/neutral{ dir = 4 }, -/obj/machinery/atmospherics/pipe/smart/manifold4w/general/visible, /turf/open/floor/iron, /area/science/mixing/launch) "cKP" = ( @@ -65648,7 +65646,7 @@ /turf/open/floor/iron/white/side, /area/service/kitchen/abandoned) "mHA" = ( -/obj/machinery/research/explosive_compressor, +/obj/machinery/research/anomaly_refinery, /obj/effect/turf_decal/bot, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/iron, @@ -78578,7 +78576,9 @@ /area/ai_monitored/command/storage/eva) "pUU" = ( /obj/effect/turf_decal/stripes/line, -/obj/machinery/atmospherics/components/unary/portables_connector/visible, +/obj/machinery/atmospherics/components/binary/tank_compressor{ + dir = 4 + }, /turf/open/floor/iron, /area/science/mixing/launch) "pVL" = ( @@ -81687,11 +81687,23 @@ "qOX" = ( /obj/structure/table, /obj/effect/decal/cleanable/dirt, -/obj/item/clothing/gloves/color/latex, -/obj/item/clothing/glasses/science, -/obj/item/clothing/glasses/science, /obj/effect/turf_decal/bot, /obj/machinery/light/directional/south, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, /turf/open/floor/iron, /area/science/mixing/launch) "qPa" = ( @@ -102359,11 +102371,13 @@ /turf/open/floor/wood, /area/commons/dorms) "vZS" = ( +/obj/machinery/newscaster/directional/north, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 8 + }, /obj/effect/turf_decal/stripes/line{ dir = 6 }, -/obj/machinery/atmospherics/components/unary/portables_connector/visible, -/obj/machinery/newscaster/directional/north, /obj/machinery/light_switch/directional/east{ pixel_y = 26 }, @@ -104585,7 +104599,7 @@ /turf/open/floor/iron, /area/science/lobby) "wCz" = ( -/obj/machinery/doppler_array/research/science{ +/obj/machinery/doppler_array{ dir = 8 }, /obj/effect/turf_decal/stripes/line{ @@ -104651,9 +104665,6 @@ /obj/effect/turf_decal/tile/neutral{ dir = 4 }, -/obj/machinery/atmospherics/components/trinary/filter{ - dir = 8 - }, /turf/open/floor/iron, /area/science/mixing/launch) "wCU" = ( @@ -108567,7 +108578,9 @@ /obj/effect/turf_decal/stripes/line{ dir = 10 }, -/obj/machinery/atmospherics/components/unary/portables_connector/visible, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 4 + }, /turf/open/floor/iron, /area/science/mixing/launch) "xHF" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index a66093e49a3..7268f54f23a 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -5496,9 +5496,9 @@ /turf/open/floor/iron, /area/hallway/primary/central) "aXj" = ( -/obj/machinery/portable_atmospherics/canister/oxygen, /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/plasma, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "aXp" = ( @@ -9392,6 +9392,7 @@ /obj/structure/sign/warning/fire{ pixel_x = 32 }, +/obj/machinery/portable_atmospherics/canister, /turf/open/floor/iron, /area/science/mixing) "cjS" = ( @@ -16472,9 +16473,9 @@ /turf/open/floor/iron, /area/cargo/sorting) "fvo" = ( -/obj/machinery/portable_atmospherics/canister/tier_1, /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/nitrogen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "fvu" = ( @@ -17656,10 +17657,10 @@ /turf/open/floor/iron/freezer, /area/commons/toilet/locker) "fWL" = ( -/obj/machinery/portable_atmospherics/canister/oxygen, /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/plasma, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "fWQ" = ( @@ -20356,9 +20357,23 @@ network = list("ss13","rd") }, /obj/structure/table, -/obj/item/clothing/glasses/science, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/north, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, /turf/open/floor/iron, /area/science/mixing/launch) "hgT" = ( @@ -21249,10 +21264,10 @@ /turf/closed/wall, /area/engineering/atmos) "hFn" = ( -/obj/machinery/atmospherics/components/trinary/filter/flipped{ +/obj/machinery/light/directional/north, +/obj/machinery/atmospherics/components/binary/tank_compressor{ dir = 4 }, -/obj/machinery/light/directional/north, /turf/open/floor/iron, /area/science/mixing) "hFq" = ( @@ -23841,10 +23856,10 @@ /turf/open/floor/iron/white, /area/science/mixing) "iSF" = ( -/obj/machinery/portable_atmospherics/canister/plasma, /obj/effect/turf_decal/bot, /obj/structure/extinguisher_cabinet/directional/west, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/oxygen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "iSQ" = ( @@ -25615,6 +25630,7 @@ dir = 8 }, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/nitrogen, /turf/open/floor/iron, /area/science/mixing) "jKR" = ( @@ -27510,10 +27526,10 @@ /turf/open/floor/iron/dark, /area/command/heads_quarters/cmo) "kIF" = ( -/obj/machinery/portable_atmospherics/canister/oxygen, /obj/effect/turf_decal/bot, /obj/machinery/firealarm/directional/west, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/plasma, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "kIT" = ( @@ -27631,7 +27647,6 @@ /turf/open/floor/iron/white, /area/science/mixing) "kMd" = ( -/obj/machinery/portable_atmospherics/canister/tier_1, /obj/effect/turf_decal/stripes/line, /turf/open/floor/iron, /area/science/mixing) @@ -29173,6 +29188,7 @@ dir = 10 }, /obj/effect/turf_decal/bot, +/obj/machinery/portable_atmospherics/canister, /turf/open/floor/iron, /area/science/mixing) "lFx" = ( @@ -41807,7 +41823,7 @@ /turf/open/floor/glass/reinforced, /area/engineering/atmos) "rXL" = ( -/obj/machinery/research/explosive_compressor, +/obj/machinery/research/anomaly_refinery, /obj/effect/turf_decal/bot, /obj/item/radio/intercom/directional/north, /turf/open/floor/iron, @@ -45149,10 +45165,10 @@ /turf/open/floor/iron, /area/command/teleporter) "tDW" = ( -/obj/machinery/portable_atmospherics/canister/plasma, /obj/effect/turf_decal/bot, /obj/machinery/light/directional/south, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/oxygen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "tEd" = ( @@ -45169,8 +45185,8 @@ "tEi" = ( /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, -/obj/machinery/portable_atmospherics/canister/tier_1, /obj/structure/extinguisher_cabinet/directional/east, +/obj/machinery/portable_atmospherics/canister/nitrogen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "tEm" = ( @@ -50789,9 +50805,9 @@ /turf/closed/wall/r_wall, /area/command/gateway) "wnu" = ( -/obj/machinery/portable_atmospherics/canister/plasma, /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/oxygen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "wnP" = ( @@ -50844,13 +50860,13 @@ }, /area/service/chapel) "woB" = ( -/obj/machinery/portable_atmospherics/canister/plasma, /obj/effect/turf_decal/bot, /obj/machinery/camera/autoname/directional/south{ c_tag = "Gas Storage"; network = list("ss13","rd") }, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/oxygen, /turf/open/floor/iron/dark/textured_large, /area/science/storage) "woD" = ( @@ -50969,7 +50985,7 @@ /turf/open/floor/iron, /area/hallway/primary/central) "wrl" = ( -/obj/machinery/doppler_array/research/science{ +/obj/machinery/doppler_array{ dir = 4 }, /obj/effect/turf_decal/bot, @@ -104372,7 +104388,7 @@ tKu kMq bEC hFn -mpU +kMd iqv jJE bEs diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 0c9e1f90acf..a9e42fecf51 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -3539,6 +3539,22 @@ /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/cable, /obj/effect/decal/cleanable/dirt, +/obj/structure/table, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, /turf/open/floor/iron/dark, /area/science/mixing/launch) "aqD" = ( @@ -6123,9 +6139,6 @@ /turf/open/floor/plating, /area/hallway/primary/fore) "aEG" = ( -/obj/machinery/atmospherics/components/unary/portables_connector/visible{ - dir = 1 - }, /obj/effect/turf_decal/tile/neutral{ dir = 4 }, @@ -10822,13 +10835,13 @@ /turf/open/floor/iron/dark, /area/science/robotics/lab) "bbS" = ( -/obj/machinery/atmospherics/components/trinary/mixer/flipped{ - dir = 1 - }, /obj/effect/turf_decal/tile/neutral{ dir = 4 }, /obj/effect/turf_decal/tile/neutral, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 4 + }, /turf/open/floor/iron/dark, /area/science/mixing) "bbU" = ( @@ -10917,6 +10930,7 @@ dir = 1 }, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister, /turf/open/floor/iron/dark, /area/science/mixing/chamber) "bch" = ( @@ -11081,6 +11095,7 @@ dir = 8 }, /obj/effect/decal/cleanable/dirt, +/obj/machinery/atmospherics/components/unary/portables_connector/visible, /turf/open/floor/iron/dark, /area/science/mixing) "bcK" = ( @@ -15447,6 +15462,7 @@ dir = 4; pixel_x = 25 }, +/obj/machinery/portable_atmospherics/canister, /turf/open/floor/iron/dark, /area/science/mixing/chamber) "bHI" = ( @@ -26868,8 +26884,8 @@ /obj/effect/turf_decal/tile/neutral{ dir = 1 }, -/obj/machinery/computer/atmos_control/ordnancemix{ - dir = 8 +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 1 }, /turf/open/floor/iron/dark, /area/science/mixing) @@ -32585,13 +32601,13 @@ /turf/open/floor/iron/dark, /area/command/heads_quarters/ce) "fyH" = ( -/obj/machinery/portable_atmospherics/scrubber/huge, /obj/effect/turf_decal/delivery, /obj/effect/turf_decal/tile/neutral, /obj/effect/turf_decal/tile/neutral{ dir = 8 }, /obj/structure/extinguisher_cabinet/directional/east, +/obj/machinery/portable_atmospherics/canister/nitrogen, /turf/open/floor/iron/dark, /area/science/storage) "fyV" = ( @@ -44127,14 +44143,12 @@ }, /area/maintenance/fore) "jLK" = ( -/obj/machinery/doppler_array/research/science{ - dir = 4 - }, /obj/item/radio/intercom/directional/east, /obj/effect/turf_decal/delivery, /obj/effect/turf_decal/tile/neutral{ dir = 8 }, +/obj/machinery/research/anomaly_refinery, /turf/open/floor/iron/dark, /area/science/mixing/launch) "jLU" = ( @@ -51322,17 +51336,6 @@ /turf/open/floor/engine/vacuum, /area/engineering/atmos) "mjh" = ( -/obj/structure/chair{ - dir = 4 - }, -/obj/machinery/computer/security/telescreen{ - desc = "Used for watching the test chamber."; - dir = 8; - layer = 4; - name = "Test Chamber Telescreen"; - network = list("ordnance"); - pixel_x = 30 - }, /obj/effect/turf_decal/tile/neutral{ dir = 8 }, @@ -51343,6 +51346,10 @@ /obj/machinery/computer/pod/old/mass_driver_controller/ordnancedriver/longrange{ pixel_y = -26 }, +/obj/machinery/doppler_array{ + dir = 4 + }, +/obj/effect/turf_decal/delivery, /turf/open/floor/iron/dark, /area/science/mixing/launch) "mjr" = ( @@ -59354,7 +59361,6 @@ /obj/effect/turf_decal/tile/purple{ dir = 8 }, -/obj/machinery/research/explosive_compressor, /turf/open/floor/iron/showroomfloor, /area/science/mixing/launch) "pgJ" = ( @@ -67425,9 +67431,6 @@ /turf/open/floor/iron/dark, /area/service/library) "rZg" = ( -/obj/machinery/atmospherics/components/unary/portables_connector/visible{ - dir = 8 - }, /obj/effect/turf_decal/tile/neutral{ dir = 8 }, @@ -67439,6 +67442,7 @@ name = "science camera"; network = list("ss13","rd") }, +/obj/machinery/atmospherics/components/trinary/filter/flipped, /turf/open/floor/iron/dark, /area/science/mixing) "rZr" = ( @@ -70713,13 +70717,14 @@ /turf/open/floor/engine, /area/ai_monitored/turret_protected/ai) "tlV" = ( -/obj/machinery/atmospherics/components/unary/portables_connector/visible, /obj/effect/turf_decal/box/corners{ dir = 1 }, /obj/effect/turf_decal/tile/neutral, /obj/item/radio/intercom/directional/north, -/obj/machinery/portable_atmospherics/canister, +/obj/machinery/portable_atmospherics/scrubber{ + name = "scrubber ducky" + }, /turf/open/floor/iron/dark, /area/science/mixing) "tmg" = ( @@ -77171,6 +77176,9 @@ dir = 1 }, /obj/structure/extinguisher_cabinet/directional/east, +/obj/machinery/computer/atmos_control/ordnancemix{ + dir = 8 + }, /turf/open/floor/iron/dark, /area/science/mixing) "vHK" = ( @@ -81746,6 +81754,7 @@ }, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/east, +/obj/machinery/atmospherics/components/binary/tank_compressor, /turf/open/floor/iron/dark, /area/science/mixing) "xrO" = ( @@ -83601,11 +83610,9 @@ /obj/effect/turf_decal/tile/neutral{ dir = 1 }, -/obj/machinery/portable_atmospherics/scrubber{ - name = "scrubber ducky" - }, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/north, +/obj/machinery/portable_atmospherics/canister/nitrogen, /turf/open/floor/iron/dark, /area/science/storage) "xZv" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 8ea929f69c0..6c7c478b053 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -5168,6 +5168,7 @@ /obj/machinery/atmospherics/components/unary/portables_connector/visible, /obj/effect/turf_decal/stripes/line, /obj/machinery/light/small/directional/west, +/obj/machinery/portable_atmospherics/canister, /turf/open/floor/iron, /area/science/mixing) "aWK" = ( @@ -14158,8 +14159,8 @@ /obj/effect/turf_decal/stripes/white/line{ dir = 1 }, -/obj/machinery/holopad, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/atmospherics/components/unary/portables_connector/visible, /turf/open/floor/iron, /area/science/mixing) "djf" = ( @@ -15909,8 +15910,23 @@ /turf/open/floor/iron/dark, /area/engineering/atmos) "dMq" = ( -/obj/structure/filingcabinet/chestdrawer, /obj/machinery/light_switch/directional/west, +/obj/structure/table, +/obj/item/transfer_valve{ + pixel_x = 5 + }, +/obj/item/transfer_valve{ + pixel_x = 5 + }, +/obj/item/transfer_valve{ + pixel_x = 5 + }, +/obj/item/transfer_valve{ + pixel_x = 5 + }, +/obj/item/transfer_valve{ + pixel_x = 5 + }, /turf/open/floor/iron/white, /area/science/mixing/launch) "dMw" = ( @@ -17910,7 +17926,7 @@ /obj/effect/turf_decal/tile/neutral{ dir = 8 }, -/obj/machinery/research/explosive_compressor, +/obj/machinery/research/anomaly_refinery, /obj/effect/turf_decal/delivery, /turf/open/floor/iron/dark, /area/science/mixing/launch) @@ -20790,7 +20806,19 @@ }, /obj/structure/table, /obj/effect/turf_decal/bot, -/obj/item/clothing/mask/gas, +/obj/item/assembly/timer{ + pixel_x = -4; + pixel_y = 2 + }, +/obj/item/assembly/timer{ + pixel_x = 5; + pixel_y = 4 + }, +/obj/item/assembly/timer{ + pixel_x = 6; + pixel_y = -4 + }, +/obj/item/assembly/timer, /turf/open/floor/iron/dark, /area/science/mixing/launch) "foF" = ( @@ -24074,6 +24102,11 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/closet/crate/secure{ + desc = "A secure crate containing various materials for building a customised test-site."; + name = "Test Site Materials Crate"; + req_access_txt = "8" + }, /turf/open/floor/iron/dark, /area/science/mixing/launch) "grN" = ( @@ -27309,7 +27342,7 @@ dir = 8 }, /obj/machinery/light/directional/south, -/obj/machinery/doppler_array/research/science{ +/obj/machinery/doppler_array{ dir = 4 }, /obj/effect/turf_decal/delivery, @@ -30732,14 +30765,11 @@ /turf/open/floor/wood, /area/service/theater) "iNw" = ( -/obj/structure/table/reinforced, -/obj/item/wrench, -/obj/item/screwdriver{ - pixel_y = 10 +/obj/machinery/firealarm/directional/north, +/obj/machinery/atmospherics/components/binary/tank_compressor{ + dir = 8 }, /obj/effect/decal/cleanable/dirt, -/obj/item/analyzer, -/obj/item/pipe_dispenser, /obj/effect/turf_decal/bot, /obj/machinery/firealarm/directional/north, /obj/structure/cable, @@ -31266,7 +31296,9 @@ /turf/open/floor/iron, /area/commons/fitness/recreation) "iXv" = ( -/obj/machinery/atmospherics/components/unary/portables_connector/visible, +/obj/machinery/atmospherics/components/trinary/mixer{ + dir = 1 + }, /turf/open/floor/iron, /area/science/mixing) "iXC" = ( @@ -31599,9 +31631,22 @@ }, /obj/item/radio/intercom/directional/east, /obj/structure/table, -/obj/item/clothing/gloves/color/latex, -/obj/item/clothing/glasses/science, /obj/effect/turf_decal/bot, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, /turf/open/floor/iron/dark, /area/science/mixing/launch) "jfl" = ( @@ -34183,6 +34228,7 @@ /area/maintenance/aft/greater) "kbu" = ( /obj/structure/cable, +/obj/machinery/atmospherics/components/trinary/filter/flipped, /turf/open/floor/iron, /area/science/mixing) "kbv" = ( @@ -37936,7 +37982,8 @@ /turf/open/floor/iron, /area/hallway/primary/starboard) "lrs" = ( -/obj/structure/filingcabinet/chestdrawer, +/obj/structure/table, +/obj/item/storage/toolbox/mechanical, /turf/open/floor/iron/white, /area/science/mixing/launch) "lry" = ( @@ -44526,7 +44573,8 @@ "nyc" = ( /obj/structure/table, /obj/effect/turf_decal/bot, -/obj/item/clothing/mask/gas, +/obj/item/storage/toolbox/mechanical, +/obj/item/pipe_dispenser, /turf/open/floor/iron/white, /area/science/mixing) "nyf" = ( @@ -45521,6 +45569,10 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/item/target/alien, +/obj/item/target/alien, +/obj/item/target/clown, +/obj/item/target/clown, /turf/open/floor/iron/dark, /area/science/mixing/launch) "nPM" = ( @@ -45808,6 +45860,12 @@ /obj/machinery/duct, /turf/open/floor/iron/white, /area/medical/treatment_center) +"nUP" = ( +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 8 + }, +/turf/open/floor/iron, +/area/science/mixing) "nUR" = ( /obj/effect/turf_decal/tile/blue{ dir = 1 @@ -46219,6 +46277,10 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/plating, /area/maintenance/department/science/xenobiology) +"obt" = ( +/obj/effect/landmark/start/scientist, +/turf/open/floor/iron/white, +/area/science/mixing/launch) "obv" = ( /obj/structure/sign/poster/contraband/random{ pixel_x = 32 @@ -46864,25 +46926,12 @@ /turf/open/floor/iron/dark, /area/engineering/supermatter/room) "omN" = ( -/obj/item/assembly/signaler{ - pixel_y = 8 +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 8 }, -/obj/item/assembly/signaler{ - pixel_x = -8; - pixel_y = 5 - }, -/obj/item/assembly/signaler{ - pixel_x = 6; - pixel_y = 5 - }, -/obj/item/assembly/signaler{ - pixel_x = -2; - pixel_y = -2 - }, -/obj/structure/table/reinforced, -/obj/effect/turf_decal/bot, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/north, +/obj/effect/turf_decal/box, /turf/open/floor/iron/white, /area/science/mixing/launch) "omY" = ( @@ -47024,24 +47073,13 @@ }, /area/service/chapel/funeral) "opb" = ( -/obj/structure/table/reinforced, -/obj/item/transfer_valve{ - pixel_x = 5 +/obj/effect/turf_decal/stripes/white/line{ + dir = 1 }, -/obj/item/transfer_valve{ - pixel_x = 5 - }, -/obj/item/transfer_valve{ - pixel_x = 5 - }, -/obj/item/transfer_valve{ - pixel_x = 5 - }, -/obj/item/transfer_valve{ - pixel_x = 5 - }, -/turf/open/floor/iron/white, -/area/science/mixing/launch) +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/machinery/holopad, +/turf/open/floor/iron, +/area/science/mixing) "opc" = ( /obj/effect/turf_decal/stripes/line{ dir = 10 @@ -52398,9 +52436,7 @@ "qjh" = ( /obj/machinery/power/apc/auto_name/directional/east, /obj/structure/cable, -/obj/machinery/atmospherics/components/unary/portables_connector/visible{ - dir = 8 - }, +/obj/machinery/atmospherics/components/unary/portables_connector/visible, /turf/open/floor/iron, /area/science/mixing) "qjo" = ( @@ -52628,7 +52664,7 @@ /turf/open/floor/iron, /area/engineering/atmos) "qmZ" = ( -/obj/machinery/atmospherics/components/trinary/filter{ +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ dir = 1 }, /turf/open/floor/iron, @@ -53926,6 +53962,9 @@ "qKO" = ( /obj/machinery/status_display/evac/directional/south, /obj/structure/cable, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 1 + }, /turf/open/floor/iron, /area/science/mixing) "qLc" = ( @@ -57237,16 +57276,10 @@ /turf/open/floor/iron, /area/construction/storage_wing) "rVt" = ( -/obj/effect/turf_decal/bot, -/obj/structure/closet/crate/secure{ - desc = "A secure crate containing various materials for building a customised test-site."; - name = "Test Site Materials Crate"; - req_access_txt = "8" +/obj/effect/turf_decal/box, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 4 }, -/obj/item/target/clown, -/obj/item/target/clown, -/obj/item/target/alien, -/obj/item/target/alien, /obj/structure/cable, /turf/open/floor/iron/white, /area/science/mixing/launch) @@ -60060,6 +60093,7 @@ /area/maintenance/fore/lesser) "sXG" = ( /obj/structure/disposalpipe/segment, +/obj/machinery/holopad, /turf/open/floor/iron/white, /area/science/mixing/launch) "sXI" = ( @@ -60587,7 +60621,7 @@ /area/hallway/secondary/entry) "tgJ" = ( /obj/machinery/atmospherics/components/unary/portables_connector/visible{ - dir = 1 + dir = 4 }, /turf/open/floor/iron, /area/science/mixing) @@ -61426,11 +61460,23 @@ "ttZ" = ( /obj/machinery/airalarm/directional/west, /obj/structure/table, -/obj/item/crowbar, -/obj/item/wrench, -/obj/item/clothing/mask/gas, /obj/machinery/power/apc/auto_name/directional/south, /obj/structure/cable, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, /turf/open/floor/iron/white, /area/science/storage) "tuk" = ( @@ -64299,10 +64345,24 @@ }, /obj/structure/table, /obj/effect/turf_decal/bot, -/obj/item/wrench, /obj/machinery/camera/directional/west{ c_tag = "Science Ordnance Test Lab" }, +/obj/item/assembly/prox_sensor{ + pixel_y = 2 + }, +/obj/item/assembly/prox_sensor{ + pixel_x = 9; + pixel_y = -2 + }, +/obj/item/assembly/prox_sensor{ + pixel_x = -4; + pixel_y = 1 + }, +/obj/item/assembly/prox_sensor{ + pixel_x = 8; + pixel_y = 9 + }, /turf/open/floor/iron/dark, /area/science/mixing/launch) "uxp" = ( @@ -64515,22 +64575,8 @@ /turf/open/floor/plating, /area/service/hydroponics/garden) "uCp" = ( -/obj/item/assembly/timer{ - pixel_x = 5; - pixel_y = 4 - }, -/obj/item/assembly/timer{ - pixel_x = -4; - pixel_y = 2 - }, -/obj/item/assembly/timer{ - pixel_x = 6; - pixel_y = -4 - }, -/obj/item/assembly/timer, -/obj/structure/table/reinforced, -/obj/effect/turf_decal/bot, /obj/item/radio/intercom/directional/east, +/obj/structure/tank_dispenser, /turf/open/floor/iron/white, /area/science/mixing/launch) "uCA" = ( @@ -67461,9 +67507,6 @@ /turf/open/floor/iron/dark/telecomms, /area/tcommsat/server) "vEn" = ( -/obj/structure/chair/office/light{ - dir = 8 - }, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, /turf/open/floor/iron/white, /area/science/mixing/launch) @@ -72856,12 +72899,26 @@ /obj/effect/turf_decal/stripes/white/line{ dir = 1 }, -/obj/item/crowbar, /obj/machinery/requests_console/directional/west{ department = "Ordnance Test Range"; departmentType = 5; name = "Test Range Requests Console" }, +/obj/item/assembly/signaler{ + pixel_x = 6; + pixel_y = 5 + }, +/obj/item/assembly/signaler{ + pixel_x = -2; + pixel_y = -2 + }, +/obj/item/assembly/signaler{ + pixel_x = -8; + pixel_y = 5 + }, +/obj/item/assembly/signaler{ + pixel_y = 8 + }, /turf/open/floor/iron/dark, /area/science/mixing/launch) "xyl" = ( @@ -73333,8 +73390,10 @@ /turf/open/floor/iron, /area/hallway/secondary/command) "xGk" = ( -/obj/machinery/holopad, -/obj/effect/landmark/start/scientist, +/obj/effect/turf_decal/box, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 1 + }, /turf/open/floor/iron/white, /area/science/mixing/launch) "xGy" = ( @@ -74608,25 +74667,13 @@ /turf/open/floor/plating, /area/maintenance/fore) "yhY" = ( -/obj/item/assembly/prox_sensor{ - pixel_x = -4; - pixel_y = 1 +/obj/machinery/light/directional/north, +/obj/machinery/atmospherics/components/trinary/filter{ + dir = 4 }, -/obj/item/assembly/prox_sensor{ - pixel_x = 8; - pixel_y = 9 - }, -/obj/item/assembly/prox_sensor{ - pixel_x = 9; - pixel_y = -2 - }, -/obj/item/assembly/prox_sensor{ - pixel_y = 2 - }, -/obj/structure/table/reinforced, -/obj/effect/turf_decal/bot, /obj/machinery/light/directional/north, /obj/structure/cable, +/obj/effect/turf_decal/bot, /turf/open/floor/iron/white, /area/science/mixing/launch) "yib" = ( @@ -105728,8 +105775,8 @@ jvq uGO gmC eqN -swS -nrI +opb +nUP qjh kbu qKO @@ -109587,7 +109634,7 @@ ged ged crR rVt -jnU +obt jFj iux jfb @@ -110615,7 +110662,7 @@ dvY dvY nOc uCp -opb +jnU eID vLD ylf diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 6c9b7834e37..b7a929b3a5b 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -9,6 +9,7 @@ "aae" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/iron, /area/science/mixing/launch) "aas" = ( @@ -14367,6 +14368,7 @@ /area/security/processing) "dIi" = ( /obj/structure/cable, +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, /turf/open/floor/iron, /area/science/mixing/launch) "dIv" = ( @@ -21068,8 +21070,8 @@ /turf/open/floor/iron, /area/commons/storage/art) "fZU" = ( -/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, /obj/machinery/firealarm/directional/west, +/obj/machinery/research/anomaly_refinery, /turf/open/floor/iron, /area/science/mixing/launch) "gal" = ( @@ -23050,12 +23052,9 @@ /turf/open/floor/iron/dark, /area/medical/morgue) "gJk" = ( -/obj/machinery/research/explosive_compressor, /obj/effect/turf_decal/bot, -/obj/machinery/camera{ - c_tag = "Science - Ordnance Launch Room"; - dir = 9; - network = list("ss13","rd") +/obj/machinery/atmospherics/components/binary/tank_compressor{ + dir = 8 }, /turf/open/floor/iron, /area/science/mixing/launch) @@ -34055,6 +34054,14 @@ "kNV" = ( /obj/effect/turf_decal/bot, /obj/machinery/airalarm/directional/north, +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 8 + }, +/obj/machinery/camera{ + c_tag = "Science - Ordnance Launch Room"; + dir = 9; + network = list("ss13","rd") + }, /turf/open/floor/iron, /area/science/mixing/launch) "kOa" = ( @@ -43615,12 +43622,25 @@ /turf/open/floor/iron, /area/engineering/main) "opt" = ( -/obj/structure/chair{ - dir = 4 - }, /obj/effect/turf_decal/stripes/line{ dir = 6 }, +/obj/structure/table, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -5; + pixel_y = 8 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = 7; + pixel_y = 2 + }, +/obj/item/computer_hardware/hard_drive/portable{ + pixel_x = -8; + pixel_y = -3 + }, /turf/open/floor/iron, /area/science/mixing/launch) "opw" = ( @@ -50714,6 +50734,9 @@ /turf/open/floor/catwalk_floor, /area/hallway/primary/tram/left) "qUg" = ( +/obj/machinery/atmospherics/components/unary/portables_connector/visible{ + dir = 4 + }, /obj/effect/turf_decal/bot, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/north, @@ -51925,7 +51948,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 5 }, -/obj/machinery/doppler_array/research/science{ +/obj/machinery/doppler_array{ dir = 4 }, /obj/item/radio/intercom/directional/north, diff --git a/code/__DEFINES/atmospherics/atmos_piping.dm b/code/__DEFINES/atmospherics/atmos_piping.dm index 98a74102c18..f0919f37276 100644 --- a/code/__DEFINES/atmospherics/atmos_piping.dm +++ b/code/__DEFINES/atmospherics/atmos_piping.dm @@ -51,6 +51,13 @@ #define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) /// Range scaling constant for tank explosions. Calibrated so that a TTV assembled using two 70L tanks will hit the maxcap at at least 160atm. #define TANK_FRAGMENT_SCALE (84.*ONE_ATMOSPHERE) +/// Denotes that our tank is overpressurized simply from gas merging. +#define TANK_MERGE_OVERPRESSURE "tank_overpressure" +// Indices for the reaction_results returned by explosion_information() +/// Reactions that have happened in the tank. +#define TANK_RESULTS_REACTION 1 +/// Additional information of the tank. +#define TANK_RESULTS_MISC 2 //MULTIPIPES //IF YOU EVER CHANGE THESE CHANGE SPRITES TO MATCH. diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_explosion.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_explosion.dm index a2caf6b2095..d2328f5e500 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_explosion.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_explosion.dm @@ -10,5 +10,4 @@ #define COMSIG_AREA_INTERNAL_EXPLOSION "area_internal_explosion" /// When returned on a signal hooked to [COMSIG_ATOM_EXPLODE], [COMSIG_ATOM_INTERNAL_EXPLOSION], or [COMSIG_AREA_INTERNAL_EXPLOSION] it prevents the explosion from being propagated further. #define COMSIG_CANCEL_EXPLOSION (1<<0) -///from /obj/machinery/doppler_array/proc/sense_explosion(...): Runs when an explosion is succesfully detected by a doppler array(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) -#define COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED "atom_dopplerarray_explosion_detected" + diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm index 1a57698dd64..826dd0e6642 100644 --- a/code/__DEFINES/dcs/signals/signals_datum.dm +++ b/code/__DEFINES/dcs/signals/signals_datum.dm @@ -31,3 +31,21 @@ #define COMSIG_MERGER_REMOVING "comsig_merger_removing" /// Called on the merger after finishing a refresh: (list/leaving_members, list/joining_members) #define COMSIG_MERGER_REFRESH_COMPLETE "comsig_merger_refresh_complete" + +// Gas mixture signals +/// From /datum/gas_mixture/proc/merge: () +#define COMSIG_GASMIX_MERGED "comsig_gasmix_merged" +/// From /datum/gas_mixture/proc/remove: () +#define COMSIG_GASMIX_REMOVED "comsig_gasmix_removed" +/// From /datum/gas_mixture/proc/react: () +#define COMSIG_GASMIX_REACTED "comsig_gasmix_reacted" + +// Modular computer's file signals. Tells the program datum something is going on. +/// From /obj/item/computer_hardware/hard_drive/proc/store_file: () +#define COMSIG_MODULAR_COMPUTER_FILE_ADDING "comsig_modular_computer_file_adding" +/// From /obj/item/computer_hardware/hard_drive/proc/store_file: () +#define COMSIG_MODULAR_COMPUTER_FILE_ADDED "comsig_modular_computer_file_adding" +/// From /obj/item/computer_hardware/hard_drive/proc/remove_file: () +#define COMSIG_MODULAR_COMPUTER_FILE_DELETING "comsig_modular_computer_file_deleting" +/// From /obj/item/computer_hardware/hard_drive/proc/store_file: () +#define COMSIG_MODULAR_COMPUTER_FILE_DELETED "comsig_modular_computer_file_adding" diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 569fe5a0629..249c178b52d 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -98,8 +98,8 @@ //Program categories #define PROGRAM_CATEGORY_CREW "Crew" #define PROGRAM_CATEGORY_ENGI "Engineering" -#define PROGRAM_CATEGORY_ROBO "Robotics" #define PROGRAM_CATEGORY_SUPL "Supply" +#define PROGRAM_CATEGORY_SCI "Science" #define PROGRAM_CATEGORY_MISC "Other" #define FIREDOOR_OPEN 1 diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index 0bf8cb1aa00..f7660d2293b 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -30,17 +30,6 @@ TECHWEB_POINT_TYPE_GENERIC = "General Research",\ ) -/// The maximum amount of cash you can get from toxins experiments. -#define TECHWEB_BOMB_CASHCAP 50000 -/// The range required to generate any cash from toxins experiments. -#define TECHWEB_BOMB_MIN_RANGE (GLOB.MAX_EX_LIGHT_RANGE) -/// The range required to produce the maximum amount of cash from toxins experiments. -#define TECHWEB_BOMB_MAX_RANGE 300 -/// A constant used to scale the cash produced by toxins experiments. -#define TECHWEB_BOMB_SCALE_CONST 300000 -/// A constant used to scale the cash produced by toxins experiments. -#define TECHWEB_BOMB_SCALE_DIVISOR 1500 - //! Amount of points gained per second by a single R&D server, see: [research][code/controllers/subsystem/research.dm] #define TECHWEB_SINGLE_SERVER_INCOME 52.3 diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index fb08e7b5495..9041e1cbec2 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -6,3 +6,38 @@ /proc/molar_cmp_equals(a,b,epsilon = MAXIMUM_ERROR_GAS_REMOVAL) return (((a + epsilon) > b) && ((a - epsilon) < b)) + +/** A simple rudimentary gasmix to information list converter. Can be used for UIs. + * Args: + * - gasmix: [/datum/gas_mixture] + * - name: String used to name the list, optional. + * Returns: A list parsed_gasmixes with the following structure: + * - parsed_gasmixes - Assoc List + * -- Key: name Value: String Desc: Gasmix Name + * -- Key: temperature Value: Number Desc: Temperature in kelvins + * -- Key: volume Value: Number Desc: Volume in liters + * -- Key: pressure Value: Number Desc: Pressure in kPa + * -- Key: ref Value: Text Desc: The reference for the instantiated gasmix. + * -- Key: gases Value: Assoc list Desc: List of gasses in our gasmix + * --- Key: gas_name Value: Gas Mole Desc: Gas Name - Gas Amount pair + * Returned list should always be filled with keys even if value are nulls. + */ +/proc/gas_mixture_parser(datum/gas_mixture/gasmix, name) + . = list( + "gases" = list(), + "name" = name, + "total_moles" = null, + "temperature" = null, + "volume"= null, + "pressure"= null, + "ref" = null, + ) + if(!gasmix) + return + for(var/gas_id in gasmix.gases) + .["gases"][gasmix.gases[gas_id][GAS_META][META_GAS_NAME]] = gasmix.gases[gas_id][MOLES] + .["total_moles"] = gasmix.total_moles() + .["temperature"] = gasmix.temperature + .["volume"] = gasmix.volume + .["pressure"] = gasmix.return_pressure() + .["ref"] = REF(gasmix) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 2323a614c3b..f3da1e9f954 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -41,6 +41,8 @@ SUBSYSTEM_DEF(air) var/list/turf/active_super_conductivity = list() var/list/turf/open/high_pressure_delta = list() var/list/atom_process = list() + /// Reactions which will contribute to a hotspot's size. + var/list/hotspot_reactions /// A cache of objects that perisists between processing runs when resumed == TRUE. Dangerous, qdel'd objects not cleared from this may cause runtimes on processing. var/list/currentrun = list() @@ -82,6 +84,7 @@ SUBSYSTEM_DEF(air) /datum/controller/subsystem/air/Initialize(timeofday) map_loading = FALSE gas_reactions = init_gas_reactions() + hotspot_reactions = init_hotspot_reactions() setup_allturfs() setup_atmos_machinery() diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index da3655ad745..81f0c54fa6e 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -31,7 +31,8 @@ SUBSYSTEM_DEF(explosions) var/list/med_mov_atom = list() var/list/high_mov_atom = list() - var/list/explosions = list() + // Track how many explosions have happened. + var/explosion_index = 0 var/currentpart = SSAIR_PIPENETS @@ -460,7 +461,9 @@ SUBSYSTEM_DEF(explosions) if(GLOB.Debug2) log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPLOSION, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) + explosion_index += 1 + + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPLOSION, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range, explosion_cause, explosion_index) // Explosion SFX defines... /// The probability that a quaking explosion will make the station creak per unit. Maths! diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 0d154dd1c53..46651acfb69 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -55,10 +55,16 @@ SUBSYSTEM_DEF(research) ANOMALY_CORE_FLUX = MAX_CORES_FLUX ) + /// Lookup list for ordnance briefers. + var/list/ordnance_experiments + /// Lookup list for scipaper partners. + var/list/scientific_partners + /datum/controller/subsystem/research/Initialize() point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES initialize_all_techweb_designs() initialize_all_techweb_nodes() + populate_ordnance_experiments() science_tech = new /datum/techweb/science admin_tech = new /datum/techweb/admin autosort_categories() @@ -293,3 +299,17 @@ SUBSYSTEM_DEF(research) else techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) CHECK_TICK + +/datum/controller/subsystem/research/proc/populate_ordnance_experiments() + ordnance_experiments = list() + scientific_partners = list() + + for (var/datum/experiment/ordnance/experiment_path as anything in subtypesof(/datum/experiment/ordnance)) + if (initial(experiment_path.experiment_proper)) + ordnance_experiments += new experiment_path() + for(var/partner_path in subtypesof(/datum/scientific_partner)) + var/datum/scientific_partner/partner = new partner_path + if(!partner.accepted_experiments.len) + for (var/datum/experiment/ordnance/ordnance_experiment as anything in ordnance_experiments) + partner.accepted_experiments += ordnance_experiment.type + scientific_partners += partner diff --git a/code/datums/components/atmos_reaction_recorder.dm b/code/datums/components/atmos_reaction_recorder.dm new file mode 100644 index 00000000000..02ec672655c --- /dev/null +++ b/code/datums/components/atmos_reaction_recorder.dm @@ -0,0 +1,59 @@ + +/** + * Component for atmos-related atoms to be able to record and transmit their reaction_results data + * Use this ONLY for atoms, gasmixtures will be accessed by calling return air on the object. + * Add this component after gasmixes has been initialized. + */ +/datum/component/atmos_reaction_recorder + /// The list we write append each reaction tick to. + /// This is often a list initialized by something else (passed as a reference under Initialize). + var/list/copied_reaction_results + /// Signals we have been listening to. + var/list/registered_signals + +/** + * Verify that parent is indeed an atom, and then register signals. + * Args: + * - target_list (list): The list we are writing the captured reaction_results to. + * - reset_criteria (list): Assoc signal-source list containing signals to be registered to. We will reset if any of them are sent. + */ +/datum/component/atmos_reaction_recorder/Initialize(list/target_list=list(), list/reset_criteria = list()) + . = ..() + + var/atom/parent_atom = parent + var/datum/gas_mixture/parent_air = parent_atom?.return_air() + if((!istype(parent_atom) || !istype(parent_air))) + return COMPONENT_INCOMPATIBLE + + if(islist(target_list)) + copied_reaction_results = target_list + else + // Still can record reactions without a list, but it most likely wont be accesible outside of the component. + stack_trace("Atmos reaction recorder component initialized without a list.") + copied_reaction_results = list() + + registered_signals = list() + RegisterSignal(parent_air, COMSIG_GASMIX_REACTED, .proc/update_data) + registered_signals += list(COMSIG_GASMIX_REACTED = parent_air) + + for(var/signal in reset_criteria) + // We currently dont implement the same signal registered twice even from different sources. This allows this component to be simpler. + if(signal in registered_signals) + return COMPONENT_INCOMPATIBLE + RegisterSignal(reset_criteria[signal], signal, .proc/reset_data) + registered_signals[signal] = reset_criteria[signal] + +/// Fetches reaction_results and updates the list. +/datum/component/atmos_reaction_recorder/proc/update_data(datum/gas_mixture/recorded_gasmix) + SIGNAL_HANDLER + for (var/reaction in recorded_gasmix.reaction_results) + copied_reaction_results[reaction] += recorded_gasmix.reaction_results[reaction] + +/datum/component/atmos_reaction_recorder/proc/reset_data() + SIGNAL_HANDLER + copied_reaction_results.Cut() + +/datum/component/atmos_reaction_recorder/UnregisterFromParent() + . = ..() + for(var/signal in registered_signals) + UnregisterSignal(registered_signals[signal], signal) diff --git a/code/game/machinery/computer/arcade/arcade.dm b/code/game/machinery/computer/arcade/arcade.dm index 97c5ce27887..69a9b80a75b 100644 --- a/code/game/machinery/computer/arcade/arcade.dm +++ b/code/game/machinery/computer/arcade/arcade.dm @@ -550,7 +550,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list( playsound(loc, 'sound/arcade/win.ogg', 50, TRUE) if(obj_flags & EMAGGED) - new /obj/effect/spawner/newbomb/timer(loc) + new /obj/effect/spawner/newbomb/plasma(loc, /obj/item/assembly/timer) new /obj/item/clothing/head/collectable/petehat(loc) message_admins("[ADMIN_LOOKUPFLW(usr)] has outbombed Cuban Pete and been awarded a bomb.") log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.") diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm deleted file mode 100644 index 9167c93220e..00000000000 --- a/code/game/machinery/doppler_array.dm +++ /dev/null @@ -1,270 +0,0 @@ -#define PRINTER_TIMEOUT 40 - -/obj/machinery/doppler_array - name = "tachyon-doppler array" - desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" - icon = 'icons/obj/machines/research.dmi' - icon_state = "tdoppler" - base_icon_state = "tdoppler" - density = TRUE - verb_say = "states coldly" - var/cooldown = 10 - var/next_announce = 0 - var/max_dist = 150 - /// Number which will be part of the name of the next record, increased by one for each already created record - var/record_number = 1 - /// Cooldown for the print function - var/printer_ready = 0 - /// List of all explosion records in the form of /datum/data/tachyon_record - var/list/records = list() - -/obj/machinery/doppler_array/Initialize(mapload) - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) - RegisterSignal(src, COMSIG_MOVABLE_SET_ANCHORED, .proc/power_change) - printer_ready = world.time + PRINTER_TIMEOUT - // Alt clicking when unwrenched does not rotate. (likely from UI not returning the mouse click) - // Also there is no sprite change for rotation dir, this shouldn't even have a rotate component tbh - AddComponent(/datum/component/simple_rotation, AfterRotation = CALLBACK(src, .proc/RotationMessage)) - -/datum/data/tachyon_record - name = "Log Recording" - var/timestamp - var/coordinates = "" - var/displacement = 0 - var/factual_radius = list() - var/theory_radius = list() - -/obj/machinery/doppler_array/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "TachyonArray", name) - ui.open() - -/obj/machinery/doppler_array/ui_data(mob/user) - var/list/data = list() - data["records"] = list() - for(var/datum/data/tachyon_record/R in records) - var/list/record_data = list( - name = R.name, - timestamp = R.timestamp, - coordinates = R.coordinates, - displacement = R.displacement, - factual_epicenter_radius = R.factual_radius["epicenter_radius"], - factual_outer_radius = R.factual_radius["outer_radius"], - factual_shockwave_radius = R.factual_radius["shockwave_radius"], - theory_epicenter_radius = R.theory_radius["epicenter_radius"], - theory_outer_radius = R.theory_radius["outer_radius"], - theory_shockwave_radius = R.theory_radius["shockwave_radius"], - ref = REF(R) - ) - data["records"] += list(record_data) - return data - -/obj/machinery/doppler_array/ui_act(action, list/params) - . = ..() - if(.) - return - - switch(action) - if("delete_record") - var/datum/data/tachyon_record/record = locate(params["ref"]) in records - if(!records || !(record in records)) - return - records -= record - return TRUE - if("print_record") - var/datum/data/tachyon_record/record = locate(params["ref"]) in records - if(!records || !(record in records)) - return - print(usr, record) - return TRUE - -/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record) - if(!record) - return - if(printer_ready < world.time) - printer_ready = world.time + PRINTER_TIMEOUT - new /obj/item/paper/record_printout(loc, record) - else if(user) - to_chat(user, span_warning("[src] is busy right now.")) - -/obj/item/paper/record_printout - name = "paper - Log Recording" - -/obj/item/paper/record_printout/Initialize(mapload, datum/data/tachyon_record/record) - . = ..() - - if(record) - name = "paper - [record.name]" - - info += {"

[record.name]

- "} - - if(length(record.theory_radius)) - info += {""} - - update_appearance() - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(!anchored && !isinspace()) - set_anchored(TRUE) - to_chat(user, span_notice("You fasten [src].")) - else if(anchored) - set_anchored(FALSE) - to_chat(user, span_notice("You unfasten [src].")) - I.play_tool_sound(src) - return - return ..() - -/obj/machinery/doppler_array/AltClick(mob/user) - return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation - -/obj/machinery/doppler_array/proc/RotationMessage(mob/user) - to_chat(user, span_notice("You adjust [src]'s dish to face to the [dir2text(dir)].")) - playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) - -/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) - SIGNAL_HANDLER - - if(machine_stat & NOPOWER) - return FALSE - var/turf/zone = get_turf(src) - if(zone.z != epicenter.z) - return FALSE - - if(next_announce > world.time) - return FALSE - next_announce = world.time + cooldown - - if((get_dist(epicenter, zone) > max_dist) || !(get_dir(zone, epicenter) & dir)) - return FALSE - - var/datum/data/tachyon_record/R = new /datum/data/tachyon_record() - R.name = "Log Recording #[record_number]" - R.timestamp = station_time_timestamp() - R.coordinates = "[epicenter.x], [epicenter.y]" - R.displacement = took - R.factual_radius["epicenter_radius"] = devastation_range - R.factual_radius["outer_radius"] = heavy_impact_range - R.factual_radius["shockwave_radius"] = light_impact_range - - var/list/messages = list("Explosive disturbance detected.", - "Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.", - "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].", - ) - - // If the bomb was capped, say its theoretical size. - if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) - messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." - R.theory_radius["epicenter_radius"] = orig_dev_range - R.theory_radius["outer_radius"] = orig_heavy_range - R.theory_radius["shockwave_radius"] = orig_light_range - - for(var/message in messages) - say(message) - - record_number++ - records += R - - SEND_SIGNAL(src, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) - - return TRUE - -/obj/machinery/doppler_array/powered() - return anchored && ..() - -/obj/machinery/doppler_array/update_icon_state() - if(machine_stat & BROKEN) - icon_state = "[base_icon_state]-broken" - return ..() - icon_state = "[base_icon_state][powered() ? null : "-off"]" - return ..() - -/obj/machinery/doppler_array/research - name = "tachyon-doppler research array" - desc = "A specialized tachyon-doppler bomb detection array that uses complex on-board software to record data for experiments." - circuit = /obj/item/circuitboard/machine/doppler_array - var/datum/techweb/linked_techweb - -/obj/machinery/doppler_array/research/Initialize(mapload) - ..() - linked_techweb = SSresearch.science_tech - return INITIALIZE_HINT_LATELOAD - -// Late initialize to allow the server machinery to initialize first -/obj/machinery/doppler_array/research/LateInitialize() - . = ..() - AddComponent(/datum/component/experiment_handler, \ - allowed_experiments = list(/datum/experiment/explosion), \ - config_mode = EXPERIMENT_CONFIG_UI, \ - config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE) - -/obj/machinery/doppler_array/research/attackby(obj/item/I, mob/user, params) - if (default_deconstruction_screwdriver(user, "tdoppler", "tdoppler", I) || default_deconstruction_crowbar(I)) - update_icon() - return - return ..() - -/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on - . = ..() - if(!.) - return - - if(!istype(linked_techweb)) - say("Warning: no linked research system!") - return - - var/cash_gain = 0 - - /*****The Point Calculator*****/ - if(orig_light_range < TECHWEB_BOMB_MIN_RANGE) - say("Explosion not large enough for profitability.") - return - else if(orig_light_range < TECHWEB_BOMB_MAX_RANGE) - cash_gain = (TECHWEB_BOMB_SCALE_CONST * orig_light_range) / (TECHWEB_BOMB_SCALE_DIVISOR + orig_light_range) - else - cash_gain = TECHWEB_BOMB_CASHCAP - - /*****The Point Capper*****/ - if(cash_gain > linked_techweb.largest_bomb_value) - if(cash_gain <= TECHWEB_BOMB_CASHCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_CASHCAP) - var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math - linked_techweb.largest_bomb_value = cash_gain - cash_gain -= old_tech_largest_bomb_value - cash_gain = min(cash_gain, TECHWEB_BOMB_CASHCAP) - else - linked_techweb.largest_bomb_value = TECHWEB_BOMB_CASHCAP - cash_gain = 1000 - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) - if(D) - D.adjust_money(cash_gain) - say("Explosion details and mixture analysis sold to the highest bidder for [cash_gain] cr.") - else //you've made smaller bombs - say("Data already captured. Aborting.") - return - -/obj/machinery/doppler_array/research/science/Initialize(mapload) - . = ..() - linked_techweb = SSresearch.science_tech - -/obj/machinery/doppler_array/research/ui_data(mob/user) - . = ..() - .["is_research"] = TRUE - -/obj/machinery/doppler_array/research/ui_act(action, list/params) - . = ..() - if (.) - return - -#undef PRINTER_TIMEOUT diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index 2f1384cd24b..64fc60062e6 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -6,52 +6,118 @@ name = "bomb" icon = 'icons/hud/screen_gen.dmi' icon_state = "x" - /// The initial temperature of the plasma tank. - var/temp_p = 1413 - /// The initial temperature of the oxygen tank. - var/temp_o = 141.3 - /// The initial pressure of the plasma tank. - var/pressure_p = TANK_LEAK_PRESSURE - 1 - /// The initial pressure of the oxygen tank. - var/pressure_o = TANK_LEAK_PRESSURE - 1 - /// The typepath of the assembly to attach to the TTV. - var/assembly_type + /* Gasmixes for tank_one and tank_two of the ttv respectively. + * Populated on /obj/effect/spawner/newbomb/Initialize, depopulated right after by the children procs. + */ + var/datum/gas_mixture/first_gasmix + var/datum/gas_mixture/second_gasmix -/obj/effect/spawner/newbomb/Initialize(mapload) +/** + * The part of code that actually spawns the bomb. Always call the parent's initialize first for subtypes of these. + * + * Arguments: + * * assembly - An assembly typepath to add to the ttv. + */ +/obj/effect/spawner/newbomb/Initialize(mapload, assembly = null) . = ..() var/obj/item/transfer_valve/ttv = new(loc) - var/obj/item/tank/internals/plasma/plasma_tank = new(ttv) - var/obj/item/tank/internals/oxygen/oxygen_tank = new(ttv) - - var/datum/gas_mixture/plasma_mix = plasma_tank.return_air() - plasma_mix.assert_gas(/datum/gas/plasma) - plasma_mix.gases[/datum/gas/plasma][MOLES] = pressure_p*plasma_mix.volume/(R_IDEAL_GAS_EQUATION*temp_p) - plasma_mix.temperature = temp_p - - var/datum/gas_mixture/oxygen_mix = oxygen_tank.return_air() - oxygen_mix.assert_gas(/datum/gas/oxygen) - oxygen_mix.gases[/datum/gas/oxygen][MOLES] = pressure_o*oxygen_mix.volume/(R_IDEAL_GAS_EQUATION*temp_o) - oxygen_mix.temperature = temp_o - - ttv.tank_one = plasma_tank - ttv.tank_two = oxygen_tank - plasma_tank.master = ttv - oxygen_tank.master = ttv - - if(assembly_type) - var/obj/item/assembly/detonator = new assembly_type(ttv) - ttv.attached_device = detonator - detonator.holder = ttv - + ttv.tank_one = new /obj/item/tank/internals/plasma (ttv) + ttv.tank_two = new /obj/item/tank/internals/oxygen (ttv) + first_gasmix = ttv.tank_one.return_air() + second_gasmix = ttv.tank_two.return_air() + first_gasmix.remove_ratio(1) + second_gasmix.remove_ratio(1) + if(ispath(assembly, /obj/item/assembly)) + var/obj/item/assembly/newassembly = new assembly (ttv) + ttv.attached_device = newassembly + newassembly.on_attach() + newassembly.holder = ttv ttv.update_appearance() - return INITIALIZE_HINT_QDEL -/obj/effect/spawner/newbomb/timer - assembly_type = /obj/item/assembly/timer +/obj/effect/spawner/newbomb/proc/calculate_pressure(datum/gas_mixture/gasmix, pressure) + return pressure * gasmix.volume/(R_IDEAL_GAS_EQUATION*gasmix.temperature) -/obj/effect/spawner/newbomb/proximity - assembly_type = /obj/item/assembly/prox_sensor +/obj/effect/spawner/newbomb/plasma -/obj/effect/spawner/newbomb/radio - assembly_type = /obj/item/assembly/signaler +/obj/effect/spawner/newbomb/plasma/Initialize(mapload) + . = ..() + if(!first_gasmix || !second_gasmix) + return + + first_gasmix.temperature = 1413 + second_gasmix.temperature = 141.3 + + first_gasmix.assert_gas(/datum/gas/plasma) + second_gasmix.assert_gas(/datum/gas/oxygen) + + first_gasmix.gases[/datum/gas/plasma][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/oxygen][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + +/obj/effect/spawner/newbomb/tritium + +/obj/effect/spawner/newbomb/tritium/Initialize(mapload, obj/item/assembly) + . = ..() + if(!first_gasmix || !second_gasmix) + return + + first_gasmix.temperature = 8000 + second_gasmix.temperature = 43 + + first_gasmix.assert_gas(/datum/gas/plasma) + second_gasmix.assert_gas(/datum/gas/oxygen) + second_gasmix.assert_gas(/datum/gas/tritium) + + first_gasmix.gases[/datum/gas/plasma][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/oxygen][MOLES] = 0.67 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/tritium][MOLES] = 0.33 * calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + +/obj/effect/spawner/newbomb/isolated_tritium + +/obj/effect/spawner/newbomb/isolated_tritium/Initialize(mapload) + . = ..() + if(!first_gasmix || !second_gasmix) + return + + first_gasmix.temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1 + second_gasmix.temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 1 + + first_gasmix.assert_gas(/datum/gas/hypernoblium) + first_gasmix.assert_gas(/datum/gas/tritium) + second_gasmix.assert_gas(/datum/gas/oxygen) + + first_gasmix.gases[/datum/gas/hypernoblium][MOLES] = REACTION_OPPRESSION_THRESHOLD - 0.01 + first_gasmix.gases[/datum/gas/tritium][MOLES] = 0.5 * calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/oxygen][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE-1) + +/obj/effect/spawner/newbomb/noblium + +/obj/effect/spawner/newbomb/noblium/Initialize(mapload) + . = ..() + if(!first_gasmix || !second_gasmix) + return + + first_gasmix.temperature = 2.7 + second_gasmix.temperature = 2.7 + + first_gasmix.assert_gas(/datum/gas/nitrogen) + second_gasmix.assert_gas(/datum/gas/tritium) + + first_gasmix.gases[/datum/gas/nitrogen][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/tritium][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) + +/obj/effect/spawner/newbomb/pressure + +/obj/effect/spawner/newbomb/pressure/Initialize(mapload) + . = ..() + if(!first_gasmix || !second_gasmix) + return + + first_gasmix.temperature = 20000 + second_gasmix.temperature = 2.7 + + first_gasmix.assert_gas(/datum/gas/hypernoblium) + second_gasmix.assert_gas(/datum/gas/tritium) + + first_gasmix.gases[/datum/gas/hypernoblium][MOLES] = calculate_pressure(first_gasmix, TANK_LEAK_PRESSURE - 1) + second_gasmix.gases[/datum/gas/tritium][MOLES] = calculate_pressure(second_gasmix, TANK_LEAK_PRESSURE - 1) diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 2c40dadcb23..15011c2bef4 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1294,7 +1294,7 @@ /obj/item/circuitboard/machine/doppler_array name = "Tachyon-Doppler Research Array (Machine Board)" greyscale_colors = CIRCUIT_COLOR_SCIENCE - build_path = /obj/machinery/doppler_array/research + build_path = /obj/machinery/doppler_array req_components = list( /obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/scanning_module = 4) @@ -1321,3 +1321,22 @@ build_path = /obj/machinery/ecto_sniffer req_components = list( /obj/item/stock_parts/scanning_module = 1) + +/obj/item/circuitboard/machine/anomaly_refinery + name = "Anomaly Refinery (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/research/anomaly_refinery + req_components = list( + /obj/item/stack/sheet/plasteel = 15, + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/manipulator = 1, + ) + +/obj/item/circuitboard/machine/tank_compressor + name = "Tank Compressor (Machine Board)" + greyscale_colors = CIRCUIT_COLOR_SCIENCE + build_path = /obj/machinery/atmospherics/components/binary/tank_compressor + req_components = list( + /obj/item/stack/sheet/plasteel = 5, + /obj/item/stock_parts/scanning_module = 4, + ) diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index c2363794c90..8009ffa92cd 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -133,24 +133,24 @@ . += "proxy_beam" -/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) - var/target_self = FALSE - var/datum/gas_mixture/mix_one = tank_one.return_air() - var/datum/gas_mixture/mix_two = tank_two.return_air() - if(!target || (target == mix_one)) - target = mix_two - if(target == mix_two) - target_self = TRUE +/// Merge both gases into a single tank. Combine the volume by default. If target tank isn't specified default to tank_two +/obj/item/transfer_valve/proc/merge_gases(obj/item/tank/target, change_volume = TRUE) + if(!target) + target = tank_two + + if(!istype(target) || (target != tank_one && target != tank_two)) + return FALSE + + // Throw both tanks into processing queue + var/datum/gas_mixture/target_mix = target.return_air() + var/datum/gas_mixture/other_mix + other_mix = (target == tank_one ? tank_two : tank_one).return_air() + if(change_volume) - if(!target_self) - target.volume += tank_two.volume - target.volume += mix_one.volume - var/datum/gas_mixture/temp - temp = mix_one.remove_ratio(1) - target.merge(temp) - if(!target_self) - temp = mix_two.remove_ratio(1) - target.merge(temp) + target_mix.volume += other_mix.volume + + target_mix.merge(other_mix.remove_ratio(1)) + return TRUE /obj/item/transfer_valve/proc/split_gases() if (!valve_open || !tank_one || !tank_two) @@ -168,9 +168,8 @@ Exadv1: I know this isn't how it's going to work, but this was just to check it explodes properly when it gets a signal (and it does). */ -/obj/item/transfer_valve/proc/toggle_valve() +/obj/item/transfer_valve/proc/toggle_valve(obj/item/tank/target, change_volume = TRUE) if(!valve_open && tank_one && tank_two) - valve_open = TRUE var/turf/bombturf = get_turf(src) var/attachment @@ -201,7 +200,11 @@ message_admins(admin_bomb_message) log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") - merge_gases() + valve_open = merge_gases(target, change_volume) + + if(!valve_open) + stack_trace("TTV gas merging failed.") + for(var/i in 1 to 6) addtimer(CALLBACK(src, /atom/.proc/update_appearance), 20 + (i - 1) * 10) diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index d8b7fd91763..093048267da 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -38,6 +38,10 @@ var/tank_holder_icon_state = "holder_generic" ///Used by process() to track if there's a reason to process each tick var/excited = TRUE + /// How our particular tank explodes. + var/list/explosion_info + /// List containing reactions happening inside our tank. + var/list/reaction_info /obj/item/tank/ui_action_click(mob/user) toggle_internals(user) @@ -80,21 +84,31 @@ populate_gas() + reaction_info = list() + explosion_info = list() + + AddComponent(/datum/component/atmos_reaction_recorder, reset_criteria = list(COMSIG_GASMIX_MERGING = air_contents, COMSIG_GASMIX_REMOVING = air_contents), target_list = reaction_info) + + // This is separate from the reaction recorder. + // In this case we are only listening to determine if the tank is overpressurized but not destroyed. + RegisterSignal(air_contents, COMSIG_GASMIX_MERGED, .proc/merging_information) + START_PROCESSING(SSobj, src) /obj/item/tank/proc/populate_gas() return -/obj/item/tank/Destroy() - air_contents = null - STOP_PROCESSING(SSobj, src) - return ..() - /obj/item/tank/ComponentInitialize() . = ..() if(tank_holder_icon_state) AddComponent(/datum/component/container_item/tank_holder, tank_holder_icon_state) +/obj/item/tank/Destroy() + UnregisterSignal(air_contents, COMSIG_GASMIX_MERGED) + air_contents = null + STOP_PROCESSING(SSobj, src) + return ..() + /obj/item/tank/examine(mob/user) var/obj/icon = src . = ..() @@ -324,4 +338,12 @@ dyn_explosion(src, power, flash_range = 1.5, ignorecap = FALSE) return ..() +/obj/item/tank/proc/merging_information() + SIGNAL_HANDLER + if(air_contents.return_pressure() > TANK_FRAGMENT_PRESSURE) + explosion_info += TANK_MERGE_OVERPRESSURE + +/obj/item/tank/proc/explosion_information() + return list(TANK_RESULTS_REACTION = reaction_info, TANK_RESULTS_MISC = explosion_info) + #undef ASSUME_AIR_DT_FACTOR diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm index 6d0ea3fa379..659816a51be 100644 --- a/code/modules/atmospherics/environmental/LINDA_fire.dm +++ b/code/modules/atmospherics/environmental/LINDA_fire.dm @@ -1,4 +1,11 @@ +/// Returns reactions which will contribute to a hotspot's size. +/proc/init_hotspot_reactions() + var/list/fire_reactions = list() + for (var/datum/gas_reaction/reaction as anything in subtypesof(/datum/gas_reaction)) + if(initial(reaction.expands_hotspot)) + fire_reactions += reaction + return fire_reactions /atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) return null @@ -109,6 +116,7 @@ */ /obj/effect/hotspot/proc/perform_exposure() var/turf/open/location = loc + var/datum/gas_mixture/reference if(!istype(location) || !(location.air)) return @@ -118,18 +126,23 @@ //Passive mode if(bypassing) - volume = location.air.reaction_results["fire"]*FIRE_GROWTH_RATE - temperature = location.air.temperature + reference = location.air // Our color and volume will depend on the turf's gasmix //Active mode else var/datum/gas_mixture/affected = location.air.remove_ratio(volume/location.air.volume) if(affected) //in case volume is 0 + reference = affected // Our color and volume will depend on this small sparked gasmix affected.temperature = temperature affected.react(src) - temperature = affected.temperature - volume = affected.reaction_results["fire"]*FIRE_GROWTH_RATE location.assume_air(affected) + if(reference) + volume = 0 + var/list/cached_results = reference.reaction_results + for (var/reaction in SSair.hotspot_reactions) + volume += cached_results[reaction] * FIRE_GROWTH_RATE + temperature = reference.temperature + // Handles the burning of atoms. for(var/A in location) var/atom/AT = A diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 7dff950645b..335c1c0da08 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -25,7 +25,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/tmp/temperature_archived = 0 var/volume = CELL_VOLUME //liters var/last_share = 0 - /// The fire key contains information that might determine the volume of hotspots. + /// Tells us what reactions have happened in our gasmix. Assoc list of reaction - moles reacted pair. var/list/reaction_results /// Used for analyzer feedback - not initialized until its used var/list/analyzer_results @@ -164,6 +164,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ASSERT_GAS(giver_id, src) cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + SEND_SIGNAL(src, COMSIG_GASMIX_MERGED) return TRUE ///Proportionally removes amount of gas from the gas_mixture. @@ -186,6 +187,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) cached_gases[id][MOLES] -= removed_gases[id][MOLES] garbage_collect() + SEND_SIGNAL(src, COMSIG_GASMIX_REMOVED) return removed ///Proportionally removes amount of gas from the gas_mixture. @@ -208,6 +210,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) garbage_collect() + SEND_SIGNAL(src, COMSIG_GASMIX_REMOVED) return removed ///Removes an amount of a specific gas from the gas_mixture. @@ -534,6 +537,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) if(.) //If we changed the mix to any degree garbage_collect() + SEND_SIGNAL(src, COMSIG_GASMIX_REACTED) /** diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 83aad86e6fe..548c1aab062 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -1,4 +1,5 @@ -//All defines used in reactions are located in ..\__DEFINES\reactions.dm +//Most other defines used in reactions are located in ..\__DEFINES\reactions.dm +#define SET_REACTION_RESULTS(amount) air.reaction_results[type] = amount /proc/init_gas_reactions() var/list/priority_reactions = list() @@ -52,6 +53,8 @@ var/priority_group var/name = "reaction" var/id = "r" + /// Whether the presence of our reaction should make fires bigger or not. + var/expands_hotspot = FALSE /datum/gas_reaction/New() init_reqs() @@ -75,10 +78,12 @@ . = NO_REACTION if (air.temperature <= WATER_VAPOR_FREEZE) if(location?.freeze_turf()) + SET_REACTION_RESULTS(0) . = REACTING else if(air.temperature <= T20C + 10) if(location?.water_vapor_gas_act()) air.gases[/datum/gas/water_vapor][MOLES] -= MOLES_GAS_VISIBLE + SET_REACTION_RESULTS(MOLES_GAS_VISIBLE) . = REACTING //N2O decomposition. @@ -100,31 +105,32 @@ var/temperature = air.temperature var/burned_fuel = 0 - burned_fuel = max(0,0.00002 * (temperature - (0.00001 * (temperature**2)))) * cached_gases[/datum/gas/nitrous_oxide][MOLES] - if(cached_gases[/datum/gas/nitrous_oxide][MOLES] - burned_fuel < 0) + if(!burned_fuel || cached_gases[/datum/gas/nitrous_oxide][MOLES] - burned_fuel < 0) return NO_REACTION + cached_gases[/datum/gas/nitrous_oxide][MOLES] -= burned_fuel - if(burned_fuel) - energy_released += (N2O_DECOMPOSITION_ENERGY_RELEASED * burned_fuel) + ASSERT_GAS(/datum/gas/oxygen, air) + cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel * 0.5 + ASSERT_GAS(/datum/gas/nitrogen, air) + cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel - ASSERT_GAS(/datum/gas/oxygen, air) - cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel * 0.5 - ASSERT_GAS(/datum/gas/nitrogen, air) - cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel + energy_released += (N2O_DECOMPOSITION_ENERGY_RELEASED * burned_fuel) + SET_REACTION_RESULTS(burned_fuel) - var/new_heat_capacity = air.heat_capacity() - if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) - air.temperature = (temperature * old_heat_capacity + energy_released) / new_heat_capacity - return REACTING - return NO_REACTION + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.temperature = (temperature * old_heat_capacity + energy_released) / new_heat_capacity + + return REACTING //tritium combustion: combustion of oxygen and tritium (treated as hydrocarbons). creates hotspots. exothermic /datum/gas_reaction/tritfire priority_group = PRIORITY_FIRE name = "Tritium Combustion" id = "tritfire" + expands_hotspot = TRUE /datum/gas_reaction/tritfire/init_reqs() requirements = list( @@ -138,8 +144,6 @@ var/old_heat_capacity = air.heat_capacity() var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow var/temperature = air.temperature - var/list/cached_results = air.reaction_results - cached_results["fire"] = 0 var/turf/open/location if(istype(holder,/datum/pipeline)) //Find the tile the reaction is occuring on, or a random part of the network if it's a pipenet. var/datum/pipeline/pipenet = holder @@ -155,7 +159,7 @@ cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel / TRITIUM_BURN_OXY_FACTOR energy_released += (FIRE_HYDROGEN_ENERGY_WEAK * burned_fuel) - cached_results["fire"] += burned_fuel + SET_REACTION_RESULTS(burned_fuel) else burned_fuel = cached_gases[/datum/gas/tritium][MOLES] @@ -167,7 +171,7 @@ cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel / TRITIUM_BURN_TRIT_FACTOR energy_released += (FIRE_HYDROGEN_ENERGY_RELEASED * burned_fuel) - cached_results["fire"] += burned_fuel * 10 + SET_REACTION_RESULTS(burned_fuel * 10) // This is actually a lie. We use 10x less moles here but make 10x more energy. if(location && prob(10) && burned_fuel > TRITIUM_MINIMUM_RADIATION_ENERGY && energy_released > FIRE_HYDROGEN_ENERGY_RELEASED * air.volume / 2500) //Reduces chances of radiation getting released from the tritium getting formed from oxygen rich plasmafires in waste. radiation_pulse(location, max_range = min(6 + sqrt(energy_released / FIRE_HYDROGEN_ENERGY_RELEASED) / 4, 20), threshold = 15 * INVERSE(15 + energy_released / FIRE_HYDROGEN_ENERGY_RELEASED), chance = 100 * (1 - 0.5 ** (energy_released / (600 * FIRE_HYDROGEN_ENERGY_RELEASED)))) @@ -182,13 +186,14 @@ if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST) location.hotspot_expose(temperature, CELL_VOLUME) - return cached_results["fire"] ? REACTING : NO_REACTION + return energy_released ? REACTING : NO_REACTION //plasma combustion: combustion of oxygen and plasma (treated as hydrocarbons). creates hotspots. exothermic /datum/gas_reaction/plasmafire priority_group = PRIORITY_FIRE name = "Plasma Combustion" id = "plasmafire" + expands_hotspot = TRUE /datum/gas_reaction/plasmafire/init_reqs() requirements = list( @@ -202,8 +207,6 @@ var/old_heat_capacity = air.heat_capacity() var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow var/temperature = air.temperature - var/list/cached_results = air.reaction_results - cached_results["fire"] = 0 var/turf/open/location = isturf(holder) ? holder : null //Handle plasma burning @@ -242,7 +245,7 @@ energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate) - cached_results["fire"] += (plasma_burn_rate) * (1 + oxygen_burn_rate) + SET_REACTION_RESULTS((plasma_burn_rate) * (1 + oxygen_burn_rate)) if(energy_released > 0) var/new_heat_capacity = air.heat_capacity() @@ -255,7 +258,7 @@ if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST) location.hotspot_expose(temperature, CELL_VOLUME) - return cached_results["fire"] ? REACTING : NO_REACTION + return energy_released ? REACTING : NO_REACTION //freon reaction (is not a fire yet) /datum/gas_reaction/freonfire @@ -308,6 +311,7 @@ new /obj/item/stack/sheet/hot_ice(location) energy_released += FIRE_FREON_ENERGY_RELEASED * (freon_burn_rate) + SET_REACTION_RESULTS(freon_burn_rate) . = REACTING if(energy_released < 0) @@ -320,6 +324,7 @@ priority_group = PRIORITY_FIRE name = "Hydrogen Combustion" id = "h2fire" + expands_hotspot = TRUE /datum/gas_reaction/h2fire/init_reqs() requirements = list( @@ -333,8 +338,6 @@ var/old_heat_capacity = air.heat_capacity() var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow var/temperature = air.temperature - var/list/cached_results = air.reaction_results - cached_results["fire"] = 0 var/turf/open/location = isturf(holder) ? holder : null var/burned_fuel = 0 if(cached_gases[/datum/gas/oxygen][MOLES] < cached_gases[/datum/gas/hydrogen][MOLES] || MINIMUM_H2_OXYBURN_ENERGY > air.thermal_energy()) @@ -345,7 +348,7 @@ cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel / HYDROGEN_BURN_OXY_FACTOR energy_released += (FIRE_HYDROGEN_ENERGY_WEAK * burned_fuel) - cached_results["fire"] += burned_fuel + SET_REACTION_RESULTS(burned_fuel) else burned_fuel = cached_gases[/datum/gas/hydrogen][MOLES] @@ -357,7 +360,7 @@ cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel / HYDROGEN_BURN_H2_FACTOR energy_released += (FIRE_HYDROGEN_ENERGY_RELEASED * burned_fuel) - cached_results["fire"] += burned_fuel * 10 + SET_REACTION_RESULTS(burned_fuel * 10) // This is actually a lie. We use 10x less moles here but make 10x more energy. if(energy_released > 0) var/new_heat_capacity = air.heat_capacity() @@ -370,7 +373,7 @@ if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST) location.hotspot_expose(temperature, CELL_VOLUME) - return cached_results["fire"] ? REACTING : NO_REACTION + return energy_released ? REACTING : NO_REACTION /datum/gas_reaction/nitrousformation //formationn of n2o, exothermic, requires bz as catalyst priority_group = PRIORITY_FORMATION @@ -401,6 +404,8 @@ cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficency * 2 cached_gases[/datum/gas/nitrous_oxide][MOLES] += heat_efficency + SET_REACTION_RESULTS(heat_efficency) + if(energy_used > 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -436,6 +441,8 @@ cached_gases[/datum/gas/hydrogen][MOLES] += heat_efficency cached_gases[/datum/gas/nitrogen][MOLES] += heat_efficency + SET_REACTION_RESULTS(heat_efficency) + if(energy_produced> 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -462,15 +469,19 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature / (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 8), cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/nitrogen][MOLES], cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.05)) var/energy_used = heat_efficency * NITRIUM_FORMATION_ENERGY - ASSERT_GAS(/datum/gas/nitrium, air) + if ((cached_gases[/datum/gas/tritium][MOLES] - heat_efficency < 0 ) || (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficency < 0) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.05 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + ASSERT_GAS(/datum/gas/nitrium, air) + cached_gases[/datum/gas/tritium][MOLES] -= heat_efficency cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficency cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy cached_gases[/datum/gas/nitrium][MOLES] += heat_efficency + SET_REACTION_RESULTS(heat_efficency) + if(energy_used > 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -497,6 +508,7 @@ // This slows down in relation to pressure, very quickly. Please don't expect it to be anything more then a snail var/reaction_efficency = min(1 / ((pressure / (0.1 * ONE_ATMOSPHERE)) * (max(cached_gases[/datum/gas/plasma][MOLES] / cached_gases[/datum/gas/nitrous_oxide][MOLES], 1))), cached_gases[/datum/gas/nitrous_oxide][MOLES], cached_gases[/datum/gas/plasma][MOLES] * INVERSE(2)) var/energy_released = 2 * reaction_efficency * FIRE_CARBON_ENERGY_RELEASED + if ((cached_gases[/datum/gas/nitrous_oxide][MOLES] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma][MOLES] - (2 * reaction_efficency) < 0) || energy_released <= 0) //Shouldn't produce gas from nothing. return NO_REACTION @@ -509,6 +521,8 @@ cached_gases[/datum/gas/nitrous_oxide][MOLES] -= reaction_efficency cached_gases[/datum/gas/plasma][MOLES] -= 2 * reaction_efficency + SET_REACTION_RESULTS(reaction_efficency) + if(energy_released > 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -534,6 +548,7 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature / (FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 10), cached_gases[/datum/gas/plasma][MOLES] * INVERSE(1.5), cached_gases[/datum/gas/carbon_dioxide][MOLES] * INVERSE(0.75), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.25)) var/energy_used = heat_efficency * 100 + if ((cached_gases[/datum/gas/plasma][MOLES] - heat_efficency * 1.5 < 0 ) || (cached_gases[/datum/gas/carbon_dioxide][MOLES] - heat_efficency * 0.75 < 0) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.25 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION @@ -543,6 +558,8 @@ cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.25 cached_gases[/datum/gas/freon][MOLES] += heat_efficency * 2.5 + SET_REACTION_RESULTS(heat_efficency * 2.5) + if(energy_used > 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -568,6 +585,7 @@ var/old_heat_capacity = air.heat_capacity() var/nob_formed = min((cached_gases[/datum/gas/nitrogen][MOLES] + cached_gases[/datum/gas/tritium][MOLES]) * 0.01, cached_gases[/datum/gas/tritium][MOLES] * INVERSE(5), cached_gases[/datum/gas/nitrogen][MOLES] * INVERSE(10)) var/energy_produced = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/bz][MOLES], 1))) + if ((cached_gases[/datum/gas/tritium][MOLES] - 5 * nob_formed < 0) || (cached_gases[/datum/gas/nitrogen][MOLES] - 10 * nob_formed < 0)) return NO_REACTION @@ -575,6 +593,8 @@ cached_gases[/datum/gas/nitrogen][MOLES] -= 10 * nob_formed cached_gases[/datum/gas/hypernoblium][MOLES] += nob_formed + SET_REACTION_RESULTS(nob_formed) + if (nob_formed) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -595,6 +615,7 @@ /datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder) var/list/cached_gases = air.gases + // As the name says it, it needs to be dry if(cached_gases[/datum/gas/water_vapor] && cached_gases[/datum/gas/water_vapor][MOLES] / air.total_moles() > 0.1) return NO_REACTION @@ -605,6 +626,8 @@ ASSERT_GAS(/datum/gas/oxygen, air) cached_gases[/datum/gas/oxygen][MOLES] += cleaned_air + SET_REACTION_RESULTS(cleaned_air) + //Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable air.temperature += cleaned_air * 0.002 @@ -629,13 +652,17 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature * 0.01, cached_gases[/datum/gas/tritium][MOLES] * INVERSE(4), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.25)) var/energy_used = heat_efficency * 300 + ASSERT_GAS(/datum/gas/halon, air) if ((cached_gases[/datum/gas/tritium][MOLES] - heat_efficency * 4 < 0 ) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.25 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + cached_gases[/datum/gas/tritium][MOLES] -= heat_efficency * 4 cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.25 cached_gases[/datum/gas/halon][MOLES] += heat_efficency * 4.25 + SET_REACTION_RESULTS(heat_efficency * 4.25) + if(energy_used) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -661,13 +688,17 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature * 0.3, cached_gases[/datum/gas/freon][MOLES] * INVERSE(2.75), cached_gases[/datum/gas/bz][MOLES] * INVERSE(0.25)) var/energy_used = heat_efficency * 9000 + ASSERT_GAS(/datum/gas/healium, air) if ((cached_gases[/datum/gas/freon][MOLES] - heat_efficency * 2.75 < 0 ) || (cached_gases[/datum/gas/bz][MOLES] - heat_efficency * 0.25 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + cached_gases[/datum/gas/freon][MOLES] -= heat_efficency * 2.75 cached_gases[/datum/gas/bz][MOLES] -= heat_efficency * 0.25 cached_gases[/datum/gas/healium][MOLES] += heat_efficency * 3 + SET_REACTION_RESULTS(heat_efficency * 3) + if(energy_used) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -693,13 +724,17 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature * 0.005, cached_gases[/datum/gas/pluoxium][MOLES] * INVERSE(0.2), cached_gases[/datum/gas/hydrogen][MOLES] * INVERSE(2)) var/energy_used = heat_efficency * 650 + ASSERT_GAS(/datum/gas/proto_nitrate, air) if ((cached_gases[/datum/gas/pluoxium][MOLES] - heat_efficency * 0.2 < 0 ) || (cached_gases[/datum/gas/hydrogen][MOLES] - heat_efficency * 2 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + cached_gases[/datum/gas/hydrogen][MOLES] -= heat_efficency * 2 cached_gases[/datum/gas/pluoxium][MOLES] -= heat_efficency * 0.2 cached_gases[/datum/gas/proto_nitrate][MOLES] += heat_efficency * 2.2 + SET_REACTION_RESULTS(heat_efficency * 2.2) + if(energy_used > 0) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -725,13 +760,17 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature * 0.000005, cached_gases[/datum/gas/hypernoblium][MOLES] * INVERSE(0.01), cached_gases[/datum/gas/nitrium][MOLES] * INVERSE(0.5)) var/energy_used = heat_efficency * 5000 - ASSERT_GAS(/datum/gas/zauker, air) + if ((cached_gases[/datum/gas/hypernoblium][MOLES] - heat_efficency * 0.01 < 0 ) || (cached_gases[/datum/gas/nitrium][MOLES] - heat_efficency * 0.5 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + + ASSERT_GAS(/datum/gas/zauker, air) cached_gases[/datum/gas/hypernoblium][MOLES] -= heat_efficency * 0.01 cached_gases[/datum/gas/nitrium][MOLES] -= heat_efficency * 0.5 cached_gases[/datum/gas/zauker][MOLES] += heat_efficency * 0.5 + SET_REACTION_RESULTS(heat_efficency * 0.5) + if(energy_used) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -756,13 +795,17 @@ var/old_heat_capacity = air.heat_capacity() var/heat_efficency = min(temperature / ( FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 10), cached_gases[/datum/gas/halon][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(20)) var/energy_used = heat_efficency * 2500 - ASSERT_GAS(/datum/gas/carbon_dioxide, air) + if ((cached_gases[/datum/gas/halon][MOLES] - heat_efficency < 0 ) || (cached_gases[/datum/gas/oxygen][MOLES] - heat_efficency * 20 < 0)) //Shouldn't produce gas from nothing. return NO_REACTION + + ASSERT_GAS(/datum/gas/carbon_dioxide, air) cached_gases[/datum/gas/halon][MOLES] -= heat_efficency cached_gases[/datum/gas/oxygen][MOLES] -= heat_efficency * 20 cached_gases[/datum/gas/carbon_dioxide][MOLES] += heat_efficency * 5 + SET_REACTION_RESULTS(heat_efficency * 5) + if(energy_used) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -786,24 +829,26 @@ var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow var/temperature = air.temperature var/burned_fuel = 0 + burned_fuel = min(20, cached_gases[/datum/gas/nitrogen][MOLES], cached_gases[/datum/gas/zauker][MOLES]) - if(cached_gases[/datum/gas/zauker][MOLES] - burned_fuel < 0) + if(!burned_fuel || cached_gases[/datum/gas/zauker][MOLES] - burned_fuel < 0) return NO_REACTION + + energy_released += (460 * burned_fuel) + cached_gases[/datum/gas/zauker][MOLES] -= burned_fuel - if(burned_fuel) - energy_released += (460 * burned_fuel) + ASSERT_GAS(/datum/gas/oxygen, air) + ASSERT_GAS(/datum/gas/nitrogen, air) + cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel * 0.3 + cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel * 0.7 - ASSERT_GAS(/datum/gas/oxygen, air) - ASSERT_GAS(/datum/gas/nitrogen, air) - cached_gases[/datum/gas/oxygen][MOLES] += burned_fuel * 0.3 - cached_gases[/datum/gas/nitrogen][MOLES] += burned_fuel * 0.7 + SET_REACTION_RESULTS(burned_fuel) - var/new_heat_capacity = air.heat_capacity() - if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) - air.temperature = max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB) - return REACTING - return NO_REACTION + var/new_heat_capacity = air.heat_capacity() + if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) + air.temperature = max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB) + return REACTING /datum/gas_reaction/proto_nitrate_bz_response priority_group = PRIORITY_PRE_FORMATION @@ -824,6 +869,7 @@ var/list/cached_gases = air.gases var/temperature = air.temperature var/turf/open/location + if(istype(holder,/datum/pipeline)) //Find the tile the reaction is occuring on, or a random part of the network if it's a pipenet. var/datum/pipeline/pipenet = holder location = get_turf(pick(pipenet.members)) @@ -842,6 +888,7 @@ cached_gases[/datum/gas/bz][MOLES] -= consumed_amount energy_released += consumed_amount * 60000 radiation_pulse(location, max_range = min(sqrt(consumed_amount) / 1.5, 20), threshold = 15 * INVERSE(15 + consumed_amount), chance = 50) + SET_REACTION_RESULTS(consumed_amount) for(var/mob/living/carbon/L in location) L.hallucination += consumed_amount if(energy_released) @@ -877,12 +924,14 @@ var produced_amount = min(air.temperature / 34 * (cached_gases[/datum/gas/tritium][MOLES] * cached_gases[/datum/gas/proto_nitrate][MOLES]) / (cached_gases[/datum/gas/tritium][MOLES] + 10 * cached_gases[/datum/gas/proto_nitrate][MOLES]), cached_gases[/datum/gas/tritium][MOLES], cached_gases[/datum/gas/proto_nitrate][MOLES] * INVERSE(0.01)) if(cached_gases[/datum/gas/tritium][MOLES] - produced_amount < 0 || cached_gases[/datum/gas/proto_nitrate][MOLES] - produced_amount * 0.01 < 0) return NO_REACTION + ASSERT_GAS(/datum/gas/hydrogen, air) cached_gases[/datum/gas/tritium][MOLES] -= produced_amount cached_gases[/datum/gas/proto_nitrate][MOLES] -= produced_amount * 0.01 cached_gases[/datum/gas/hydrogen][MOLES] += produced_amount energy_released += produced_amount * 10000 radiation_pulse(location, max_range = min(sqrt(produced_amount) / 1.5, 20), threshold = 15 * INVERSE(15 + produced_amount), chance = 50) + SET_REACTION_RESULTS(produced_amount) if(energy_released) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -906,11 +955,15 @@ var/list/cached_gases = air.gases var/temperature = air.temperature var produced_amount = min(5, cached_gases[/datum/gas/hydrogen][MOLES], cached_gases[/datum/gas/proto_nitrate][MOLES]) + if(cached_gases[/datum/gas/hydrogen][MOLES] - produced_amount < 0) return NO_REACTION + cached_gases[/datum/gas/hydrogen][MOLES] -= produced_amount cached_gases[/datum/gas/proto_nitrate][MOLES] += produced_amount * 0.5 energy_released = produced_amount * 2500 + SET_REACTION_RESULTS(produced_amount) + if(energy_released) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) @@ -936,19 +989,28 @@ var/old_heat_capacity = air.heat_capacity() var/list/cached_gases = air.gases var/temperature = air.temperature - var produced_amount = min(5, cached_gases[/datum/gas/carbon_dioxide][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5), cached_gases[/datum/gas/tritium][MOLES] * INVERSE(0.01)) + var/produced_amount = min(5, cached_gases[/datum/gas/carbon_dioxide][MOLES], cached_gases[/datum/gas/oxygen][MOLES] * INVERSE(0.5), cached_gases[/datum/gas/tritium][MOLES] * INVERSE(0.01)) + if(cached_gases[/datum/gas/carbon_dioxide][MOLES] - produced_amount < 0 || cached_gases[/datum/gas/oxygen][MOLES] - produced_amount * 0.5 < 0 || cached_gases[/datum/gas/tritium][MOLES] - produced_amount * 0.01 < 0) return NO_REACTION + + ASSERT_GAS(/datum/gas/pluoxium, air) + ASSERT_GAS(/datum/gas/hydrogen, air) + cached_gases[/datum/gas/carbon_dioxide][MOLES] -= produced_amount cached_gases[/datum/gas/oxygen][MOLES] -= produced_amount * 0.5 cached_gases[/datum/gas/tritium][MOLES] -= produced_amount * 0.01 - ASSERT_GAS(/datum/gas/pluoxium, air) + cached_gases[/datum/gas/pluoxium][MOLES] += produced_amount - ASSERT_GAS(/datum/gas/hydrogen, air) cached_gases[/datum/gas/hydrogen][MOLES] += produced_amount * 0.01 energy_released += produced_amount * 250 + + SET_REACTION_RESULTS(produced_amount) + if(energy_released) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) air.temperature = max((temperature * old_heat_capacity + energy_released) / new_heat_capacity, TCMB) return REACTING + +#undef SET_REACTION_RESULTS diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index b0d9f2ab035..c3c4e8b4180 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -54,27 +54,86 @@ /obj/effect/decal/cleanable/blood) total_requirement = 3 -/datum/experiment/explosion/calibration - name = "Is This Thing On?" - description = "The engineers from last shift left a notice for us that the doppler array seemed to be malfunctioning. \ - Could you check that it is still working? Any explosion will do!" - required_light = 1 +/datum/experiment/ordnance/explosive/lowyieldbomb + name = "Low-Yield Explosives" + description = "Low-yield explosives may prove useful for our asset protection teams. Perform research and publish papers on this field. Explosion from any source is allowed." + gain = list(10,15,20) + target_amount = list(5,10,20) + experiment_proper = TRUE + sanitized_misc = FALSE + sanitized_reactions = FALSE + allow_any_source = TRUE -/datum/experiment/explosion/maxcap - name = "Mother of God" - description = "A recent outbreak of a blood-cult in a nearby sector necessitates the development of a large explosive. \ - Create a large enough explosion to prove your bomb, we'll be watching." +/datum/experiment/ordnance/explosive/highyieldbomb + name = "High-Yield Explosives" + description = "Several reactions react very energetically and can be utilized for bigger explosives. Perform research and publish papers on this field. Any gas reaction is allowed." + gain = list(10,50,100) + target_amount = list(50,100,300) + experiment_proper = TRUE + sanitized_misc = FALSE + sanitized_reactions = FALSE -/datum/experiment/explosion/medium - name = "Explosive Ordinance Experiment" - description = "Alright, can we really call ourselves professionals if we can't make shit blow up?" - required_heavy = 2 - required_light = 6 +/datum/experiment/ordnance/explosive/hydrogenbomb + name = "Hydrogen Explosives" + description = "Combustion of Hydrogen and it's derivatives can be very powerful. Perform research and publish papers on this field. Only the specified gas reactions are allowed." + gain = list(15,40,60) + target_amount = list(50,75,150) + experiment_proper = TRUE + sanitized_misc = TRUE + sanitized_reactions = TRUE + require_all = FALSE + required_reactions = list(/datum/gas_reaction/h2fire, /datum/gas_reaction/tritfire) -/datum/experiment/explosion/maxcap/New() - required_devastation = GLOB.MAX_EX_DEVESTATION_RANGE - required_heavy = GLOB.MAX_EX_HEAVY_RANGE - required_light = GLOB.MAX_EX_LIGHT_RANGE +/datum/experiment/ordnance/explosive/nobliumbomb + name = "Noblium Explosives" + description = "The formation of Hyper-Noblium is very energetic and can be harnessed for explosives. Perform research and publish papers on this field. Only the specified gas reaction is allowed." + gain = list(15,60,120) + target_amount = list(50,100,300) + experiment_proper = TRUE + sanitized_misc = TRUE + sanitized_reactions = TRUE + required_reactions = list(/datum/gas_reaction/nobliumformation) + +/datum/experiment/ordnance/explosive/pressurebomb + name = "Reactionless Explosives" + description = "Gases with high specific heat can heat up those with a low one and produce a lot of pressure. Perform research and publish papers on this field. No gas reactions are allowed." + gain = list(10,50,100) + target_amount = list(20,75,200) + experiment_proper = TRUE + sanitized_misc = FALSE + sanitized_reactions = TRUE + +/datum/experiment/ordnance/gaseous/nitrium + name = "Nitrium Gas Shells" + description = "The delivery of Nitrium gas into an area of operation might prove useful. Perform research and publish papers on this field." + gain = list(20,60,120) + target_amount = list(20,120,500) + experiment_proper = TRUE + required_gas = /datum/gas/nitrium + +/datum/experiment/ordnance/gaseous/bz + name = "BZ Gas Shells" + description = "The delivery of BZ gas into an area of operation might prove useful. Perform research and publish papers on this field." + gain = list(25,50) + target_amount = list(200,600) + experiment_proper = TRUE + required_gas = /datum/gas/bz + +/datum/experiment/ordnance/gaseous/noblium + name = "Noblium Gas Shells" + description = "The delivery of Noblium gas into an area of operation might prove useful. Perform research and publish papers on this field." + gain = list(10,40,80) + target_amount = list(15,55,250) + experiment_proper = TRUE + required_gas = /datum/gas/hypernoblium + +/datum/experiment/ordnance/gaseous/halon + name = "Halon Gas Shells" + description = "The delivery of Halon gas into an area of operation might prove useful. Perform research and publish papers on this field." + gain = list(10,30,60) + target_amount = list(15,55,250) + experiment_proper = TRUE + required_gas = /datum/gas/halon /datum/experiment/scanning/random/material/meat name = "Biological Material Scanning Experiment" diff --git a/code/modules/experisci/experiment/handlers/experiment_handler.dm b/code/modules/experisci/experiment/handlers/experiment_handler.dm index cc177a79eb4..4909578fede 100644 --- a/code/modules/experisci/experiment/handlers/experiment_handler.dm +++ b/code/modules/experisci/experiment/handlers/experiment_handler.dm @@ -52,8 +52,6 @@ if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/try_run_handheld_experiment) RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/ignored_handheld_experiment_attempt) - if(istype(parent, /obj/machinery/doppler_array)) - RegisterSignal(parent, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, .proc/try_run_doppler_experiment) if(istype(parent, /obj/machinery/destructive_scanner)) RegisterSignal(parent, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, .proc/try_run_destructive_experiment) if(istype(parent, /obj/machinery/computer/operating)) @@ -161,19 +159,6 @@ else playsound(src, 'sound/machines/buzz-sigh.ogg', 25) our_scanner.say("The scan did not result in anything.") -/** - * Hooks on successful explosions on the doppler array this is attached to - */ -/datum/component/experiment_handler/proc/try_run_doppler_experiment(datum/source, turf/epicenter, devastation_range, - heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range -) - SIGNAL_HANDLER - var/atom/movable/our_array = parent - if(action_experiment(source, devastation_range, heavy_impact_range, light_impact_range)) - playsound(src, 'sound/machines/ping.ogg', 25) - else - playsound(src, 'sound/machines/buzz-sigh.ogg', 25) - our_array.say("Insufficient explosion to contribute to current experiment.") /// Hooks on a successful dissection experiment /datum/component/experiment_handler/proc/try_run_dissection_experiment(obj/source, mob/living/target) diff --git a/code/modules/experisci/experiment/types/explosive.dm b/code/modules/experisci/experiment/types/explosive.dm deleted file mode 100644 index 288e4d1f6d9..00000000000 --- a/code/modules/experisci/experiment/types/explosive.dm +++ /dev/null @@ -1,39 +0,0 @@ -/datum/experiment/explosion - name = "Explosive Experiment" - description = "An experiment requiring an explosion to progress" - exp_tag = "Explosion" - performance_hint = "Perform explosive experiments using the research doppler array in the ordnance lab." - /// The required devastation range to complete the experiment - var/required_devastation = 0 - /// The required heavy impact range to complete the experiment - var/required_heavy = 0 - /// The required light impact range to complete the experiment - var/required_light = 0 - /// The last measured devastation range - var/last_devastation - /// The last measured heavy range - var/last_heavy - /// The last measured light range - var/last_light - -/datum/experiment/explosion/is_complete() - return (!required_devastation || required_devastation <= last_devastation) \ - && (!required_heavy || required_heavy <= last_heavy) \ - && (!required_light || required_light <= last_light) - -/datum/experiment/explosion/check_progress() - var/status_message = "You must record an explosion with ranges of at least \ - [required_devastation] devastation, [required_heavy] heavy, and [required_light] light." - if (last_devastation || last_heavy || last_light) - status_message += " The last attempt had ranges of [last_devastation]D/[last_heavy]H/[last_light]L." - . += EXPERIMENT_PROG_BOOL(status_message, is_complete()) - -/datum/experiment/explosion/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, devastation, heavy, light) - last_devastation = devastation - last_heavy = heavy - last_light = light - return is_complete() - -/datum/experiment/explosion/actionable(datum/component/experiment_handler/experiment_handler, devastation, heavy, light) - return !is_complete() - diff --git a/code/modules/experisci/experiment/types/ordnance.dm b/code/modules/experisci/experiment/types/ordnance.dm new file mode 100644 index 00000000000..944fa5a757b --- /dev/null +++ b/code/modules/experisci/experiment/types/ordnance.dm @@ -0,0 +1,48 @@ +/** + * A ordnance experiment datum. What gives the science in the first place. + * One is instantiated by the techweb, another one is also kept on SSresearch for the briefing. + * A disk should contain several of these in a list. Only one should get picked for the final paper. + */ +/datum/experiment/ordnance + name = "Toxin Research" + description = "An experiment conducted in the ordnance subdepartment." + exp_tag = "ordnance" + performance_hint = "Perform or purchase research experiments in the ordnance lab." + /// Lookup experiments are initialized using subtypes, + /// this lets us ignore the ones made for subtyping. + var/experiment_proper = FALSE + /// Projected gain from an experiment. In list form, indexed = tier. + var/list/gain + /// The variable that influences the point equation; Determines when you get the most bang for your buck + /// More rigorously, this is the middle of the derivative bell curve. + /// In list form, indexed = tier. + var/list/target_amount + +/datum/experiment/ordnance/is_complete() + return completed + +/datum/experiment/ordnance/check_progress() + var/status_message = "You must publish or purchase a paper on [name]" + . += EXPERIMENT_PROG_BOOL(status_message, is_complete()) + +/datum/experiment/ordnance/actionable(datum/component/experiment_handler/experiment_handler) + return !is_complete() + +/datum/experiment/ordnance/explosive + /// Typepath to gas reactions that we want. Empty if we do not require any at all or don't want any. + var/list/required_reactions + /// Whether we only accept explosions with reactions that we set in src.required_reactions or do we allow other reactions in. + var/sanitized_reactions = FALSE + /// Whether we want everything in required_reactions to be fulfilled, or only one is needed. + var/require_all = TRUE + /// Whether we want a clear misc or not. + var/sanitized_misc = TRUE + /** + * Whether we allow bombs without properly filled reaction_results or not. + * Setting this to true means the experiment will always pass the cause and effect check. + */ + var/allow_any_source = FALSE + +/datum/experiment/ordnance/gaseous + /// Typepath to the gas we require for the experiment. + var/required_gas diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm index 0cb2a653755..e2e79eed7d0 100644 --- a/code/modules/modular_computers/computers/machinery/console_presets.dm +++ b/code/modules/modular_computers/computers/machinery/console_presets.dm @@ -52,7 +52,7 @@ hard_drive.store_file(new/datum/computer_file/program/chatclient()) hard_drive.store_file(new/datum/computer_file/program/aidiag()) hard_drive.store_file(new/datum/computer_file/program/robocontrol()) - + hard_drive.store_file(new/datum/computer_file/program/scipaper_program()) // ===== COMMAND CONSOLE ===== /obj/machinery/modular_computer/console/preset/command diff --git a/code/modules/modular_computers/file_system/data.dm b/code/modules/modular_computers/file_system/data.dm index e0404e787fb..a67f8c9e02e 100644 --- a/code/modules/modular_computers/file_system/data.dm +++ b/code/modules/modular_computers/file_system/data.dm @@ -1,20 +1,26 @@ -// /data/ files store data in string format. -// They don't contain other logic for now. +/// Data files. Doesn't really hold many important functionalities, here for organization. /datum/computer_file/data - var/stored_data = "" // Stored data in string format. + /// Whether the user will be reminded that the file probably shouldn't be edited. + var/do_not_edit = FALSE filetype = "DAT" - var/block_size = 250 - var/do_not_edit = 0 // Whether the user will be reminded that the file probably shouldn't be edited. -/datum/computer_file/data/clone() - var/datum/computer_file/data/temp = ..() - temp.stored_data = stored_data +// /data/text files store data in string format. +// They don't contain other logic for now. +/datum/computer_file/data/text + filetype = "TXT" + /// Stored data in string format. + var/stored_text = "" + var/block_size = 250 + +/datum/computer_file/data/text/clone() + var/datum/computer_file/data/text/temp = ..() + temp.stored_text = stored_text return temp // Calculates file size from amount of characters in saved string -/datum/computer_file/data/proc/calculate_size() - size = max(1, round(length(stored_data) / block_size)) +/datum/computer_file/data/text/proc/calculate_size() + size = max(1, round(length(stored_text) / block_size)) -/datum/computer_file/data/logfile +/datum/computer_file/data/text/logfile filetype = "LOG" diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm index 2c24c37a04a..e3b4f2ac742 100644 --- a/code/modules/modular_computers/file_system/programs/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/airestorer.dm @@ -1,7 +1,7 @@ /datum/computer_file/program/aidiag filename = "aidiag" filedesc = "NT FRK" - category = PROGRAM_CATEGORY_ROBO + category = PROGRAM_CATEGORY_SCI program_icon_state = "generic" extended_desc = "Firmware Restoration Kit, capable of reconstructing damaged AI systems. Requires direct AI connection via intellicard slot." size = 12 diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm index cebde040f02..9d9bdcc0321 100644 --- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm @@ -1,7 +1,7 @@ /datum/computer_file/program/borg_monitor filename = "siliconnect" filedesc = "SiliConnect" - category = PROGRAM_CATEGORY_ROBO + category = PROGRAM_CATEGORY_SCI ui_header = "borg_mon.gif" program_icon_state = "generic" extended_desc = "This program allows for remote monitoring of station cyborgs." @@ -162,7 +162,7 @@ /datum/computer_file/program/borg_monitor/syndicate filename = "roboverlord" filedesc = "Roboverlord" - category = PROGRAM_CATEGORY_ROBO + category = PROGRAM_CATEGORY_SCI ui_header = "borg_mon.gif" program_icon_state = "generic" extended_desc = "This program allows for remote monitoring of mission-assigned cyborgs." diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index 15dd3278031..a7010efd78a 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -24,7 +24,7 @@ var/list/show_categories = list( PROGRAM_CATEGORY_CREW, PROGRAM_CATEGORY_ENGI, - PROGRAM_CATEGORY_ROBO, + PROGRAM_CATEGORY_SCI, PROGRAM_CATEGORY_SUPL, PROGRAM_CATEGORY_MISC, ) diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm index 20949b0dffd..60a8b20a8f1 100644 --- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm @@ -116,13 +116,13 @@ var/logname = stripped_input(params["log_name"]) if(!logname) return - var/datum/computer_file/data/logfile = new /datum/computer_file/data/logfile() + var/datum/computer_file/data/text/logfile = new() // Now we will generate HTML-compliant file that can actually be viewed/printed. logfile.filename = logname - logfile.stored_data = "\[b\]Logfile dump from NTNRC channel [channel.title]\[/b\]\[BR\]" + logfile.stored_text = "\[b\]Logfile dump from NTNRC channel [channel.title]\[/b\]\[BR\]" for(var/logstring in channel.messages) - logfile.stored_data = "[logfile.stored_data][logstring]\[BR\]" - logfile.stored_data = "[logfile.stored_data]\[b\]Logfile dump completed.\[/b\]" + logfile.stored_text = "[logfile.stored_text][logstring]\[BR\]" + logfile.stored_text = "[logfile.stored_text]\[b\]Logfile dump completed.\[/b\]" logfile.calculate_size() var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD] if(!computer || !hard_drive || !hard_drive.store_file(logfile)) diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 5d2ec5d422f..2ea4acb06f8 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -2,7 +2,7 @@ /datum/computer_file/program/robocontrol filename = "botkeeper" filedesc = "BotKeeper" - category = PROGRAM_CATEGORY_ROBO + category = PROGRAM_CATEGORY_SCI program_icon_state = "robot" extended_desc = "A remote controller used for giving basic commands to non-sentient robots." transfer_access = null diff --git a/code/modules/modular_computers/file_system/programs/robotact.dm b/code/modules/modular_computers/file_system/programs/robotact.dm index 339aa01649b..3e5f83410f2 100644 --- a/code/modules/modular_computers/file_system/programs/robotact.dm +++ b/code/modules/modular_computers/file_system/programs/robotact.dm @@ -1,7 +1,7 @@ /datum/computer_file/program/robotact filename = "robotact" filedesc = "RoboTact" - category = PROGRAM_CATEGORY_ROBO + category = PROGRAM_CATEGORY_SCI extended_desc = "A built-in app for cyborg self-management and diagnostics." ui_header = "robotact.gif" //DEBUG -- new icon before PR program_icon_state = "command" diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index 957498f3d10..097bc881040 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/science filename = "experi_track" filedesc = "Nanotrasen Science Hub" + category = PROGRAM_CATEGORY_SCI program_icon_state = "research" extended_desc = "Connect to the internal science server in order to assist in station research efforts." requires_ntnet = TRUE diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index 4dc40ef4466..d499fca4443 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -48,9 +48,13 @@ if(F in stored_files) return FALSE + SEND_SIGNAL(F, COMSIG_MODULAR_COMPUTER_FILE_ADDING) + F.holder = src stored_files.Add(F) recalculate_size() + + SEND_SIGNAL(F, COMSIG_MODULAR_COMPUTER_FILE_ADDED) return TRUE // Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. @@ -65,8 +69,10 @@ return FALSE if(F in stored_files) + SEND_SIGNAL(F, COMSIG_MODULAR_COMPUTER_FILE_DELETING) stored_files -= F recalculate_size() + SEND_SIGNAL(F, COMSIG_MODULAR_COMPUTER_FILE_DELETED) return TRUE else return FALSE diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 1c4b621da8c..dc196871d0d 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -167,16 +167,17 @@ if(machine_stat & BROKEN) return + var/initial_stat = machine_stat if(powered(power_channel)) - if(machine_stat & NOPOWER) + set_machine_stat(machine_stat & ~NOPOWER) + if(initial_stat & NOPOWER) SEND_SIGNAL(src, COMSIG_MACHINERY_POWER_RESTORED) . = TRUE - set_machine_stat(machine_stat & ~NOPOWER) else - if(!(machine_stat & NOPOWER)) + set_machine_stat(machine_stat | NOPOWER) + if(!(initial_stat & NOPOWER)) SEND_SIGNAL(src, COMSIG_MACHINERY_POWER_LOST) . = TRUE - set_machine_stat(machine_stat | NOPOWER) update_appearance() // connect the machine to a powernet if a node cable or a terminal is present on the turf diff --git a/code/modules/research/anomaly/anomaly_refinery.dm b/code/modules/research/anomaly/anomaly_refinery.dm new file mode 100644 index 00000000000..f29beef1846 --- /dev/null +++ b/code/modules/research/anomaly/anomaly_refinery.dm @@ -0,0 +1,314 @@ +#define MAX_RADIUS_REQUIRED 20 //maxcap +#define MIN_RADIUS_REQUIRED 4 //1, 2, 4 +/// How long the compression test can last before the machine just gives up and ejects the items. +#define COMPRESSION_TEST_TIME (SSOBJ_DT SECONDS * 5) + +/** + * # Explosive compressor machines + * + * The explosive compressor machine used in anomaly core production. + * + * Uses the standard ordnance/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/anomaly_refinery + name = "anomaly refinery" + desc = "An advanced machine capable of implosion-compressing raw anomaly cores into finished artifacts. Also equipped with state of the art bomb prediction software." + circuit = /obj/item/circuitboard/machine/anomaly_refinery + icon = 'icons/obj/machines/research.dmi' + base_icon_state = "explosive_compressor" + icon_state = "explosive_compressor" + density = TRUE + + /// 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 timer that lets us timeout the test. + var/datum/timedevent/timeout_timer + /// Whether we are currently active a bomb and core. + var/active = FALSE + /// The message produced by the explosive compressor at the end of the compression test. + var/test_status = null + /// Determines which tank will be the merge_gases target (destroyed upon testing). + var/obj/item/tank/tank_to_target + + // These vars are used for the explosion simulation and doesn't affect the core detonation. + /// Combined result of the first two tanks. Exists only in our machine. + var/datum/gas_mixture/combined_gasmix + /// Here for the UI, tracks the amounts of reaction that has occured. 1 means valve opened but not reacted. + var/reaction_increment = 0 + +/obj/machinery/research/anomaly_refinery/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, .proc/check_test) + +/obj/machinery/research/anomaly_refinery/assume_air(datum/gas_mixture/giver) + return null // Required to make the TTV not vent directly into the air. + +/** + * 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/anomaly_refinery/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/anomaly_refinery/attackby(obj/item/tool, mob/living/user, params) + if(active) + to_chat(user, span_warning("You can't insert [tool] into [src] while [p_theyre()] currently active.")) + return + if(istype(tool, /obj/item/raw_anomaly_core)) + if(inserted_core) + to_chat(user, span_warning("There is already a core in [src].")) + return + if(!user.transferItemToLoc(tool, src)) + to_chat(user, span_warning("[tool] is stuck to your hand.")) + return + var/obj/item/raw_anomaly_core/raw_core = tool + if(!get_required_radius(raw_core.anomaly_type)) + say("Unfortunately, due to diminishing supplies of condensed anomalous matter, [raw_core] and any cores of its type are no longer of a sufficient quality level to be compressed into a working core.") + inserted_core = raw_core + to_chat(user, span_notice("You insert [raw_core] into [src].")) + return + if(istype(tool, /obj/item/transfer_valve)) + if(inserted_bomb) + to_chat(user, span_warning("There is already a bomb in [src].")) + return + var/obj/item/transfer_valve/valve = tool + if(!valve.ready()) + to_chat(user, span_warning("[valve] is incomplete.")) + return + if(!user.transferItemToLoc(tool, src)) + to_chat(user, span_warning("[tool] is stuck to your hand.")) + return + inserted_bomb = tool + tank_to_target = inserted_bomb.tank_two + to_chat(user, span_notice("You insert [tool] into [src]")) + return + update_appearance() + return ..() + +/obj/machinery/research/anomaly_refinery/wrench_act(mob/living/user, obj/item/tool) + if(!default_unfasten_wrench(user, tool)) + return FALSE + return TRUE + +/obj/machinery/research/anomaly_refinery/screwdriver_act(mob/living/user, obj/item/tool) + if(!default_deconstruction_screwdriver(user, "[base_icon_state]-off", "[base_icon_state]", tool)) + return FALSE + update_appearance() + return TRUE + +/obj/machinery/research/anomaly_refinery/crowbar_act(mob/living/user, obj/item/tool) + if(!default_deconstruction_crowbar(tool)) + return FALSE + return TRUE + +/** + * Starts a compression test. + */ +/obj/machinery/research/anomaly_refinery/proc/start_test() + if(!istype(inserted_core) || !istype(inserted_bomb)) + end_test("ERROR: Missing equpment. Items ejected.") + return + + if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two || !(tank_to_target == inserted_bomb?.tank_one || tank_to_target == inserted_bomb?.tank_two)) + end_test("ERROR: Transfer valve malfunctioning. Items ejected.") + return + + say("Beginning compression test. Opening transfer valve.") + active = TRUE + test_status = null + inserted_bomb.toggle_valve(tank_to_target) + tank_to_target = null + timeout_timer = addtimer(CALLBACK(src, .proc/timeout_test), COMPRESSION_TEST_TIME, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT) + return + +/** + * Ends a compression test. + * + * Arguments: + * - message: A message for the compressor to say when the test ends. + */ +/obj/machinery/research/anomaly_refinery/proc/end_test(message) + active = FALSE + if(inserted_core) + eject_core() + if(inserted_bomb) + eject_bomb() + if(timeout_timer) + QDEL_NULL(timeout_timer) + if(message) + say(message) + return + +/** + * Checks whether an internal explosion was sufficient to compress the core. + */ +/obj/machinery/research/anomaly_refinery/proc/check_test(atom/source, list/arguments) + SIGNAL_HANDLER + if(!inserted_core) + test_status = "ERROR: No core present during detonation." + return COMSIG_CANCEL_EXPLOSION + + var/heavy = arguments[EXARG_KEY_DEV_RANGE] + var/medium = arguments[EXARG_KEY_HEAVY_RANGE] + var/light = arguments[EXARG_KEY_LIGHT_RANGE] + var/explosion_range = max(heavy, medium, light, 0) + var/required_range = get_required_radius(inserted_core.anomaly_type) + var/turf/location = get_turf(src) + + var/cap_multiplier = SSmapping.level_trait(location.z, ZTRAIT_BOMBCAP_MULTIPLIER) + if(isnull(cap_multiplier)) + cap_multiplier = 1 + var/capped_heavy = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, heavy) + var/capped_medium = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, medium) + SSexplosions.shake_the_room(location, explosion_range, (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium) + + if(explosion_range < required_range) + test_status = "Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Items ejected." + return COMSIG_CANCEL_EXPLOSION + + if(test_status) + return COMSIG_CANCEL_EXPLOSION + inserted_core = inserted_core.create_core(src, TRUE, TRUE) + test_status = "Success. Resultant detonation has theoretical range of [explosion_range]. Required radius was [required_range]. Core production complete." + return COMSIG_CANCEL_EXPLOSION + +/** + * Handles timing out the test after a while. + */ +/obj/machinery/research/anomaly_refinery/proc/timeout_test() + timeout_timer = null + if(!test_status) + test_status = "Transfer valve resulted in negligible explosive power. Items ejected." + end_test(test_status) + +/// This is not the real valve opening process. This is the simulated one used for displaying reactions. +/obj/machinery/research/anomaly_refinery/proc/simulate_valve() + if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two) + eject_bomb() + return FALSE + + if(reaction_increment == 0) + var/datum/gas_mixture/first_gasmix = inserted_bomb.tank_one.return_air() + var/datum/gas_mixture/second_gasmix = inserted_bomb.tank_two.return_air() + + combined_gasmix = new(70) + combined_gasmix.volume = first_gasmix.volume + second_gasmix.volume + combined_gasmix.merge(first_gasmix.copy()) + combined_gasmix.merge(second_gasmix.copy()) + else + combined_gasmix.react() + + reaction_increment += 1 + +/// We dont allow incomplete valves to go in but do code in checks for incomplete valves. Just in case. +/obj/machinery/research/anomaly_refinery/proc/eject_bomb(mob/user) + if(!inserted_bomb || active) + return + if(user) + user.put_in_hands(inserted_bomb) + to_chat(user, span_notice("You remove [inserted_bomb] from [src].")) + else + inserted_bomb.forceMove(drop_location()) + combined_gasmix = null + reaction_increment = 0 + +/obj/machinery/research/anomaly_refinery/proc/eject_core(mob/user) + if(!inserted_core || active) + return + if(user) + user.put_in_hands(inserted_core) + to_chat(user, span_notice("You remove [inserted_core] from [src].")) + else + inserted_core.forceMove(drop_location()) + +/// We rely on exited to clear references. +/obj/machinery/research/anomaly_refinery/Exited(atom/movable/gone, direction) + if(gone == inserted_bomb) + inserted_bomb = null + tank_to_target = null + if(gone == inserted_core) + inserted_core = null + . = ..() + +/obj/machinery/research/anomaly_refinery/proc/swap_target() + if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two) + eject_bomb() + return FALSE + tank_to_target = (tank_to_target == inserted_bomb.tank_one) ? inserted_bomb.tank_two : inserted_bomb.tank_one + +/obj/machinery/research/anomaly_refinery/on_deconstruction() + eject_bomb() + eject_core() + return ..() + +/obj/machinery/research/anomaly_refinery/Destroy() + inserted_bomb = null + inserted_core = null + combined_gasmix = null + return ..() + +/obj/machinery/research/anomaly_refinery/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AnomalyRefinery") + ui.open() + +/obj/machinery/research/anomaly_refinery/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("react") + simulate_valve() + if("eject_bomb") + eject_bomb(usr) + if("eject_core") + eject_core(usr) + if("start_implosion") + start_test() + if("swap") + swap_target() + +/obj/machinery/research/anomaly_refinery/ui_data(mob/user) + var/list/data = list() + var/list/parsed_gasmixes = list() + var/obj/item/tank/other_tank + + if(inserted_bomb?.tank_one && inserted_bomb?.tank_two) + other_tank = inserted_bomb.tank_one == tank_to_target ? inserted_bomb.tank_two : inserted_bomb.tank_one + + parsed_gasmixes += list(gas_mixture_parser(tank_to_target?.return_air(), tank_to_target?.name)) + parsed_gasmixes += list(gas_mixture_parser(other_tank?.return_air(), other_tank?.name)) + parsed_gasmixes += list(gas_mixture_parser(combined_gasmix, "Combined Gasmix")) + + data["gasList"] = parsed_gasmixes + + data["valvePresent"] = inserted_bomb ? TRUE : FALSE + data["valveReady"] = (inserted_bomb?.tank_one && inserted_bomb?.tank_two) ? TRUE : FALSE + data["reactionIncrement"] = reaction_increment + + data["core"] = inserted_core ? inserted_core.name : FALSE + data["requiredRadius"] = inserted_core ? get_required_radius(inserted_core.anomaly_type) : null + + data["active"] = active + + return data + +#undef MAX_RADIUS_REQUIRED +#undef MIN_RADIUS_REQUIRED diff --git a/code/modules/research/anomaly/explosive_compressor.dm b/code/modules/research/anomaly/explosive_compressor.dm deleted file mode 100644 index f7c81c1630b..00000000000 --- a/code/modules/research/anomaly/explosive_compressor.dm +++ /dev/null @@ -1,221 +0,0 @@ -#define MAX_RADIUS_REQUIRED 20 //maxcap -#define MIN_RADIUS_REQUIRED 4 //1, 2, 4 -/// How long the compression test can last before the machine just gives up and ejects the items. -#define COMPRESSION_TEST_TIME (SSOBJ_DT SECONDS * 5) - -/** - * # Explosive compressor machines - * - * The explosive compressor machine used in anomaly core production. - * - * Uses the standard ordnance/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 - - /// 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 timer that lets us timeout the test. - var/datum/timedevent/timeout_timer - /// Whether we are currently testing a bomb and core. - var/testing = FALSE - /// The message produced by the explosive compressor at the end of the compression test. - var/test_status = null - /// The last time we did say_requirements(), because someone will inevitably click spam this. - var/last_requirements_say = 0 - -/obj/machinery/research/explosive_compressor/Initialize(mapload) - . = ..() - RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, .proc/check_test) - -/obj/machinery/research/explosive_compressor/Destroy() - UnregisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION) - return ..() - -/obj/machinery/research/explosive_compressor/examine(mob/user) - . = ..() - . += span_notice("Ctrl-Click to remove an inserted core.") - . += span_notice("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/assume_air(datum/gas_mixture/giver) - qdel(giver) - return null // Required to make the TTV not vent directly into the air. - -/obj/machinery/research/explosive_compressor/attack_hand(mob/living/user, list/modifiers) - . = ..() - if(.) - return - if(!inserted_core) - to_chat(user, span_warning("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, span_warning("There is no core inserted.")) - return - if(testing) - to_chat(user, span_warning("You can't remove [inserted_core] from [src] while [p_theyre()] in testing mode.")) - return - inserted_core.forceMove(get_turf(user)) - to_chat(user, span_notice("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/core) - var/required = get_required_radius(core.anomaly_type) - if(isnull(required)) - say("Unfortunately, due to diminishing supplies of condensed anomalous matter, [core] and any cores of its type are no longer of a sufficient quality level to be compressed into a working core.") - else - say("[core] 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/tool, mob/living/user, params) - . = ..() - if(istype(tool, /obj/item/raw_anomaly_core)) - if(inserted_core) - to_chat(user, span_warning("There is already a core in [src].")) - return - if(testing) - to_chat(user, span_warning("You can't insert [tool] into [src] while [p_theyre()] in testing mode.")) - return - if(!user.transferItemToLoc(tool, src)) - to_chat(user, span_warning("[tool] is stuck to your hand.")) - return - inserted_core = tool - to_chat(user, span_notice("You insert [tool] into [src].")) - return - if(istype(tool, /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, span_warning("There is no core inserted in [src]. What would be the point of detonating an implosion without a core?")) - return - if(testing) - to_chat(user, span_warning("You can't insert [tool] into [src] while [p_theyre()] in testing mode.")) - return - var/obj/item/transfer_valve/valve = tool - if(!valve.ready()) - to_chat(user, span_warning("[valve] is incomplete.")) - return - if(!user.transferItemToLoc(tool, src)) - to_chat(user, span_warning("[tool] is stuck to your hand.")) - return - inserted_bomb = tool - to_chat(user, span_notice("You insert [tool] and press the start button.")) - start_test() - - -/** - * Starts a compression test. - */ -/obj/machinery/research/explosive_compressor/proc/start_test() - if(!istype(inserted_core) || !istype(inserted_bomb)) - end_test("ERROR: Missing equpment. Items ejected.") - return - - say("Beginning compression test. Opening transfer valve.") - testing = TRUE - test_status = null - inserted_bomb.toggle_valve() - timeout_timer = addtimer(CALLBACK(src, .proc/timeout_test), COMPRESSION_TEST_TIME, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT) - return - -/** - * Ends a compression test. - * - * Arguments: - * - message: A message for the compressor to say when the test ends. - */ -/obj/machinery/research/explosive_compressor/proc/end_test(message) - if(inserted_core) - inserted_core.forceMove(drop_location()) - inserted_core = null - if(inserted_bomb) - inserted_bomb.forceMove(drop_location()) - inserted_bomb = null - if(timeout_timer) - QDEL_NULL(timeout_timer) - if(message) - say(message) - testing = FALSE - return - -/** - * Checks whether an internal explosion was sufficient to compress the core. - */ -/obj/machinery/research/explosive_compressor/proc/check_test(atom/source, list/arguments) - SIGNAL_HANDLER - . = COMSIG_CANCEL_EXPLOSION - if(!inserted_core) - test_status = "ERROR: No core present during detonation." - return - - var/heavy = arguments[EXARG_KEY_DEV_RANGE] - var/medium = arguments[EXARG_KEY_HEAVY_RANGE] - var/light = arguments[EXARG_KEY_LIGHT_RANGE] - var/explosion_range = max(heavy, medium, light, 0) - var/required_range = get_required_radius(inserted_core.anomaly_type) - var/turf/location = get_turf(src) - - var/cap_multiplier = SSmapping.level_trait(location.z, ZTRAIT_BOMBCAP_MULTIPLIER) - if(isnull(cap_multiplier)) - cap_multiplier = 1 - var/capped_heavy = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, heavy) - var/capped_medium = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, medium) - SSexplosions.shake_the_room(location, explosion_range, (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium) - - if(explosion_range < required_range) - test_status = "Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Core ejected." - return - - if(test_status) - return - inserted_core = inserted_core.create_core(src, TRUE, TRUE) - test_status = "Success. Resultant detonation has theoretical range of [explosion_range]. Required radius was [required_range]. Core production complete." - return - -/** - * Handles timing out the test after a while. - */ -/obj/machinery/research/explosive_compressor/proc/timeout_test() - timeout_timer = null - if(!test_status) - test_status = "Transfer valve resulted in negligible explosive power. Items ejected." - end_test(test_status) - -#undef MAX_RADIUS_REQUIRED -#undef MIN_RADIUS_REQUIRED diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index 65af7ec4355..47d5ada291e 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -97,7 +97,7 @@ Note: Must be placed within 3 tiles of the R&D Console return FALSE SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[TN.id]", "[loaded_item.type]")) if(destroy_item(loaded_item)) - stored_research.boost_with_path(SSresearch.techweb_node_by_id(TN.id), dpath) + stored_research.boost_with_item(SSresearch.techweb_node_by_id(TN.id), dpath) else var/list/point_value = techweb_item_point_check(loaded_item) diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index a225699e40a..18b72f43189 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -197,7 +197,7 @@ if(dotype != FAIL) var/list/nodes = techweb_item_boost_check(process) var/picked = pick_weight(nodes) //This should work. - stored_research.boost_with_path(SSresearch.techweb_node_by_id(picked), process.type) + stored_research.boost_with_item(SSresearch.techweb_node_by_id(picked), process.type) updateUsrDialog() /obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction) diff --git a/code/modules/research/ordnance/_scipaper.dm b/code/modules/research/ordnance/_scipaper.dm new file mode 100644 index 00000000000..5317dd15792 --- /dev/null +++ b/code/modules/research/ordnance/_scipaper.dm @@ -0,0 +1,355 @@ +#define SCIPAPER_COOPERATION_INDEX 1 +#define SCIPAPER_FUNDING_INDEX 2 +#define SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER 0.01 + +/// Scientific paper datum for retrieval and re-reading. A lot of the variables are there for fluff & flavor. +/datum/scientific_paper + /// The title of our paper. + var/title + /// The principal author of our paper. + var/author + /// Whether this paper is co-authored or not. + var/et_alia = FALSE + /// Abstract. + var/abstract + /// The coop and funding gains from the paper. + var/list/gains + /// Experiment typepath. + var/datum/experiment/ordnance/experiment_path + /// The main "score" of a particular experiment. + var/tracked_variable + /** + * Derived from tracked_variable. Used for indexing and to reduce duplicates. + * Only one paper can be published in each tier for each experiment. + */ + var/tier + /// The selected sponsor for our paper. Pathtype form. + var/datum/scientific_partner/partner_path + +/datum/scientific_paper/New() + . = ..() + set_amount() + +/** + * Calculate the gains of an experiment. + * Gain calculation follows a sigmoid curve. + * f(x) = L / (1+e^(-k(x-xo))) + * L is the upper limit. This should be the gain variable * 2. + * k is the steepness. + * x0 is the midpoint. + * x is our tracked variable. + * Returns the expected value of that tier. + */ +/datum/scientific_paper/proc/calculate_gains(calculated_tier) + if(!experiment_path || !tracked_variable) + return FALSE + + var/datum/experiment/ordnance/initialized_experiment = locate(experiment_path) in SSresearch.ordnance_experiments + var/gain = initialized_experiment.gain[calculated_tier] + var/target_amount = initialized_experiment.target_amount[calculated_tier] + /// Steepness is calculated so that f(x=0) is always 1. + var/steepness = log(gain*2 - 1) / target_amount + var/calculated_gain = gain*2 / (1+NUM_E**(-steepness*(tracked_variable-target_amount))) + return calculated_gain + +/// Determine which tier can we publish at. Lower limit for an allowed tier is 10% of gain. Empty list if none are allowed. +/datum/scientific_paper/proc/calculate_tier() + var/list/allowed_tiers = list() + if(!experiment_path || !tracked_variable) + return allowed_tiers + var/datum/experiment/ordnance/initialized_experiment = locate(experiment_path) in SSresearch.ordnance_experiments + for (var/each_tier in 1 to min(length(initialized_experiment.target_amount), length(initialized_experiment.gain))) + var/calculated_gain = calculate_gains(each_tier) + if(calculated_gain > 0.1 * initialized_experiment.gain[each_tier]) + allowed_tiers += each_tier + return allowed_tiers + +/** + * Experiment -> Tier -> Gains + * Experiment -> Partners -> Gains + * Changing anything in the chain means those following it should be recounted. + */ + +/** + * Used when assigning an experiment to a specific paper. + * Failing to provide a proper path, a tracked variable, or a correct data should null every non-fluff data. + * Implement this in the children procs. + */ +/datum/scientific_paper/proc/set_experiment(ex_path = null, variable = null, data = null) + return + +/// Sets a tier for us. Nulls the tier when called without args. Re-counts the amount. +/datum/scientific_paper/proc/set_tier(assigned_tier = null) + tier = null + if(assigned_tier && (assigned_tier in calculate_tier())) + tier = assigned_tier + set_amount() + +/** Sets a specific partner for our paper. Partners give money and boost points. + * Partners exist separately from experiments and wont need to be reset every time something changes. + */ +/datum/scientific_paper/proc/set_partner(new_partner = null) + partner_path = null + if(ispath(new_partner, /datum/scientific_partner)) + var/datum/scientific_partner/partner = locate(new_partner) in SSresearch.scientific_partners + if(experiment_path in partner.accepted_experiments) + partner_path = new_partner + set_amount() + +/** Does the counting for gains. + * Call this whenever experiments/tier/partners are changed. + * Handled automatically, calling this proc externally shouldn't be necessary. + * Also doubles as an initialization for the gains list. + */ +/datum/scientific_paper/proc/set_amount() + gains = list(SCIPAPER_COOPERATION_INDEX = 0, SCIPAPER_FUNDING_INDEX = 0) + if(!tier || !experiment_path || !tracked_variable) + return FALSE + var/gain = calculate_gains(tier) + for (var/gain_type in 1 to gains.len) + gains[gain_type] = gain + if(!partner_path) + continue + var/datum/scientific_partner/partner = locate(partner_path) in SSresearch.scientific_partners + gains[gain_type] *= partner.multipliers[gain_type] + +/** Fully check if our paper have all the required variables, and prevent duplicate papers being published in the same tier. + * Things to check: tier, gain, and partner here. ex_path and record datums in subtypes. + */ +/datum/scientific_paper/proc/allowed_to_publish(datum/techweb/techweb_to_check) + if(!tier || !gains || !partner_path || (0 in gains)) + return FALSE + return techweb_to_check.published_papers[experiment_path][tier] ? FALSE : TRUE + +/datum/scientific_paper/proc/publish_paper(datum/techweb/techweb_to_publish) + autofill() + techweb_to_publish.published_papers[experiment_path][tier] = src + techweb_to_publish.scientific_cooperation[partner_path] += gains[SCIPAPER_COOPERATION_INDEX] + if(istype(techweb_to_publish, /datum/techweb/science)) + var/datum/bank_account/dept_budget = SSeconomy.get_dep_account(ACCOUNT_SCI) + if(dept_budget) + dept_budget.adjust_money(gains[SCIPAPER_FUNDING_INDEX] * 1000) + +/** + * Clones into a new paper type. + * Important (non-fluff) variables will be carried over and should be cleaned with set_experiment by whoever is calling this. + * + * clone_into your own typepath will be like a normal clone. + * + * If you want to subtype this, do it in a way that doesn't mess with the type change. + */ +/datum/scientific_paper/proc/clone_into(typepath) + var/datum/scientific_paper/new_paper = new typepath + new_paper.title = title + new_paper.author = author + new_paper.et_alia = et_alia + new_paper.abstract = abstract + new_paper.gains = gains + new_paper.experiment_path = experiment_path + new_paper.tracked_variable = tracked_variable + new_paper.tier = tier + new_paper.partner_path = partner_path + return new_paper + +/// Returns the formatted, readable gist of our paper in a list. +/datum/scientific_paper/proc/return_gist() + var/list/gist = list() + var/list/transcripted_gains = list(SCIPAPER_COOPERATION_INDEX, SCIPAPER_FUNDING_INDEX) + for (var/index in 1 to transcripted_gains.len) + if (!gains) + transcripted_gains[index] = "None" + continue + switch (round(gains[index])) + if(-INFINITY to 0) + transcripted_gains[index] = "None" + if(1 to 24) + transcripted_gains[index] = "Little" + if(25 to 49) + transcripted_gains[index] = "Moderate" + if(50 to 99) + transcripted_gains[index] = "Significant" + if(100 to INFINITY) + transcripted_gains[index] = "Huge" + else + transcripted_gains[index] = "Undefined" + gist["gains"] = transcripted_gains + gist["title"] = title + gist["author"] = author + gist["etAlia"] = et_alia + gist["abstract"] = abstract + gist["experimentName"] = initial(experiment_path?.name) + gist["tier"] = tier + gist["partner"] = initial(partner_path?.name) + return gist + +/datum/scientific_paper/proc/autofill() + if(!title) + title = "On [initial(experiment_path.name)] - [tier]" + if(!author) + author = "Unknown" + et_alia = FALSE + if(!abstract) + abstract = "Published on [station_time_timestamp()]" + +/datum/scientific_paper/explosive + /** + * Used to check explosive experiments that needs to be unique. + * References a tachyon record where applicable. + */ + var/datum/data/tachyon_record/explosion_record + +/// Check if our explosion has already been published and whether the experiment path is correct or not. +/datum/scientific_paper/explosive/allowed_to_publish(datum/techweb/techweb_to_check) + if(!ispath(experiment_path, /datum/experiment/ordnance/explosive)) + return FALSE + if(!istype(explosion_record)) + return FALSE + var/list/published_papers = techweb_to_check.published_papers + for(var/experiment_path in published_papers) + for(var/datum/scientific_paper/explosive/papers in published_papers[experiment_path]) + var/datum/data/tachyon_record/record_to_check = papers.explosion_record + if(explosion_record.explosion_identifier == record_to_check.explosion_identifier) + return FALSE + . = ..() + +/datum/scientific_paper/explosive/set_experiment(ex_path = null, variable = null, data = null) + var/invalid = FALSE + + invalid = invalid || !ispath(ex_path, /datum/experiment/ordnance/explosive) + invalid = invalid || !variable + invalid = invalid || !istype(data, /datum/data/tachyon_record) + + if(invalid) + experiment_path = null + tracked_variable = null + explosion_record = null + else + experiment_path = ex_path + tracked_variable = variable + explosion_record = data + + set_tier() + set_partner() + +/datum/scientific_paper/explosive/clone_into(typepath) + if(typepath != type) + return ..() + + var/datum/scientific_paper/explosive/new_paper = ..(typepath) + new_paper.explosion_record = explosion_record + return new_paper + +/datum/scientific_paper/gaseous + var/datum/data/compressor_record/compressor_record + +/** + * Check if our record datum is a duplicate or no. + * No index number is necessary because compressor records cant be replicated. + * Also checks the experiment path. + */ +/datum/scientific_paper/gaseous/allowed_to_publish(datum/techweb/techweb_to_check) + if(!ispath(experiment_path, /datum/experiment/ordnance/gaseous)) + return FALSE + if(!istype(compressor_record)) + return FALSE + var/list/published_papers = techweb_to_check.published_papers + for(var/experiment_path in published_papers) + for(var/datum/scientific_paper/gaseous/papers in published_papers[experiment_path]) + if(compressor_record == papers.compressor_record) + return FALSE + . = ..() + +/datum/scientific_paper/gaseous/set_experiment(ex_path = null, variable = null, data = null) + var/invalid = FALSE + + invalid = invalid || !ispath(ex_path, /datum/experiment/ordnance/gaseous) + invalid = invalid || !variable + invalid = invalid || !istype(data, /datum/data/compressor_record) + + if(invalid) + experiment_path = null + tracked_variable = null + compressor_record = null + else + experiment_path = ex_path + tracked_variable = variable + compressor_record = data + + set_tier() + set_partner() + +/datum/scientific_paper/gaseous/clone_into(typepath) + if(typepath != type) + return ..() + + var/datum/scientific_paper/gaseous/new_paper = ..(typepath) + new_paper.compressor_record = compressor_record + return new_paper + +/// Various informations on companies/scientific programs/journals etc that the players can sign on to. +/datum/scientific_partner + var/name + /// Brief explanation of the associated program. Can be used for lore. + var/flufftext + /// Cash and renown multiplier for allying with this partner. + var/list/multipliers = list(SCIPAPER_COOPERATION_INDEX = 1, SCIPAPER_FUNDING_INDEX = 1) + /// List of ordnance experiments that our partner is willing to accept. If this list is not filled it means the partner will accept everything. + var/list/accepted_experiments = list() + /// Associative list of which technology the partner might be able to boost and by how much. + var/list/boosted_nodes = list() + + +/datum/scientific_partner/proc/purchase_boost(datum/techweb/purchasing_techweb, datum/techweb_node/node) + if(!allowed_to_boost(purchasing_techweb, node.id)) + return FALSE + purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC=boosted_nodes[node.id])) + purchasing_techweb.scientific_cooperation[type] -= boosted_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER + return TRUE + +/datum/scientific_partner/proc/allowed_to_boost(datum/techweb/purchasing_techweb, node_id) + if(purchasing_techweb.scientific_cooperation[type] < (boosted_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive + return FALSE + if(!(node_id in purchasing_techweb.get_available_nodes())) // Not currently available + return FALSE + if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boosted_nodes[node_id])) // Already bought or we have a bigger discount + return FALSE + return TRUE + +/datum/computer_file/data/ordnance + /// List of experiments filtered by doppler array or populated by the tank compressor. Experiment path as key, score as value. + var/list/possible_experiments + size = 4 + filetype = "ORD" + +/datum/computer_file/data/ordnance/proc/return_data() + return null + +/datum/computer_file/data/ordnance/clone() + var/datum/computer_file/data/ordnance/temp = ..() + temp.possible_experiments = possible_experiments + return temp + +/datum/computer_file/data/ordnance/explosive + /// Tachyon record, used for an explosive experiment. + var/datum/data/tachyon_record/explosion_record + filetype = "DOP" + +/datum/computer_file/data/ordnance/explosive/return_data() + return explosion_record + +/datum/computer_file/data/ordnance/explosive/clone() + var/datum/computer_file/data/ordnance/explosive/temp = ..() + temp.explosion_record = explosion_record + return temp + +/datum/computer_file/data/ordnance/gaseous + var/datum/data/compressor_record/gas_record + filetype = "COM" + +/datum/computer_file/data/ordnance/gaseous/return_data() + return gas_record + +/datum/computer_file/data/ordnance/gaseous/clone() + var/datum/computer_file/data/ordnance/gaseous/temp = ..() + temp.gas_record = gas_record + return temp diff --git a/code/modules/research/ordnance/doppler_array.dm b/code/modules/research/ordnance/doppler_array.dm new file mode 100644 index 00000000000..d3f0eb30006 --- /dev/null +++ b/code/modules/research/ordnance/doppler_array.dm @@ -0,0 +1,315 @@ +/obj/machinery/doppler_array + name = "tachyon-doppler array" + desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" + circuit = /obj/item/circuitboard/machine/doppler_array + icon = 'icons/obj/machines/research.dmi' + icon_state = "tdoppler" + base_icon_state = "tdoppler" + density = TRUE + verb_say = "states coldly" + var/cooldown = 10 + var/next_announce = 0 + var/max_dist = 150 + /// Number which will be part of the name of the next record, increased by one for each already created record + var/record_number = 1 + /// List of all explosion records in the form of /datum/data/tachyon_record + var/list/records = list() + /// Reference to a disk we are going to print to. + var/obj/item/computer_hardware/hard_drive/portable/inserted_disk + + // Lighting system to better communicate the directions. + light_system = MOVABLE_LIGHT_DIRECTIONAL + light_range = 4 + light_power = 1 + light_color = COLOR_RED + +/obj/machinery/doppler_array/Initialize(mapload) + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) + RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, .proc/update_doppler_light) + RegisterSignal(src, COMSIG_MACHINERY_POWER_RESTORED, .proc/update_doppler_light) + update_doppler_light() + + // Rotation determines the detectable direction. + AddComponent(/datum/component/simple_rotation) + +/datum/data/tachyon_record + name = "Log Recording" + var/timestamp + var/coordinates = "" + var/displacement = 0 + var/factual_radius = list() + var/theory_radius = list() + /// Indexed to length 3 if filled properly. Should be an empty list otherwise. + var/reaction_results = list() + var/explosion_identifier + +/obj/machinery/doppler_array/examine(mob/user) + . = ..() + . += span_notice("It is currently facing [dir2text(dir)]") + +/obj/machinery/doppler_array/attackby(obj/item/item, mob/user, params) + if(istype(item, /obj/item/computer_hardware/hard_drive/portable)) + var/obj/item/computer_hardware/hard_drive/portable/disk = item + eject_disk(user) + if(user.transferItemToLoc(disk, src)) + inserted_disk = disk + return + else + balloon_alert(user, span_warning("[disk] is stuck to your hand.")) + return ..() + return ..() + +/obj/machinery/doppler_array/wrench_act(mob/living/user, obj/item/tool) + if(!default_unfasten_wrench(user, tool)) + return FALSE + return TRUE + +/obj/machinery/doppler_array/screwdriver_act(mob/living/user, obj/item/tool) + if(!default_deconstruction_screwdriver(user, "[base_icon_state]", "[base_icon_state]", tool)) + return FALSE + power_change() + update_appearance() + return TRUE + +/obj/machinery/doppler_array/crowbar_act(mob/living/user, obj/item/tool) + if(!default_deconstruction_crowbar(tool)) + return FALSE + return TRUE + +/// Printing of a record into a disk. +/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record) + if(!record || !inserted_disk) + return + + var/datum/computer_file/data/ordnance/explosive/record_data = new + record_data.filename = "Doppler Array " + record.name //Doppler Array Log Recording #x + record_data.explosion_record = record + record_data.possible_experiments = apply_experiments(record) + + if(inserted_disk.store_file(record_data)) + playsound(src, 'sound/machines/ping.ogg', 25) + else + playsound(src, 'sound/machines/terminal_error.ogg', 25) + +/** + * Checks a specified tachyon record for fitting reactions, then returns a list with + * the experiment typepath as key and score as value. + * The score is the same for all explosive experiments (light radius). + */ +/obj/machinery/doppler_array/proc/apply_experiments(datum/data/tachyon_record/record) + var/list/passed_experiments = list() + var/list/record_reactions = record.reaction_results + + var/list/radius_used = length(record.theory_radius) ? record.theory_radius : record.factual_radius + var/explosion_score = radius_used["shockwave_radius"] + if(!explosion_score) // Dont even bother. + return passed_experiments + + /// Early return for explosions without proper reaction_results. We will append experiments that are always fair game. + if(!record_reactions.len) + for (var/datum/experiment/ordnance/explosive/experiment in SSresearch.ordnance_experiments) + if(experiment.allow_any_source) + passed_experiments += list(experiment.type = explosion_score) + return passed_experiments + + for (var/datum/experiment/ordnance/explosive/experiment in SSresearch.ordnance_experiments) + var/list/experiment_reactions = experiment.required_reactions + /// The reactions in record.reaction_results that corresponds to the required_reactions list. + var/list/fitting_reactions = list() + /// The reactions in required_reactions list that are missing in record.reaction_results. + var/list/missing_reactions = list() + /// The reactions in record.reaction_results that are not present in required_reactions list. + var/list/extra_reactions = list() + + for(var/reaction in experiment_reactions) + if(reaction in record_reactions[TANK_RESULTS_REACTION]) + fitting_reactions += reaction + else + missing_reactions += reaction + + for(var/reaction in record_reactions[TANK_RESULTS_REACTION]) + if(!(reaction in fitting_reactions)) + extra_reactions += reaction + + // Check for extra reactions. + if(experiment.sanitized_reactions && extra_reactions.len) + continue + // Check for misc properties + if(experiment.sanitized_misc && length(record_reactions[TANK_RESULTS_MISC])) + continue + // Check for missing reactions for those who require all. + if(experiment.require_all && missing_reactions.len) + continue + // For those who only need one, check if there is actually one. + if(!experiment.require_all && experiment_reactions.len) + if(!fitting_reactions.len) + continue + + // Suceeded all check, append to the returned list. + passed_experiments += list(experiment.type = explosion_score) + + return passed_experiments + +/// Sensing, recording, and broadcasting of explosion +/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range, explosion_cause, explosion_index) + SIGNAL_HANDLER + var/list/fetched_reaction_results = list() + + if(istype(explosion_cause, /obj/item/tank)) + var/obj/item/tank/exploding_tank = explosion_cause + fetched_reaction_results = exploding_tank.explosion_information() + + if(machine_stat & NOPOWER) + return FALSE + var/turf/zone = get_turf(src) + if(zone.z != epicenter.z) + return FALSE + + if(next_announce > world.time) + return FALSE + next_announce = world.time + cooldown + + if((get_dist(epicenter, zone) > max_dist) || !(get_dir(zone, epicenter) & dir)) + return FALSE + + var/datum/data/tachyon_record/new_record = new /datum/data/tachyon_record() + new_record.name = "Log Recording #[record_number]" + new_record.timestamp = station_time_timestamp() + new_record.coordinates = "[epicenter.x], [epicenter.y]" + new_record.displacement = took + new_record.factual_radius["epicenter_radius"] = devastation_range + new_record.factual_radius["outer_radius"] = heavy_impact_range + new_record.factual_radius["shockwave_radius"] = light_impact_range + new_record.reaction_results = fetched_reaction_results + new_record.explosion_identifier = explosion_index + + var/list/messages = list("Explosive disturbance detected.", + "Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.", + "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].", + ) + + // If the bomb was capped, say its theoretical size. + if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) + messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." + new_record.theory_radius["epicenter_radius"] = orig_dev_range + new_record.theory_radius["outer_radius"] = orig_heavy_range + new_record.theory_radius["shockwave_radius"] = orig_light_range + + for(var/message in messages) + say(message) + + record_number++ + records += new_record + + return TRUE + +/obj/machinery/doppler_array/proc/eject_disk(mob/user) + if(!inserted_disk) + return FALSE + if(user) + user.put_in_hands(inserted_disk) + else + inserted_disk.forceMove(drop_location()) + playsound(src, 'sound/machines/card_slide.ogg', 50) + return TRUE + +/// We rely on exited to clear references. +/obj/machinery/doppler_array/Exited(atom/movable/gone, direction) + if(gone == inserted_disk) + inserted_disk = null + . = ..() + +/obj/machinery/doppler_array/powered() + if(panel_open) + return FALSE + return ..() + +/obj/machinery/doppler_array/update_overlays() + . = ..() + if(panel_open) + . += mutable_appearance(icon, "[base_icon_state]_open") + else + . += mutable_appearance(icon,"[base_icon_state]_cable") + + if(machine_stat & BROKEN) // Probably meant to be used on an indestructible doppler, but this is not implemented. + . += mutable_appearance(icon, "[base_icon_state]_screen-broken") + else if (machine_stat & NOPOWER) + . += mutable_appearance(icon, "[base_icon_state]_screen-off") + +/obj/machinery/doppler_array/on_deconstruction() + eject_disk() + . = ..() + +/obj/machinery/doppler_array/Destroy() + inserted_disk = null + QDEL_NULL(records) //We only want the list nuked, not the contents. + . = ..() + +/obj/machinery/doppler_array/proc/update_doppler_light() + SIGNAL_HANDLER + set_light_on(!(machine_stat & NOPOWER)) + +/obj/machinery/doppler_array/AltClick(mob/user) + return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation + +/obj/machinery/doppler_array/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DopplerArray", name) + ui.open() + +/obj/machinery/doppler_array/ui_data(mob/user) + var/list/data = list() + data["records"] = list() + data["disk"] = inserted_disk?.name + data["storage"] = "[inserted_disk?.used_capacity] / [inserted_disk?.max_capacity] GQ" + for(var/datum/data/tachyon_record/singular_record in records) + var/list/record_data = list( + "name" = singular_record.name, + "timestamp" = singular_record.timestamp, + "coordinates" = singular_record.coordinates, + "displacement" = singular_record.displacement, + "factual_epicenter_radius" = singular_record.factual_radius["epicenter_radius"], + "factual_outer_radius" = singular_record.factual_radius["outer_radius"], + "factual_shockwave_radius" = singular_record.factual_radius["shockwave_radius"], + "theory_epicenter_radius" = singular_record.theory_radius["epicenter_radius"], + "theory_outer_radius" = singular_record.theory_radius["outer_radius"], + "theory_shockwave_radius" = singular_record.theory_radius["shockwave_radius"], + "reaction_results" = list(), + "ref" = REF(singular_record) + ) + + var/list/reaction_data = singular_record.reaction_results + // Make sure the list is indexed first. + if(reaction_data.len) + for (var/path in reaction_data[TANK_RESULTS_REACTION]) + var/datum/gas_reaction/reaction_path = path + record_data["reaction_results"] += initial(reaction_path.name) + if(TANK_MERGE_OVERPRESSURE in reaction_data[TANK_RESULTS_MISC]) + record_data["reaction_results"] += "Tank overpressurized before reaction" + + data["records"] += list(record_data) + return data + +/obj/machinery/doppler_array/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("delete_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + records -= record + return TRUE + if("print_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + print(usr, record) + return TRUE + if("eject_disk") + eject_disk(usr) diff --git a/code/modules/research/ordnance/scipaper_partner.dm b/code/modules/research/ordnance/scipaper_partner.dm new file mode 100644 index 00000000000..897172decde --- /dev/null +++ b/code/modules/research/ordnance/scipaper_partner.dm @@ -0,0 +1,47 @@ +/datum/scientific_partner/spinward_science + name="Spinward Science" + flufftext="A local scientific community started by the diverse inhabitants of the Spinward Sector. Not generally advanced, but they will gladly work with us." + multipliers=list(SCIPAPER_COOPERATION_INDEX = 1, SCIPAPER_FUNDING_INDEX=0.75) + boosted_nodes=list("emp_basic" = 500, "NVGtech" = 1500, "integrated_HUDs" = 500) + +/datum/scientific_partner/baron + name = "Ghost Writing" + flufftext="A nearby research station ran by a very wealthy captain seems to be struggling with their scientific output. They might reward us handsomely if we ghostwrite for them." + multipliers = list(SCIPAPER_COOPERATION_INDEX = 0.25, SCIPAPER_FUNDING_INDEX=5) + boosted_nodes = list("comp_recordkeeping" = 500, "computer_hardware_basic"=500) + +/datum/scientific_partner/defense + name="Defense Partnership" + flufftext="We can work directly for Nanotrasen's \[REDACTED\] division, potentially providing us access with advanced defensive gadgets." + accepted_experiments=list(/datum/experiment/ordnance/explosive/lowyieldbomb, /datum/experiment/ordnance/explosive/highyieldbomb, /datum/experiment/ordnance/explosive/pressurebomb) + boosted_nodes = list("adv_weaponry" = 5000, "weaponry" = 2500, "sec_basic" = 1250, "explosive_weapons"=1250) + +/datum/scientific_partner/energy + name="High-Energy Research" + flufftext="A recently established high-energy research concern started by Nanotrasen. They might be able to assist our energy-based research." + accepted_experiments=list(/datum/experiment/ordnance/explosive/hydrogenbomb, /datum/experiment/ordnance/explosive/nobliumbomb) + boosted_nodes = list("adv_beam_weapons" = 1250, "beam_weapons" = 1250, "electronic_weapons"=1250, "mech_laser"=1250, "mech_laser_heavy"=1250) + +/datum/scientific_partner/engineering + name="Corps of Engineers" + flufftext = "Many engineers are interested in the application of exotic gases in their day-to-day work. They might be able to offer us information on some their gadgets in return." + accepted_experiments=list(/datum/experiment/ordnance/gaseous/halon, /datum/experiment/ordnance/gaseous/noblium, /datum/experiment/ordnance/explosive/lowyieldbomb) + boosted_nodes=list(/datum/techweb_node/adv_engi=2500, /datum/techweb_node/adv_power=1500, /datum/techweb_node/bluespace_power=2000, /datum/techweb_node/high_efficiency=2500, /datum/techweb_node/micro_bluespace=2500) + +/datum/scientific_partner/medical + name="Biological Research Division" + flufftext="A collegiate of the best medical researchers Nanotrason employs. They seem to be interested in the biological effects of some more exotic gases." + accepted_experiments=list(/datum/experiment/ordnance/gaseous/nitrium, /datum/experiment/ordnance/gaseous/bz) + boosted_nodes=list("cyber_organs"=750, "cyber_organs_upgraded"=1000, "genetics"=500, "subdermal_implants"=1250, "adv_biotech"=1000) + +/datum/scientific_partner/ordnance + name="Ordnance Partners" + flufftext="There are other stations tasked with researching the more esoteric reactions. We might be able to exchange some information with them." + accepted_experiments=list(/datum/experiment/ordnance/explosive/pressurebomb, /datum/experiment/ordnance/explosive/nobliumbomb) + boosted_nodes=list("gravity_gun"=1250, "mecha_phazon"=1500, "mech_wormhole_gen"=1250, "bluespace_travel"=1000, "micro_bluespace"=3000, "basic_plasma"=1000, "adv_plasma"=1000) + +/datum/scientific_partner/cold_physics + name="Low Temperature Research" + flufftext="A Nanotrasen division researching matter interactions at very low temperatures. Very interested in our hyper-noblium research." + accepted_experiments=list(/datum/experiment/ordnance/gaseous/noblium, /datum/experiment/ordnance/explosive/nobliumbomb) + boosted_nodes=list("emp_super" = 3000, "emp_adv"=1250, "cryotech"=1500) diff --git a/code/modules/research/ordnance/scipaper_publisher.dm b/code/modules/research/ordnance/scipaper_publisher.dm new file mode 100644 index 00000000000..22dffae8277 --- /dev/null +++ b/code/modules/research/ordnance/scipaper_publisher.dm @@ -0,0 +1,227 @@ +/datum/computer_file/program/scipaper_program + filename = "ntfrontier" + filedesc = "NT Frontier" + category = PROGRAM_CATEGORY_SCI + extended_desc = "Scientific paper publication and navigation software." + requires_ntnet = TRUE + size = 12 + program_icon_state = "research" + tgui_id = "NtosScipaper" + program_icon = "paper-plane" + transfer_access = ACCESS_ORDNANCE + + var/datum/techweb/linked_techweb + /// Unpublished, temporary paper datum. + var/datum/scientific_paper/paper_to_be + /// Here to reduce load. Corresponds to the tab in the UI. + var/current_tab = 1 + /// The file under consideration. + var/datum/computer_file/data/ordnance/selected_file + +/datum/computer_file/program/scipaper_program/New() + . = ..() + paper_to_be = new + +/datum/computer_file/program/scipaper_program/run_program(mob/living/user) + . = ..() + linked_techweb = SSresearch.science_tech + +/datum/computer_file/program/scipaper_program/proc/recheck_file_presence() + if(selected_file in holder.stored_files) + return FALSE + UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED) + selected_file = null + paper_to_be.set_experiment() + +/datum/computer_file/program/scipaper_program/ui_static_data(mob/user) + var/list/data = list() + var/list/parsed_experiments = list() + for (var/datum/experiment/ordnance/experiment in SSresearch.ordnance_experiments) + var/list/singular_experiment = list() + singular_experiment["path"] = experiment.type + singular_experiment["name"] = experiment.name + singular_experiment["description"] = experiment.description + singular_experiment["target"] = experiment.target_amount + if(istype(experiment, /datum/experiment/ordnance/explosive)) + singular_experiment["suffix"] = "tiles" + singular_experiment["prefix"] = "Range" + else if(istype(experiment, /datum/experiment/ordnance/gaseous)) + singular_experiment["suffix"] = "moles" + singular_experiment["prefix"] = "Gas" + parsed_experiments += list(singular_experiment) + + var/list/parsed_partners = list() + for (var/datum/scientific_partner/partner in SSresearch.scientific_partners) + var/list/singular_partner = list() + singular_partner["name"] = partner.name + singular_partner["flufftext"] = partner.flufftext + singular_partner["multipliers"] = partner.multipliers + singular_partner["path"] = partner.type + singular_partner["boostedNodes"] = list() + singular_partner["acceptedExperiments"] = list() + for (var/node_id in partner.boosted_nodes) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id) + singular_partner["boostedNodes"] += list(list("name" = node.display_name, "discount" = partner.boosted_nodes[node_id], "id"=node_id)) + for (var/datum/experiment/ordnance/ordnance_experiment as anything in partner.accepted_experiments) + singular_partner["acceptedExperiments"] += initial(ordnance_experiment.name) + parsed_partners += list(singular_partner) + + data["experimentInformation"] = parsed_experiments + data["partnersInformation"] = parsed_partners + data["coopIndex"] = SCIPAPER_COOPERATION_INDEX + data["fundingIndex"] = SCIPAPER_FUNDING_INDEX + return data + +/datum/computer_file/program/scipaper_program/ui_data() + // Program Headers: + var/list/data = get_header_data() + data["currentTab"] = current_tab + + // First page. Form submission. + if(current_tab == 1) + data["fileList"] = list() + data["expList"] = list() + data["allowedTiers"] = list() + data["allowedPartners"] = list() + // Both the file and experiment list are assoc lists. ID as value, display name as keys. + for(var/datum/computer_file/data/ordnance/ordnance_file in holder.stored_files) + data["fileList"] += list(ordnance_file.filename = ordnance_file.uid) + if(selected_file) + for (var/possible_experiment in selected_file.possible_experiments) + var/datum/experiment/ordnance/experiment = possible_experiment + data["expList"] += list(initial(experiment.name) = experiment) + data["allowedTiers"] = paper_to_be.calculate_tier() + for (var/partner in SSresearch.scientific_partners) + var/datum/scientific_partner/scientific_partner = partner + if(paper_to_be.experiment_path in scientific_partner.accepted_experiments) + data["allowedPartners"] += list(scientific_partner.name = scientific_partner.type) + + data += paper_to_be.return_gist() + data["selectedFile"] = selected_file?.filename + // Renamed both of these to be more topical. + data["selectedExperiment"] = data["experimentName"] + data -= "experimentName" + data["selectedPartner"] = data["partner"] + data -= "partner" + + // Second page. View previous + if(current_tab == 2) + data["publishedPapers"] = list() + for (var/experiment_types in linked_techweb.published_papers) + for (var/datum/scientific_paper/paper in linked_techweb.published_papers[experiment_types]) + data["publishedPapers"] += list(paper.return_gist()) + + if(current_tab == 4) + data["purchaseableBoosts"] = list() + data["relations"] = list() + var/list/visible_nodes = list() + visible_nodes += linked_techweb.get_available_nodes() + visible_nodes += linked_techweb.get_researched_nodes() + data["visibleNodes"] = list() + for (var/id in visible_nodes) + if(visible_nodes[id]) + data["visibleNodes"] += id + + for (var/datum/scientific_partner/partner as anything in SSresearch.scientific_partners) + var/relations = linked_techweb.scientific_cooperation[partner.type] + switch (round(relations / SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // We use points to determine these + if(-INFINITY to 0) + data["relations"][partner.type] = "Nonexistant" + if(1 to 2499) + data["relations"][partner.type] = "Negligible" + if(2500 to 4999) + data["relations"][partner.type] = "Limited" + if(5000 to 9999) + data["relations"][partner.type] = "Cordial" + if(10000 to 19999) + data["relations"][partner.type] = "Partners" + if(20000 to INFINITY) + data["relations"][partner.type] = "Devoted" + else + data["relations"][partner.type] = "Undefined" + data["purchaseableBoosts"][partner.type] = list() + for(var/node_id in linked_techweb.get_available_nodes()) + // Not from our partner + if(!(node_id in partner.boosted_nodes)) + continue + if(!partner.allowed_to_boost(linked_techweb, node_id)) + continue + data["purchaseableBoosts"][partner.type] += node_id + return data + +/datum/computer_file/program/scipaper_program/ui_act(action, params) + . = ..() + if (.) + return + + switch(action) + if("et_alia") + paper_to_be.et_alia = !paper_to_be.et_alia + return TRUE + // Handle the publication + if("publish") + publish() + return TRUE + // For every change in the input, we correspond it with the paper_data list and update it. + if("rewrite") + if(length(params)) + for (var/changed_entry in params) + if (changed_entry == "title") + paper_to_be.title = sanitize(params[changed_entry]) + if (changed_entry == "author") + paper_to_be.author = sanitize(params[changed_entry]) + if (changed_entry == "abstract") + paper_to_be.abstract = sanitize(params[changed_entry]) + return TRUE + if("change_tab") + current_tab = params["new_tab"] + return TRUE + if("select_file") // Selecting new file will necessitate a change in paper type. This will be done on select_experiment and not here. + if(selected_file) + UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED) + paper_to_be.set_experiment() // Clears the paper info. + for(var/datum/computer_file/data/ordnance/ordnance_data in holder.stored_files) + if(ordnance_data.uid == params["selected_uid"]) + selected_file = ordnance_data + RegisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED, .proc/recheck_file_presence) + return TRUE + if("select_experiment") + var/ex_path = text2path(params["selected_expath"]) + var/variable = selected_file.possible_experiments[text2path(params["selected_expath"])] + var/data = null + if(ispath(ex_path, /datum/experiment/ordnance/explosive)) + paper_to_be = paper_to_be.clone_into(/datum/scientific_paper/explosive) + if(ispath(ex_path, /datum/experiment/ordnance/gaseous)) + paper_to_be = paper_to_be.clone_into(/datum/scientific_paper/gaseous) + data = selected_file.return_data() + paper_to_be.set_experiment(ex_path, variable, data) + return TRUE + if("select_tier") + paper_to_be.set_tier(params["selected_tier"]) + return TRUE + if("select_partner") + paper_to_be.set_partner(text2path(params["selected_partner"]), linked_techweb) + return TRUE + if("purchase_boost") + var/datum/scientific_partner/partner = locate(text2path(params["boost_seller"])) in SSresearch.scientific_partners + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(params["purchased_boost"]) + if(partner && node) + if(partner.purchase_boost(linked_techweb, node)) + computer.say("Purchase succesful.") + playsound(computer, 'sound/machines/ping.ogg', 25) + return TRUE + playsound(computer, 'sound/machines/terminal_error.ogg', 25) + return FALSE + +/// Publication and adding points. +/datum/computer_file/program/scipaper_program/proc/publish() + if(linked_techweb.add_scientific_paper(paper_to_be)) + computer.say("\"[paper_to_be.title]\" has been published!") + paper_to_be = new + UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED) + selected_file = null + SStgui.update_uis(src) + playsound(computer, 'sound/machines/ping.ogg', 25) + return TRUE + playsound(computer, 'sound/machines/terminal_error.ogg', 25) + return FALSE diff --git a/code/modules/research/ordnance/tank_compressor.dm b/code/modules/research/ordnance/tank_compressor.dm new file mode 100644 index 00000000000..b2061f45695 --- /dev/null +++ b/code/modules/research/ordnance/tank_compressor.dm @@ -0,0 +1,365 @@ +#define TANK_COMPRESSOR_PRESSURE_LIMIT 5000 +#define TANK_COMPRESSOR_MAX_TRANSFER_RATE 20 +#define SIGNIFICANT_AMOUNT_OF_MOLES 10 + +/obj/machinery/atmospherics/components/binary/tank_compressor + name = "Tank Compressor" + desc = "Heavy duty shielded air compressor designed to pressurize tanks above the safe limit." + circuit = /obj/item/circuitboard/machine/tank_compressor + icon = 'icons/obj/machines/research.dmi' + base_icon_state = "tank_compressor" + icon_state = "tank_compressor-open" + density = TRUE + + var/active = FALSE + var/transfer_rate = TANK_COMPRESSOR_MAX_TRANSFER_RATE + var/datum/gas_mixture/leaked_gas_buffer + var/list/compressor_record + var/last_recorded_pressure = 0 + var/record_number = 1 + var/obj/item/tank/inserted_tank + /// Reference to a disk we are going to print to. + var/obj/item/computer_hardware/hard_drive/portable/inserted_disk + + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + +/obj/machinery/atmospherics/components/binary/tank_compressor/Initialize(mapload) + . = ..() + leaked_gas_buffer = new(200) + compressor_record = list() + +/obj/machinery/atmospherics/components/binary/tank_compressor/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, .proc/explosion_handle) + +/obj/machinery/atmospherics/components/binary/tank_compressor/examine() + . = ..() + . += "This one is rated for up to [TANK_COMPRESSOR_PRESSURE_LIMIT] kPa." + +/// Stores the record of the gas data for a significant enough tank leak +/datum/data/compressor_record + /// Tank Name + var/experiment_source + /// Key: Path, Value: Moles + var/list/gas_data = list() + var/timestamp + +/obj/machinery/atmospherics/components/binary/tank_compressor/attackby(obj/item/item, mob/living/user) + if (panel_open) + return ..() + + if(istype(item, /obj/item/tank)) + var/obj/item/tank/tank_item = item + if(inserted_tank) + if(!eject_tank(user)) + balloon_alert(user, span_warning("[inserted_tank] is stuck inside.")) + return ..() + if(!user.transferItemToLoc(tank_item, src)) + balloon_alert(user, span_warning("[tank_item] is stuck to your hand.")) + return ..() + inserted_tank = tank_item + last_recorded_pressure = 0 + RegisterSignal(inserted_tank, COMSIG_PARENT_QDELETING, .proc/tank_destruction) + update_appearance() + return + if(istype(item, /obj/item/computer_hardware/hard_drive/portable)) + var/obj/item/computer_hardware/hard_drive/portable/attacking_disk = item + eject_disk(user) + if(user.transferItemToLoc(attacking_disk, src)) + inserted_disk = attacking_disk + else + balloon_alert(user, span_warning("[attacking_disk] is stuck to your hand.")) + return + return ..() + +/obj/machinery/atmospherics/components/binary/tank_compressor/wrench_act(mob/living/user, obj/item/tool) + if(active || inserted_tank) + return FALSE + if(!default_change_direction_wrench(user, tool)) + return FALSE + return TRUE + +/obj/machinery/atmospherics/components/binary/tank_compressor/screwdriver_act(mob/living/user, obj/item/tool) + if(active || inserted_tank) + return FALSE + if(!default_deconstruction_screwdriver(user, "[base_icon_state]-open", "[base_icon_state]-open", tool)) + return FALSE + update_appearance() + return TRUE + +/obj/machinery/atmospherics/components/binary/tank_compressor/crowbar_act(mob/living/user, obj/item/tool) + if(active || inserted_tank) + return FALSE + if(!default_deconstruction_crowbar(tool)) + return FALSE + return TRUE + +/obj/machinery/atmospherics/components/binary/tank_compressor/default_change_direction_wrench(mob/user, obj/item/wrench) + . = ..() + if(!.) + return + + // Disconnect our partner. + if(nodes[1]) + nodes[1].disconnect(src) + nodes[1] = null + if(parents[1]) + nullify_pipenet(parents[1]) + if(nodes[2]) + nodes[2].disconnect(src) + nodes[2] = null + if(parents[2]) + nullify_pipenet(parents[2]) + set_init_directions() + // Connect to a new one. + atmos_init() + if(nodes[1]) + nodes[1].atmos_init() + nodes[1].add_member(src) + if(nodes[2]) + nodes[2].atmos_init() + nodes[2].add_member(src) + SSair.add_to_rebuild_queue(src) + update_appearance() + +/// Glorified volume pump. +/obj/machinery/atmospherics/components/binary/tank_compressor/process_atmos() + var/datum/gas_mixture/input_air = airs[2] + if(!input_air?.total_moles() || !active || !transfer_rate || !inserted_tank) + return + + var/datum/gas_mixture/tank_air = inserted_tank.return_air() + if(!tank_air) + return + + if(input_air.return_pressure() < 0.01 || tank_air.return_pressure() > TANK_COMPRESSOR_PRESSURE_LIMIT) + return + + /// Prevent pumping if tank is taking damage but still below pressure limit. Here to prevent exploiting the buffer system. + if((inserted_tank.leaking) && (tank_air.return_pressure() <= TANK_LEAK_PRESSURE)) + active = FALSE + return + + var/datum/gas_mixture/removed = input_air.remove_ratio(transfer_rate / input_air.volume) + if(!removed) + return + + tank_air.merge(removed) + update_parents() + +/obj/machinery/atmospherics/components/binary/tank_compressor/assume_air(datum/gas_mixture/giver) + if(!leaked_gas_buffer) + return ..() + leaked_gas_buffer.merge(giver) + return TRUE + +/// Recording of last pressure of the tank. Ran when a tank is about to explode or disintegrate. We dont care about last pressure if the tank is ejected. +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/tank_destruction() + SIGNAL_HANDLER + if(inserted_tank.get_integrity() > 0) + return + flush_buffer() + var/datum/gas_mixture/tank_air = inserted_tank.return_air() + last_recorded_pressure = tank_air.return_pressure() + active = FALSE + return + +/// Use this to absorb explosions. +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/explosion_handle(atom/source, list/arguments) + SIGNAL_HANDLER + say("Internal explosion detected and absorbed.") + SSexplosions.shake_the_room(get_turf(src), 1, 8, 0.5, 0.25, FALSE) + return COMSIG_CANCEL_EXPLOSION + +/** + * Everytime a tank is destroyed or a new tank is inserted, our buffer is flushed. + * Mole requirements in experiments are tracked by buffer data. + */ +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/flush_buffer() + if(!leaked_gas_buffer.total_moles()) + return + if(leaked_gas_buffer.total_moles() > SIGNIFICANT_AMOUNT_OF_MOLES) + record_data() + else + say("Buffer data discarded. Required moles for storage: [SIGNIFICANT_AMOUNT_OF_MOLES] moles.") + var/datum/gas_mixture/removed = leaked_gas_buffer.remove_ratio(1) + airs[1].merge(removed) + say("Gas stored in buffer flushed to output port. Compressor ready to start the next experiment.") + +/// This proc should be called whenever we want to store our buffer data. +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/record_data() + var/datum/data/compressor_record/new_record = new() + new_record.name = "Log Recording #[record_number]" + new_record.experiment_source = inserted_tank.name + new_record.timestamp = station_time_timestamp() + for(var/gas_path in leaked_gas_buffer.gases) + new_record.gas_data[gas_path] = leaked_gas_buffer.gases[gas_path][MOLES] + + compressor_record += new_record + record_number += 1 + say("Buffer data stored.") + +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/apply_experiments(datum/data/compressor_record/record) + var/list/passed_experiments = list() + var/list/gas_data = record.gas_data + + for (var/datum/experiment/ordnance/gaseous/experiment in SSresearch.ordnance_experiments) + if(experiment.required_gas in gas_data) + if(gas_data[experiment.required_gas] > MINIMUM_MOLE_COUNT) + passed_experiments += list(experiment.type = gas_data[experiment.required_gas]) + + return passed_experiments + +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/print(mob/user, datum/data/compressor_record/record) + if(!record || !inserted_disk) + return + + var/datum/computer_file/data/ordnance/gaseous/record_data = new + record_data.filename = "Gas Compressor " + record.name //Gas Compressor Log Recording #x + record_data.gas_record = record + record_data.possible_experiments = apply_experiments(record) + + if(inserted_disk.store_file(record_data)) + playsound(src, 'sound/machines/ping.ogg', 25) + else + playsound(src, 'sound/machines/terminal_error.ogg', 25) + +/// Ejecting a tank. Also called on insertion to clear previous tanks. +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/eject_tank(mob/user) + if(!inserted_tank) + return FALSE + var/datum/gas_mixture/tank_air = inserted_tank.return_air() + if(!tank_air.return_pressure() >= PUMP_MAX_PRESSURE) + return FALSE + flush_buffer() + if(user) + user.put_in_hands(inserted_tank) + else + inserted_tank.forceMove(drop_location()) + active = FALSE + return TRUE + +/obj/machinery/atmospherics/components/binary/tank_compressor/proc/eject_disk(mob/user) + if(!inserted_disk) + return FALSE + if(user) + user.put_in_hands(inserted_disk) + else + inserted_disk.forceMove(drop_location()) + playsound(src, 'sound/machines/card_slide.ogg', 50) + return TRUE + +/// We rely on exited to clear references. +/obj/machinery/atmospherics/components/binary/tank_compressor/Exited(atom/movable/gone, direction) + if(gone == inserted_disk) + inserted_disk = null + if(gone == inserted_tank) + UnregisterSignal(inserted_tank, COMSIG_PARENT_QDELETING) + inserted_tank = null + update_appearance() + . = ..() + +/obj/machinery/atmospherics/components/binary/tank_compressor/on_deconstruction() + eject_tank() + eject_disk() + return ..() + +/obj/machinery/atmospherics/components/binary/tank_compressor/Destroy() + inserted_tank = null + inserted_disk = null + leaked_gas_buffer = null + QDEL_NULL(compressor_record) //We only want the list nuked, not the contents. + return ..() + +/obj/machinery/atmospherics/components/binary/tank_compressor/update_icon_state() + if(istype(inserted_tank)) + icon_state = "[base_icon_state]-closed" + else + icon_state = "[base_icon_state]-open" + return ..() + +/obj/machinery/atmospherics/components/binary/tank_compressor/update_overlays() + . = ..() + . += get_pipe_image(icon, "[base_icon_state]-pipe", dir, COLOR_BLUE, piping_layer) + . += get_pipe_image(icon, "[base_icon_state]-pipe", turn(dir, 180), COLOR_RED, piping_layer) + if(!istype(inserted_tank)) + . += mutable_appearance(icon, "[base_icon_state]-doors",) + if(panel_open) + . += mutable_appearance(icon, "[base_icon_state]-maintenance") + else + . += mutable_appearance(icon, "[base_icon_state]-cables") + +/obj/machinery/atmospherics/components/binary/tank_compressor/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TankCompressor") + ui.open() + +/obj/machinery/atmospherics/components/binary/tank_compressor/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("change_rate") + transfer_rate = clamp(params["target"], 0, TANK_COMPRESSOR_MAX_TRANSFER_RATE) + if("toggle_injection") + active = !active + if("eject_tank") + eject_tank(usr) + if("eject_disk") + eject_disk(usr) + if("delete_record") + var/datum/data/compressor_record/record = locate(params["ref"]) in compressor_record + if(!compressor_record || !(record in compressor_record)) + return + compressor_record -= record + return TRUE + if("print_record") + var/datum/data/compressor_record/record = locate(params["ref"]) in compressor_record + if(!compressor_record || !(record in compressor_record)) + return + print(usr, record) + return TRUE + +/obj/machinery/atmospherics/components/binary/tank_compressor/ui_static_data() + var/list/data = list( + "maxTransfer" = TANK_COMPRESSOR_MAX_TRANSFER_RATE, + "leakPressure" = round(TANK_LEAK_PRESSURE), + "fragmentPressure" = round(TANK_FRAGMENT_PRESSURE), + "ejectPressure" = PUMP_MAX_PRESSURE + ) + return data + +/obj/machinery/atmospherics/components/binary/tank_compressor/ui_data(mob/user) + var/list/data = list() + data["tankPresent"] = inserted_tank ? TRUE : FALSE + var/datum/gas_mixture/tank_air = inserted_tank?.return_air() + data["tankPressure"] = tank_air?.return_pressure() + data["leaking"] = inserted_tank?.leaking + + data["active"] = active + data["transferRate"] = transfer_rate + data["lastPressure"] = last_recorded_pressure + + data["portData"] = list() + data["portData"] += list(gas_mixture_parser(airs[2], "Input Port")) + data["portData"] += list(gas_mixture_parser(airs[1], "Ouput Port")) + data["portData"] += list(gas_mixture_parser(leaked_gas_buffer, "Gas Buffer")) + + data["disk"] = inserted_disk?.name + data["storage"] = "[inserted_disk?.used_capacity] / [inserted_disk?.max_capacity] GQ" + data["records"] = list() + for (var/datum/data/compressor_record/record in compressor_record) + var/list/single_record_data = list( + "ref" = REF(record), + "name" = record.name, + "source" = record.experiment_source, + "timestamp" = record.timestamp, + "gases" = list() + ) + for (var/path in record.gas_data) + var/datum/gas/gas_path = path + single_record_data["gases"] += list(initial(gas_path.name) = record.gas_data[gas_path]) + data["records"] += list(single_record_data) + return data diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index 351e26b980a..abccd710972 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -29,7 +29,6 @@ var/id = "generic" /// IC logs var/list/research_logs = list() - var/largest_bomb_value = 0 /// Organization name, used for display var/organization = "Third-Party" /// Current per-second production, used for display only. @@ -42,6 +41,18 @@ var/list/available_experiments = list() /// Completed experiments var/list/completed_experiments = list() + + /** + * Assoc list of relationships with various partners + * scientific_cooperation[partner_typepath] = relationship + */ + var/list/scientific_cooperation + /** + * Assoc list of papers already published by the crew. + * published_papers[experiment_typepath][tier] = paper + * Filled with nulls on init, populated only on publication. + */ + var/list/published_papers /datum/techweb/New() SSresearch.techwebs += src @@ -49,8 +60,8 @@ var/datum/techweb_node/DN = SSresearch.techweb_node_by_id(i) research_node(DN, TRUE, FALSE, FALSE) hidden_nodes = SSresearch.techweb_nodes_hidden.Copy() + initialize_published_papers() return ..() - /datum/techweb/admin id = "ADMIN" organization = "CentCom" @@ -337,15 +348,24 @@ researched_nodes -= node.id recalculate_nodes(TRUE) //Fully rebuild the tree. -/datum/techweb/proc/boost_with_path(datum/techweb_node/N, itempath) - if(!istype(N) || !ispath(itempath)) +/// Boosts a techweb node. +/datum/techweb/proc/boost_techweb_node(datum/techweb_node/node, list/pointlist) + if(!istype(node)) return FALSE - LAZYINITLIST(boosted_nodes[N.id]) - for(var/i in N.boost_item_paths[itempath]) - boosted_nodes[N.id][i] = max(boosted_nodes[N.id][i], N.boost_item_paths[itempath][i]) - if(N.autounlock_by_boost) - hidden_nodes -= N.id - update_node_status(N) + LAZYINITLIST(boosted_nodes[node.id]) + for(var/point_type in pointlist) + boosted_nodes[node.id][point_type] = max(boosted_nodes[node.id][point_type], pointlist[point_type]) + if(node.autounlock_by_boost) + hidden_nodes -= node.id + update_node_status(node) + return TRUE + +/// Boosts a techweb node by using items. +/datum/techweb/proc/boost_with_item(datum/techweb_node/node, itempath) + if(!istype(node) || !ispath(itempath)) + return FALSE + var/list/boost_amount = node.boost_item_paths[itempath] + boost_techweb_node(node, boost_amount) return TRUE /datum/techweb/proc/update_tiers(datum/techweb_node/base) @@ -478,3 +498,41 @@ /datum/techweb/specialized/autounlocking/exofab allowed_buildtypes = MECHFAB + +/// Fill published_papers with nulls. +/datum/techweb/proc/initialize_published_papers() + published_papers = list() + scientific_cooperation = list() + for (var/datum/experiment/ordnance/ordnance_experiment as anything in SSresearch.ordnance_experiments) + var/max_tier = min(length(ordnance_experiment.gain), length(ordnance_experiment.target_amount)) + var/list/tier_list[max_tier] + published_papers[ordnance_experiment.type] = tier_list + for (var/datum/scientific_partner/partner as anything in SSresearch.scientific_partners) + scientific_cooperation[partner.type] = 0 + +/// Publish the paper into our techweb. Cancel if we are not allowed to. +/datum/techweb/proc/add_scientific_paper(datum/scientific_paper/paper_to_add) + if(!paper_to_add.allowed_to_publish(src)) + return FALSE + paper_to_add.publish_paper(src) + + // If we haven't published a paper in the same topic ... + if(locate(paper_to_add.experiment_path) in published_papers[paper_to_add.experiment_path]) + return TRUE + // Quickly add and complete it. + // PS: It's also possible to use add_experiment() together with a list/available_experiments check + // to determine if we need to run all this, but this pretty much does the same while only needing one evaluation. + + add_experiment(paper_to_add.experiment_path) + + for (var/datum/experiment/experiment as anything in available_experiments) + if(experiment.type != paper_to_add.experiment_path) + continue + + experiment.completed = TRUE + complete_experiment(experiment) + if(length(GLOB.experiment_handlers)) + var/datum/component/experiment_handler/handler = GLOB.experiment_handlers[1] + handler.announce_message_to_all("The [experiment.name] has been completed!") + + return TRUE diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index 0245417d249..6ab7d68beba 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -72,21 +72,25 @@ unlock_ids -= node_id /datum/techweb_node/proc/get_price(datum/techweb/host) - if(host) - var/list/actual_costs = research_costs.Copy() - if(host.boosted_nodes[id]) - var/list/boostlist = host.boosted_nodes[id] - for(var/booster in boostlist) - if(actual_costs[booster]) - actual_costs[booster] -= boostlist[booster] - for(var/cost_type in actual_costs) - for(var/experiment_type in discount_experiments) - if(host.completed_experiments[experiment_type]) //do we have this discount_experiment unlocked AND it wasn't applied already? - actual_costs[cost_type] -= discount_experiments[experiment_type] - return actual_costs - else + if(!host) return research_costs + var/list/actual_costs = research_costs.Copy() + + for(var/cost_type in actual_costs) + for(var/experiment_type in discount_experiments) + if(host.completed_experiments[experiment_type]) //do we have this discount_experiment unlocked? + actual_costs[cost_type] -= discount_experiments[experiment_type] + + if(host.boosted_nodes[id]) // Boosts should be subservient to experiments. Discount from boosts are capped when costs fall below 250. + var/list/boostlist = host.boosted_nodes[id] + for(var/booster in boostlist) + if(actual_costs[booster]) + var/delta = max(0, actual_costs[booster] - 250) + actual_costs[booster] -= min(boostlist[booster], delta) + + return actual_costs + /datum/techweb_node/proc/price_display(datum/techweb/TN) return techweb_point_display_generic(get_price(TN)) diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index a8329ceee74..420e2e61d58 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -935,6 +935,7 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) discount_experiments = list(/datum/experiment/scanning/points/machinery_pinpoint_scan/tier3_microlaser = 4000) + required_experiments = list(/datum/experiment/ordnance/gaseous/noblium) /////////////////////////Clown tech///////////////////////// /datum/techweb_node/clown @@ -1169,6 +1170,7 @@ "cybernetic_stomach_tier3", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500) + required_experiments = list(/datum/experiment/ordnance/gaseous/bz) /datum/techweb_node/cyber_implants id = "cyber_implants" @@ -1198,6 +1200,7 @@ "ci-toolset", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + required_experiments = list(/datum/experiment/ordnance/gaseous/nitrium) /datum/techweb_node/combat_cyber_implants id = "combat_cyber_implants" @@ -1350,7 +1353,6 @@ "tele_shield", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000) - required_experiments = list(/datum/experiment/explosion/calibration) /datum/techweb_node/adv_weaponry id = "adv_weaponry" @@ -1361,7 +1363,7 @@ "pin_loyalty", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000) - required_experiments = list(/datum/experiment/explosion/medium) + required_experiments = list(/datum/experiment/ordnance/explosive/highyieldbomb) /datum/techweb_node/electric_weapons id = "electronic_weapons" @@ -1416,7 +1418,6 @@ "pyro_Grenade", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) - required_experiments = list(/datum/experiment/explosion/maxcap) /datum/techweb_node/exotic_ammo id = "exotic_ammo" diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi index d834bbd32f5..2f3f0d43f43 100644 Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 7ad8d4ca645..d875537e97c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -662,6 +662,7 @@ #include "code\datums\components\area_sound_manager.dm" #include "code\datums\components\areabound.dm" #include "code\datums\components\armor_plate.dm" +#include "code\datums\components\atmos_reaction_recorder.dm" #include "code\datums\components\atmospheric_scanner.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" @@ -1177,7 +1178,6 @@ #include "code\game\machinery\deployable.dm" #include "code\game\machinery\dish_drive.dm" #include "code\game\machinery\dna_scanner.dm" -#include "code\game\machinery\doppler_array.dm" #include "code\game\machinery\droneDispenser.dm" #include "code\game\machinery\ecto_sniffer.dm" #include "code\game\machinery\electrolyzer.dm" @@ -2687,7 +2687,7 @@ #include "code\modules\experisci\experiment\types\dissection_experiment.dm" #include "code\modules\experisci\experiment\types\experiment.dm" #include "code\modules\experisci\experiment\types\exploration.dm" -#include "code\modules\experisci\experiment\types\explosive.dm" +#include "code\modules\experisci\experiment\types\ordnance.dm" #include "code\modules\experisci\experiment\types\physical_experiment.dm" #include "code\modules\experisci\experiment\types\random_scanning.dm" #include "code\modules\experisci\experiment\types\scanning.dm" @@ -3813,7 +3813,7 @@ #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\anomaly_refinery.dm" #include "code\modules\research\anomaly\raw_anomaly.dm" #include "code\modules\research\designs\AI_module_designs.dm" #include "code\modules\research\designs\autolathe_designs.dm" @@ -3844,6 +3844,11 @@ #include "code\modules\research\machinery\departmental_techfab.dm" #include "code\modules\research\machinery\protolathe.dm" #include "code\modules\research\machinery\techfab.dm" +#include "code\modules\research\ordnance\_scipaper.dm" +#include "code\modules\research\ordnance\doppler_array.dm" +#include "code\modules\research\ordnance\scipaper_partner.dm" +#include "code\modules\research\ordnance\scipaper_publisher.dm" +#include "code\modules\research\ordnance\tank_compressor.dm" #include "code\modules\research\techweb\__techweb_helpers.dm" #include "code\modules\research\techweb\_techweb.dm" #include "code\modules\research\techweb\_techweb_node.dm" diff --git a/tgui/packages/tgui/interfaces/AnomalyRefinery.js b/tgui/packages/tgui/interfaces/AnomalyRefinery.js new file mode 100644 index 00000000000..3bd96534d59 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnomalyRefinery.js @@ -0,0 +1,220 @@ +import { useBackend, useSharedState } from '../backend'; +import { + Button, + Icon, + Modal, + Section, + Stack, + LabeledList, + Box, +} from '../components'; +import { Window } from '../layouts'; +import { GasmixParser } from './common/GasmixParser'; + +export const AnomalyRefinery = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + + + + ); +}; + + +const AnomalyRefineryContent = (props, context) => { + const { act, data } = useBackend(context); + const [currentTab, changeTab] = useSharedState(context, 'exploderTab', 1); + const { core, valvePresent, active } = data; + return ( + + {currentTab === 1 && } + {currentTab === 2 && } + + + + + + + + + + + + + + + ); +}; + +const CoreCompressorContent = (props, context) => { + const { act, data } = useBackend(context); + const { core, requiredRadius, gasList, valveReady, active, valvePresent } + = data; + return ( + <> + +
act('start_implosion')} + disabled={active || !valveReady || !core}> + {'Implode Core'} + + }> + {!core && {'No Core Inserted!'}} + + {core ? core : '-'} + + {requiredRadius + ? requiredRadius + ' tiles' + : 'Implosion not possible.'} + + +
+
+ +
act('swap')}> + {'Swap Merging Order'} + + }> + {!valvePresent && {'No Bomb Inserted!'}} + + + + {'Giver Tank (' + + (gasList[1].name ? gasList[1].name : 'Not Available') + + ')'} + + + {(gasList[1].total_moles + ? String(gasList[0].total_moles.toFixed(2)) + : '-') + + ' moles at ' + + (gasList[1].total_moles + ? String(gasList[1].temperature.toFixed(2)) + : '-') + + ' Kelvin'} + + + {(gasList[1].total_moles + ? String(gasList[1].pressure.toFixed(2)) + : '-') + ' kPa'} + + + + + + + + {'Target Tank (' + + (gasList[0].name ? gasList[0].name : 'Not Available') + + ')'} + + + {(gasList[0].total_moles + ? String(gasList[0].total_moles.toFixed(2)) + : '-') + + ' moles at ' + + (gasList[0].total_moles + ? String(gasList[0].temperature.toFixed(2)) + : '-') + + ' Kelvin'} + + + {(gasList[1].total_moles + ? String(gasList[0].pressure.toFixed(2)) + : '-') + ' kPa'} + + + +
+
+ + ); +}; +const BombProcessorContent = (props, context) => { + const { act, data } = useBackend(context); + const { gasList, reactionIncrement } = data; + return ( + <> + +
act('react')} + textAlign="center" + disabled={!gasList[0].total_moles || !gasList[1].total_moles} + content={reactionIncrement === 0 ? 'Open Valve' : 'React'} /> + }> + {!gasList[2].total_moles && ( + {'No Gas Present'} + )} + +
+
+ + + {[gasList[0], gasList[1]].map((individualGasmix) => ( + +
+ {!individualGasmix.total_moles && ( + {'No Gas Present'} + )} + +
+
+ ))} +
+
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/DopplerArray.js b/tgui/packages/tgui/interfaces/DopplerArray.js new file mode 100644 index 00000000000..d482ec78901 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DopplerArray.js @@ -0,0 +1,151 @@ +import { useBackend, useSharedState } from '../backend'; +import { + Box, + Button, + Flex, + LabeledList, + NoticeBox, + Section, + Stack, + Tabs, +} from '../components'; +import { Window } from '../layouts'; + +export const DopplerArray = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + + + + ); +}; + +const DopplerArrayContent = (props, context) => { + const { act, data } = useBackend(context); + const { records = [], disk, storage } = data; + const [activeRecordName, setActiveRecordName] = useSharedState( + context, + 'activeRecordrecord', + records[0]?.name + ); + const activeRecord = records.find((record) => { + return record.name === activeRecordName; + }); + const DopplerArrayFooter = ( +
+
+ ); + const DopplerArrayRecords = ( +
+ + + + {records.map((record) => ( + setActiveRecordName(record.name)}> + {record.name} + + ))} + + + {activeRecord ? ( + +
+ + act('delete_record', { + 'ref': activeRecord.ref, + })} + /> +
+
+ ) : ( + + No Record Selected + + )} +
+
+ ); + return ( + + + {!records.length ? ( + No Records + ) : ( + DopplerArrayRecords + )} + + {DopplerArrayFooter} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosScipaper.js b/tgui/packages/tgui/interfaces/NtosScipaper.js new file mode 100644 index 00000000000..961c3098fc6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosScipaper.js @@ -0,0 +1,385 @@ +import { useBackend } from '../backend'; +import { + BlockQuote, + Button, + Collapsible, + Dropdown, + Input, + LabeledList, + Section, + Stack, + Tabs, + Box, + Table, + NoticeBox, + Tooltip, + Icon, +} from '../components'; +import { TableCell, TableRow } from '../components/Table'; +import { NtosWindow } from '../layouts'; + +export const NtosScipaper = (props, context) => { + return ( + + + + + + ); +}; + +const PaperPublishing = (props, context) => { + const { act, data } = useBackend(context); + const { + title, + author, + etAlia, + abstract, + fileList = [], + expList = [], + allowedTiers = [], + allowedPartners = [], + gains, + selectedFile, + selectedExperiment, + tier, + selectedPartner, + coopIndex, + fundingIndex, + } = data; + return ( + <> +
+ + + + act('rewrite', { + title: value, + })} + /> + + + + act('rewrite', { + author: value, + })} + /> + + + + + act('rewrite', { + abstract: value, + })} + /> + + + + + + act('select_file', { + selected_uid: fileList[ordfile_name], + })} + /> + + + + + + + + + + + + + act('select_experiment', { + selected_expath: expList[experiment_name], + })} + /> + + + + + + + + + + + + String(number))} + displayText={tier ? String(tier) : '-'} + onSelected={(new_tier) => + act('select_tier', { + selected_tier: Number(new_tier), + })} + /> + + + + + + + + + + + + + act('select_partner', { + selected_partner: allowedPartners[new_partner], + })} + /> + + + + + + + + + +
+
+ + + + + + {' Cooperation: '} +
{gains[coopIndex - 1]}
+
+ + + + + {' Funding: '} +
{gains[fundingIndex - 1]}
+
+
+
+
+ + ); +}; + +const PaperBrowser = (props, context) => { + const { act, data } = useBackend(context); + const { publishedPapers, coopIndex, fundingIndex } = data; + if (publishedPapers.length === 0) { + return No Published Papers! ; + } else { + return publishedPapers.map((paper) => ( + +
+ + + {paper['experimentName'] + ' - ' + paper['tier']} + + + {paper['author'] + (paper.etAlia ? ' et al.' : '')} + + + {paper['partner']} + + + + + {paper['gains'][coopIndex - 1]} + + + {paper['gains'][fundingIndex - 1]} + + + + + {paper['abstract']} + + +
+
+ )); + } +}; +const ExperimentBrowser = (props, context) => { + const { act, data } = useBackend(context); + const { experimentInformation = [] } = data; + return experimentInformation.map((experiment) => ( +
+ {experiment.description} +
+ + {Object.keys(experiment.target).map((tier) => ( + + {experiment.target[tier] + ' ' + experiment.suffix} + + ))} + +
+ )); +}; + +const PartnersBrowser = (props, context) => { + const { act, data } = useBackend(context); + const { + partnersInformation, + coopIndex, + fundingIndex, + purchaseableBoosts = [], + relations = [], + visibleNodes = [], + } = data; + return partnersInformation.map((partner) => ( +
+ + + + {partner.flufftext} + + + {relations[partner.path]} + + + {partner.multipliers[coopIndex - 1] + 'x'} + + + {partner.multipliers[fundingIndex - 1] + 'x'} + + + {partner.acceptedExperiments.map((experiment_name) => ( + {experiment_name} + ))} + + + + {partner.boostedNodes.map((node) => ( + + + {visibleNodes.includes(node.id) + ? node.name + : 'Unknown Technology'} + + +
+
+
+
+
+ )); +}; + +export const NtosScipaperContent = (props, context) => { + const { act, data } = useBackend(context); + const { currentTab } = data; + return ( + <> + + + act('change_tab', { + new_tab: 1, + })}> + {'Publish Papers'} + + + act('change_tab', { + new_tab: 2, + })}> + {'View Previous Publications'} + + + act('change_tab', { + new_tab: 3, + })}> + {'View Available Experiments'} + + + act('change_tab', { + new_tab: 4, + })}> + {'View Scientific Partners'} + + + {currentTab === 1 && } + {currentTab === 2 && } + {currentTab === 3 && } + {currentTab === 4 && } + + ); +}; diff --git a/tgui/packages/tgui/interfaces/TachyonArray.js b/tgui/packages/tgui/interfaces/TachyonArray.js deleted file mode 100644 index 87c3395edfa..00000000000 --- a/tgui/packages/tgui/interfaces/TachyonArray.js +++ /dev/null @@ -1,133 +0,0 @@ -import { useBackend, useSharedState } from '../backend'; -import { Button, Flex, LabeledList, NoticeBox, Section, Tabs } from '../components'; -import { Window } from '../layouts'; - -export const TachyonArray = (props, context) => { - const { act, data } = useBackend(context); - const { - records = [], - is_research, - } = data; - return ( - - - - - {!records.length ? ( - - No Records - - ) : ( - - )} - - {!!is_research && ( - - - - )} - - - - ); -}; - -export const TachyonArrayContent = (props, context) => { - const { act, data } = useBackend(context); - const { - records = [], - } = data; - const [ - activeRecordName, - setActiveRecordName, - ] = useSharedState(context, 'record', records[0]?.name); - const activeRecord = records.find(record => { - return record.name === activeRecordName; - }); - return ( -
- - - - {records.map(record => ( - setActiveRecordName(record.name)}> - {record.name} - - ))} - - - {activeRecord ? ( - -
- act('delete_record', { - 'ref': activeRecord.ref, - })} /> -
-
- ) : ( - - - No Record Selected - - - )} -
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/TankCompressor.js b/tgui/packages/tgui/interfaces/TankCompressor.js new file mode 100644 index 00000000000..417f15b0d85 --- /dev/null +++ b/tgui/packages/tgui/interfaces/TankCompressor.js @@ -0,0 +1,327 @@ +import { useBackend, useSharedState } from '../backend'; +import { + Box, + Button, + Flex, + Icon, + Modal, + RoundGauge, + Section, + Slider, + Stack, + NoticeBox, + Tabs, + LabeledList, +} from '../components'; +import { Window } from '../layouts'; +import { GasmixParser } from './common/GasmixParser'; + +export const TankCompressor = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + + + + ); +}; + +const TankCompressorContent = (props, context) => { + const { act, data } = useBackend(context); + const { disk, storage } = data; + const [currentTab, changeTab] = useSharedState(context, 'compressorTab', 1); + return ( + + {currentTab === 1 && } + {currentTab === 2 && } + +
+ + + + + +
+
+
+ ); +}; + +const AlertBoxes = (props, context) => { + const { text_content, icon_name, icon_break, color, active } = props; + const { act, data } = useBackend(context); + return ( + + + + + {icon_break &&
} + {text_content} +
+
+
+ ); +}; + +const TankCompressorControls = (props, context) => { + const { act, data } = useBackend(context); + const { + tankPresent, + leaking, + lastPressure, + leakPressure, + fragmentPressure, + tankPressure, + maxTransfer, + active, + transferRate, + ejectPressure, + portData = [], + } = data; + const pressure = tankPresent ? tankPressure : lastPressure; + const usingLastData = !!(lastPressure && !tankPresent); + return ( + <> + +
ejectPressure} + onClick={() => act('eject_tank')}> + {'Eject Tank'} + + }> + {!pressure && {'No Pressure Detected'}} + {usingLastData && ( + + {'Tank destroyed. Displaying last recorded data.'} + + )} + + + (value ? value.toFixed(2) : '-') + ' kPa'} + /> + + + + + + = leakPressure} + /> + + + + + = leakPressure + && pressure < fragmentPressure) + || leaking + } + /> + + + = fragmentPressure} + /> + + + + +
+
+ +
+ + + + act('change_rate', { target: new_rate })} + /> + + + + + +
+
+ + + {portData.map((individualGasmix) => ( + +
+ {!individualGasmix.total_moles && ( + {'No Gas Present'} + )} + +
+
+ ))} +
+
+ + ); +}; + +const TankCompressorRecords = (props, context) => { + const { act, data } = useBackend(context); + const { records = [], disk } = data; + const [activeRecordRef, setActiveRecordRef] = useSharedState( + context, + 'recordRef', + records[0]?.ref + ); + const activeRecord = !!activeRecordRef && records.find((record) => + activeRecordRef === record.ref); + if (records.length === 0) { + return ( + + No Records + ); + } + else { + return ( + + + + + {records.map((record) => ( + setActiveRecordRef(record.ref)}> + {record.name} + + ))} + + + {activeRecord ? ( + +
{ + act('delete_record', { + 'ref': activeRecord.ref, + }); + }} + />, +
+
+ ) : ( + + No Record Selected + + )} +
+
+ ); + } +}; diff --git a/tgui/packages/tgui/interfaces/ThermoMachine.js b/tgui/packages/tgui/interfaces/ThermoMachine.js index 87043b8cd26..ee044bc3329 100644 --- a/tgui/packages/tgui/interfaces/ThermoMachine.js +++ b/tgui/packages/tgui/interfaces/ThermoMachine.js @@ -27,7 +27,7 @@ export const ThermoMachine = (props, context) => { average: [.65, .825], bad: [.4225, .64], }}> - {Math.round(data.efficiency * 10000)/100 + " %"} + {(data.efficiency * 100).toFixed(2) + " %"} ); diff --git a/tgui/packages/tgui/interfaces/common/GasmixParser.js b/tgui/packages/tgui/interfaces/common/GasmixParser.js new file mode 100644 index 00000000000..26b37b2724f --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/GasmixParser.js @@ -0,0 +1,25 @@ + +import { useBackend } from '../../backend'; +import { LabeledList } from '../../components'; + +export const GasmixParser = (props, context) => { + const { act, data } = useBackend(context); + const { gases=[], temperature, volume, pressure, total_moles } = props; + return ( + + {Object.keys(gases).map((gas_name) => ( + + {gases[gas_name].toFixed(2) + + ' mol (' + + ((gases[gas_name] / total_moles) * 100).toFixed(2) + + ' %)'} + + ))} + + {(total_moles ? temperature.toFixed(2) : "-")+ ' K'} + + {(total_moles ? volume.toFixed(2) : "-") + ' L'} + {(total_moles ? pressure.toFixed(2) : "-") + ' kPa'} + + ); +};