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..c7b69a0 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)", @@ -121,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", @@ -140,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", @@ -172,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", @@ -198,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", @@ -222,7 +241,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 +296,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..d78bce8 --- /dev/null +++ b/src/generated/resources/assets/nerospace/models/block/quarry_frame.json @@ -0,0 +1,393 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "down": { + "texture": "#side" + }, + "east": { + "texture": "#side" + }, + "north": { + "texture": "#side" + }, + "south": { + "texture": "#side" + }, + "up": { + "texture": "#side" + }, + "west": { + "texture": "#side" + } + }, + "from": [ + 0, + 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": { + "particle": "nerospace:block/quarry_frame", + "side": "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/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/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..2427e2c 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,14 @@ 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; + /** + * 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. */ @@ -262,6 +274,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 +327,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..3b99b49 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderState.java @@ -0,0 +1,29 @@ +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 + drill bit. */ + public int accent; + /** 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 new file mode 100644 index 0000000..8d46571 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/client/QuarryControllerRenderer.java @@ -0,0 +1,263 @@ +package za.co.neroland.nerospace.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; + +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.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; + +/** + * 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): + * + * 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 { + + /** 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. */ + 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() { + return new QuarryControllerRenderState(); + } + + @Override + 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) { + BlockEntityRenderer.super.extractRenderState(be, s, partialTick, cameraPos, breakProgress); + QuarryRegion r = be.renderRegion(); + QuarryControllerBlockEntity.State st = be.renderState(); + if (r == null || st == QuarryControllerBlockEntity.State.IDLE || be.getLevel() == null) { + 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 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 { + // 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(); + + 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 + public void submit(QuarryControllerRenderState s, PoseStack poseStack, SubmitNodeCollector collector, + CameraRenderState cameraState) { + if (!s.active) { + return; + } + // 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, RenderTypes.entityCutout(DRILL_TEX), + (pose, c) -> drawBit(pose, c)); + poseStack.popPose(); + } + + /** 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); + } + + /** 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); + } + } + } +} 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..f2b079c 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,27 @@ 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 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 " + "line, oxygen generator + lever, terraformer crew + lever — flip a lever to start " @@ -397,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/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/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 { 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); 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..02fed5c --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryControllerBlockEntity.java @@ -0,0 +1,1022 @@ +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; + /** 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 + 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; + } + + 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. + */ + 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); + // 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 -> { } + } + + // 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) { // every tick while mining → smooth head sweep + 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++; // 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; + } + // 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++; + 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(); + ItemStack tool = miningTool(level); + int columns = region.columns(); + boolean changed = false; + + // 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 < 1 && 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(); + + // 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; + } + + 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); + // 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++; + changed = true; + } + + if (changed) { + setChanged(); + } + } + + /** + * 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 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() { + 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); + // 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); + } + } + + 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() { + // 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); + } + 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; + 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); + 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); + 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.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; + 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..85bab33 --- /dev/null +++ b/src/main/java/za/co/neroland/nerospace/machine/quarry/QuarryFrameBlock.java @@ -0,0 +1,32 @@ +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"). + * + *

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 { + + 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/progression/StarGuide.java b/src/main/java/za/co/neroland/nerospace/progression/StarGuide.java index 3f095cb..dc9eec9 100644 --- a/src/main/java/za/co/neroland/nerospace/progression/StarGuide.java +++ b/src/main/java/za/co/neroland/nerospace/progression/StarGuide.java @@ -82,6 +82,11 @@ private static Step step(String id, Supplier 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/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 0000000..d7c05b1 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/quarry_controller.png differ 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 0000000..ee9768d Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/quarry_drill.png differ diff --git a/src/main/resources/assets/nerospace/textures/block/quarry_frame.png b/src/main/resources/assets/nerospace/textures/block/quarry_frame.png new file mode 100644 index 0000000..87be25c Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/quarry_frame.png differ 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 0000000..f349f94 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/quarry_gantry.png differ 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 0000000..617692e Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/quarry_landmark.png differ 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 0000000..d29be5f Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/block/trash_can.png differ 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 0000000..28b22b3 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/gui/quarry.png differ 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 0000000..3ae35f6 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/efficiency_module.png differ 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 0000000..21bb95f Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/fortune_module.png differ 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 0000000..17dba83 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/frame_casing.png differ diff --git a/src/main/resources/assets/nerospace/textures/item/silk_touch_module.png b/src/main/resources/assets/nerospace/textures/item/silk_touch_module.png new file mode 100644 index 0000000..d5cdac8 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/silk_touch_module.png differ diff --git a/src/main/resources/assets/nerospace/textures/item/speed_module.png b/src/main/resources/assets/nerospace/textures/item/speed_module.png new file mode 100644 index 0000000..a88b430 Binary files /dev/null and b/src/main/resources/assets/nerospace/textures/item/speed_module.png differ 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..817b54f 100644 --- a/tools/gen_textures.py +++ b/tools/gen_textures.py @@ -2635,7 +2635,209 @@ 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_strut(name, rail, node, seed): + # Energised strut texture: steel body + coloured energy rails + glow weld nodes. Shared shape for + # the quarry frame block model AND (via entity render types) the moving gantry + drill head, each + # in its own colour. Narrow beam faces sample a thin UV slice, so a rail sits every 4px on both + # axes so any slice lights up. + rng = random.Random(seed) + img = new_img() + px = img.load() + noise_fill(img, METAL, rng) + bevel(img, METAL_L, METAL_D) + for i in range(S): + for k in range(0, S, 4): + px[k, i] = rail + px[i, k] = rail + for y in range(0, S, 4): + for x in range(0, S, 4): + px[x, y] = node + save(img, os.path.join(BLOCK_DIR, name + ".png")) + + +def gen_quarry_frame(): + # Frame ring — BLUE. + _gen_quarry_strut("quarry_frame", (52, 120, 246, 255), (150, 198, 255, 255), 1303) + + +def gen_quarry_gantry(): + # Moving gantry (bridge / carriage / support shaft) — PURPLE. + _gen_quarry_strut("quarry_gantry", N_PURPLE, (200, 130, 255, 255), 1304) + + +def gen_quarry_drill(): + # Drill head — keeps the original red/steel look. + _gen_quarry_strut("quarry_drill", N_RED, N_GLOW, 1305) + + +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_quarry_gantry() + gen_quarry_drill() + 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..16407d1 --- /dev/null +++ b/wiki/Quarry-Controller.md @@ -0,0 +1,118 @@ +# Quarry Controller + +A BuildCraft-style automated miner: mark out an area with landmarks and the controller builds a +glowing frame and excavates the interior, 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 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. + +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 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 + +- **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-cycle ceiling × your modules' speed bonus × the planet's speed + factor. Base cost is **40 FE per block** (lowered by Efficiency modules). The dig is **paced** (a + work cycle every few ticks) so even with unlimited power it mines at a steady rate, not instantly. +- **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. +- **Interior only:** it excavates the **inside** of the rectangle and never digs the columns under + the frame ring, so the frame keeps its footing and the pit walls stay clean. +- **Drill head + gantry:** while it runs, a solid 3-D **gantry** rides the frame — a bridge beam with + end trucks, a carriage that tracks the dig column, and a support shaft — and a spinning 3-D + **drill bit** (chuck, flutes, and a glowing tapered tip) descends point-down to the exact block + being mined. +- **The frame** is a real 3-D structure — glowing corner posts and edge rails with an open, + see-through centre (so you can watch the dig through it). +- **Far edges:** a large area is force-loaded **one chunk at a time** while actively mining, so the + dig keeps going as you range its edges; each chunk is released as soon as the dig moves past it (and + any remainder when it finishes or is removed), so the quarry never pins the whole region — keeping + memory use and world-save time low. +- **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..6080509 100644 --- a/wiki/Star-Guide.md +++ b/wiki/Star-Guide.md @@ -3,31 +3,41 @@ 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. ## 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 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 + - 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)