mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 10:00:54 +01:00
Compare commits
4 Commits
2496e0cdd5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01d3c133e1 | ||
|
|
903c7f7d17 | ||
|
|
302acaaa18 | ||
|
|
341ed8a17d |
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cd /tmp)",
|
||||
"Bash(jar -xf \"C:/Users/Xevian/.gradle/caches/modules-2/files-2.1/net.neoforged/neoforge/21.1.217/f8798213b260c83be365a3d8ec5537d36dd44d1c/neoforge-21.1.217-sources.jar\" \"net/neoforged/neoforge/common/extensions/IPlayerExtension.java\")",
|
||||
"Read(//tmp/**)"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
doc/Aphelion/.obsidian/app.json
vendored
Normal file
3
doc/Aphelion/.obsidian/app.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"alwaysUpdateLinks": true
|
||||
}
|
||||
1
doc/Aphelion/.obsidian/appearance.json
vendored
Normal file
1
doc/Aphelion/.obsidian/appearance.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
33
doc/Aphelion/.obsidian/core-plugins.json
vendored
Normal file
33
doc/Aphelion/.obsidian/core-plugins.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
22
doc/Aphelion/.obsidian/graph.json
vendored
Normal file
22
doc/Aphelion/.obsidian/graph.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
166
doc/Aphelion/.obsidian/workspace-mobile.json
vendored
Normal file
166
doc/Aphelion/.obsidian/workspace-mobile.json
vendored
Normal file
@@ -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": []
|
||||
}
|
||||
202
doc/Aphelion/.obsidian/workspace.json
vendored
Normal file
202
doc/Aphelion/.obsidian/workspace.json
vendored
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
0
doc/Aphelion/Planets.md
Normal file
0
doc/Aphelion/Planets.md
Normal file
16
doc/Aphelion/Tiers/Tier 1 - Beginning.md
Normal file
16
doc/Aphelion/Tiers/Tier 1 - Beginning.md
Normal file
@@ -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]]
|
||||
@@ -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]]
|
||||
@@ -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]]
|
||||
@@ -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]]
|
||||
2
doc/Aphelion/Tiers/Tier 5 - Warp.md
Normal file
2
doc/Aphelion/Tiers/Tier 5 - Warp.md
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
Speculative
|
||||
22
doc/OLD/Tier 1 — Launch — Chemical Rocketry.md
Normal file
22
doc/OLD/Tier 1 — Launch — Chemical Rocketry.md
Normal file
@@ -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
|
||||
@@ -0,0 +1,17 @@
|
||||
Materials:
|
||||
[[Titanium]]
|
||||
[[Uranium]]
|
||||
[[Cobalt]]
|
||||
|
||||
Machines:
|
||||
???
|
||||
|
||||
Technologies:
|
||||
[[Nuclear Fission Reactors]]
|
||||
???
|
||||
|
||||
Milestones:
|
||||
[[Nuclear Thermal Rocket]] maybe?
|
||||
[[Space Stations]]
|
||||
|
||||
|
||||
@@ -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
|
||||
16
doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md
Normal file
16
doc/OLD/Tier 4 — Warp — Warp Technology and Fusion Power.md
Normal file
@@ -0,0 +1,16 @@
|
||||
Materials and Stuffs:
|
||||
[[Iridium]]
|
||||
[[Helium-3]]
|
||||
|
||||
Machines:
|
||||
???
|
||||
|
||||
Technologies:
|
||||
[[Fusion Energy]]
|
||||
[[Alcubierre Drives]]
|
||||
|
||||
Milestones:
|
||||
Interstellar Travel
|
||||
Limitless Interplanetary Travel
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import net.xevianlight.aphelion.fluid.ModFluids;
|
||||
import net.xevianlight.aphelion.recipe.ModRecipes;
|
||||
import net.xevianlight.aphelion.screen.ElectricArcFurnaceScreen;
|
||||
import net.xevianlight.aphelion.screen.ModMenuTypes;
|
||||
import net.xevianlight.aphelion.screen.StationFlightComputerScreen;
|
||||
import net.xevianlight.aphelion.screen.TestBlockScreen;
|
||||
import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen;
|
||||
import org.slf4j.Logger;
|
||||
@@ -152,6 +153,7 @@ public class Aphelion {
|
||||
event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new);
|
||||
event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new);
|
||||
event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new);
|
||||
event.register(ModMenuTypes.STATION_FLIGHT_COMPUTER_MENU.get(), StationFlightComputerScreen::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -2,16 +2,27 @@ package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
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.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicHorizontalEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationFlightComputerBlockEntity;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.AvailableDestinationsPayload;
|
||||
import net.xevianlight.aphelion.network.packet.PlanetInfo;
|
||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StationFlightComputerBlock extends BasicHorizontalEntityBlock {
|
||||
|
||||
public static final MapCodec<StationFlightComputerBlock> CODEC = simpleCodec(StationFlightComputerBlock::new);
|
||||
@@ -30,8 +41,31 @@ public class StationFlightComputerBlock extends BasicHorizontalEntityBlock {
|
||||
return new StationFlightComputerBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull InteractionResult useWithoutItem(@NotNull BlockState state, @NotNull Level level,
|
||||
@NotNull BlockPos pos, @NotNull Player player,
|
||||
@NotNull BlockHitResult hitResult) {
|
||||
if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer) {
|
||||
if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity be) {
|
||||
List<PlanetInfo> planets = PlanetCache.PLANETS.entrySet().stream()
|
||||
.map(e -> new PlanetInfo(
|
||||
e.getKey(),
|
||||
e.getValue().orbit().location(),
|
||||
e.getValue().orbitDistance(),
|
||||
e.getValue().parentPlanet().map(k -> k.location())))
|
||||
.collect(Collectors.toList());
|
||||
// Send planet list before opening the menu so DestinationClientCache is populated when the screen opens.
|
||||
PacketDistributor.sendToPlayer(serverPlayer, new AvailableDestinationsPayload(planets));
|
||||
serverPlayer.openMenu(be, be.getBlockPos());
|
||||
}
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
// Breaking the flight computer aborts travel — no computer, no navigation.
|
||||
if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) {
|
||||
PartitionData data = computerBE.getData();
|
||||
if (data != null) {
|
||||
@@ -39,4 +73,9 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@ package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
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.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
@@ -11,30 +18,41 @@ import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModDimensions;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.screen.StationFlightComputerMenu;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationFlightComputerBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
public StationFlightComputerBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.STATION_FLIGHT_COMPUTER_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
public class StationFlightComputerBlockEntity extends BlockEntity implements TickableBlockEntity, MenuProvider {
|
||||
|
||||
protected PartitionData data;
|
||||
private boolean isInitialized = false;
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {
|
||||
private final ContainerData containerData = new ContainerData() {
|
||||
@Override
|
||||
public int get(int index) {
|
||||
if (data == null) return 0;
|
||||
return switch (index) {
|
||||
case StationFlightComputerMenu.DATA_TRAVELING -> data.isTraveling() ? 1 : 0;
|
||||
case StationFlightComputerMenu.DATA_ENGINE_COUNT -> data.getEngines().size();
|
||||
case StationFlightComputerMenu.DATA_PAD_COUNT -> data.getLandingPadControllers().size();
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
// set() is intentionally a no-op: client writes go through explicit network packets, not ContainerData
|
||||
@Override public void set(int index, int value) {}
|
||||
@Override public int getCount() { return StationFlightComputerMenu.DATA_COUNT; }
|
||||
};
|
||||
|
||||
public StationFlightComputerBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.STATION_FLIGHT_COMPUTER_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
if (data == null) return;
|
||||
data.setTraveling(true);
|
||||
}
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {}
|
||||
|
||||
public @Nullable PartitionData getData() {
|
||||
return data;
|
||||
}
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {}
|
||||
|
||||
public @Nullable PartitionData getData() { return data; }
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
@@ -42,20 +60,34 @@ public class StationFlightComputerBlockEntity extends BlockEntity implements Tic
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
|
||||
}
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
protected boolean setTraveling(boolean value) {
|
||||
public boolean setTraveling(boolean value) {
|
||||
if (data == null) return false;
|
||||
data.setTraveling(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
public void setDestination(@Nullable ResourceLocation destination) {
|
||||
if (data == null) return;
|
||||
data.setDestination(destination);
|
||||
}
|
||||
|
||||
public ContainerData getContainerData() { return containerData; }
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return Component.translatable("block.aphelion.station_flight_computer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int windowId, Inventory inventory, Player player) {
|
||||
return new StationFlightComputerMenu(windowId, inventory, this, containerData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() { return isInitialized; }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import net.xevianlight.aphelion.network.packet.PlanetInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class DestinationClientCache {
|
||||
// volatile: written from the netty network thread, read from the render thread
|
||||
private static volatile List<PlanetInfo> planets = Collections.emptyList();
|
||||
|
||||
public static void set(List<PlanetInfo> list) { planets = List.copyOf(list); }
|
||||
public static List<PlanetInfo> get() { return planets; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<BlockPos> landingPadControllers;
|
||||
private List<BlockPos> 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<ByteBuf, PartitionData> 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.
|
||||
*
|
||||
* <p>This increases {@code distanceTraveledAU} by the given amount and clamps
|
||||
* the result so it never exceeds {@code tripDistanceAU}.</p>
|
||||
*
|
||||
* <p>If the requested distance would overshoot the destination, the traveled
|
||||
* distance is set to exactly {@code tripDistanceAU}.</p>
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<BlockPos> 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<BlockPos> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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°. <p>{@code 0.005493°} precision.
|
||||
public short pitch;
|
||||
/// Fixed-point yaw rotation. {@code 0} = 0°, {@code 32768} = 180°. <p>{@code 0.005493°} precision.
|
||||
public short yaw;
|
||||
/// Fixed-point roll rotation. {@code 0} = 0°, {@code 32768} = 180°. <p>{@code 0.005493°} precision.
|
||||
public short roll;
|
||||
/**
|
||||
* Orbital distance, stored as an unsigned 16-bit value. <p> {@code 0.00195 AU} precision.
|
||||
* <p>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}.
|
||||
*
|
||||
* <p>Layout (low to high bits):
|
||||
* <pre>
|
||||
* [0..15] pitch
|
||||
* [16..31] yaw
|
||||
* [32..47] roll
|
||||
* [48..63] distance
|
||||
* </pre>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>{@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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,10 +15,14 @@ import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModEntities;
|
||||
import net.xevianlight.aphelion.network.FlightComputerPayloadHandler;
|
||||
import net.xevianlight.aphelion.network.RocketPayloadHandlers;
|
||||
import net.xevianlight.aphelion.network.PartitionPayloadHandler;
|
||||
import net.xevianlight.aphelion.network.packet.AvailableDestinationsPayload;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.network.packet.RocketLaunchPayload;
|
||||
import net.xevianlight.aphelion.network.packet.SetDestinationPayload;
|
||||
import net.xevianlight.aphelion.network.packet.SetTravelingPayload;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID)
|
||||
public class ModBusEvents {
|
||||
@@ -58,5 +62,23 @@ public class ModBusEvents {
|
||||
RocketPayloadHandlers::handleRocketLaunch
|
||||
);
|
||||
|
||||
registrar.playToClient(
|
||||
AvailableDestinationsPayload.TYPE,
|
||||
AvailableDestinationsPayload.STREAM_CODEC,
|
||||
FlightComputerPayloadHandler::handleAvailableDestinations
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
SetDestinationPayload.TYPE,
|
||||
SetDestinationPayload.STREAM_CODEC,
|
||||
FlightComputerPayloadHandler::handleSetDestination
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
SetTravelingPayload.TYPE,
|
||||
SetTravelingPayload.STREAM_CODEC,
|
||||
FlightComputerPayloadHandler::handleSetTraveling
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.xevianlight.aphelion.network;
|
||||
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationFlightComputerBlockEntity;
|
||||
import net.xevianlight.aphelion.client.DestinationClientCache;
|
||||
import net.xevianlight.aphelion.network.packet.AvailableDestinationsPayload;
|
||||
import net.xevianlight.aphelion.network.packet.SetDestinationPayload;
|
||||
import net.xevianlight.aphelion.network.packet.SetTravelingPayload;
|
||||
|
||||
public class FlightComputerPayloadHandler {
|
||||
|
||||
// Runs on the CLIENT: caches the planet list so the screen has it immediately on open.
|
||||
public static void handleAvailableDestinations(AvailableDestinationsPayload payload, IPayloadContext context) {
|
||||
DestinationClientCache.set(payload.planets());
|
||||
}
|
||||
|
||||
// Runs on the SERVER: client-side button sends this; server commits it to PartitionData.
|
||||
public static void handleSetDestination(SetDestinationPayload payload, IPayloadContext context) {
|
||||
var level = context.player().level();
|
||||
if (level.getBlockEntity(payload.computerPos()) instanceof StationFlightComputerBlockEntity be) {
|
||||
be.setDestination(payload.destination().orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
// Runs on the SERVER: the Launch/Abort button toggles traveling via this packet.
|
||||
public static void handleSetTraveling(SetTravelingPayload payload, IPayloadContext context) {
|
||||
var level = context.player().level();
|
||||
if (level.getBlockEntity(payload.computerPos()) instanceof StationFlightComputerBlockEntity be) {
|
||||
be.setTraveling(payload.traveling());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AvailableDestinationsPayload(List<PlanetInfo> planets) implements CustomPacketPayload {
|
||||
public static final Type<AvailableDestinationsPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "available_destinations"));
|
||||
|
||||
// 256 is a hard cap on the list codec to prevent oversized packets; the solar system has ~8 planets
|
||||
public static final StreamCodec<ByteBuf, AvailableDestinationsPayload> STREAM_CODEC =
|
||||
PlanetInfo.STREAM_CODEC.apply(ByteBufCodecs.list(256))
|
||||
.map(AvailableDestinationsPayload::new, AvailableDestinationsPayload::planets);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record PlanetInfo(ResourceLocation id, ResourceLocation orbit, double orbitDistance, Optional<ResourceLocation> parentPlanet) {
|
||||
|
||||
public static final StreamCodec<ByteBuf, PlanetInfo> STREAM_CODEC = StreamCodec.composite(
|
||||
ResourceLocation.STREAM_CODEC, PlanetInfo::id,
|
||||
ResourceLocation.STREAM_CODEC, PlanetInfo::orbit,
|
||||
ByteBufCodecs.DOUBLE, PlanetInfo::orbitDistance,
|
||||
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC), PlanetInfo::parentPlanet,
|
||||
(id, orb, dist, parent) -> new PlanetInfo(id, orb, dist, parent)
|
||||
);
|
||||
|
||||
public boolean isMoon() {
|
||||
return parentPlanet.isPresent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record SetDestinationPayload(BlockPos computerPos, Optional<ResourceLocation> destination) implements CustomPacketPayload {
|
||||
public static final Type<SetDestinationPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "set_destination"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, SetDestinationPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
BlockPos.STREAM_CODEC,
|
||||
SetDestinationPayload::computerPos,
|
||||
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
|
||||
SetDestinationPayload::destination,
|
||||
SetDestinationPayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record SetTravelingPayload(BlockPos computerPos, boolean traveling) implements CustomPacketPayload {
|
||||
public static final Type<SetTravelingPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "set_traveling"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, SetTravelingPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
BlockPos.STREAM_CODEC,
|
||||
SetTravelingPayload::computerPos,
|
||||
ByteBufCodecs.BOOL,
|
||||
SetTravelingPayload::traveling,
|
||||
SetTravelingPayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public @NotNull Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -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<Level> dimension,
|
||||
ResourceKey<Orbit> orbit,
|
||||
double orbitDistance,
|
||||
ResourceKey<StarSystem> system,
|
||||
boolean oxygen,
|
||||
float gravity
|
||||
float gravity,
|
||||
Optional<ResourceKey<Planet>> parentPlanet /// nullable moon parent
|
||||
) {
|
||||
public static final Codec<Planet> 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));
|
||||
}
|
||||
|
||||
@@ -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<ResourceLocation, Planet> PLANETS = new HashMap<>();
|
||||
public static final Map<ResourceKey<Level>, ResourceLocation> PLANET_BY_DIMENSION = new HashMap<>();
|
||||
public static final Map<ResourceLocation, Planet> 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<ResourceLocation, Planet> 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<Level> dimension) {
|
||||
ResourceLocation planetId = PLANET_BY_DIMENSION.get(dimension);
|
||||
return planetId == null ? null : PLANETS.get(planetId);
|
||||
}
|
||||
|
||||
public static @NotNull List<Planet> getSatellites(ResourceLocation id) {
|
||||
return PLANETS.values().stream()
|
||||
.filter(planet -> planet.parentPlanet()
|
||||
.map(key -> key.location().equals(id))
|
||||
.orElse(false))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static @NotNull List<Planet> getSatellites(ResourceKey<Planet> key) {
|
||||
return getSatellites(key.location());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ public class ModMenuTypes {
|
||||
public static DeferredHolder<MenuType<?>,MenuType<VacuumArcFurnaceMenu>> VACUUM_ARC_FURNACE_MENU =
|
||||
registerMenuType("vacuum_arc_furnace_menu", VacuumArcFurnaceMenu::new);
|
||||
|
||||
public static DeferredHolder<MenuType<?>,MenuType<StationFlightComputerMenu>> STATION_FLIGHT_COMPUTER_MENU =
|
||||
registerMenuType("station_flight_computer_menu", StationFlightComputerMenu::new);
|
||||
|
||||
private static <T extends AbstractContainerMenu>DeferredHolder<MenuType<?>, MenuType<T>> registerMenuType(String name,
|
||||
IContainerFactory<T> factory) {
|
||||
return MENUS.register(name, () -> IMenuTypeExtension.create(factory));
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.xevianlight.aphelion.screen;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
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.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationFlightComputerBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class StationFlightComputerMenu extends AbstractContainerMenu {
|
||||
public final StationFlightComputerBlockEntity blockEntity;
|
||||
|
||||
public static final int DATA_TRAVELING = 0;
|
||||
public static final int DATA_ENGINE_COUNT = 1;
|
||||
public static final int DATA_PAD_COUNT = 2;
|
||||
public static final int DATA_COUNT = 3;
|
||||
|
||||
private final ContainerData data;
|
||||
|
||||
public StationFlightComputerMenu(int windowId, Inventory inv, FriendlyByteBuf buf) {
|
||||
this(windowId, inv, inv.player.level().getBlockEntity(buf.readBlockPos()), new SimpleContainerData(DATA_COUNT));
|
||||
}
|
||||
|
||||
public StationFlightComputerMenu(int windowId, Inventory inv, BlockEntity be, ContainerData data) {
|
||||
super(ModMenuTypes.STATION_FLIGHT_COMPUTER_MENU.get(), windowId);
|
||||
this.blockEntity = (StationFlightComputerBlockEntity) be;
|
||||
this.data = data;
|
||||
addDataSlots(data);
|
||||
}
|
||||
|
||||
public boolean isTraveling() { return data.get(DATA_TRAVELING) != 0; }
|
||||
public int getEngineCount() { return data.get(DATA_ENGINE_COUNT); }
|
||||
public int getPadCount() { return data.get(DATA_PAD_COUNT); }
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(@NotNull Player player) {
|
||||
// Always valid — players can interact with the computer from anywhere on the station.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
package net.xevianlight.aphelion.screen;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.client.DestinationClientCache;
|
||||
import net.xevianlight.aphelion.client.PartitionClientState;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.PlanetInfo;
|
||||
import net.xevianlight.aphelion.network.packet.SetDestinationPayload;
|
||||
import net.xevianlight.aphelion.network.packet.SetTravelingPayload;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class StationFlightComputerScreen extends AbstractContainerScreen<StationFlightComputerMenu> {
|
||||
|
||||
// ── Orbital animation ──────────────────────────────────────────────────
|
||||
/** Shared epoch so planet positions persist across screen open/close. */
|
||||
private static final long EPOCH_MS = System.currentTimeMillis();
|
||||
/** Real-world ms for one Earth orbit. Inner planets scale by AU^1.5. */
|
||||
private static final double EARTH_PERIOD_MS = 120_000.0; // 15 seconds
|
||||
/** Fixed screen-space orbit radius for moons around their parent dot (system view). */
|
||||
private static final int MOON_ORBIT_R = 13;
|
||||
/** Screen-space orbit radius for moons when the orrery is zoomed into a subsystem. */
|
||||
private static final int ZOOM_MOON_ORBIT_R = 45;
|
||||
/** Fixed animation period for all moons — actual AU is too small for Keplerian scaling. */
|
||||
private static final double MOON_PERIOD_MS = 18_000.0;
|
||||
|
||||
// ── Layout ─────────────────────────────────────────────────────────────
|
||||
private static final int ORRERY_W = 176;
|
||||
private static final int ORRERY_H = 186;
|
||||
private static final int ORRERY_CX = ORRERY_W / 2; // 88
|
||||
private static final int ORRERY_CY = ORRERY_H / 2; // 93
|
||||
private static final int MAX_ORBIT_R = 82;
|
||||
private static final int MIN_ORBIT_R = 18;
|
||||
private static final int INFO_X = 180; // relative to leftPos
|
||||
private static final int INFO_W = 98;
|
||||
|
||||
// ── Colors ─────────────────────────────────────────────────────────────
|
||||
private static final int C_BG = 0xFF0D0D1A;
|
||||
private static final int C_SPACE = 0xFF030308;
|
||||
private static final int C_BORDER = 0xFF2A2A4A;
|
||||
private static final int C_PANEL = 0xFF0A0A16;
|
||||
private static final int C_GOLD = 0xFFFFD700;
|
||||
private static final int C_WHITE = 0xFFE8E8E8;
|
||||
private static final int C_GRAY = 0xFF888899;
|
||||
private static final int C_ORBIT = 0xFF1A1A30;
|
||||
private static final int C_ORBIT_CUR = 0xFF334466;
|
||||
private static final int C_ORBIT_DEST = 0xFF2A4A2A;
|
||||
private static final int C_ORBIT_SEL = 0xFF2A3A6A;
|
||||
private static final int C_STAR = 0xFFFFE866;
|
||||
private static final int C_STATION = 0xFFFFFFFF;
|
||||
private static final int C_TRAVEL_LINE = 0xFF446644;
|
||||
private static final int C_PROG_BG = 0xFF0A1A0A;
|
||||
private static final int C_PROG_FILL = 0xFF00BB44;
|
||||
|
||||
private static final int[] PLANET_COLORS = {
|
||||
0xFFE8A060, 0xFF88CC44, 0xFF4499FF, 0xFFCC8844,
|
||||
0xFF88AACC, 0xFFCC44CC, 0xFFFFCC44, 0xFF99FFAA,
|
||||
};
|
||||
|
||||
// ── Background stars (fixed, deterministic) ────────────────────────────
|
||||
private static final int STAR_COUNT = 60;
|
||||
private static final int[] STAR_PX = new int[STAR_COUNT];
|
||||
private static final int[] STAR_PY = new int[STAR_COUNT];
|
||||
private static final int[] STAR_COL = new int[STAR_COUNT];
|
||||
static {
|
||||
long s = 0x5DEECE66DL;
|
||||
for (int i = 0; i < STAR_COUNT; i++) {
|
||||
s = (s * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFL;
|
||||
STAR_PX[i] = (int)(s % ORRERY_W);
|
||||
s = (s * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFL;
|
||||
STAR_PY[i] = (int)(s % ORRERY_H);
|
||||
int alpha = 0x50 + (i % 4) * 0x18;
|
||||
STAR_COL[i] = (alpha << 24) | 0xCCCCCC;
|
||||
}
|
||||
}
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────
|
||||
@Nullable private ResourceLocation selectedPlanet = null;
|
||||
/** Non-null when the orrery is zoomed into a planet's local subsystem. */
|
||||
@Nullable private ResourceLocation zoomedSystem = null;
|
||||
|
||||
/** Cached each frame in renderBg; read in mouseClicked to stay in sync. */
|
||||
private final List<int[]> planetDrawCache = new ArrayList<>(); // [absX, absY, screenR, colorIdx]
|
||||
private final List<PlanetInfo> planetCache = new ArrayList<>();
|
||||
|
||||
private Button setDestButton;
|
||||
private Button travelButton;
|
||||
|
||||
public StationFlightComputerScreen(StationFlightComputerMenu menu, Inventory inv, Component title) {
|
||||
super(menu, inv, title);
|
||||
this.imageWidth = 280;
|
||||
this.imageHeight = 190;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
this.titleLabelY = 10000;
|
||||
this.inventoryLabelY = 10000;
|
||||
|
||||
int bx = leftPos + INFO_X + 4;
|
||||
int bw = INFO_W - 8;
|
||||
|
||||
setDestButton = addRenderableWidget(Button.builder(
|
||||
Component.literal("Set Destination"),
|
||||
btn -> {
|
||||
if (selectedPlanet != null) {
|
||||
PacketDistributor.sendToServer(new SetDestinationPayload(
|
||||
menu.blockEntity.getBlockPos(),
|
||||
Optional.of(selectedPlanet)));
|
||||
}
|
||||
})
|
||||
.pos(bx, topPos + 152)
|
||||
.size(bw, 16)
|
||||
.build());
|
||||
|
||||
travelButton = addRenderableWidget(Button.builder(
|
||||
Component.literal("Launch"),
|
||||
btn -> PacketDistributor.sendToServer(new SetTravelingPayload(
|
||||
menu.blockEntity.getBlockPos(),
|
||||
!menu.isTraveling())))
|
||||
.pos(bx, topPos + 170)
|
||||
.size(bw, 16)
|
||||
.build());
|
||||
}
|
||||
|
||||
// ── Rendering ──────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics g, int mouseX, int mouseY, float partialTick) {
|
||||
renderBackground(g, mouseX, mouseY, partialTick);
|
||||
super.render(g, mouseX, mouseY, partialTick);
|
||||
renderTooltip(g, mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics g, float partialTick, int mouseX, int mouseY) {
|
||||
// Outer frame
|
||||
g.fill(leftPos, topPos, leftPos + imageWidth, topPos + imageHeight, C_BG);
|
||||
|
||||
drawOrrery(g, mouseX, mouseY);
|
||||
drawInfoPanel(g, mouseX, mouseY);
|
||||
|
||||
// Dynamic button labels / states
|
||||
travelButton.setMessage(menu.isTraveling()
|
||||
? Component.literal("Abort Travel")
|
||||
: Component.literal("Launch"));
|
||||
setDestButton.active = selectedPlanet != null;
|
||||
}
|
||||
|
||||
// ── Orrery ─────────────────────────────────────────────────────────────
|
||||
|
||||
private void drawOrrery(GuiGraphics g, int mouseX, int mouseY) {
|
||||
int orrX = leftPos + 2;
|
||||
int orrY = topPos + 2;
|
||||
int cx = orrX + ORRERY_CX;
|
||||
int cy = orrY + ORRERY_CY;
|
||||
|
||||
// Space background
|
||||
g.fill(orrX, orrY, orrX + ORRERY_W, orrY + ORRERY_H, C_SPACE);
|
||||
|
||||
// Panel border
|
||||
g.fill(orrX, orrY, orrX + ORRERY_W, orrY + 1, C_BORDER);
|
||||
g.fill(orrX, orrY + ORRERY_H - 1, orrX + ORRERY_W, orrY + ORRERY_H, C_BORDER);
|
||||
g.fill(orrX, orrY, orrX + 1, orrY + ORRERY_H, C_BORDER);
|
||||
g.fill(orrX + ORRERY_W - 1, orrY, orrX + ORRERY_W, orrY + ORRERY_H, C_BORDER);
|
||||
|
||||
// Background stars
|
||||
for (int i = 0; i < STAR_COUNT; i++) {
|
||||
g.fill(orrX + STAR_PX[i], orrY + STAR_PY[i],
|
||||
orrX + STAR_PX[i] + 1, orrY + STAR_PY[i] + 1, STAR_COL[i]);
|
||||
}
|
||||
|
||||
List<PlanetInfo> allPlanets = DestinationClientCache.get();
|
||||
PartitionData data = PartitionClientState.get().map(p -> p.partitionData()).orElse(null);
|
||||
|
||||
// Filter to the station's star system. Resolved client-side so the server
|
||||
// can send all planets without needing to know the station's current system.
|
||||
ResourceLocation currentSystem = null;
|
||||
if (data != null && data.getOrbit() != null) {
|
||||
var currentPlanet = net.xevianlight.aphelion.planet.PlanetCache.getByOrbitOrNull(data.getOrbit());
|
||||
if (currentPlanet != null) currentSystem = currentPlanet.system().location();
|
||||
}
|
||||
final ResourceLocation systemFilter = currentSystem;
|
||||
List<PlanetInfo> planets = systemFilter == null ? allPlanets : allPlanets.stream()
|
||||
.filter(p -> {
|
||||
var planet = net.xevianlight.aphelion.planet.PlanetCache.getOrNull(p.id());
|
||||
return planet == null || planet.system().location().equals(systemFilter);
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// Rebuild cached positions
|
||||
planetDrawCache.clear();
|
||||
planetCache.clear();
|
||||
|
||||
if (planets.isEmpty()) {
|
||||
var font = Minecraft.getInstance().font;
|
||||
String msg = "No planet data";
|
||||
g.drawString(font, msg, cx - font.width(msg) / 2, cy - 4, C_GRAY, false);
|
||||
drawDot(g, cx, cy, 4, C_STAR);
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsed = System.currentTimeMillis() - EPOCH_MS;
|
||||
|
||||
if (zoomedSystem == null) {
|
||||
// ── SYSTEM VIEW ────────────────────────────────────────────────
|
||||
// Exclude moons from the star-scale min/max so inner planets aren't crushed.
|
||||
double maxOrbit = planets.stream().filter(p -> !p.isMoon()).mapToDouble(PlanetInfo::orbitDistance).max().orElse(1);
|
||||
double minOrbit = planets.stream().filter(p -> !p.isMoon()).mapToDouble(PlanetInfo::orbitDistance).min().orElse(0);
|
||||
|
||||
// Pass 1 — non-moon bodies: positions relative to the central star.
|
||||
int nonMoonCount = (int) planets.stream().filter(p -> !p.isMoon()).count();
|
||||
int phaseSlot = 0;
|
||||
for (int i = 0; i < planets.size(); i++) {
|
||||
PlanetInfo p = planets.get(i);
|
||||
if (p.isMoon()) {
|
||||
planetDrawCache.add(null); // placeholder; filled in pass 2
|
||||
planetCache.add(p);
|
||||
continue;
|
||||
}
|
||||
double phase = (2.0 * Math.PI * phaseSlot / Math.max(1, nonMoonCount));
|
||||
double period = EARTH_PERIOD_MS * Math.pow(p.orbitDistance(), 1.5);
|
||||
double angle = phase + elapsed * 2.0 * Math.PI / period;
|
||||
int screenR = orbitRadius(p.orbitDistance(), minOrbit, maxOrbit);
|
||||
int px = cx + (int)(screenR * Math.cos(angle));
|
||||
int py = cy + (int)(screenR * Math.sin(angle));
|
||||
planetDrawCache.add(new int[]{px, py, screenR, i % PLANET_COLORS.length});
|
||||
planetCache.add(p);
|
||||
phaseSlot++;
|
||||
}
|
||||
|
||||
// Pass 2 — moons: positions relative to their parent's draw position.
|
||||
// moonSlotByParent tracks per-parent slot so phase offsets spread siblings evenly.
|
||||
java.util.Map<java.util.Optional<ResourceLocation>, Integer> moonSlotByParent = new java.util.HashMap<>();
|
||||
for (int i = 0; i < planets.size(); i++) {
|
||||
PlanetInfo p = planets.get(i);
|
||||
if (!p.isMoon()) continue;
|
||||
int parentIdx = indexById(p.parentPlanet().orElse(null));
|
||||
int[] parentPos = (parentIdx >= 0) ? planetDrawCache.get(parentIdx) : null;
|
||||
int ocx = (parentPos != null) ? parentPos[0] : cx;
|
||||
int ocy = (parentPos != null) ? parentPos[1] : cy;
|
||||
int slot = moonSlotByParent.getOrDefault(p.parentPlanet(), 0);
|
||||
int siblings = (int) planets.stream().filter(q -> q.isMoon() && q.parentPlanet().equals(p.parentPlanet())).count();
|
||||
double phase = (2.0 * Math.PI * slot / Math.max(1, siblings));
|
||||
double angle = phase + elapsed * 2.0 * Math.PI / MOON_PERIOD_MS;
|
||||
int px = ocx + (int)(MOON_ORBIT_R * Math.cos(angle));
|
||||
int py = ocy + (int)(MOON_ORBIT_R * Math.sin(angle));
|
||||
planetDrawCache.set(i, new int[]{px, py, MOON_ORBIT_R, i % PLANET_COLORS.length});
|
||||
moonSlotByParent.put(p.parentPlanet(), slot + 1);
|
||||
}
|
||||
} else {
|
||||
// ── SUBSYSTEM VIEW ─────────────────────────────────────────────
|
||||
// Focused planet sits at orrery centre; its moons orbit it at ZOOM_MOON_ORBIT_R.
|
||||
// Every other body is hidden (null entry → unclickable, not drawn).
|
||||
java.util.Map<java.util.Optional<ResourceLocation>, Integer> moonSlotByParent = new java.util.HashMap<>();
|
||||
for (int i = 0; i < planets.size(); i++) {
|
||||
PlanetInfo p = planets.get(i);
|
||||
planetCache.add(p);
|
||||
if (p.id().equals(zoomedSystem)) {
|
||||
// The focused planet itself: place it at the centre.
|
||||
planetDrawCache.add(new int[]{cx, cy, 0, i % PLANET_COLORS.length});
|
||||
} else if (p.isMoon() && p.parentPlanet().map(zoomedSystem::equals).orElse(false)) {
|
||||
// Moon of the focused planet: orbit around centre.
|
||||
int slot = moonSlotByParent.getOrDefault(p.parentPlanet(), 0);
|
||||
int siblings = (int) planets.stream().filter(q -> q.isMoon() && q.parentPlanet().equals(p.parentPlanet())).count();
|
||||
double phase = (2.0 * Math.PI * slot / Math.max(1, siblings));
|
||||
double angle = phase + elapsed * 2.0 * Math.PI / MOON_PERIOD_MS;
|
||||
int px = cx + (int)(ZOOM_MOON_ORBIT_R * Math.cos(angle));
|
||||
int py = cy + (int)(ZOOM_MOON_ORBIT_R * Math.sin(angle));
|
||||
planetDrawCache.add(new int[]{px, py, ZOOM_MOON_ORBIT_R, i % PLANET_COLORS.length});
|
||||
moonSlotByParent.put(p.parentPlanet(), slot + 1);
|
||||
} else {
|
||||
planetDrawCache.add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Identify current and destination planets
|
||||
ResourceLocation curOrbit = data != null ? data.getOrbit() : null;
|
||||
ResourceLocation destId = data != null ? data.getDestination() : null;
|
||||
|
||||
int curIdx = indexByOrbit(curOrbit);
|
||||
int destIdx = indexById(destId);
|
||||
|
||||
// ── Orbit rings ────────────────────────────────────────────────────
|
||||
for (int i = 0; i < planets.size(); i++) {
|
||||
int[] pos = planetDrawCache.get(i);
|
||||
if (pos == null) continue;
|
||||
PlanetInfo p = planets.get(i);
|
||||
boolean isCur = i == curIdx;
|
||||
boolean isDest = i == destIdx;
|
||||
boolean isSel = p.id().equals(selectedPlanet);
|
||||
int col = isCur ? C_ORBIT_CUR : isDest ? C_ORBIT_DEST : isSel ? C_ORBIT_SEL : C_ORBIT;
|
||||
if (p.isMoon()) {
|
||||
int parentIdx = indexById(p.parentPlanet().orElse(null));
|
||||
int[] parentPos = (parentIdx >= 0) ? planetDrawCache.get(parentIdx) : null;
|
||||
int ocx = (parentPos != null) ? parentPos[0] : cx;
|
||||
int ocy = (parentPos != null) ? parentPos[1] : cy;
|
||||
drawOrbitRing(g, ocx, ocy, pos[2], col); // pos[2] holds the orbit radius (system vs zoom)
|
||||
} else if (pos[2] > 0) {
|
||||
drawOrbitRing(g, cx, cy, pos[2], col); // pos[2] == 0 for the focused planet in zoom mode
|
||||
}
|
||||
}
|
||||
|
||||
// ── Travel arc ─────────────────────────────────────────────────────
|
||||
if (data != null && data.isTraveling() && curIdx >= 0 && destIdx >= 0) {
|
||||
int[] sp = planetDrawCache.get(curIdx);
|
||||
int[] dp = planetDrawCache.get(destIdx);
|
||||
if (sp != null && dp != null)
|
||||
drawDashedLine(g, sp[0], sp[1], dp[0], dp[1], C_TRAVEL_LINE);
|
||||
}
|
||||
|
||||
// ── Central body (star in system view; focused planet in subsystem view) ──
|
||||
if (zoomedSystem == null) {
|
||||
drawOrbitRing(g, cx, cy, 7, 0x20FFE866);
|
||||
drawDot(g, cx, cy, 4, C_STAR);
|
||||
} else {
|
||||
// Draw focused planet at centre with a glow ring, and show a back hint.
|
||||
int focusedIdx = indexById(zoomedSystem);
|
||||
int focusedColor = (focusedIdx >= 0) ? PLANET_COLORS[planetDrawCache.get(focusedIdx)[3]] : C_WHITE;
|
||||
drawOrbitRing(g, cx, cy, 9, focusedColor & 0x40FFFFFF);
|
||||
drawDot(g, cx, cy, 5, focusedColor);
|
||||
var font = Minecraft.getInstance().font;
|
||||
String hint = "↩ system view";
|
||||
g.drawString(font, hint, orrX + 4, orrY + ORRERY_H - 10, C_GRAY, false);
|
||||
}
|
||||
|
||||
// ── Planet dots ────────────────────────────────────────────────────
|
||||
var font = Minecraft.getInstance().font;
|
||||
for (int i = 0; i < planets.size(); i++) {
|
||||
PlanetInfo p = planets.get(i);
|
||||
int[] pos = planetDrawCache.get(i);
|
||||
if (pos == null) continue;
|
||||
int px = pos[0], py = pos[1];
|
||||
|
||||
boolean isCur = i == curIdx;
|
||||
boolean isDest = i == destIdx;
|
||||
boolean isSel = p.id().equals(selectedPlanet);
|
||||
boolean isHover = distSq(mouseX, mouseY, px, py) <= 64;
|
||||
|
||||
int baseColor = PLANET_COLORS[pos[3]];
|
||||
int dotColor = isSel ? blend(baseColor, 0xFFFFFFFF, 0.5f)
|
||||
: isDest ? blend(baseColor, 0xFF44FF44, 0.5f)
|
||||
: isCur ? blend(baseColor, 0xFFFFFFFF, 0.3f)
|
||||
: baseColor;
|
||||
int dotR = (isSel || isCur || isDest) ? 4 : 3;
|
||||
drawDot(g, px, py, dotR, dotColor);
|
||||
|
||||
// Label: always for current/dest/selected, on hover otherwise
|
||||
if (isCur || isDest || isSel || isHover) {
|
||||
String name = formatId(p.id());
|
||||
int labelX = px + dotR + 3;
|
||||
int labelY = py - 4;
|
||||
if (labelX + font.width(name) > orrX + ORRERY_W - 4)
|
||||
labelX = px - font.width(name) - dotR - 2;
|
||||
int labelCol = isCur ? 0xFFAAAAFF : isDest ? 0xFF88FF88 : isSel ? C_WHITE : C_GRAY;
|
||||
g.drawString(font, name, labelX, labelY, labelCol, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Station marker ─────────────────────────────────────────────────
|
||||
if (data != null) {
|
||||
if (data.isTraveling() && curIdx >= 0 && destIdx >= 0) {
|
||||
int[] sp = planetDrawCache.get(curIdx);
|
||||
int[] dp = planetDrawCache.get(destIdx);
|
||||
if (sp != null && dp != null) {
|
||||
double tripDist = Math.abs(data.getTripDistanceAU());
|
||||
float progress = tripDist > 0
|
||||
? (float)(Math.abs(data.getDistanceTraveledAU()) / tripDist) : 0f;
|
||||
progress = Math.max(0f, Math.min(1f, progress));
|
||||
int sx = (int)(sp[0] + (dp[0] - sp[0]) * progress);
|
||||
int sy = (int)(sp[1] + (dp[1] - sp[1]) * progress);
|
||||
drawStation(g, sx, sy, C_STATION);
|
||||
}
|
||||
} else if (curIdx >= 0) {
|
||||
int[] pos = planetDrawCache.get(curIdx);
|
||||
if (pos != null) {
|
||||
PlanetInfo curPlanet = planetCache.get(curIdx);
|
||||
int ocx = cx, ocy = cy;
|
||||
if (curPlanet.isMoon()) {
|
||||
int parentIdx = indexById(curPlanet.parentPlanet().orElse(null));
|
||||
int[] parentPos = (parentIdx >= 0) ? planetDrawCache.get(parentIdx) : null;
|
||||
if (parentPos != null) { ocx = parentPos[0]; ocy = parentPos[1]; }
|
||||
}
|
||||
// Offset slightly along the orbit ring so station doesn't overlap the planet dot
|
||||
double baseAngle = Math.atan2(pos[1] - ocy, pos[0] - ocx);
|
||||
double stAngle = baseAngle + 0.35;
|
||||
int sx = ocx + (int)(pos[2] * Math.cos(stAngle));
|
||||
int sy = ocy + (int)(pos[2] * Math.sin(stAngle));
|
||||
drawStation(g, sx, sy, C_STATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Info panel ─────────────────────────────────────────────────────────
|
||||
|
||||
private void drawInfoPanel(GuiGraphics g, int mouseX, int mouseY) {
|
||||
var font = Minecraft.getInstance().font;
|
||||
int ix = leftPos + INFO_X;
|
||||
int iy = topPos + 2;
|
||||
|
||||
g.fill(ix, iy, ix + INFO_W, iy + ORRERY_H, C_PANEL);
|
||||
// Divider from orrery
|
||||
g.fill(ix - 2, iy + 6, ix - 1, iy + ORRERY_H - 6, C_BORDER);
|
||||
|
||||
int tx = ix + 5;
|
||||
int ty = iy + 6;
|
||||
|
||||
// Title
|
||||
String title = "COMPUTER";
|
||||
g.drawString(font, title, ix + (INFO_W - font.width(title)) / 2, ty, C_GOLD, false);
|
||||
ty += 10;
|
||||
g.fill(tx, ty, ix + INFO_W - 5, ty + 1, C_BORDER);
|
||||
ty += 5;
|
||||
|
||||
PartitionData data = PartitionClientState.get().map(p -> p.partitionData()).orElse(null);
|
||||
|
||||
// Resolve current planet for system + orbit display
|
||||
var currentPlanet = (data != null && data.getOrbit() != null)
|
||||
? net.xevianlight.aphelion.planet.PlanetCache.getByOrbitOrNull(data.getOrbit())
|
||||
: null;
|
||||
|
||||
// System
|
||||
String systemName = currentPlanet != null ? formatId(currentPlanet.system().location()) : "—";
|
||||
g.drawString(font, "System:", tx, ty, C_GRAY, false);
|
||||
ty += 9;
|
||||
g.drawString(font, systemName, tx + 2, ty, 0xFFCCCCFF, false);
|
||||
ty += 11;
|
||||
|
||||
// Orbit (last segment only — strips the "orbit/" prefix)
|
||||
String orbitName = (data != null && data.getOrbit() != null) ? formatLastSegment(data.getOrbit()) : "—";
|
||||
g.drawString(font, "Orbit:", tx, ty, C_GRAY, false);
|
||||
ty += 9;
|
||||
g.drawString(font, orbitName, tx + 2, ty, 0xFFAAAAFF, false);
|
||||
ty += 11;
|
||||
|
||||
// Destination (committed from PartitionData, or preview from selection)
|
||||
boolean hasDest = data != null && data.getDestination() != null;
|
||||
ResourceLocation destId = hasDest ? data.getDestination() : selectedPlanet;
|
||||
String destName = destId != null ? formatId(destId) : "—";
|
||||
int destColor = hasDest ? 0xFF88FF88 : selectedPlanet != null ? C_GOLD : C_GRAY;
|
||||
g.drawString(font, "Dest:", tx, ty, C_GRAY, false);
|
||||
ty += 9;
|
||||
g.drawString(font, destName, tx + 2, ty, destColor, false);
|
||||
ty += 10;
|
||||
|
||||
// Distance to destination (shown whenever a destination is selected or committed)
|
||||
if (destId != null && data != null) {
|
||||
double currentAU = data.getOrbitDistance();
|
||||
double destAU = currentAU; // fallback
|
||||
for (PlanetInfo pi : planetCache) {
|
||||
if (pi.id().equals(destId)) { destAU = pi.orbitDistance(); break; }
|
||||
}
|
||||
double dist = Math.abs(destAU - currentAU);
|
||||
String distLabel = dist < 0.01
|
||||
? "%.4f AU to dest".formatted(dist)
|
||||
: "%.2f AU to dest".formatted(dist);
|
||||
g.drawString(font, distLabel, tx + 2, ty, C_GRAY, false);
|
||||
ty += 10;
|
||||
}
|
||||
ty += 2;
|
||||
|
||||
g.fill(tx, ty, ix + INFO_W - 5, ty + 1, C_BORDER);
|
||||
ty += 4;
|
||||
|
||||
// Engines & Pads
|
||||
g.drawString(font, "Engines: " + menu.getEngineCount(), tx, ty, C_GRAY, false);
|
||||
ty += 10;
|
||||
g.drawString(font, "Pads: " + menu.getPadCount(), tx, ty, C_GRAY, false);
|
||||
ty += 13;
|
||||
|
||||
g.fill(tx, ty, ix + INFO_W - 5, ty + 1, C_BORDER);
|
||||
ty += 4;
|
||||
|
||||
// Travel status
|
||||
boolean traveling = data != null && data.isTraveling();
|
||||
g.drawString(font, traveling ? "TRAVELING" : "IDLE", tx, ty, traveling ? C_GOLD : C_GRAY, false);
|
||||
ty += 11;
|
||||
|
||||
if (traveling) {
|
||||
double tripDist = Math.abs(data.getTripDistanceAU());
|
||||
float progress = tripDist > 0
|
||||
? (float)(Math.abs(data.getDistanceTraveledAU()) / tripDist) : 0f;
|
||||
progress = Math.max(0f, Math.min(1f, progress));
|
||||
|
||||
int bw = INFO_W - 10;
|
||||
g.fill(tx, ty, tx + bw, ty + 7, C_PROG_BG);
|
||||
if (progress > 0) g.fill(tx, ty, tx + (int)(bw * progress), ty + 7, C_PROG_FILL);
|
||||
g.fill(tx, ty, tx + bw, ty + 1, C_BORDER);
|
||||
g.fill(tx, ty, tx + 1, ty + 7, C_BORDER);
|
||||
ty += 10;
|
||||
|
||||
String progStr = "%.2f / %.2f AU".formatted(
|
||||
Math.abs(data.getDistanceTraveledAU()),
|
||||
Math.abs(data.getTripDistanceAU()));
|
||||
g.drawString(font, progStr, tx, ty, C_GRAY, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Input ──────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
int orrX = leftPos + 2;
|
||||
int orrY = topPos + 2;
|
||||
|
||||
if (button == 0 && mouseX >= orrX && mouseX < orrX + ORRERY_W
|
||||
&& mouseY >= orrY && mouseY < orrY + ORRERY_H) {
|
||||
// Find nearest body within 10px
|
||||
ResourceLocation nearest = null;
|
||||
int nearestDist2 = 100;
|
||||
for (int i = 0; i < planetDrawCache.size(); i++) {
|
||||
int[] pos = planetDrawCache.get(i);
|
||||
if (pos == null) continue;
|
||||
int d2 = distSq((int)mouseX, (int)mouseY, pos[0], pos[1]);
|
||||
if (d2 < nearestDist2) { nearestDist2 = d2; nearest = planetCache.get(i).id(); }
|
||||
}
|
||||
|
||||
if (nearest == null) {
|
||||
// Clicked empty space: zoom out if zoomed in.
|
||||
zoomedSystem = null;
|
||||
} else if (zoomedSystem != null) {
|
||||
// In subsystem view: clicking any body selects it.
|
||||
selectedPlanet = nearest.equals(selectedPlanet) ? null : nearest;
|
||||
} else {
|
||||
// In system view: check if this body has moons.
|
||||
final ResourceLocation nearestFinal = nearest;
|
||||
boolean hasMoons = planetCache.stream().anyMatch(p ->
|
||||
p.isMoon() && p.parentPlanet().map(nearestFinal::equals).orElse(false));
|
||||
if (hasMoons) {
|
||||
// Zoom into this planet's subsystem; clear selection to avoid confusion.
|
||||
zoomedSystem = nearest;
|
||||
selectedPlanet = null;
|
||||
} else {
|
||||
selectedPlanet = nearest.equals(selectedPlanet) ? null : nearest;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
// ── Drawing helpers ────────────────────────────────────────────────────
|
||||
|
||||
private static void drawOrbitRing(GuiGraphics g, int cx, int cy, int r, int color) {
|
||||
int steps = Math.max(64, r * 6);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
double a = 2.0 * Math.PI * i / steps;
|
||||
int x = cx + (int)Math.round(r * Math.cos(a));
|
||||
int y = cy + (int)Math.round(r * Math.sin(a));
|
||||
g.fill(x, y, x + 1, y + 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawDot(GuiGraphics g, int cx, int cy, int r, int color) {
|
||||
int r2 = r * r + r;
|
||||
for (int dy = -r; dy <= r; dy++)
|
||||
for (int dx = -r; dx <= r; dx++)
|
||||
if (dx * dx + dy * dy <= r2)
|
||||
g.fill(cx + dx, cy + dy, cx + dx + 1, cy + dy + 1, color);
|
||||
}
|
||||
|
||||
/** Small cross (station marker). */
|
||||
private static void drawStation(GuiGraphics g, int cx, int cy, int color) {
|
||||
g.fill(cx - 4, cy, cx + 5, cy + 1, color); // horizontal
|
||||
g.fill(cx, cy - 4, cx + 1, cy + 5, color); // vertical
|
||||
// Hollow center for clarity
|
||||
g.fill(cx - 1, cy - 1, cx + 2, cy + 2, 0xFF000000);
|
||||
g.fill(cx, cy, cx + 1, cy + 1, color);
|
||||
}
|
||||
|
||||
private static void drawDashedLine(GuiGraphics g, int x1, int y1, int x2, int y2, int color) {
|
||||
float dx = x2 - x1, dy = y2 - y1;
|
||||
int steps = (int)Math.sqrt(dx * dx + dy * dy);
|
||||
if (steps == 0) return;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
if ((i / 3) % 2 == 0) {
|
||||
int x = x1 + (int)(dx * i / steps);
|
||||
int y = y1 + (int)(dy * i / steps);
|
||||
g.fill(x, y, x + 1, y + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Utilities ──────────────────────────────────────────────────────────
|
||||
|
||||
// Log scale so inner planets (Mercury, Venus) aren't crushed to the center on a linear AU axis.
|
||||
private int orbitRadius(double orbitDist, double minDist, double maxDist) {
|
||||
if (maxDist <= minDist) return (MIN_ORBIT_R + MAX_ORBIT_R) / 2;
|
||||
double logMin = Math.log(Math.max(minDist, 0.001));
|
||||
double logMax = Math.log(Math.max(maxDist, 0.001));
|
||||
double logDist = Math.log(Math.max(orbitDist, 0.001));
|
||||
double t = (logDist - logMin) / (logMax - logMin);
|
||||
return (int)(MIN_ORBIT_R + t * (MAX_ORBIT_R - MIN_ORBIT_R));
|
||||
}
|
||||
|
||||
private int indexByOrbit(@Nullable ResourceLocation orbitRl) {
|
||||
if (orbitRl == null) return -1;
|
||||
for (int i = 0; i < planetCache.size(); i++)
|
||||
if (planetCache.get(i).orbit().equals(orbitRl)) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int indexById(@Nullable ResourceLocation planetId) {
|
||||
if (planetId == null) return -1;
|
||||
for (int i = 0; i < planetCache.size(); i++)
|
||||
if (planetCache.get(i).id().equals(planetId)) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int distSq(int x1, int y1, int x2, int y2) {
|
||||
int dx = x1 - x2, dy = y1 - y2;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
private static int blend(int base, int target, float t) {
|
||||
int ba = (base >> 24) & 0xFF, br = (base >> 16) & 0xFF,
|
||||
bg = (base >> 8) & 0xFF, bb = base & 0xFF;
|
||||
int ta = (target >> 24) & 0xFF, tr = (target >> 16) & 0xFF,
|
||||
tg = (target >> 8) & 0xFF, tb = target & 0xFF;
|
||||
return ((int)(ba + (ta - ba) * t) << 24)
|
||||
| ((int)(br + (tr - br) * t) << 16)
|
||||
| ((int)(bg + (tg - bg) * t) << 8)
|
||||
| (int)(bb + (tb - bb) * t);
|
||||
}
|
||||
|
||||
private static String formatId(ResourceLocation id) {
|
||||
String[] parts = id.getPath().split("[_/]");
|
||||
var sb = new StringBuilder();
|
||||
for (String p : parts) {
|
||||
if (p.isEmpty()) continue;
|
||||
if (sb.length() > 0) sb.append(' ');
|
||||
sb.append(Character.toUpperCase(p.charAt(0))).append(p.substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Like formatId but only uses the last path segment, so orbit/earth → "Earth". */
|
||||
private static String formatLastSegment(ResourceLocation id) {
|
||||
String path = id.getPath();
|
||||
int slash = path.lastIndexOf('/');
|
||||
String name = slash >= 0 ? path.substring(slash + 1) : path;
|
||||
String[] parts = name.split("_");
|
||||
var sb = new StringBuilder();
|
||||
for (String p : parts) {
|
||||
if (p.isEmpty()) continue;
|
||||
if (sb.length() > 0) sb.append(' ');
|
||||
sb.append(Character.toUpperCase(p.charAt(0))).append(p.substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"custom_clouds": false,
|
||||
"custom_sky": true,
|
||||
"custom_weather": false,
|
||||
"dimension": "aphelion:space",
|
||||
"has_fog": false,
|
||||
"has_thick_fog": false,
|
||||
"render_in_rain": false,
|
||||
"sunrise_angle": 0,
|
||||
"sunrise_color": 14180147,
|
||||
"horizon_height": -128,
|
||||
"clear_color_scale": 1.0
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"custom_clouds": false,
|
||||
"custom_sky": true,
|
||||
"custom_weather": false,
|
||||
"dimension": "aphelion:space",
|
||||
"has_fog": false,
|
||||
"has_thick_fog": false,
|
||||
"render_in_rain": false,
|
||||
"sunrise_angle": 0,
|
||||
"sunrise_color": 14180147,
|
||||
"horizon_height": -128,
|
||||
"clear_color_scale": 1.0
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"custom_clouds": false,
|
||||
"custom_sky": true,
|
||||
"custom_weather": false,
|
||||
"dimension": "aphelion:space",
|
||||
"has_fog": false,
|
||||
"has_thick_fog": false,
|
||||
"render_in_rain": false,
|
||||
"sunrise_angle": 0,
|
||||
"sunrise_color": 14180147,
|
||||
"horizon_height": -128,
|
||||
"clear_color_scale": 1.0
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"entity.aphelion.rocket": "Rocket",
|
||||
|
||||
"fluid_type.aphelion.oil": "Oil",
|
||||
"fluid_type.aphelion.rocket_fuel": "Rocket Fuel",
|
||||
|
||||
|
||||
"command.aphelion.station.orbit.set": "Set station (%s, %s)'s orbit to %s",
|
||||
@@ -59,5 +60,7 @@
|
||||
"command.aphelion.station.owner.unset": "Station has no owner",
|
||||
"command.aphelion.station.owner.get": "Station (%s %s) belongs to %s",
|
||||
"command.aphelion.station.owner.set.success": "Set station (%s %s)'s owner to %s",
|
||||
"command.aphelion.player.invalid": "Player is invalid"
|
||||
"command.aphelion.player.invalid": "Player is invalid",
|
||||
|
||||
"block.aphelion.station_flight_computer": "Station Flight Computer"
|
||||
}
|
||||
|
||||
9
src/main/resources/data/aphelion/planet/deimos.json
Normal file
9
src/main/resources/data/aphelion/planet/deimos.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dimension": "aphelion:space",
|
||||
"orbit": "aphelion:orbit/deimos",
|
||||
"orbit_distance": 0.000157,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.003,
|
||||
"oxygen": false,
|
||||
"parent_planet": "aphelion:mars"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"dimension": "minecraft:overworld",
|
||||
"orbit": "aphelion:orbit/overworld",
|
||||
"orbit": "aphelion:orbit/earth",
|
||||
"orbit_distance": 1,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": false
|
||||
"oxygen": true
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/jupiter.json
Normal file
8
src/main/resources/data/aphelion/planet/jupiter.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:jupiter",
|
||||
"orbit": "aphelion:orbit/jupiter",
|
||||
"orbit_distance": 5.203,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 2.528,
|
||||
"oxygen": false
|
||||
}
|
||||
9
src/main/resources/data/aphelion/planet/luna.json
Normal file
9
src/main/resources/data/aphelion/planet/luna.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dimension": "aphelion:space",
|
||||
"orbit": "aphelion:orbit/luna",
|
||||
"orbit_distance": 0.00257,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.165,
|
||||
"oxygen": false,
|
||||
"parent_planet": "aphelion:earth"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:mars",
|
||||
"orbit": "aphelion:orbit/mars",
|
||||
"orbit_distance": 1.5,
|
||||
"orbit_distance": 1.524,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1,
|
||||
"gravity": 0.379,
|
||||
"oxygen": false
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/mercury.json
Normal file
8
src/main/resources/data/aphelion/planet/mercury.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:mercury",
|
||||
"orbit": "aphelion:orbit/mercury",
|
||||
"orbit_distance": 0.387,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.38,
|
||||
"oxygen": false
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/neptune.json
Normal file
8
src/main/resources/data/aphelion/planet/neptune.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:neptune",
|
||||
"orbit": "aphelion:orbit/neptune",
|
||||
"orbit_distance": 30.069,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1.148,
|
||||
"oxygen": false
|
||||
}
|
||||
9
src/main/resources/data/aphelion/planet/phobos.json
Normal file
9
src/main/resources/data/aphelion/planet/phobos.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dimension": "aphelion:space",
|
||||
"orbit": "aphelion:orbit/phobos",
|
||||
"orbit_distance": 0.0000627,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.0057,
|
||||
"oxygen": false,
|
||||
"parent_planet": "aphelion:mars"
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/pluto.json
Normal file
8
src/main/resources/data/aphelion/planet/pluto.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:pluto",
|
||||
"orbit": "aphelion:orbit/pluto",
|
||||
"orbit_distance": 39.482,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.063,
|
||||
"oxygen": false
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/saturn.json
Normal file
8
src/main/resources/data/aphelion/planet/saturn.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:saturn",
|
||||
"orbit": "aphelion:orbit/saturn",
|
||||
"orbit_distance": 9.537,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1.065,
|
||||
"oxygen": false
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/uranus.json
Normal file
8
src/main/resources/data/aphelion/planet/uranus.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:uranus",
|
||||
"orbit": "aphelion:orbit/uranus",
|
||||
"orbit_distance": 19.191,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.886,
|
||||
"oxygen": false
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/venus.json
Normal file
8
src/main/resources/data/aphelion/planet/venus.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:venus",
|
||||
"orbit": "aphelion:orbit/venus",
|
||||
"orbit_distance": 0.723,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 0.905,
|
||||
"oxygen": false
|
||||
}
|
||||
3
src/main/resources/data/aphelion/star_system/sol.json
Normal file
3
src/main/resources/data/aphelion/star_system/sol.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"temp": 5778
|
||||
}
|
||||
Reference in New Issue
Block a user