From 2bada9544c772c0a4c1dbb80fa42fb602a473f4b Mon Sep 17 00:00:00 2001 From: Dario Maselli <117168592+Dario-Maselli@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:31:18 +0800 Subject: [PATCH 1/5] Add quarry, modules, trash can and assets Introduce a new quarry feature plus supporting modules and a trash can: added Java classes for quarry machine (controller, frame, landmark, controller BE, menu, renderer, render state, chunk loader, region, miner tiers, output filter, planet profiles), module registry and item classes, and trash can storage BE. Added generated resources (blockstates, models, item models, recipes, loot tables, advancements, tags, language entries) plus Blockbench model sources and textures. Updated datagen providers, registries, client code, tests, tools, and docs (wiki pages) to wire everything together. Also minor docs/format tweaks (.markdownlint, PRIVACY.md code fence, RELEASE_CHECKLIST formatting). --- .markdownlint.json | 1 + PRIVACY.md | 2 +- RELEASE_CHECKLIST.md | 3 + .../block/quarry_controller.bbmodel | 136 +++ art/blockbench/block/quarry_frame.bbmodel | 136 +++ art/blockbench/block/quarry_landmark.bbmodel | 136 +++ art/blockbench/block/trash_can.bbmodel | 136 +++ art/blockbench/item/efficiency_module.bbmodel | 136 +++ art/blockbench/item/fortune_module.bbmodel | 136 +++ art/blockbench/item/frame_casing.bbmodel | 136 +++ art/blockbench/item/silk_touch_module.bbmodel | 136 +++ art/blockbench/item/speed_module.bbmodel | 136 +++ .../blockstates/quarry_controller.json | 7 + .../nerospace/blockstates/quarry_frame.json | 7 + .../blockstates/quarry_landmark.json | 7 + .../nerospace/blockstates/trash_can.json | 7 + .../nerospace/items/efficiency_module.json | 6 + .../nerospace/items/fortune_module.json | 6 + .../assets/nerospace/items/frame_casing.json | 6 + .../nerospace/items/quarry_controller.json | 6 + .../nerospace/items/quarry_landmark.json | 6 + .../nerospace/items/silk_touch_module.json | 6 + .../assets/nerospace/items/speed_module.json | 6 + .../assets/nerospace/items/trash_can.json | 6 + .../assets/nerospace/lang/en_us.json | 15 + .../models/block/quarry_controller.json | 6 + .../nerospace/models/block/quarry_frame.json | 41 + .../models/block/quarry_landmark.json | 6 + .../nerospace/models/block/trash_can.json | 6 + .../models/item/efficiency_module.json | 6 + .../nerospace/models/item/fortune_module.json | 6 + .../nerospace/models/item/frame_casing.json | 6 + .../models/item/silk_touch_module.json | 6 + .../nerospace/models/item/speed_module.json | 6 + .../tags/block/mineable/pickaxe.json | 6 +- .../minecraft/tags/block/needs_iron_tool.json | 3 +- .../recipes/misc/efficiency_module.json | 32 + .../recipes/misc/fortune_module.json | 32 + .../recipes/misc/frame_casing.json | 32 + .../recipes/misc/quarry_controller.json | 32 + .../recipes/misc/quarry_landmark.json | 32 + .../recipes/misc/silk_touch_module.json | 32 + .../recipes/misc/speed_module.json | 32 + .../advancement/recipes/misc/trash_can.json | 32 + .../loot_table/blocks/quarry_controller.json | 21 + .../loot_table/blocks/quarry_landmark.json | 21 + .../loot_table/blocks/trash_can.json | 21 + .../nerospace/recipe/efficiency_module.json | 17 + .../data/nerospace/recipe/fortune_module.json | 17 + .../data/nerospace/recipe/frame_casing.json | 16 + .../nerospace/recipe/quarry_controller.json | 18 + .../nerospace/recipe/quarry_landmark.json | 18 + .../nerospace/recipe/silk_touch_module.json | 17 + .../data/nerospace/recipe/speed_module.json | 17 + .../data/nerospace/recipe/trash_can.json | 16 + .../neroland/nerospace/NerospaceClient.java | 7 + .../java/za/co/neroland/nerospace/Tuning.java | 18 + .../client/GalleryCaptureHarness.java | 46 +- .../client/QuarryControllerRenderState.java | 27 + .../client/QuarryControllerRenderer.java | 144 +++ .../nerospace/client/QuarryScreen.java | 57 ++ .../nerospace/command/NerospaceCommands.java | 53 ++ .../compat/jei/RefiningCategory.java | 2 +- .../datagen/ModBlockLootSubProvider.java | 5 + .../datagen/ModBlockTagProvider.java | 9 +- .../datagen/ModLanguageProvider.java | 17 + .../nerospace/datagen/ModModelProvider.java | 37 + .../nerospace/datagen/ModRecipeProvider.java | 67 ++ .../gametest/NerospaceGameTests.java | 4 + .../nerospace/machine/quarry/MinerTier.java | 89 ++ .../machine/quarry/OutputFilter.java | 19 + .../machine/quarry/PlanetMiningProfile.java | 35 + .../machine/quarry/QuarryChunkLoader.java | 29 + .../machine/quarry/QuarryControllerBlock.java | 80 ++ .../quarry/QuarryControllerBlockEntity.java | 888 ++++++++++++++++++ .../machine/quarry/QuarryFrameBlock.java | 27 + .../machine/quarry/QuarryLandmarkBlock.java | 65 ++ .../quarry/QuarryLandmarkBlockEntity.java | 45 + .../nerospace/machine/quarry/QuarryMenu.java | 184 ++++ .../machine/quarry/QuarryRegion.java | 210 +++++ .../nerospace/module/MachineModules.java | 95 ++ .../neroland/nerospace/module/ModuleType.java | 30 + .../nerospace/module/UpgradeModuleItem.java | 35 + .../nerospace/registry/ModBlockEntities.java | 20 + .../nerospace/registry/ModBlocks.java | 49 + .../nerospace/registry/ModCapabilities.java | 32 + .../registry/ModCreativeModeTabs.java | 11 + .../neroland/nerospace/registry/ModItems.java | 27 + .../nerospace/registry/ModMenuTypes.java | 6 + .../nerospace/storage/FluidTankBlock.java | 14 +- .../nerospace/storage/TrashCanBlock.java | 52 + .../storage/TrashCanBlockEntity.java | 84 ++ .../textures/block/quarry_controller.png | Bin 0 -> 463 bytes .../nerospace/textures/block/quarry_frame.png | Bin 0 -> 146 bytes .../textures/block/quarry_landmark.png | Bin 0 -> 371 bytes .../nerospace/textures/block/trash_can.png | Bin 0 -> 455 bytes .../assets/nerospace/textures/gui/quarry.png | Bin 0 -> 10877 bytes .../textures/item/efficiency_module.png | Bin 0 -> 156 bytes .../textures/item/fortune_module.png | Bin 0 -> 154 bytes .../nerospace/textures/item/frame_casing.png | Bin 0 -> 186 bytes .../textures/item/silk_touch_module.png | Bin 0 -> 154 bytes .../nerospace/textures/item/speed_module.png | Bin 0 -> 156 bytes tools/ecj.prefs | 14 +- tools/fix_markdown.py | 133 +++ tools/gen_bbmodels.py | 6 +- tools/gen_textures.py | 186 ++++ tools/gradle-mcp/server.js | 351 ++++++- wiki/Battery.md | 7 +- wiki/Block-of-Cindrite.md | 3 + wiki/Block-of-Glacite.md | 3 + wiki/Block-of-Nerosium.md | 3 + wiki/Block-of-Nerosteel.md | 3 + wiki/Block-of-Raw-Nerosium.md | 3 + wiki/Cindrite-Ore.md | 4 + wiki/Combustion-Generator.md | 7 +- wiki/Configurator.md | 4 + wiki/Creative-Source-Blocks.md | 1 + wiki/Creatures.md | 8 + wiki/Deepslate-Nerosium-Ore.md | 4 + wiki/Fluid-Tank.md | 7 +- wiki/Fuel-Refinery.md | 8 +- wiki/Fuel-Tank.md | 8 +- wiki/Future-Features.md | 5 + wiki/Gas-Tank.md | 7 +- wiki/Glacite-Ore.md | 4 + wiki/Home.md | 2 + wiki/Hydration-Module.md | 8 +- wiki/Item-Store.md | 7 +- wiki/Items.md | 8 + wiki/Launch-Gantry.md | 8 +- wiki/Nerosium-Grinder.md | 9 +- wiki/Nerosium-Ore.md | 4 + wiki/Nerosteel-Ore.md | 4 + wiki/Oxygen-Generator.md | 9 +- wiki/Oxygen-Suit.md | 9 +- wiki/Passive-Generator.md | 7 +- wiki/Pipe-Filters-and-Upgrades.md | 4 + wiki/Quarry-Controller.md | 107 +++ wiki/Quarry-Landmark.md | 39 + wiki/Roadmap.md | 3 +- wiki/Rocket-Launch-Pad.md | 8 +- wiki/Star-Guide.md | 8 +- wiki/Station-Charter.md | 8 +- wiki/Station-Core.md | 4 + wiki/Station-Floor.md | 3 + wiki/Station-Wall.md | 4 + wiki/Terraform-Monitor.md | 7 +- wiki/Terraformer.md | 8 +- wiki/Trash-Can.md | 42 + wiki/Universal-Pipe.md | 9 +- wiki/Upgrade-Modules.md | 55 ++ wiki/Xertz-Quartz-Ore.md | 4 + wiki/_Sidebar.md | 13 + 153 files changed, 5619 insertions(+), 54 deletions(-) create mode 100644 art/blockbench/block/quarry_controller.bbmodel create mode 100644 art/blockbench/block/quarry_frame.bbmodel create mode 100644 art/blockbench/block/quarry_landmark.bbmodel create mode 100644 art/blockbench/block/trash_can.bbmodel create mode 100644 art/blockbench/item/efficiency_module.bbmodel create mode 100644 art/blockbench/item/fortune_module.bbmodel create mode 100644 art/blockbench/item/frame_casing.bbmodel create mode 100644 art/blockbench/item/silk_touch_module.bbmodel create mode 100644 art/blockbench/item/speed_module.bbmodel create mode 100644 src/generated/resources/assets/nerospace/blockstates/quarry_controller.json create mode 100644 src/generated/resources/assets/nerospace/blockstates/quarry_frame.json create mode 100644 src/generated/resources/assets/nerospace/blockstates/quarry_landmark.json create mode 100644 src/generated/resources/assets/nerospace/blockstates/trash_can.json create mode 100644 src/generated/resources/assets/nerospace/items/efficiency_module.json create mode 100644 src/generated/resources/assets/nerospace/items/fortune_module.json create mode 100644 src/generated/resources/assets/nerospace/items/frame_casing.json create mode 100644 src/generated/resources/assets/nerospace/items/quarry_controller.json create mode 100644 src/generated/resources/assets/nerospace/items/quarry_landmark.json create mode 100644 src/generated/resources/assets/nerospace/items/silk_touch_module.json create mode 100644 src/generated/resources/assets/nerospace/items/speed_module.json create mode 100644 src/generated/resources/assets/nerospace/items/trash_can.json create mode 100644 src/generated/resources/assets/nerospace/models/block/quarry_controller.json create mode 100644 src/generated/resources/assets/nerospace/models/block/quarry_frame.json create mode 100644 src/generated/resources/assets/nerospace/models/block/quarry_landmark.json create mode 100644 src/generated/resources/assets/nerospace/models/block/trash_can.json create mode 100644 src/generated/resources/assets/nerospace/models/item/efficiency_module.json create mode 100644 src/generated/resources/assets/nerospace/models/item/fortune_module.json create mode 100644 src/generated/resources/assets/nerospace/models/item/frame_casing.json create mode 100644 src/generated/resources/assets/nerospace/models/item/silk_touch_module.json create mode 100644 src/generated/resources/assets/nerospace/models/item/speed_module.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/efficiency_module.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/fortune_module.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/frame_casing.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_controller.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_landmark.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/silk_touch_module.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/speed_module.json create mode 100644 src/generated/resources/data/nerospace/advancement/recipes/misc/trash_can.json create mode 100644 src/generated/resources/data/nerospace/loot_table/blocks/quarry_controller.json create mode 100644 src/generated/resources/data/nerospace/loot_table/blocks/quarry_landmark.json create mode 100644 src/generated/resources/data/nerospace/loot_table/blocks/trash_can.json create mode 100644 src/generated/resources/data/nerospace/recipe/efficiency_module.json create mode 100644 src/generated/resources/data/nerospace/recipe/fortune_module.json create mode 100644 src/generated/resources/data/nerospace/recipe/frame_casing.json create mode 100644 src/generated/resources/data/nerospace/recipe/quarry_controller.json create mode 100644 src/generated/resources/data/nerospace/recipe/quarry_landmark.json create mode 100644 src/generated/resources/data/nerospace/recipe/silk_touch_module.json create mode 100644 src/generated/resources/data/nerospace/recipe/speed_module.json create mode 100644 src/generated/resources/data/nerospace/recipe/trash_can.json create mode 100644 src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java create mode 100644 src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java create mode 100644 src/main/java/za/co/neroland/nerospace/client/QuarryScreen.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/MinerTier.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/OutputFilter.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/PlanetMiningProfile.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryChunkLoader.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlock.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlock.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlockEntity.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryMenu.java create mode 100644 src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryRegion.java create mode 100644 src/main/java/za/co/neroland/nerospace/module/MachineModules.java create mode 100644 src/main/java/za/co/neroland/nerospace/module/ModuleType.java create mode 100644 src/main/java/za/co/neroland/nerospace/module/UpgradeModuleItem.java create mode 100644 src/main/java/za/co/neroland/nerospace/storage/TrashCanBlock.java create mode 100644 src/main/java/za/co/neroland/nerospace/storage/TrashCanBlockEntity.java create mode 100644 src/main/resources/assets/nerospace/textures/block/quarry_controller.png create mode 100644 src/main/resources/assets/nerospace/textures/block/quarry_frame.png create mode 100644 src/main/resources/assets/nerospace/textures/block/quarry_landmark.png create mode 100644 src/main/resources/assets/nerospace/textures/block/trash_can.png create mode 100644 src/main/resources/assets/nerospace/textures/gui/quarry.png create mode 100644 src/main/resources/assets/nerospace/textures/item/efficiency_module.png create mode 100644 src/main/resources/assets/nerospace/textures/item/fortune_module.png create mode 100644 src/main/resources/assets/nerospace/textures/item/frame_casing.png create mode 100644 src/main/resources/assets/nerospace/textures/item/silk_touch_module.png create mode 100644 src/main/resources/assets/nerospace/textures/item/speed_module.png create mode 100644 tools/fix_markdown.py create mode 100644 wiki/Quarry-Controller.md create mode 100644 wiki/Quarry-Landmark.md create mode 100644 wiki/Trash-Can.md create mode 100644 wiki/Upgrade-Modules.md diff --git a/.markdownlint.json b/.markdownlint.json index ca567e6..71e2d9c 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,6 +1,7 @@ { "MD013": false, "MD033": false, + "MD036": false, "MD041": false, "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/v0.40.0/schema/markdownlint-config-schema.json" } diff --git a/PRIVACY.md b/PRIVACY.md index a3cbd35..3e8db53 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -68,7 +68,7 @@ No env file or credentials are needed — the DSN is embedded. Launch a dev clie self-test flag and one synthetic event is sent on config load (environment matches the version channel, with tag `runtime=dev`): -``` +```text ./gradlew runClient -PtelemetryTest=true ``` diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 86eb9ca..ea698b3 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -26,6 +26,7 @@ Tick a box only after the gradle-MCP build + gametests are green AND the in-game ## 4. Verification & testing ### Targeted runClient pass (every pending in-game item) + - [ ] Fuel Tank auto-fuel + 3×3 speed bonus + pumping particles/sound - [ ] Pad gating messages (3×3, T3 Station Wall ring) + grounding on pad break - [ ] Oxygen drain/refill/suffocation + sealed rooms + airlock refill @@ -38,9 +39,11 @@ Tick a box only after the gradle-MCP build + gametests are green AND the in-game - [ ] Terraformer + oxygen field end-to-end ### Survival playthrough + - [ ] Full start-to-terraform survival playthrough (no creative shortcuts) — log friction/balance notes ### Multiplayer + - [ ] Dedicated-server pass (`runServer` + 2 clients): oxygen sync, rocket rides, pipe networks, GUIs, world join (registry sync), suit HUD per player diff --git a/art/blockbench/block/quarry_controller.bbmodel b/art/blockbench/block/quarry_controller.bbmodel new file mode 100644 index 0000000..a87a752 --- /dev/null +++ b/art/blockbench/block/quarry_controller.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "quarry_controller", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "quarry_controller", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "404f1f9b-bf26-4498-9eb3-52ae8cf5d4d8" + } + ], + "outliner": [ + "404f1f9b-bf26-4498-9eb3-52ae8cf5d4d8" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\block\\quarry_controller.png", + "name": "quarry_controller.png", + "folder": "block", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "1f2fa489-a44b-4912-9ee3-3f4b16597f33", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/block/quarry_controller.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlklEQVR4nKWSMWsbMRiGHxtDixFClMNoOI4OIhwdj5KpU+fOHTqUDP0BnUsGT5kzhwwmdCodTIf8gkymePBkioZy3CCOIxyH8FRwBldKD9M4kHcRkj7pe99HGszmqy1P0Ajg4nyKTg12vWSiM2pXYvKCrm2oXclEZ0iV0LUNUiW4yuJ9x23jGALo1JBOzzB5wca3e4drV+IqS+1KAF5ffkMICcBgNl9tf3z/CoBdL3v2TF7gKrtneywUG99S/v61iwDEwtvGxcLFzTVWJxjXxLXs5RFSJXE+BOja+4LHKMSLF0iVoFODyYuDh8dCUbsyMhgBPfqHtPEtQki87+4vCNQnOmMx+tM78OzjDHt1EufvhEKqJALfc2CaPg97ddKH+PwFUiWYvGBxc71jEPKE8SEF6wH8MCxKlcTNhxRAh6eM/yAwCC687zB5wdvKcvzmmBD13+8cHUx0xqvzi9hlLBRCSLq2QacmNhBCUruSdHoW3Q5h9zQ/P73vxRj/pe0qi10ve3vV9EtsNpjNV9vTzx8OZv+f7gBTd8GK7JAejwAAAABJRU5ErkJggg==" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/block/quarry_frame.bbmodel b/art/blockbench/block/quarry_frame.bbmodel new file mode 100644 index 0000000..fd1718a --- /dev/null +++ b/art/blockbench/block/quarry_frame.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "quarry_frame", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "quarry_frame", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "7f001289-a12e-49c7-a3b7-173ba6e80fdf" + } + ], + "outliner": [ + "7f001289-a12e-49c7-a3b7-173ba6e80fdf" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\block\\quarry_frame.png", + "name": "quarry_frame.png", + "folder": "block", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "a6f0faf6-15f1-44dd-a96e-464be8799d70", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/block/quarry_frame.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfElEQVR4nGM88un//5S2XgZywJyqYgYGjYqe/1xSCnBMCj/vzp//TGRZjQQoNoBRo6LnPyUGsDAwMDA8WjQFLiAXl0M0P+XQHcq9QPswmFNVzIAvnRAMgxQ88kMkDAiBgU8HjEc+/f+/6tVfuMCuORMY3FIKiOKHiTEzAADIc2ClB+GSQgAAAABJRU5ErkJggg==" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/block/quarry_landmark.bbmodel b/art/blockbench/block/quarry_landmark.bbmodel new file mode 100644 index 0000000..297e86a --- /dev/null +++ b/art/blockbench/block/quarry_landmark.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "quarry_landmark", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "quarry_landmark", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "696adef1-3712-424b-8235-8bad4b71f3cd" + } + ], + "outliner": [ + "696adef1-3712-424b-8235-8bad4b71f3cd" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\block\\quarry_landmark.png", + "name": "quarry_landmark.png", + "folder": "block", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "dbf7e4c9-c9c6-4f5c-a65d-64f5c932bb32", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/block/quarry_landmark.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOklEQVR4nKWSrU7EQBDHf5ALoplUNBVVzam+BO9xogKBROEwSDRPgCCISnKPcOJ4AJBVTVXFpqKZnCIBQWZvlxZCwphmt/l/zp48bt8++MesAG6va8p1BYDqhEiK6sTr0xaA86uL6B5gdAMApyFbBL58gJ2DneNlc8/oBlQnRjcgkpLlxdGBgY1VJOXmbk+dCM1BqRMhywtEUkTSuQNjs2/ftRG4OagH9l1L37U+chTB1IEIXCeC6oTqRJYXHuwJLJcpAD86CCN7AmO0crK8mDmw/6MbvEhUYsg6umHmwNyF9kc3fDmwfDblulrsIBSKIoRj61lyYFsK+/AlWpHlukJ1WuzAYoRvxpdo7L91YC5MyBNYHmP/yxYMswozWYy+a9lXzzRdC2fQvEMpx/bDrlamHGb8frd0tvkEyXPwzP3mU8UAAAAASUVORK5CYII=" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/block/trash_can.bbmodel b/art/blockbench/block/trash_can.bbmodel new file mode 100644 index 0000000..7ec0eb0 --- /dev/null +++ b/art/blockbench/block/trash_can.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "trash_can", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "trash_can", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + } + }, + "type": "cube", + "uuid": "690c6b53-10e0-4d3b-bfb1-1e8b7267685e" + } + ], + "outliner": [ + "690c6b53-10e0-4d3b-bfb1-1e8b7267685e" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\block\\trash_can.png", + "name": "trash_can.png", + "folder": "block", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "74ac186f-fb4e-430c-990b-e3c792e2dfaa", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/block/trash_can.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjklEQVR4nKWSv2obQRDGfxFXiWVZzCHWcFG1hQqBQQQ1yROoTO0yqBZ+Aj+CH8CFC5UpQgqVrlQEYVQYF4dZQhAHXowwx3GotYtjx3cxGEK2mZt/33zzzX24+nH7zH+8BGB5ecFj2AHgRhNC4QGwmQPA51uJ28yJ3axXDcChLvmd3wCIBbi9uZbvGG/HhIHNHPd3v/6Jus0cuz/3DUAoPEmSAPCw0gAcz6p3/c9nvsugrwwAH79uGI2njD8h/lFqGWSWp33gZG5RSkt9D6Aq9wBokwrFx7ATYaERN9q+Mhzq8hVAm5RDXeLzrTTHBgCltORivsMgnqXdEAqPUs2+sRiakyqlhV0valCVe5lqM0ddVx3VB3Yo4DZzAp5EDdr7hsLjRhPR5lCXAhiHdVbQJmVghyJiXxn5G6MfJ/p8izap1PbaNNsi2syJ0tqkwiDqE5+c8fTborNnKLw0+Xwrub4yzBfnAiIMfn5fAnCUWuq6Etqj8VRUn36ZSW28WgKQ3234+z3tw5vYZr16E3sBACS6/q3AiDwAAAAASUVORK5CYII=" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/item/efficiency_module.bbmodel b/art/blockbench/item/efficiency_module.bbmodel new file mode 100644 index 0000000..539afcc --- /dev/null +++ b/art/blockbench/item/efficiency_module.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "efficiency_module", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "efficiency_module", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 7.5 + ], + "to": [ + 16, + 16, + 8.5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "west": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + } + }, + "type": "cube", + "uuid": "6b3d814c-e83a-4bef-8e8e-a7a69981db23" + } + ], + "outliner": [ + "6b3d814c-e83a-4bef-8e8e-a7a69981db23" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\item\\efficiency_module.png", + "name": "efficiency_module.png", + "folder": "item", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "b7eb1dc7-1ca1-45c3-8027-7c7cb3cf0ee8", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/item/efficiency_module.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAY0lEQVR4nGNgGGjAiMypeNbznxhNHVIlcH0s6JIb3Fbh1RywKwyFz0SMjfgAhgtw2YTLZXhd0C5ZTNAFeA2ofN5LmQHEAJxhQCg2qOaCgTcAIwwCdoUxaJxyY7hhtguDHpwAAMOuGSBgz4JUAAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/item/fortune_module.bbmodel b/art/blockbench/item/fortune_module.bbmodel new file mode 100644 index 0000000..211c983 --- /dev/null +++ b/art/blockbench/item/fortune_module.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "fortune_module", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "fortune_module", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 7.5 + ], + "to": [ + 16, + 16, + 8.5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "west": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + } + }, + "type": "cube", + "uuid": "82cc059f-6686-4eeb-869b-d0a5914b1ca7" + } + ], + "outliner": [ + "82cc059f-6686-4eeb-869b-d0a5914b1ca7" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\item\\fortune_module.png", + "name": "fortune_module.png", + "folder": "item", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "5e29cfa3-c285-4e19-9c5f-bd1af016c9f3", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/item/fortune_module.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAYUlEQVR4nGNgGGjAiMyp2PL/PzGaOnwY4fpY0CU3VJjj1RzQcRKFz0SMjfgAhgtw2YTLZXhd0O5N2AV4DajcSqEBxACcYUAoNqjmgoE3ACMMAjpOMmj8ucxwg0UXgx6cAABYHxggJ92mbwAAAABJRU5ErkJggg==" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/item/frame_casing.bbmodel b/art/blockbench/item/frame_casing.bbmodel new file mode 100644 index 0000000..1ee2484 --- /dev/null +++ b/art/blockbench/item/frame_casing.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "frame_casing", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "frame_casing", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 7.5 + ], + "to": [ + 16, + 16, + 8.5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "west": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + } + }, + "type": "cube", + "uuid": "c5c8c7f3-636b-487c-89f1-4ea186d81d78" + } + ], + "outliner": [ + "c5c8c7f3-636b-487c-89f1-4ea186d81d78" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\item\\frame_casing.png", + "name": "frame_casing.png", + "folder": "item", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "9998e84e-67c7-464f-8e14-032c2fcb64e0", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/item/frame_casing.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgUlEQVR4nGNgGF5gwYZL/xdsuPTfxScJL42shxHdgJqCKLyWaOiYMezZMo8Rq6SLT9J/GQWd/1glGRgYZBR0MFzAhMyJSSnAazsDAwPDkjkTUPhM+CSxAXRLhoELUKLDxSfp/40rp/Aa0DJhGUNCgB5cH4YLNHTMGFomLMNJD0MAAJvkSblf0WceAAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/item/silk_touch_module.bbmodel b/art/blockbench/item/silk_touch_module.bbmodel new file mode 100644 index 0000000..7d864ca --- /dev/null +++ b/art/blockbench/item/silk_touch_module.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "silk_touch_module", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "silk_touch_module", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 7.5 + ], + "to": [ + 16, + 16, + 8.5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "west": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + } + }, + "type": "cube", + "uuid": "b5574a33-7bcf-44c2-a36b-9cb9203e4786" + } + ], + "outliner": [ + "b5574a33-7bcf-44c2-a36b-9cb9203e4786" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\item\\silk_touch_module.png", + "name": "silk_touch_module.png", + "folder": "item", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "f0f68cd0-1d19-499e-a9d3-f3ff072418d5", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/item/silk_touch_module.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAYUlEQVR4nGNgGGjAiMx5tuX/f2I0SfkwwvWxoEs6VZjj1byv4yQKn4kYG/EBDBfgsgmXy/C6QNKbsAvwGvB8K4UGEANwhgGh2KCaCwbeAKxJedefywxuLLoYNAwgJ+WBBwA0QxoOn8oZsQAAAABJRU5ErkJggg==" + } + ] +} \ No newline at end of file diff --git a/art/blockbench/item/speed_module.bbmodel b/art/blockbench/item/speed_module.bbmodel new file mode 100644 index 0000000..d2ecd2a --- /dev/null +++ b/art/blockbench/item/speed_module.bbmodel @@ -0,0 +1,136 @@ +{ + "meta": { + "format_version": "4.10", + "model_format": "java_block", + "box_uv": false + }, + "name": "speed_module", + "model_identifier": "", + "visible_box": [ + 1, + 1, + 0 + ], + "variable_placeholders": "", + "variable_placeholder_buttons": [], + "timeline_setups": [], + "unhandled_root_fields": {}, + "resolution": { + "width": 16, + "height": 16 + }, + "elements": [ + { + "name": "speed_module", + "box_uv": false, + "rescale": false, + "locked": false, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [ + 0, + 0, + 7.5 + ], + "to": [ + 16, + 16, + 8.5 + ], + "autouv": 0, + "color": 0, + "origin": [ + 8, + 8, + 8 + ], + "uv_offset": [ + 0, + 0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": 0 + }, + "south": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": 0 + }, + "east": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "west": { + "uv": [ + 0, + 0, + 0, + 16 + ], + "texture": null + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0 + ], + "texture": null + } + }, + "type": "cube", + "uuid": "98b1e344-dc62-4a2e-ab36-c0cd3ebe031f" + } + ], + "outliner": [ + "98b1e344-dc62-4a2e-ab36-c0cd3ebe031f" + ], + "textures": [ + { + "path": "C:\\Users\\dario\\Documents\\Github\\nerospace\\src\\main\\resources\\assets\\nerospace\\textures\\item\\speed_module.png", + "name": "speed_module.png", + "folder": "item", + "namespace": "nerospace", + "id": "0", + "particle": true, + "render_mode": "default", + "render_sides": "auto", + "frame_time": 1, + "frame_order_type": "loop", + "frame_order": "", + "frame_interpolate": false, + "visible": true, + "mode": "bitmap", + "saved": true, + "uuid": "bb47cf56-3c6a-49b2-a4f4-515a30f7697e", + "relative_path": "../../../src/main/resources/assets/nerospace/textures/item/speed_module.png", + "source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAY0lEQVR4nGNgGGjAiMyJOvH/PzGallkwwvWxoEueSzHHq9lozkkUPhMxNuIDGC7AZRMul+F1wVL8viFsQPRJfLJEGEAMwBkGhGKDai4YeAMwwsBozkkGtxeXGXZJ6GLQgxMAAGQJGCDslbSkAAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/quarry_controller.json b/src/generated/resources/assets/nerospace/blockstates/quarry_controller.json new file mode 100644 index 0000000..0633ba2 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/quarry_controller.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/quarry_controller" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/quarry_frame.json b/src/generated/resources/assets/nerospace/blockstates/quarry_frame.json new file mode 100644 index 0000000..28514e7 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/quarry_frame.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/quarry_frame" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/quarry_landmark.json b/src/generated/resources/assets/nerospace/blockstates/quarry_landmark.json new file mode 100644 index 0000000..74d3609 --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/quarry_landmark.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/quarry_landmark" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/blockstates/trash_can.json b/src/generated/resources/assets/nerospace/blockstates/trash_can.json new file mode 100644 index 0000000..568d42b --- /dev/null +++ b/src/generated/resources/assets/nerospace/blockstates/trash_can.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "nerospace:block/trash_can" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/efficiency_module.json b/src/generated/resources/assets/nerospace/items/efficiency_module.json new file mode 100644 index 0000000..5a85e8b --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/efficiency_module.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/efficiency_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/fortune_module.json b/src/generated/resources/assets/nerospace/items/fortune_module.json new file mode 100644 index 0000000..35aa1ae --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/fortune_module.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/fortune_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/frame_casing.json b/src/generated/resources/assets/nerospace/items/frame_casing.json new file mode 100644 index 0000000..918a6a5 --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/frame_casing.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/frame_casing" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/quarry_controller.json b/src/generated/resources/assets/nerospace/items/quarry_controller.json new file mode 100644 index 0000000..306b27c --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/quarry_controller.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/quarry_controller" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/quarry_landmark.json b/src/generated/resources/assets/nerospace/items/quarry_landmark.json new file mode 100644 index 0000000..e72831d --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/quarry_landmark.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/quarry_landmark" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/silk_touch_module.json b/src/generated/resources/assets/nerospace/items/silk_touch_module.json new file mode 100644 index 0000000..b65939f --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/silk_touch_module.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/silk_touch_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/speed_module.json b/src/generated/resources/assets/nerospace/items/speed_module.json new file mode 100644 index 0000000..dd51c6f --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/speed_module.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:item/speed_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/items/trash_can.json b/src/generated/resources/assets/nerospace/items/trash_can.json new file mode 100644 index 0000000..4576c2e --- /dev/null +++ b/src/generated/resources/assets/nerospace/items/trash_can.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "nerospace:block/trash_can" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/lang/en_us.json b/src/generated/resources/assets/nerospace/lang/en_us.json index ee8fa9d..965d950 100644 --- a/src/generated/resources/assets/nerospace/lang/en_us.json +++ b/src/generated/resources/assets/nerospace/lang/en_us.json @@ -37,6 +37,9 @@ "block.nerospace.nerosteel_ore": "Nerosteel Ore", "block.nerospace.oxygen_generator": "Oxygen Generator", "block.nerospace.passive_generator": "Passive Generator", + "block.nerospace.quarry_controller": "Quarry Controller", + "block.nerospace.quarry_frame": "Quarry Frame", + "block.nerospace.quarry_landmark": "Quarry Landmark", "block.nerospace.raw_nerosium_block": "Block of Raw Nerosium", "block.nerospace.rocket_fuel": "Rocket Fuel", "block.nerospace.rocket_launch_pad": "Rocket Launch Pad", @@ -58,6 +61,7 @@ "block.nerospace.tank.readout": "Tank: %s / %s mB of %s", "block.nerospace.terraform_monitor": "Terraform Monitor", "block.nerospace.terraformer": "Terraformer", + "block.nerospace.trash_can": "Trash Can", "block.nerospace.universal_pipe": "Universal Pipe", "block.nerospace.universal_pipe.energy": "Pipe energy: %s FE", "block.nerospace.universal_pipe.fluid": "Pipe fluid: %s mB of %s", @@ -74,6 +78,7 @@ "container.nerospace.nerosium_grinder": "Nerosium Grinder", "container.nerospace.oxygen_generator": "Oxygen Generator", "container.nerospace.passive_generator": "Passive Generator", + "container.nerospace.quarry_controller": "Quarry Controller", "container.nerospace.rocket": "Rocket", "container.nerospace.star_guide": "Star Guide", "container.nerospace.terraform_monitor": "Terraform Monitor", @@ -110,6 +115,11 @@ "gui.nerospace.oxygen_generator.power": "Power: %s%%", "gui.nerospace.oxygen_generator.producing": "Producing oxygen", "gui.nerospace.oxygen_generator.starved": "No power", + "gui.nerospace.quarry.state.building_frame": "Building frame", + "gui.nerospace.quarry.state.done": "Finished", + "gui.nerospace.quarry.state.idle": "Idle — place landmarks", + "gui.nerospace.quarry.state.mining": "Mining", + "gui.nerospace.quarry.state.paused": "Paused", "gui.nerospace.rocket.found": "FOUND", "gui.nerospace.rocket.fuel": "Fuel: %s / %s mB", "gui.nerospace.rocket.fuel_pct": "Fuel: %s%% (%s / %s mB)", @@ -222,7 +232,10 @@ "item.nerospace.configurator.selected": "Configuring: %s", "item.nerospace.destination_compass.travel": "Travelling to %s", "item.nerospace.drift_fleece": "Drift Fleece", + "item.nerospace.efficiency_module": "Efficiency Module", "item.nerospace.ember_strutter_spawn_egg": "Ember Strutter Spawn Egg", + "item.nerospace.fortune_module": "Fortune Module", + "item.nerospace.frame_casing": "Frame Casing", "item.nerospace.frost_strider_spawn_egg": "Frost Strider Spawn Egg", "item.nerospace.glacira_compass": "Glacira Compass", "item.nerospace.glacite": "Glacite", @@ -274,6 +287,8 @@ "item.nerospace.rocket_tier_2": "Tier 2 Rocket", "item.nerospace.rocket_tier_3": "Tier 3 Rocket", "item.nerospace.rocket_tier_4": "Tier 4 Rocket", + "item.nerospace.silk_touch_module": "Silk Touch Module", + "item.nerospace.speed_module": "Speed Module", "item.nerospace.speed_upgrade": "Speed Upgrade", "item.nerospace.star_guide_book": "Star Guide Book", "item.nerospace.station_charter": "Station Charter", diff --git a/src/generated/resources/assets/nerospace/models/block/quarry_controller.json b/src/generated/resources/assets/nerospace/models/block/quarry_controller.json new file mode 100644 index 0000000..3e82d8e --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/quarry_controller.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/quarry_controller" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/quarry_frame.json b/src/generated/resources/assets/nerospace/models/block/quarry_frame.json new file mode 100644 index 0000000..13e4264 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/quarry_frame.json @@ -0,0 +1,41 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "down": { + "texture": "#all" + }, + "east": { + "texture": "#all" + }, + "north": { + "texture": "#all" + }, + "south": { + "texture": "#all" + }, + "up": { + "texture": "#all" + }, + "west": { + "texture": "#all" + } + }, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ] + } + ], + "textures": { + "all": "nerospace:block/quarry_frame", + "particle": "nerospace:block/quarry_frame" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/quarry_landmark.json b/src/generated/resources/assets/nerospace/models/block/quarry_landmark.json new file mode 100644 index 0000000..a5281f7 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/quarry_landmark.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/quarry_landmark" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/block/trash_can.json b/src/generated/resources/assets/nerospace/models/block/trash_can.json new file mode 100644 index 0000000..b063bd6 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/trash_can.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "nerospace:block/trash_can" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/efficiency_module.json b/src/generated/resources/assets/nerospace/models/item/efficiency_module.json new file mode 100644 index 0000000..dfa19d3 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/efficiency_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/efficiency_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/fortune_module.json b/src/generated/resources/assets/nerospace/models/item/fortune_module.json new file mode 100644 index 0000000..b0fd1ea --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/fortune_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/fortune_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/frame_casing.json b/src/generated/resources/assets/nerospace/models/item/frame_casing.json new file mode 100644 index 0000000..a259d85 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/frame_casing.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/frame_casing" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/silk_touch_module.json b/src/generated/resources/assets/nerospace/models/item/silk_touch_module.json new file mode 100644 index 0000000..0928d44 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/silk_touch_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/silk_touch_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/nerospace/models/item/speed_module.json b/src/generated/resources/assets/nerospace/models/item/speed_module.json new file mode 100644 index 0000000..b59484c --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/item/speed_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "nerospace:item/speed_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json index c60989a..872d627 100644 --- a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json +++ b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -28,6 +28,10 @@ "nerospace:gas_tank", "nerospace:item_store", "nerospace:star_guide", - "nerospace:launch_gantry" + "nerospace:launch_gantry", + "nerospace:quarry_controller", + "nerospace:quarry_landmark", + "nerospace:quarry_frame", + "nerospace:trash_can" ] } \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json b/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json index 746bee0..a0847ce 100644 --- a/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json +++ b/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json @@ -19,6 +19,7 @@ "nerospace:terraform_monitor", "nerospace:universal_pipe", "nerospace:combustion_generator", - "nerospace:passive_generator" + "nerospace:passive_generator", + "nerospace:quarry_controller" ] } \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/efficiency_module.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/efficiency_module.json new file mode 100644 index 0000000..135c0e1 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/efficiency_module.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:efficiency_module" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:efficiency_module" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/fortune_module.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/fortune_module.json new file mode 100644 index 0000000..2d61233 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/fortune_module.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:fortune_module" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:fortune_module" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/frame_casing.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/frame_casing.json new file mode 100644 index 0000000..2aa7d07 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/frame_casing.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:frame_casing" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:frame_casing" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_controller.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_controller.json new file mode 100644 index 0000000..d27fb4d --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_controller.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_frame_casing": { + "conditions": { + "items": [ + { + "items": "nerospace:frame_casing" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:quarry_controller" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_frame_casing" + ] + ], + "rewards": { + "recipes": [ + "nerospace:quarry_controller" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_landmark.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_landmark.json new file mode 100644 index 0000000..9665732 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/quarry_landmark.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:quarry_landmark" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:quarry_landmark" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/silk_touch_module.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/silk_touch_module.json new file mode 100644 index 0000000..e7d6455 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/silk_touch_module.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:silk_touch_module" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:silk_touch_module" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/speed_module.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/speed_module.json new file mode 100644 index 0000000..d199379 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/speed_module.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_nerosteel_ingot": { + "conditions": { + "items": [ + { + "items": "nerospace:nerosteel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:speed_module" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_nerosteel_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:speed_module" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/recipes/misc/trash_can.json b/src/generated/resources/data/nerospace/advancement/recipes/misc/trash_can.json new file mode 100644 index 0000000..0976d3e --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/recipes/misc/trash_can.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_iron_ingot": { + "conditions": { + "items": [ + { + "items": "minecraft:iron_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "nerospace:trash_can" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_iron_ingot" + ] + ], + "rewards": { + "recipes": [ + "nerospace:trash_can" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/quarry_controller.json b/src/generated/resources/data/nerospace/loot_table/blocks/quarry_controller.json new file mode 100644 index 0000000..7c0e673 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/quarry_controller.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:quarry_controller" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/quarry_controller" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/quarry_landmark.json b/src/generated/resources/data/nerospace/loot_table/blocks/quarry_landmark.json new file mode 100644 index 0000000..6d00bf2 --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/quarry_landmark.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:quarry_landmark" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/quarry_landmark" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/loot_table/blocks/trash_can.json b/src/generated/resources/data/nerospace/loot_table/blocks/trash_can.json new file mode 100644 index 0000000..7ddc66e --- /dev/null +++ b/src/generated/resources/data/nerospace/loot_table/blocks/trash_can.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "nerospace:trash_can" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "nerospace:blocks/trash_can" +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/efficiency_module.json b/src/generated/resources/data/nerospace/recipe/efficiency_module.json new file mode 100644 index 0000000..a730bc3 --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/efficiency_module.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "N": "#c:ingots/nerosteel", + "R": "minecraft:redstone", + "S": "minecraft:lapis_lazuli" + }, + "pattern": [ + " N ", + "RSR", + " N " + ], + "result": { + "id": "nerospace:efficiency_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/fortune_module.json b/src/generated/resources/data/nerospace/recipe/fortune_module.json new file mode 100644 index 0000000..c810de9 --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/fortune_module.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "N": "#c:ingots/nerosteel", + "R": "minecraft:redstone", + "S": "minecraft:diamond" + }, + "pattern": [ + " N ", + "RSR", + " N " + ], + "result": { + "id": "nerospace:fortune_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/frame_casing.json b/src/generated/resources/data/nerospace/recipe/frame_casing.json new file mode 100644 index 0000000..389137c --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/frame_casing.json @@ -0,0 +1,16 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": "#c:ingots/nerosteel" + }, + "pattern": [ + "III", + "I I", + "III" + ], + "result": { + "count": 4, + "id": "nerospace:frame_casing" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/quarry_controller.json b/src/generated/resources/data/nerospace/recipe/quarry_controller.json new file mode 100644 index 0000000..b1db75d --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/quarry_controller.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "D": "#c:gems/diamond", + "F": "nerospace:frame_casing", + "I": "#c:ingots/nerosteel", + "R": "minecraft:redstone_block" + }, + "pattern": [ + "IDI", + "FRF", + "III" + ], + "result": { + "id": "nerospace:quarry_controller" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/quarry_landmark.json b/src/generated/resources/data/nerospace/recipe/quarry_landmark.json new file mode 100644 index 0000000..60db2fc --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/quarry_landmark.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "G": "#c:glass_blocks", + "I": "#c:ingots/nerosteel", + "R": "minecraft:redstone" + }, + "pattern": [ + "R", + "G", + "I" + ], + "result": { + "count": 3, + "id": "nerospace:quarry_landmark" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/silk_touch_module.json b/src/generated/resources/data/nerospace/recipe/silk_touch_module.json new file mode 100644 index 0000000..f33c329 --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/silk_touch_module.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "N": "#c:ingots/nerosteel", + "R": "minecraft:redstone", + "S": "minecraft:amethyst_shard" + }, + "pattern": [ + " N ", + "RSR", + " N " + ], + "result": { + "id": "nerospace:silk_touch_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/speed_module.json b/src/generated/resources/data/nerospace/recipe/speed_module.json new file mode 100644 index 0000000..83034fe --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/speed_module.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "N": "#c:ingots/nerosteel", + "R": "minecraft:redstone", + "S": "minecraft:sugar" + }, + "pattern": [ + " N ", + "RSR", + " N " + ], + "result": { + "id": "nerospace:speed_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/recipe/trash_can.json b/src/generated/resources/data/nerospace/recipe/trash_can.json new file mode 100644 index 0000000..5159299 --- /dev/null +++ b/src/generated/resources/data/nerospace/recipe/trash_can.json @@ -0,0 +1,16 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "C": "minecraft:cactus", + "I": "#c:ingots/iron" + }, + "pattern": [ + "III", + "ICI", + "III" + ], + "result": { + "id": "nerospace:trash_can" + } +} \ No newline at end of file diff --git a/src/main/java/za/co/neroland/nerospace/NerospaceClient.java b/src/main/java/za/co/neroland/nerospace/NerospaceClient.java index 8922e9d..204361c 100644 --- a/src/main/java/za/co/neroland/nerospace/NerospaceClient.java +++ b/src/main/java/za/co/neroland/nerospace/NerospaceClient.java @@ -88,6 +88,8 @@ static void onRegisterFluidModels(net.neoforged.neoforge.client.event.RegisterFl @SubscribeEvent static void onRegisterMenuScreens(RegisterMenuScreensEvent event) { event.register(ModMenuTypes.NEROSIUM_GRINDER.get(), NerosiumGrinderScreen::new); + event.register(ModMenuTypes.QUARRY_CONTROLLER.get(), + za.co.neroland.nerospace.client.QuarryScreen::new); event.register(ModMenuTypes.OXYGEN_GENERATOR.get(), OxygenGeneratorScreen::new); event.register(ModMenuTypes.FUEL_TANK.get(), FuelTankScreen::new); event.register(ModMenuTypes.FUEL_REFINERY.get(), FuelRefineryScreen::new); @@ -176,6 +178,11 @@ static void onRegisterEntityRenderers(EntityRenderersEvent.RegisterRenderers eve event.registerBlockEntityRenderer( za.co.neroland.nerospace.registry.ModBlockEntities.STAR_GUIDE.get(), context -> new za.co.neroland.nerospace.client.StarGuideHologramRenderer()); + + // Quarry controller: glowing gantry + moving drill head (MINER_DESIGN). + event.registerBlockEntityRenderer( + za.co.neroland.nerospace.registry.ModBlockEntities.QUARRY_CONTROLLER.get(), + context -> new za.co.neroland.nerospace.client.QuarryControllerRenderer()); } @SubscribeEvent diff --git a/src/main/java/za/co/neroland/nerospace/Tuning.java b/src/main/java/za/co/neroland/nerospace/Tuning.java index f43aadc..efdf68c 100644 --- a/src/main/java/za/co/neroland/nerospace/Tuning.java +++ b/src/main/java/za/co/neroland/nerospace/Tuning.java @@ -70,6 +70,10 @@ private Tuning() { public static final int BASE_FUEL_REFINERY_TANK = 8_000; public static final int BASE_OXYGEN_GENERATOR_O2_CAPACITY = 8_000; public static final int BASE_TERRAFORMER_BUFFER = 100_000; + /** Quarry controller energy buffer (MINER_DESIGN — power-fed, throughput scales with supply). */ + public static final int BASE_QUARRY_BUFFER = 200_000; + /** Quarry internal fluid buffer (mB) for sucked-up liquids before they auto-eject to a tank. */ + public static final int BASE_QUARRY_FLUID_CAPACITY = 16_000; // ------------------------------------------------------------------ // Base values: machine speeds / item logistics @@ -106,6 +110,8 @@ private Tuning() { public static final int BASE_TERRAFORM_ENERGY_PER_BLOCK = 12; /** Grinder energy consumed per progress tick (FE). */ public static final int BASE_GRINDER_ENERGY_PER_TICK = 30; + /** Quarry energy consumed per mined block (FE); modules' efficiency lowers it. */ + public static final int BASE_QUARRY_ENERGY_PER_BLOCK = 40; /** Fuel Refinery energy consumed per working tick (FE); over a batch ≈ 4,000 FE per 2,000 mB. */ public static final int BASE_FUEL_REFINERY_FE_PER_TICK = 40; /** Rocket fuel produced per refining batch (mB) — one coal + one blaze powder = 2,000 mB. */ @@ -262,6 +268,14 @@ public static int terraformerBuffer() { return scale(BASE_TERRAFORMER_BUFFER, Config.ENERGY_RATE_MULTIPLIER.get()); } + public static int quarryBuffer() { + return scale(BASE_QUARRY_BUFFER, Config.ENERGY_RATE_MULTIPLIER.get()); + } + + public static int quarryFluidCapacity() { + return scale(BASE_QUARRY_FLUID_CAPACITY, Config.ENERGY_RATE_MULTIPLIER.get()); + } + public static int fuelRefineryBuffer() { return scale(BASE_FUEL_REFINERY_BUFFER, Config.ENERGY_RATE_MULTIPLIER.get()); } @@ -307,6 +321,10 @@ public static int grinderEnergyPerTick() { return scale(BASE_GRINDER_ENERGY_PER_TICK, Config.FUEL_COST_MULTIPLIER.get()); } + public static int quarryEnergyPerBlock() { + return scale(BASE_QUARRY_ENERGY_PER_BLOCK, Config.FUEL_COST_MULTIPLIER.get()); + } + /** Fuel Refinery energy per working tick (a consumable cost, so fuelCostMultiplier). */ public static int fuelRefineryFePerTick() { return scale(BASE_FUEL_REFINERY_FE_PER_TICK, Config.FUEL_COST_MULTIPLIER.get()); diff --git a/src/main/java/za/co/neroland/nerospace/client/GalleryCaptureHarness.java b/src/main/java/za/co/neroland/nerospace/client/GalleryCaptureHarness.java index bd9b783..5019edd 100644 --- a/src/main/java/za/co/neroland/nerospace/client/GalleryCaptureHarness.java +++ b/src/main/java/za/co/neroland/nerospace/client/GalleryCaptureHarness.java @@ -202,35 +202,51 @@ public static void onClientTick(ClientTickEvent.Post event) { switch (phase) { case MOVE -> { - current = QUEUE.poll(); - if (current == null) { + final Shot shot = QUEUE.poll(); + current = shot; + if (shot == null) { finish(mc); return; } - for (String cmd : current.setup()) { // pre-warmup: teleport / gamerules / time / summon + for (String cmd : shot.setup()) { // pre-warmup: teleport / gamerules / time / summon player.connection.sendCommand(cmd); } - warmup = current.warmup(); + warmup = shot.warmup(); phase = Phase.WARMUP; } case WARMUP -> { + final Shot shot = current; + if (shot == null) { + phase = Phase.MOVE; + return; + } if (--warmup <= 0) { // teleport + chunks now loaded - for (String cmd : current.build()) { // block placement needs loaded chunks (else "not loaded") + for (String cmd : shot.build()) { // block placement needs loaded chunks (else "not loaded") player.connection.sendCommand(cmd); } - applyPose(player, current); + applyPose(player, shot); settle = SETTLE_TICKS; phase = Phase.SETTLE; } } case SETTLE -> { - applyPose(player, current); // re-pin every tick so gravity/AI can't drift the camera + final Shot shot = current; + if (shot == null) { + phase = Phase.MOVE; + return; + } + applyPose(player, shot); // re-pin every tick so gravity/AI can't drift the camera if (--settle <= 0) { phase = Phase.SHOOT; } } case SHOOT -> { - grab(mc, current.name()); + final Shot shot = current; + if (shot == null) { + phase = Phase.MOVE; + return; + } + grab(mc, shot.name()); phase = Phase.MOVE; } } @@ -238,11 +254,14 @@ public static void onClientTick(ClientTickEvent.Post event) { /** Snap the player (the render camera) to the shot's pose, holding it still. */ private static void applyPose(LocalPlayer player, @Nullable Shot shot) { - if (shot == null || shot.camera() == null || shot.target() == null) { - return; // "keep current view" shot + if (shot == null) { + return; } Vec3 cam = shot.camera(); Vec3 tgt = shot.target(); + if (cam == null || tgt == null) { + return; // "keep current view" shot + } double dx = tgt.x - cam.x; double dy = tgt.y - cam.y; double dz = tgt.z - cam.z; @@ -303,6 +322,13 @@ private static java.util.List buildShots(int ox, int oy, int oz) { // Suits (NW, row along +X, stands face ~south): close, head-on from the south. shots.add(new Shot("suits", none, none, 0, new Vec3(ox - 33, oy + 3, oz - 26), new Vec3(ox - 34, oy + 2.5, oz - 34))); + // Quarry landmark-only display (NE): the L of three landmarks + their projected lasers. + shots.add(new Shot("quarry_landmarks", none, none, 0, + new Vec3(ox + 31, oy + 4, oz - 30), new Vec3(ox + 31, oy + 2, oz - 38))); + // Operating quarry (NE, further out): raised + angled to look INTO the pit and read the + // glowing frame, the moving drill head and the power hookup. + shots.add(new Shot("quarry_operating", none, none, 0, + new Vec3(ox + 44, oy + 9, oz - 28), new Vec3(ox + 42, oy - 2, oz - 39))); return shots; } diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java new file mode 100644 index 0000000..79c862b --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java @@ -0,0 +1,27 @@ +package za.co.neroland.nerospace.client; + +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; + +/** + * Render state for the quarry's gantry + drill head. All coordinates are relative to the controller + * block's corner (the BER's pose origin), computed server-synced in + * {@link QuarryControllerRenderer#extractRenderState}. + */ +public class QuarryControllerRenderState extends BlockEntityRenderState { + + public boolean active; + public boolean mining; + /** Region edges (relative to the controller). */ + public double x0; + public double x1; + public double z0; + public double z1; + /** Top of the frame plane (relative). */ + public double topY; + /** Smoothed drill-head centre (relative). */ + public double hx; + public double hy; + public double hz; + /** Tier accent (ARGB) for the gantry rails. */ + public int accent; +} diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java new file mode 100644 index 0000000..f048096 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java @@ -0,0 +1,144 @@ +package za.co.neroland.nerospace.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +import za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity; +import za.co.neroland.nerospace.machine.quarry.QuarryRegion; + +/** + * Draws the quarry's working machinery: glowing gantry rails around the claimed region, a moving + * bridge rail tracking the dig column, a vertical drill shaft and a bright drill head at the cell + * currently being mined (MINER_DESIGN — "build frame + drill head"). Geometry is emissive + * position-colour quads via the lightning render type (same approach as the Universal Pipe streams). + * The head position is server-synced (region + cursor + currentY) and smoothed client-side. + */ +public class QuarryControllerRenderer + implements BlockEntityRenderer { + + private static final double RAIL = 0.12; + private static final double SHAFT = 0.05; + private static final double HEAD = 0.18; + + @Override + public QuarryControllerRenderState createRenderState() { + return new QuarryControllerRenderState(); + } + + @Override + public int getViewDistance() { + return 128; // regions can stretch well past the default 64 from the controller + } + + @Override + public void extractRenderState(QuarryControllerBlockEntity be, QuarryControllerRenderState s, + float partialTick, Vec3 cameraPos, ModelFeatureRenderer.CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(be, s, partialTick, cameraPos, breakProgress); + QuarryRegion r = be.renderRegion(); + QuarryControllerBlockEntity.State st = be.renderState(); + if (r == null || st == QuarryControllerBlockEntity.State.IDLE) { + s.active = false; + return; + } + s.active = true; + BlockPos p = be.getBlockPos(); + s.x0 = r.minX() - p.getX(); + s.x1 = r.maxX() + 1 - p.getX(); + s.z0 = r.minZ() - p.getZ(); + s.z1 = r.maxZ() + 1 - p.getZ(); + s.topY = r.refY() + 1 - p.getY(); + s.accent = be.tier().accentColor(); + s.mining = st == QuarryControllerBlockEntity.State.MINING; + + double tx; + double ty; + double tz; + if (s.mining) { + int cols = Math.max(1, r.columns()); + int cur = Mth.clamp(be.renderCursor(), 0, cols - 1); + BlockPos col = r.columnPos(cur, be.renderCurrentY()); + tx = col.getX() + 0.5; + ty = be.renderCurrentY() + 0.5; + tz = col.getZ() + 0.5; + } else { + tx = (r.minX() + r.maxX() + 1) / 2.0; + ty = r.refY() + 0.5; + tz = (r.minZ() + r.maxZ() + 1) / 2.0; + } + if (!be.dispInit) { + be.dispX = tx; + be.dispY = ty; + be.dispZ = tz; + be.dispInit = true; + } else { + double k = 0.3; + be.dispX += (tx - be.dispX) * k; + be.dispY += (ty - be.dispY) * k; + be.dispZ += (tz - be.dispZ) * k; + } + s.hx = be.dispX - p.getX(); + s.hy = be.dispY - p.getY(); + s.hz = be.dispZ - p.getZ(); + } + + @Override + public void submit(QuarryControllerRenderState s, PoseStack poseStack, SubmitNodeCollector collector, + CameraRenderState cameraState) { + if (!s.active) { + return; + } + collector.order(1).submitCustomGeometry(poseStack, RenderTypes.lightning(), + (pose, consumer) -> draw(s, pose, consumer)); + } + + private static void draw(QuarryControllerRenderState s, PoseStack.Pose pose, VertexConsumer consumer) { + int ar = (s.accent >> 16) & 0xFF; + int ag = (s.accent >> 8) & 0xFF; + int ab = s.accent & 0xFF; + int a = 170; + double ty = s.topY; + + // The frame BLOCKS already draw the static perimeter, so the renderer only adds the MOVING + // gantry parts: a bridge rail tracking the dig column, the drill shaft and the drill head. + // Moving bridge rail (spans Z at the head's X). + box(pose, consumer, s.hx - RAIL / 2, ty - RAIL, s.z0, s.hx + RAIL / 2, ty, s.z1, ar, ag, ab, a); + + // Vertical drill shaft from the head up to the bridge. + box(pose, consumer, s.hx - SHAFT, s.hy, s.hz - SHAFT, s.hx + SHAFT, ty - RAIL, s.hz + SHAFT, + 120, 230, 255, 150); + + // Drill head. + box(pose, consumer, s.hx - HEAD, s.hy - HEAD, s.hz - HEAD, s.hx + HEAD, s.hy + HEAD, s.hz + HEAD, + 200, 245, 255, 220); + } + + /** Emit the 6 faces of an axis-aligned box as position-colour quads. */ + private static void box(PoseStack.Pose pose, VertexConsumer c, double x0, double y0, double z0, + double x1, double y1, double z1, int r, int g, int b, int a) { + quad(pose, c, x0, y0, z0, x0, y1, z0, x1, y1, z0, x1, y0, z0, r, g, b, a); // -Z + quad(pose, c, x1, y0, z1, x1, y1, z1, x0, y1, z1, x0, y0, z1, r, g, b, a); // +Z + quad(pose, c, x0, y0, z1, x0, y1, z1, x0, y1, z0, x0, y0, z0, r, g, b, a); // -X + quad(pose, c, x1, y0, z0, x1, y1, z0, x1, y1, z1, x1, y0, z1, r, g, b, a); // +X + quad(pose, c, x0, y1, z0, x0, y1, z1, x1, y1, z1, x1, y1, z0, r, g, b, a); // +Y + quad(pose, c, x0, y0, z1, x0, y0, z0, x1, y0, z0, x1, y0, z1, r, g, b, a); // -Y + } + + private static void quad(PoseStack.Pose pose, VertexConsumer c, + double ax, double ay, double az, double bx, double by, double bz, + double cx, double cy, double cz, double dx, double dy, double dz, + int r, int g, int b, int a) { + c.addVertex(pose, (float) ax, (float) ay, (float) az).setColor(r, g, b, a); + c.addVertex(pose, (float) bx, (float) by, (float) bz).setColor(r, g, b, a); + c.addVertex(pose, (float) cx, (float) cy, (float) cz).setColor(r, g, b, a); + c.addVertex(pose, (float) dx, (float) dy, (float) dz).setColor(r, g, b, a); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryScreen.java b/src/main/java/za/co/neroland/nerospace/client/QuarryScreen.java new file mode 100644 index 0000000..def004e --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryScreen.java @@ -0,0 +1,57 @@ +package za.co.neroland.nerospace.client; + +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; + +import za.co.neroland.nerospace.Nerospace; +import za.co.neroland.nerospace.machine.quarry.MinerTier; +import za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity; +import za.co.neroland.nerospace.machine.quarry.QuarryMenu; + +/** + * Screen for the quarry controller: sci-fi panel with a power gauge, the dig state, current depth and + * a fluid-buffer gauge, around the frame/module/output slots. + */ +public class QuarryScreen extends TexturedContainerScreen { + + private static final Identifier TEXTURE = + Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/gui/quarry.png"); + private static final int ACCENT = MinerTier.TIER_1.accentColor(); + private static final int FLUID = 0xFF4FA8FF; + + public QuarryScreen(QuarryMenu menu, Inventory playerInventory, Component title) { + super(menu, playerInventory, title, TEXTURE, ACCENT, 176, 210); + this.titleLabelX = 8; + this.inventoryLabelX = 8; + this.inventoryLabelY = 116; + } + + @Override + protected void extractForeground(GuiGraphicsExtractor g) { + int energy = this.menu.getEnergy(); + int maxEnergy = this.menu.getMaxEnergy(); + float energyFrac = maxEnergy == 0 ? 0f : (float) energy / maxEnergy; + int pct = maxEnergy == 0 ? 0 : energy * 100 / maxEnergy; + + // Readout band between the output grid (ends y≈76) and the player inventory (y=126). + label(g, Component.literal("Power: " + pct + "%"), 8, 80, TITLE); + segGauge(g, 8, 90, 160, 3, energyFrac, ACCENT); + + QuarryControllerBlockEntity.State state = this.menu.getState(); + label(g, Component.translatable("gui.nerospace.quarry.state." + state.name().toLowerCase(java.util.Locale.ROOT)), + 8, 97, SUBTLE); + int depth = Math.max(0, this.menu.getRefY() - this.menu.getCurrentY()); + if (state != QuarryControllerBlockEntity.State.IDLE) { + label(g, Component.literal("Depth: " + depth), 110, 97, SUBTLE); + } + + int fluid = this.menu.getFluid(); + int maxFluid = this.menu.getMaxFluid(); + float fluidFrac = maxFluid == 0 ? 0f : (float) fluid / maxFluid; + label(g, Component.literal("Fluid: " + fluid + " mB"), 8, 106, 0xFFB9D7FF); + // compact fluid bar to the right of the label + fluidGauge(g, 96, 107, 72, 3, fluidFrac, FLUID); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java b/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java index d7f8790..8d6bb62 100644 --- a/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java +++ b/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java @@ -40,6 +40,8 @@ import za.co.neroland.nerospace.machine.FuelRefineryBlockEntity; import za.co.neroland.nerospace.machine.HydrationModuleBlockEntity; import za.co.neroland.nerospace.machine.NerosiumGrinderBlockEntity; +import za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity; +import za.co.neroland.nerospace.machine.quarry.QuarryRegion; import za.co.neroland.nerospace.pipe.PipeIoMode; import za.co.neroland.nerospace.pipe.PipeResourceType; import za.co.neroland.nerospace.pipe.UniversalPipeBlockEntity; @@ -327,6 +329,57 @@ private static int buildGallery(CommandSourceStack source) { spawnShowcase(level, creatures.get(i), new BlockPos(mx + i * 4, fy + 1, mz + 1), true); } + // QUARRY (MINER_DESIGN): two NE displays. + // 1. Landmark-only — three landmarks in an L (shows the projected marker lasers). + // 2. Fully operating — a powered quarry mid-dig: frame ring, drill head, a real pit forming. + BlockState landmark = ModBlocks.QUARRY_LANDMARK.get().defaultBlockState(); + int lx = origin.getX() + 28; // landmark-only display (NE, nearer the centre) + int lz = origin.getZ() - 40; + for (int dx = -1; dx <= 7; dx++) { + for (int dz = -1; dz <= 7; dz++) { + level.setBlockAndUpdate(new BlockPos(lx + dx, fy, lz + dz), floor); + } + } + level.setBlockAndUpdate(new BlockPos(lx, fy + 1, lz), landmark); + level.setBlockAndUpdate(new BlockPos(lx + 6, fy + 1, lz), landmark); + level.setBlockAndUpdate(new BlockPos(lx, fy + 1, lz + 6), landmark); + + // Operating quarry: a 5x5 region west-fed by an endless battery, staged straight into mining. + int qx = origin.getX() + 42; + int qz = origin.getZ() - 40; + int refY = fy + 1; + for (int dx = -5; dx <= 5; dx++) { // power pad to the west of the region + for (int dz = -1; dz <= 5; dz++) { + level.setBlockAndUpdate(new BlockPos(qx + dx, fy, qz + dz), floor); + } + } + QuarryRegion region = new QuarryRegion(qx, qz, qx + 4, qz + 4, refY); + BlockState frameBlock = ModBlocks.QUARRY_FRAME.get().defaultBlockState(); + for (BlockPos fp : region.framePositions()) { + level.setBlockAndUpdate(fp, frameBlock); + } + // Pre-carve a starter pit so it reads as mid-dig the instant it's shot. + for (int x = qx; x <= qx + 4; x++) { + for (int z = qz; z <= qz + 4; z++) { + for (int y = refY - 1; y >= refY - 4; y--) { + level.setBlockAndUpdate(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState()); + } + } + } + BlockPos quarryPos = new BlockPos(qx - 2, refY, qz + 2); + level.setBlockAndUpdate(new BlockPos(qx - 4, refY, qz + 2), + ModBlocks.CREATIVE_BATTERY.get().defaultBlockState()); + level.setBlockAndUpdate(new BlockPos(qx - 3, refY, qz + 2), + ModBlocks.UNIVERSAL_PIPE.get().defaultBlockState()); + level.setBlockAndUpdate(quarryPos, ModBlocks.QUARRY_CONTROLLER.get().defaultBlockState()); + setAllModes(level, new BlockPos(qx - 3, refY, qz + 2), Direction.WEST, PipeIoMode.IN); + setAllModes(level, new BlockPos(qx - 3, refY, qz + 2), Direction.EAST, PipeIoMode.OUT); + if (level.getBlockEntity(quarryPos) instanceof QuarryControllerBlockEntity quarry) { + quarry.setItem(QuarryControllerBlockEntity.FRAME_SLOT, + new ItemStack(ModItems.FRAME_CASING.get(), 64)); + quarry.stageDisplay(region, refY - 5); + } + source.sendSuccess(() -> Component.literal("Built the Nerospace gallery: " + blocks.size() + " blocks, 4 RUNNING machine clusters (grinder line, fuel refinery " + "line, oxygen generator + lever, terraformer crew + lever — flip a lever to start " diff --git a/src/main/java/za/co/neroland/nerospace/compat/jei/RefiningCategory.java b/src/main/java/za/co/neroland/nerospace/compat/jei/RefiningCategory.java index 17ad103..8151629 100644 --- a/src/main/java/za/co/neroland/nerospace/compat/jei/RefiningCategory.java +++ b/src/main/java/za/co/neroland/nerospace/compat/jei/RefiningCategory.java @@ -54,7 +54,7 @@ public void setRecipe(IRecipeLayoutBuilder builder, RefiningRecipe recipe, IFocu builder.addInputSlot(1, 21).setStandardSlotBackground().addItemStacks(recipe.catalyst()); builder.addOutputSlot(66, 11).setStandardSlotBackground() .setFluidRenderer(Math.max(1, mb), false, 16, 16) - .add(ModFluids.ROCKET_FUEL.get(), (long) mb); + .add(ModFluids.ROCKET_FUEL.get(), mb); } @Override diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java index a988ed3..c0f6117 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockLootSubProvider.java @@ -54,6 +54,10 @@ protected void generate() { dropSelf(ModBlocks.HYDRATION_MODULE.get()); dropSelf(ModBlocks.TERRAFORM_MONITOR.get()); + // Quarry / Miner (the frame has noLootTable — drops nothing, like the rocket fuel block). + dropSelf(ModBlocks.QUARRY_CONTROLLER.get()); + dropSelf(ModBlocks.QUARRY_LANDMARK.get()); + // Power grid. dropSelf(ModBlocks.UNIVERSAL_PIPE.get()); dropSelf(ModBlocks.COMBUSTION_GENERATOR.get()); @@ -68,6 +72,7 @@ protected void generate() { dropSelf(ModBlocks.CREATIVE_GAS_TANK.get()); dropSelf(ModBlocks.ITEM_STORE.get()); dropSelf(ModBlocks.CREATIVE_ITEM_STORE.get()); + dropSelf(ModBlocks.TRASH_CAN.get()); // Phase 3 — Greenxertz. dropSelf(ModBlocks.NEROSTEEL_BLOCK.get()); diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java index f69aa31..9dc5c9d 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModBlockTagProvider.java @@ -56,7 +56,11 @@ protected void addTags(HolderLookup.Provider provider) { // Star Guide pedestal: pickaxe-mineable but breaks fine by hand (no // requiresCorrectToolForDrops) — it's the day-one tutorial block. ModBlocks.STAR_GUIDE.get(), - ModBlocks.LAUNCH_GANTRY.get()); + ModBlocks.LAUNCH_GANTRY.get(), + ModBlocks.QUARRY_CONTROLLER.get(), + ModBlocks.QUARRY_LANDMARK.get(), + ModBlocks.QUARRY_FRAME.get(), + ModBlocks.TRASH_CAN.get()); this.tag(BlockTags.NEEDS_IRON_TOOL) .add(ModBlocks.NEROSIUM_ORE.get(), @@ -79,7 +83,8 @@ protected void addTags(HolderLookup.Provider provider) { ModBlocks.TERRAFORM_MONITOR.get(), ModBlocks.UNIVERSAL_PIPE.get(), ModBlocks.COMBUSTION_GENERATOR.get(), - ModBlocks.PASSIVE_GENERATOR.get()); + ModBlocks.PASSIVE_GENERATOR.get(), + ModBlocks.QUARRY_CONTROLLER.get()); this.tag(Tags.Blocks.ORES) .add(ModBlocks.NEROSIUM_ORE.get(), ModBlocks.DEEPSLATE_NEROSIUM_ORE.get(), diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java index 8f1521e..d723439 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModLanguageProvider.java @@ -73,6 +73,7 @@ protected void addTranslations() { add(ModBlocks.CREATIVE_GAS_TANK.get(), "Creative Gas Tank"); add(ModBlocks.ITEM_STORE.get(), "Item Store"); add(ModBlocks.CREATIVE_ITEM_STORE.get(), "Creative Item Store"); + add(ModBlocks.TRASH_CAN.get(), "Trash Can"); add("container.nerospace.item_store", "Item Store"); add("block.nerospace.battery.readout", "Battery: %s / %s FE"); add("block.nerospace.creative_battery.readout", "Creative Battery: endless energy"); @@ -127,6 +128,22 @@ protected void addTranslations() { add("gui.nerospace.generator.core_active", "Core active"); add("gui.nerospace.generator.core_empty", "No core"); + // Quarry / Miner (MINER_DESIGN). + add(ModBlocks.QUARRY_CONTROLLER.get(), "Quarry Controller"); + add(ModBlocks.QUARRY_LANDMARK.get(), "Quarry Landmark"); + add(ModBlocks.QUARRY_FRAME.get(), "Quarry Frame"); + add(ModItems.FRAME_CASING.get(), "Frame Casing"); + add(ModItems.SPEED_MODULE.get(), "Speed Module"); + add(ModItems.EFFICIENCY_MODULE.get(), "Efficiency Module"); + add(ModItems.FORTUNE_MODULE.get(), "Fortune Module"); + add(ModItems.SILK_TOUCH_MODULE.get(), "Silk Touch Module"); + add("container.nerospace.quarry_controller", "Quarry Controller"); + add("gui.nerospace.quarry.state.idle", "Idle — place landmarks"); + add("gui.nerospace.quarry.state.building_frame", "Building frame"); + add("gui.nerospace.quarry.state.mining", "Mining"); + add("gui.nerospace.quarry.state.done", "Finished"); + add("gui.nerospace.quarry.state.paused", "Paused"); + // Phase 7 blocks. add(ModBlocks.CINDRITE_ORE.get(), "Cindrite Ore"); add(ModBlocks.CINDRITE_BLOCK.get(), "Block of Cindrite"); diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java index edad0d3..65dd0c0 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java @@ -91,6 +91,13 @@ protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerat blockModels.createTrivialCube(ModBlocks.STATION_FLOOR.get()); blockModels.createTrivialCube(ModBlocks.STATION_WALL.get()); + // Quarry / Miner (MINER_DESIGN): controller + landmark cubes; frame is a glowing translucent + // cube — a full-cube element model with AO off so the sprite's alpha drives the render layer + // (same trick as the Universal Pipe), letting you see through the open frame. + blockModels.createTrivialCube(ModBlocks.QUARRY_CONTROLLER.get()); + blockModels.createTrivialCube(ModBlocks.QUARRY_LANDMARK.get()); + registerTranslucentCube(blockModels, ModBlocks.QUARRY_FRAME.get()); + // Developer diagnostics — Sentry test block (hidden; /give only). blockModels.createTrivialCube(ModBlocks.SENTRY_TEST.get()); @@ -144,6 +151,13 @@ protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerat itemModels.generateFlatItem(ModItems.SPEED_UPGRADE.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.CAPACITY_UPGRADE.get(), ModelTemplates.FLAT_ITEM); + // Quarry / Miner — frame casing + cross-machine upgrade module cards. + itemModels.generateFlatItem(ModItems.FRAME_CASING.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.SPEED_MODULE.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.EFFICIENCY_MODULE.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.FORTUNE_MODULE.get(), ModelTemplates.FLAT_ITEM); + itemModels.generateFlatItem(ModItems.SILK_TOUCH_MODULE.get(), ModelTemplates.FLAT_ITEM); + // Phase 8d — oxygen suit (inventory item models; the worn layer is the equipment asset). itemModels.generateFlatItem(ModItems.OXYGEN_SUIT_HELMET.get(), ModelTemplates.FLAT_ITEM); itemModels.generateFlatItem(ModItems.OXYGEN_SUIT_CHESTPLATE.get(), ModelTemplates.FLAT_ITEM); @@ -323,6 +337,8 @@ private void registerShapedMachines(BlockModelGenerators blockModels) { .build(), machineMapping(ModBlocks.ITEM_STORE.get(), true, true), true); // Creative Item Store keeps the plain cube (no drawer — it conjures items, not stores them). blockModels.createTrivialCube(ModBlocks.CREATIVE_ITEM_STORE.get()); + // Trash Can — plain textured cube. + blockModels.createTrivialCube(ModBlocks.TRASH_CAN.get()); // Tanks: a corner-beam frame around a visible content core (real depth, no cutout needed). registerTank(blockModels, ModBlocks.FLUID_TANK.get()); @@ -391,6 +407,27 @@ private void registerTank(BlockModelGenerators blockModels, Block block) { shapedBlock(blockModels, block, builder.build(), mapping, false); } + /** + * A full-cube model with ambient occlusion OFF so the sprite's alpha selects the translucent + * render layer (26.1 derives the chunk layer from the texture) — used for the glowing, + * see-through quarry frame. + */ + private void registerTranslucentCube(BlockModelGenerators blockModels, Block block) { + var tex = TextureMapping.getBlockTexture(block); + TextureMapping mapping = new TextureMapping().put(TextureSlot.ALL, tex).put(TextureSlot.PARTICLE, tex); + ExtendedModelTemplate template = ExtendedModelTemplateBuilder.builder() + .requiredTextureSlot(TextureSlot.ALL) + .requiredTextureSlot(TextureSlot.PARTICLE) + .ambientOcclusion(false) + .element(e -> e.from(0, 0, 0).to(16, 16, 16) + .allFaces((dir, face) -> face.texture(TextureSlot.ALL))) + .build(); + Identifier model = template.create( + ModelLocationUtils.getModelLocation(block), mapping, blockModels.modelOutput); + blockModels.blockStateOutput.accept( + BlockModelGenerators.createSimpleBlock(block, BlockModelGenerators.plainVariant(model))); + } + /** * The Universal Pipe: a multipart blockstate — always the translucent core (4..12 cube), plus one * arm per connected face (the north arm model rotated for the other five). Translucency comes from diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModRecipeProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModRecipeProvider.java index 122820a..ef79398 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModRecipeProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModRecipeProvider.java @@ -30,6 +30,20 @@ protected ModRecipeProvider(HolderLookup.Provider registries, RecipeOutput outpu super(registries, output); } + /** A cross-machine upgrade module: a nerosteel/redstone frame around a signature centre item. */ + private void moduleRecipe(net.minecraft.world.item.Item result, net.minecraft.world.item.Item signature) { + ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), + RecipeCategory.MISC, result) + .pattern(" N ") + .pattern("RSR") + .pattern(" N ") + .define('N', ModTags.Items.INGOTS_NEROSTEEL) + .define('R', Items.REDSTONE) + .define('S', signature) + .unlockedBy("has_nerosteel_ingot", this.has(ModItems.NEROSTEEL_INGOT)) + .save(this.output); + } + @Override protected void buildRecipes() { // --- Smelting & blasting: raw nerosium -> ingot --------------------- @@ -137,6 +151,59 @@ protected void buildRecipes() { .unlockedBy("has_nerosium_ingot", this.has(ModItems.NEROSIUM_INGOT)) .save(this.output); + // --- Quarry / Miner (MINER_DESIGN) --------------------------------- + // Frame casing: a hollow ring of nerosteel (4 per craft). + ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), + RecipeCategory.MISC, ModItems.FRAME_CASING.get(), 4) + .pattern("III") + .pattern("I I") + .pattern("III") + .define('I', ModTags.Items.INGOTS_NEROSTEEL) + .unlockedBy("has_nerosteel_ingot", this.has(ModItems.NEROSTEEL_INGOT)) + .save(this.output); + + // Quarry Controller (Tier 1). + ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), + RecipeCategory.MISC, ModBlocks.QUARRY_CONTROLLER.get()) + .pattern("IDI") + .pattern("FRF") + .pattern("III") + .define('I', ModTags.Items.INGOTS_NEROSTEEL) + .define('D', Tags.Items.GEMS_DIAMOND) + .define('F', ModItems.FRAME_CASING.get()) + .define('R', Items.REDSTONE_BLOCK) + .unlockedBy("has_frame_casing", this.has(ModItems.FRAME_CASING.get())) + .save(this.output); + + // Quarry Landmark (3 per craft). + ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), + RecipeCategory.MISC, ModBlocks.QUARRY_LANDMARK.get(), 3) + .pattern("R") + .pattern("G") + .pattern("I") + .define('R', Items.REDSTONE) + .define('G', Tags.Items.GLASS_BLOCKS) + .define('I', ModTags.Items.INGOTS_NEROSTEEL) + .unlockedBy("has_nerosteel_ingot", this.has(ModItems.NEROSTEEL_INGOT)) + .save(this.output); + + // Cross-machine upgrade modules (same frame, signature centre item). + moduleRecipe(ModItems.SPEED_MODULE.get(), Items.SUGAR); + moduleRecipe(ModItems.EFFICIENCY_MODULE.get(), Items.LAPIS_LAZULI); + moduleRecipe(ModItems.FORTUNE_MODULE.get(), Items.DIAMOND); + moduleRecipe(ModItems.SILK_TOUCH_MODULE.get(), Items.AMETHYST_SHARD); + + // Trash Can: an iron bin around a cactus core. + ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), + RecipeCategory.MISC, ModBlocks.TRASH_CAN.get()) + .pattern("III") + .pattern("ICI") + .pattern("III") + .define('I', Tags.Items.INGOTS_IRON) + .define('C', Items.CACTUS) + .unlockedBy("has_iron_ingot", this.has(Items.IRON_INGOT)) + .save(this.output); + // Dust smelts/blasts back into an ingot (closes the processing loop). SimpleCookingRecipeBuilder.smelting(Ingredient.of(ModItems.NEROSIUM_DUST), RecipeCategory.MISC, CookingBookCategory.MISC, ModItems.NEROSIUM_INGOT, 0.7F, 200) diff --git a/src/main/java/za/co/neroland/nerospace/gametest/NerospaceGameTests.java b/src/main/java/za/co/neroland/nerospace/gametest/NerospaceGameTests.java index 96e4d1b..671f1b4 100644 --- a/src/main/java/za/co/neroland/nerospace/gametest/NerospaceGameTests.java +++ b/src/main/java/za/co/neroland/nerospace/gametest/NerospaceGameTests.java @@ -1419,6 +1419,10 @@ private static void testStarGuideAdvancementsResolve(GameTestHelper helper) { } /** Awarding a step's advancement flips its completion bit; a menu click marks it seen. */ + // makeMockServerPlayerInLevel is deprecated-for-removal, but the only non-deprecated mock + // (makeMockPlayer) returns a plain Player — this test needs a ServerPlayer (advancements + + // attachments), so there is no drop-in replacement yet. + @SuppressWarnings("removal") private static void testStarGuideProgressAndSeen(GameTestHelper helper) { net.minecraft.server.level.ServerPlayer player = helper.makeMockServerPlayerInLevel(); var manager = helper.getLevel().getServer().getAdvancements(); diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/MinerTier.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/MinerTier.java new file mode 100644 index 0000000..d2b7ae6 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/MinerTier.java @@ -0,0 +1,89 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; + +import za.co.neroland.nerospace.registry.ModDimensions; + +/** + * Quarry progression tiers (MINER_DESIGN). A tier gates three things: the largest square the quarry + * may claim ({@link #maxAreaSide}), how many upgrade-module slots it has ({@link #moduleSlots}), and + * the base per-cycle work ceiling ({@link #baseBlocksPerCycle}) — actual throughput scales with the + * power fed in, up to that ceiling × the modules' speed multiplier. + * + *

Planets are gated independently of the tier list (the quarry otherwise runs anywhere it has + * power): the harsh outer moons need a higher tier — Cindara wants Tier 2, Glacira wants Tier 3 — + * while the Overworld, Greenxertz, the Station and any unlisted dimension run at Tier 1. Per-planet + * speed/yield differences live in {@link PlanetMiningProfile}.

+ * + *

Tier 1 is the only one with content this slice; Tier 2/3 are scaffolded here so adding them is + * just a block + recipe + texture.

+ */ +public enum MinerTier { + + TIER_1(1, 16, 1, 2, 0xFFE0405A), + TIER_2(2, 32, 2, 4, 0xFFB060E0), + TIER_3(3, 64, 4, 8, 0xFFE0C040); + + private final int level; + private final int maxAreaSide; + private final int moduleSlots; + private final int baseBlocksPerCycle; + private final int accentColor; + + MinerTier(int level, int maxAreaSide, int moduleSlots, int baseBlocksPerCycle, int accentColor) { + this.level = level; + this.maxAreaSide = maxAreaSide; + this.moduleSlots = moduleSlots; + this.baseBlocksPerCycle = baseBlocksPerCycle; + this.accentColor = accentColor; + } + + /** Human-facing tier number (1-based). */ + public int level() { + return this.level; + } + + /** Longest side (blocks) of the rectangle this tier may claim. */ + public int maxAreaSide() { + return this.maxAreaSide; + } + + public int moduleSlots() { + return this.moduleSlots; + } + + /** Base per-work-cycle block ceiling (before the modules' speed multiplier). */ + public int baseBlocksPerCycle() { + return this.baseBlocksPerCycle; + } + + /** GUI / drill-head accent (ARGB): red → purple → gold across the tiers. */ + public int accentColor() { + return this.accentColor; + } + + /** Whether a quarry of this tier may operate in {@code dimension}. */ + public boolean canOperateIn(ResourceKey dimension) { + return this.level >= requiredTier(dimension); + } + + /** The minimum quarry tier needed to mine in {@code dimension} (1 for everything but the moons). */ + public static int requiredTier(ResourceKey dimension) { + if (ModDimensions.GLACIRA_LEVEL.equals(dimension)) { + return 3; + } + if (ModDimensions.CINDARA_LEVEL.equals(dimension)) { + return 2; + } + return 1; + } + + public static MinerTier byOrdinal(int ordinal) { + MinerTier[] values = values(); + if (ordinal < 0 || ordinal >= values.length) { + return TIER_1; + } + return values[ordinal]; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/OutputFilter.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/OutputFilter.java new file mode 100644 index 0000000..f3c3ef2 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/OutputFilter.java @@ -0,0 +1,19 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.world.item.ItemStack; + +/** + * The seam for the future quarry filter feature (MINER_DESIGN — "whitelist-keep, void-rest"). Every + * mined drop passes through {@link #keep(ItemStack)} before it is buffered; today the only + * implementation is {@link #KEEP_ALL}, so nothing is voided. A later filter card will supply a + * whitelist implementation and the mining loop needs no further change — items that fail the filter + * are simply not buffered (voided). + */ +@FunctionalInterface +public interface OutputFilter { + + OutputFilter KEEP_ALL = stack -> true; + + /** @return whether {@code drop} should be kept (buffered/output) rather than voided. */ + boolean keep(ItemStack drop); +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/PlanetMiningProfile.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/PlanetMiningProfile.java new file mode 100644 index 0000000..1848446 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/PlanetMiningProfile.java @@ -0,0 +1,35 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.Level; + +import za.co.neroland.nerospace.registry.ModDimensions; + +/** + * Per-planet mining characteristics (MINER_DESIGN — "yield/speed varies per planet"). The dense, + * deep moons mine slower; the established planets mine at the baseline. A profile is a pure data + * lookup keyed by dimension, with a default for anything unlisted. + * + *

Yield bonus is reserved for a follow-up (richer veins on the outer moons); it is surfaced here + * so the mining loop already has the hook, but defaults to none.

+ */ +public record PlanetMiningProfile(double speedMultiplier, double bonusDropChance) { + + private static final PlanetMiningProfile DEFAULT = new PlanetMiningProfile(1.0D, 0.0D); + private static final PlanetMiningProfile GREENXERTZ = new PlanetMiningProfile(1.0D, 0.0D); + private static final PlanetMiningProfile CINDARA = new PlanetMiningProfile(0.8D, 0.0D); + private static final PlanetMiningProfile GLACIRA = new PlanetMiningProfile(0.7D, 0.0D); + + public static PlanetMiningProfile forDimension(ResourceKey dimension) { + if (ModDimensions.GREENXERTZ_LEVEL.equals(dimension)) { + return GREENXERTZ; + } + if (ModDimensions.CINDARA_LEVEL.equals(dimension)) { + return CINDARA; + } + if (ModDimensions.GLACIRA_LEVEL.equals(dimension)) { + return GLACIRA; + } + return DEFAULT; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryChunkLoader.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryChunkLoader.java new file mode 100644 index 0000000..c64e711 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryChunkLoader.java @@ -0,0 +1,29 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.resources.Identifier; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.common.world.chunk.RegisterTicketControllersEvent; +import net.neoforged.neoforge.common.world.chunk.TicketController; + +import za.co.neroland.nerospace.Nerospace; + +/** + * The quarry's chunk {@link TicketController}. An actively-mining quarry force-loads the (bounded) + * chunks its region spans so a layer-by-layer dig continues even when the player walks its edges out + * of view; tickets are released when the quarry finishes, pauses or is removed. + */ +@EventBusSubscriber(modid = Nerospace.MODID) +public final class QuarryChunkLoader { + + public static final TicketController CONTROLLER = new TicketController( + Identifier.fromNamespaceAndPath(Nerospace.MODID, "quarry")); + + private QuarryChunkLoader() { + } + + @SubscribeEvent + public static void register(RegisterTicketControllersEvent event) { + event.register(CONTROLLER); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlock.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlock.java new file mode 100644 index 0000000..d58e9eb --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlock.java @@ -0,0 +1,80 @@ +package za.co.neroland.nerospace.machine.quarry; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.server.level.ServerPlayer; + +import za.co.neroland.nerospace.registry.ModBlockEntities; + +/** + * The quarry controller block (MINER_DESIGN). Holds its {@link MinerTier}; backed by + * {@link QuarryControllerBlockEntity}. Right-click opens the menu (status, buffers, module + frame + * slots). Activation is automatic: with valid landmarks nearby and power available, it builds its + * frame and starts mining. + */ +public class QuarryControllerBlock extends BaseEntityBlock { + + public static final MapCodec CODEC = + simpleCodec(props -> new QuarryControllerBlock(props, MinerTier.TIER_1)); + + private final MinerTier tier; + + public QuarryControllerBlock(Properties properties) { + this(properties, MinerTier.TIER_1); + } + + public QuarryControllerBlock(Properties properties, MinerTier tier) { + super(properties); + this.tier = tier; + } + + public MinerTier tier() { + return this.tier; + } + + @Override + protected MapCodec codec() { + return CODEC; + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new QuarryControllerBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + if (level.isClientSide()) { + return null; + } + return createTickerHelper(type, ModBlockEntities.QUARRY_CONTROLLER.get(), + (lvl, pos, st, be) -> be.tick(lvl, pos, st)); + } + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { + if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof QuarryControllerBlockEntity controller) { + serverPlayer.openMenu(controller); + } + } + return InteractionResult.SUCCESS; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java new file mode 100644 index 0000000..b228829 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java @@ -0,0 +1,888 @@ +package za.co.neroland.nerospace.machine.quarry; + +import java.util.ArrayList; +import java.util.List; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.ResourceHandlerUtil; +import net.neoforged.neoforge.transfer.energy.EnergyHandler; +import net.neoforged.neoforge.transfer.energy.SimpleEnergyHandler; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.fluid.FluidStacksResourceHandler; +import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.transaction.Transaction; + +import org.jetbrains.annotations.Nullable; + +import za.co.neroland.nerospace.Tuning; +import za.co.neroland.nerospace.machine.MachineItemHandler; +import za.co.neroland.nerospace.module.MachineModules; +import za.co.neroland.nerospace.registry.ModBlockEntities; +import za.co.neroland.nerospace.registry.ModBlocks; +import za.co.neroland.nerospace.registry.ModItems; + +/** + * The quarry controller (MINER_DESIGN): the single block that runs the dig. Once landmarks mark an + * L-shaped region nearby, it materialises a frame ring (one {@code frame_casing} per open perimeter + * cell) and then excavates the rectangle layer-by-layer from the landmark plane down to the world + * floor, like a 3D printer in reverse. Mined items buffer internally and auto-eject to adjacent + * storage / pipes; source fluids are sucked into a fluid buffer that auto-ejects to adjacent tanks. + * Mining pauses (never loses items) when the buffers fill or power runs out. + * + *

Throughput scales with supplied power up to the tier's per-tick ceiling × the modules' speed + * multiplier × the planet's speed factor. See {@link MinerTier}, {@link MachineModules}, + * {@link PlanetMiningProfile}.

+ */ +public class QuarryControllerBlockEntity extends BlockEntity implements Container, MenuProvider { + + /** Internal output buffer slot count. */ + public static final int OUTPUT_SLOTS = 12; + public static final int FRAME_SLOT = 0; + /** Insert cap on the energy buffer (so feeding more power raises throughput to the tier ceiling). */ + public static final int ENERGY_MAX_INSERT = 10_000; + public static final int DATA_COUNT = 7; + /** Max cells examined per tick (mined + skipped) so empty volumes can't spike a single tick. */ + private static final int SCAN_BUDGET_PER_TICK = 4096; + + /** Lifecycle of the dig. */ + public enum State { + IDLE, BUILDING_FRAME, MINING, DONE, PAUSED + } + + private final MinerTier tier; + private final int moduleSlots; + private final int containerSize; + + private final QuarryEnergy energy = new QuarryEnergy(); + + @SuppressWarnings("this-escape") + private final MachineItemHandler frameHandler = new MachineItemHandler(1, this::setChanged, + (index, resource) -> resource.toStack(1).is(ModItems.FRAME_CASING.get())); + + /** Output buffer: external insertion is rejected (mining-only); extraction (pipes) is allowed. */ + @SuppressWarnings("this-escape") + private final MachineItemHandler outputHandler = new MachineItemHandler(OUTPUT_SLOTS, this::setChanged, + (index, resource) -> false); + + @SuppressWarnings("this-escape") + private final MachineModules modules; + + @SuppressWarnings("this-escape") + private final QuarryFluidBuffer fluidBuffer = new QuarryFluidBuffer(); + + private final OutputFilter filter = OutputFilter.KEEP_ALL; + + private State state = State.IDLE; + private String pauseReason = ""; + @Nullable + private QuarryRegion region; + private int frameIndex; + private int currentY; + private int cursor; + /** Columns (packed relative dx*128+dz) skipped because a tile-entity sits in them. */ + private final IntOpenHashSet skippedColumns = new IntOpenHashSet(); + /** Chunks this quarry force-loads while mining. */ + private final transient LongOpenHashSet forcedChunks = new LongOpenHashSet(); + /** Cached frame-cell count (recomputed lazily). */ + private transient int frameTotal = -1; + /** Last state pushed to clients (so a change forces a sync). */ + private transient State lastSyncedState = State.IDLE; + /** Client-side smoothed drill-head position (interpolated by the renderer). */ + public double dispX; + public double dispY; + public double dispZ; + public boolean dispInit; + + private final ContainerData dataAccess = new ContainerData() { + @Override + public int get(int index) { + return switch (index) { + case 0 -> energy.getAmountAsInt(); + case 1 -> energy.getCapacityAsInt(); + case 2 -> state.ordinal(); + case 3 -> fluidBuffer.getAmountAsInt(0); + case 4 -> fluidBuffer.getCapacity(); + case 5 -> currentY; + case 6 -> { + QuarryRegion rg = region; + yield rg == null ? 0 : rg.refY(); + } + default -> 0; + }; + } + + @Override + public void set(int index, int value) { + // server-authoritative; nothing client-settable + } + + @Override + public int getCount() { + return DATA_COUNT; + } + }; + + @SuppressWarnings("this-escape") // setChanged callbacks are only invoked after construction + public QuarryControllerBlockEntity(BlockPos pos, BlockState blockState) { + super(ModBlockEntities.QUARRY_CONTROLLER.get(), pos, blockState); + this.tier = blockState.getBlock() instanceof QuarryControllerBlock controller + ? controller.tier() : MinerTier.TIER_1; + this.moduleSlots = this.tier.moduleSlots(); + this.modules = new MachineModules(this.moduleSlots, this::setChanged); + this.containerSize = 1 + this.moduleSlots + OUTPUT_SLOTS; + } + + // --- Capability accessors --------------------------------------------------- + + public EnergyHandler getEnergyHandler() { + return this.energy; + } + + public ResourceHandler getOutputHandler() { + return this.outputHandler; + } + + public ResourceHandler getFrameHandler() { + return this.frameHandler; + } + + public ResourceHandler getFluidHandler() { + return this.fluidBuffer; + } + + public ContainerData getDataAccess() { + return this.dataAccess; + } + + public MinerTier tier() { + return this.tier; + } + + // --- Client render accessors (populated on the client via the update tag) ---- + + public State renderState() { + return this.state; + } + + @Nullable + public QuarryRegion renderRegion() { + return this.region; + } + + /** Linear column index currently being mined (clamped into range for the renderer). */ + public int renderCursor() { + return this.cursor; + } + + public int renderCurrentY() { + return this.currentY; + } + + /** + * Creative/gallery helper: adopt an already-built {@code region} (frame assumed placed) and drop + * straight into a powered MINING state from {@code startY}, so a staged display mines for real. + */ + public void stageDisplay(QuarryRegion region, int startY) { + this.region = region; + this.frameTotal = region.framePositions().size(); + this.frameIndex = this.frameTotal; + this.currentY = startY; + this.cursor = 0; + this.skippedColumns.clear(); + this.state = State.MINING; + this.pauseReason = ""; + this.energy.fill(); + setChanged(); + } + + // --- Ticking ---------------------------------------------------------------- + + public void tick(Level level, BlockPos pos, BlockState blockState) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + State before = this.state; + switch (this.state) { + case IDLE -> tryActivate(serverLevel, pos); + case BUILDING_FRAME -> buildFrame(serverLevel); + case MINING -> mine(serverLevel); + case PAUSED -> resume(serverLevel, pos); + case DONE -> { } + default -> { } + } + + // Always try to drain the buffers once we own a region — otherwise a "buffer full" pause + // could never clear itself (nothing would push items/fluids out while paused). + if (this.region != null) { + autoEject(serverLevel, pos); + } + + // Push a client update on a state change, and periodically while mining so the drill-head + // renderer can follow the cursor. + boolean stateChanged = this.state != before || this.state != this.lastSyncedState; + if (stateChanged || (this.state == State.MINING && serverLevel.getGameTime() % 5L == 0L)) { + this.lastSyncedState = this.state; + serverLevel.sendBlockUpdated(pos, blockState, blockState, Block.UPDATE_CLIENTS); + } + } + + /** Resume a paused dig: retry the phase that paused, without bouncing the displayed state. */ + private void resume(ServerLevel level, BlockPos pos) { + if (this.region == null) { + this.state = State.IDLE; + tryActivate(level, pos); + return; + } + if (!this.tier.canOperateIn(level.dimension())) { + this.pauseReason = "wrong_planet"; + return; + } + if (this.frameIndex < frameTotal()) { + this.state = State.BUILDING_FRAME; + buildFrame(level); + } else { + this.state = State.MINING; + mine(level); + } + } + + /** Look for landmarks and, if a valid region exists and the planet allows this tier, start. */ + private void tryActivate(ServerLevel level, BlockPos pos) { + // Throttle the search. + if (level.getGameTime() % 20L != 0L) { + return; + } + if (!this.tier.canOperateIn(level.dimension())) { + setPaused("wrong_planet"); + return; + } + BlockPos seed = QuarryRegion.findNearbyLandmark(level, pos, this.tier.maxAreaSide()); + if (seed == null) { + return; + } + QuarryRegion found = QuarryRegion.fromLandmarks(level, seed, this.tier.maxAreaSide()); + if (found == null) { + setPaused("bad_region"); + return; + } + this.region = found; + this.frameTotal = -1; + consumeLandmarks(level, found); + this.frameIndex = 0; + // Stay at the reference plane while building (depth 0); drop to the first layer when mining. + this.currentY = found.refY(); + this.cursor = 0; + this.skippedColumns.clear(); + this.state = State.BUILDING_FRAME; + this.pauseReason = ""; + setChanged(); + } + + /** Total frame cells (cached; recomputed lazily from the region). */ + private int frameTotal() { + QuarryRegion rg = this.region; + if (this.frameTotal < 0 && rg != null) { + this.frameTotal = rg.framePositions().size(); + } + return Math.max(0, this.frameTotal); + } + + /** Remove the landmark blocks inside the claimed rectangle at the reference plane. */ + private void consumeLandmarks(ServerLevel level, QuarryRegion region) { + for (int x = region.minX(); x <= region.maxX(); x++) { + for (int z = region.minZ(); z <= region.maxZ(); z++) { + BlockPos lp = new BlockPos(x, region.refY(), z); + if (level.getBlockState(lp).getBlock() instanceof QuarryLandmarkBlock) { + level.removeBlock(lp, false); + } + } + } + } + + /** Place the frame ring a few cells per tick, consuming a casing per open cell. */ + private void buildFrame(ServerLevel level) { + QuarryRegion region = this.region; + if (region == null) { + this.state = State.IDLE; + return; + } + List ring = region.framePositions(); + int placedThisTick = 0; + boolean changed = false; + while (this.frameIndex < ring.size() && placedThisTick < 8) { + BlockPos fp = ring.get(this.frameIndex); + BlockState existing = level.getBlockState(fp); + if (existing.getBlock() instanceof QuarryFrameBlock) { + this.frameIndex++; + continue; + } + // Only draw the frame through open cells; terrain-backed perimeter cells need no casing. + if (!existing.isAir() && !existing.canBeReplaced()) { + this.frameIndex++; + continue; + } + ItemStack casing = this.frameHandler.getStack(FRAME_SLOT); + if (casing.isEmpty()) { + setPaused("need_material"); + return; + } + level.setBlock(fp, ModBlocks.QUARRY_FRAME.get().defaultBlockState(), Block.UPDATE_CLIENTS); + casing.shrink(1); + placedThisTick++; + this.frameIndex++; + changed = true; + } + if (this.frameIndex >= ring.size()) { + this.state = State.MINING; + this.currentY = region.refY() - 1; // drop to the first layer below the frame plane + this.pauseReason = ""; + changed = true; + } + if (changed) { + setChanged(); + } + } + + private void mine(ServerLevel level) { + QuarryRegion region = this.region; + if (region == null) { + this.state = State.IDLE; + return; + } + int floor = level.getMinY(); + int energyPerBlock = quarryEnergyPerBlock(); + int cap = blocksPerTick(level); + ItemStack tool = miningTool(level); + int columns = region.columns(); + boolean changed = false; + + // Skips (air, caves, already-cleared cells) don't cost energy or count toward the mined cap, + // so bound the cells examined per tick to avoid a spike when sweeping large empty volumes. + int scanned = 0; + for (int processed = 0; processed < cap && scanned < SCAN_BUDGET_PER_TICK; ) { + scanned++; + if (this.currentY < floor) { + this.state = State.DONE; + releaseForcedChunks(level); + changed = true; + break; + } + if (this.cursor >= columns) { + this.cursor = 0; + this.currentY--; + continue; + } + BlockPos target = region.columnPos(this.cursor, this.currentY); + int x = target.getX(); + int z = target.getZ(); + + if (isColumnSkipped(x, z)) { + this.cursor++; + continue; + } + + int cx = x >> 4; + int cz = z >> 4; + if (!level.hasChunk(cx, cz)) { + forceLoad(level, cx, cz); + changed = true; + break; // retry next tick once the chunk is in + } + + BlockState state = level.getBlockState(target); + if (state.isAir() || state.getBlock() instanceof QuarryFrameBlock) { + this.cursor++; + continue; + } + // Respect tile-entities: skip the whole column (richer handling is a follow-up). + if (state.hasBlockEntity()) { + markColumnSkipped(x, z); + this.cursor++; + continue; + } + + FluidState fluidState = state.getFluidState(); + if (state.getBlock() instanceof net.minecraft.world.level.block.LiquidBlock && fluidState.isSource()) { + // A pure liquid source block: pull it into the fluid buffer or pause if full. + if (!suckFluid(level, target, fluidState)) { + setPaused("fluid_full"); + changed = true; + break; + } + this.cursor++; + changed = true; + continue; + } + + // Unbreakable (bedrock, barrier, command blocks): skip without spending energy. + if (state.getDestroySpeed(level, target) < 0.0F) { + this.cursor++; + continue; + } + + if (this.energy.getAmountAsInt() < energyPerBlock) { + setPaused("no_power"); + changed = true; + break; + } + + List drops = Block.getDrops(state, level, target, null, null, tool); + if (!acceptDrops(drops)) { + setPaused("buffer_full"); + changed = true; + break; + } + level.removeBlock(target, false); + spawnDrillFx(level, target); + this.energy.consume(energyPerBlock); + this.cursor++; + processed++; + changed = true; + } + + if (changed) { + setChanged(); + } + } + + /** Per-tick block ceiling = tier base × module speed × planet speed, ≥ 1. */ + private int blocksPerTick(ServerLevel level) { + double planet = PlanetMiningProfile.forDimension(level.dimension()).speedMultiplier(); + double scaled = this.tier.baseBlocksPerCycle() * this.modules.speedMultiplier() * planet; + return Math.max(1, (int) Math.round(scaled)); + } + + private int quarryEnergyPerBlock() { + return Math.max(1, (int) Math.round(Tuning.quarryEnergyPerBlock() * this.modules.energyMultiplier())); + } + + /** Build the synthetic harvest tool reflecting the Silk-Touch / Fortune modules. */ + private ItemStack miningTool(ServerLevel level) { + ItemStack tool = new ItemStack(Items.NETHERITE_PICKAXE); + var enchantments = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT); + if (this.modules.silkTouch()) { + tool.enchant(enchantments.getOrThrow(Enchantments.SILK_TOUCH), 1); + } else { + int fortune = this.modules.fortuneLevel(); + if (fortune > 0) { + tool.enchant(enchantments.getOrThrow(Enchantments.FORTUNE), fortune); + } + } + return tool; + } + + /** Try to buffer all kept drops atomically; filtered-out drops are voided. */ + private boolean acceptDrops(List drops) { + List kept = new ArrayList<>(); + for (ItemStack drop : drops) { + if (!drop.isEmpty() && this.filter.keep(drop)) { + kept.add(drop.copy()); + } + } + if (kept.isEmpty()) { + return true; + } + ItemStack[] sim = new ItemStack[OUTPUT_SLOTS]; + for (int i = 0; i < OUTPUT_SLOTS; i++) { + sim[i] = this.outputHandler.getStack(i).copy(); + } + for (ItemStack drop : kept) { + if (!mergeInto(sim, drop)) { + return false; + } + } + for (int i = 0; i < OUTPUT_SLOTS; i++) { + this.outputHandler.setStack(i, sim[i]); + } + return true; + } + + /** Merge {@code stack} into the slot array (existing matches first, then empty); true if it all fit. */ + private static boolean mergeInto(ItemStack[] slots, ItemStack stack) { + for (int i = 0; i < slots.length && !stack.isEmpty(); i++) { + ItemStack slot = slots[i]; + if (!slot.isEmpty() && ItemStack.isSameItemSameComponents(slot, stack)) { + int room = Math.min(slot.getMaxStackSize(), 64) - slot.getCount(); + int moved = Math.min(room, stack.getCount()); + if (moved > 0) { + slot.grow(moved); + stack.shrink(moved); + } + } + } + for (int i = 0; i < slots.length && !stack.isEmpty(); i++) { + if (slots[i].isEmpty()) { + slots[i] = stack.copy(); + stack.setCount(0); + } + } + return stack.isEmpty(); + } + + /** A small spark burst at the drill head, plus a glowing beam back up to the frame plane. */ + private void spawnDrillFx(ServerLevel level, BlockPos target) { + double cx = target.getX() + 0.5; + double cy = target.getY() + 0.5; + double cz = target.getZ() + 0.5; + level.sendParticles(net.minecraft.core.particles.ParticleTypes.ELECTRIC_SPARK, + cx, cy, cz, 3, 0.2, 0.2, 0.2, 0.0); + QuarryRegion region = this.region; + if (region != null) { + double topY = region.refY() + 0.5; + level.sendParticles(net.minecraft.core.particles.ParticleTypes.END_ROD, + cx, (cy + topY) / 2.0, cz, 1, 0.02, (topY - cy) / 4.0, 0.02, 0.0); + } + } + + private boolean suckFluid(ServerLevel level, BlockPos pos, FluidState fluidState) { + FluidResource resource = FluidResource.of(fluidState.getType()); + try (Transaction tx = Transaction.openRoot()) { + int inserted = this.fluidBuffer.insert(0, resource, 1000, tx); + if (inserted >= 1000) { + tx.commit(); + level.removeBlock(pos, false); + return true; + } + } + return false; + } + + // --- Skipped-column bookkeeping -------------------------------------------- + + private int columnKey(int x, int z) { + QuarryRegion region = this.region; + if (region == null) { + return -1; + } + return (x - region.minX()) * 128 + (z - region.minZ()); + } + + private boolean isColumnSkipped(int x, int z) { + return this.skippedColumns.contains(columnKey(x, z)); + } + + private void markColumnSkipped(int x, int z) { + this.skippedColumns.add(columnKey(x, z)); + } + + // --- Auto-eject ------------------------------------------------------------- + + private void autoEject(ServerLevel level, BlockPos pos) { + for (Direction dir : Direction.values()) { + BlockPos np = pos.relative(dir); + ResourceHandler itemTarget = net.neoforged.neoforge.capabilities.Capabilities.Item.BLOCK + .getCapability(level, np, null, null, dir.getOpposite()); + if (itemTarget != null) { + try (Transaction tx = Transaction.openRoot()) { + int moved = ResourceHandlerUtil.move(this.outputHandler, itemTarget, r -> true, 16, tx); + if (moved > 0) { + tx.commit(); + } + } + } + ResourceHandler fluidTarget = net.neoforged.neoforge.capabilities.Capabilities.Fluid.BLOCK + .getCapability(level, np, null, null, dir.getOpposite()); + if (fluidTarget != null) { + try (Transaction tx = Transaction.openRoot()) { + int moved = ResourceHandlerUtil.move(this.fluidBuffer, fluidTarget, r -> true, 500, tx); + if (moved > 0) { + tx.commit(); + } + } + } + } + } + + // --- Chunk loading ---------------------------------------------------------- + + private void forceLoad(ServerLevel level, int cx, int cz) { + long key = ((long) cx << 32) | (cz & 0xFFFFFFFFL); + if (this.forcedChunks.add(key)) { + QuarryChunkLoader.CONTROLLER.forceChunk(level, this.worldPosition, cx, cz, true, false); + } + } + + private void releaseForcedChunks(ServerLevel level) { + if (this.forcedChunks.isEmpty()) { + return; + } + LongIterator it = this.forcedChunks.iterator(); + while (it.hasNext()) { + long key = it.nextLong(); + int cx = (int) (key >> 32); + int cz = (int) key; + QuarryChunkLoader.CONTROLLER.forceChunk(level, this.worldPosition, cx, cz, false, false); + } + this.forcedChunks.clear(); + } + + private void setPaused(String reason) { + this.state = State.PAUSED; + this.pauseReason = reason; + setChanged(); + } + + @Override + public void setRemoved() { + if (this.level instanceof ServerLevel serverLevel) { + releaseForcedChunks(serverLevel); + removeFrame(serverLevel); + } + super.setRemoved(); + } + + /** Tear down the frame ring this controller built. */ + private void removeFrame(ServerLevel level) { + QuarryRegion region = this.region; + if (region == null) { + return; + } + for (BlockPos fp : region.framePositions()) { + if (level.getBlockState(fp).getBlock() instanceof QuarryFrameBlock) { + level.removeBlock(fp, false); + } + } + } + + // --- Persistence ------------------------------------------------------------ + + @Override + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + this.energy.serialize(output.child("Energy")); + this.fluidBuffer.serialize(output.child("Fluid")); + output.store("Frame", ItemStack.OPTIONAL_CODEC, this.frameHandler.getStack(FRAME_SLOT)); + for (int i = 0; i < OUTPUT_SLOTS; i++) { + output.store("Out" + i, ItemStack.OPTIONAL_CODEC, this.outputHandler.getStack(i)); + } + this.modules.save(output); + output.putString("MinerState", this.state.name()); + output.putString("PauseReason", this.pauseReason); + output.putInt("FrameIndex", this.frameIndex); + output.putInt("CurrentY", this.currentY); + output.putInt("Cursor", this.cursor); + QuarryRegion region = this.region; + if (region != null) { + output.putBoolean("HasRegion", true); + region.save(output.child("Region")); + } else { + output.putBoolean("HasRegion", false); + } + int[] skipped = this.skippedColumns.toIntArray(); + output.putInt("SkipCount", skipped.length); + for (int i = 0; i < skipped.length; i++) { + output.putInt("Skip" + i, skipped[i]); + } + } + + @Override + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + this.energy.deserialize(input.childOrEmpty("Energy")); + this.fluidBuffer.deserialize(input.childOrEmpty("Fluid")); + this.frameHandler.setStack(FRAME_SLOT, input.read("Frame", ItemStack.OPTIONAL_CODEC).orElse(ItemStack.EMPTY)); + for (int i = 0; i < OUTPUT_SLOTS; i++) { + this.outputHandler.setStack(i, input.read("Out" + i, ItemStack.OPTIONAL_CODEC).orElse(ItemStack.EMPTY)); + } + this.modules.load(input); + this.state = parseState(input.getStringOr("MinerState", State.IDLE.name())); + this.pauseReason = input.getStringOr("PauseReason", ""); + this.frameIndex = input.getIntOr("FrameIndex", 0); + this.currentY = input.getIntOr("CurrentY", 0); + this.cursor = input.getIntOr("Cursor", 0); + this.region = input.getBooleanOr("HasRegion", false) + ? QuarryRegion.load(input.childOrEmpty("Region")) : null; + this.frameTotal = -1; + this.skippedColumns.clear(); + int skipCount = input.getIntOr("SkipCount", 0); + for (int i = 0; i < skipCount; i++) { + this.skippedColumns.add(input.getIntOr("Skip" + i, -1)); + } + } + + private static State parseState(String name) { + try { + return State.valueOf(name); + } catch (IllegalArgumentException ex) { + return State.IDLE; + } + } + + // --- Client sync (region + state + cursor drive the drill-head renderer) ----- + + @Override + public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + return saveCustomOnly(registries); + } + + // --- MenuProvider ----------------------------------------------------------- + + @Override + public Component getDisplayName() { + return Component.translatable("container.nerospace.quarry_controller"); + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) { + return new QuarryMenu(containerId, playerInventory, this, this.dataAccess, this.moduleSlots); + } + + // --- Container (combined view: [0]=frame, [1..M]=modules, [M+1..]=output) ---- + + public int moduleSlots() { + return this.moduleSlots; + } + + private MachineItemHandler routeHandler(int slot) { + if (slot == FRAME_SLOT) { + return this.frameHandler; + } + if (slot <= this.moduleSlots) { + return this.modules.store(); + } + return this.outputHandler; + } + + private int routeIndex(int slot) { + if (slot == FRAME_SLOT) { + return 0; + } + if (slot <= this.moduleSlots) { + return slot - 1; + } + return slot - 1 - this.moduleSlots; + } + + @Override + public int getContainerSize() { + return this.containerSize; + } + + @Override + public boolean isEmpty() { + return this.frameHandler.isStoreEmpty() && this.modules.store().isStoreEmpty() + && this.outputHandler.isStoreEmpty(); + } + + @Override + public ItemStack getItem(int slot) { + return routeHandler(slot).getStack(routeIndex(slot)); + } + + @Override + public ItemStack removeItem(int slot, int amount) { + return routeHandler(slot).removeStack(routeIndex(slot), amount); + } + + @Override + public ItemStack removeItemNoUpdate(int slot) { + return routeHandler(slot).takeStack(routeIndex(slot)); + } + + @Override + public void setItem(int slot, ItemStack stack) { + stack.limitSize(Math.min(this.getMaxStackSize(), stack.getMaxStackSize())); + routeHandler(slot).setStack(routeIndex(slot), stack); + } + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + if (slot == FRAME_SLOT) { + return stack.is(ModItems.FRAME_CASING.get()); + } + if (slot <= this.moduleSlots) { + return za.co.neroland.nerospace.module.UpgradeModuleItem.isModule(stack); + } + return false; // output slots: take only + } + + @Override + public boolean stillValid(Player player) { + if (this.level == null || this.level.getBlockEntity(this.worldPosition) != this) { + return false; + } + return player.distanceToSqr(this.worldPosition.getX() + 0.5, + this.worldPosition.getY() + 0.5, this.worldPosition.getZ() + 0.5) <= 64.0; + } + + @Override + public void clearContent() { + this.frameHandler.clearStore(); + this.modules.store().clearStore(); + this.outputHandler.clearStore(); + } + + // --- Internal buffers ------------------------------------------------------- + + private final class QuarryEnergy extends SimpleEnergyHandler { + private QuarryEnergy() { + super(Tuning.quarryBuffer(), ENERGY_MAX_INSERT, 0); + } + + @Override + protected void onEnergyChanged(int previousAmount) { + QuarryControllerBlockEntity.this.setChanged(); + } + + void consume(int amount) { + set(Math.max(0, getAmountAsInt() - amount)); + } + + void fill() { + set(getCapacityAsInt()); + } + } + + private final class QuarryFluidBuffer extends FluidStacksResourceHandler { + private QuarryFluidBuffer() { + super(1, Tuning.quarryFluidCapacity()); + } + + int getCapacity() { + return Tuning.quarryFluidCapacity(); + } + + @Override + protected void onContentsChanged(int index, FluidStack oldStack) { + QuarryControllerBlockEntity.this.setChanged(); + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java new file mode 100644 index 0000000..cad27f1 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java @@ -0,0 +1,27 @@ +package za.co.neroland.nerospace.machine.quarry; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.world.level.block.Block; + +/** + * The glowing structural frame the quarry materialises around its claimed region (MINER_DESIGN — + * "build frame + drill head"). Built by the controller (one {@code frame_casing} per block) and + * removed when the controller is removed; it carries no loot table, so breaking one by hand yields + * nothing. A dedicated class so the mining loop can recognise and skip frames — its own and other + * quarries' ("respect claims"). The translucent/emissive look is a client render-type choice; the + * block is registered with {@code noOcclusion()} and an emissive light level. + */ +public class QuarryFrameBlock extends Block { + + public static final MapCodec CODEC = simpleCodec(QuarryFrameBlock::new); + + public QuarryFrameBlock(Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return CODEC; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlock.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlock.java new file mode 100644 index 0000000..d88e7d8 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlock.java @@ -0,0 +1,65 @@ +package za.co.neroland.nerospace.machine.quarry; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import za.co.neroland.nerospace.registry.ModBlockEntities; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.level.BlockGetter; + +/** + * The quarry Landmark: a small marker post placed at the corners of the area to mine. Three forming + * an L define the rectangle (see {@link QuarryRegion}); the controller scans them on activation and + * consumes them. Carries a {@link QuarryLandmarkBlockEntity} purely so the client can draw the + * projected marker lasers. + */ +public class QuarryLandmarkBlock extends BaseEntityBlock { + + public static final MapCodec CODEC = simpleCodec(QuarryLandmarkBlock::new); + + private static final VoxelShape SHAPE = Block.box(5.0D, 0.0D, 5.0D, 11.0D, 12.0D, 11.0D); + + public QuarryLandmarkBlock(Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + @Override + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return SHAPE; + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new QuarryLandmarkBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // Client-only cosmetic ticker for the projected marker lasers. + if (!level.isClientSide()) { + return null; + } + return createTickerHelper(type, ModBlockEntities.QUARRY_LANDMARK.get(), + (lvl, pos, st, be) -> be.clientTick(lvl, pos, st)); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlockEntity.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlockEntity.java new file mode 100644 index 0000000..0bd9297 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryLandmarkBlockEntity.java @@ -0,0 +1,45 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import za.co.neroland.nerospace.registry.ModBlockEntities; + +/** + * Block entity for a {@link QuarryLandmarkBlock}: it exists so the client can animate the Nerospace + * marker lasers — a glowing vertical beam plus marching projection dots along the four horizontal + * axes (the "links" the controller scans). Purely cosmetic; no server state. + */ +public class QuarryLandmarkBlockEntity extends BlockEntity { + + public QuarryLandmarkBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.QUARRY_LANDMARK.get(), pos, state); + } + + /** Client-only cosmetic tick: project the animated marker lasers. */ + public void clientTick(Level level, BlockPos pos, BlockState state) { + long time = level.getGameTime(); + if ((time & 3L) != 0L) { + return; + } + double x = pos.getX() + 0.5; + double y = pos.getY() + 0.5; + double z = pos.getZ() + 0.5; + + // Vertical beam. + double rise = (time % 16L) * 0.06; + for (int i = 0; i < 3; i++) { + level.addParticle(ParticleTypes.END_ROD, x, y + i * 0.6 + rise, z, 0.0, 0.01, 0.0); + } + // Marching projection dots along each horizontal axis. + double march = (time % 12L) * 0.4; + for (Direction dir : Direction.Plane.HORIZONTAL) { + level.addParticle(ParticleTypes.GLOW, + x + dir.getStepX() * march, y, z + dir.getStepZ() * march, 0.0, 0.0, 0.0); + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryMenu.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryMenu.java new file mode 100644 index 0000000..af62ae2 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryMenu.java @@ -0,0 +1,184 @@ +package za.co.neroland.nerospace.machine.quarry; + +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import za.co.neroland.nerospace.module.UpgradeModuleItem; +import za.co.neroland.nerospace.registry.ModItems; +import za.co.neroland.nerospace.registry.ModMenuTypes; + +/** + * Menu for the quarry controller. Combined machine inventory layout: slot 0 = frame casing, + * 1..M = module cards, then the output buffer; followed by the player inventory. Status (energy, + * state, fluid, depth) is synced through {@link ContainerData}. + * + *

The client constructor assumes a Tier-1 layout (1 module slot). Higher tiers will pass the slot + * count through the open buffer when they ship — for now only Tier 1 is registered.

+ */ +public class QuarryMenu extends AbstractContainerMenu { + + private static final int TIER1_MODULE_SLOTS = 1; + + private final Container container; + private final ContainerData data; + private final int machineSlots; + private final int moduleSlots; + + /** Client constructor (Tier-1 layout). */ + public QuarryMenu(int containerId, Inventory playerInventory) { + this(containerId, playerInventory, + new SimpleContainer(1 + TIER1_MODULE_SLOTS + QuarryControllerBlockEntity.OUTPUT_SLOTS), + new SimpleContainerData(QuarryControllerBlockEntity.DATA_COUNT), + TIER1_MODULE_SLOTS); + } + + /** Server constructor. */ + @SuppressWarnings("this-escape") + public QuarryMenu(int containerId, Inventory playerInventory, Container container, ContainerData data, int moduleSlots) { + super(ModMenuTypes.QUARRY_CONTROLLER.get(), containerId); + this.container = container; + this.data = data; + this.moduleSlots = moduleSlots; + this.machineSlots = container.getContainerSize(); + + // Frame casing slot, then module slots along the top row. + this.addSlot(new FrameSlot(container, QuarryControllerBlockEntity.FRAME_SLOT, 8, 20)); + for (int i = 0; i < moduleSlots; i++) { + this.addSlot(new ModuleSlot(container, 1 + i, 26 + i * 18, 20)); + } + + // Output buffer: 2 rows x 6. + int outStart = 1 + moduleSlots; + for (int i = 0; i < QuarryControllerBlockEntity.OUTPUT_SLOTS; i++) { + int row = i / 6; + int col = i % 6; + this.addSlot(new OutputSlot(container, outStart + i, 8 + col * 18, 42 + row * 18)); + } + + this.addStandardInventorySlots(playerInventory, 8, 126); + this.addDataSlots(data); + } + + @Override + public boolean stillValid(Player player) { + return this.container.stillValid(player); + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + ItemStack moved = ItemStack.EMPTY; + Slot slot = this.slots.get(index); + if (slot == null || !slot.hasItem()) { + return ItemStack.EMPTY; + } + ItemStack raw = slot.getItem(); + moved = raw.copy(); + int playerStart = this.machineSlots; + int playerEnd = this.machineSlots + 36; + + if (index < this.machineSlots) { + // Machine -> player inventory. + if (!this.moveItemStackTo(raw, playerStart, playerEnd, true)) { + return ItemStack.EMPTY; + } + } else { + // Player inventory -> frame slot or module slots (output rejects placement). + if (raw.is(ModItems.FRAME_CASING.get())) { + if (!this.moveItemStackTo(raw, QuarryControllerBlockEntity.FRAME_SLOT, + QuarryControllerBlockEntity.FRAME_SLOT + 1, false)) { + return ItemStack.EMPTY; + } + } else if (UpgradeModuleItem.isModule(raw)) { + if (!this.moveItemStackTo(raw, 1, 1 + this.moduleSlots, false)) { + return ItemStack.EMPTY; + } + } else { + return ItemStack.EMPTY; + } + } + + if (raw.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + if (raw.getCount() == moved.getCount()) { + return ItemStack.EMPTY; + } + slot.onTake(player, raw); + return moved; + } + + // --- Screen helpers --------------------------------------------------------- + + public int getEnergy() { + return this.data.get(0); + } + + public int getMaxEnergy() { + return this.data.get(1); + } + + public QuarryControllerBlockEntity.State getState() { + return QuarryControllerBlockEntity.State.values()[ + Math.floorMod(this.data.get(2), QuarryControllerBlockEntity.State.values().length)]; + } + + public int getFluid() { + return this.data.get(3); + } + + public int getMaxFluid() { + return this.data.get(4); + } + + public int getCurrentY() { + return this.data.get(5); + } + + public int getRefY() { + return this.data.get(6); + } + + // --- Slot kinds ------------------------------------------------------------- + + private static final class FrameSlot extends Slot { + FrameSlot(Container container, int slot, int x, int y) { + super(container, slot, x, y); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return stack.is(ModItems.FRAME_CASING.get()); + } + } + + private static final class ModuleSlot extends Slot { + ModuleSlot(Container container, int slot, int x, int y) { + super(container, slot, x, y); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return UpgradeModuleItem.isModule(stack); + } + } + + private static final class OutputSlot extends Slot { + OutputSlot(Container container, int slot, int x, int y) { + super(container, slot, x, y); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryRegion.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryRegion.java new file mode 100644 index 0000000..5dc287c --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryRegion.java @@ -0,0 +1,210 @@ +package za.co.neroland.nerospace.machine.quarry; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; + +import org.jetbrains.annotations.Nullable; + +/** + * The rectangular footprint a quarry mines, derived from its landmarks (MINER_DESIGN — 3 landmarks + * forming an L). Landmarks "project" along the four horizontal axes (BuildCraft-style), so two + * landmarks are linked when they share a row or column at the same Y within range; a flood-fill over + * those links collects the cluster and its X/Z bounding box becomes the mined rectangle. The + * reference plane {@link #refY()} is the landmarks' Y; mining runs from {@code refY - 1} down to the + * world floor. + * + *

Immutable; persisted in the controller's NBT via {@link #save}/{@link #load}.

+ */ +public final class QuarryRegion { + + private final int minX; + private final int minZ; + private final int maxX; + private final int maxZ; + private final int refY; + + public QuarryRegion(int minX, int minZ, int maxX, int maxZ, int refY) { + this.minX = Math.min(minX, maxX); + this.minZ = Math.min(minZ, maxZ); + this.maxX = Math.max(minX, maxX); + this.maxZ = Math.max(minZ, maxZ); + this.refY = refY; + } + + public int minX() { + return this.minX; + } + + public int minZ() { + return this.minZ; + } + + public int maxX() { + return this.maxX; + } + + public int maxZ() { + return this.maxZ; + } + + public int refY() { + return this.refY; + } + + public int width() { + return this.maxX - this.minX + 1; + } + + public int length() { + return this.maxZ - this.minZ + 1; + } + + /** Number of columns (interior cells) the quarry sweeps per layer. */ + public int columns() { + return width() * length(); + } + + public boolean containsColumn(int x, int z) { + return x >= this.minX && x <= this.maxX && z >= this.minZ && z <= this.maxZ; + } + + /** Whether {@code (x,z)} sits on the rectangle's perimeter (where the frame ring is built). */ + public boolean isPerimeter(int x, int z) { + return x == this.minX || x == this.maxX || z == this.minZ || z == this.maxZ; + } + + /** The perimeter cells (at {@link #refY}) the frame ring occupies. */ + public List framePositions() { + List out = new ArrayList<>(); + for (int x = this.minX; x <= this.maxX; x++) { + for (int z = this.minZ; z <= this.maxZ; z++) { + if (isPerimeter(x, z)) { + out.add(new BlockPos(x, this.refY, z)); + } + } + } + return out; + } + + /** The mining target at linear column {@code index} (row-major) and layer {@code y}. */ + public BlockPos columnPos(int index, int y) { + int w = width(); + int dx = index % w; + int dz = index / w; + return new BlockPos(this.minX + dx, y, this.minZ + dz); + } + + // --- Discovery from landmarks ------------------------------------------------ + + /** Maximum landmarks gathered by a single scan (a sane safety cap). */ + private static final int MAX_LANDMARKS = 16; + + /** + * Build a region from the landmark cluster reachable from {@code seed}, validated against + * {@code maxSide}. Returns {@code null} if the cluster is degenerate (a single landmark) or the + * span exceeds the tier's area cap. + */ + @Nullable + public static QuarryRegion fromLandmarks(Level level, BlockPos seed, int maxSide) { + if (!isLandmark(level, seed)) { + return null; + } + Set cluster = new HashSet<>(); + Deque queue = new ArrayDeque<>(); + cluster.add(seed.immutable()); + queue.add(seed.immutable()); + int refY = seed.getY(); + + while (!queue.isEmpty() && cluster.size() < MAX_LANDMARKS) { + BlockPos pos = queue.poll(); + for (Direction dir : Direction.Plane.HORIZONTAL) { + BlockPos linked = projectToLandmark(level, pos, dir, maxSide); + if (linked != null && cluster.add(linked)) { + queue.add(linked); + } + } + } + + int minX = Integer.MAX_VALUE; + int minZ = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxZ = Integer.MIN_VALUE; + for (BlockPos pos : cluster) { + minX = Math.min(minX, pos.getX()); + minZ = Math.min(minZ, pos.getZ()); + maxX = Math.max(maxX, pos.getX()); + maxZ = Math.max(maxZ, pos.getZ()); + } + + int w = maxX - minX + 1; + int l = maxZ - minZ + 1; + if (cluster.size() < 2 || w < 2 || l < 2 || w > maxSide || l > maxSide) { + return null; + } + return new QuarryRegion(minX, minZ, maxX, maxZ, refY); + } + + /** + * Find a landmark to seed a scan from, near {@code origin} (the controller): the nearest landmark + * along any horizontal axis within {@code range}, or {@code null}. + */ + @Nullable + public static BlockPos findNearbyLandmark(Level level, BlockPos origin, int range) { + for (Direction dir : Direction.Plane.HORIZONTAL) { + BlockPos found = projectToLandmark(level, origin, dir, range); + if (found != null) { + return found; + } + } + return null; + } + + /** Scan along {@code dir} up to {@code range} for the next landmark sharing this row/column. */ + @Nullable + private static BlockPos projectToLandmark(Level level, BlockPos from, Direction dir, int range) { + BlockPos.MutableBlockPos cursor = from.mutable(); + for (int step = 1; step <= range; step++) { + cursor.move(dir); + if (isLandmark(level, cursor)) { + return cursor.immutable(); + } + } + return null; + } + + private static boolean isLandmark(Level level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + return state.getBlock() instanceof QuarryLandmarkBlock; + } + + // --- Persistence ------------------------------------------------------------- + + public void save(ValueOutput output) { + output.putInt("MinX", this.minX); + output.putInt("MinZ", this.minZ); + output.putInt("MaxX", this.maxX); + output.putInt("MaxZ", this.maxZ); + output.putInt("RefY", this.refY); + } + + /** Reads a region from a child written by {@link #save}; the caller gates on whether one exists. */ + public static QuarryRegion load(ValueInput input) { + return new QuarryRegion( + input.getIntOr("MinX", 0), + input.getIntOr("MinZ", 0), + input.getIntOr("MaxX", 0), + input.getIntOr("MaxZ", 0), + input.getIntOr("RefY", 0)); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/module/MachineModules.java b/src/main/java/za/co/neroland/nerospace/module/MachineModules.java new file mode 100644 index 0000000..e7a2ff4 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/module/MachineModules.java @@ -0,0 +1,95 @@ +package za.co.neroland.nerospace.module; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.item.ItemResource; + +import za.co.neroland.nerospace.machine.MachineItemHandler; + +/** + * A reusable bank of upgrade-module slots that any machine can embed (MINER_DESIGN — the quarry is + * the first consumer). It owns a {@link MachineItemHandler} validated to {@link UpgradeModuleItem}s + * (so only module cards can be inserted, by hand or by pipe) and exposes the aggregated effects the + * host machine reads each tick. Effects scale with the number of matching modules across the slots, + * each clamped so a stack of cards can't trivialise balance. + */ +public final class MachineModules { + + /** Each Speed module raises the work cap by this fraction. */ + private static final double SPEED_PER_MODULE = 0.5D; + /** Each Efficiency module cuts energy cost by this fraction (floored — see {@link #energyMultiplier}). */ + private static final double EFFICIENCY_PER_MODULE = 0.15D; + /** Energy cost never drops below this fraction of the base, however many Efficiency modules. */ + private static final double MIN_ENERGY_FRACTION = 0.25D; + /** Fortune is capped at this level regardless of how many Fortune modules are present. */ + private static final int MAX_FORTUNE = 3; + /** Speed multiplier ceiling. */ + private static final double MAX_SPEED = 8.0D; + + private final MachineItemHandler handler; + + public MachineModules(int slots, Runnable onChange) { + this.handler = new MachineItemHandler(slots, onChange, + (index, resource) -> UpgradeModuleItem.isModule(resource.toStack(1))); + } + + /** The capability/GUI/Container-facing handler (the single source of truth for the slots). */ + public ResourceHandler handler() { + return this.handler; + } + + /** Live accessor used by menus/containers. */ + public MachineItemHandler store() { + return this.handler; + } + + public int slots() { + return this.handler.size(); + } + + private int count(ModuleType type) { + int total = 0; + for (int i = 0; i < this.handler.size(); i++) { + ItemStack stack = this.handler.getStack(i); + if (!stack.isEmpty() && UpgradeModuleItem.typeOf(stack) == type) { + total += stack.getCount(); + } + } + return total; + } + + /** Work-cap multiplier (>= 1): more Speed modules = a higher per-cycle ceiling. */ + public double speedMultiplier() { + return Math.min(MAX_SPEED, 1.0D + count(ModuleType.SPEED) * SPEED_PER_MODULE); + } + + /** Energy-cost multiplier (<= 1): more Efficiency modules = cheaper work, floored. */ + public double energyMultiplier() { + return Math.max(MIN_ENERGY_FRACTION, 1.0D - count(ModuleType.EFFICIENCY) * EFFICIENCY_PER_MODULE); + } + + /** Effective Fortune level applied to harvested blocks (0 = none), ignored when {@link #silkTouch()}. */ + public int fortuneLevel() { + return Math.min(MAX_FORTUNE, count(ModuleType.FORTUNE)); + } + + public boolean silkTouch() { + return count(ModuleType.SILK_TOUCH) > 0; + } + + // --- Persistence (host machine calls these from save/loadAdditional) -------- + + public void save(ValueOutput output) { + for (int i = 0; i < this.handler.size(); i++) { + output.store("Module" + i, ItemStack.OPTIONAL_CODEC, this.handler.getStack(i)); + } + } + + public void load(ValueInput input) { + for (int i = 0; i < this.handler.size(); i++) { + this.handler.setStack(i, input.read("Module" + i, ItemStack.OPTIONAL_CODEC).orElse(ItemStack.EMPTY)); + } + } +} diff --git a/src/main/java/za/co/neroland/nerospace/module/ModuleType.java b/src/main/java/za/co/neroland/nerospace/module/ModuleType.java new file mode 100644 index 0000000..2552241 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/module/ModuleType.java @@ -0,0 +1,30 @@ +package za.co.neroland.nerospace.module; + +/** + * The kinds of cross-machine upgrade module (MINER_DESIGN — the quarry's first consumer, but the + * system is deliberately machine-agnostic: any machine can embed a {@link MachineModules} and read + * the same effects). Each module is its own registered item ({@link UpgradeModuleItem}); a machine + * counts the modules in its slots and aggregates their effects. + * + *

Planet-survival modules (heat-shield / cryo / vacuum) are intentionally NOT here yet — kept in + * mind for a later pass (see MINER_DESIGN). Adding one is a new enum constant plus its item.

+ */ +public enum ModuleType { + + /** Raises the per-cycle work cap so the machine does more when fed more power. */ + SPEED, + /** Lowers the energy cost per unit of work. */ + EFFICIENCY, + /** Applies a Fortune level to harvested blocks (mutually overridden by {@link #SILK_TOUCH}). */ + FORTUNE, + /** Harvests blocks with Silk Touch (takes precedence over {@link #FORTUNE}). */ + SILK_TOUCH; + + public static ModuleType byOrdinal(int ordinal) { + ModuleType[] values = values(); + if (ordinal < 0 || ordinal >= values.length) { + return SPEED; + } + return values[ordinal]; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/module/UpgradeModuleItem.java b/src/main/java/za/co/neroland/nerospace/module/UpgradeModuleItem.java new file mode 100644 index 0000000..e8acdb8 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/module/UpgradeModuleItem.java @@ -0,0 +1,35 @@ +package za.co.neroland.nerospace.module; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +import org.jetbrains.annotations.Nullable; + +/** + * A machine upgrade module card. One {@link UpgradeModuleItem} class backs all module types; each + * registered item fixes its {@link ModuleType} so the card is identified purely by its item (no data + * component needed) and is portable across every machine that embeds a {@link MachineModules}. + */ +public class UpgradeModuleItem extends Item { + + private final ModuleType type; + + public UpgradeModuleItem(Properties properties, ModuleType type) { + super(properties); + this.type = type; + } + + public ModuleType moduleType() { + return this.type; + } + + /** @return the module type of {@code stack}, or {@code null} if it is not a module card. */ + @Nullable + public static ModuleType typeOf(ItemStack stack) { + return stack.getItem() instanceof UpgradeModuleItem module ? module.moduleType() : null; + } + + public static boolean isModule(ItemStack stack) { + return stack.getItem() instanceof UpgradeModuleItem; + } +} diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java b/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java index 4f9eae3..03622d3 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModBlockEntities.java @@ -87,6 +87,21 @@ public final class ModBlockEntities { false, ModBlocks.TERRAFORM_MONITOR.get())); + // Quarry / Miner (MINER_DESIGN). + public static final Supplier> QUARRY_CONTROLLER = + BLOCK_ENTITY_TYPES.register("quarry_controller", + () -> new BlockEntityType<>( + za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity::new, + false, + ModBlocks.QUARRY_CONTROLLER.get())); + + public static final Supplier> QUARRY_LANDMARK = + BLOCK_ENTITY_TYPES.register("quarry_landmark", + () -> new BlockEntityType<>( + za.co.neroland.nerospace.machine.quarry.QuarryLandmarkBlockEntity::new, + false, + ModBlocks.QUARRY_LANDMARK.get())); + public static final Supplier> UNIVERSAL_PIPE = BLOCK_ENTITY_TYPES.register( "universal_pipe", () -> new BlockEntityType<>( @@ -137,6 +152,11 @@ public final class ModBlockEntities { BLOCK_ENTITY_TYPES.register("creative_item_store", () -> new BlockEntityType<>(CreativeItemStoreBlockEntity::new, false, ModBlocks.CREATIVE_ITEM_STORE.get())); + public static final Supplier> TRASH_CAN = + BLOCK_ENTITY_TYPES.register("trash_can", + () -> new BlockEntityType<>( + za.co.neroland.nerospace.storage.TrashCanBlockEntity::new, false, ModBlocks.TRASH_CAN.get())); + // Station Core (MULTI_STATION_DESIGN.md). public static final Supplier> STATION_CORE = BLOCK_ENTITY_TYPES.register("station_core", diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java b/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java index d70cf25..a50553b 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModBlocks.java @@ -311,6 +311,49 @@ public final class ModBlocks { .sound(SoundType.METAL) .noOcclusion()); // pedestal-screen model (art overhaul §3) + // --- Quarry / Miner (MINER_DESIGN) -------------------------------------- + + /** + * Quarry Controller (Tier 1): the brain of the miner. Marks out a region with landmarks, builds a + * frame and excavates it layer-by-layer. Backed by + * {@link za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity}. + */ + public static final DeferredBlock QUARRY_CONTROLLER = + BLOCKS.registerBlock("quarry_controller", + za.co.neroland.nerospace.machine.quarry.QuarryControllerBlock::new, + props -> props + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .requiresCorrectToolForDrops() + .sound(SoundType.METAL) + .noOcclusion()); + + /** Quarry Landmark: a corner marker; three forming an L define the region to mine. */ + public static final DeferredBlock QUARRY_LANDMARK = + BLOCKS.registerBlock("quarry_landmark", + za.co.neroland.nerospace.machine.quarry.QuarryLandmarkBlock::new, + props -> props + .mapColor(MapColor.COLOR_PURPLE) + .strength(0.8F, 1.0F) + .sound(SoundType.METAL) + .noOcclusion()); + + /** + * Quarry Frame: the glowing structural ring the controller materialises around its region. + * Machine-placed only (no block item), drops nothing ({@code noLootTable}); broken when the + * controller is removed. + */ + public static final DeferredBlock QUARRY_FRAME = + BLOCKS.registerBlock("quarry_frame", + za.co.neroland.nerospace.machine.quarry.QuarryFrameBlock::new, + props -> props + .mapColor(MapColor.COLOR_CYAN) + .strength(1.0F, 1.0F) + .sound(SoundType.METAL) + .lightLevel(state -> 10) + .noOcclusion() + .noLootTable()); + // --- Power grid (Universal Pipe + generators) --------------------------- /** The Universal Pipe: a connection-aware transmitter that moves energy (and later items/fluids/gas). */ @@ -390,6 +433,12 @@ public final class ModBlocks { props -> props.mapColor(MapColor.COLOR_PINK).strength(-1.0F, 3_600_000.0F) .sound(SoundType.METAL)); + /** Trash Can: voids any item / fluid / gas piped into it. */ + public static final DeferredBlock TRASH_CAN = + BLOCKS.registerBlock("trash_can", za.co.neroland.nerospace.storage.TrashCanBlock::new, + props -> props.mapColor(MapColor.COLOR_GRAY).strength(2.0F, 6.0F) + .requiresCorrectToolForDrops().sound(SoundType.METAL).noOcclusion()); + // --- Star Guide (progression block, 1.0) --------------------------------- /** diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModCapabilities.java b/src/main/java/za/co/neroland/nerospace/registry/ModCapabilities.java index 9d43467..0892924 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModCapabilities.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModCapabilities.java @@ -175,6 +175,38 @@ public static void registerCapabilities(RegisterCapabilitiesEvent event) { ModBlockEntities.OXYGEN_GENERATOR.get(), (blockEntity, side) -> blockEntity.getGasHandler()); + // Quarry Controller (MINER_DESIGN): grid power in. ONLY the mined OUTPUTS are exposed for + // export — the item-output buffer (extract-only; it rejects insertion) on every side, and the + // sucked-up fluid buffer. The configuration slots (frame casing + upgrade modules) are NOT + // exposed to automation, so pipes/hoppers can never pull them out. + event.registerBlockEntity( + Capabilities.Energy.BLOCK, + ModBlockEntities.QUARRY_CONTROLLER.get(), + (blockEntity, side) -> blockEntity.getEnergyHandler()); + event.registerBlockEntity( + Capabilities.Item.BLOCK, + ModBlockEntities.QUARRY_CONTROLLER.get(), + (blockEntity, side) -> blockEntity.getOutputHandler()); + event.registerBlockEntity( + Capabilities.Fluid.BLOCK, + ModBlockEntities.QUARRY_CONTROLLER.get(), + (blockEntity, side) -> blockEntity.getFluidHandler()); + + // Trash Can: a void sink on every layer — accepts items, fluids and gas from any side and + // discards them (no extraction surface, so nothing comes back out). + event.registerBlockEntity( + Capabilities.Item.BLOCK, + ModBlockEntities.TRASH_CAN.get(), + (blockEntity, side) -> blockEntity.getItemHandler()); + event.registerBlockEntity( + Capabilities.Fluid.BLOCK, + ModBlockEntities.TRASH_CAN.get(), + (blockEntity, side) -> blockEntity.getFluidHandler()); + event.registerBlockEntity( + za.co.neroland.nerospace.gas.GasCapability.BLOCK, + ModBlockEntities.TRASH_CAN.get(), + (blockEntity, side) -> blockEntity.getGasHandler()); + // Nerosium Grinder — sided: insert into the input from the top/sides, extract output below. event.registerBlockEntity( Capabilities.Item.BLOCK, diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java b/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java index 52f8c3e..7a92c8b 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModCreativeModeTabs.java @@ -72,6 +72,16 @@ public final class ModCreativeModeTabs { output.accept(ModBlocks.COMBUSTION_GENERATOR.get()); output.accept(ModBlocks.PASSIVE_GENERATOR.get()); output.accept(ModBlocks.UNIVERSAL_PIPE.get()); + + // Quarry / Miner (MINER_DESIGN). + output.accept(ModBlocks.QUARRY_CONTROLLER.get()); + output.accept(ModBlocks.QUARRY_LANDMARK.get()); + output.accept(ModItems.FRAME_CASING.get()); + output.accept(ModItems.SPEED_MODULE.get()); + output.accept(ModItems.EFFICIENCY_MODULE.get()); + output.accept(ModItems.FORTUNE_MODULE.get()); + output.accept(ModItems.SILK_TOUCH_MODULE.get()); + output.accept(ModItems.CONFIGURATOR.get()); output.accept(ModItems.PIPE_FILTER.get()); output.accept(ModItems.SPEED_UPGRADE.get()); @@ -86,6 +96,7 @@ public final class ModCreativeModeTabs { output.accept(ModBlocks.CREATIVE_FLUID_TANK.get()); output.accept(ModBlocks.CREATIVE_GAS_TANK.get()); output.accept(ModBlocks.CREATIVE_ITEM_STORE.get()); + output.accept(ModBlocks.TRASH_CAN.get()); output.accept(ModItems.OXYGEN_SUIT_HELMET.get()); output.accept(ModItems.OXYGEN_SUIT_CHESTPLATE.get()); output.accept(ModItems.OXYGEN_SUIT_LEGGINGS.get()); diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModItems.java b/src/main/java/za/co/neroland/nerospace/registry/ModItems.java index b2f349a..cb7bbf8 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModItems.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModItems.java @@ -334,6 +334,12 @@ public final class ModItems { public static final DeferredItem TERRAFORM_MONITOR_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.TERRAFORM_MONITOR); + // Quarry / Miner block items (the frame has no item — machine-placed only). + public static final DeferredItem QUARRY_CONTROLLER_ITEM = + ITEMS.registerSimpleBlockItem(ModBlocks.QUARRY_CONTROLLER); + public static final DeferredItem QUARRY_LANDMARK_ITEM = + ITEMS.registerSimpleBlockItem(ModBlocks.QUARRY_LANDMARK); + // Power grid block items. public static final DeferredItem UNIVERSAL_PIPE_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.UNIVERSAL_PIPE); @@ -359,6 +365,27 @@ public final class ModItems { ITEMS.registerSimpleBlockItem(ModBlocks.ITEM_STORE); public static final DeferredItem CREATIVE_ITEM_STORE_ITEM = ITEMS.registerSimpleBlockItem(ModBlocks.CREATIVE_ITEM_STORE); + public static final DeferredItem TRASH_CAN_ITEM = + ITEMS.registerSimpleBlockItem(ModBlocks.TRASH_CAN); + + // --- Quarry / Miner (MINER_DESIGN) -------------------------------------- + + /** Frame Casing: consumed by the quarry to materialise its frame ring (one per open cell). */ + public static final DeferredItem FRAME_CASING = ITEMS.registerSimpleItem("frame_casing"); + + // Cross-machine upgrade module cards (module package). Each fixes its ModuleType. + public static final DeferredItem SPEED_MODULE = ITEMS.registerItem("speed_module", + props -> new za.co.neroland.nerospace.module.UpgradeModuleItem(props, + za.co.neroland.nerospace.module.ModuleType.SPEED)); + public static final DeferredItem EFFICIENCY_MODULE = ITEMS.registerItem("efficiency_module", + props -> new za.co.neroland.nerospace.module.UpgradeModuleItem(props, + za.co.neroland.nerospace.module.ModuleType.EFFICIENCY)); + public static final DeferredItem FORTUNE_MODULE = ITEMS.registerItem("fortune_module", + props -> new za.co.neroland.nerospace.module.UpgradeModuleItem(props, + za.co.neroland.nerospace.module.ModuleType.FORTUNE)); + public static final DeferredItem SILK_TOUCH_MODULE = ITEMS.registerItem("silk_touch_module", + props -> new za.co.neroland.nerospace.module.UpgradeModuleItem(props, + za.co.neroland.nerospace.module.ModuleType.SILK_TOUCH)); // The Configurator — the network tool (per-face I/O modes). public static final DeferredItem CONFIGURATOR = ITEMS.registerItem( diff --git a/src/main/java/za/co/neroland/nerospace/registry/ModMenuTypes.java b/src/main/java/za/co/neroland/nerospace/registry/ModMenuTypes.java index 28213a6..07921aa 100644 --- a/src/main/java/za/co/neroland/nerospace/registry/ModMenuTypes.java +++ b/src/main/java/za/co/neroland/nerospace/registry/ModMenuTypes.java @@ -59,6 +59,12 @@ public final class ModMenuTypes { "terraform_monitor", () -> new MenuType<>(TerraformMonitorMenu::new, FeatureFlags.DEFAULT_FLAGS)); + // Quarry / Miner (MINER_DESIGN). + public static final Supplier> QUARRY_CONTROLLER = + MENU_TYPES.register("quarry_controller", + () -> new MenuType<>(za.co.neroland.nerospace.machine.quarry.QuarryMenu::new, + FeatureFlags.DEFAULT_FLAGS)); + public static final Supplier> COMBUSTION_GENERATOR = MENU_TYPES.register( "combustion_generator", () -> new MenuType<>(CombustionGeneratorMenu::new, FeatureFlags.DEFAULT_FLAGS)); diff --git a/src/main/java/za/co/neroland/nerospace/storage/FluidTankBlock.java b/src/main/java/za/co/neroland/nerospace/storage/FluidTankBlock.java index 76b0f17..6e53669 100644 --- a/src/main/java/za/co/neroland/nerospace/storage/FluidTankBlock.java +++ b/src/main/java/za/co/neroland/nerospace/storage/FluidTankBlock.java @@ -13,6 +13,8 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.fluid.FluidResource; import net.neoforged.neoforge.transfer.fluid.FluidUtil; /** Fluid Tank block — bucket in/out via right-click with a (filled/empty) bucket; bare hand reads out. */ @@ -34,11 +36,19 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new FluidTankBlockEntity(pos, state); } + // The whole FluidUtil.interactWithFluidHandler family is deprecated-for-removal; the replacement + // is hand-composing tryPlaceFluid/tryPickupFluid (item swap + sounds). Until that helper is + // rebuilt, use the handler overload (cleaner than the location/Direction one) and suppress. + @SuppressWarnings("removal") @Override protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (FluidUtil.interactWithFluidHandler(player, hand, level, pos, hit.getDirection())) { - return InteractionResult.SUCCESS; + // Pass the tank's fluid handler directly. + if (level.getBlockEntity(pos) instanceof FluidTankBlockEntity tank) { + ResourceHandler handler = tank.getFluidHandler(); + if (FluidUtil.interactWithFluidHandler(player, hand, pos, handler)) { + return InteractionResult.SUCCESS; + } } return super.useItemOn(stack, state, level, pos, player, hand, hit); } diff --git a/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlock.java b/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlock.java new file mode 100644 index 0000000..f56430f --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlock.java @@ -0,0 +1,52 @@ +package za.co.neroland.nerospace.storage; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import za.co.neroland.nerospace.registry.ModBlockEntities; + +/** + * Trash Can: a void sink for items, fluids and gas. Pipe or hopper anything into it and it is + * destroyed — handy for dumping the cobble/dirt a quarry digs up until item filters arrive. Backed + * by {@link TrashCanBlockEntity}. + */ +public class TrashCanBlock extends BaseEntityBlock { + + public static final MapCodec CODEC = simpleCodec(TrashCanBlock::new); + + public TrashCanBlock(Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new TrashCanBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + if (level.isClientSide()) { + return null; + } + return createTickerHelper(type, ModBlockEntities.TRASH_CAN.get(), + (lvl, pos, st, be) -> be.tick(lvl, pos, st)); + } +} diff --git a/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlockEntity.java b/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlockEntity.java new file mode 100644 index 0000000..3f18836 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/storage/TrashCanBlockEntity.java @@ -0,0 +1,84 @@ +package za.co.neroland.nerospace.storage; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.fluid.FluidStacksResourceHandler; +import net.neoforged.neoforge.transfer.item.ItemResource; + +import za.co.neroland.nerospace.gas.GasResource; +import za.co.neroland.nerospace.gas.GasStacksResourceHandler; +import za.co.neroland.nerospace.machine.MachineItemHandler; +import za.co.neroland.nerospace.registry.ModBlockEntities; + +/** + * Trash Can: a bottomless sink for every transferable layer. It exposes item, fluid and gas + * capabilities that accept anything inserted (by hopper or pipe) and then void it. Each layer + * is a high-capacity buffer that is emptied every server tick, so it always has room and never spills + * anything back out — there is no extraction surface, so nothing can be pulled out of it. + */ +public class TrashCanBlockEntity extends BlockEntity { + + private static final int ITEM_SLOTS = 9; + /** Per-tick headroom for fluids/gas (mB) — far above any pipe's throughput. */ + private static final int VOID_CAPACITY = 1_000_000; + + private final MachineItemHandler items = new MachineItemHandler(ITEM_SLOTS, () -> { }); + private final VoidFluid fluid = new VoidFluid(); + private final VoidGas gas = new VoidGas(); + + public TrashCanBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.TRASH_CAN.get(), pos, state); + } + + public ResourceHandler getItemHandler() { + return this.items; + } + + public ResourceHandler getFluidHandler() { + return this.fluid; + } + + public ResourceHandler getGasHandler() { + return this.gas; + } + + /** Empty every sink each tick so it always accepts more and stores nothing. */ + public void tick(Level level, BlockPos pos, BlockState state) { + if (level.isClientSide()) { + return; + } + if (!this.items.isStoreEmpty()) { + this.items.clearStore(); + } + this.fluid.clear(); + this.gas.clear(); + } + + private static final class VoidFluid extends FluidStacksResourceHandler { + private VoidFluid() { + super(1, VOID_CAPACITY); + } + + void clear() { + if (getAmountAsInt(0) > 0) { + set(0, FluidResource.EMPTY, 0); + } + } + } + + private static final class VoidGas extends GasStacksResourceHandler { + private VoidGas() { + super(1, VOID_CAPACITY); + } + + void clear() { + if (getAmountAsInt(0) > 0) { + set(0, GasResource.EMPTY, 0); + } + } + } +} diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_controller.png b/src/main/resources/assets/nerospace/textures/block/quarry_controller.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c05b187b164617852f0f7f8f1a1ea63b9ef813 GIT binary patch literal 463 zcmV;=0WkiFP)4z!7=|AkLyHkYl*4E^jt(Lm9gmW!Q|HbdI+P6k0i8<*Pnk1^3?_6c9ZZM(f=s4( z@MMaHa@-(}BOHhDRB#4YN)OXGkarQ1KIwbkM;grLt1VB^2;ky;icSr7y=0;@YFA?N zg05|-UCBgeNR`mF4N@hz%Hn;;ZQ~dM=+ux-&oFtx#K+L%{5jh7}vzL?Bx+sC2>9k z=-T^&$0)>$7f6+$Q-jHigNMgLQM)oS7y&){hqUo6LP)&t?k@<`Cpu#>`qMk`?Bfi3 zbrR?AghHw$0^f6Bnm!MEb<&6U`~^}an7m+dbG>5_@`Ujr1?vI)Ff2-y#Lb5kK!>M{ z{~$03F1+tBdBNMta{T6)5cS{h934z_#;fy-*kvpfLP)x{L8m4*5JFPBl5~0&-40>* zG(SJR@5LDZrR^$gu&C*&t;ucLK6V+urlcY literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_landmark.png b/src/main/resources/assets/nerospace/textures/block/quarry_landmark.png new file mode 100644 index 0000000000000000000000000000000000000000..617692e0f732a7d5e89b9d58862412410d6e239e GIT binary patch literal 371 zcmV-(0gV2MP)9XkX21p#ipW}oFV~1 zW^b1kLc*A4xA}k0o;+^fKKRG10B)~q%5?>R>V!y&>cs2Q7J&1si|8I;bOQjVAzK8# ze1M&Uo#SP5&*%nJCq_3Ql9J`c(Sd=DRV3x+Zcm*E%|WRXAO__o9_$G$mni^0ItU|s0%Nptvb>l%u>YeQyhv1H z$U3@V=D^&A_D45-Ik0>-<+@tzAQXxsgk$Sja$s8u`|u@Nk>$EVbz1DeVuWwTmuT$2 zbzm+`$P-u|WByhAC+_QS4LrW|Jcez$bC`2@*x@XY<@ RQ^f!P002ovPDHLkV1m(Aq?Z5y literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/block/trash_can.png b/src/main/resources/assets/nerospace/textures/block/trash_can.png new file mode 100644 index 0000000000000000000000000000000000000000..d29be5f97be52494255080294f74b331f8834b59 GIT binary patch literal 455 zcmV;&0XY7NP)0vLyNR?sG8V22`NUm5uuqaP>we2X@z z)b`pm+alV`iPGAn!XWAspPcdZ+oH2)D7-ZJd@H%WGRzm}0wN54eqRD`lv?V#656Bj xR8*gSnMrNDS^|*YZTR~=?ZcbcX1#h7djS9>y8f-eh&%uQ002ovPDHLkV1gf6%uWCR literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/gui/quarry.png b/src/main/resources/assets/nerospace/textures/gui/quarry.png new file mode 100644 index 0000000000000000000000000000000000000000..28b22b326657f584d5ff3a27a23fc4e4cb4c4a42 GIT binary patch literal 10877 zcmd6thcjH!`~Qg^geB3UL>Dc3i*6CJdM^Q=R{8pTKfmAn{)OMnojG^z+_`to`#k4)zvg*PvZ1~<^<9>`1Ox=sI?sT{1O$Y) zw}b@bq_=~uUyTa^fgn%^sAd{cbWl9?me!!zPf-EG0z+qBwzDw1D|?&6wX=4@;-OB9Mv_+&?7e;x3fQe^dAU#($bqSy z$P@X!i$cW2-Z5)2BRU`boc$(o;bnX?jRhc|B#p$9K0+QrR!*bHIC*fOXJf0=c^DaK zL!*NK+|!f1@;>;CMs}s|%Wv$9^uF+uFU#h91IoDI^mhw>@Ae{^L?rSQpCE_jM-E@* zoagrm0nA9n&AJ8Oz^hMyC}sL|%<7akV>#&0b&Ao; zKHU!{1nPTnTOv|LdQh@ElH(J>w~<&+YrpSLDAY$vSCE!Gydh#RO7IIg&KhNE{QQv` z&bo~o)i~fQfGNNKUMio0K4!x=J&mpeJ9}9;$=NIrvlC&=UHJPY7ChrYipZpc;wpb$ z&Ixl7VU=nQ`lRy_z9Gyr$zohuI=NWQA9b0g(*9}!M?9v-aL6P{?$sx@+}4RQSvOus z{@q`5ks*yz!BC~X^=>G`m&p&h_;^BxqdJN*&x)&Vxxd5SA12ixwxQHhWkCb3RT$%h z#&I6x#F0?8p}%)HJ1qzvQDZ1pl2Dfvrd=p1rd!fV?S?etW*i(P( zcsE5l-X)JtINgp~Z-ZZLSU)1Nn0Ut4U4-n0xuqH6r(J8#+!Wa?07BYk{12F8mOrsn z&3#pW7-&qmgx%nsN}M9ma>)T-CEai#I~KFlJK=Y{0fyEjPi0Mu=mNJEFk<7r1GP&;`MbyDMpyNhE}j`uQ6;Z=2Sby^-V{!+K}ds(kU z`=E`~IkxC&x*5LqtFrLE&R1TC?QSeTb1@VQiEO56*W*)(XRvwoYY-H|u6OqZ<=eN0 zwkJM5CxyMmW6*hNhkXq<0>J9lFRq^&C9Klf^(%7imj+S~%_<_=3b&Ghr1O;MdGp17 zmfuiTuU8;XQPMT?i3%4yWsCsM9nTv1fXgs%yu z>LpGi6?m7GIe%W0E_I=2S_va2Jk*rVi?{C>ZyJMs7&2qN*hrO5SF0z;RSfljdMFuW z%xM+PgGBEgJm(E<{{V)ZZbbC1PI8y>K)J{i6Nf>GiyKc3_2A+?1MTLB8sgETZSmYz z=`~rUss`LUe;Ik*lSseR+c>SZt1}MsEwU>RVb0Q`%Zc$(v^jH8WXmO+X8%+`w96#? zG|F&c-a%{75apMmiuznRj?n9|@oBSEg2n0n3G!j< z6sEt0Z2Nk*E0xgTN8S05JC5Z8jipmntl^)$3y#kr}6zbb3`t8!Xs*05eY+dgD+bHf#! zs(Z!YN0UbQ11+2inlk(4@}B;~i)Ff4FL%vl2bc{@N`nyZway?e@=NKF{%KF=9094c zwT6vNilS$W&Vhx?z!bwVU2XN70t{#KX6J)lH-oM)3FpOpcx(1>F;4-!GQ~{{tQdM7 zpu)pWhra*iJr~ow;H`TJMJPkw3J{t+HyYHdua%J~KB}ZIeg6`g@&r)#-m{ zP_42ka!kKs6yzOHUi-ZXhU@;69jF$kHeDpp=99!SjZRMrLers#?pRbCFaJCUA1=)w zd04?s!HqRxl(;eADw3k3E7Fn!zuh^{$(ak?4L8xkWfs@_YdV@ecrl?YO__7_hZRBr zcL3WU=9&u#OmFzB$2S-a$rdWE>xkdWeO_LMWE;r%mE=2RY?tXScM1B+nE^$i7=5 z27mG18?zR9GgPjSF+(ZF6L>$%S2OzP8QAyey=qIji;<`My?%Q^y&2YP7$(`WO%df| zy2;NcGP0O>OqipgGw2j`%@`H>Oxw%#p~Ka+QSg&Z`K9)?SV8ia_Ecm%-yc# z&lf5vToRMnL2}cHZJJ|iq?b2?qi8nqyJ#0{+cod)5gA+yK)JQx6@*3aC;tS=1zAWG zLfO>Fa2ZnaV~SEVAopLW@YM56aIQ|x+&dt;T)+niH&A1q$?^#ZgsmdmB;y}0DYJDy zpNs=Sz>^>Vz)1y?yHjC3aS;wqR@K;cAIXV>g2-Te=b5Ed!1s?|#vvJ3!nZY&v!}tT zW4z4jar^$dOyx2TtZ}*|eC>3usRk*Jm)=4G>C`B*s}87;FgkJyOZ>OU$MMX(rCK%C z<#c=IeciGUHj~J&1&J}j2*gL*`vJ0oG|}k=ZH|5T5vU*;i5hD zmvT&qld$e!q@cZlZ~`_jW4q{y#4d0A?M)bwH(GM7LjwztvzkHD0U8OBbp z=Ke#O3;MTeR4M-ZeDdP{N!mx?=~s7YW&ua^$Vg{Td)dA5{pT&j`8<^|IvbxI)Y=}N zQK>S<>P1hHw&6@>4c_E~gA@v4T+lP_Ds7tDD;icIbAfZDCG~$DAk@!~0T-2TV05Jl zl1Ek7i4a~p*SJ-dfXW(b)=?D*Z=6pP-wXw% z&%Q89)LthxZRBOBPB5j%_^Xk9@MT@zousCg!jSENPqC9#09D@^oK91Rzz4Z)U~V$*JMz0KgE_KJkx~WlCCc6 zSMVVDO~AHtGzj#;4M^&`Ka|4JhrhmRjQn@d=^IC>#`Y8tVKSO^eiGfHBRN9{)f%-7 zdMn=mv+R^=W+jkQI5K$OArgJQCqy` zRX+B^-qR&e5d2=6s(pF%gE zk7CX*g?xA~mIJ9?HE_6j_W6sKf8#Z&GV=6C!>c{9LtIs#nYCGzGS-#|a{LM=rNN+G z+Duu`mAMsOH?Q+vyl>7+$TeH|b^A&IC0%;ww&`n-v3ko&XQ!!#PG+sh0q1vpQcXwq zE4#CW_qkW;@r|i&V=+}wLcI5KmRN6*Y}*a4L&UY{^`VV0yTen7QP%wH>&&@~-gt_? zDqz@sQi#@{*G?+SUaIO&{g6Gq6qhm6Qu-w0@VjI)_nf=3@4m-+*QV@q<4a84q$!Vo ze6g!2=WA*WeYpSoi?@#7`i~l&a!4OyHP?4Le9wPeo?BcG9U+mRgMrcoeq*X_-o}|{ zC!I;^oXYU&C`zQOJs>G&*aCzSSSba2+i07Tq$mA*a&vOX&N_um#hI8fmcRw0#tZxu zZ?2=RvD^GJu66Q6(2&-eJ-R$oRPl}Ln$(AHujJcDtU5QRmcB4Lar~x>89vIU`CZ-N zt#78ZaG*#<$1=gfd!5Aoy28{9DUfW#Rw5NBb!U38V35_cYsK%Buc{f14w3x{==`M; zu)H{Oe5q=pUF@3k51@p!%cxUHP%a(ipvA@MJHW35*XN4d|d#*vsol%MPB(Oqs7rEWusi_!XQl z$;61D=}SThf9iO1J5rBeI*An~%%w*AOWaf2lh@aCk;H8$xNN6s+Dg57CnG)r-Zzyu zI3Qhd!cTDA9FYod)REj31>kJ>Gmktcy^$mte-yC_%J=hwwgNw))GIKH>M(T6U6I2u zn_Eruujyu9z%aFH9BGY?K}x-T#`Pn)9HZ7XuitwF62!&!cXH6M1*~{Y>?6Qcj3s4A zWa(-HvE~SzLJrDS>xP0xVkCrziN?9_}TrG4mXyN1-63>^Y}!Y5Kq?HcONQ=Gf~DmnUZcu?`-U&(+a}BAObha865Awm3Xv9Qe8&M^M66<7luF z_gD$F((4h|TM#=+9-{Xg|G=b7{*P!>Hs%R_G)S<=(vR_2vM@(F+G+Z3teEdmNQq(j z<2&~!M3Z%tE&#=HpSgV;i2HI$atLdu)K@-{`cT0ik;{e@5GopMS3T4< zqAr`Mm64A^;bFvz2Q2fKF?kT-uliCu0*`@;$wx;XO3Vh=T=Br`)YxF`R+bK25I zrVCSGeUJTX_GIXSs=6b3?37M1GETKWq|&VPs4#P-OavV-wSM?xd(6ChueWSEaD~e= z^_>)SP~E`XkEUlU2+n6)Rikw-oTzYUD1nG%IhMNBk;737oMqhRu6^Rwomx>-B8E(OAgz&_VU&SbN+iJ{O?ZMRJJTYrD;lnx!-c z0+N%Iw?6-ul?J|Z>8aC!QoO@M|1Nq*`{!hT-ix<6PIOILwC%>J=Xi5?^O02RTUCJj zL=fgY_VfZMmos|)P{W&rp~N&;f-!-Gxn0OD-O%A7)-x(tb*1sqb?V6YZRMe>yp%3( z_3jN57NJ3+bbMB2PO~nr)T?><_@I`J$2fYMv|NOrJ(w!1a9i$^r?=(Pdm=Q^_t^ax zu_<3C*X*}ULPUpxDPB-MhlmOOo$f9bnZhlJt~iBw!woCmk-FC)OLb$#BR!~phQ^-C z`R{gAgwU;jdcX*@X%1f6_z+4kY%WvVP}}ZqC>8hju}8c>k8+0E#INC{G;AWnvQxV9 zuYH?j)qo#vvl$8d*5+ufH)>Jur6{hH4>+fv0n?VO`@LF$rFm)25BIEM&o6BBJCYJ% zXRYF(780}9b)zt+!gf*{^nt;p@<b z?tDBUuJRn-H<2V6VTIDF3m6Thcm7M3bmqAwkUGQ$@c(QUYsH_h_^jE zKpevKWhAMK8pm>?l(_?jei}R>P^PxK{I_~`c(ZbIv6>b5V8%>tVr=Vd#Ogzz9;D`V zK%q@ZAFiX_4M?ZXzHDFaH0@i1d+s#v5v5yHxa9^FLdUsa-Hyky44y0(PA=svs7x%G zqx&MUN~vz$zqpr;F@d%wHYOK;Hu?+^M|*W=NAFdx*EN|8w9JU#Cb^vTMBu7BL(g#8 zEIr8wd;eAf&>DSvA?z$`i3s#XsBF~DG}Cbha(ph}N@EQv69CF6Y@uoGS;AHp*YCk3 z)-brW5JY3|MT?nJ$Q4RE9lfZY%pADLgeWX!PD?jti!)~kHzuUU2piH*E@pJCMmK#f zA^{fxzUVEh7hmmAprQh9lN~F$rm=OLk+-Ar! z-B?t+xxWSG&9`44+!~gNRUISuS24OwxZrjccG)n};6^eI$T~BkuzwbV@V%v%3%J0L+{13?E6dwd za=t1yNovm6F7Lzo8$?h`woM^3Nt$r&(ZHU#eDC97uxzSi8Jv*Xn_H$N-JCAfJM&hm z^)kk{EC$-S#Ls{4@=#(v+Se@Ky6?|kVL9|7S0e%HN54w`)Y~z;4x>)S;8d?z*yXCdWQNP{@KAZ`5_Wtoz^oz}8;)vNrrBkR@eC zSe07W#(x(FKOsx|P|MMjV=Gr%ma^|3G0hFto+gpH!ZGe7nZYQ9@nr_7U&X@kU0`n#LH;>^j+zpiuHLufgyk+PA6OGWxovqU>x$8LlgEhs*)s zN6rabRWmag3Nqpua#aRKUKxt+x3#fW$7mhf{pXA_jIyvH;5;j)HMDLQn_`N+!Zz

7W;)EYq z%}?h|@WNk65J3oR+SwVUCz4soc%9J}ZDCf6dMAuJV`1aInUDDVW3LR2v;HhgtE#%|Ca&q>px#Qm2(a%zdqOT2u&SEupBFt=xmyuDB;TkxSJ!%xwDDO zK*pdtbj?;~DqOr}tUKS75m5`&Owcm{HWq_ui&I!NCY>f~pSekpJdg+%GPAP*Q8|q( z``aQS=femEC~EVx|0a&a7qWKsSg4vt=z?;;ncT^OBYA@8j<^oKK3=#a{ABpRHYDda z{HTT~J5`nKJtU!~O8rj5Ijyuc@>`f~Q4kRT4y#;R_lrpPot=dS@t#Sig?u&~d6Y)yLvW3bQRM09uU@cYJROf8I75RUli}W=?=>1!3^e1`jLag z>tbC;X(2iN+=9POuU=C0H0bC(Q{`1{!&QfkhHdh%l)sF@Rlj*>s6Oo~o|as|3d^=$ zm0~|H260b$-gwIio>&=V*&e=R%aA5(?6Yo=W>0)A%~Y0^Llc+&*l#^Y6jEI~T1^u$ zOLwgBI%X%t6kC*DO%6bZ>k2_+Sc%L)5Ej!zlu0Ah$T4d-XOLz!?DWMf4N#LpuWDwC zlebs#NxGUI!^yGti(42Wov0nrU*icFC*xM1zqcZ9A9S0%^NAnwnKLH&xwM@>o97YJ zHPCe@;Z3`^H=j`&R0$EL=hLYws5r~3|4|`dz=0eI^PJ6M50QJeq=jqMXfZ^h9r<1H zKBj^i27j#H`OYdYxbp?tPKZdzUl=03B^FSmE>G`#ZkRaRBa&i|FZX%T`Q&9!`pr%C z&Djj~)!);Jkjp*q-n6wF++h%n>54;?O@vBSF|ze&ZbkO}*-7)kMySbC17?$UT!H&Z z(M~VBWP+Pw23s8a&Ht^{vhp@lG(9|+P&JLo9=bVydAZ5S_L;2%XIhiclB?mM$nN=6 z4CQreG0NZPC6(C^Q1)&swF^sPy*%?Xkew)2L3*)r{-cnrv=&uv-S37O8ifRcC-siZ zu4*g%e^l>}geVF=kaL_^kx6YCbm(_7G>Q(I{`?)0T&iCCy}B)W6?{S%j9F?@8N3Z3s+!P03BC+IWSCZpu@D6)P3h)tSXZSlR}FjRGsESyy1fwuGe~9ML+v4_WI;2xH~XGr}l6L&wXtn z@{o?rci=YBepnRDkgx- z9Yn36G~p8#I6W#|V}-gvQ|(0{N{&7t>TU9koGH6%m=cqEtZN+=CMYuW0Nf1y!SrhX zmMyr~C5c0-M#a3a`AhePqCL}kuOSvIh>o~TAn zeR`hYOA6*R#MZJVZmvexLPBHHS{_N7J}2@SfDRc_MX!j6T%}2>NfHFQHys7!{BGRI zMc?x%BC)0$ZzO$s?*!8HhX1>qBx{)hgGlWcO(Mk+gih(TK;s0aDTnyENx!(otzPEv z!v?9-vL5DdoBgShzjk-O^gLKJipKan@SQR%_H`}-e8y)45_*ub#?2CVd81Emg|x@; zJiEoE$QSUfGIX`E5smb|{+Gyz*Rc4 z*OsuYnC@7POCk%XNUfmsN2n`u2Z5RIPal0VtmFxwgG{g0lu@B7S~GT{CfSlBW`OdH zXyO35+kc9@4y6?0RpbQ$g@0V#9webOYD?rNo_b;D*^y5>VA>{!M7IB(&>CGdt*qbk z`N+dG#rFFtR*OOuzuwK~|IB4ryd{GD1F6xJh8oYo{T9FFM$iYRS%Ej<`k89rnbo302#sOi`7dh?cIn#=i5QS}J@_(9&dy?@q|# zcci)qorCwjktMN82ROBIlBQ{tBVIo#;#gY zJ>IguxiyMf_|TW)mo889{=x2f-tLuZxC_%u78Bk_PYE;BqLk$3h5TP&JQn?UZgES9 zLd(0OrygOYx|UN%$adQSXbM@cCuHKk^MOdz(w}vAvlQTeoahklW+3oL=scH<`FP9wcxkSpYQOut2mN^$2&(~V;14VCK62HopX{wZ6Cz(r zh!ztVDqw9iz5HuOtkhQmTzt22%E|DAAi(k9mH6bHJnj$j_WRnBg!`7_ljQYOuc%Mt z=2v1m1ka1=6+W(d}$8mf6U_ z>yW6|J(IboL!X^FXv`S47Tb%atj#Quf6f{kUOWilUkk-y@Pq-4tr3jo($StB+~p^gzu5DP9>g8 z+=@z5rnu=*?duP>2oWq{!61TCVp@`AR%h7o_zE;)cAX8fY?e>S7j&BQr;uPs$8kmIwKY7LX3SpnRYWUJ1sx^4HU^>^x34M^25P;{6pF9kBn76-4& zdM4!-w^^)HVLJD)`GX1_eOZdwp++5|=BfyJjGh-Z+$jb*3IQr>Ta0gDxPgd+c_s$4 z%c{PoY817(<5fEWSD#e!Uk$U4#f!Vz$dmRgWSxpt`-Q(gWX1A~$kQxUL3k;N^d#wV zaJ47Usi1L|@{u4Tx0;PTly|H9@ZhP5V+JVln@EMs#9H4C^V zbh=Yki{PJ%s{PAw*|67#p=;SKlK5(;z`7F5edYIvQtM~wxo7eZHj4^!BoZ!7Ho{RX ztXtatrtMPPn4{aiana@1Wa1GcWWtRZWx@O?*T;ctGg9NevQ?4t0@jR)88Szs+1IQP7J!xlZV7bS|-!8S4ZZq0K4n&zS)nse-`rx{nd;=f=u z9UH}$yHr4wn0?Yzki8C7%o*A?yxG>3>?idgHBo!IjXZ5TrOj5{-(dLAz7YN=)A8$* z>pi&>IlZ*6IRl*mAov&WwmN++UBuU%&$$+P$>*PTx^tSC3kNnjxlPtDh0D6te$5tk zIM>bD&etTLnyDv6(n?n<({f6%XXFC_Q8&8^Txf0E7s|;p>;EEj2EXl9lLd&t6mO1u zmWP^mVQVMWZvL$AQzMx94__XszHID!E93pV%a2C7;cO_PhXYZMnGyGA^?Tx}d<2qD zIK2q~l^CU&;b->RFIczK@O{v&f-`rf1c(1WF11Vk6>LXSv;4@{+he5!I-2^x26g+` F{{hGmLJj}` literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/item/efficiency_module.png b/src/main/resources/assets/nerospace/textures/item/efficiency_module.png new file mode 100644 index 0000000000000000000000000000000000000000..3ae35f627a9ec44fbc416c507504269c66d7ab70 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`$(}BbAr*6y6C^SYbevgPaqaVb z318VD)q+}`7n7v#ggv|}vq77u^}I_@?+=DH?t+^wr&B9pFX%=3Twtx>T~Tw-dnV%% zhB=828fGh=H8q+uDDyCtwHjv3DN5dvxQ)A6ZVm&(;dPP<3Fn(afYvd1y85}Sb4q9e E0AfNmjsO4v literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/item/fortune_module.png b/src/main/resources/assets/nerospace/textures/item/fortune_module.png new file mode 100644 index 0000000000000000000000000000000000000000..21bb95f0735cc670c3283dc7682e2bdd04e3ae17 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`iJmTwAr*6y6C^SYbevgv4A;>~h%7#JetB^1=}F3Sg6#^CAd=d#Wzp$P!0 CtT$2s literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/item/frame_casing.png b/src/main/resources/assets/nerospace/textures/item/frame_casing.png new file mode 100644 index 0000000000000000000000000000000000000000..17dba83133fd488b9cef0f7e83ea29378bea0fd5 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jh-%!Ar*6y6C~mi4z_v!7th)8 z<^EB1PW|3B?Sgk3W;JQ-nU?WlhT)r=M*V`TmvsAIs+VM)`#(ZeQbHm{q{JZRK+Bm< zPt60_*38VzhbLJI2mJ8)p|X*&#JjDLJ7Ce_qv|jJ_i8WyAa%hgQPSC|K`utVjoYN; jj6{^0&S6h}X9kAZPdshUp7oDaxaN0CNPlGH5r43WbNcBIyBsdf-oX7q>O+loE1Ljw zK^x=38w+M}v)yA@<5_uq{%M{(om~rH=HdNAnEKE)h|FP=4i=+$9!VC;4oDvFerfyjRw2r~k)z4*}Q$iB} DU*a}@ literal 0 HcmV?d00001 diff --git a/tools/ecj.prefs b/tools/ecj.prefs index 36890d5..399d371 100644 --- a/tools/ecj.prefs +++ b/tools/ecj.prefs @@ -8,11 +8,23 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.annotation.nullable=org.jetbrains.annotations.Nullable org.eclipse.jdt.core.compiler.annotation.nonnull=org.jetbrains.annotations.NotNull org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +# Recognise the other @Nullable/@Nonnull flavours the codebase uses (VS Code's "automatic" mode +# does this for us; the batch compiler needs them listed). Without javax here, @Nullable fields +# like GalleryCaptureHarness#current were silently not analysed. +org.eclipse.jdt.core.compiler.annotation.nullable.secondary=javax.annotation.Nullable,jakarta.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=javax.annotation.Nonnull,jakarta.annotation.Nonnull org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.problem.missingNonNullByDefaultAnnotation=ignore +# Generic-null TYPE-ANNOTATION noise from Minecraft/NeoForge's heavily @NonNull-annotated generics +# meeting our unannotated code. nullUncheckedConversion (vanilla CriterionTrigger generics) can be +# ignored outright; nullSpecViolation ("Null constraint mismatch", e.g. Feature bounds) is a +# mandatory JDT problem that JDT refuses to set to `ignore` (it reverts to ERROR), so it stays a +# warning and the handful of framework-interop sites carry a local @SuppressWarnings("null"). +# The high-value deref checks (nullReference / potentialNullReference) stay ON — they caught the +# real @Nullable field/variable bugs. org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning org.eclipse.jdt.core.compiler.problem.nullReference=warning diff --git a/tools/fix_markdown.py b/tools/fix_markdown.py new file mode 100644 index 0000000..0286260 --- /dev/null +++ b/tools/fix_markdown.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Auto-fix the Markdown violations the gradle-mcp `markdown_check` tool reports, in place. + +Companion to that linter: it applies the same markdownlint rule subset the repo's +.markdownlint.json leaves enabled, so `markdown_check` comes back clean afterwards. + +Fixes: + MD009 strip trailing whitespace (keeping a valid 2-space hard break) + MD012 collapse runs of blank lines to a single blank + MD022 surround ATX headings with blank lines + MD031 surround fenced code blocks with blank lines + MD040 give a bare ``` fence a language (text) + MD047 end the file with exactly one newline + +Idempotent: run it as often as you like. Skips build/vcs directories. +Usage: python3 tools/fix_markdown.py # whole repo + python3 tools/fix_markdown.py wiki a.md # specific files/dirs +""" +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SKIP_DIRS = {'node_modules', '.git', '.gradle', 'build', 'out', 'bin', '.idea', '.vscode', 'run'} + +FENCE_RE = re.compile(r'^(\s{0,3})(`{3,}|~{3,})(.*)$') +HEADING_RE = re.compile(r'^#{1,6}(\s|$)') + + +def fix_text(text): + raw = text.split('\n') + + # MD009: strip trailing whitespace, but preserve an intentional 2-space hard break. + lines = [] + for ln in raw: + stripped = ln.rstrip() + trail = ln[len(stripped):] + lines.append(stripped + ' ' if stripped != '' and trail == ' ' else stripped) + + out = [] + in_fence = False + i, n = 0, len(lines) + while i < n: + ln = lines[i] + fence = FENCE_RE.match(ln) + + if fence and not in_fence: + if fence.group(3).strip() == '': # MD040: bare fence -> add a language + ln = fence.group(1) + fence.group(2) + 'text' + if out and out[-1] != '': # MD031: blank line before + out.append('') + out.append(ln) + in_fence = True + i += 1 + continue + + if fence and in_fence: # closing fence + out.append(ln) + in_fence = False + if i + 1 < n and lines[i + 1] != '': # MD031: blank line after + out.append('') + i += 1 + continue + + if in_fence: + out.append(ln) + i += 1 + continue + + if ln == '': # MD012: collapse blank runs + if not out or out[-1] != '': + out.append('') + i += 1 + continue + + if HEADING_RE.match(ln): # MD022: surround headings with blanks + if out and out[-1] != '': + out.append('') + out.append(ln) + if i + 1 < n and lines[i + 1] != '': + out.append('') + i += 1 + continue + + out.append(ln) + i += 1 + + while out and out[-1] == '': # MD047: exactly one trailing newline + out.pop() + return '\n'.join(out) + '\n' + + +def fix_file(path): + with open(path, 'r', encoding='utf-8') as f: + text = f.read() + fixed = fix_text(text) + if fixed != text: + with open(path, 'w', encoding='utf-8', newline='\n') as f: + f.write(fixed) + return True + return False + + +def collect(target): + if os.path.isfile(target): + return [target] if target.lower().endswith(('.md', '.markdown')) else [] + found = [] + for dirpath, dirnames, filenames in os.walk(target): + dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] + for name in filenames: + if name.lower().endswith(('.md', '.markdown')): + found.append(os.path.join(dirpath, name)) + return found + + +def main(): + targets = sys.argv[1:] or [ROOT] + files = [] + for t in targets: + p = t if os.path.isabs(t) else os.path.join(ROOT, t) + files.extend(collect(p)) + files = sorted(set(files)) + changed = 0 + for f in files: + if fix_file(f): + changed += 1 + print('fixed', os.path.relpath(f, ROOT)) + print(f'\n{changed}/{len(files)} file(s) changed.') + + +if __name__ == '__main__': + main() diff --git a/tools/gen_bbmodels.py b/tools/gen_bbmodels.py index 19ccadb..da29bad 100644 --- a/tools/gen_bbmodels.py +++ b/tools/gen_bbmodels.py @@ -43,7 +43,8 @@ "universal_pipe", "combustion_generator", "passive_generator", "battery", "creative_battery", "fluid_tank", "creative_fluid_tank", "gas_tank", "creative_gas_tank", "item_store", "creative_item_store", - "star_guide", "launch_gantry", "sentry_test"] + "star_guide", "launch_gantry", "sentry_test", + "quarry_controller", "quarry_landmark", "quarry_frame", "trash_can"] ITEMS = ["nerosium_ingot", "nerosium_dust", "raw_nerosium", "nerosium_pickaxe", "raw_nerosteel", "nerosteel_ingot", "xertz_quartz", "greenxertz_navigator", "rocket_fuel_canister", "rocket_tier_1", "rocket_tier_2", "rocket_tier_3", @@ -51,7 +52,8 @@ "cindrite", "rocket_fuel_bucket", "glacite", "loper_haunch", "strutter_drumstick", "drift_fleece", "station_compass", "greenxertz_compass", "cindara_compass", "glacira_compass", - "star_guide_book", "station_charter"] + "star_guide_book", "station_charter", + "frame_casing", "speed_module", "efficiency_module", "fortune_module", "silk_touch_module"] # Entity bbmodels are NOT generated here any more (art overhaul A3): `tools/model_sync.py` owns # every entity source bidirectionally from the per-model Java geometry. Generating them here too diff --git a/tools/gen_textures.py b/tools/gen_textures.py index c92ddc0..2eb5245 100644 --- a/tools/gen_textures.py +++ b/tools/gen_textures.py @@ -2635,7 +2635,193 @@ def recess(x0, y0, x1, y1): save(img, os.path.join(GUI_DIR, "star_guide.png")) +# ---------------- MINER_DESIGN: QUARRY / MINER ---------------- + +def gen_quarry_controller(): + rng = random.Random(1301) + img = new_img() + px = img.load() + noise_fill(img, METAL, rng) + bevel(img, METAL_L, METAL_D) + # recessed core + for y in range(4, 12): + for x in range(4, 12): + px[x, y] = METAL_D + # tier-1 red drill cross + for i in range(4, 12): + px[i, 8] = N_RED + px[8, i] = N_RED + px[8, 8] = N_GLOW + # cyan projector corner accents + for (bx, by) in [(2, 2), (13, 2), (2, 13), (13, 13)]: + px[bx, by] = I_CYAN + save(img, os.path.join(BLOCK_DIR, "quarry_controller.png")) + + +def gen_quarry_landmark(): + rng = random.Random(1302) + img = new_img() + px = img.load() + # opaque dark-steel body (rendered as a full cube) + noise_fill(img, [METAL[2], METAL[0], METAL_D], rng) + bevel(img, METAL_L, METAL_D) + # central purple emitter column with a bright top + for y in range(2, 14): + for x in range(6, 10): + px[x, y] = N_PURPLE if (x + y) % 2 else N_MAG + for x in range(6, 10): + px[x, 2] = N_GLOW + px[7, 1] = N_BRIGHT + px[8, 1] = N_BRIGHT + save(img, os.path.join(BLOCK_DIR, "quarry_landmark.png")) + + +def gen_quarry_frame(): + # Glowing space frame rendered as a clean WIREFRAME: every pixel is either fully opaque (the + # glowing border beams) or fully transparent (the open centre). All-or-nothing alpha makes 26.1 + # render it on the alpha-TESTED cutout layer (not the blended translucent layer), so a ring of + # these cubes reads as crisp glowing bars with no z-fighting / overdraw haze. + img = new_img() # CLEAR (alpha 0) background — the see-through centre + px = img.load() + # 2px opaque glowing border beam on every edge. + for i in range(S): + for t in (0, 1): + px[i, t] = I_CYAN + px[i, S - 1 - t] = I_CYAN + px[t, i] = I_CYAN + px[S - 1 - t, i] = I_CYAN + # brighter corner nodes (still fully opaque) so the lattice joints pop. + for (bx, by) in [(0, 0), (1, 1), (S - 1, 0), (S - 2, 1), + (0, S - 1), (1, S - 2), (S - 1, S - 1), (S - 2, S - 2)]: + px[bx, by] = I_WHITE + save(img, os.path.join(BLOCK_DIR, "quarry_frame.png")) + + +def gen_frame_casing(): + img = new_img() + px = img.load() + for y in range(3, 13): + for x in range(3, 13): + if x in (3, 12) or y in (3, 12): + px[x, y] = METAL_L if (x + y) % 2 else METAL[1] + for y in range(4, 12): + for x in range(4, 12): + if x in (4, 11) or y in (4, 11): + px[x, y] = METAL_D + for (bx, by) in [(3, 3), (12, 3), (3, 12), (12, 12)]: + px[bx, by] = METAL_L + save(img, os.path.join(ITEM_DIR, "frame_casing.png")) + + +def gen_quarry_module(name, accent): + img = new_img() + px = img.load() + for y in range(2, 14): + for x in range(3, 13): + px[x, y] = (40, 44, 54, 255) + for x in range(3, 13): + px[x, 2] = accent + px[x, 13] = accent + for y in range(2, 14): + px[3, y] = accent + px[12, y] = accent + for y in range(5, 9): + for x in range(6, 10): + px[x, y] = accent + px[7, 6] = (255, 255, 255, 255) + for x in range(5, 11, 2): + px[x, 13] = METAL_L + save(img, os.path.join(ITEM_DIR, name + ".png")) + + +def gen_gui_quarry(accent, accent_d): + """A 176x210 quarry-screen hull: frame + module sockets on top, a 2x6 output grid, a readout + band, and the standard player inventory at (8,126).""" + W, H = 176, 210 + img = Image.new("RGBA", (256, 256), CLEAR) + px = img.load() + rng = random.Random(hash("quarry") & 0xFFFF) + INK = (5, 8, 13, 255) + HULL = [(13, 17, 25, 255), (15, 20, 29, 255), (11, 15, 22, 255)] + PANEL = (8, 11, 17, 255) + SOCKET = (20, 26, 36, 255) + SOCKET_HI = (44, 56, 74, 255) + for y in range(H): + for x in range(W): + px[x, y] = rng.choice(HULL) + for i in range(W): + px[i, 0] = accent + px[i, H - 1] = accent_d + for i in range(H): + px[0, i] = accent + px[W - 1, i] = accent_d + + def socket(sx, sy): + for y in range(sy - 1, sy + 17): + for x in range(sx - 1, sx + 17): + px[x, y] = SOCKET + for x in range(sx - 1, sx + 17): + px[x, sy - 1] = INK + px[x, sy + 16] = SOCKET_HI + for y in range(sy - 1, sy + 17): + px[sx - 1, y] = INK + px[sx + 16, y] = SOCKET_HI + + def recess(x0, y0, x1, y1): + for y in range(y0, y1): + for x in range(x0, x1): + px[x, y] = PANEL + + socket(8, 20) # frame casing + for i in range(4): # module sockets (room for up to 4 — Tier 3) + socket(26 + i * 18, 20) + for r in range(2): # output grid + for c in range(6): + socket(8 + c * 18, 42 + r * 18) + recess(6, 78, 170, 114) # readout band + for row in range(3): # player inventory + for col in range(9): + socket(8 + col * 18, 126 + row * 18) + for col in range(9): # hotbar + socket(8 + col * 18, 184) + save(img, os.path.join(GUI_DIR, "quarry.png")) + + +def gen_trash_can(): + rng = random.Random(1401) + img = new_img() + px = img.load() + noise_fill(img, METAL, rng) + bevel(img, METAL_L, METAL_D) + # vertical ribs + for x in (4, 8, 11): + for y in range(2, 14): + px[x, y] = METAL_D + # dark open mouth across the top + for x in range(2, 14): + px[x, 2] = (12, 12, 16, 255) + px[x, 3] = (20, 20, 26, 255) + # hazard band under the rim + for x in range(2, 14): + px[x, 4] = HAZ_Y if (x // 2) % 2 == 0 else HAZ_K + for (rx, ry) in [(2, 13), (13, 13)]: + px[rx, ry] = METAL_L + save(img, os.path.join(BLOCK_DIR, "trash_can.png")) + + if __name__ == "__main__": + # Trash Can (logistics void sink). + gen_trash_can() + # Quarry / Miner (MINER_DESIGN). + gen_quarry_controller() + gen_quarry_landmark() + gen_quarry_frame() + gen_frame_casing() + gen_quarry_module("speed_module", (90, 200, 255, 255)) + gen_quarry_module("efficiency_module", (120, 230, 140, 255)) + gen_quarry_module("fortune_module", (120, 180, 255, 255)) + gen_quarry_module("silk_touch_module", (230, 180, 255, 255)) + gen_gui_quarry((224, 64, 90, 255), (90, 22, 28, 255)) # Heavy Launch Complex + cockpit rework. gen_launch_gantry() gen_rocket_tier_entities() diff --git a/tools/gradle-mcp/server.js b/tools/gradle-mcp/server.js index 83024af..807e54c 100644 --- a/tools/gradle-mcp/server.js +++ b/tools/gradle-mcp/server.js @@ -1,7 +1,9 @@ #!/usr/bin/env node /* * gradle-mcp: a tiny, zero-dependency MCP server that runs Gradle builds - * on the local machine and exposes them as async tools. + * on the local machine and exposes them as async tools. It also reports + * compiler/analyzer diagnostics from each build, and lints Markdown files + * (markdown_check) against a markdownlint-style rule subset. * * Why this exists: build verification (NeoForge decompile + compile) needs * real RAM, multiple cores, and minutes of uninterrupted runtime that a @@ -28,7 +30,7 @@ const path = require('node:path'); const crypto = require('node:crypto'); const SERVER_NAME = 'gradle-mcp'; -const SERVER_VERSION = '1.1.0'; +const SERVER_VERSION = '1.3.0'; const DEFAULT_PROTOCOL = '2025-06-18'; // Where to run Gradle. Override per-call with the `project_dir` argument. @@ -62,13 +64,228 @@ function tailFile(p, lines) { } } -function detectOutcome(p) { - const txt = tailFile(p, 0); - if (/BUILD SUCCESSFUL/.test(txt)) return 'SUCCESSFUL'; - if (/BUILD FAILED/.test(txt)) return 'FAILED'; +function outcomeOf(text) { + if (/BUILD SUCCESSFUL/.test(text)) return 'SUCCESSFUL'; + if (/BUILD FAILED/.test(text)) return 'FAILED'; return null; } +/** + * Extract compiler / analyzer diagnostics from a build log. Handles both: + * - javac (gradle_build / compileJava): ".../Foo.java:42: error: message" + * - Eclipse ecj (gradle_analyze / ecjCheck): + * 1. WARNING in /abs/Foo.java (at line 42) + * offending source line + * ^^^^^^ + * The actual problem description + * ---------- + * Returns a flat list of { severity, file, line, message }. + */ +function parseDiagnostics(text) { + const lines = text.split(/\r?\n/); + const diags = []; + let ecj = null; // a pending ecj block whose message arrives on a later line + const flush = () => { + if (ecj) { + diags.push(ecj); + ecj = null; + } + }; + for (const line of lines) { + // An ecj block ends at its dashed separator. + if (/^-{5,}\s*$/.test(line)) { + flush(); + continue; + } + // javac single-line diagnostic. + let m = line.match(/^(.*\.java):(\d+):\s*(error|warning):\s*(.*)$/); + if (m) { + flush(); + diags.push({ + severity: m[3].toLowerCase(), + file: m[1].trim(), + line: Number(m[2]), + message: m[4].trim(), + }); + continue; + } + // ecj diagnostic header. + m = line.match(/^\s*\d+\.\s*(WARNING|ERROR)\s+in\s+(.+?\.java)\s*\(at line (\d+)\)/); + if (m) { + flush(); + ecj = { + severity: m[1].toLowerCase(), + file: m[2].trim(), + line: Number(m[3]), + message: '', + }; + continue; + } + // Inside an ecj block the description is the last non-caret, non-blank line. + if (ecj) { + const t = line.trim(); + if (t && !/^[\^~\s]+$/.test(t)) { + ecj.message = t; + } + } + } + flush(); + return diags; +} + +const MAX_DIAGNOSTIC_MESSAGES = 50; + +// ---- Markdown linting (zero-dependency subset of markdownlint) ------------- + +const MARKDOWN_SKIP_DIRS = new Set([ + 'node_modules', '.git', '.gradle', 'build', 'out', 'bin', '.idea', '.vscode', 'run', +]); + +/** Recursively collect *.md / *.markdown files under {@code root}, skipping build/vcs dirs. */ +function findMarkdownFiles(root) { + const results = []; + const walk = (dir) => { + if (results.length > 5000) return; + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) { + if (!MARKDOWN_SKIP_DIRS.has(e.name)) walk(full); + } else if (/\.(md|markdown)$/i.test(e.name)) { + results.push(full); + } + } + }; + walk(root); + return results; +} + +/** Read the repo's .markdownlint.json (rule toggles); {} if absent/unparseable. */ +function loadMarkdownConfig(projectDir) { + const p = path.join(projectDir, '.markdownlint.json'); + try { + if (fs.existsSync(p)) return { config: JSON.parse(fs.readFileSync(p, 'utf8')), path: p }; + } catch { + /* malformed config → fall back to defaults */ + } + return { config: {}, path: null }; +} + +/** + * Lint one Markdown document against a useful subset of markdownlint rules. A rule whose id is set + * to {@code false} in {@code config} is skipped (so the repo .markdownlint.json is honoured). + * Returns [{ line, rule, description }]. + */ +function lintMarkdown(text, config) { + const on = (id) => config[id] !== false; + let lines = text.split(/\r?\n/); + // Drop the empty element produced by a trailing newline so it isn't seen as a blank line. + if (lines.length && lines[lines.length - 1] === '') lines = lines.slice(0, -1); + + const v = []; + const add = (line, rule, description) => v.push({ line, rule, description }); + + let inFence = false; + let fenceChar = ''; + let fenceLen = 0; + let blankRun = 0; + let h1Count = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const prev = i > 0 ? lines[i - 1] : null; + const next = i + 1 < lines.length ? lines[i + 1] : null; + const trimmed = line.trim(); + + const fence = line.match(/^(\s{0,3})(`{3,}|~{3,})(.*)$/); + if (fence) { + const marker = fence[2]; + const after = fence[3].trim(); + if (!inFence) { + inFence = true; + fenceChar = marker[0]; + fenceLen = marker.length; + blankRun = 0; + if (on('MD031') && prev !== null && prev.trim() !== '') { + add(i + 1, 'MD031', 'Fenced code block should be preceded by a blank line'); + } + if (on('MD040') && after === '') { + add(i + 1, 'MD040', 'Fenced code block should have a language specified'); + } + continue; + } + if (marker[0] === fenceChar && marker.length >= fenceLen && after === '') { + inFence = false; + blankRun = 0; + if (on('MD031') && next !== null && next.trim() !== '') { + add(i + 1, 'MD031', 'Fenced code block should be followed by a blank line'); + } + continue; + } + } + if (inFence) continue; // don't apply prose rules to code content + + if (trimmed === '') { + blankRun++; + if (on('MD012') && blankRun > 1) { + add(i + 1, 'MD012', 'Multiple consecutive blank lines'); + } + if (on('MD009') && line.length > 0) { + add(i + 1, 'MD009', 'Trailing spaces'); + } + continue; + } + blankRun = 0; + + if (on('MD010') && line.includes('\t')) { + add(i + 1, 'MD010', 'Hard tabs'); + } + if (on('MD009')) { + const ws = line.match(/[ \t]+$/); + if (ws && (ws[0].includes('\t') || ws[0].length !== 2)) { + // markdownlint allows exactly 2 trailing spaces as a hard line break (br_spaces=2). + add(i + 1, 'MD009', 'Trailing spaces'); + } + } + + const h = line.match(/^(#{1,6})(\s*)(.*?)\s*$/); + if (h && line.startsWith('#')) { + const level = h[1].length; + if (on('MD018') && h[2] === '' && h[3] !== '') { + add(i + 1, 'MD018', 'No space after hash on atx style heading'); + } + if (on('MD022')) { + if (prev !== null && prev.trim() !== '') { + add(i + 1, 'MD022', 'Heading should be preceded by a blank line'); + } + if (next !== null && next.trim() !== '') { + add(i + 1, 'MD022', 'Heading should be followed by a blank line'); + } + } + if (level === 1) { + h1Count++; + if (on('MD025') && h1Count > 1) { + add(i + 1, 'MD025', 'Multiple top-level (H1) headings in the same document'); + } + } + if (on('MD026') && /[.,;:!?]$/.test(h[3])) { + add(i + 1, 'MD026', 'Trailing punctuation in heading'); + } + } + } + + if (on('MD047') && text.length > 0 && (!text.endsWith('\n') || text.endsWith('\n\n'))) { + add(lines.length, 'MD047', 'File should end with a single newline character'); + } + + return v; +} + function startBuild({ tasks, extra_args, project_dir }) { const projectDir = project_dir || DEFAULT_PROJECT_DIR; const wrapper = gradlewCommand(projectDir); @@ -154,18 +371,38 @@ function resolveBuild(build_id) { function summarize(rec, tailLines = 0) { const elapsedMs = (rec.endedAt || Date.now()) - rec.startedAt; - const outcome = detectOutcome(rec.logPath); + const full = tailFile(rec.logPath, 0); // read once; reused for outcome + diagnostics + tail + const diags = parseDiagnostics(full); + const errorCount = diags.reduce((n, d) => n + (d.severity === 'error' ? 1 : 0), 0); + const warningCount = diags.length - errorCount; const obj = { build_id: rec.id, status: rec.status, // running | succeeded | failed | stopped | error - outcome, // SUCCESSFUL | FAILED | null (still running / unknown) + outcome: outcomeOf(full), // SUCCESSFUL | FAILED | null (still running / unknown) exit_code: rec.exitCode, project_dir: rec.projectDir, command: `gradlew ${[...rec.tasks, ...rec.args].join(' ')}`, elapsed_seconds: Math.round(elapsedMs / 1000), log_file: rec.logPath, + // Compiler + ecj analyzer diagnostics, parsed straight from the log so callers + // see errors/warnings without a separate gradle_log grep. + diagnostics: { + errors: errorCount, + warnings: warningCount, + messages: diags + .slice(0, MAX_DIAGNOSTIC_MESSAGES) + .map( + (d) => + `${d.severity.toUpperCase()} ${d.file}:${d.line}` + + (d.message ? ` — ${d.message}` : '') + ), + truncated: diags.length > MAX_DIAGNOSTIC_MESSAGES, + }, }; - if (tailLines > 0) obj.log_tail = tailFile(rec.logPath, tailLines); + if (tailLines > 0) { + const arr = full.split(/\r?\n/); + obj.log_tail = arr.slice(Math.max(0, arr.length - tailLines)).join('\n'); + } return obj; } @@ -217,8 +454,9 @@ const TOOLS = [ description: 'Convenience: start the `ecjCheck` task asynchronously — runs the Eclipse ' + 'compiler (the same analyzer as the VS Code Problems panel, configured by ' + - 'tools/ecj.prefs) over the main sources. Poll with gradle_status, then read ' + - 'diagnostics with gradle_log (grep "WARNING|ERROR" for a summary).', + 'tools/ecj.prefs) over the main sources. Poll with gradle_status — its ' + + '`diagnostics` field now reports the parsed analyzer errors/warnings directly ' + + '(use gradle_log for the full raw output).', inputSchema: { type: 'object', properties: { @@ -231,7 +469,9 @@ const TOOLS = [ name: 'gradle_status', description: 'Poll a build. Returns status, outcome (BUILD SUCCESSFUL/FAILED once known), ' + - 'exit code, elapsed time, and a tail of the log. Omit build_id for the latest build.', + 'exit code, elapsed time, a tail of the log, and a `diagnostics` summary — ' + + 'compiler (javac) and analyzer (ecj/ecjCheck) error/warning counts plus the ' + + 'first matching messages, parsed straight from the log. Omit build_id for the latest build.', inputSchema: { type: 'object', properties: { @@ -260,6 +500,33 @@ const TOOLS = [ }, }, }, + { + name: 'markdown_check', + description: + 'Lint Markdown files for common markdownlint-style violations and return them immediately ' + + '(synchronous — no build_id/polling). Checks: MD009 trailing spaces, MD010 hard tabs, ' + + 'MD012 multiple blank lines, MD018 missing space after #, MD022 blanks around headings, ' + + 'MD025 multiple H1, MD026 heading trailing punctuation, MD031 blanks around fenced code, ' + + 'MD040 fenced code language, MD047 single trailing newline. Honours the repo ' + + '.markdownlint.json (a rule set to false there is skipped). Scans the project for ' + + '*.md/*.markdown (skipping node_modules/.git/build/...) unless `paths` is given.', + inputSchema: { + type: 'object', + properties: { + paths: { + type: 'array', + items: { type: 'string' }, + description: + 'Specific files or directories to lint (relative to project_dir or absolute). ' + + 'Default: scan the whole project.', + }, + project_dir: { + type: 'string', + description: 'Project root (defaults to GRADLE_PROJECT_DIR).', + }, + }, + }, + }, { name: 'gradle_stop', description: 'Stop a running build (sends terminate). Omit build_id for the latest build.', @@ -329,6 +596,66 @@ function callTool(name, args) { } return { build_id: rec.id, log: text }; } + case 'markdown_check': { + const projectDir = args.project_dir || DEFAULT_PROJECT_DIR; + if (!fs.existsSync(projectDir)) { + throw new Error(`project_dir does not exist: ${projectDir}`); + } + const { config, path: configPath } = loadMarkdownConfig(projectDir); + + let files = []; + if (Array.isArray(args.paths) && args.paths.length) { + for (const rel of args.paths) { + const p = path.isAbsolute(rel) ? rel : path.join(projectDir, rel); + try { + const st = fs.statSync(p); + if (st.isDirectory()) files.push(...findMarkdownFiles(p)); + else if (/\.(md|markdown)$/i.test(p)) files.push(p); + } catch { + /* skip missing path */ + } + } + } else { + files = findMarkdownFiles(projectDir); + } + files = [...new Set(files)]; + + const MAX_VIOLATIONS = 300; + const violations = []; + const countsByRule = {}; + let truncated = false; + for (const f of files) { + let text; + try { + text = fs.readFileSync(f, 'utf8'); + } catch { + continue; + } + for (const x of lintMarkdown(text, config)) { + countsByRule[x.rule] = (countsByRule[x.rule] || 0) + 1; + if (violations.length < MAX_VIOLATIONS) { + violations.push({ + file: path.relative(projectDir, f) || f, + line: x.line, + rule: x.rule, + description: x.description, + }); + } else { + truncated = true; + } + } + } + const total = Object.values(countsByRule).reduce((a, b) => a + b, 0); + return { + project_dir: projectDir, + config_file: configPath, + files_checked: files.length, + total_violations: total, + counts_by_rule: countsByRule, + violations, + truncated, + }; + } case 'gradle_stop': { const rec = resolveBuild(args.build_id); if (!rec) throw new Error('No build found.'); diff --git a/wiki/Battery.md b/wiki/Battery.md index fecb102..a2dc9f2 100644 --- a/wiki/Battery.md +++ b/wiki/Battery.md @@ -3,15 +3,19 @@ A passive energy store that buffers your grid. ## Obtaining + **Craft** (shaped): a nerosteel casing around a redstone-and-nerosium cell — -``` + +```text N R N R I R N R N ``` + `N` = Nerosteel Ingot · `R` = Redstone · `I` = Nerosium Ingot ## How it works + - Stores **200,000 FE** (configurable); accepts and provides power on **every side** at pipe throughput. - Generators fill it through the network; machines drain it the same way — so production hiccups @@ -22,5 +26,6 @@ A **Creative Battery** variant (creative tab only) is an endless source and sink testing grids — see [Creative Source Blocks](Creative-Source-Blocks). ## Details + - ID: `nerospace:battery` · Tool: pickaxe, iron tier · Drops: itself - Config: `batteryCapacity` diff --git a/wiki/Block-of-Cindrite.md b/wiki/Block-of-Cindrite.md index d7e9af9..cd041bd 100644 --- a/wiki/Block-of-Cindrite.md +++ b/wiki/Block-of-Cindrite.md @@ -3,12 +3,15 @@ Compact storage for nine Cindrite gems. ## Overview + A glowing ember-coloured storage block of the Cindara crystal. ## Obtaining + - **Craft:** fill a 3×3 grid with **Cindrite**. - **Unpack:** craft the block alone to get **9 Cindrite** back. ## Details + - ID: `nerospace:cindrite_block` - Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Block-of-Glacite.md b/wiki/Block-of-Glacite.md index 9ca3472..5a75aa4 100644 --- a/wiki/Block-of-Glacite.md +++ b/wiki/Block-of-Glacite.md @@ -3,12 +3,15 @@ Compact storage for nine Glacite gems. ## Overview + A pale, ice-blue storage block of the Glacira crystal. ## Obtaining + - **Craft:** fill a 3×3 grid with **Glacite**. - **Unpack:** craft the block alone to get **9 Glacite** back. ## Details + - ID: `nerospace:glacite_block` - Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Block-of-Nerosium.md b/wiki/Block-of-Nerosium.md index 1e1b443..6541227 100644 --- a/wiki/Block-of-Nerosium.md +++ b/wiki/Block-of-Nerosium.md @@ -3,14 +3,17 @@ Compact storage for nine Nerosium Ingots. ## Overview + A decorative + storage block of refined nerosium. Purely for compact storage and building; it has no machine behaviour. ## Obtaining + - **Craft:** fill a 3×3 grid with **Nerosium Ingots**. - **Unpack:** craft the block alone to get **9 Nerosium Ingots** back. - It is also the core ingredient of the **[Rocket Launch Pad](Rocket-Launch-Pad)**. ## Details + - ID: `nerospace:nerosium_block` - Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Block-of-Nerosteel.md b/wiki/Block-of-Nerosteel.md index 8123dfc..7a2efc2 100644 --- a/wiki/Block-of-Nerosteel.md +++ b/wiki/Block-of-Nerosteel.md @@ -3,13 +3,16 @@ Compact storage for nine Nerosteel Ingots. ## Overview + A storage/building block of refined nerosteel. It also appears as a core component in rocket recipes. ## Obtaining + - **Craft:** fill a 3×3 grid with **Nerosteel Ingots**. - **Unpack:** craft the block alone to get **9 Nerosteel Ingots** back. - Used as the engine core in the **Tier 1 / 2 / 3 rockets**. ## Details + - ID: `nerospace:nerosteel_block` - Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Block-of-Raw-Nerosium.md b/wiki/Block-of-Raw-Nerosium.md index bb2a1e8..828995d 100644 --- a/wiki/Block-of-Raw-Nerosium.md +++ b/wiki/Block-of-Raw-Nerosium.md @@ -3,12 +3,15 @@ Compact storage for nine units of Raw Nerosium. ## Overview + Stores unprocessed **Raw Nerosium** in block form — handy for hauling a mining haul before smelting. ## Obtaining + - **Craft:** fill a 3×3 grid with **Raw Nerosium**. - **Unpack:** craft the block alone to get **9 Raw Nerosium** back. ## Details + - ID: `nerospace:raw_nerosium_block` - Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Cindrite-Ore.md b/wiki/Cindrite-Ore.md index f976bcf..b4bc430 100644 --- a/wiki/Cindrite-Ore.md +++ b/wiki/Cindrite-Ore.md @@ -3,20 +3,24 @@ The signature crystal of the volcanic moon **Cindara**. ## Overview + Cindrite is a glowing ember-coloured gem found only on Cindara. It is rarer than the Greenxertz ores and sits deeper, rewarding a trip to the most hostile world in the mod. ## Obtaining + - **Mining:** requires an **iron-tier pickaxe** or better. Drops the **Cindrite** gem directly (Fortune-affected). - **Generation:** spawns in the **Cindara** dimension, in a triangular band roughly **y −48 to y 48** — rarer than the Greenxertz ores. ## Use + - Pack into a **[Block of Cindrite](Block-of-Cindrite)** for storage. - Upgrade a **[Terraformer](Terraformer)** to **Tier 3** (drop a Cindrite into its upgrade slot). - Reserved as a high-tier reagent for future content. ## Details + - ID: `nerospace:cindrite_ore` · Dimension: Cindara - Tool: pickaxe, iron tier · Drops: Cindrite (gem) diff --git a/wiki/Combustion-Generator.md b/wiki/Combustion-Generator.md index 255405e..e83afc8 100644 --- a/wiki/Combustion-Generator.md +++ b/wiki/Combustion-Generator.md @@ -3,15 +3,19 @@ Burns fuel into energy (FE) for the pipe network. ## Obtaining + **Craft** (shaped): a nerosteel frame around a Furnace, wired with Redstone — -``` + +```text N N N N F N N R N ``` + `N` = Nerosteel Ingot · `F` = Furnace · `R` = Redstone ## How it works + - **Fuel slot** (GUI, hand or hopper/pipe fed): coal, charcoal, coal blocks, blaze rods, or Rocket Fuel Canisters. - Burning generates **60 FE/t** (configurable) into a 50,000 FE buffer. @@ -20,5 +24,6 @@ N R N - GUI shows the power buffer and burn progress; emits a comparator signal from its charge. ## Details + - ID: `nerospace:combustion_generator` · Tool: pickaxe, iron tier · Drops: itself - Config: `combustionGeneratorFePerTick` diff --git a/wiki/Configurator.md b/wiki/Configurator.md index 54307ee..05660f2 100644 --- a/wiki/Configurator.md +++ b/wiki/Configurator.md @@ -3,9 +3,11 @@ The network tool — sets what each pipe face does, per resource layer. ## Obtaining + **Craft** (shaped): two Nerosteel Ingots over Redstone, wand-style. ## How it works + Every Universal Pipe face has four independent I/O modes (one per layer: Energy, Fluid, Gas, Items): - **Auto** — pulls from providers and pushes to receivers (default) @@ -21,8 +23,10 @@ With the Configurator: (Energy → Fluid → Gas → Items). ## Tips + Faces touching an endless source (or any block that both gives and takes) should be set to **In** so the pipe doesn't push its buffer straight back. Faces feeding machines are happiest on **Out**. ## Details + - ID: `nerospace:configurator` · Stack size: 1 diff --git a/wiki/Creative-Source-Blocks.md b/wiki/Creative-Source-Blocks.md index 617f74d..b244c9b 100644 --- a/wiki/Creative-Source-Blocks.md +++ b/wiki/Creative-Source-Blocks.md @@ -14,6 +14,7 @@ All four also act as voids: anything pushed into them disappears, which makes th throughput testing. ## Gallery + `/nerospace gallery` (creative, cheats on) builds four live demonstration rows — one per resource layer — each running creative source → pipes → real storage block, pre-configured with the recommended face modes (source face **In**, sink face **Out**). diff --git a/wiki/Creatures.md b/wiki/Creatures.md index d36f385..7202c5a 100644 --- a/wiki/Creatures.md +++ b/wiki/Creatures.md @@ -7,6 +7,7 @@ the dark. ## Greenxertz ### Xertz Stalker — "Crystal Hunter" (hostile) + A tall, upright crystalline biped — the planet's apex predator. Uses a light-independent spawn rule, so it's dangerous **day and night**. Strides toward prey on two legs with down-swept blade-arms. @@ -18,6 +19,7 @@ a cold cyan silhouette in the dark. vanilla spider sounds until bespoke audio is recorded — see "Sound notes" below.)* ### Quartz Crawler — "Geode Skitterer" (neutral) + A low, domed six-legged crawler with a back crystal cluster. Skitters along the ground and leaves you alone unless provoked. @@ -29,6 +31,7 @@ quartz. silverfish sounds.)* ### Greenling — "Sprout" (passive) + A small, chubby grounded critter with an oversized cheeky head and a leaf crest. Harmless ambient life; toddles around on two stubby legs. @@ -41,6 +44,7 @@ sounds for a soft, gentle voice.)* ## Cindara ### Cinder Stalker — "Magma Hulk" (hostile) + A heavy, horned quadruped of the volcanic moon, ridged with obsidian back-plates and glowing embers. Fire-immune and aggressive — the main threat on Cindara. @@ -54,6 +58,7 @@ sounds.)* ## Glacira ### Frost Strider — "Ice Stilt-Walker" (hostile) + A tall, gangly predator stalking the frozen moon on four stilt legs, with a long low-slung neck and a row of ice-shard back spines. Freeze-immune; slightly faster but more fragile than the Cinder Stalker. @@ -69,15 +74,18 @@ ecosystem of deeper terraforming. All three are peaceful, follow their breed foo and spawn naturally on Living ground (the [Terraformer](Terraformer) also seeds starter herds). ### Meadow Loper (Greenxertz — cow-analogue) + A placid bulk grazer: a deep barrel body on sturdy legs, broad low head, small horn nubs, lazy swishing tail. **Breeds with wheat; drops Loper Haunch** (hearty food). ### Ember Strutter (Cindara — chicken-analogue) + A skittish little ground bird with ember-orange feathers, a comb and quick two-legged strut. Fire-proof, like everything that survives Cindara. **Breeds with seeds; drops Strutter Drumstick** (food). Its ember flecks glow faintly in the dark. ### Woolly Drift (Glacira — sheep-analogue) + A shaggy snowdrift of a grazer: one big rounded fleece ridged with wind-packed tufts over stubby legs. Cold-proof (no freeze build-up). **Breeds with wheat; drops Drift Fleece** (crafts into 4 string). diff --git a/wiki/Deepslate-Nerosium-Ore.md b/wiki/Deepslate-Nerosium-Ore.md index 4fb43c9..26e79c1 100644 --- a/wiki/Deepslate-Nerosium-Ore.md +++ b/wiki/Deepslate-Nerosium-Ore.md @@ -3,17 +3,21 @@ The deepslate-hosted variant of [Nerosium Ore](Nerosium-Ore), found in the lower part of its range. ## Overview + Identical drops and uses to Nerosium Ore, but embedded in deepslate, so it is slightly tougher to mine. ## Obtaining + - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosium** (Fortune-affected). - **Generation:** the deepslate form of the Overworld nerosium ore deposit (appears where the deposit intersects deepslate, in the lower part of the **y −24 to y 56** band). ## Use + Same as [Nerosium Ore](Nerosium-Ore): smelt/blast Raw Nerosium into a **Nerosium Ingot**, or grind it in a **[Nerosium Grinder](Nerosium-Grinder)** for double yield. ## Details + - ID: `nerospace:deepslate_nerosium_ore` - Tool: pickaxe, iron tier · Drops: Raw Nerosium diff --git a/wiki/Fluid-Tank.md b/wiki/Fluid-Tank.md index 3c3e67b..8b8e6d0 100644 --- a/wiki/Fluid-Tank.md +++ b/wiki/Fluid-Tank.md @@ -4,15 +4,19 @@ A passive single-fluid store for the pipe network. (Not to be confused with the [Fuel Tank](Fuel-Tank) machine.) ## Obtaining + **Craft** (shaped): a nerosteel shell with glass windows — -``` + +```text N G N G G N G N ``` + `N` = Nerosteel Ingot · `G` = Glass ## How it works + - Holds **16,000 mB** (configurable) of any one fluid. - **Buckets:** right-click with a filled bucket to pour in, an empty bucket to draw out. - **Pipes:** the fluid layer fills and drains it on every side — remember the network carries one @@ -23,5 +27,6 @@ A **Creative Fluid Tank** variant supplies endless fluid — see [Creative Source Blocks](Creative-Source-Blocks). ## Details + - ID: `nerospace:fluid_tank` · Tool: pickaxe, iron tier · Drops: itself - Config: `fluidTankCapacity` diff --git a/wiki/Fuel-Refinery.md b/wiki/Fuel-Refinery.md index 8bf420c..0dea577 100644 --- a/wiki/Fuel-Refinery.md +++ b/wiki/Fuel-Refinery.md @@ -3,23 +3,28 @@ Refines coal + blaze powder + grid energy into pipeable liquid rocket fuel. ## Overview + The Fuel Refinery is the **logistics-grade** way to make rocket fuel. Instead of hand-crafting canisters, feed it raw **coal** (or charcoal), **blaze powder** and **grid power**, and it produces liquid **Rocket Fuel** in an internal tank that [Universal Pipes](Universal-Pipe) can pull straight into a [Fuel Tank](Fuel-Tank) or a padded rocket. It is craftable before your first launch. ## Obtaining + **Craft** (shaped): -``` + +```text N G N N F N N R N ``` + `N` = Nerosteel Ingot · `G` = Glass · `F` = Furnace · `R` = Redstone (Ingredients are tag-based, so other mods' iron-equivalent glass/redstone work too.) ## How it works + - **Inputs:** a **carbon** slot (coal or charcoal) and a **catalyst** slot (blaze powder). Hoppers and pipes feed them through the item capability — coal routes to carbon, blaze to catalyst automatically. - **Power:** grid power only (insert-capped 1,000 FE/tick). Feed it from a generator or Battery over @@ -34,4 +39,5 @@ All rates scale with the config multipliers (`energyRate`, `fuelCost`, `machineS [Configuration](Configuration). ## Details + - ID: `nerospace:fuel_refinery` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Fuel-Tank.md b/wiki/Fuel-Tank.md index cb9bcf8..2f82949 100644 --- a/wiki/Fuel-Tank.md +++ b/wiki/Fuel-Tank.md @@ -3,20 +3,25 @@ Stores rocket fuel and auto-fuels a rocket sitting on an adjacent launch pad. ## Overview + The Fuel Tank is the first piece of launch-pad automation: it holds a large buffer of **Rocket Fuel** and, each tick, pumps it into a rocket standing on a connected **[Rocket Launch Pad](Rocket-Launch-Pad)** — so you don't have to hand-fill rockets with canisters. ## Obtaining + **Craft** (shaped): -``` + +```text N G N G C G N G N ``` + `N` = Nerosteel Ingot · `G` = Glass · `C` = Rocket Fuel Canister ## How it works + - **Capacity:** 32,000 mB of rocket fuel. - **Filling:** right-click with a **Rocket Fuel Bucket** or **Canister** to add fuel (empty bucket is returned); a fluid pipe can fill it via the fluid capability. Empty-hand right-click prints the @@ -35,4 +40,5 @@ N G N - **Comparator:** emits a redstone signal scaled to its fill level. ## Details + - ID: `nerospace:fuel_tank` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Future-Features.md b/wiki/Future-Features.md index 1f3a2de..c155cab 100644 --- a/wiki/Future-Features.md +++ b/wiki/Future-Features.md @@ -4,28 +4,33 @@ A longer-term wish list — ideas under consideration after 1.0, not commitments (open a GitHub issue or post in the [Discord](https://discord.gg/ArPXvYUzJG)). Grouped by theme. ## Space & travel + - More **destinations** — an asteroid field, gas giant moons… each with its own materials, hazards and biomes. - **Deeper station building** — more station modules and reasons to expand a founded station beyond its landing platform. ## Survival & atmosphere + - A **radiation hazard + suit variant** — deliberately deferred from 1.0 until a destination exists that justifies it (the asteroid field is the leading candidate). - **Smarter oxygen visuals** — fog/haze tuning, boundary shimmer, optional top-down "fill" effect. ## Terraforming & worlds + - **More late-game terraforming toys** — richer flora control, configurable target biomes, more livestock interactions (shearing, milking analogues). - **Active (away-from-player) terraforming** — opt-in bounded chunk-loading so terraforming continues while you're elsewhere (off by default — a performance footgun). ## Machines & tech + - **More processing** — ore washing/smelting tiers, alloys, an expanded material tree. - **Data-driven grinder recipes** — move grinding to a datapack `RecipeType` so packs can add their own. ## Content & polish + - **Bespoke audio** — real rocket-launch, machine, ambience and creature `.ogg` sounds (the 1.0 sound events alias fitting vanilla audio as placeholders; swapping in real files is a pure resource change). diff --git a/wiki/Gas-Tank.md b/wiki/Gas-Tank.md index 194cdcd..2013ddb 100644 --- a/wiki/Gas-Tank.md +++ b/wiki/Gas-Tank.md @@ -3,15 +3,19 @@ A pressurised store for one gas, filled and drained through the pipe network's gas layer. ## Obtaining + **Craft** (shaped): a sealed [Fluid Tank](Fluid-Tank) — -``` + +```text N N N N T N N N N ``` + `N` = Nerosteel Ingot · `T` = Fluid Tank ## How it works + - Holds **16,000 mB** (configurable) of one gas — **Oxygen** is the first gas in the mod. - Universal Pipes (gas layer, green stream) fill and drain it on every side; pipe a surplus from your [Oxygen Generator](Oxygen-Generator) into it as a life-support buffer. @@ -26,5 +30,6 @@ A **Creative Gas Tank** variant supplies endless Oxygen — see [Creative Source Blocks](Creative-Source-Blocks). ## Details + - ID: `nerospace:gas_tank` · Tool: pickaxe, iron tier · Drops: itself - Config: `gasTankCapacity` diff --git a/wiki/Glacite-Ore.md b/wiki/Glacite-Ore.md index d147e74..d58ff32 100644 --- a/wiki/Glacite-Ore.md +++ b/wiki/Glacite-Ore.md @@ -3,17 +3,20 @@ The signature crystal of the frozen moon **Glacira**. ## Overview + Glacite is a pale, ice-blue gem found only on Glacira — the Tier 4 destination. It closes the loop on the late game: it hardens the **Cryo Suit** against Glacira's cold and feeds the **water stage** of deeper terraforming. ## Obtaining + - **Mining:** requires an **iron-tier pickaxe** or better. Drops the **Glacite** gem directly (Fortune-affected). - **Generation:** spawns in the **Glacira** dimension, in a band roughly **y −48 to y 48** — similar rarity to [Cindrite](Cindrite-Ore). ## Use + - Pack into a **[Block of Glacite](Block-of-Glacite)** for storage. - Craft the **[Cryo Suit](Oxygen-Suit)** (each piece = Tier 2 piece + 4 Glacite) — Glacira's cold quadruples oxygen drain without it. @@ -21,5 +24,6 @@ deeper terraforming. cycle that pushes a [Terraformer](Terraformer)'s land from *Rooted* to *Hydrated*. ## Details + - ID: `nerospace:glacite_ore` · Dimension: Glacira - Tool: pickaxe, iron tier · Drops: Glacite (gem) diff --git a/wiki/Home.md b/wiki/Home.md index b3de881..be41332 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -18,6 +18,8 @@ eventually **terraform** a dead planet into livable, rained-on ground. Station; progress to **Nerosteel** and **Xertz Quartz** on Greenxertz. 4. Pack an **[Oxygen Suit](Oxygen-Suit)** and set up **Oxygen Generators** — every world out there is airless. Bring the **Thermal**/**Cryo** variants for Cindara and Glacira. + - Automate the digging with a **[Quarry Controller](Quarry-Controller)**: mark an area with + landmarks and it strip-mines the whole rectangle to bedrock on its own. 5. Late game: build the **[Heavy Launch Complex](Launch-Gantry)**, found stations with a **[Station Charter](Station-Charter)**, and run a **[Terraformer](Terraformer)** until a dead planet grows, rains, and fills with livestock. diff --git a/wiki/Hydration-Module.md b/wiki/Hydration-Module.md index 25eda75..e2044e9 100644 --- a/wiki/Hydration-Module.md +++ b/wiki/Hydration-Module.md @@ -4,21 +4,26 @@ The glacite intake of deeper terraforming: melts glacite into **hydration units* [Terraformer](Terraformer)'s **Hydrated** stage (water). ## Overview + Deeper terraforming advances in stages — Rooted → Hydrated → Living. The Hydrated stage fills basins below the Terraformer's water table with real water, and every water source placed costs **1 hydration unit**. The Hydration Module is where those units come from: it melts glacite (crystallised water-ice mined on [Glacira](Home)) into the Terraformer's hydration buffer. ## Obtaining + **Craft** (shaped): -``` + +```text G N G N F N G N G ``` + `G` = Glacite · `N` = Nerosteel Ingot · `F` = [Fluid Tank](Fluid-Tank) ## How it works + - **Must touch the Terraformer:** place it directly against any face of a Terraformer block. A module with even a one-block gap feeds nothing. - **Feed it glacite:** drop glacite (16 units each) or Blocks of Glacite (144 units) into its input @@ -29,6 +34,7 @@ G N G [Terraform Monitor](Terraform-Monitor)) shows "Needs glacite" while stage 2 waits. ## Details + - ID: `nerospace:hydration_module` · Tool: pickaxe, iron tier · Drops: itself - Input items are tag-driven (`nerospace:hydration_input`) — glacite-only by default; modpacks can widen it (vanilla ice is deliberately excluded so the Glacira trip stays meaningful). diff --git a/wiki/Item-Store.md b/wiki/Item-Store.md index 0275ca2..ff05364 100644 --- a/wiki/Item-Store.md +++ b/wiki/Item-Store.md @@ -3,15 +3,19 @@ A nerosteel-reinforced 27-slot container built for pipe automation. ## Obtaining + **Craft** (shaped): a nerosteel frame around a Chest — -``` + +```text N N N N C N N N N ``` + `N` = Nerosteel Ingot · `C` = Chest ## How it works + - **27 slots**, chest-style GUI on right-click. - Universal Pipes (item layer) and hoppers can insert/extract on **every side** — unlike a chest, nothing blocks the top face. @@ -21,4 +25,5 @@ A **Creative Item Store** variant supplies an endless stream of one configured i [Creative Source Blocks](Creative-Source-Blocks). ## Details + - ID: `nerospace:item_store` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Items.md b/wiki/Items.md index 9ddfa3c..cc8dc0a 100644 --- a/wiki/Items.md +++ b/wiki/Items.md @@ -3,6 +3,7 @@ A reference for Nerospace's non-block items. (Blocks have their own pages — see the sidebar.) ## Materials + | Item | Notes | |---|---| | **Raw Nerosium** | Mined from [Nerosium Ore](Nerosium-Ore); smelt/blast into a Nerosium Ingot. | @@ -15,6 +16,7 @@ A reference for Nerospace's non-block items. (Blocks have their own pages — se | **Glacite** | Gem from [Glacite Ore](Glacite-Ore) on Glacira; [Cryo Suit](Oxygen-Suit) pieces and the [Hydration Module](Hydration-Module)'s water cycle. | ## Tools + - **Nerosium Pickaxe** — iron-tier mining, higher durability and a small attack bonus. Craft: 3 Nerosium Ingots over two sticks (standard pickaxe pattern). - **[Configurator](Configurator)** — the pipe-network tool: per-face × per-layer I/O modes, with a @@ -23,6 +25,7 @@ A reference for Nerospace's non-block items. (Blocks have their own pages — se [Pipe Filters and Upgrades](Pipe-Filters-and-Upgrades). ## Oxygen Suit + A four-piece nerosteel armour set — **Helmet, Chestplate, Leggings, Boots** — that doubles as personal life support. Wearing the **full set** lets you breathe off-world away from a generator/pad; it draws from a finite air tank that refills in any breathable zone (so it greatly extends your air rather than @@ -39,6 +42,7 @@ Two **hazard variants** of the Tier 2 suit shield you from planetary extremes (u engage. See **[Oxygen Suit](Oxygen-Suit)** for the full page. ## Rockets & Fuel + - **Rocket Fuel Canister** — a stackable fuel unit. Craft (shapeless): Blaze Powder + Coal + Iron Ingot → 2 canisters. Used to fuel rockets/machines and as a crafting core. (Reachable before your first launch — no off-world materials needed.) @@ -60,20 +64,24 @@ engage. See **[Oxygen Suit](Oxygen-Suit)** for the full page. **FOUND** node to found your own named orbital station. ## Star Guide + - **Star Guide Book** — Book + Raw Nerosium (shapeless). Right-click to open the **[Star Guide](Star-Guide)**, the interactive 7-chapter progression tree. - **[Star Guide](Star-Guide)** (pedestal block) — holds the book, projects a next-goal hologram. ## Food & creature drops + - **Loper Haunch** — dropped by the Meadow Loper; hearty food. - **Strutter Drumstick** — dropped by the Ember Strutter; food. - **Drift Fleece** — dropped by the Woolly Drift; crafts into 4 String. See **[Creatures](Creatures)** for the livestock themselves. ## Travel devices (creative) + - **Greenxertz Navigator** and the **Station / Greenxertz / Cindara / Glacira Compasses** are creative-only one-click travel aids for testing and building (no survival recipe — rockets are the survival route). ## Spawn eggs + Creative spawn eggs exist for all eight creatures — see **[Creatures](Creatures)**. diff --git a/wiki/Launch-Gantry.md b/wiki/Launch-Gantry.md index 7ebcb92..5ad7b15 100644 --- a/wiki/Launch-Gantry.md +++ b/wiki/Launch-Gantry.md @@ -3,21 +3,26 @@ The boarding tower that turns a 5×5 pad into the **Heavy Launch Complex**. ## Overview + The Launch Gantry is the heavy-launch module: place at least one next to a complete, aligned **5×5 [Rocket Launch Pad](Rocket-Launch-Pad)** (at pad level) and the cluster forms a **Heavy Launch Complex** — the departure point for the biggest rockets, and a comfortable boarding ramp for all of them. ## Obtaining + **Craft** (shaped): -``` + +```text N . N N I N N S N ``` + `N` = Nerosteel Ingot · `I` = Iron Bars · `S` = [Station Wall](Station-Wall) ## How it works + - **Forming the complex:** a full, aligned **5×5 pad** plus **≥1 Launch Gantry** adjacent at pad level = Heavy Launch Complex. Empty-hand right-click any pad block for a **formation report** (cluster size, largest square, gantry/fuel modules, and the next missing piece). @@ -33,4 +38,5 @@ N S N base rate) — a Tier 4's 24,000 mB tank fills in under a minute. ## Details + - ID: `nerospace:launch_gantry` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Nerosium-Grinder.md b/wiki/Nerosium-Grinder.md index 943219f..58d46ae 100644 --- a/wiki/Nerosium-Grinder.md +++ b/wiki/Nerosium-Grinder.md @@ -3,19 +3,24 @@ Your first machine — grinds ore into dust to double your metal yield. ## Overview + The Nerosium Grinder processes nerosium inputs into **Nerosium Dust** over time. Since each dust smelts back into a Nerosium Ingot, grinding ore before smelting **doubles** your ingot output. ## Obtaining + **Craft** (shaped): -``` + +```text I I I I F I C C C ``` + `I` = Nerosium Ingot · `F` = Furnace · `C` = Cobblestone ## How it works + - **Two slots:** an **input** (top/sides) and an **output** (bottom). - **Grinding recipes:** - Nerosium Ore / Deepslate Nerosium Ore / Raw Nerosium → **2 Nerosium Dust** @@ -27,8 +32,10 @@ C C C - **GUI:** shows a power gauge and grind-progress; emits a **comparator signal** from its energy level. ## Tips + Feed it raw nerosium or ore, pipe the dust into a furnace array, and you get 2 ingots per ore instead of 1. ## Details + - ID: `nerospace:nerosium_grinder` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Nerosium-Ore.md b/wiki/Nerosium-Ore.md index faf0bae..b560074 100644 --- a/wiki/Nerosium-Ore.md +++ b/wiki/Nerosium-Ore.md @@ -3,19 +3,23 @@ The mod's starting material — a red/purple cosmic crystal embedded in stone. ## Overview + Nerosium is the first Nerospace material and the gateway to its tech tree. **Nerosium Ore** is the stone-height variant; the [Deepslate Nerosium Ore](Deepslate-Nerosium-Ore) variant appears deeper. ## Obtaining + - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosium** (affected by Fortune). - **Generation:** spawns in **all Overworld biomes** in the underground-ores step, in a triangular height band roughly **y −24 to y 56** (peaking around y 16), at a similar rate to iron. ## Use + - Smelt or blast **Raw Nerosium** (or the ore itself) into a **Nerosium Ingot**. - For more yield, run it through a **[Nerosium Grinder](Nerosium-Grinder)**: one ore grinds into **2 Nerosium Dust**, and each dust smelts into an ingot — effectively doubling your output. ## Details + - ID: `nerospace:nerosium_ore` - Tool: pickaxe, iron tier · Drops: Raw Nerosium diff --git a/wiki/Nerosteel-Ore.md b/wiki/Nerosteel-Ore.md index 457c448..bda89b4 100644 --- a/wiki/Nerosteel-Ore.md +++ b/wiki/Nerosteel-Ore.md @@ -3,12 +3,14 @@ The primary metal of the **Greenxertz** planet. ## Overview + Nerosteel is an iron-class metal and the backbone of mid-game crafting — rockets, the Oxygen Suit, machines and station blocks are all built from **Nerosteel Ingots**. **Greenxertz** is its home and its abundant source, but a small, deep seam also occurs on the **Overworld** so the metal that gates your first rocket is reachable before you ever leave home. ## Obtaining + - **Mining:** requires an **iron-tier pickaxe** or better. Drops **Raw Nerosteel** (Fortune-affected). - **Generation:** - **Overworld:** a **rare, deep** seam — roughly **y −56 to y 16** (peaking below sea level), about a @@ -17,11 +19,13 @@ your first rocket is reachable before you ever leave home. the place to mine it in bulk once you arrive. ## Use + - Smelt or blast **Raw Nerosteel** (or the ore) into a **Nerosteel Ingot**. - Nerosteel Ingots craft the [Rocket Launch Pad](Rocket-Launch-Pad), [Fuel Tank](Fuel-Tank), [Oxygen Generator](Oxygen-Generator), [Station Floor](Station-Floor)/[Wall](Station-Wall), the Oxygen Suit, and the rockets themselves. ## Details + - ID: `nerospace:nerosteel_ore` · Dimensions: Overworld (rare, deep) + Greenxertz (common) - Tool: pickaxe, iron tier · Drops: Raw Nerosteel diff --git a/wiki/Oxygen-Generator.md b/wiki/Oxygen-Generator.md index 9936d7f..d43aeb1 100644 --- a/wiki/Oxygen-Generator.md +++ b/wiki/Oxygen-Generator.md @@ -3,21 +3,26 @@ Keeps you alive off-world by turning grid power into Oxygen gas — and pressurising the air around it. ## Overview + Every Nerospace dimension is **airless** — exposed players lose oxygen and suffocate. The Oxygen Generator is an **electrolysis machine**: it consumes energy from the pipe network and produces **Oxygen gas** into an internal tank. That tank both feeds the breathable field around the machine and can be **piped out** through Universal Pipes (gas layer) into Gas Tanks or other rooms. ## Obtaining + **Craft** (shaped): -``` + +```text N G N R C R N N N ``` + `N` = Nerosteel Ingot · `G` = Glass · `R` = Redstone · `C` = Rocket Fuel Canister ## How it works + - **Power in:** connect a Universal Pipe carrying energy (from a Combustion/Passive Generator or a Battery). The machine stores up to 10,000 FE and cannot burn fuel directly — it is grid-only. - **Oxygen out:** while powered it produces up to 5 mB of Oxygen per tick (2 FE per mB) into an @@ -38,11 +43,13 @@ N N N - **Automation:** emits a **comparator signal** from its oxygen tank level. ## Tips + Build an airtight room, run a power line to a generator inside it, and the room stays breathable as long as the grid is up. Pipe surplus oxygen into a **Gas Tank** as a buffer for power outages. A full **Oxygen Suit** is the portable alternative for exploring away from a generator. ## Details + - ID: `nerospace:oxygen_generator` · Tool: pickaxe, iron tier · Drops: itself - GUI: power gauge + oxygen tank gauge with a Producing/No-power status - Config: `oxygenBubbleRadius`, `oxygenLeakRange`, `oxygenEvaporateSeconds`, `oxygenBreathableThreshold`, … diff --git a/wiki/Oxygen-Suit.md b/wiki/Oxygen-Suit.md index 8105062..b2f635c 100644 --- a/wiki/Oxygen-Suit.md +++ b/wiki/Oxygen-Suit.md @@ -4,6 +4,7 @@ Four-piece armour that doubles as personal life support on airless worlds. Two t Tier-2-class **hazard variants** for the extreme worlds. ## Overview + Wearing the **full set** (all four pieces) keeps you breathing off-world by draining a finite air tank instead of suffocating. The tank refills instantly in any breathable zone (oxygen field, terraformed ground, launch-pad safe zone) — and, since the suit-and-station integration, also at @@ -38,26 +39,31 @@ The shield engages only when **all four pieces match** the hazard; the HUD badge other Tier 2 pieces keeps Tier-2 capacity — you just lose the hazard shield. ## Progression note + Cindrite (Thermal) is mined on Cindara itself, so the usual loop is: dash in a plain Tier 2 suit, mine your first cindrite, then craft the Thermal Suit for serious expeditions. The same applies to glacite and the Cryo Suit on Glacira. ## Airlock refill + Within a few blocks (default **3**, `oxygenAirlockRadius`) of a [Gas Tank](Gas-Tank) or [Oxygen Generator](Oxygen-Generator) **holding Oxygen**, a worn suit refills its air from that store — each air unit drains gas (default **5 mB**, `oxygenAirlockMbPerAir`). A Gas Tank at the base entrance therefore acts as an airlock: step inside, top up, head back out. The Creative Gas Tank works too. ## Obtaining + **Tier 1** (shaped, nerosteel): helmet `NNN / NGN` (G = Glass visor), chestplate `N N / NCN / NNN` (C = Rocket Fuel Canister life-support core), leggings and boots all-nerosteel. **Tier 2** (shaped): each Tier 1 piece surrounded by 4 **Cindrite** — -``` + +```text . C . C P C . C . ``` + `P` = the matching Tier 1 piece · `C` = Cindrite Cindrite only mines on **Cindara** (Tier 3 rocket, itself gated on @@ -65,6 +71,7 @@ Cindrite only mines on **Cindara** (Tier 3 rocket, itself gated on rocket. ## Details + - IDs: `nerospace:oxygen_suit_{helmet,chestplate,leggings,boots}`, `nerospace:oxygen_suit_t2_{...}`, `nerospace:oxygen_suit_heat_{...}` (Thermal), `nerospace:oxygen_suit_cold_{...}` (Cryo) diff --git a/wiki/Passive-Generator.md b/wiki/Passive-Generator.md index 5aa3902..84afb63 100644 --- a/wiki/Passive-Generator.md +++ b/wiki/Passive-Generator.md @@ -3,20 +3,25 @@ A slow, hands-off trickle of energy from a nerosium core. ## Obtaining + **Craft** (shaped): a nerosteel frame around a Block of Nerosium, wired with Redstone — -``` + +```text N N N N B N N R N ``` + `N` = Nerosteel Ingot · `B` = Block of Nerosium · `R` = Redstone ## How it works + - **Core slot** (GUI, hand or hopper/pipe fed): raw nerosium, a nerosium ingot, or nerosium dust. - Each core runs for **20 minutes**, trickling **10 FE/t** (configurable) into a 20,000 FE buffer. - Extract-only buffer — the pipe network pulls the power out. - Weaker than the Combustion Generator but ideal as set-and-forget base power. ## Details + - ID: `nerospace:passive_generator` · Tool: pickaxe, iron tier · Drops: itself - Config: `passiveGeneratorFePerTick` diff --git a/wiki/Pipe-Filters-and-Upgrades.md b/wiki/Pipe-Filters-and-Upgrades.md index 1d328ba..bbe29de 100644 --- a/wiki/Pipe-Filters-and-Upgrades.md +++ b/wiki/Pipe-Filters-and-Upgrades.md @@ -3,6 +3,7 @@ Fine-tune what flows where, and how fast. ## Pipe Filter + Restricts a pipe face's **item layer** to a single item. - **Craft** (shaped, yields 4): nerosteel corners around Iron Bars. @@ -15,16 +16,19 @@ Filters affect extraction (pulling faces only grab the matching item), routing ( toward a filtered face that rejects them) and delivery. ## Speed Upgrade + - **Craft** (shaped): redstone + gold core in a nerosteel frame. - **Install:** right-click a pipe segment (consumed, up to **3** per pipe). - Each one multiplies the segment's energy/fluid/gas **throughput** and makes items **travel faster** through it. ## Capacity Upgrade + - **Craft** (shaped): xertz quartz + chest core in a nerosteel frame. - **Install:** right-click a pipe segment (up to **3**). - Each one multiplies the segment's fluid/gas **buffers** and how many item stacks may be **in transit** at once. ## Removing upgrades + Sneak-right-click the pipe with an **empty hand** — all installed upgrades pop back out. diff --git a/wiki/Quarry-Controller.md b/wiki/Quarry-Controller.md new file mode 100644 index 0000000..a63b588 --- /dev/null +++ b/wiki/Quarry-Controller.md @@ -0,0 +1,107 @@ +# Quarry Controller + +A BuildCraft-style automated miner: mark out an area with landmarks and the controller builds a +glowing frame and excavates the whole rectangle, layer by layer, down to bedrock. + +## Overview + +The Quarry Controller is the brain of the miner. You mark a rectangular area with **[Quarry +Landmarks](Quarry-Landmark)**, place the controller beside it, give it **frame material** and +**power**, and it: + +1. **Builds a frame** — a glowing, see-through structural ring around the claimed rectangle. +2. **Mines** the whole rectangle **layer by layer**, top to bedrock, like a 3D printer in reverse — + a drill head travels the gantry to each block. +3. **Buffers and auto-ejects** everything it digs: mined items into an internal inventory, and any + liquids it hits into an internal fluid tank — both push out to adjacent storage / pipes. + +It never destroys what it can't store: if its buffers fill or the power runs out, it **pauses** (the +GUI tells you why) and resumes on its own once you fix it. + +## Obtaining + +**Craft** (shaped): + +```text +I D I +F R F +I I I +``` + +`I` = [Nerosteel Ingot](Items) · `D` = Diamond · `F` = [Frame Casing](Upgrade-Modules) · +`R` = Block of Redstone + +> Tier 1 is the craftable version today. Tier 2 / Tier 3 controllers (bigger areas, more module +> slots, harsh-planet access) are planned — see [Tiers & planets](#tiers--planets). + +## Setting it up + +1. **Place 3 [Quarry Landmarks](Quarry-Landmark) in an L** at the **same Y level** to define the + corners of the rectangle. The longest side must fit the tier's cap (**Tier 1 = 16×16**). +2. **Place the controller next to / in line with** a landmark — it scans along the axes to find the + cluster, then **consumes the landmarks** and starts. +3. **Put [Frame Casing](Upgrade-Modules) in the frame slot** (top-left of the GUI). The frame costs + **one casing per open-air perimeter cell** (cells already backed by terrain are free). +4. **Pipe power in** (see below). Building the frame is free, but **mining needs energy**. + +## How it works + +- **Power:** an internal **200,000 FE** buffer, filled through the energy capability on any side — + connect a [Universal Pipe](Universal-Pipe) from a [Combustion](Combustion-Generator)/[Passive + Generator](Passive-Generator) or a [Battery](Battery). **Dig speed scales with the power you + supply**, up to the tier's per-tick ceiling × your modules' speed bonus × the planet's speed + factor. Base cost is **40 FE per block** (lowered by Efficiency modules). +- **Output buffer:** 12 internal slots for mined items; **auto-ejects** into an adjacent inventory / + pipe. Mining **pauses** ("buffer full") when it can't fit a drop — nothing is ever voided. +- **Fluid buffer:** source liquids (water, lava) in the dig area are **sucked up** into a + **16,000 mB** internal tank that auto-ejects to an adjacent [Fluid Tank](Fluid-Tank) / pipe. +- **Obstacles:** it **skips** bedrock and other unbreakable blocks, and **skips the entire column** + under a tile-entity (chests, spawners, machines) so they're left intact. It also skips other + quarries' frames. +- **Drill head + gantry:** while it runs, a glowing gantry traces the frame and a **drill head** + moves to the exact block being mined, with a vertical beam down the shaft. +- **Far edges:** a large area is force-loaded a chunk at a time while actively mining, so the dig + keeps going as you range its edges; the tickets are released when it finishes or is removed. +- **Reclaiming:** breaking the controller tears down its frame. + +### GUI status lines + +| Line | Meaning | +|---|---| +| **Idle — place landmarks** | No valid region found yet. | +| **Building frame** | Placing the frame ring (consuming casings). | +| **Mining** | Digging — `Depth` shows how many layers below the frame plane it has reached. | +| **Paused** | Stopped — out of casings, out of power, buffers full, or wrong planet for this tier. | +| **Finished** | Reached bedrock; frame stays until you break the controller. | + +## Tiers & planets + +The miner runs **anywhere it has power**, but the harsh outer moons are gated by tier: + +| Tier | Max area | Module slots | Base speed | Planets it can mine | +|---|---|---|---|---| +| **Tier 1** | 16 × 16 | 1 | 2 blocks/cycle | Overworld, Greenxertz, Orbital Station | +| Tier 2 *(planned)* | 32 × 32 | 2 | 4 blocks/cycle | + **Cindara** | +| Tier 3 *(planned)* | 64 × 64 | 4 | 8 blocks/cycle | + **Glacira** | + +Mining **speed/yield also varies per planet** (the dense outer moons mine a little slower). A +too-low tier on a gated planet pauses with "wrong planet". + +## Upgrades & the future + +- **[Upgrade Modules](Upgrade-Modules)** (Speed / Efficiency / Fortune / Silk Touch) slot into the + controller and tune its behaviour. They're a **cross-machine** system — the same cards will work + in other machines. +- **Filters** (whitelist-keep, void the rest — e.g. trash cobble) are a planned follow-up; the + output pipeline is already built to drop them in. + +## Details + +- ID: `nerospace:quarry_controller` · Tool: pickaxe, iron tier · Drops: itself +- Companion blocks: [Quarry Landmark](Quarry-Landmark), Quarry Frame (machine-placed; no item, + drops nothing) +- Capabilities: energy **in** (any side); mined **items out** (any side); **fluid out** (any side). + The frame-casing and module slots are configuration-only — they can't be piped in or out, so + automation can never pull your modules or casings (load casings by hand in the GUI) +- Config: scales with the standard `energyRateMultiplier`, `fuelCostMultiplier`, and + `machineSpeedMultiplier` (see [Configuration](Configuration)) diff --git a/wiki/Quarry-Landmark.md b/wiki/Quarry-Landmark.md new file mode 100644 index 0000000..bd7daf2 --- /dev/null +++ b/wiki/Quarry-Landmark.md @@ -0,0 +1,39 @@ +# Quarry Landmark + +A corner marker that defines the area a [Quarry Controller](Quarry-Controller) will mine. + +## Overview + +Landmarks are how you draw a quarry's footprint. Each one projects animated **marker lasers** along +the horizontal axes; place **three in an L** and the controller reads them as the corners of a +rectangle. They're consumed when the quarry activates. + +## Obtaining + +**Craft** (shaped, makes 3): + +```text +R +G +I +``` + +`R` = Redstone · `G` = Glass · `I` = [Nerosteel Ingot](Items) + +## How it works + +- **Three landmarks = a box.** Two landmarks "link" when they share a row or column at the same Y + within range; an L of three gives all four extents of the rectangle. +- **Same Y level.** Place all three at the same height — that height becomes the quarry's reference + plane (the frame is built there; mining runs from just below it down to bedrock). +- **Within the tier's cap.** The rectangle's longest side must fit the controller's area cap + (Tier 1 = 16). An oversized or degenerate layout makes the controller pause with "bad region". +- **Binding.** Place the [Quarry Controller](Quarry-Controller) next to / in line with a landmark. + On activation it scans the cluster, **removes the landmark blocks**, and builds the frame. +- **Cosmetic only otherwise** — the lasers are a client-side effect; landmarks have no inventory or + power. + +## Details + +- ID: `nerospace:quarry_landmark` · Tool: pickaxe · Drops: itself +- See [Quarry Controller](Quarry-Controller) for the full mining setup. diff --git a/wiki/Roadmap.md b/wiki/Roadmap.md index 698bdd1..e74ae8c 100644 --- a/wiki/Roadmap.md +++ b/wiki/Roadmap.md @@ -38,8 +38,7 @@ release** — the complete progression from the first nerosium ore to a terrafor - The **Star Guide** interactive progression tree (7 chapters / 31 steps) backed by a full advancement tree; the creative `/nerospace gallery` showcase; a 36+-test gametest suite. - -**JEI integration** +**JEI integration** - With JEI installed, the grinder, fuel refinery and combustion generator show their own recipe categories (standard recipes/tags already worked out of the box). ## 🛠️ Next up (first post-1.0 updates) diff --git a/wiki/Rocket-Launch-Pad.md b/wiki/Rocket-Launch-Pad.md index 0675a9a..fbd6587 100644 --- a/wiki/Rocket-Launch-Pad.md +++ b/wiki/Rocket-Launch-Pad.md @@ -3,20 +3,25 @@ The mount a rocket is deployed onto before launch — and a safe landing zone. ## Overview + Deploy a **Rocket** (right-click a launch pad with a rocket item) and it appears on the pad, ready to fuel and fly. The pad also acts as a small **breathable safe zone** so arrivals aren't immediately punished by the airless atmosphere. ## Obtaining + **Craft** (shaped): -``` + +```text N N N N B N N N N ``` + `N` = Nerosteel Ingot · `B` = [Block of Nerosium](Block-of-Nerosium) ## How it works + - **Deploy:** right-click the pad with a rocket item to place the rocket entity on it. **A complete, aligned 3×3 pad is required** — deploying on a partial pad shows a clear message instead. The same check re-runs at launch, so breaking pad blocks under a deployed rocket grounds it. @@ -39,4 +44,5 @@ N N N land. Build an [Oxygen Generator](Oxygen-Generator) base for anything beyond the landing area. ## Details + - ID: `nerospace:rocket_launch_pad` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Star-Guide.md b/wiki/Star-Guide.md index 420c152..5ba7c66 100644 --- a/wiki/Star-Guide.md +++ b/wiki/Star-Guide.md @@ -3,23 +3,28 @@ The in-game progression guide — a pedestal, a book, and a live quest tree. ## Overview + The Star Guide is Nerospace's built-in tutorial and progression tracker: an interactive tree of **7 chapters and 31 steps**, from your first nerosium ore to a fully matured terraformed world. Completion is tracked per player through the mod's **advancement tree**, so the guide is always live — finish something anywhere and the tree lights up. ## Obtaining + **Star Guide Book** (shapeless): **Book + Raw Nerosium** — available minutes into a playthrough. **Star Guide** pedestal (shaped): -``` + +```text . R . . P . S S S ``` + `R` = Raw Nerosium · `P` = any Planks · `S` = Cobblestone ## How it works + - **Read anywhere:** right-click with the **Star Guide Book** in hand to open the guide GUI. - **Pedestal:** right-click the empty pedestal with the book to install it (lectern-style; breaking the pedestal or sneak-right-clicking pops the book back out). An installed pedestal projects a @@ -30,4 +35,5 @@ S S S - **Comparator:** the pedestal emits a signal when a book is installed. ## Details + - IDs: `nerospace:star_guide` (block) · `nerospace:star_guide_book` (item) diff --git a/wiki/Station-Charter.md b/wiki/Station-Charter.md index 8c89966..10c0ef1 100644 --- a/wiki/Station-Charter.md +++ b/wiki/Station-Charter.md @@ -3,6 +3,7 @@ Found your own orbital station — name included. ## Overview + The Station Charter is how players create **additional stations** beyond the shared [Orbital Station](Home): carry one aboard a rocket, pick the **FOUND** node in the rocket UI, and launch. The charter is consumed and a brand-new station platform is built in orbit, anchored by a @@ -10,15 +11,19 @@ launch. The charter is consumed and a brand-new station platform is built in orb for every player and every rocket tier. ## Obtaining + **Craft** (shaped): -``` + +```text W W W W F W W W W ``` + `W` = [Station Wall](Station-Wall) · `F` = [Station Floor](Station-Floor) ## How it works + - **Naming:** rename the charter in an **anvil** before flying — that becomes the station's name. An unnamed charter founds "Station N". - **Founding:** board any rocket with the charter in your inventory; the rocket UI shows the @@ -32,4 +37,5 @@ W W W station. ## Details + - ID: `nerospace:station_charter` diff --git a/wiki/Station-Core.md b/wiki/Station-Core.md index 00d5260..0cb878d 100644 --- a/wiki/Station-Core.md +++ b/wiki/Station-Core.md @@ -3,16 +3,19 @@ The anchor block of a player-founded station. ## Overview + Every station founded with a **[Station Charter](Station-Charter)** is built around a Station Core. It marks the station's registration: as long as the Core stands, the station is listed as a rocket destination. ## Obtaining + **Not craftable.** A Station Core is placed automatically by the founding flow (FOUND node in the rocket UI). Breaking one pops the installed **Station Charter** back out — name preserved — so a station can be dissolved and re-founded elsewhere. ## How it works + - **Anchor:** the Core binds the platform to its registry slot; it survives restarts and chunk unloads. - **Unregister:** break the Core and the station disappears from every rocket's destination list @@ -21,4 +24,5 @@ station can be dissolved and re-founded elsewhere. station. ## Details + - ID: `nerospace:station_core` · Dimension: the station dimension diff --git a/wiki/Station-Floor.md b/wiki/Station-Floor.md index 41c4f93..2950ff0 100644 --- a/wiki/Station-Floor.md +++ b/wiki/Station-Floor.md @@ -3,14 +3,17 @@ Metal platform plating for building out the Orbital Station. ## Overview + The **Orbital Station** arrival platform is made of Station Floor, and you craft more to expand your base in orbit (and anywhere else). A sturdy, blast-resistant deco/building block. ## Obtaining + - **Craft:** a ring of **8 Nerosteel Ingots** around an empty centre → **8 Station Floor**. - The station's starter platform is generated automatically on first arrival (a single shared platform — see [Rocket Launch Pad](Rocket-Launch-Pad) / Roadmap). ## Details + - ID: `nerospace:station_floor` - Tool: pickaxe, iron tier · Drops: itself · High blast resistance diff --git a/wiki/Station-Wall.md b/wiki/Station-Wall.md index a2ba678..48f9fcb 100644 --- a/wiki/Station-Wall.md +++ b/wiki/Station-Wall.md @@ -3,14 +3,17 @@ Hull panelling for the Orbital Station — and a Tier-3 progression gate. ## Overview + Station Wall is the station's wall/hull block. Because it is crafted from station-grade materials, it also **gates the Tier 3 rocket**: you must reach the Orbital Station (Tier 1) and build station hull before you can craft a Tier 3 rocket. ## Obtaining + - **Craft:** a ring of **8 Nerosteel Ingots** around an **Iron Ingot** core → **8 Station Wall**. ## Use + - Building/sealing pressurised rooms — like all full opaque blocks, it is **airtight** and counts as an oxygen seal (see [Oxygen Generator](Oxygen-Generator)). - Required in the **Tier 3 Rocket** recipe. @@ -20,5 +23,6 @@ hull before you can craft a Tier 3 rocket. **[Launch Gantry](Launch-Gantry)**. ## Details + - ID: `nerospace:station_wall` - Tool: pickaxe, iron tier · Drops: itself · High blast resistance diff --git a/wiki/Terraform-Monitor.md b/wiki/Terraform-Monitor.md index c3f8df9..5c30676 100644 --- a/wiki/Terraform-Monitor.md +++ b/wiki/Terraform-Monitor.md @@ -5,15 +5,19 @@ radii, hydration buffer and stall reason, and reports the **local ground's terra comparator. ## Obtaining + **Craft** (shaped): -``` + +```text N G N Q R Q N N N ``` + `N` = Nerosteel Ingot · `G` = Glass · `Q` = Xertz Quartz · `R` = Redstone ## How it works + - **Place it anywhere** on or near terraformed land. It links to the nearest Terraformer within **32 blocks** automatically (no wiring). - **GUI readout:** the local column's stage (Dead / Rooted / Hydrated / Living), the linked @@ -23,4 +27,5 @@ N N N ranch gates when the land turns Living, or alarm when terraforming reaches your base. ## Details + - ID: `nerospace:terraform_monitor` · Tool: pickaxe, iron tier · Drops: itself diff --git a/wiki/Terraformer.md b/wiki/Terraformer.md index d88873c..da47351 100644 --- a/wiki/Terraformer.md +++ b/wiki/Terraformer.md @@ -4,6 +4,7 @@ The end-game machine: slowly converts a dead planet into livable, breathable, ** three visible stages. ## Overview + Place a Terraformer on a barren world, power it, and it advances an **ever-expanding circular frontier** outward from itself. With deeper terraforming the machine runs **three frontiers**, each trailing the last, so a long-running world is always a gradient — raw chemistry at the edge, a living ecosystem at @@ -16,15 +17,19 @@ the centre: | 3 | **Living** | the biome settles into a natural **per-planet palette** (meadow / savanna / tundra) with grown trees, **rain or snow**, and starter herds of the planet's [livestock](Creatures) | ## Obtaining + **Craft** (shaped): -``` + +```text N D N D O D N B N ``` + `N` = Nerosteel Ingot · `D` = Dirt · `O` = [Oxygen Generator](Oxygen-Generator) · `B` = Block of Nerosteel ## How it works + - **Grid power:** runs exclusively on piped energy — connect a Universal Pipe carrying FE. Its 100,000 FE buffer drains as it works (higher tiers / more power = faster). - **Expanding frontiers:** each work cycle it converts a ring of surface columns per stage, then grows @@ -50,6 +55,7 @@ N B N new stages simply sweep over it once you build the new blocks. No migration. ## Details + - ID: `nerospace:terraformer` · Tool: pickaxe, iron tier · Drops: itself - Companions: [Hydration Module](Hydration-Module) (glacite intake, must touch), [Terraform Monitor](Terraform-Monitor) (stage readout + comparator) diff --git a/wiki/Trash-Can.md b/wiki/Trash-Can.md new file mode 100644 index 0000000..e9915ac --- /dev/null +++ b/wiki/Trash-Can.md @@ -0,0 +1,42 @@ +# Trash Can + +A bottomless sink for unwanted items, fluids, and gas. + +## Overview + +Pipe or hopper anything into the Trash Can and it is **destroyed**. It accepts all three transfer +layers — **items, fluids, and gas** — from any side, with no extraction surface, so nothing can ever +be pulled back out. Handy for dumping the cobble and dirt a [Quarry Controller](Quarry-Controller) +digs up (until item filters arrive), venting excess gas, or draining a fluid line. + +## Obtaining + +**Craft** (shaped): + +```text +I I I +I C I +I I I +``` + +`I` = Iron Ingot · `C` = Cactus + +## How it works + +- **Voids every layer:** exposes the item, fluid, and gas capabilities on **all six faces**; whatever + is inserted is discarded. +- **Never backs up:** its internal sinks are emptied every tick, so it always has room and never + rejects or returns anything. +- **Input only:** there is no way to extract from it — your modules, fuel, or anything else routed + past it stays safe; only what is explicitly piped *into* the Trash Can is lost. +- **No energy:** it does not accept power (energy isn't "trash"); only items, fluids, and gas. + +## Tips + +Point a [Universal Pipe](Universal-Pipe) face set to **OUT** at the Trash Can to dump a filtered +stream, or sit one under a quarry/machine output to auto-clear overflow. + +## Details + +- ID: `nerospace:trash_can` · Tool: pickaxe · Drops: itself +- Capabilities: items **in**, fluid **in**, gas **in** (all sides) — all discarded; no extraction diff --git a/wiki/Universal-Pipe.md b/wiki/Universal-Pipe.md index aea9561..a7c56fb 100644 --- a/wiki/Universal-Pipe.md +++ b/wiki/Universal-Pipe.md @@ -4,6 +4,7 @@ One pipe for everything: energy, fluids, gases and items flow through the same t the same time. ## Overview + The Universal Pipe is the backbone of Nerospace logistics. Placed pipes auto-connect to each other and to any machine, tank or inventory, forming a **network** that behaves as one shared system. All four resource layers ride the same connection graph simultaneously: @@ -16,15 +17,19 @@ resource layers ride the same connection graph simultaneously: | Items | — | travel as **visible packets** (~2 blocks/s), round-robin between destinations | ## Obtaining + **Craft** (shaped, yields 8): a nerosteel sheath around a glass core — -``` + +```text N N N N G N N N N ``` + `N` = Nerosteel Ingot · `G` = Glass ## How it works + - **Connections:** the tube grows an arm toward anything it can talk to (pipes, machines, tanks, chests). Every face has an independent I/O mode **per layer**: Auto → In → Out → Off (set with the [Configurator](Configurator)). @@ -38,10 +43,12 @@ N N N an empty hand pops installed upgrades out. ## Tips + One line can power a machine, feed it items and carry its outputs away at once — use face modes when a single line serves both a source and a sink (set the source-side face to In so nothing flows back). ## Details + - ID: `nerospace:universal_pipe` · Tool: pickaxe, iron tier · Drops: itself - Config: `energyPipeCapacity/Throughput`, `fluidPipe…`, `gasPipe…`, `itemPipeTicksPerBlock`, `itemPipeExtractAmount`, `itemPipeExtractPeriod` diff --git a/wiki/Upgrade-Modules.md b/wiki/Upgrade-Modules.md new file mode 100644 index 0000000..22acd74 --- /dev/null +++ b/wiki/Upgrade-Modules.md @@ -0,0 +1,55 @@ +# Upgrade Modules & Frame Casing + +The crafting parts and tuning cards for the [Quarry Controller](Quarry-Controller). The modules are a +**cross-machine** system — designed so other machines can use the same cards in the future. + +## Frame Casing + +The structural material the quarry spends to build its frame ring — **one casing per open-air +perimeter cell** (cells already backed by terrain are free). + +**Craft** (shaped, makes 4): + +```text +I I I +I I +I I I +``` + +`I` = [Nerosteel Ingot](Items) + +## Upgrade Modules + +Slot module cards into a machine's module slots to change how it works. A machine counts the modules +across its slots and sums their effects (you can stack several). The [Quarry Controller](Quarry-Controller) +has **1 module slot at Tier 1** (more at higher tiers). + +| Module | Effect | Notes | +|---|---|---| +| **Speed Module** | +50% to the work-cap per module | Lets the machine do more when fed more power; capped at ×8. | +| **Efficiency Module** | −15% energy cost per module | Floors at 25% of the base cost. | +| **Fortune Module** | Applies Fortune to mined blocks | Stacks up to Fortune III. | +| **Silk Touch Module** | Mines blocks with Silk Touch | **Overrides** Fortune when present. | + +**Craft** — every module shares one frame with a signature centre item: + +```text + N +R S R + N +``` + +`N` = [Nerosteel Ingot](Items) · `R` = Redstone · `S` = signature: + +| Module | Signature `S` | +|---|---| +| Speed | Sugar | +| Efficiency | Lapis Lazuli | +| Fortune | Diamond | +| Silk Touch | Amethyst Shard | + +## Details + +- IDs: `nerospace:frame_casing`, `nerospace:speed_module`, `nerospace:efficiency_module`, + `nerospace:fortune_module`, `nerospace:silk_touch_module` +- Used by: [Quarry Controller](Quarry-Controller) (more machines planned) diff --git a/wiki/Xertz-Quartz-Ore.md b/wiki/Xertz-Quartz-Ore.md index 3206878..5c8d14b 100644 --- a/wiki/Xertz-Quartz-Ore.md +++ b/wiki/Xertz-Quartz-Ore.md @@ -3,19 +3,23 @@ A pale-green crystal ore on **Greenxertz**, the mod's nether-quartz analogue. ## Overview + Xertz Quartz behaves like nether quartz: the ore drops the gem **directly** (no smelting required) and is plentiful. It is a key crafting reagent, most notably for **Rocket Fuel Canisters**. ## Obtaining + - **Mining:** any pickaxe works. Drops **Xertz Quartz** directly (Fortune-affected). The ore can also be smelted into Xertz Quartz if you prefer. - **Generation:** spawns abundantly across the **Greenxertz** dimension, uniformly between about **y 0 and y 110**. ## Use + - Craft **[Rocket Fuel Canisters](Items#rocket-fuel)** (with blaze powder, coal and iron). - A general crafting gem with room to grow in future updates. ## Details + - ID: `nerospace:xertz_quartz_ore` · Dimension: Greenxertz - Tool: any pickaxe · Drops: Xertz Quartz (gem) diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md index 7fdc989..51cb7f4 100644 --- a/wiki/_Sidebar.md +++ b/wiki/_Sidebar.md @@ -4,6 +4,7 @@ - [Star Guide](Star-Guide) **Blocks — Ores** + - [Nerosium Ore](Nerosium-Ore) - [Deepslate Nerosium Ore](Deepslate-Nerosium-Ore) - [Nerosteel Ore](Nerosteel-Ore) @@ -12,6 +13,7 @@ - [Glacite Ore](Glacite-Ore) **Blocks — Material Storage** + - [Block of Nerosium](Block-of-Nerosium) - [Block of Raw Nerosium](Block-of-Raw-Nerosium) - [Block of Nerosteel](Block-of-Nerosteel) @@ -19,6 +21,7 @@ - [Block of Glacite](Block-of-Glacite) **Blocks — Machines** + - [Nerosium Grinder](Nerosium-Grinder) - [Oxygen Generator](Oxygen-Generator) - [Fuel Tank](Fuel-Tank) @@ -27,7 +30,14 @@ - [Hydration Module](Hydration-Module) - [Terraform Monitor](Terraform-Monitor) +**Mining** + +- [Quarry Controller](Quarry-Controller) +- [Quarry Landmark](Quarry-Landmark) +- [Upgrade Modules](Upgrade-Modules) + **Logistics** + - [Universal Pipe](Universal-Pipe) - [Configurator](Configurator) - [Pipe Filters and Upgrades](Pipe-Filters-and-Upgrades) @@ -37,9 +47,11 @@ - [Fluid Tank](Fluid-Tank) - [Gas Tank](Gas-Tank) - [Item Store](Item-Store) +- [Trash Can](Trash-Can) - [Creative Source Blocks](Creative-Source-Blocks) **Blocks — Structures** + - [Rocket Launch Pad](Rocket-Launch-Pad) - [Launch Gantry](Launch-Gantry) - [Station Floor](Station-Floor) @@ -47,6 +59,7 @@ - [Station Core](Station-Core) **More** + - [Items](Items) - [Oxygen Suit](Oxygen-Suit) - [Station Charter](Station-Charter) From 9dba673090876622ae98e69719884d14b7084390 Mon Sep 17 00:00:00 2001 From: Dario Maselli <117168592+Dario-Maselli@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:04:08 +0800 Subject: [PATCH 2/5] Add 3D quarry frame and spinning drill head Replace the old translucent cube frame with a real 3-D open structural frame: new model elements and a dedicated registerQuarryFrame data-gen method, updated quarry_frame texture generator, and model JSON now uses a SIDE texture. Make the controller always materialise perimeter frame blocks (skip only existing quarry frames) so the ring is visible regardless of terrain. Extend render state with an ItemStackRenderState and headSpin, and update the renderer to draw depth-tested lines for the moving gantry/shaft and render a spinning, full-bright item model as the drill head (with proper transforms and lighting). Update QuarryFrameBlock javadoc and wiki to reflect the new visual/behaviour changes. --- .../nerospace/models/block/quarry_frame.json | 368 +++++++++++++++++- .../client/QuarryControllerRenderState.java | 5 + .../client/QuarryControllerRenderer.java | 99 +++-- .../nerospace/datagen/ModModelProvider.java | 63 +-- .../quarry/QuarryControllerBlockEntity.java | 13 +- .../machine/quarry/QuarryFrameBlock.java | 9 +- .../nerospace/textures/block/quarry_frame.png | Bin 146 -> 343 bytes tools/gen_textures.py | 32 +- wiki/Quarry-Controller.md | 6 +- 9 files changed, 493 insertions(+), 102 deletions(-) diff --git a/src/generated/resources/assets/nerospace/models/block/quarry_frame.json b/src/generated/resources/assets/nerospace/models/block/quarry_frame.json index 13e4264..d78bce8 100644 --- a/src/generated/resources/assets/nerospace/models/block/quarry_frame.json +++ b/src/generated/resources/assets/nerospace/models/block/quarry_frame.json @@ -4,22 +4,22 @@ { "faces": { "down": { - "texture": "#all" + "texture": "#side" }, "east": { - "texture": "#all" + "texture": "#side" }, "north": { - "texture": "#all" + "texture": "#side" }, "south": { - "texture": "#all" + "texture": "#side" }, "up": { - "texture": "#all" + "texture": "#side" }, "west": { - "texture": "#all" + "texture": "#side" } }, "from": [ @@ -27,15 +27,367 @@ 0, 0 ], + "to": [ + 2, + 16, + 2 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 14, + 0, + 0 + ], + "to": [ + 16, + 16, + 2 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 0, + 0, + 14 + ], + "to": [ + 2, + 16, + 16 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 14, + 0, + 14 + ], "to": [ 16, 16, 16 ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 2, + 0, + 0 + ], + "to": [ + 14, + 2, + 2 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 2, + 0, + 14 + ], + "to": [ + 14, + 2, + 16 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 0, + 0, + 2 + ], + "to": [ + 2, + 2, + 14 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 14, + 0, + 2 + ], + "to": [ + 16, + 2, + 14 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 2, + 14, + 0 + ], + "to": [ + 14, + 16, + 2 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 2, + 14, + 14 + ], + "to": [ + 14, + 16, + 16 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 0, + 14, + 2 + ], + "to": [ + 2, + 16, + 14 + ] + }, + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 14, + 14, + 2 + ], + "to": [ + 16, + 16, + 14 + ] } ], "textures": { - "all": "nerospace:block/quarry_frame", - "particle": "nerospace:block/quarry_frame" + "particle": "nerospace:block/quarry_frame", + "side": "nerospace:block/quarry_frame" } } \ No newline at end of file diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java index 79c862b..4afea46 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java @@ -1,6 +1,7 @@ package za.co.neroland.nerospace.client; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.item.ItemStackRenderState; /** * Render state for the quarry's gantry + drill head. All coordinates are relative to the controller @@ -24,4 +25,8 @@ public class QuarryControllerRenderState extends BlockEntityRenderState { public double hz; /** Tier accent (ARGB) for the gantry rails. */ public int accent; + /** The drill head, rendered as a spinning item model. */ + public final ItemStackRenderState head = new ItemStackRenderState(); + /** Drill-head spin (degrees). */ + public float headSpin; } diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java index f048096..ed17580 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java @@ -2,32 +2,38 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.feature.ModelFeatureRenderer; import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity; import za.co.neroland.nerospace.machine.quarry.QuarryRegion; +import za.co.neroland.nerospace.registry.ModItems; /** - * Draws the quarry's working machinery: glowing gantry rails around the claimed region, a moving - * bridge rail tracking the dig column, a vertical drill shaft and a bright drill head at the cell - * currently being mined (MINER_DESIGN — "build frame + drill head"). Geometry is emissive - * position-colour quads via the lightning render type (same approach as the Universal Pipe streams). - * The head position is server-synced (region + cursor + currentY) and smoothed client-side. + * Draws the quarry's working machinery on top of the (solid, block-based) frame ring: a moving + * bridge rail + drill shaft drawn as depth-tested lines, and the drill head itself rendered as a + * spinning item model (the proven Star-Guide-hologram path — solid, lit, depth-correct). The head + * position is server-synced (region + cursor + currentY) and smoothed client-side. */ public class QuarryControllerRenderer implements BlockEntityRenderer { - private static final double RAIL = 0.12; - private static final double SHAFT = 0.05; - private static final double HEAD = 0.18; + /** Per-vertex line width required by the lines vertex format (POSITION_COLOR_NORMAL_LINE_WIDTH). */ + private static final float LINE_WIDTH = 3.0F; + /** Full-bright light so the head glows like a powered tool. */ + private static final int FULL_BRIGHT = 0x00F000F0; @Override public QuarryControllerRenderState createRenderState() { @@ -45,7 +51,7 @@ public void extractRenderState(QuarryControllerBlockEntity be, QuarryControllerR BlockEntityRenderer.super.extractRenderState(be, s, partialTick, cameraPos, breakProgress); QuarryRegion r = be.renderRegion(); QuarryControllerBlockEntity.State st = be.renderState(); - if (r == null || st == QuarryControllerBlockEntity.State.IDLE) { + if (r == null || st == QuarryControllerBlockEntity.State.IDLE || be.getLevel() == null) { s.active = false; return; } @@ -88,6 +94,13 @@ public void extractRenderState(QuarryControllerBlockEntity be, QuarryControllerR s.hx = be.dispX - p.getX(); s.hy = be.dispY - p.getY(); s.hz = be.dispZ - p.getZ(); + + float now = be.getLevel().getGameTime() + partialTick; + s.headSpin = (now * 6.0F) % 360.0F; + // Render the drill head as a spinning pickaxe item (solid/lit/depth-correct). + Minecraft.getInstance().getItemModelResolver().updateForTopItem( + s.head, new ItemStack(ModItems.NEROSIUM_PICKAXE.get()), ItemDisplayContext.GROUND, + be.getLevel(), null, (int) p.asLong()); } @Override @@ -96,49 +109,51 @@ public void submit(QuarryControllerRenderState s, PoseStack poseStack, SubmitNod if (!s.active) { return; } - collector.order(1).submitCustomGeometry(poseStack, RenderTypes.lightning(), + // Depth-tested lines for the moving bridge + drill shaft. + collector.order(1).submitCustomGeometry(poseStack, RenderTypes.lines(), (pose, consumer) -> draw(s, pose, consumer)); + + // The drill head: a spinning, full-bright item model at the dig cell. Spin around the vertical + // axis, then flip 180° so the pickaxe head points DOWN into the dig face (was rendering upside + // down — GROUND context keeps the sprite head-up by default). + poseStack.pushPose(); + poseStack.translate(s.hx, s.hy, s.hz); + poseStack.mulPose(Axis.YP.rotationDegrees(s.headSpin)); + poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F)); + poseStack.scale(0.7F, 0.7F, 0.7F); + s.head.submit(poseStack, collector, FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 0); + poseStack.popPose(); } private static void draw(QuarryControllerRenderState s, PoseStack.Pose pose, VertexConsumer consumer) { int ar = (s.accent >> 16) & 0xFF; int ag = (s.accent >> 8) & 0xFF; int ab = s.accent & 0xFF; - int a = 170; double ty = s.topY; - // The frame BLOCKS already draw the static perimeter, so the renderer only adds the MOVING - // gantry parts: a bridge rail tracking the dig column, the drill shaft and the drill head. - // Moving bridge rail (spans Z at the head's X). - box(pose, consumer, s.hx - RAIL / 2, ty - RAIL, s.z0, s.hx + RAIL / 2, ty, s.z1, ar, ag, ab, a); - - // Vertical drill shaft from the head up to the bridge. - box(pose, consumer, s.hx - SHAFT, s.hy, s.hz - SHAFT, s.hx + SHAFT, ty - RAIL, s.hz + SHAFT, - 120, 230, 255, 150); - - // Drill head. - box(pose, consumer, s.hx - HEAD, s.hy - HEAD, s.hz - HEAD, s.hx + HEAD, s.hy + HEAD, s.hz + HEAD, - 200, 245, 255, 220); + // The frame itself is the solid frame BLOCKS — the renderer only adds the MOVING gantry parts. + // Moving bridge: a rail spanning Z at the head's X, along the top of the frame plane. + line(pose, consumer, s.hx, ty, s.z0, s.hx, ty, s.z1, ar, ag, ab, 255); + // Drill shaft: from the head up to the frame plane. + line(pose, consumer, s.hx, s.hy, s.hz, s.hx, ty - 1.0, s.hz, 120, 230, 255, 255); } - /** Emit the 6 faces of an axis-aligned box as position-colour quads. */ - private static void box(PoseStack.Pose pose, VertexConsumer c, double x0, double y0, double z0, - double x1, double y1, double z1, int r, int g, int b, int a) { - quad(pose, c, x0, y0, z0, x0, y1, z0, x1, y1, z0, x1, y0, z0, r, g, b, a); // -Z - quad(pose, c, x1, y0, z1, x1, y1, z1, x0, y1, z1, x0, y0, z1, r, g, b, a); // +Z - quad(pose, c, x0, y0, z1, x0, y1, z1, x0, y1, z0, x0, y0, z0, r, g, b, a); // -X - quad(pose, c, x1, y0, z0, x1, y1, z0, x1, y1, z1, x1, y0, z1, r, g, b, a); // +X - quad(pose, c, x0, y1, z0, x0, y1, z1, x1, y1, z1, x1, y1, z0, r, g, b, a); // +Y - quad(pose, c, x0, y0, z1, x0, y0, z0, x1, y0, z0, x1, y0, z1, r, g, b, a); // -Y - } - - private static void quad(PoseStack.Pose pose, VertexConsumer c, - double ax, double ay, double az, double bx, double by, double bz, - double cx, double cy, double cz, double dx, double dy, double dz, - int r, int g, int b, int a) { - c.addVertex(pose, (float) ax, (float) ay, (float) az).setColor(r, g, b, a); - c.addVertex(pose, (float) bx, (float) by, (float) bz).setColor(r, g, b, a); - c.addVertex(pose, (float) cx, (float) cy, (float) cz).setColor(r, g, b, a); - c.addVertex(pose, (float) dx, (float) dy, (float) dz).setColor(r, g, b, a); + /** A single line segment (RenderTypes.lines needs a per-vertex normal = the segment direction). */ + private static void line(PoseStack.Pose pose, VertexConsumer c, double x1, double y1, double z1, + double x2, double y2, double z2, int r, int g, int b, int a) { + float nx = (float) (x2 - x1); + float ny = (float) (y2 - y1); + float nz = (float) (z2 - z1); + float len = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); + if (len < 1.0e-5f) { + return; + } + nx /= len; + ny /= len; + nz /= len; + c.addVertex(pose, (float) x1, (float) y1, (float) z1) + .setColor(r, g, b, a).setNormal(pose, nx, ny, nz).setLineWidth(LINE_WIDTH); + c.addVertex(pose, (float) x2, (float) y2, (float) z2) + .setColor(r, g, b, a).setNormal(pose, nx, ny, nz).setLineWidth(LINE_WIDTH); } } diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java index 65dd0c0..9d26a30 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModModelProvider.java @@ -91,12 +91,13 @@ protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerat blockModels.createTrivialCube(ModBlocks.STATION_FLOOR.get()); blockModels.createTrivialCube(ModBlocks.STATION_WALL.get()); - // Quarry / Miner (MINER_DESIGN): controller + landmark cubes; frame is a glowing translucent - // cube — a full-cube element model with AO off so the sprite's alpha drives the render layer - // (same trick as the Universal Pipe), letting you see through the open frame. + // Quarry / Miner (MINER_DESIGN): controller + landmark cubes; the frame is a real 3-D open + // structural frame (four corner posts + edge rails, see-through centre — BuildCraft-style), + // built from the same beam recipe as the tanks. Emissive via the bright strut texture + the + // block's light level; .noOcclusion() (ModBlocks) stops the open gaps culling the world behind. blockModels.createTrivialCube(ModBlocks.QUARRY_CONTROLLER.get()); blockModels.createTrivialCube(ModBlocks.QUARRY_LANDMARK.get()); - registerTranslucentCube(blockModels, ModBlocks.QUARRY_FRAME.get()); + registerQuarryFrame(blockModels); // Developer diagnostics — Sentry test block (hidden; /give only). blockModels.createTrivialCube(ModBlocks.SENTRY_TEST.get()); @@ -377,6 +378,39 @@ private void registerShapedMachines(BlockModelGenerators blockModels) { shapedBlock(blockModels, ModBlocks.STAR_GUIDE.get(), starTemplate, starMapping, false); } + /** + * The quarry frame: a 3-D OPEN structural frame — four 2px corner posts + top/bottom edge rails, + * with a see-through centre (no content core, unlike {@link #registerTank}). Reuses the tank's + * beam layout so the gantry reads as a matching machine. The block's own {@code quarry_frame} + * texture skins every strut. + */ + private void registerQuarryFrame(BlockModelGenerators blockModels) { + Block block = ModBlocks.QUARRY_FRAME.get(); + var beam = TextureMapping.getBlockTexture(block); + TextureMapping mapping = new TextureMapping() + .put(TextureSlot.SIDE, beam) + .put(TextureSlot.PARTICLE, beam); + ExtendedModelTemplateBuilder builder = ExtendedModelTemplateBuilder.builder() + .requiredTextureSlot(TextureSlot.SIDE) + .requiredTextureSlot(TextureSlot.PARTICLE) + .ambientOcclusion(false); // emissive struts read flat, no AO darkening at the joints + // four vertical corner posts + for (int[] c : new int[][] {{0, 0}, {14, 0}, {0, 14}, {14, 14}}) { + int x = c[0]; + int z = c[1]; + builder.element(e -> { e.from(x, 0, z).to(x + 2, 16, z + 2); faces(e, TextureSlot.SIDE, null); }); + } + // horizontal rails, bottom + top, both axes (the centre stays open) + for (int y : new int[] {0, 14}) { + int yy = y; + builder.element(e -> { e.from(2, yy, 0).to(14, yy + 2, 2); faces(e, TextureSlot.SIDE, null); }); + builder.element(e -> { e.from(2, yy, 14).to(14, yy + 2, 16); faces(e, TextureSlot.SIDE, null); }); + builder.element(e -> { e.from(0, yy, 2).to(2, yy + 2, 14); faces(e, TextureSlot.SIDE, null); }); + builder.element(e -> { e.from(14, yy, 2).to(16, yy + 2, 14); faces(e, TextureSlot.SIDE, null); }); + } + shapedBlock(blockModels, block, builder.build(), mapping, false); + } + /** A tank: 12 frame beams (the block's own texture) around a {@code _core} content core. */ private void registerTank(BlockModelGenerators blockModels, Block block) { var frame = TextureMapping.getBlockTexture(block); @@ -407,27 +441,6 @@ private void registerTank(BlockModelGenerators blockModels, Block block) { shapedBlock(blockModels, block, builder.build(), mapping, false); } - /** - * A full-cube model with ambient occlusion OFF so the sprite's alpha selects the translucent - * render layer (26.1 derives the chunk layer from the texture) — used for the glowing, - * see-through quarry frame. - */ - private void registerTranslucentCube(BlockModelGenerators blockModels, Block block) { - var tex = TextureMapping.getBlockTexture(block); - TextureMapping mapping = new TextureMapping().put(TextureSlot.ALL, tex).put(TextureSlot.PARTICLE, tex); - ExtendedModelTemplate template = ExtendedModelTemplateBuilder.builder() - .requiredTextureSlot(TextureSlot.ALL) - .requiredTextureSlot(TextureSlot.PARTICLE) - .ambientOcclusion(false) - .element(e -> e.from(0, 0, 0).to(16, 16, 16) - .allFaces((dir, face) -> face.texture(TextureSlot.ALL))) - .build(); - Identifier model = template.create( - ModelLocationUtils.getModelLocation(block), mapping, blockModels.modelOutput); - blockModels.blockStateOutput.accept( - BlockModelGenerators.createSimpleBlock(block, BlockModelGenerators.plainVariant(model))); - } - /** * The Universal Pipe: a multipart blockstate — always the translucent core (4..12 cube), plus one * arm per connected face (the north arm model rotated for the other five). Translucency comes from diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java index b228829..a1c3cd2 100644 --- a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java @@ -343,14 +343,8 @@ private void buildFrame(ServerLevel level) { boolean changed = false; while (this.frameIndex < ring.size() && placedThisTick < 8) { BlockPos fp = ring.get(this.frameIndex); - BlockState existing = level.getBlockState(fp); - if (existing.getBlock() instanceof QuarryFrameBlock) { - this.frameIndex++; - continue; - } - // Only draw the frame through open cells; terrain-backed perimeter cells need no casing. - if (!existing.isAir() && !existing.canBeReplaced()) { - this.frameIndex++; + if (level.getBlockState(fp).getBlock() instanceof QuarryFrameBlock) { + this.frameIndex++; // already framed continue; } ItemStack casing = this.frameHandler.getStack(FRAME_SLOT); @@ -358,6 +352,9 @@ private void buildFrame(ServerLevel level) { setPaused("need_material"); return; } + // Always materialise the frame at the perimeter (replacing whatever is there) so the ring + // is visible regardless of terrain — marking a region on flat ground used to place NO + // frame blocks because every perimeter cell was solid. level.setBlock(fp, ModBlocks.QUARRY_FRAME.get().defaultBlockState(), Block.UPDATE_CLIENTS); casing.shrink(1); placedThisTick++; diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java index cad27f1..85bab33 100644 --- a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java @@ -9,8 +9,13 @@ * "build frame + drill head"). Built by the controller (one {@code frame_casing} per block) and * removed when the controller is removed; it carries no loot table, so breaking one by hand yields * nothing. A dedicated class so the mining loop can recognise and skip frames — its own and other - * quarries' ("respect claims"). The translucent/emissive look is a client render-type choice; the - * block is registered with {@code noOcclusion()} and an emissive light level. + * quarries' ("respect claims"). + * + *

Renders as a real 3-D open structural frame — four corner posts + edge rails with a see-through + * centre (BuildCraft-style), built by {@code ModModelProvider#registerQuarryFrame} from the same beam + * layout as the tanks; emissive via the strut texture + the block's light level, and {@code + * .noOcclusion()} so the open gaps don't cull the world behind. The controller's {@code + * QuarryControllerRenderer} adds only the MOVING gantry parts (drill head + bridge) on top.

*/ public class QuarryFrameBlock extends Block { diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_frame.png b/src/main/resources/assets/nerospace/textures/block/quarry_frame.png index abc4b252a2bb461aa0a8deb84f258cfa9417613e..828890e831a11fe6842c24eb0938e21bee872f5e 100644 GIT binary patch delta 317 zcmV-D0mA;00oMYMBYy!NNkl#R1%xG*|)vF{j`jp?__XbB`DTXtf4sc{+C< z#GES7%z!!&GibHe1afZ^3Kp|yQ0<(ShU$dU6(DBiT)ILl6FY!G0}Ym}J-;!8mbEX( z!BaemdFWDa6J6%fK<2<|pfG*sEC&v?&c*fj=Xnl$X7sULcg*aNy_JR2{ zIBqw?D~EbggErXtr@sII{`+f~gQ?z2sa7A+LRv~dQq$sgX2K-EWtkn0(Thwx8KrZ3SV=+ Y0Es&wWWzQE?EnA(07*qoM6N<$f> Date: Tue, 16 Jun 2026 11:49:14 +0800 Subject: [PATCH 3/5] Improve quarry rendering, pacing and frame handling Revamp quarry visuals and behaviour: add QUARRY_MINE_INTERVAL and throttle mining to one block per work cycle derived from tier/modules/planet speed; smooth client-side drill-head motion by tracking previous/current mined blocks and their timestamps. Replace spinning item-model head with textured 3D gantry and drill-bit rendering (entityCutout, custom geometry), expand render bounding box and enable off-screen rendering. Make frame placement/resumption more robust: avoid framing over controllers or block entities, pre-carve interior-only pits (leave frame columns), and stage gallery quarries via a new helper in the dev command. Improve chunk ticketing by pinning only the active dig chunk and releasing older tickets; stop tearing down the frame on chunk unload (move teardown to preRemoveSideEffects). Persist new mine tracking fields to save/load. Update quarry frame texture and texture generator script to new visual theme, and refresh wiki notes to document pacing, interior-only excavation, and chunk-loading behaviour. --- .../java/za/co/neroland/nerospace/Tuning.java | 6 + .../client/QuarryControllerRenderState.java | 7 +- .../client/QuarryControllerRenderer.java | 257 ++++++++++++------ .../nerospace/command/NerospaceCommands.java | 83 +++--- .../quarry/QuarryControllerBlockEntity.java | 167 +++++++++++- .../nerospace/textures/block/quarry_frame.png | Bin 343 -> 416 bytes tools/gen_textures.py | 22 +- wiki/Quarry-Controller.md | 19 +- 8 files changed, 411 insertions(+), 150 deletions(-) diff --git a/src/main/java/za/co/neroland/nerospace/Tuning.java b/src/main/java/za/co/neroland/nerospace/Tuning.java index efdf68c..2427e2c 100644 --- a/src/main/java/za/co/neroland/nerospace/Tuning.java +++ b/src/main/java/za/co/neroland/nerospace/Tuning.java @@ -112,6 +112,12 @@ private Tuning() { public static final int BASE_GRINDER_ENERGY_PER_TICK = 30; /** Quarry energy consumed per mined block (FE); modules' efficiency lowers it. */ public static final int BASE_QUARRY_ENERGY_PER_BLOCK = 40; + /** + * Minimum game-ticks between quarry dig cycles. Throttles raw dig speed so a fully-powered quarry + * doesn't strip the map instantly: at the default cycle of {@code baseBlocksPerCycle} blocks every + * {@code QUARRY_MINE_INTERVAL} ticks, Tier 1 (2 blocks / 8 ticks) ≈ 5 blocks/s. + */ + public static final int QUARRY_MINE_INTERVAL = 8; /** Fuel Refinery energy consumed per working tick (FE); over a batch ≈ 4,000 FE per 2,000 mB. */ public static final int BASE_FUEL_REFINERY_FE_PER_TICK = 40; /** Rocket fuel produced per refining batch (mB) — one coal + one blaze powder = 2,000 mB. */ diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java index 4afea46..3b99b49 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java @@ -1,7 +1,6 @@ package za.co.neroland.nerospace.client; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; -import net.minecraft.client.renderer.item.ItemStackRenderState; /** * Render state for the quarry's gantry + drill head. All coordinates are relative to the controller @@ -23,10 +22,8 @@ public class QuarryControllerRenderState extends BlockEntityRenderState { public double hx; public double hy; public double hz; - /** Tier accent (ARGB) for the gantry rails. */ + /** Tier accent (ARGB) for the gantry rails + drill bit. */ public int accent; - /** The drill head, rendered as a spinning item model. */ - public final ItemStackRenderState head = new ItemStackRenderState(); - /** Drill-head spin (degrees). */ + /** Drill-bit spin (degrees). */ public float headSpin; } diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java index ed17580..73aa386 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java @@ -4,36 +4,48 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.Axis; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.feature.ModelFeatureRenderer; import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.BlockPos; -import net.minecraft.util.Mth; -import net.minecraft.world.item.ItemDisplayContext; -import net.minecraft.world.item.ItemStack; +import net.minecraft.resources.Identifier; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import za.co.neroland.nerospace.Nerospace; import za.co.neroland.nerospace.machine.quarry.QuarryControllerBlockEntity; import za.co.neroland.nerospace.machine.quarry.QuarryRegion; -import za.co.neroland.nerospace.registry.ModItems; /** - * Draws the quarry's working machinery on top of the (solid, block-based) frame ring: a moving - * bridge rail + drill shaft drawn as depth-tested lines, and the drill head itself rendered as a - * spinning item model (the proven Star-Guide-hologram path — solid, lit, depth-correct). The head - * position is server-synced (region + cursor + currentY) and smoothed client-side. + * Draws the quarry's working machinery on top of the (solid, block-based) frame ring — as real + * textured geometry via {@link RenderTypes#entityCutout(Identifier)} with the frame's own texture, so + * the gantry matches the frame and is depth-correct (entity-cutout writes depth, so it is properly + * occluded by terrain — no see-through — and is cull-off so winding is a non-issue): + *
    + *
  • a gantry that rides the frame: a bridge beam spanning one axis with end trucks, a carriage + * that tracks the dig column, and a support shaft down to the head;
  • + *
  • the drill head: a multi-segment bit (chuck collar, shaft, flared hub, four flutes, and a + * tapered spike with a glowing tip) that spins and sits on top of the block being mined.
  • + *
+ * The head follows the last block actually mined (server-synced) so it tracks the dig in order rather + * than chasing the raw scan cursor; the position is smoothed client-side. */ public class QuarryControllerRenderer implements BlockEntityRenderer { - /** Per-vertex line width required by the lines vertex format (POSITION_COLOR_NORMAL_LINE_WIDTH). */ - private static final float LINE_WIDTH = 3.0F; - /** Full-bright light so the head glows like a powered tool. */ + /** The frame texture, reused so the gantry + bit match the frame ring exactly. */ + private static final Identifier TEX = + Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/block/quarry_frame.png"); + /** Full-bright light (the frame is emissive); per-face lighting still shades by normal. */ private static final int FULL_BRIGHT = 0x00F000F0; + /** Top of the bit (chuck collar top, local Y) — the support shaft meets it here. */ + private static final float BIT_TOP = 0.72F; + /** Spike apex (local Y); the head is placed so this sits on the mined block's top face. */ + private static final float TIP_Y = -0.6F; @Override public QuarryControllerRenderState createRenderState() { @@ -45,6 +57,28 @@ public int getViewDistance() { return 128; // regions can stretch well past the default 64 from the controller } + @Override + public boolean shouldRenderOffScreen() { + // The gantry + drill render far from the controller block, so register as a global block entity: + // otherwise the renderer is dropped whenever the controller's own chunk section is occlusion- + // culled (e.g. when the camera is down inside the pit), and the whole gantry vanishes. + return true; + } + + @Override + public AABB getRenderBoundingBox(QuarryControllerBlockEntity be) { + QuarryRegion r = be.renderRegion(); + if (r == null) { + return new AABB(be.getBlockPos()); + } + // The gantry + drill render across the whole region and down the dig shaft, far from the + // controller block. Without this, the default unit-cube render box gets frustum-culled when the + // camera is close and the controller block leaves view — and the gantry vanishes. Cover the + // whole region down to the world floor. + int floor = be.getLevel() != null ? be.getLevel().getMinY() : r.refY() - 64; + return new AABB(r.minX(), floor, r.minZ(), r.maxX() + 1, r.refY() + 2, r.maxZ() + 1); + } + @Override public void extractRenderState(QuarryControllerBlockEntity be, QuarryControllerRenderState s, float partialTick, Vec3 cameraPos, ModelFeatureRenderer.CrumblingOverlay breakProgress) { @@ -65,42 +99,44 @@ public void extractRenderState(QuarryControllerBlockEntity be, QuarryControllerR s.accent = be.tier().accentColor(); s.mining = st == QuarryControllerBlockEntity.State.MINING; - double tx; - double ty; - double tz; - if (s.mining) { - int cols = Math.max(1, r.columns()); - int cur = Mth.clamp(be.renderCursor(), 0, cols - 1); - BlockPos col = r.columnPos(cur, be.renderCurrentY()); - tx = col.getX() + 0.5; - ty = be.renderCurrentY() + 0.5; - tz = col.getZ() + 0.5; - } else { - tx = (r.minX() + r.maxX() + 1) / 2.0; - ty = r.refY() + 0.5; - tz = (r.minZ() + r.maxZ() + 1) / 2.0; - } - if (!be.dispInit) { - be.dispX = tx; - be.dispY = ty; - be.dispZ = tz; + double cx = (r.minX() + r.maxX() + 1) / 2.0; + double cz = (r.minZ() + r.maxZ() + 1) / 2.0; + double now = be.getLevel().getGameTime() + partialTick; + if (s.mining && be.renderHasMine()) { + // Sweep the head continuously from the previously-mined block to the current one over the + // real elapsed interval (1 block at a time), so motion is smooth rather than stop-start. + // The spike apex sits on the block's top face. dur self-adapts to the actual dig pace. + double dur = Math.max(1.0, Math.min(20.0, be.renderMineTime() - be.renderPrevMineTime())); + double a = Math.max(0.0, Math.min(1.0, (now - be.renderMineTime()) / dur)); + be.dispX = lerp(be.renderPrevMineX() + 0.5, be.renderMineX() + 0.5, a); + be.dispY = lerp(be.renderPrevMineY(), be.renderMineY(), a) + 1.0 - TIP_Y; + be.dispZ = lerp(be.renderPrevMineZ() + 0.5, be.renderMineZ() + 0.5, a); be.dispInit = true; } else { - double k = 0.3; - be.dispX += (tx - be.dispX) * k; - be.dispY += (ty - be.dispY) * k; - be.dispZ += (tz - be.dispZ) * k; + // Idle / pre-first-block: hover at the region centre, lightly eased. + double tx = cx; + double ty = s.mining ? r.refY() + 1.0 - TIP_Y : r.refY() + 0.5; + double tz = cz; + if (!be.dispInit) { + be.dispX = tx; + be.dispY = ty; + be.dispZ = tz; + be.dispInit = true; + } else { + be.dispX += (tx - be.dispX) * 0.2; + be.dispY += (ty - be.dispY) * 0.2; + be.dispZ += (tz - be.dispZ) * 0.2; + } } s.hx = be.dispX - p.getX(); s.hy = be.dispY - p.getY(); s.hz = be.dispZ - p.getZ(); - float now = be.getLevel().getGameTime() + partialTick; - s.headSpin = (now * 6.0F) % 360.0F; - // Render the drill head as a spinning pickaxe item (solid/lit/depth-correct). - Minecraft.getInstance().getItemModelResolver().updateForTopItem( - s.head, new ItemStack(ModItems.NEROSIUM_PICKAXE.get()), ItemDisplayContext.GROUND, - be.getLevel(), null, (int) p.asLong()); + s.headSpin = (float) ((now * 18.0) % 360.0); // fast spin reads as a working drill + } + + private static double lerp(double a, double b, double t) { + return a + (b - a) * t; } @Override @@ -109,51 +145,116 @@ public void submit(QuarryControllerRenderState s, PoseStack poseStack, SubmitNod if (!s.active) { return; } - // Depth-tested lines for the moving bridge + drill shaft. - collector.order(1).submitCustomGeometry(poseStack, RenderTypes.lines(), - (pose, consumer) -> draw(s, pose, consumer)); - - // The drill head: a spinning, full-bright item model at the dig cell. Spin around the vertical - // axis, then flip 180° so the pickaxe head points DOWN into the dig face (was rendering upside - // down — GROUND context keeps the sprite head-up by default). + RenderType rt = RenderTypes.entityCutout(TEX); + // Gantry: world-relative, at the base pose. + collector.order(1).submitCustomGeometry(poseStack, rt, (pose, c) -> drawGantry(s, pose, c)); + // Drill head: spun around the vertical axis at the dig cell. poseStack.pushPose(); poseStack.translate(s.hx, s.hy, s.hz); poseStack.mulPose(Axis.YP.rotationDegrees(s.headSpin)); - poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F)); - poseStack.scale(0.7F, 0.7F, 0.7F); - s.head.submit(poseStack, collector, FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 0); + collector.order(1).submitCustomGeometry(poseStack, rt, (pose, c) -> drawBit(pose, c)); poseStack.popPose(); } - private static void draw(QuarryControllerRenderState s, PoseStack.Pose pose, VertexConsumer consumer) { - int ar = (s.accent >> 16) & 0xFF; - int ag = (s.accent >> 8) & 0xFF; - int ab = s.accent & 0xFF; - double ty = s.topY; - - // The frame itself is the solid frame BLOCKS — the renderer only adds the MOVING gantry parts. - // Moving bridge: a rail spanning Z at the head's X, along the top of the frame plane. - line(pose, consumer, s.hx, ty, s.z0, s.hx, ty, s.z1, ar, ag, ab, 255); - // Drill shaft: from the head up to the frame plane. - line(pose, consumer, s.hx, s.hy, s.hz, s.hx, ty - 1.0, s.hz, 120, 230, 255, 255); + /** The gantry crane that rides the frame: bridge + end trucks + carriage + support shaft. */ + private static void drawGantry(QuarryControllerRenderState s, PoseStack.Pose pose, VertexConsumer c) { + float ty = (float) s.topY; + float hx = (float) s.hx; + float hz = (float) s.hz; + float z0 = (float) s.z0; + float z1 = (float) s.z1; + // Bridge beam: spans Z at the head's X, sitting on the frame plane (moves in X with the dig). + texBeam(c, pose, hx - 0.15F, ty, z0, hx + 0.15F, ty + 0.22F, z1); + // End trucks: ride the two side rails of the frame. + texBox(c, pose, hx - 0.22F, ty - 0.05F, z0, hx + 0.22F, ty + 0.28F, z0 + 0.35F); + texBox(c, pose, hx - 0.22F, ty - 0.05F, z1 - 0.35F, hx + 0.22F, ty + 0.28F, z1); + // Carriage: the moving drill mount that tracks the dig column. + texBox(c, pose, hx - 0.26F, ty - 0.06F, hz - 0.26F, hx + 0.26F, ty + 0.30F, hz + 0.26F); + // Support shaft from the carriage down to the top of the spinning bit. + texBeam(c, pose, hx - 0.07F, (float) s.hy + BIT_TOP, hz - 0.07F, hx + 0.07F, ty, hz + 0.07F); } - /** A single line segment (RenderTypes.lines needs a per-vertex normal = the segment direction). */ - private static void line(PoseStack.Pose pose, VertexConsumer c, double x1, double y1, double z1, - double x2, double y2, double z2, int r, int g, int b, int a) { - float nx = (float) (x2 - x1); - float ny = (float) (y2 - y1); - float nz = (float) (z2 - z1); - float len = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); - if (len < 1.0e-5f) { - return; + /** The drill head (local space, origin at the head centre, Y up): a detailed frame-textured bit. */ + private static void drawBit(PoseStack.Pose pose, VertexConsumer c) { + texBox(c, pose, -0.24F, 0.58F, -0.24F, 0.24F, BIT_TOP, 0.24F); // chuck collar + texBox(c, pose, -0.12F, 0.22F, -0.12F, 0.12F, 0.58F, 0.12F); // shaft + texBox(c, pose, -0.22F, 0.05F, -0.22F, 0.22F, 0.22F, 0.22F); // flared hub + texBox(c, pose, 0.20F, 0.05F, -0.05F, 0.34F, 0.30F, 0.05F); // flute +x + texBox(c, pose, -0.34F, 0.05F, -0.05F, -0.20F, 0.30F, 0.05F); // flute -x + texBox(c, pose, -0.05F, 0.05F, 0.20F, 0.05F, 0.30F, 0.34F); // flute +z + texBox(c, pose, -0.05F, 0.05F, -0.34F, 0.05F, 0.30F, -0.20F); // flute -z + // tapering spike with a glowing tip + float bw = 0.22F; + spikeFace(c, pose, -bw, -bw, bw, -bw, 0, 0, -1); // north + spikeFace(c, pose, bw, -bw, bw, bw, 1, 0, 0); // east + spikeFace(c, pose, bw, bw, -bw, bw, 0, 0, 1); // south + spikeFace(c, pose, -bw, bw, -bw, -bw, -1, 0, 0); // west + } + + /** One tapering spike face: two base corners (at y=0.05) up to the apex, base white, tip glowing. */ + private static void spikeFace(VertexConsumer c, PoseStack.Pose pose, + float ax, float az, float bx, float bz, float nx, float ny, float nz) { + tv(c, pose, ax, 0.05F, az, 0.0F, 0.0F, nx, ny, nz, 255, 255, 255); + tv(c, pose, bx, 0.05F, bz, 1.0F, 0.0F, nx, ny, nz, 255, 255, 255); + tv(c, pose, 0.0F, TIP_Y, 0.0F, 0.5F, 1.0F, nx, ny, nz, 255, 176, 230); + tv(c, pose, 0.0F, TIP_Y, 0.0F, 0.5F, 1.0F, nx, ny, nz, 255, 176, 230); + } + + // --- textured geometry helpers (entityCutout = QUADS, cull off, depth-correct) ------------- + + private static void tv(VertexConsumer c, PoseStack.Pose pose, float x, float y, float z, + float u, float v, float nx, float ny, float nz, int r, int g, int b) { + c.addVertex(pose, x, y, z) + .setColor(r, g, b, 255) + .setUv(u, v) + .setOverlay(OverlayTexture.NO_OVERLAY) + .setLight(FULL_BRIGHT) + .setNormal(pose, nx, ny, nz); + } + + /** A textured quad (4 corners, one normal, UV from (0,0) to (uMax,vMax)), drawn white. */ + private static void quad(VertexConsumer c, PoseStack.Pose pose, + float ax, float ay, float az, float bx, float by, float bz, + float cx, float cy, float cz, float dx, float dy, float dz, + float nx, float ny, float nz, float uMax, float vMax) { + tv(c, pose, ax, ay, az, 0.0F, 0.0F, nx, ny, nz, 255, 255, 255); + tv(c, pose, bx, by, bz, uMax, 0.0F, nx, ny, nz, 255, 255, 255); + tv(c, pose, cx, cy, cz, uMax, vMax, nx, ny, nz, 255, 255, 255); + tv(c, pose, dx, dy, dz, 0.0F, vMax, nx, ny, nz, 255, 255, 255); + } + + /** A textured box; UVs map 1 texture tile per block (dims are assumed ≤ ~1, so no wrap needed). */ + private static void texBox(VertexConsumer c, PoseStack.Pose pose, + float x0, float y0, float z0, float x1, float y1, float z1) { + float dx = x1 - x0; + float dy = y1 - y0; + float dz = z1 - z0; + quad(c, pose, x0, y0, z0, x1, y0, z0, x1, y0, z1, x0, y0, z1, 0, -1, 0, dx, dz); // down + quad(c, pose, x0, y1, z0, x1, y1, z0, x1, y1, z1, x0, y1, z1, 0, 1, 0, dx, dz); // up + quad(c, pose, x0, y0, z0, x1, y0, z0, x1, y1, z0, x0, y1, z0, 0, 0, -1, dx, dy); // north + quad(c, pose, x0, y0, z1, x1, y0, z1, x1, y1, z1, x0, y1, z1, 0, 0, 1, dx, dy); // south + quad(c, pose, x0, y0, z0, x0, y0, z1, x0, y1, z1, x0, y1, z0, -1, 0, 0, dz, dy); // west + quad(c, pose, x1, y0, z0, x1, y0, z1, x1, y1, z1, x1, y1, z0, 1, 0, 0, dz, dy); // east + } + + /** A textured beam: subdivides the longest axis into ≤1-block segments so the texture tiles. */ + private static void texBeam(VertexConsumer c, PoseStack.Pose pose, + float x0, float y0, float z0, float x1, float y1, float z1) { + float dx = x1 - x0; + float dy = y1 - y0; + float dz = z1 - z0; + if (dx >= dy && dx >= dz) { + for (float x = x0; x < x1 - 1.0e-4F; x += 1.0F) { + texBox(c, pose, x, y0, z0, Math.min(x + 1.0F, x1), y1, z1); + } + } else if (dz >= dx && dz >= dy) { + for (float z = z0; z < z1 - 1.0e-4F; z += 1.0F) { + texBox(c, pose, x0, y0, z, x1, y1, Math.min(z + 1.0F, z1)); + } + } else { + for (float y = y0; y < y1 - 1.0e-4F; y += 1.0F) { + texBox(c, pose, x0, y, z0, x1, Math.min(y + 1.0F, y1), z1); + } } - nx /= len; - ny /= len; - nz /= len; - c.addVertex(pose, (float) x1, (float) y1, (float) z1) - .setColor(r, g, b, a).setNormal(pose, nx, ny, nz).setLineWidth(LINE_WIDTH); - c.addVertex(pose, (float) x2, (float) y2, (float) z2) - .setColor(r, g, b, a).setNormal(pose, nx, ny, nz).setLineWidth(LINE_WIDTH); } } diff --git a/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java b/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java index 8d6bb62..f2b079c 100644 --- a/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java +++ b/src/main/java/za/co/neroland/nerospace/command/NerospaceCommands.java @@ -344,41 +344,11 @@ private static int buildGallery(CommandSourceStack source) { level.setBlockAndUpdate(new BlockPos(lx + 6, fy + 1, lz), landmark); level.setBlockAndUpdate(new BlockPos(lx, fy + 1, lz + 6), landmark); - // Operating quarry: a 5x5 region west-fed by an endless battery, staged straight into mining. - int qx = origin.getX() + 42; - int qz = origin.getZ() - 40; - int refY = fy + 1; - for (int dx = -5; dx <= 5; dx++) { // power pad to the west of the region - for (int dz = -1; dz <= 5; dz++) { - level.setBlockAndUpdate(new BlockPos(qx + dx, fy, qz + dz), floor); - } - } - QuarryRegion region = new QuarryRegion(qx, qz, qx + 4, qz + 4, refY); - BlockState frameBlock = ModBlocks.QUARRY_FRAME.get().defaultBlockState(); - for (BlockPos fp : region.framePositions()) { - level.setBlockAndUpdate(fp, frameBlock); - } - // Pre-carve a starter pit so it reads as mid-dig the instant it's shot. - for (int x = qx; x <= qx + 4; x++) { - for (int z = qz; z <= qz + 4; z++) { - for (int y = refY - 1; y >= refY - 4; y--) { - level.setBlockAndUpdate(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState()); - } - } - } - BlockPos quarryPos = new BlockPos(qx - 2, refY, qz + 2); - level.setBlockAndUpdate(new BlockPos(qx - 4, refY, qz + 2), - ModBlocks.CREATIVE_BATTERY.get().defaultBlockState()); - level.setBlockAndUpdate(new BlockPos(qx - 3, refY, qz + 2), - ModBlocks.UNIVERSAL_PIPE.get().defaultBlockState()); - level.setBlockAndUpdate(quarryPos, ModBlocks.QUARRY_CONTROLLER.get().defaultBlockState()); - setAllModes(level, new BlockPos(qx - 3, refY, qz + 2), Direction.WEST, PipeIoMode.IN); - setAllModes(level, new BlockPos(qx - 3, refY, qz + 2), Direction.EAST, PipeIoMode.OUT); - if (level.getBlockEntity(quarryPos) instanceof QuarryControllerBlockEntity quarry) { - quarry.setItem(QuarryControllerBlockEntity.FRAME_SLOT, - new ItemStack(ModItems.FRAME_CASING.get(), 64)); - quarry.stageDisplay(region, refY - 5); - } + // Operating quarries: staged straight into a deep mid-dig so the frame, gantry, drill head and + // interior-only excavation all read at a glance. Two sizes — a standard 9x9 and a big 17x17 to + // stress-test rendering + mining over a large area. + buildGalleryQuarry(level, floor, origin.getX() + 42, origin.getZ() - 40, fy, 8, 8); + buildGalleryQuarry(level, floor, origin.getX() + 64, origin.getZ() - 56, fy, 16, 12); source.sendSuccess(() -> Component.literal("Built the Nerospace gallery: " + blocks.size() + " blocks, 4 RUNNING machine clusters (grinder line, fuel refinery " @@ -450,6 +420,49 @@ private static int clearGallery(CommandSourceStack source) { return Command.SINGLE_SUCCESS; } + /** + * Build one staged, fully-powered gallery quarry: a {@code (side+1) x (side+1)} region with its + * frame ring, a west-side creative battery + pipe feed, an interior-only pre-carved pit + * {@code pitDepth} deep (the columns under the frame stay, matching real mining), dropped straight + * into MINING so the gantry + drill animate immediately. + */ + private static void buildGalleryQuarry(ServerLevel level, BlockState floor, int qx, int qz, int fy, + int side, int pitDepth) { + int refY = fy + 1; + int mid = side / 2; + for (int dx = -5; dx <= side; dx++) { // ground: power pad (west) + under the region + for (int dz = -1; dz <= side; dz++) { + level.setBlockAndUpdate(new BlockPos(qx + dx, fy, qz + dz), floor); + } + } + QuarryRegion region = new QuarryRegion(qx, qz, qx + side, qz + side, refY); + BlockState frameBlock = ModBlocks.QUARRY_FRAME.get().defaultBlockState(); + for (BlockPos fp : region.framePositions()) { + level.setBlockAndUpdate(fp, frameBlock); + } + // Pre-carve a starter pit — INTERIOR only, leaving the columns under the frame intact. + for (int x = qx + 1; x <= qx + side - 1; x++) { + for (int z = qz + 1; z <= qz + side - 1; z++) { + for (int y = refY - 1; y >= refY - pitDepth; y--) { + level.setBlockAndUpdate(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState()); + } + } + } + BlockPos quarryPos = new BlockPos(qx - 2, refY, qz + mid); + level.setBlockAndUpdate(new BlockPos(qx - 4, refY, qz + mid), + ModBlocks.CREATIVE_BATTERY.get().defaultBlockState()); + level.setBlockAndUpdate(new BlockPos(qx - 3, refY, qz + mid), + ModBlocks.UNIVERSAL_PIPE.get().defaultBlockState()); + level.setBlockAndUpdate(quarryPos, ModBlocks.QUARRY_CONTROLLER.get().defaultBlockState()); + setAllModes(level, new BlockPos(qx - 3, refY, qz + mid), Direction.WEST, PipeIoMode.IN); + setAllModes(level, new BlockPos(qx - 3, refY, qz + mid), Direction.EAST, PipeIoMode.OUT); + if (level.getBlockEntity(quarryPos) instanceof QuarryControllerBlockEntity quarry) { + quarry.setItem(QuarryControllerBlockEntity.FRAME_SLOT, + new ItemStack(ModItems.FRAME_CASING.get(), 64)); + quarry.stageDisplay(region, refY - pitDepth); + } + } + /** A full {@code size x size} square of launch pads with min-corner {@code corner}. */ private static void fillPad(ServerLevel level, BlockPos corner, int size, BlockState pad) { for (int dx = 0; dx < size; dx++) { diff --git a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java index a1c3cd2..02fed5c 100644 --- a/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java @@ -123,6 +123,17 @@ public enum State { public double dispY; public double dispZ; public boolean dispInit; + /** The block most recently mined + the one before it, with their mine times: the drill head sweeps + * smoothly from the previous block to the current one over the real elapsed interval (synced). */ + private int lastMineX; + private int lastMineY; + private int lastMineZ; + private int prevMineX; + private int prevMineY; + private int prevMineZ; + private long lastMineTime; + private long prevMineTime; + private boolean hasLastMine; private final ContainerData dataAccess = new ContainerData() { @Override @@ -209,6 +220,42 @@ public int renderCurrentY() { return this.currentY; } + public boolean renderHasMine() { + return this.hasLastMine; + } + + public int renderMineX() { + return this.lastMineX; + } + + public int renderMineY() { + return this.lastMineY; + } + + public int renderMineZ() { + return this.lastMineZ; + } + + public int renderPrevMineX() { + return this.prevMineX; + } + + public int renderPrevMineY() { + return this.prevMineY; + } + + public int renderPrevMineZ() { + return this.prevMineZ; + } + + public long renderMineTime() { + return this.lastMineTime; + } + + public long renderPrevMineTime() { + return this.prevMineTime; + } + /** * Creative/gallery helper: adopt an already-built {@code region} (frame assumed placed) and drop * straight into a powered MINING state from {@code startY}, so a staged display mines for real. @@ -237,7 +284,13 @@ public void tick(Level level, BlockPos pos, BlockState blockState) { switch (this.state) { case IDLE -> tryActivate(serverLevel, pos); case BUILDING_FRAME -> buildFrame(serverLevel); - case MINING -> mine(serverLevel); + // Throttle the dig: only work every QUARRY_MINE_INTERVAL ticks so a fully-powered quarry + // mines at a sane pace instead of stripping blocks as fast as the buffer allows. + case MINING -> { + if (serverLevel.getGameTime() % miningInterval(serverLevel) == 0L) { + mine(serverLevel); + } + } case PAUSED -> resume(serverLevel, pos); case DONE -> { } default -> { } @@ -252,7 +305,7 @@ public void tick(Level level, BlockPos pos, BlockState blockState) { // Push a client update on a state change, and periodically while mining so the drill-head // renderer can follow the cursor. boolean stateChanged = this.state != before || this.state != this.lastSyncedState; - if (stateChanged || (this.state == State.MINING && serverLevel.getGameTime() % 5L == 0L)) { + if (stateChanged || this.state == State.MINING) { // every tick while mining → smooth head sweep this.lastSyncedState = this.state; serverLevel.sendBlockUpdated(pos, blockState, blockState, Block.UPDATE_CLIENTS); } @@ -343,18 +396,26 @@ private void buildFrame(ServerLevel level) { boolean changed = false; while (this.frameIndex < ring.size() && placedThisTick < 8) { BlockPos fp = ring.get(this.frameIndex); - if (level.getBlockState(fp).getBlock() instanceof QuarryFrameBlock) { + BlockState existing = level.getBlockState(fp); + if (existing.getBlock() instanceof QuarryFrameBlock) { this.frameIndex++; // already framed continue; } + // Never frame over the controller itself or any other block entity (chests, machines): + // a controller placed ON the region edge (in line with the corner landmarks — a natural + // placement) used to get replaced by a frame block here, which removed it and tore down + // its own frame. It "broke itself". Leave a gap in the ring instead. + if (fp.equals(this.worldPosition) || existing.hasBlockEntity()) { + this.frameIndex++; + continue; + } ItemStack casing = this.frameHandler.getStack(FRAME_SLOT); if (casing.isEmpty()) { setPaused("need_material"); return; } - // Always materialise the frame at the perimeter (replacing whatever is there) so the ring - // is visible regardless of terrain — marking a region on flat ground used to place NO - // frame blocks because every perimeter cell was solid. + // Materialise the frame at the perimeter (replacing terrain) so the ring is visible + // regardless of terrain — marking a region on flat ground used to place NO frame blocks. level.setBlock(fp, ModBlocks.QUARRY_FRAME.get().defaultBlockState(), Block.UPDATE_CLIENTS); casing.shrink(1); placedThisTick++; @@ -380,15 +441,15 @@ private void mine(ServerLevel level) { } int floor = level.getMinY(); int energyPerBlock = quarryEnergyPerBlock(); - int cap = blocksPerTick(level); ItemStack tool = miningTool(level); int columns = region.columns(); boolean changed = false; - // Skips (air, caves, already-cleared cells) don't cost energy or count toward the mined cap, - // so bound the cells examined per tick to avoid a spike when sweeping large empty volumes. + // Mine exactly ONE block per work cycle (the head then sweeps onto it); skips (air, caves, + // already-cleared cells) are free and don't count, so bound the scan to avoid a spike when + // sweeping large empty volumes. int scanned = 0; - for (int processed = 0; processed < cap && scanned < SCAN_BUDGET_PER_TICK; ) { + for (int processed = 0; processed < 1 && scanned < SCAN_BUDGET_PER_TICK; ) { scanned++; if (this.currentY < floor) { this.state = State.DONE; @@ -405,6 +466,13 @@ private void mine(ServerLevel level) { int x = target.getX(); int z = target.getZ(); + // Never dig the columns under the frame ring: the frame sits on the perimeter, so only the + // interior is excavated. This keeps the frame's footing and leaves clean pit walls. + if (region.isPerimeter(x, z)) { + this.cursor++; + continue; + } + if (isColumnSkipped(x, z)) { this.cursor++; continue; @@ -463,6 +531,23 @@ private void mine(ServerLevel level) { } level.removeBlock(target, false); spawnDrillFx(level, target); + // The head sweeps from the previously-mined block to this one over the real elapsed time. + if (this.hasLastMine) { + this.prevMineX = this.lastMineX; + this.prevMineY = this.lastMineY; + this.prevMineZ = this.lastMineZ; + this.prevMineTime = this.lastMineTime; + } else { + this.prevMineX = x; + this.prevMineY = this.currentY; + this.prevMineZ = z; + this.prevMineTime = level.getGameTime(); + } + this.lastMineX = x; + this.lastMineY = this.currentY; + this.lastMineZ = z; + this.lastMineTime = level.getGameTime(); + this.hasLastMine = true; this.energy.consume(energyPerBlock); this.cursor++; processed++; @@ -474,11 +559,16 @@ private void mine(ServerLevel level) { } } - /** Per-tick block ceiling = tier base × module speed × planet speed, ≥ 1. */ - private int blocksPerTick(ServerLevel level) { + /** + * Game-ticks between dig cycles (one block per cycle). Faster tier/modules/planet → shorter + * interval, so a higher rate means quicker single-block steps rather than multi-block bursts: + * {@code QUARRY_MINE_INTERVAL / (tier base × module speed × planet speed)}. Tier 1 (rate 2) on the + * Overworld ≈ {@code 8/2 = 4} ticks/block ≈ 5 blocks/s. + */ + private long miningInterval(ServerLevel level) { double planet = PlanetMiningProfile.forDimension(level.dimension()).speedMultiplier(); - double scaled = this.tier.baseBlocksPerCycle() * this.modules.speedMultiplier() * planet; - return Math.max(1, (int) Math.round(scaled)); + double rate = this.tier.baseBlocksPerCycle() * this.modules.speedMultiplier() * planet; + return Math.max(1L, Math.round(Tuning.QUARRY_MINE_INTERVAL / Math.max(0.01, rate))); } private int quarryEnergyPerBlock() { @@ -626,6 +716,22 @@ private void autoEject(ServerLevel level, BlockPos pos) { private void forceLoad(ServerLevel level, int cx, int cz) { long key = ((long) cx << 32) | (cz & 0xFFFFFFFFL); + // Pin ONLY the chunk we're actively digging. The dig sweeps one column (one chunk) at a time, + // so release every chunk we've already swept past — otherwise the forced-chunk set grows to + // cover the whole region, keeping it all loaded with persistent tickets and bloating every + // world save. forceLoad() only runs (and then breaks) for a single chunk per tick, so we never + // need more than the current one held. + if (this.forcedChunks.size() > (this.forcedChunks.contains(key) ? 1 : 0)) { + LongIterator it = this.forcedChunks.iterator(); + while (it.hasNext()) { + long k = it.nextLong(); + if (k != key) { + QuarryChunkLoader.CONTROLLER.forceChunk( + level, this.worldPosition, (int) (k >> 32), (int) k, false, false); + it.remove(); + } + } + } if (this.forcedChunks.add(key)) { QuarryChunkLoader.CONTROLLER.forceChunk(level, this.worldPosition, cx, cz, true, false); } @@ -653,13 +759,26 @@ private void setPaused(String reason) { @Override public void setRemoved() { + // setRemoved() ALSO fires on chunk unload (e.g. quitting the world), so it must NOT tear down + // the frame here — doing so deleted the whole frame every time the chunk unloaded, so it never + // survived a relaunch. Releasing the (purely transient) chunk tickets is correct on unload; + // they re-pin when the quarry reloads and resumes. The frame teardown lives in + // preRemoveSideEffects(), which fires only when the controller is actually broken/replaced. if (this.level instanceof ServerLevel serverLevel) { releaseForcedChunks(serverLevel); - removeFrame(serverLevel); } super.setRemoved(); } + /** Only on ACTUAL removal (controller broken/replaced), not chunk unload: reclaim the frame. */ + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + super.preRemoveSideEffects(pos, state); + if (this.level instanceof ServerLevel serverLevel) { + removeFrame(serverLevel); + } + } + /** Tear down the frame ring this controller built. */ private void removeFrame(ServerLevel level) { QuarryRegion region = this.region; @@ -690,6 +809,15 @@ protected void saveAdditional(ValueOutput output) { output.putInt("FrameIndex", this.frameIndex); output.putInt("CurrentY", this.currentY); output.putInt("Cursor", this.cursor); + output.putBoolean("HasMine", this.hasLastMine); + output.putInt("MineX", this.lastMineX); + output.putInt("MineY", this.lastMineY); + output.putInt("MineZ", this.lastMineZ); + output.putInt("PrevMineX", this.prevMineX); + output.putInt("PrevMineY", this.prevMineY); + output.putInt("PrevMineZ", this.prevMineZ); + output.putLong("MineTime", this.lastMineTime); + output.putLong("PrevMineTime", this.prevMineTime); QuarryRegion region = this.region; if (region != null) { output.putBoolean("HasRegion", true); @@ -719,6 +847,15 @@ protected void loadAdditional(ValueInput input) { this.frameIndex = input.getIntOr("FrameIndex", 0); this.currentY = input.getIntOr("CurrentY", 0); this.cursor = input.getIntOr("Cursor", 0); + this.hasLastMine = input.getBooleanOr("HasMine", false); + this.lastMineX = input.getIntOr("MineX", 0); + this.lastMineY = input.getIntOr("MineY", 0); + this.lastMineZ = input.getIntOr("MineZ", 0); + this.prevMineX = input.getIntOr("PrevMineX", this.lastMineX); + this.prevMineY = input.getIntOr("PrevMineY", this.lastMineY); + this.prevMineZ = input.getIntOr("PrevMineZ", this.lastMineZ); + this.lastMineTime = input.getLongOr("MineTime", 0L); + this.prevMineTime = input.getLongOr("PrevMineTime", 0L); this.region = input.getBooleanOr("HasRegion", false) ? QuarryRegion.load(input.childOrEmpty("Region")) : null; this.frameTotal = -1; diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_frame.png b/src/main/resources/assets/nerospace/textures/block/quarry_frame.png index 828890e831a11fe6842c24eb0938e21bee872f5e..e40fdf29ed02bc9e3035dd2b3534033de61e828d 100644 GIT binary patch delta 389 zcmV;00eb$|0-ytsB!6c~L_t(|oUM~FYXU(Kg}($LBFk~eAyEO#58^bAv#Y#Mu%;ETNnrYbR28P>G5!L?0Cf=6hevpj6! z;kk%oD{U~62XZSbCTa&Q{ubI@7dkrkr6 jyMH)n+x+QG6E5Mu=$!=CieH2400000NkvXXu0mjfgq*x@ delta 316 zcmV-C0mJ^F1J?qOB!3=BL_t(|oUKzUZo@zjeMtulh=MHRp2ILFSeOIk7=b-TfI)zv zPY~D>6ct4U1(p@CvZ7VAQ3}>Go;W6z{K$Uhv-94}E_{4E_UAhUAMbDY&zbh;J4#o8 zvmBvqfcg{F&LMU_c~mDHw;KRJ3_V(o2AZF!Ux=w@9j}taMw=& O0000 Date: Tue, 16 Jun 2026 11:54:49 +0800 Subject: [PATCH 4/5] Add mining chapter and quarry advancements Introduce a new "mining" chapter and quarry progression: adds four advancement JSONs (frame_casing, quarry_landmark, quarry_controller, upgrade_module), updates ModAdvancements to register the new advancement chain, and extends StarGuide to include the mining steps. Also add language entries (en_us.json / ModLanguageProvider) for the new chapter and guide steps. Documentation updated: clarify quarry behavior and framing/casing rules in Quarry-Controller.md and update Star-Guide.md chapter/step counts and mining chapter summary. --- .../assets/nerospace/lang/en_us.json | 9 +++ .../advancement/guide/frame_casing.json | 28 +++++++++ .../advancement/guide/quarry_controller.json | 28 +++++++++ .../advancement/guide/quarry_landmark.json | 28 +++++++++ .../advancement/guide/upgrade_module.json | 61 +++++++++++++++++++ .../nerospace/datagen/ModAdvancements.java | 23 +++++++ .../datagen/ModLanguageProvider.java | 21 +++++++ .../nerospace/progression/StarGuide.java | 5 ++ wiki/Quarry-Controller.md | 10 +-- wiki/Star-Guide.md | 10 ++- 10 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 src/generated/resources/data/nerospace/advancement/guide/frame_casing.json create mode 100644 src/generated/resources/data/nerospace/advancement/guide/quarry_controller.json create mode 100644 src/generated/resources/data/nerospace/advancement/guide/quarry_landmark.json create mode 100644 src/generated/resources/data/nerospace/advancement/guide/upgrade_module.json diff --git a/src/generated/resources/assets/nerospace/lang/en_us.json b/src/generated/resources/assets/nerospace/lang/en_us.json index 965d950..c7b69a0 100644 --- a/src/generated/resources/assets/nerospace/lang/en_us.json +++ b/src/generated/resources/assets/nerospace/lang/en_us.json @@ -131,6 +131,7 @@ "gui.nerospace.rocket.stations_none": "NO STATIONS", "gui.nerospace.rocket.tier": "Tier %s Rocket", "gui.nerospace.star_guide.chapter.machines": "Machines", + "gui.nerospace.star_guide.chapter.mining": "Mining", "gui.nerospace.star_guide.chapter.nerosium": "Nerosium", "gui.nerospace.star_guide.chapter.new_worlds": "New Worlds", "gui.nerospace.star_guide.chapter.power_grid": "Power Grid", @@ -150,6 +151,8 @@ "gui.nerospace.star_guide.step.configurator.text": "The Configurator sets per-face pipe modes (in / out / off) so you can route exactly what flows where.", "gui.nerospace.star_guide.step.cryo_suit": "Dressed for the Deep Cold", "gui.nerospace.star_guide.step.cryo_suit.text": "Glacira's cold drains an unprotected suit four times faster and frosts your visor. The glacite-lined Cryo Suit keeps you warm — all four pieces, or no shield.", + "gui.nerospace.star_guide.step.frame_casing": "Frameworks", + "gui.nerospace.star_guide.step.frame_casing.text": "Frame Casing is a hollow ring of nerosteel. The quarry spends one casing per frame block as it materialises its glowing structural frame around the region.", "gui.nerospace.star_guide.step.gas_tank": "Bottled Air", "gui.nerospace.star_guide.step.gas_tank.text": "Gas Tanks store oxygen piped from a generator. A tank by your base door acts as an airlock — suits refill from it automatically.", "gui.nerospace.star_guide.step.glacira": "Into the Cold", @@ -182,6 +185,10 @@ "gui.nerospace.star_guide.step.oxygen_suit_t2.text": "The cindrite-upgraded Tier 2 suit doubles your air and refills twice as fast. A mixed set counts as Tier 1 — wear all four pieces.", "gui.nerospace.star_guide.step.passive_generator": "Slow and Steady", "gui.nerospace.star_guide.step.passive_generator.text": "The Passive Generator trickles energy from a nerosium core for a long time — weaker than combustion but completely hands-off.", + "gui.nerospace.star_guide.step.quarry_controller": "Strip Miner", + "gui.nerospace.star_guide.step.quarry_controller.text": "Place the Controller beside the landmarks, load it with Frame Casing and feed it power. It builds the frame, then a gantry-mounted drill excavates the interior layer by layer down to bedrock — sucking up liquids, skipping tile-entity columns, and auto-ejecting drops into adjacent storage or pipes.", + "gui.nerospace.star_guide.step.quarry_landmark": "Stake a Claim", + "gui.nerospace.star_guide.step.quarry_landmark.text": "Place three Quarry Landmarks in an L to mark a rectangle — they project guide lines along the ground. Their reference height is the top of the dig.", "gui.nerospace.star_guide.step.raw_nerosium": "Strange Red Rock", "gui.nerospace.star_guide.step.raw_nerosium.text": "Nerosium ore veins thread the overworld's stone. Mine them with an iron pickaxe or better — the raw red metal is the seed of everything that follows.", "gui.nerospace.star_guide.step.rocket_fuel_canister": "Highly Flammable", @@ -208,6 +215,8 @@ "gui.nerospace.star_guide.step.thermal_suit.text": "Cindara's heat makes any other suit burn air four times faster. The Thermal Suit (Tier 2 piece + cindrite) shrugs it off — all four pieces, or no shield.", "gui.nerospace.star_guide.step.universal_pipe": "Connect Everything", "gui.nerospace.star_guide.step.universal_pipe.text": "One pipe carries everything: energy, fluids, gas and items. Pipes form networks that balance their contents and feed adjacent machines.", + "gui.nerospace.star_guide.step.upgrade_module": "Tune It Up", + "gui.nerospace.star_guide.step.upgrade_module.text": "Slot cross-machine upgrade modules into the Controller: Speed digs faster, Efficiency cuts power use, and Fortune or Silk Touch change what the dig drops — just like enchanting your pickaxe.", "gui.nerospace.terraform_monitor.hydration": "Water: %s", "gui.nerospace.terraform_monitor.no_link": "No Terraformer within 32 blocks", "gui.nerospace.terraform_monitor.radii": "Radii: %s / %s / %s", diff --git a/src/generated/resources/data/nerospace/advancement/guide/frame_casing.json b/src/generated/resources/data/nerospace/advancement/guide/frame_casing.json new file mode 100644 index 0000000..c6b0d59 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/guide/frame_casing.json @@ -0,0 +1,28 @@ +{ + "parent": "nerospace:greenxertz", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "nerospace:frame_casing" + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "display": { + "description": "Craft Frame Casing from nerosteel — the quarry builds its frame from these", + "icon": { + "id": "nerospace:frame_casing" + }, + "title": "Frameworks" + }, + "requirements": [ + [ + "has_item" + ] + ], + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/guide/quarry_controller.json b/src/generated/resources/data/nerospace/advancement/guide/quarry_controller.json new file mode 100644 index 0000000..de09a7f --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/guide/quarry_controller.json @@ -0,0 +1,28 @@ +{ + "parent": "nerospace:guide/quarry_landmark", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "nerospace:quarry_controller" + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "display": { + "description": "Build a Quarry Controller and automate your digging", + "icon": { + "id": "nerospace:quarry_controller" + }, + "title": "Strip Miner" + }, + "requirements": [ + [ + "has_item" + ] + ], + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/guide/quarry_landmark.json b/src/generated/resources/data/nerospace/advancement/guide/quarry_landmark.json new file mode 100644 index 0000000..e7aaed4 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/guide/quarry_landmark.json @@ -0,0 +1,28 @@ +{ + "parent": "nerospace:guide/frame_casing", + "criteria": { + "has_item": { + "conditions": { + "items": [ + { + "items": "nerospace:quarry_landmark" + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "display": { + "description": "Craft Quarry Landmarks to mark out a rectangular mining region", + "icon": { + "id": "nerospace:quarry_landmark" + }, + "title": "Stake a Claim" + }, + "requirements": [ + [ + "has_item" + ] + ], + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/src/generated/resources/data/nerospace/advancement/guide/upgrade_module.json b/src/generated/resources/data/nerospace/advancement/guide/upgrade_module.json new file mode 100644 index 0000000..0c94c77 --- /dev/null +++ b/src/generated/resources/data/nerospace/advancement/guide/upgrade_module.json @@ -0,0 +1,61 @@ +{ + "parent": "nerospace:guide/quarry_controller", + "criteria": { + "efficiency": { + "conditions": { + "items": [ + { + "items": "nerospace:efficiency_module" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "fortune": { + "conditions": { + "items": [ + { + "items": "nerospace:fortune_module" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "silk_touch": { + "conditions": { + "items": [ + { + "items": "nerospace:silk_touch_module" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "speed": { + "conditions": { + "items": [ + { + "items": "nerospace:speed_module" + } + ] + }, + "trigger": "minecraft:inventory_changed" + } + }, + "display": { + "description": "Craft an upgrade module — speed, efficiency, fortune or silk touch", + "icon": { + "id": "nerospace:speed_module" + }, + "title": "Tune It Up" + }, + "requirements": [ + [ + "speed", + "efficiency", + "fortune", + "silk_touch" + ] + ], + "sends_telemetry_event": true +} \ No newline at end of file diff --git a/src/main/java/za/co/neroland/nerospace/datagen/ModAdvancements.java b/src/main/java/za/co/neroland/nerospace/datagen/ModAdvancements.java index 93d31b5..47f9893 100644 --- a/src/main/java/za/co/neroland/nerospace/datagen/ModAdvancements.java +++ b/src/main/java/za/co/neroland/nerospace/datagen/ModAdvancements.java @@ -133,6 +133,29 @@ public void generate(HolderLookup.Provider registries, Consumer icon, String ad step("rocket_tier_4", () -> ModItems.ROCKET_TIER_4.get(), "guide/rocket_tier_4"), step("glacira", () -> ModItems.GLACITE.get(), "glacira"), step("glacite", () -> ModBlocks.GLACITE_BLOCK.get(), "guide/glacite"))), + new Chapter("mining", List.of( + step("quarry_landmark", () -> ModBlocks.QUARRY_LANDMARK.get(), "guide/quarry_landmark"), + step("frame_casing", () -> ModItems.FRAME_CASING.get(), "guide/frame_casing"), + step("quarry_controller", () -> ModBlocks.QUARRY_CONTROLLER.get(), "guide/quarry_controller"), + step("upgrade_module", () -> ModItems.SPEED_MODULE.get(), "guide/upgrade_module"))), new Chapter("vacuum", List.of( step("oxygen_generator", () -> ModBlocks.OXYGEN_GENERATOR.get(), "guide/oxygen_generator"), step("gas_tank", () -> ModBlocks.GAS_TANK.get(), "guide/gas_tank"), diff --git a/wiki/Quarry-Controller.md b/wiki/Quarry-Controller.md index 7d76dc0..16407d1 100644 --- a/wiki/Quarry-Controller.md +++ b/wiki/Quarry-Controller.md @@ -1,7 +1,7 @@ # Quarry Controller A BuildCraft-style automated miner: mark out an area with landmarks and the controller builds a -glowing frame and excavates the whole rectangle, layer by layer, down to bedrock. +glowing frame and excavates the interior, layer by layer, down to bedrock. ## Overview @@ -10,8 +10,9 @@ Landmarks](Quarry-Landmark)**, place the controller beside it, give it **frame m **power**, and it: 1. **Builds a frame** — a glowing, see-through structural ring around the claimed rectangle. -2. **Mines** the whole rectangle **layer by layer**, top to bedrock, like a 3D printer in reverse — - a drill head travels the gantry to each block. +2. **Mines** the rectangle's **interior** (the columns under the frame ring are left intact) **layer + by layer**, top to bedrock, like a 3D printer in reverse — a drill head travels the gantry to each + block, one block at a time. 3. **Buffers and auto-ejects** everything it digs: mined items into an internal inventory, and any liquids it hits into an internal fluid tank — both push out to adjacent storage / pipes. @@ -41,7 +42,8 @@ I I I 2. **Place the controller next to / in line with** a landmark — it scans along the axes to find the cluster, then **consumes the landmarks** and starts. 3. **Put [Frame Casing](Upgrade-Modules) in the frame slot** (top-left of the GUI). The frame costs - **one casing per open-air perimeter cell** (cells already backed by terrain are free). + **one casing per perimeter cell** (the controller's own cell and any block entities on the + perimeter are skipped, leaving a gap rather than overwriting them). 4. **Pipe power in** (see below). Building the frame is free, but **mining needs energy**. ## How it works diff --git a/wiki/Star-Guide.md b/wiki/Star-Guide.md index 5ba7c66..6080509 100644 --- a/wiki/Star-Guide.md +++ b/wiki/Star-Guide.md @@ -5,7 +5,7 @@ The in-game progression guide — a pedestal, a book, and a live quest tree. ## Overview The Star Guide is Nerospace's built-in tutorial and progression tracker: an interactive tree of -**7 chapters and 31 steps**, from your first nerosium ore to a fully matured terraformed world. +**8 chapters and 35 steps**, from your first nerosium ore to a fully matured terraformed world. Completion is tracked per player through the mod's **advancement tree**, so the guide is always live — finish something anywhere and the tree lights up. @@ -30,8 +30,12 @@ S S S the pedestal or sneak-right-clicking pops the book back out). An installed pedestal projects a rotating **hologram of your next goal**, and right-clicking it opens the same GUI. - **The tree:** chapters cover **Nerosium → Machines → Power Grid → Rocketry → New Worlds → - Surviving Vacuum → Terraforming**. Completed steps pulse; each step carries guide text telling you - exactly what to do next. + [Mining](Quarry-Controller) → Surviving Vacuum → Terraforming**. Completed steps pulse; each step + carries guide text telling you exactly what to do next. +- **Mining chapter:** the quarry automation track — [Quarry Landmark](Quarry-Landmark) → + [Frame Casing](Upgrade-Modules) → [Quarry Controller](Quarry-Controller) → + [Upgrade Modules](Upgrade-Modules). Gated behind nerosteel (Greenxertz), since the frame is built + from it. - **Comparator:** the pedestal emits a signal when a book is installed. ## Details From 68442b93e819b5ebc4f849f9e955ba9f43bbea29 Mon Sep 17 00:00:00 2001 From: Dario Maselli <117168592+Dario-Maselli@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:44:47 +0800 Subject: [PATCH 5/5] Split quarry gantry and drill textures Use distinct textures for the quarry gantry and drill head in the renderer and update texture generation accordingly. QuarryControllerRenderer: remove unused RenderType import, add GANTRY_TEX and DRILL_TEX identifiers, and submit separate RenderTypes.entityCutout calls for gantry and drill. Assets: add quarry_gantry.png and quarry_drill.png and update quarry_frame.png. tools/gen_textures.py: factor common strut creation into _gen_quarry_strut, make gen_quarry_frame use the generic function, add gen_quarry_gantry and gen_quarry_drill, and invoke them from main so all three textures are generated. --- .../client/QuarryControllerRenderer.java | 21 +++++---- .../nerospace/textures/block/quarry_drill.png | Bin 0 -> 417 bytes .../nerospace/textures/block/quarry_frame.png | Bin 416 -> 441 bytes .../textures/block/quarry_gantry.png | Bin 0 -> 464 bytes tools/gen_textures.py | 40 ++++++++++++------ 5 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/main/resources/assets/nerospace/textures/block/quarry_drill.png create mode 100644 src/main/resources/assets/nerospace/textures/block/quarry_gantry.png diff --git a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java index 73aa386..8d46571 100644 --- a/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java @@ -5,7 +5,6 @@ import com.mojang.math.Axis; import net.minecraft.client.renderer.SubmitNodeCollector; -import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.feature.ModelFeatureRenderer; import net.minecraft.client.renderer.rendertype.RenderTypes; @@ -37,9 +36,12 @@ public class QuarryControllerRenderer implements BlockEntityRenderer { - /** The frame texture, reused so the gantry + bit match the frame ring exactly. */ - private static final Identifier TEX = - Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/block/quarry_frame.png"); + /** Purple strut texture for the moving gantry (bridge / trucks / carriage / shaft). */ + private static final Identifier GANTRY_TEX = + Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/block/quarry_gantry.png"); + /** Red/steel strut texture for the drill head (kept its original colour). */ + private static final Identifier DRILL_TEX = + Identifier.fromNamespaceAndPath(Nerospace.MODID, "textures/block/quarry_drill.png"); /** Full-bright light (the frame is emissive); per-face lighting still shades by normal. */ private static final int FULL_BRIGHT = 0x00F000F0; /** Top of the bit (chuck collar top, local Y) — the support shaft meets it here. */ @@ -145,14 +147,15 @@ public void submit(QuarryControllerRenderState s, PoseStack poseStack, SubmitNod if (!s.active) { return; } - RenderType rt = RenderTypes.entityCutout(TEX); - // Gantry: world-relative, at the base pose. - collector.order(1).submitCustomGeometry(poseStack, rt, (pose, c) -> drawGantry(s, pose, c)); - // Drill head: spun around the vertical axis at the dig cell. + // Gantry (purple): world-relative, at the base pose. + collector.order(1).submitCustomGeometry(poseStack, RenderTypes.entityCutout(GANTRY_TEX), + (pose, c) -> drawGantry(s, pose, c)); + // Drill head (red): spun around the vertical axis at the dig cell. poseStack.pushPose(); poseStack.translate(s.hx, s.hy, s.hz); poseStack.mulPose(Axis.YP.rotationDegrees(s.headSpin)); - collector.order(1).submitCustomGeometry(poseStack, rt, (pose, c) -> drawBit(pose, c)); + collector.order(1).submitCustomGeometry(poseStack, RenderTypes.entityCutout(DRILL_TEX), + (pose, c) -> drawBit(pose, c)); poseStack.popPose(); } diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_drill.png b/src/main/resources/assets/nerospace/textures/block/quarry_drill.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9768da35311671b9a42010eef42e2fb413f252 GIT binary patch literal 417 zcmV;S0bc%zP)A3$p4K8o{%kRf4ChlI|`Hvo)AD=F9?bt-()Wo$zG@YT+4T)1T z_b2@12U}l6adTcTaR34t;(C75K_v*`V)7I_DqUXhsHC};ZUCAFrn_k(#}Rp^7zq!6 zk?^Q=Nu|rqV}QX;7|i_%uIF3NaqS?4iyTLMs)Rlx!{k`x3{9auRv<%0!n3(RhV;X5 z8nzik(^-dwd06QZAzWKGoNmWp5yhx zwL>67(rwaM$TQU$&#bS_r_yC7Z&o@qe)3FN7X4_mFk0)=jTz*b>g+w5&d4)mJ@>T` zE{FYhW8qX57#46{r|}%fu=BM6Yrz3Xx5?G8^hwssj_%iwMQ-IEAKdL(V=5mp00000 LNkvXXu0mjf0E4(~ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_frame.png b/src/main/resources/assets/nerospace/textures/block/quarry_frame.png index e40fdf29ed02bc9e3035dd2b3534033de61e828d..87be25c34638d90f6ed9515dc34b50689f1e3e65 100644 GIT binary patch delta 415 zcmV;Q0bu^11Gxi`B!7TOL_t(|oUN0+YU4l@g};~}h#7O4S7J;bu*R(S#!TWz@nlR}jX5vB-2#b8Vj!^HtNPNB@K9nu7RsyXv>?>*<3z%|400H&)3BJ@9>Y{pQGn&woLwnBD9F@7e3d z{%_;qD1U{#D34@$Ip+XR|NevnJ%eWT1Pm_gn<{Vs{TQ+D@eXdD1U_CZQv3h_002ov JPDHLkV1iDO$ld?| delta 389 zcmV;00eb$q1E2$tB!6c~L_t(|oUM~FYXU(Kg}($LBFk~eAyEO#58^bAv#Y#Mu%;ETNnrYbR28P>G5!L?0Cf=6hevpj6! z;kk%oD{U~62XZSbCTa&Q{ubI@7dkrkr6 jyMH)n+x+QG6E5Mu=$!=CieH2400000NkvXXu0mjfSc<$- diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_gantry.png b/src/main/resources/assets/nerospace/textures/block/quarry_gantry.png new file mode 100644 index 0000000000000000000000000000000000000000..f349f944ab2e864d08258dbf1d456f2ff87f4e2a GIT binary patch literal 464 zcmV;>0WbcEP)y1)5#7R`E@qIWBvZhrcy^lnA5$Vp}zXAM6N z8_IMCK=rckC0vG55ddvddnwKul9~23!s}WJr^(!d2~~`#N?#;;JP0f4PKnBGkCsZ*fcO317UZ&(1dk_pYMezg<_~vOchu7X-;R3y_CwKf{ z;H<$}YO7V8F=lfl?998m~*5 z#lBF*ULN#&Ayx(e$3F*#A^BVFNLf?8>__i!wPRmA0`M2