From 302acaaa180b1d5d6c7d0365ec9214f1d524a623 Mon Sep 17 00:00:00 2001 From: XevianLight <63034748+XevianLight@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:17:19 -0600 Subject: [PATCH] More partition and station backend --- doc/Aphelion/.obsidian/app.json | 3 + doc/Aphelion/.obsidian/appearance.json | 1 + doc/Aphelion/.obsidian/core-plugins.json | 33 +++ doc/Aphelion/.obsidian/graph.json | 22 ++ doc/Aphelion/.obsidian/workspace-mobile.json | 166 +++++++++++++ doc/Aphelion/.obsidian/workspace.json | 202 ++++++++++++++++ doc/Aphelion/Planets.md | 0 doc/Aphelion/Tiers/Tier 1 - Beginning.md | 16 ++ ...er 2 - Orbits and Interplanetary Travel.md | 16 ++ ...ier 3 - Ion Propulsion and Fusion Power.md | 12 + ... 4 - Astrophage and Interstellar Travel.md | 10 + doc/Aphelion/Tiers/Tier 5 - Warp.md | 2 + .../Tier 1 — Launch — Chemical Rocketry.md | 22 ++ ...bit — Nuclear Rocketry and Space Stations.md | 17 ++ ...terplanetary Space Stations, Ion Propulsion.md | 13 ++ ...” Warp — Warp Technology and Fusion Power.md | 16 ++ .../custom/StationFlightComputerBlock.java | 12 + .../StationFlightComputerBlockEntity.java | 4 +- .../StationRocketEngineBlockEntity.java | 4 +- .../custom/base/StationEngineBlockEntity.java | 7 + .../aphelion/client/AphelionDebugOverlay.java | 21 +- .../saveddata/SpacePartitionSavedData.java | 18 +- .../core/saveddata/types/EnvironmentData.java | 2 - .../core/saveddata/types/PartitionData.java | 175 +++++++++++--- .../core/saveddata/types/PosData.java | 218 ++++++++++++++++++ .../aphelion/network/PartitionSync.java | 9 +- .../xevianlight/aphelion/planet/Planet.java | 8 +- .../aphelion/planet/PlanetCache.java | 27 ++- .../resources/data/aphelion/planet/earth.json | 8 + 29 files changed, 1022 insertions(+), 42 deletions(-) create mode 100644 doc/Aphelion/.obsidian/app.json create mode 100644 doc/Aphelion/.obsidian/appearance.json create mode 100644 doc/Aphelion/.obsidian/core-plugins.json create mode 100644 doc/Aphelion/.obsidian/graph.json create mode 100644 doc/Aphelion/.obsidian/workspace-mobile.json create mode 100644 doc/Aphelion/.obsidian/workspace.json create mode 100644 doc/Aphelion/Planets.md create mode 100644 doc/Aphelion/Tiers/Tier 1 - Beginning.md create mode 100644 doc/Aphelion/Tiers/Tier 2 - Orbits and Interplanetary Travel.md create mode 100644 doc/Aphelion/Tiers/Tier 3 - Ion Propulsion and Fusion Power.md create mode 100644 doc/Aphelion/Tiers/Tier 4 - Astrophage and Interstellar Travel.md create mode 100644 doc/Aphelion/Tiers/Tier 5 - Warp.md create mode 100644 doc/OLD/Tier 1 — Launch — Chemical Rocketry.md create mode 100644 doc/OLD/Tier 2 — Orbit — Nuclear Rocketry and Space Stations.md create mode 100644 doc/OLD/Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md create mode 100644 doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md create mode 100644 src/main/java/net/xevianlight/aphelion/core/saveddata/types/PosData.java create mode 100644 src/main/resources/data/aphelion/planet/earth.json diff --git a/doc/Aphelion/.obsidian/app.json b/doc/Aphelion/.obsidian/app.json new file mode 100644 index 0000000..6abe4c1 --- /dev/null +++ b/doc/Aphelion/.obsidian/app.json @@ -0,0 +1,3 @@ +{ + "alwaysUpdateLinks": true +} \ No newline at end of file diff --git a/doc/Aphelion/.obsidian/appearance.json b/doc/Aphelion/.obsidian/appearance.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/doc/Aphelion/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/doc/Aphelion/.obsidian/core-plugins.json b/doc/Aphelion/.obsidian/core-plugins.json new file mode 100644 index 0000000..639b90d --- /dev/null +++ b/doc/Aphelion/.obsidian/core-plugins.json @@ -0,0 +1,33 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "footnotes": false, + "properties": true, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": true, + "bases": true, + "webviewer": false +} \ No newline at end of file diff --git a/doc/Aphelion/.obsidian/graph.json b/doc/Aphelion/.obsidian/graph.json new file mode 100644 index 0000000..e206ceb --- /dev/null +++ b/doc/Aphelion/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 1.0000000000000004, + "close": false +} \ No newline at end of file diff --git a/doc/Aphelion/.obsidian/workspace-mobile.json b/doc/Aphelion/.obsidian/workspace-mobile.json new file mode 100644 index 0000000..8dad7d1 --- /dev/null +++ b/doc/Aphelion/.obsidian/workspace-mobile.json @@ -0,0 +1,166 @@ +{ + "main": { + "id": "e4858043d22b0b8f", + "type": "split", + "children": [ + { + "id": "532e7f40f2f6d233", + "type": "tabs", + "children": [ + { + "id": "dd3aa3882d71522e", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "ef492a321aaff5d7", + "type": "mobile-drawer", + "children": [ + { + "id": "8ac904c1d115ad34", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "b5703ef3f8bb0a5b", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "0263f9cd9603ec29", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "32aff3798d7e7786", + "type": "leaf", + "state": { + "type": "all-properties", + "state": { + "sortOrder": "frequency", + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-archive", + "title": "All properties" + } + }, + { + "id": "173117389aed98b1", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ], + "currentTab": 0 + }, + "right": { + "id": "8a74ec400de5884c", + "type": "mobile-drawer", + "children": [ + { + "id": "79d5d838a514d7e0", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks" + } + }, + { + "id": "ef29c5a2c8cb51ca", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links" + } + }, + { + "id": "132e09c024f26162", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "Outline" + } + } + ], + "currentTab": 0 + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false, + "bases:Create new base": false + } + }, + "active": "dd3aa3882d71522e", + "lastOpenFiles": [] +} \ No newline at end of file diff --git a/doc/Aphelion/.obsidian/workspace.json b/doc/Aphelion/.obsidian/workspace.json new file mode 100644 index 0000000..e5df3a5 --- /dev/null +++ b/doc/Aphelion/.obsidian/workspace.json @@ -0,0 +1,202 @@ +{ + "main": { + "id": "b1b5384706f6944e", + "type": "split", + "children": [ + { + "id": "9bf552ce156753fa", + "type": "tabs", + "children": [ + { + "id": "aee743ac57c40a5d", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "Tiers/Tier 4 - Astrophage and Interstellar Travel.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "Tier 4 - Astrophage and Interstellar Travel" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "f83888d158679852", + "type": "split", + "children": [ + { + "id": "358b12ed12d59ae3", + "type": "tabs", + "children": [ + { + "id": "1f11e2a54128b8fa", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "3c8e2b518b5b99e4", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "76ec0d5a263cf8dd", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "e5dc1e339bc900c8", + "type": "split", + "children": [ + { + "id": "76abf2cf048a9890", + "type": "tabs", + "children": [ + { + "id": "0ace5339fb99d8fe", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks for Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion" + } + }, + { + "id": "973d50256169532a", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links from Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion" + } + }, + { + "id": "2bd0942d425edc95", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "694a7d044328eb0a", + "type": "leaf", + "state": { + "type": "all-properties", + "state": { + "sortOrder": "frequency", + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-archive", + "title": "All properties" + } + }, + { + "id": "21b89c93ba0de8e3", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md", + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "Outline of Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false, + "bases:Create new base": false + } + }, + "active": "aee743ac57c40a5d", + "lastOpenFiles": [ + "Tiers/Tier 3 - Ion Propulsion and Fusion Power.md", + "Tiers/Tier 2 - Orbits and Interplanetary Travel.md", + "Tiers/Tier 1 - Beginning.md", + "Tiers/Tier 5 - Warp.md", + "Tiers/Tier 4 - Astrophage and Interstellar Travel.md", + "Planets.md", + "OLD/Tier 1 — Launch — Chemical Rocketry.md", + "OLD/Tier 2 — Orbit — Nuclear Rocketry and Space Stations.md", + "OLD/Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md", + "OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md", + "OLD", + "Moon.md", + "Tiers" + ] +} \ No newline at end of file diff --git a/doc/Aphelion/Planets.md b/doc/Aphelion/Planets.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/Aphelion/Tiers/Tier 1 - Beginning.md b/doc/Aphelion/Tiers/Tier 1 - Beginning.md new file mode 100644 index 0000000..31dc0d6 --- /dev/null +++ b/doc/Aphelion/Tiers/Tier 1 - Beginning.md @@ -0,0 +1,16 @@ + +Gameplay goals: +This tier represents the beginnings of the mod. Here we have basic rocketry, machinery, and materials. [[Rocket]]s enable travel between a [[Planets|Planet]] and its [[Moons]], as well as any other planetary satellites such as future [[Space Stations]]. + +Materials and Stuffs: +[[Steel]] (Structural) +[[Aluminum]] (Structural) +[[Tin]]? (For electronics) +[[Silicon]]? from Nether Quartz (For electronics) (Or use redstone and save silicon electronics for the modpack) +Vanilla Ores (Copper, Iron, Gold) +[[Oil]] + +Machines: +[[Electric Arc Furnace]] (Processing steel and other mod metals. Can be substituted by other mods recipes. I.E. IE, Mekanism, etc) +[[Rocket Assembler]] +[[Chemical Plant]] \ No newline at end of file diff --git a/doc/Aphelion/Tiers/Tier 2 - Orbits and Interplanetary Travel.md b/doc/Aphelion/Tiers/Tier 2 - Orbits and Interplanetary Travel.md new file mode 100644 index 0000000..fa26cf1 --- /dev/null +++ b/doc/Aphelion/Tiers/Tier 2 - Orbits and Interplanetary Travel.md @@ -0,0 +1,16 @@ + +Gameplay goals: +This tier is marked by the construction of a [[Space Stations|Space Station]]. These platforms allow orbital bases, and [[Interplanetary Travel|interplanetary travel]]. [[Station Rocket Engine|Station rocket engines]] allow for [[rocket fuel]] powered propulsion to travel between [[Planets]]. Players are expected to have visited [[The Moon]] to obtain materials needed to construct a space station. [[Nuclear Power]] may also become available here. After unlocking interplanetary travel, materials needed to create [[Ion Engines]] become available. + +Materials: +[[Titanium]] (Structural) +[[Uranium]] (Power) +Cobalt (Secondary resource for reactors and alloys?) + +Machines: +[[Station Flight Computer]] +[[Nuclear Fission Reactor]] +[[Oxygen Distributor]] +[[Gravity Generator]] +[[Vacuum Arc Furnace]] (Titanium processing) +[[Station Rocket Engine]] diff --git a/doc/Aphelion/Tiers/Tier 3 - Ion Propulsion and Fusion Power.md b/doc/Aphelion/Tiers/Tier 3 - Ion Propulsion and Fusion Power.md new file mode 100644 index 0000000..9e2935c --- /dev/null +++ b/doc/Aphelion/Tiers/Tier 3 - Ion Propulsion and Fusion Power.md @@ -0,0 +1,12 @@ + +Gameplay Goals: [[Ion Engines]] allow space stations to travel between planets without needing to ship up [[Rocket Fuel]], using FE to create propulsion directly, though providing slightly less thrust. To help with this power draw, [[Fusion Energy]] also becomes available here, which will be needed for [[Tier 4 - Astrophage and Interstellar Travel]]. + +Materials: +[[Neodymium]] (Supermagnets for fusion and ion tech) +[[Tungsten]] (Heat shielding for inside of fusion reactors, maybe add tungsten carbide? Complexity here can be saved for the modpack) +Gasses such as [[Ammonia]], [[Helium-3]], and [[CO2]] from [[Gas Giant Skimmer|Gas Giant Skimming]] + +Machines: +[[Fusion Energy|Fusion Reactor]] +[[Ion Engines]] +[[Gas Giant Skimmer]] diff --git a/doc/Aphelion/Tiers/Tier 4 - Astrophage and Interstellar Travel.md b/doc/Aphelion/Tiers/Tier 4 - Astrophage and Interstellar Travel.md new file mode 100644 index 0000000..22c4484 --- /dev/null +++ b/doc/Aphelion/Tiers/Tier 4 - Astrophage and Interstellar Travel.md @@ -0,0 +1,10 @@ + +Gameplay Goals: Having mastered interplanetary travel, [[Astrophage]] presents itself as the ultimate fuel source. Capable of converting mass directly into energy, interstellar travel is now possible only using a few grams of fuel per hour. It can either be collected from space or or bred. Breeding astrophage requires inputting massive amounts of energy to "charge" it. Some small amount will need to be acquired naturally before it can be bred. + +Materials: +[[Astrophage]] + +Machines: +[[Spin Drive]] (Astrophage powered engines, uses CO2 lasers to excite the astrophage into providing thrust) +[[Astrophage Collector]] (For collecting from a [[Petrova Line]]) +[[Astrophage Breeder]] \ No newline at end of file diff --git a/doc/Aphelion/Tiers/Tier 5 - Warp.md b/doc/Aphelion/Tiers/Tier 5 - Warp.md new file mode 100644 index 0000000..6d20e59 --- /dev/null +++ b/doc/Aphelion/Tiers/Tier 5 - Warp.md @@ -0,0 +1,2 @@ + +Speculative \ No newline at end of file diff --git a/doc/OLD/Tier 1 — Launch — Chemical Rocketry.md b/doc/OLD/Tier 1 — Launch — Chemical Rocketry.md new file mode 100644 index 0000000..a5f32c2 --- /dev/null +++ b/doc/OLD/Tier 1 — Launch — Chemical Rocketry.md @@ -0,0 +1,22 @@ +Materials and Stuffs: +[[Steel]] from Coal and Iron in [[Electric Arc Furnace]] +[[Aluminum]] +[[Tin]] +[[Silicon]] from Nether Quartz +Vanilla Ores (Copper, Iron, Gold) +[[Oil]] + +Machines: +[[Generators]] +[[Solar Panels]] +[[Electric Arc Furnace]] +[[Chemical Plant]] +[[Rocket Assembler]] +[[Launch Pad]] + +Technologies: +[[Simple Electronics]] + +Milestones: +[[Liquid Propellent Rocket]] +Travel to the moon \ No newline at end of file diff --git a/doc/OLD/Tier 2 — Orbit — Nuclear Rocketry and Space Stations.md b/doc/OLD/Tier 2 — Orbit — Nuclear Rocketry and Space Stations.md new file mode 100644 index 0000000..c401cc3 --- /dev/null +++ b/doc/OLD/Tier 2 — Orbit — Nuclear Rocketry and Space Stations.md @@ -0,0 +1,17 @@ +Materials: +[[Titanium]] +[[Uranium]] +[[Cobalt]] + +Machines: +??? + +Technologies: +[[Nuclear Fission Reactors]] +??? + +Milestones: +[[Nuclear Thermal Rocket]] maybe? +[[Space Stations]] + + diff --git a/doc/OLD/Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md b/doc/OLD/Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md new file mode 100644 index 0000000..df9692a --- /dev/null +++ b/doc/OLD/Tier 3 — Transfer — Interplanetary Space Stations, Ion Propulsion.md @@ -0,0 +1,13 @@ +Materials: +[[Tungsten]] +[[Neodymium]] + +Machines: +[[Gas Giant Skimmer]] + +Technologies: +[[Ion Engines]] +[[Gas Giant Harvesting]] + +Milestones: +Interplanetary Space Station Travel via Ion Engines \ No newline at end of file diff --git a/doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md b/doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md new file mode 100644 index 0000000..e73ee72 --- /dev/null +++ b/doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md @@ -0,0 +1,16 @@ +Materials and Stuffs: +[[Iridium]] +[[Helium-3]] + +Machines: +??? + +Technologies: +[[Fusion Energy]] +[[Alcubierre Drives]] + +Milestones: +Interstellar Travel +Limitless Interplanetary Travel + + diff --git a/src/main/java/net/xevianlight/aphelion/block/custom/StationFlightComputerBlock.java b/src/main/java/net/xevianlight/aphelion/block/custom/StationFlightComputerBlock.java index 5a0a1ac..d8744a4 100644 --- a/src/main/java/net/xevianlight/aphelion/block/custom/StationFlightComputerBlock.java +++ b/src/main/java/net/xevianlight/aphelion/block/custom/StationFlightComputerBlock.java @@ -32,6 +32,7 @@ public class StationFlightComputerBlock extends BasicHorizontalEntityBlock { @Override protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) { + super.onRemove(state, level, pos, newState, movedByPiston); if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) { PartitionData data = computerBE.getData(); if (data != null) { @@ -39,4 +40,15 @@ public class StationFlightComputerBlock extends BasicHorizontalEntityBlock { } } } + + @Override + protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { + super.onPlace(state, level, pos, oldState, movedByPiston); + if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) { + PartitionData data = computerBE.getData(); + if (data != null) { + data.setTraveling(true); + } + } + } } diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationFlightComputerBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationFlightComputerBlockEntity.java index ae1af7a..cc50701 100644 --- a/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationFlightComputerBlockEntity.java +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationFlightComputerBlockEntity.java @@ -28,8 +28,6 @@ public class StationFlightComputerBlockEntity extends BlockEntity implements Tic @Override public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) { - if (data == null) return; - data.setTraveling(true); } public @Nullable PartitionData getData() { @@ -42,7 +40,7 @@ public class StationFlightComputerBlockEntity extends BlockEntity implements Tic if (level instanceof ServerLevel serverLevel) { if (serverLevel.dimension() == ModDimensions.SPACE) { data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos); - + setTraveling(true); } } isInitialized = true; diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationRocketEngineBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationRocketEngineBlockEntity.java index dd34ff7..a12f087 100644 --- a/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationRocketEngineBlockEntity.java +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/StationRocketEngineBlockEntity.java @@ -54,8 +54,8 @@ public class StationRocketEngineBlockEntity extends StationEngineBlockEntity { if (data.getDestination() != null && data.isTraveling()) { if (!tank.isEmpty() && tank.getFluid().is(ModFluidTags.ROCKET_FUEL) && tank.getFluidAmount() >= FUEL_CONSUMPTION) { // has enough fuel? - if (data.travel(getTravelSpeed())) - tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE); + data.travel(getTravelSpeed()); + tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE); } else { // not enough fuel } diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/base/StationEngineBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/base/StationEngineBlockEntity.java index 187a543..6cd70b8 100644 --- a/src/main/java/net/xevianlight/aphelion/block/entity/custom/base/StationEngineBlockEntity.java +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/base/StationEngineBlockEntity.java @@ -54,4 +54,11 @@ public abstract class StationEngineBlockEntity extends BlockEntity implements Ti } isInitialized = true; } + + @Override + public void onRemoved() { + if (data != null) + data.removeEngine(worldPosition); + TickableBlockEntity.super.onRemoved(); + } } diff --git a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java index 8ceee27..ef1aae8 100644 --- a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java +++ b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java @@ -4,9 +4,9 @@ import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.dimension.DimensionType; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.api.distmarker.Dist; import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.client.dimension.DimensionRenderer; @@ -14,8 +14,12 @@ import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects; import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData; import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData; +import net.xevianlight.aphelion.planet.Planet; +import net.xevianlight.aphelion.planet.PlanetCache; import net.xevianlight.aphelion.util.SpacePartition; +import java.util.Arrays; + @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) public class AphelionDebugOverlay { @@ -46,19 +50,28 @@ public class AphelionDebugOverlay { int x = SpacePartition.get(Math.floor(mc.player.position().x)); int z = SpacePartition.get(Math.floor(mc.player.position().z)); + ResourceLocation orbit = PartitionClientState.lastData().getOrbit(); + Planet planet = PlanetCache.getByOrbitOrDefault(orbit); + var dimension = planet.dimension(); + // Left side of F3 event.getLeft().add(""); event.getLeft().add("Aphelion:"); - event.getLeft().add(" Orbit: " + PartitionClientState.lastData().getOrbit()); + event.getLeft().add(" Orbit: " + orbit); + event.getLeft().add(" Planet: " + PlanetCache.getByOrbitOrNull(orbit)); + event.getLeft().add(" Associated Dimension: " + dimension.location().toString()); // event.getLeft().add(" Sky: " + rendererSummary); event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z)); event.getLeft().add(" Station Destination: " + PartitionClientState.lastData().getDestination()); + event.getLeft().add(" Station Destination AU: " + PlanetCache.getOrDefault(PartitionClientState.lastData().getDestination()).orbitDistance()); event.getLeft().add(" Station Owner: " + PartitionClientState.lastData().getOwner()); - event.getLeft().add(" Station Engines: " + PartitionClientState.lastData().getEngines().toArray().length); + event.getLeft().add(" Station Engines: " + Arrays.toString(PartitionClientState.lastData().getEngines().toArray())); event.getLeft().add(" Station Landing Pads: " + PartitionClientState.lastData().getLandingPadContollersAsArray().length); event.getLeft().add(" Station Traveling: " + PartitionClientState.lastData().isTraveling()); + event.getLeft().add(" Station Orbital Distance AU: " + PartitionClientState.lastData().getOrbitDistance()); event.getLeft().add(" Station Trip Distance AU: " + PartitionClientState.lastData().getTripDistanceAU()); - event.getLeft().add(" Station Distance Traveled AU: " + PartitionClientState.lastData().getDistanceTraveledAU()); + event.getLeft().add(" Station Trip Traveled AU: " + PartitionClientState.lastData().getDistanceTraveledAU()); + event.getLeft().add(" Station PosData: " + PartitionClientState.lastData().getPosData().toString()); var server = mc.getSingleplayerServer(); ServerLevel singlePlayerLevel; if (server != null) { diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java index 8fa81d3..58e4a0b 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java @@ -11,6 +11,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.core.saveddata.types.PartitionData; +import net.xevianlight.aphelion.core.saveddata.types.PosData; import net.xevianlight.aphelion.util.SpacePartition; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -74,6 +75,14 @@ public class SpacePartitionSavedData extends SavedData { pd.setLandingPadContollersFromArray(e.getLongArray("LandingPads")); } + if (e.contains("PosData", CompoundTag.TAG_LONG)) { + pd.setPosData(PosData.unpacker(e.getLong("PosData"))); + } + + if (e.contains("OrbitDistance", CompoundTag.TAG_DOUBLE)) { + pd.setOrbitDistance(e.getDouble("OrbitDistance")); + } + data.map.put(key, pd); } @@ -115,6 +124,10 @@ public class SpacePartitionSavedData extends SavedData { e.putLongArray("LandingPads", pd.getLandingPadContollersAsArray()); + e.putLong("PosData", pd.getPosDataOrDefault().pack()); + + e.putDouble("OrbitDistance", pd.getOrbitDistance()); + entries.add(e); }); @@ -178,10 +191,11 @@ public class SpacePartitionSavedData extends SavedData { if (data == null) { // pick a sensible default orbit, or null if you truly allow it - data = new PartitionData(Aphelion.id("orbit/default")); + data = new PartitionData(Aphelion.id("orbit/unassigned")); map.put(key, data); setDirty(); } + data.setDirtyCallback(this::setDirty); return data; } @@ -210,6 +224,7 @@ public class SpacePartitionSavedData extends SavedData { map.put(key, data); setDirty(); } + data.setDirtyCallback(this::setDirty); return data; } @@ -237,6 +252,7 @@ public class SpacePartitionSavedData extends SavedData { map.put(key, data); setDirty(); } + data.setDirtyCallback(this::setDirty); return data; } diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java index 0bcdf92..a38635b 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java @@ -2,8 +2,6 @@ package net.xevianlight.aphelion.core.saveddata.types; public record EnvironmentData (boolean oxygen, short temperature, float gravity){ - - public static final boolean DEFAULT_OXYGEN = true; public static final short DEFAULT_TEMPERATURE = (short) 294.2611; // 70F public static final float DEFAULT_GRAVITY = 9.80665f; // 1G diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java index baf3d2d..3029feb 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java @@ -6,6 +6,7 @@ import net.minecraft.core.UUIDUtil; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; +import net.xevianlight.aphelion.planet.Planet; import net.xevianlight.aphelion.planet.PlanetCache; import net.xevianlight.aphelion.util.BigCodec; import org.jetbrains.annotations.Nullable; @@ -18,24 +19,32 @@ public class PartitionData { @Nullable private ResourceLocation orbit; @Nullable private ResourceLocation destination; + @Nullable private ResourceLocation system; private boolean traveling; /// How far we've already gone private double distanceTraveledAU; - /// Total trip distance, from start to finish + /// Total trip distance, from start to finish. Used with distanceTraveledAU to determine trip progress for UI or other. Not used in trip calculation. private double tripDistanceAU; private boolean generated; private UUID owner; private List landingPadControllers; private List engines; - private double currentOrbitDistanceAU; + /// Data object containing station rotation. + private PosData posData; + private double orbitDistance; + + /// Cache the planet that corresponds to our orbit so we don't have to constantly look it up from PlanetCache. Will be accurate as long as setOrbit() is used exclusively. + @Nullable private Planet cachedPlanet; + /// Cache the planet that corresponds to our destination so we don't have to constantly look it up from PlanetCache. Will be accurate as long as setDestination() is used exclusively. + @Nullable private Planet cachedDestination; public PartitionData() { } public PartitionData(@Nullable ResourceLocation orbit) { - this.orbit = orbit; - this.destination = null; + setOrbit(orbit); + setDestination(null); this.traveling = false; this.distanceTraveledAU = 0; this.tripDistanceAU = 0; @@ -43,18 +52,25 @@ public class PartitionData { this.owner = null; this.landingPadControllers = List.of(); this.engines = new ArrayList<>(List.of()); + this.posData = new PosData(); + setOrbitDistance(1); } public PartitionData(PartitionData other) { this.orbit = other.orbit; + this.cachedPlanet = other.cachedPlanet; this.destination = other.destination; + this.cachedDestination = other.cachedDestination; this.traveling = other.traveling; this.distanceTraveledAU = other.distanceTraveledAU; - this.tripDistanceAU = other.tripDistanceAU; + this.tripDistanceAU = other.tripDistanceAU; // copy directly, no recalculation this.generated = other.generated; this.owner = other.owner; - this.landingPadControllers = other.landingPadControllers; - this.engines = other.engines; + this.engines = other.getEngines(); // defensive copy + this.landingPadControllers = other.getLandingPadControllers(); + this.posData = other.posData; + this.orbitDistance = other.orbitDistance; + // don't set dirty callback — caller must do that } public static final StreamCodec STREAM_CODEC = @@ -66,6 +82,9 @@ public class PartitionData { ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC), d -> Optional.ofNullable(d.getDestination()), + ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC), + d -> Optional.ofNullable(d.getSystem()), + ByteBufCodecs.BOOL, PartitionData::isTraveling, @@ -88,9 +107,16 @@ public class PartitionData { BLOCKPOS_LIST_CODEC, PartitionData::getEngines, - (orbitOpt, destOpt, traveling, distTraveled, distToDest, ownerOpt, generated, controllers, engines) -> { + ByteBufCodecs.VAR_LONG, + PartitionData::getPosDataPacked, + + ByteBufCodecs.DOUBLE, + PartitionData::getOrbitDistance, + + (orbitOpt, destOpt, systemOpt, traveling, distTraveled, distToDest, ownerOpt, generated, controllers, engines, posData, distance) -> { PartitionData data = new PartitionData(orbitOpt.orElse(null)); - data.destination = destOpt.orElse(null); + data.setDestination(destOpt.orElse(null)); + data.setSystem(systemOpt.orElse(null)); data.traveling = traveling; data.distanceTraveledAU = distTraveled; data.tripDistanceAU = distToDest; @@ -98,28 +124,51 @@ public class PartitionData { data.generated = generated; data.landingPadControllers = controllers; data.engines = engines; + data.posData = PosData.unpacker(posData); + data.setOrbitDistance(distance); return data; } ); + private Long getPosDataPacked() { + if (posData == null) posData = new PosData(); + return posData.pack(); + } + public @Nullable ResourceLocation getOrbit() { return this.orbit; } public void setOrbit(@Nullable ResourceLocation orbit) { this.orbit = orbit; + cachedPlanet = PlanetCache.getByOrbitOrNull(orbit); + if (cachedPlanet != null && this.posData != null) + setOrbitDistance(cachedPlanet.orbitDistance()); recalculateTripDistAU(); distanceTraveledAU = 0; + markDirty(); } public @Nullable ResourceLocation getDestination() { + cachedDestination = PlanetCache.getOrNull(destination); return destination; } public void setDestination(@Nullable ResourceLocation destination) { this.destination = destination; + cachedDestination = PlanetCache.getOrNull(destination); recalculateTripDistAU(); distanceTraveledAU = 0; + markDirty(); + } + + public @Nullable ResourceLocation getSystem() { + return system; + } + + public void setSystem(@Nullable ResourceLocation system) { + this.system = system; + markDirty(); } public boolean isTraveling() { @@ -127,7 +176,9 @@ public class PartitionData { } public void setTraveling(boolean traveling) { + if (this.traveling == traveling) return; this.traveling = traveling; + markDirty(); } public double getDistanceTraveledAU() { @@ -136,11 +187,13 @@ public class PartitionData { public void setDistanceTraveledAU(double distanceTraveledAU) { this.distanceTraveledAU = distanceTraveledAU; + markDirty(); } public double recalculateTripDistAU() { var currentPlanet = PlanetCache.getByOrbitOrNull(orbit); if (currentPlanet == null) { + markDirty(); return -1; } @@ -148,6 +201,7 @@ public class PartitionData { var dist = destPlanet.orbitDistance() - currentPlanet.orbitDistance(); this.tripDistanceAU = dist; + markDirty(); return dist; } @@ -155,6 +209,20 @@ public class PartitionData { return tripDistanceAU; } + public double getTripDeltaAU() { + if (cachedDestination == null) return 0; + return cachedDestination.orbitDistance() - orbitDistance; + } + + public double getOrbitDistance() { + return orbitDistance; + } + + public void setOrbitDistance(double orbitDistance) { + this.orbitDistance = orbitDistance; + markDirty(); + } + public void setTripDistanceAU(double tripDistanceAU) { this.tripDistanceAU = tripDistanceAU; } @@ -162,26 +230,38 @@ public class PartitionData { /** * Advances travel progress by the specified distance in AU. * - *

This increases {@code distanceTraveledAU} by the given amount and clamps - * the result so it never exceeds {@code tripDistanceAU}.

- * - *

If the requested distance would overshoot the destination, the traveled - * distance is set to exactly {@code tripDistanceAU}.

+ *

Each call moves the station's current AU position toward the destination + * planet's AU value by the given amount. If the step would overshoot, the + * position is clamped to the destination exactly and arrival is triggered.

* * @param distance the distance to advance in astronomical units (AU) - * @return {@code true} when we arrive at our destination, {@code false} otherwise. + * @return {@code true} when the station has arrived at its destination, + * {@code false} if travel is still in progress. */ public boolean travel(double distance) { - if (distanceTraveledAU + distance > tripDistanceAU) { + if (cachedDestination == null) return false; + if (cachedPlanet == null) return false; + + double delta = getTripDeltaAU(); + double step = distance * Math.signum(delta); + + if (Math.abs(delta) <= distance) { + this.orbitDistance = (cachedDestination.orbitDistance()); + this.orbit = cachedDestination.orbit().location(); + this.cachedPlanet = cachedDestination; + this.destination = null; + this.cachedDestination = null; + this.traveling = false; distanceTraveledAU = tripDistanceAU; - var destinationPlanet = PlanetCache.getOrNull(destination); - if (destinationPlanet != null) { - setOrbit(destinationPlanet.orbit().location()); - } - return false; + + markDirty(); + return true; } else { distanceTraveledAU += distance; - return true; + this.orbitDistance += step; + + markDirty(); + return false; } } @@ -191,6 +271,7 @@ public class PartitionData { public void setGenerated(boolean generated) { this.generated = generated; + markDirty(); } public @Nullable UUID getOwner() { @@ -199,6 +280,7 @@ public class PartitionData { public void setOwner(@Nullable UUID owner) { this.owner = owner; + markDirty(); } /** @@ -222,6 +304,7 @@ public class PartitionData { public void setLandingPadControllers(List landingPadControllers) { this.landingPadControllers = landingPadControllers; + markDirty(); } @@ -254,7 +337,11 @@ public class PartitionData { * @return {@code true} if a controller was removed, {@code false} otherwise */ public boolean removeLandingPadController(BlockPos pos) { - return landingPadControllers.remove(pos); + if (landingPadControllers.remove(pos)) { + markDirty(); + return true; + } + return false; } /** @@ -279,6 +366,7 @@ public class PartitionData { public void setEngines(List engines) { this.engines = engines; + markDirty(); } /** @@ -294,13 +382,18 @@ public class PartitionData { public boolean addEngine(BlockPos pos) { if (!engines.contains(pos)) { engines.add(pos); + markDirty(); return true; } return false; } public boolean removeEngine(BlockPos pos) { - return engines.remove(pos); + if (engines.remove(pos)) { + markDirty(); + return true; + } + return false; } @Override @@ -314,9 +407,14 @@ public class PartitionData { && Objects.equals(this.destination, that.destination) && this.traveling == that.traveling && Double.compare(this.distanceTraveledAU, that.distanceTraveledAU) == 0 - && Double.compare(this.tripDistanceAU, that.tripDistanceAU) == 0 + && Double.compare(this.orbitDistance, that.orbitDistance) == 0 && this.generated == that.generated - && Objects.equals(this.owner, that.owner); + && Objects.equals(this.owner, that.owner) + && Objects.equals(this.engines, that.engines) + && Objects.equals(this.landingPadControllers, that.landingPadControllers); + /* tripDistanceAU intentionally excluded — it is a derived value computed from + * orbit and destination, and may fluctuate due to recalculation timing. + * It should never drive packet equality decisions. */ } public long[] getLandingPadContollersAsArray() { @@ -336,6 +434,31 @@ public class PartitionData { newList.add(BlockPos.of(packedPos)); i++; } + markDirty(); setLandingPadControllers(newList); } + + public PosData getPosData() { + return posData; + } + + public PosData getPosDataOrDefault() { + if (posData == null) return new PosData(); + return posData; + } + + public void setPosData(PosData posData) { + markDirty(); + this.posData = posData; + } + + private Runnable onDirty; + + public void setDirtyCallback(Runnable onDirty) { + this.onDirty = onDirty; + } + + private void markDirty() { + if (onDirty != null) onDirty.run(); + } } diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PosData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PosData.java new file mode 100644 index 0000000..e6201d5 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PosData.java @@ -0,0 +1,218 @@ +package net.xevianlight.aphelion.core.saveddata.types; + +import org.joml.Vector3f; + +/** + * Stores the positional and rotational data for a space station. + * + *

Rotation is stored as three 16-bit fixed-point values (pitch, yaw, roll), + * where {@code 0} maps to {@code 0°} and {@code 65536} maps to {@code 360°}. + * This representation allows angle arithmetic to wrap correctly via natural + * short overflow, with no explicit modulo required. + * + *

All four fields (pitch, yaw, roll, distance) can be packed into a single + * {@code long} for efficient NBT storage. + * + * @see #pack() + * @see #packer(PosData) + * @see #unpacker(long) + */ +public class PosData { + + public static final float AU_SCALE = 1.0f / 512.0f; + + /// Fixed-point pitch rotation. {@code 0} = 0°, {@code 32768} = 180°.

{@code 0.005493°} precision. + public short pitch; + /// Fixed-point yaw rotation. {@code 0} = 0°, {@code 32768} = 180°.

{@code 0.005493°} precision. + public short yaw; + /// Fixed-point roll rotation. {@code 0} = 0°, {@code 32768} = 180°.

{@code 0.005493°} precision. + public short roll; + /** + * Orbital distance, stored as an unsigned 16-bit value.

{@code 0.00195 AU} precision. + *

Defaults to {@code 1 AU}. + */ + + public PosData() { + pitch = 0; + yaw = 0; + roll = 0; + } + + @Deprecated + public PosData(short pitch, short yaw, short roll) { + this.pitch = fromDegrees(pitch); + this.yaw = yaw; + this.roll = roll; + } + + public PosData(PosData other) { + this.pitch = other.pitch; + this.yaw = other.yaw; + this.roll = other.roll; + } + + /** + * Packs this instance into a single {@code long} for NBT storage. + * + * @return the packed {@code long} representation + * @see #packer(PosData) + */ + public long pack() { + return packer(this); + } + + + /** + * Packs a {@code PosData} into a single {@code long}. + * + *

Layout (low to high bits): + *

+     *   [0..15]  pitch
+     *   [16..31] yaw
+     *   [32..47] roll
+     *   [48..63] distance
+     * 
+ * + * @param data the {@code PosData} to pack + * @return the packed {@code long} + */ + public static long packer(PosData data) { + return ((long) data.pitch & 0xFFFFL ) + | (((long) data.yaw & 0xFFFFL) << 16) + | (((long) data.roll & 0xFFFFL) << 32); + + } + + /** + * Unpacks a {@code long} into a new {@code PosData} instance. + * + * @param packed the {@code long} to unpack, as produced by {@link #packer(PosData)} + * @return a new {@code PosData} with fields restored from the packed value + */ + public static PosData unpacker(long packed) { + PosData data = new PosData(); + data.pitch = (short) (packed & 0xFFFFL); + data.yaw = (short) ((packed >> 16) & 0xFFFFL); + data.roll = (short) ((packed >> 32) & 0xFFFFL); + return data; + } + + /** + * Converts a degree value to the fixed-point short representation. + * + *

{@code 0°} maps to {@code 0}, {@code 360°} maps to {@code 65536} + * (which overflows to {@code 0}, preserving wrap-around correctness). + * + * @param degrees the angle in degrees + * @return the fixed-point short representation + */ + public static short fromDegrees(float degrees) { + return (short) Math.round((degrees / 360.0f) * 65536.0f); + } + + public static float pitchDegrees(PosData data) { return toDegrees(data.pitch); } + public static float yawDegrees(PosData data) { return toDegrees(data.yaw); } + public static float rollDegrees(PosData data) { return toDegrees(data.roll); } + + public float pitchDegrees() { return toDegrees(pitch); } + public float yawDegrees() { return toDegrees(yaw); } + public float rollDegrees() { return toDegrees(roll); } + + /** + * Converts a fixed-point short to degrees. + * + * @param s the fixed-point value + * @return the angle in degrees, in the range {@code [0°, 360°)} + */ + private static float toDegrees(short s) { + return ((s & 0xFFFF) / 65536.0f) * 360.0f; + } + + /** + * Converts a fixed-point short to radians. + * + * @param s the fixed-point value + * @return the angle in radians, in the range {@code [0, 2π)} + */ + private static float toRadians(short s) { + return ((s & 0xFFFF) / 65536.0f) * (float) (2 * Math.PI); + } + + // ------------------------------------------------------------------------- + // Euler angles (for rendering) + // ------------------------------------------------------------------------- + + /** + * Returns the rotation as a {@link Vector3f} of Euler angles in degrees + * ({@code x} = pitch, {@code y} = yaw, {@code z} = roll). + * + * @return Euler angles in degrees + */ + public Vector3f eulerAnglesDeg() { + return new Vector3f(pitchDegrees(), yawDegrees(), rollDegrees()); + } + + /** + * Returns the rotation of the given {@code PosData} as a {@link Vector3f} + * of Euler angles in degrees ({@code x} = pitch, {@code y} = yaw, {@code z} = roll). + * + * @param data the instance to read from + * @return Euler angles in degrees + */ + public static Vector3f eulerAnglesDeg(PosData data) { + return new Vector3f(pitchDegrees(data), yawDegrees(data), rollDegrees(data)); + } + + /** + * Returns the rotation as a {@link Vector3f} of Euler angles in radians + * ({@code x} = pitch, {@code y} = yaw, {@code z} = roll). + * Suitable for direct use with JOML rotation methods. + * + * @return Euler angles in radians + */ + public Vector3f eulerAnglesRad() { + return new Vector3f(toRadians(pitch), toRadians(yaw), toRadians(roll)); + } + + /** + * Returns the rotation of the given {@code PosData} as a {@link Vector3f} + * of Euler angles in radians ({@code x} = pitch, {@code y} = yaw, {@code z} = roll). + * Suitable for direct use with JOML rotation methods. + * + * @param data the instance to read from + * @return Euler angles in radians + */ + public static Vector3f eulerAnglesRad(PosData data) { + return new Vector3f(toRadians(data.pitch), toRadians(data.yaw), toRadians(data.roll)); + } + + // ------------------------------------------------------------------------- + // Setters from degrees (convenience) + // ------------------------------------------------------------------------- + + public void setPitchDegrees(float degrees) { this.pitch = fromDegrees(degrees); } + public void setYawDegrees(float degrees) { this.yaw = fromDegrees(degrees); } + public void setRollDegrees(float degrees) { this.roll = fromDegrees(degrees); } + + public void addPitchDegrees(float degrees) { this.pitch += fromDegrees(degrees); } + public void addYawDegrees(float degrees) { this.yaw += fromDegrees(degrees); } + public void addRollDegrees(float degrees) { this.roll += fromDegrees(degrees); } + + // ------------------------------------------------------------------------- + // Utility + // ------------------------------------------------------------------------- + + /** + * Returns a human-readable representation of this {@code PosData}, + * with rotations in degrees and distance as an unsigned integer. + * + * @return a formatted string representation + */ + @Override + public String toString() { + return "PosData{pitch=%.2f°, yaw=%.2f°, roll=%.2f°}" + .formatted(pitchDegrees(), yawDegrees(), rollDegrees()); + } + + +} diff --git a/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java index f801208..ba1a961 100644 --- a/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java +++ b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java @@ -34,8 +34,15 @@ public final class PartitionSync { // If it is different, send them the new one if (prev == null || !prev.equals(now)) { + Aphelion.LOGGER.debug("Partition changed for {}: prev={} now={}", sp.getName().getString(), + prev == null ? "null" : String.format("orbit=%s dest=%s traveling=%s distTraveled=%s tripDist=%s orbitDist=%s", + prev.partitionData().getOrbit(), prev.partitionData().getDestination(), prev.partitionData().isTraveling(), + prev.partitionData().getDistanceTraveledAU(), prev.partitionData().getTripDistanceAU(), prev.partitionData().getOrbitDistance()), + String.format("orbit=%s dest=%s traveling=%s distTraveled=%s tripDist=%s orbitDist=%s", + now.partitionData().getOrbit(), now.partitionData().getDestination(), now.partitionData().isTraveling(), + now.partitionData().getDistanceTraveledAU(), now.partitionData().getTripDistanceAU(), now.partitionData().getOrbitDistance()) + ); PacketDistributor.sendToPlayer(sp, now); - // Store this packet for later LAST_SENT.put(sp.getUUID(), now); } } diff --git a/src/main/java/net/xevianlight/aphelion/planet/Planet.java b/src/main/java/net/xevianlight/aphelion/planet/Planet.java index 48e5eaa..f9f60aa 100644 --- a/src/main/java/net/xevianlight/aphelion/planet/Planet.java +++ b/src/main/java/net/xevianlight/aphelion/planet/Planet.java @@ -7,13 +7,16 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.xevianlight.aphelion.util.registries.ModRegistries; +import java.util.Optional; + public record Planet ( ResourceKey dimension, ResourceKey orbit, double orbitDistance, ResourceKey system, boolean oxygen, - float gravity + float gravity, + Optional> parentPlanet /// nullable moon parent ) { public static final Codec CODEC = RecordCodecBuilder.create(inst -> inst.group( ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension), @@ -21,7 +24,8 @@ public record Planet ( Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance), ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system), Codec.BOOL.fieldOf("oxygen").forGetter(Planet::oxygen), - Codec.FLOAT.fieldOf("gravity").forGetter(Planet::gravity) + Codec.FLOAT.fieldOf("gravity").forGetter(Planet::gravity), + ResourceKey.codec(ModRegistries.PLANET).optionalFieldOf("parent_planet").forGetter(Planet::parentPlanet) ).apply(inst, Planet::new)); } diff --git a/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java b/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java index 4659ed4..a533922 100644 --- a/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java +++ b/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java @@ -6,15 +6,19 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.util.registries.ModRegistries; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; public final class PlanetCache { public static final Map PLANETS = new HashMap<>(); public static final Map, ResourceLocation> PLANET_BY_DIMENSION = new HashMap<>(); + public static final Map PLANET_BY_ORBIT = new HashMap<>(); public static final Planet DEFAULT = new Planet( ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")), @@ -22,7 +26,8 @@ public final class PlanetCache { 1, ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")), true, - 1 + 1, + Optional.empty() ); public static void registerPlanets(Map planets) { @@ -34,6 +39,7 @@ public final class PlanetCache { planets.forEach((planetId, planet) -> { var dim = planet.dimension(); var prev = PLANET_BY_DIMENSION.put(dim, planetId); + PLANET_BY_ORBIT.put(planet.orbit().location(), planet); if (prev != null) { Aphelion.LOGGER.warn( "Dimension {} is claimed by multiple planets: {} and {}. Keeping latest: {}", @@ -61,8 +67,27 @@ public final class PlanetCache { .orElse(null); } + public static @NotNull Planet getByOrbitOrDefault(ResourceLocation id) { + return PLANETS.values().stream() + .filter(planet -> planet.orbit().location().equals(id)) + .findFirst() + .orElse(DEFAULT); + } + public static Planet getByDimensionOrNull(ResourceKey dimension) { ResourceLocation planetId = PLANET_BY_DIMENSION.get(dimension); return planetId == null ? null : PLANETS.get(planetId); } + + public static @NotNull List getSatellites(ResourceLocation id) { + return PLANETS.values().stream() + .filter(planet -> planet.parentPlanet() + .map(key -> key.location().equals(id)) + .orElse(false)) + .toList(); + } + + public static @NotNull List getSatellites(ResourceKey key) { + return getSatellites(key.location()); + } } diff --git a/src/main/resources/data/aphelion/planet/earth.json b/src/main/resources/data/aphelion/planet/earth.json new file mode 100644 index 0000000..22c408f --- /dev/null +++ b/src/main/resources/data/aphelion/planet/earth.json @@ -0,0 +1,8 @@ +{ + "dimension": "minecraft:overworld", + "orbit": "aphelion:orbit/earth", + "orbit_distance": 1, + "star_system": "aphelion:sol", + "gravity": 1, + "oxygen": false +} \ No newline at end of file