mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 10:00:54 +01:00
Compare commits
10 Commits
pipes-syst
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01d3c133e1 | ||
|
|
903c7f7d17 | ||
|
|
302acaaa18 | ||
|
|
341ed8a17d | ||
|
|
2496e0cdd5 | ||
|
|
0f4b98a912 | ||
|
|
018886768e | ||
|
|
bc8bb4ee05 | ||
|
|
ff08a51540 | ||
|
|
c0daaf2cfa |
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
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
// 1.21.1 2026-01-11T15:05:33.587044 Tags for minecraft:fluid mod id aphelion
|
||||
// 1.21.1 2026-03-14T19:22:23.3145075 Tags for minecraft:fluid mod id aphelion
|
||||
36b33555f1ae6c80989afdd9d986ec0883959f49 data/minecraft/tags/fluid/rocket_fuel.json
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// 1.21.1 2026-02-04T21:03:31.3164337 Loot Tables
|
||||
// 1.21.1 2026-02-07T22:29:56.7198656 Loot Tables
|
||||
69d8318ddba171526d1fabb87d9d93548ed8598e data/aphelion/loot_table/blocks/arc_furnace_casing.json
|
||||
05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json
|
||||
ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json
|
||||
b63130d9c10485676303d729807b6fcaac080294 data/aphelion/loot_table/blocks/electric_arc_furnace.json
|
||||
b9cfe672ead8e2673a7b2f5c4cec831e7e8e7040 data/aphelion/loot_table/blocks/launch_pad.json
|
||||
afb6519a03415b8e0d5bafc9fadb70905a398046 data/aphelion/loot_table/blocks/oxygen_test_block.json
|
||||
f3178154dadee30cc28f5ff23af45be98f2766cf data/aphelion/loot_table/blocks/rocket_assembler_block.json
|
||||
87dfbcce4a96ea0ee6e9fc7908aa77d42d1904e5 data/aphelion/loot_table/blocks/rocket_assembler.json
|
||||
9e269f103ac1ce517c1a089b8ca5474af085bd66 data/aphelion/loot_table/blocks/rocket_seat.json
|
||||
1ab50c99e9f478840b9d003fd56ebdcab12fbbce data/aphelion/loot_table/blocks/test_block.json
|
||||
7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json
|
||||
797bf9839d79e08b4832c9eaf3cb303b0471ed0c data/aphelion/loot_table/blocks/vaf_dummy_block.json
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 1.21.1 2026-01-26T19:04:46.4981336 Tags for minecraft:block mod id aphelion
|
||||
// 1.21.1 2026-02-07T23:23:26.8270373 Tags for minecraft:block mod id aphelion
|
||||
46f0160a007d32a06624ad98f25e8a1a8d01bb08 data/aphelion/tags/block/launch_pad.json
|
||||
883d37ed36ecbde487d467ddb26b34082067aa09 data/aphelion/tags/block/rocket_seat.json
|
||||
058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks.json
|
||||
058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks/steel.json
|
||||
7d420216f15b8f78d2a3b298f9bb773a9e5f79c3 data/minecraft/tags/block/mineable/pickaxe.json
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
// 1.21.1 2026-02-04T21:03:31.3154339 Block States: aphelion
|
||||
// 1.21.1 2026-02-07T22:29:56.7183649 Block States: aphelion
|
||||
851ff42f7b21dec86107c8e0cefb3934ae4ebc08 assets/aphelion/blockstates/block_steel.json
|
||||
30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json
|
||||
cb4287104006c80c8396b290ab5258df65d62cef assets/aphelion/blockstates/electric_arc_furnace.json
|
||||
28131a570d3666b7f323de4ad8a69e52ceec92e2 assets/aphelion/blockstates/oxygen_test_block.json
|
||||
85c7c0dab53d0219b315c822147a90ade9075844 assets/aphelion/blockstates/rocket_assembler_block.json
|
||||
b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json
|
||||
a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json
|
||||
2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json
|
||||
5f7e8674070f31a63875b5d6147153bfa0eef61a assets/aphelion/models/block/electric_arc_furnace.json
|
||||
746f23f150a01524ad03cbd1eb822bfbb7cf453b assets/aphelion/models/block/oxygen_test_block.json
|
||||
17eb7327e504f7a88028af804e046281d9719fdf assets/aphelion/models/block/rocket_assembler_block.json
|
||||
e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json
|
||||
cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json
|
||||
3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json
|
||||
db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json
|
||||
279080c06ada87f54fd0a7b885b256dbe25a946a assets/aphelion/models/item/electric_arc_furnace.json
|
||||
24cf60e70f7d9450b0e70cf017662e80971bae17 assets/aphelion/models/item/oxygen_test_block.json
|
||||
dafa6e1a3cfd753e211ae94179a433c69e9d2a28 assets/aphelion/models/item/rocket_assembler_block.json
|
||||
ab6884315b8f6c99666a87f673b059b5659ff13d assets/aphelion/models/item/rocket_assembler.json
|
||||
74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json
|
||||
88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "aphelion:block/rocket_assembler"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"parent": "aphelion:block/rocket_assembler_block"
|
||||
}
|
||||
@@ -11,11 +11,11 @@
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "aphelion:rocket_assembler_block"
|
||||
"name": "aphelion:rocket_assembler"
|
||||
}
|
||||
],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "aphelion:blocks/rocket_assembler_block"
|
||||
"random_sequence": "aphelion:blocks/rocket_assembler"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}
|
||||
],
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "aphelion:rocket_seat"
|
||||
}
|
||||
],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "aphelion:blocks/rocket_seat"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"aphelion:rocket_seat"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"aphelion:rocket_fuel"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.xevianlight.aphelion;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
@@ -27,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;
|
||||
@@ -127,14 +130,15 @@ public class Aphelion {
|
||||
@SubscribeEvent
|
||||
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
// ItemBlockRenderTypes.setRenderLayer(ModFluids.SOURCE_OIL_FLUID.get(), RenderType.translucent());
|
||||
// ItemBlockRenderTypes.setRenderLayer(ModFluids.FLOWING_OIL_FLUID.get(), RenderType.translucent());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModFluids.ROCKET_FUEL.get(), RenderType.translucent());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModFluids.FLOWING_ROCKET_FUEL.get(), RenderType.translucent());
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientExtensions(RegisterClientExtensionsEvent event) {
|
||||
event.registerFluidType(((BaseFluidType) ModFluidTypes.OIL_FLUID_TYPE.get()).getClientFluidTypeExtensions(), ModFluidTypes.OIL_FLUID_TYPE.get());
|
||||
event.registerFluidType(((BaseFluidType) ModFluidTypes.ROCKET_FUEL_FLUID_TYPE.get()).getClientFluidTypeExtensions(), ModFluidTypes.ROCKET_FUEL_FLUID_TYPE.get());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -149,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
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.xevianlight.aphelion.block.custom.base.BaseRocketContainer;
|
||||
import net.xevianlight.aphelion.block.custom.base.BaseRocketFuelTank;
|
||||
|
||||
public class BasicRocketContainer extends BaseRocketContainer {
|
||||
public BasicRocketContainer(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotCapacity() {
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.xevianlight.aphelion.block.custom.base.BaseRocketFuelTank;
|
||||
|
||||
public class BasicRocketFuelTank extends BaseRocketFuelTank {
|
||||
public BasicRocketFuelTank(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFuelCapacity() {
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicEntityBlock;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicHorizontalEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
|
||||
|
||||
@@ -2,8 +2,12 @@ package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
@@ -11,7 +15,12 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
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.util.ModTags;
|
||||
import net.xevianlight.aphelion.util.SpacePartition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LaunchPad extends Block {
|
||||
@@ -73,5 +82,4 @@ public class LaunchPad extends Block {
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,27 +2,37 @@ package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicHorizontalEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.RocketAssemblerBlockEntity;
|
||||
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.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RocketAssemblerBlock extends BasicHorizontalEntityBlock {
|
||||
public class RocketAssembler extends BasicHorizontalEntityBlock {
|
||||
|
||||
public static final BooleanProperty FORMED = AphelionBlockStateProperties.FORMED;
|
||||
|
||||
public RocketAssemblerBlock(Properties properties) {
|
||||
public RocketAssembler(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
public static final MapCodec<RocketAssemblerBlock> CODEC = simpleCodec(RocketAssemblerBlock::new);
|
||||
public static final MapCodec<RocketAssembler> CODEC = simpleCodec(RocketAssembler::new);
|
||||
|
||||
@Override
|
||||
protected @NotNull MapCodec<? extends BaseEntityBlock> codec() {
|
||||
@@ -53,4 +63,12 @@ public class RocketAssemblerBlock extends BasicHorizontalEntityBlock {
|
||||
builder.add(FORMED);
|
||||
super.createBlockStateDefinition(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
|
||||
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof RocketAssemblerBlockEntity rocketAssemblerBlockEntity) {
|
||||
RocketEntity rocket = rocketAssemblerBlockEntity.assemble();
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
|
||||
public class RocketSeat extends Block {
|
||||
public RocketSeat(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.STONE)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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);
|
||||
|
||||
public StationFlightComputerBlock(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||
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) {
|
||||
data.setTraveling(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
|
||||
super.onPlace(state, level, pos, oldState, movedByPiston);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
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.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.common.SoundActions;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import net.neoforged.neoforge.fluids.FluidUtil;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.base.StationEngineBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationRocketEngineBlock extends StationEngineBlock {
|
||||
public StationRocketEngineBlock(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||
return new StationRocketEngineBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemInteractionResult useItemOn(
|
||||
ItemStack stack,
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit) {
|
||||
|
||||
// Only intercept on client if holding a fluid container, otherwise let block placement through
|
||||
if (level.isClientSide) {
|
||||
return FluidUtil.getFluidHandler(stack).isPresent()
|
||||
? ItemInteractionResult.SUCCESS
|
||||
: ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (!(be instanceof StationRocketEngineBlockEntity engineBE)) {
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
IFluidHandler tankHandler = level.getCapability(
|
||||
Capabilities.FluidHandler.BLOCK, pos, state, be, null
|
||||
);
|
||||
|
||||
if (tankHandler == null) {
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
FluidStack fluidBeforeInteraction = FluidUtil.getFluidContained(player.getItemInHand(hand))
|
||||
.orElse(FluidStack.EMPTY);
|
||||
|
||||
boolean success = FluidUtil.interactWithFluidHandler(player, hand, tankHandler);
|
||||
|
||||
if (success) {
|
||||
FluidStack tankFluid = tankHandler.getFluidInTank(0);
|
||||
FluidStack relevantFluid = fluidBeforeInteraction.isEmpty() ? tankFluid : fluidBeforeInteraction;
|
||||
|
||||
Aphelion.LOGGER.info("fluidBeforeInteraction: {}", fluidBeforeInteraction);
|
||||
Aphelion.LOGGER.info("tankFluid: {}", tankFluid);
|
||||
Aphelion.LOGGER.info("relevantFluid: {}", relevantFluid);
|
||||
|
||||
if (!relevantFluid.isEmpty()) {
|
||||
FluidType fluidType = relevantFluid.getFluid().getFluidType();
|
||||
SoundEvent sound = fluidBeforeInteraction.isEmpty()
|
||||
? fluidType.getSound(SoundActions.BUCKET_FILL)
|
||||
: fluidType.getSound(SoundActions.BUCKET_EMPTY);
|
||||
|
||||
Aphelion.LOGGER.info("fluidType: {}", fluidType);
|
||||
Aphelion.LOGGER.info("sound: {}", sound);
|
||||
|
||||
if (sound != null) {
|
||||
Aphelion.LOGGER.info("Playing sound!");
|
||||
level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
return ItemInteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
|
||||
public class BaseRocketContainer extends Block implements IRocketInventoryUpgrade {
|
||||
public BaseRocketContainer(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.METAL)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotCapacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
|
||||
public class BaseRocketFuelTank extends Block implements IRocketFuelUpgrade {
|
||||
public BaseRocketFuelTank(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.METAL)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFuelCapacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,10 @@ public abstract class BasicEntityBlock extends BaseEntityBlock {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties.of();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
@@ -37,14 +41,16 @@ public abstract class BasicEntityBlock extends BaseEntityBlock {
|
||||
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(@NotNull Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
|
||||
return !shouldTick ? null : (entityLevel, pos, blockState, blockEntity) -> {
|
||||
if (blockEntity instanceof TickableBlockEntity tickable) {
|
||||
long time = level.getGameTime() - pos.hashCode();
|
||||
if (!tickable.isInitialized()) tickable.firstTick(entityLevel, blockState, pos);
|
||||
|
||||
long time = entityLevel.getGameTime() - pos.hashCode();
|
||||
tickable.tick(entityLevel, time, blockState, pos);
|
||||
if (level.isClientSide()) {
|
||||
tickable.clientTick((ClientLevel) level, time, state, pos);
|
||||
|
||||
if (entityLevel.isClientSide()) {
|
||||
tickable.clientTick((ClientLevel) entityLevel, time, blockState, pos);
|
||||
} else {
|
||||
tickable.serverTick((ServerLevel) level, time, state, pos);
|
||||
tickable.serverTick((ServerLevel) entityLevel, time, blockState, pos);
|
||||
}
|
||||
if (!tickable.isInitialized()) tickable.firstTick(level, state, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
/**
|
||||
* Used for blocks which should provide energy storage capacity to a rocket.
|
||||
* <p>Note that blocks implementing this interface should not store energy themselves.
|
||||
* Rockets determine their energy capacity from the sum of these blocks installed on them.</p>
|
||||
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
|
||||
*/
|
||||
public interface IRocketEnergyUpgrade {
|
||||
/**
|
||||
* Used to determine how much FE of energy storage a rocket receives from having this block is installed.
|
||||
*/
|
||||
int getEnergyCapacity();
|
||||
|
||||
/**
|
||||
* Used to determine how much FE transfer rate bonus a rocket receives from having this block is installed. This is added onto the base rockets energy transfer limit.
|
||||
*/
|
||||
default int getMaxTransferBonus() {
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
/**
|
||||
* Used for blocks which should provide fluid storage capacity to a rocket.
|
||||
* <p>Note that blocks implementing this interface should not store fluids themselves.
|
||||
* Rockets determine their fluid container capacity from the sum of these blocks installed on them.</p>
|
||||
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
|
||||
*/
|
||||
public interface IRocketFluidUpgrade {
|
||||
/**
|
||||
* Used to determine how many millibuckets of fluid storage a rocket receives from having this block is installed.
|
||||
*/
|
||||
int getFluidCapacity();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
|
||||
|
||||
/**
|
||||
* Used for blocks which should provide fuel storage capacity to a rocket.
|
||||
* <p>Note that blocks implementing this interface should not store fuel themselves.
|
||||
* Rockets determine their fuel container capacity from the sum of these blocks installed on them.</p>
|
||||
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
|
||||
*/
|
||||
public interface IRocketFuelUpgrade {
|
||||
/**
|
||||
* Used to determine how many millibuckets of fuel storage a rocket receives from having this block is installed.
|
||||
*/
|
||||
int getFuelCapacity();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
/**
|
||||
* Used for blocks which should provide item slots to a rockets inventory.
|
||||
* <p>Note that blocks implementing this interface should not store items themselves.
|
||||
* Rockets determine their inventory slot count from the sum of these blocks installed on them.</p>
|
||||
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
|
||||
*/
|
||||
public interface IRocketInventoryUpgrade {
|
||||
/**
|
||||
* Used to determine how many inventory slots a rocket receives from having this block installed.
|
||||
*/
|
||||
|
||||
int getSlotCapacity();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationEngineBlock extends BasicEntityBlock {
|
||||
|
||||
public static final MapCodec<StationEngineBlock> CODEC = simpleCodec(StationEngineBlock::new);
|
||||
|
||||
protected StationEngineBlock(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -9,41 +9,78 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
public interface TickableBlockEntity {
|
||||
|
||||
/**
|
||||
* Runs on both the client AND server.
|
||||
* @param level
|
||||
* @param time
|
||||
* @param state
|
||||
* @param pos
|
||||
* Runs on both the client and server once per tick.
|
||||
*
|
||||
* <p>This is intended for logic that is common to both sides. Side-specific logic
|
||||
* should go in {@link #clientTick(ClientLevel, long, BlockState, BlockPos)} or
|
||||
* {@link #serverTick(ServerLevel, long, BlockState, BlockPos)}.</p>
|
||||
*
|
||||
* @param level the current level
|
||||
* @param time a deterministic per-position tick time (see ticker implementation)
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
*/
|
||||
default void tick (Level level, long time, BlockState state, BlockPos pos) {};
|
||||
|
||||
/**
|
||||
* Runs on the client only
|
||||
* @param level
|
||||
* @param time
|
||||
* @param state
|
||||
* @param pos
|
||||
* Runs on the client only once per tick.
|
||||
*
|
||||
* <p>Use this for client-side visual updates, particles, sounds, animation state,
|
||||
* or other logic that must not run on the logical server.</p>
|
||||
*
|
||||
* @param level the client level
|
||||
* @param time a deterministic per-position tick time (see ticker implementation)
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
*/
|
||||
void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos);
|
||||
|
||||
/**
|
||||
* Runs on the server only
|
||||
* @param level
|
||||
* @param time
|
||||
* @param state
|
||||
* @param pos
|
||||
* Runs on the server only once per tick.
|
||||
*
|
||||
* <p>Use this for authoritative game logic such as inventory processing, energy
|
||||
* generation/consumption, entity spawning, saving state, and network sync triggers.</p>
|
||||
*
|
||||
* @param level the server level
|
||||
* @param time a deterministic per-position tick time (see ticker implementation)
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
*/
|
||||
void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos);
|
||||
|
||||
/**
|
||||
* Returns whether this object has completed its initialization logic.
|
||||
*
|
||||
* <p>If this method returns {@code false}, {@link #firstTick(Level, BlockState, BlockPos)}
|
||||
* will be invoked at the start of each tick on both the client and server until
|
||||
* initialization is complete.</p>
|
||||
*
|
||||
* <p>Implementations should return {@code true} once initialization has finished
|
||||
* to prevent {@code firstTick} from running again.</p>
|
||||
*
|
||||
* <p>Returns {@code true} if not implemented.</p>
|
||||
*
|
||||
* @return {@code true} if initialization has completed, {@code false} otherwise
|
||||
*/
|
||||
default boolean isInitialized() {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on client AND server, once only.
|
||||
* @param level
|
||||
* @param state
|
||||
* @param pos
|
||||
* Performs initialization logic for this object.
|
||||
*
|
||||
* <p>This method is called at the start of each tick on both the client and server
|
||||
* whenever {@link #isInitialized()} returns {@code false}. It will continue to be
|
||||
* invoked every tick until initialization is complete.</p>
|
||||
*
|
||||
* <p>Implementations should perform any required setup and ensure that
|
||||
* {@code isInitialized()} returns {@code true} afterward.</p>
|
||||
*
|
||||
* <p>Will never run if {@link #isInitialized()} is not implemented.</p>
|
||||
*
|
||||
* @param level the level the block entity exists in
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
*/
|
||||
void firstTick(Level level, BlockState state, BlockPos pos);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvider, TickableBlockEntity {
|
||||
public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvider, TickableBlockEntity, IArcFurnaceLike {
|
||||
|
||||
private final int SIZE = 4;
|
||||
private int ENERGY_CAPACITY = 64000;
|
||||
@@ -309,6 +309,11 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
|
||||
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergyStorage getTrueEnergyStorage() {
|
||||
return ENERGY_STORAGE;
|
||||
}
|
||||
|
||||
private final IItemHandler fullHandler = new SidedSlotHandler(inventory, new int[]{INPUT_SLOT, SECONDARY_INPUT_SLOT, OUTPUT_SLOT, ENERGY_SLOT}, true, true);
|
||||
private final IItemHandler emptyJeiHandler = new SidedSlotHandler(inventory, new int[]{}, false, false);
|
||||
private final IItemHandler inputHandler = new SidedSlotHandler(inventory, new int[]{0,1}, true, true);
|
||||
@@ -344,6 +349,11 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModEnergyStorage getEnergy() {
|
||||
return ENERGY_STORAGE;
|
||||
}
|
||||
|
||||
public void drops() {
|
||||
SimpleContainer inv = new SimpleContainer(inventory.getSlots());
|
||||
for(int i = 0; i < inventory.getSlots(); i++) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.energy.IEnergyStorage;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
|
||||
|
||||
|
||||
public interface IArcFurnaceLike {
|
||||
ItemStackHandler getInventory();
|
||||
ModEnergyStorage getEnergy();
|
||||
|
||||
void sendUpdate();
|
||||
|
||||
BlockState getBlockState();
|
||||
|
||||
IEnergyStorage getTrueEnergyStorage();
|
||||
}
|
||||
@@ -16,10 +16,15 @@ import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModBlocks;
|
||||
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.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
|
||||
import net.xevianlight.aphelion.util.ModTags;
|
||||
import net.xevianlight.aphelion.util.RocketStructure;
|
||||
@@ -28,6 +33,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RocketAssemblerBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
@@ -35,6 +42,8 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
BlockPos padScanStart = BlockPos.ZERO;
|
||||
private PadInfo padBounds;
|
||||
|
||||
private @Nullable PartitionData data;
|
||||
|
||||
public @Nullable PadInfo getPadBounds() {
|
||||
return padBounds;
|
||||
}
|
||||
@@ -57,50 +66,21 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
|
||||
return dx * dy * dz;
|
||||
}
|
||||
|
||||
public BlockPos getCenter() {
|
||||
int centerX = (min.getX() + max.getX()) / 2;
|
||||
int centerZ = (min.getZ() + max.getZ()) / 2;
|
||||
|
||||
// bottom Y level
|
||||
int y = min.getY();
|
||||
|
||||
return new BlockPos(centerX, y, centerZ);
|
||||
}
|
||||
}
|
||||
|
||||
private final Block TOWER_BLOCK = ModBlocks.BLOCK_STEEL.get();
|
||||
private static final Block TOWER_BLOCK = ModBlocks.BLOCK_STEEL.get();
|
||||
public BlockPos towerBasePos;
|
||||
|
||||
public @Nullable PadInfo getPlatform() {
|
||||
// TODO
|
||||
int y = this.padScanStart.getY();
|
||||
BlockPos start = this.padScanStart;
|
||||
|
||||
if (level == null) return null;
|
||||
if (!isPad(level.getBlockState(start))) return null;
|
||||
|
||||
int minX = start.getX();
|
||||
while (isPad(level.getBlockState(new BlockPos(minX - 1, y, start.getZ())))) minX--;
|
||||
|
||||
// Find maxX by walking east
|
||||
int maxX = start.getX();
|
||||
while (isPad(level.getBlockState(new BlockPos(maxX + 1, y, start.getZ())))) maxX++;
|
||||
|
||||
// Find minZ by walking north
|
||||
int minZ = start.getZ();
|
||||
while (isPad(level.getBlockState(new BlockPos(start.getX(), y, minZ - 1)))) minZ--;
|
||||
|
||||
// Find maxZ by walking south
|
||||
int maxZ = start.getZ();
|
||||
while (isPad(level.getBlockState(new BlockPos(start.getX(), y, maxZ + 1)))) maxZ++;
|
||||
|
||||
int width = (maxX - minX) + 1;
|
||||
int length = (maxZ - minZ) + 1;
|
||||
|
||||
// Must be square
|
||||
if (width != length) return null;
|
||||
|
||||
// Verify the entire rectangle is filled with pad blocks
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
if (!isPad(level.getBlockState(new BlockPos(x, y, z)))) return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new PadInfo(new BlockPos(minX, y, minZ), new BlockPos(maxX, y, maxZ));
|
||||
}
|
||||
|
||||
private boolean connected(BlockState state, Direction dir) {
|
||||
return switch (dir) {
|
||||
case NORTH -> state.getValue(BlockStateProperties.NORTH);
|
||||
@@ -111,7 +91,7 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
};
|
||||
}
|
||||
|
||||
public @Nullable PadInfo getPlatformFill() {
|
||||
public @Nullable PadInfo getPlatformViaFill() {
|
||||
if (level == null) return null;
|
||||
|
||||
BlockPos start = this.padScanStart;
|
||||
@@ -128,8 +108,8 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
visited.add(start.asLong());
|
||||
|
||||
final Direction[] CARDINALS = {Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST};
|
||||
// When the $#$# are we going to have a rocket larger than 64x64... don't...
|
||||
final int MAX_PAD_BLOCKS = 4096;
|
||||
// When the $#$# are we going to have a rocket larger than 32x32... don't...
|
||||
final int MAX_PAD_BLOCKS = 1024;
|
||||
|
||||
boolean towerFound = false;
|
||||
while (!queue.isEmpty()) {
|
||||
@@ -144,7 +124,7 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
BlockPos n = p.relative(d);
|
||||
|
||||
if (!connected(s, d)) {
|
||||
if (level.getBlockState(n).is(TOWER_BLOCK)) {
|
||||
if (isTower(level.getBlockState(n))) {
|
||||
if (!towerFound) {
|
||||
towerBasePos = n;
|
||||
towerFound = true;
|
||||
@@ -194,16 +174,154 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
int h = 0;
|
||||
BlockPos p = base.above();
|
||||
|
||||
while (level.getBlockState(p).is(TOWER_BLOCK)) {
|
||||
while (isTower(level.getBlockState(p))) {
|
||||
h++;
|
||||
p = p.above();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public RocketStructure scan() {
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
private BlockPos seatPos;
|
||||
|
||||
public @Nullable RocketStructure scan() {
|
||||
if (level == null) return null;
|
||||
|
||||
seatPos = null;
|
||||
|
||||
PadInfo bounds = padBounds;
|
||||
if (bounds == null) return null;
|
||||
|
||||
|
||||
BlockPos min = bounds.min();
|
||||
BlockPos max = bounds.max();
|
||||
|
||||
// Find seat, every rocket must have a seat and all blocks in the rocket must be attached to it
|
||||
for (int y = min.getY(); y <= max.getY(); y++) {
|
||||
for (int x = min.getX(); x <= max.getX(); x++) {
|
||||
for (int z = min.getZ(); z <= max.getZ(); z++) {
|
||||
BlockPos p = new BlockPos(x, y, z);
|
||||
BlockState st = level.getBlockState(p);
|
||||
|
||||
if (!st.is(ModTags.Blocks.ROCKET_SEAT)) continue;
|
||||
|
||||
if (seatPos != null && !seatPos.equals(p)) {
|
||||
Aphelion.LOGGER.warn("Rocket scan failed: multiple seats found");
|
||||
seatPos = null;
|
||||
return null;
|
||||
}
|
||||
seatPos = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (seatPos == null) {
|
||||
Aphelion.LOGGER.warn("Rocket scan failed: no seat found");
|
||||
seatPos = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
final Direction[] DIRS = Direction.values();
|
||||
|
||||
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
|
||||
LongOpenHashSet visited = new LongOpenHashSet();
|
||||
|
||||
queue.add(seatPos);
|
||||
visited.add(seatPos.asLong());
|
||||
|
||||
RocketStructure structure = new RocketStructure(s -> {});
|
||||
structure.addSeatOffset(0,0,0);
|
||||
|
||||
final int MAX_ROCKET_BLOCKS = 1000;
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
BlockPos p = queue.removeFirst();
|
||||
BlockState st = level.getBlockState(p);
|
||||
|
||||
if (!within(bounds, p)) continue;
|
||||
|
||||
// Any block cases we should IGNORE
|
||||
if (st.isAir()) continue; // ignore air
|
||||
if (isPad(st)) continue; // ignore the pad
|
||||
if (isTower(st)) continue; // ignore the tower
|
||||
if (p.equals(this.worldPosition)) continue; // ignore the assembler
|
||||
|
||||
// Reject block entities
|
||||
if (st.hasBlockEntity() || level.getBlockEntity(p) != null) {
|
||||
Aphelion.LOGGER.warn("Rocket scan failed: found block entity at {}", p);
|
||||
return null;
|
||||
}
|
||||
|
||||
int dx = p.getX() - seatPos.getX();
|
||||
int dy = p.getY() - seatPos.getY();
|
||||
int dz = p.getZ() - seatPos.getZ();
|
||||
|
||||
if (!fitsSignedByte(dx) || !fitsSignedByte(dy) || !fitsSignedByte(dz)) {
|
||||
Aphelion.LOGGER.warn("Rocket scan failed: structure too large to pack at {} (dx={},dy={},dz={})", p, dx, dy, dz);
|
||||
return null;
|
||||
}
|
||||
|
||||
// All checks succeeded, add the block to the rocket
|
||||
structure.add(dx, dy, dz, st);
|
||||
|
||||
// Explore neighbors
|
||||
for (Direction d : DIRS) {
|
||||
BlockPos n = p.relative(d);
|
||||
|
||||
if (!within(bounds, n)) continue; // Skip neighbor outside of rocket assembler bounds
|
||||
|
||||
long key = n.asLong();
|
||||
if (visited.contains(key)) continue; // Skip visited blocks
|
||||
|
||||
BlockState ns = level.getBlockState(n);
|
||||
|
||||
if (ns.isAir()) continue;
|
||||
if (isPad(ns)) continue;
|
||||
if (isTower(ns)) continue;
|
||||
if (n.equals(this.worldPosition)) continue;
|
||||
|
||||
visited.add(key);
|
||||
queue.addLast(n);
|
||||
|
||||
if (visited.size() >= MAX_ROCKET_BLOCKS) {
|
||||
Aphelion.LOGGER.warn("Rocket scan failed: exceeded max blocks ({})", MAX_ROCKET_BLOCKS);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (structure.size() == 0) return null;
|
||||
return structure;
|
||||
}
|
||||
|
||||
public @Nullable RocketEntity assemble() {
|
||||
if (level == null) return null;
|
||||
RocketStructure structure = scan();
|
||||
RocketEntity rocket = null;
|
||||
|
||||
var rockets = getRocketsInPad();
|
||||
if (rockets.size() == 1) rocket = rockets.getFirst();
|
||||
if (rocket != null)
|
||||
rocket.disassemble();
|
||||
|
||||
if (structure != null && seatPos != null) {
|
||||
RocketStructure.clearCaptured(level, seatPos, structure);
|
||||
rocket = RocketEntity.spawnRocket(level, seatPos, structure);
|
||||
Aphelion.LOGGER.info("Spawn rocket result: {}", rocket);
|
||||
}
|
||||
return rocket;
|
||||
}
|
||||
|
||||
private static boolean within(PadInfo pad, BlockPos p) {
|
||||
BlockPos min = pad.min;
|
||||
BlockPos max = pad.max;
|
||||
|
||||
return p.getX() >= min.getX() && p.getX() <= max.getX()
|
||||
&& p.getY() >= min.getY() && p.getY() <= max.getY()
|
||||
&& p.getZ() >= min.getZ() && p.getZ() <= max.getZ();
|
||||
}
|
||||
|
||||
private static boolean fitsSignedByte(int v) {
|
||||
return v >= -128 && v <= 127;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,27 +331,84 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
PadInfo newBounds = getPlatformFill();
|
||||
PadInfo newBounds = getPlatformViaFill();
|
||||
setPadBoundsAndSync(newBounds);
|
||||
|
||||
boolean formed = newBounds != null;
|
||||
if (state.getValue(AphelionBlockStateProperties.FORMED) != formed) {
|
||||
level.setBlockAndUpdate(pos, state.setValue(AphelionBlockStateProperties.FORMED, formed));
|
||||
if (data != null) {
|
||||
if (formed) {
|
||||
data.addLandingPadController(pos);
|
||||
} else {
|
||||
data.removeLandingPadController(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
if (level.isClientSide()) return;
|
||||
|
||||
facing = getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING);
|
||||
padScanStart = getBlockPos().mutable().below().relative(facing.getOpposite());
|
||||
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
}
|
||||
}
|
||||
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved() {
|
||||
if (data == null) return;
|
||||
data.removeLandingPadController(worldPosition);
|
||||
}
|
||||
|
||||
private static boolean isPad(BlockState s) {
|
||||
return s.is(ModTags.Blocks.LAUNCH_PAD); // or s.getBlock() == ModBlocks.PAD.get()
|
||||
}
|
||||
|
||||
private static boolean isTower(BlockState s) {
|
||||
return s.is(TOWER_BLOCK);
|
||||
}
|
||||
|
||||
public @NotNull List<RocketEntity> getRocketsInPad() {
|
||||
if (level == null || padBounds == null) return List.of();
|
||||
|
||||
AABB padBox = new AABB(
|
||||
padBounds.min().getX(),
|
||||
padBounds.min().getY(),
|
||||
padBounds.min().getZ(),
|
||||
padBounds.max().getX() + 1,
|
||||
padBounds.max().getY() + 1,
|
||||
padBounds.max().getZ() + 1
|
||||
);
|
||||
|
||||
var rockets = new ArrayList<>(level.getEntitiesOfClass(RocketEntity.class, padBox.inflate(0.2)));
|
||||
|
||||
List<RocketEntity> found = new java.util.ArrayList<>(List.of());
|
||||
|
||||
for (RocketEntity rocket : rockets) {
|
||||
AABB rocketBox = rocket.getBoundingBox();
|
||||
if (!isFullyInside(padBox, rocketBox)) continue;
|
||||
|
||||
found.add(rocket);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private static boolean isFullyInside(AABB outer, AABB inner) {
|
||||
return inner.minX >= outer.minX && inner.maxX <= outer.maxX
|
||||
&& inner.minY >= outer.minY && inner.maxY <= outer.maxY
|
||||
&& inner.minZ >= outer.minZ && inner.maxZ <= outer.maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(@NotNull CompoundTag tag, HolderLookup.@NotNull Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
@@ -296,4 +471,6 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
this.loadAdditional(tag, registries);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
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, MenuProvider {
|
||||
|
||||
protected PartitionData data;
|
||||
private boolean isInitialized = false;
|
||||
|
||||
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 clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {}
|
||||
|
||||
@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) {
|
||||
if (level.isClientSide()) return;
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
}
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
public boolean setTraveling(boolean value) {
|
||||
if (data == null) return false;
|
||||
data.setTraveling(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
|
||||
import net.xevianlight.aphelion.block.entity.custom.base.StationEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModFluidTags;
|
||||
|
||||
public class StationRocketEngineBlockEntity extends StationEngineBlockEntity {
|
||||
|
||||
/// Seconds to travel 1 AU
|
||||
private final double SECONDS_PER_AU = 60;
|
||||
/// AU per tick
|
||||
private final double SPEED = 1/(SECONDS_PER_AU*20);
|
||||
/// Fuel consumption per tick in millibuckets
|
||||
private static final int FUEL_CONSUMPTION = 10;
|
||||
|
||||
private FluidTank tank = new FluidTank(
|
||||
2000,
|
||||
fluidStack -> fluidStack.is(ModFluidTags.ROCKET_FUEL)
|
||||
);
|
||||
|
||||
@Override
|
||||
public double getTravelSpeed() {
|
||||
return SPEED;
|
||||
}
|
||||
|
||||
public IFluidHandler getFluidStorage(Direction direction) {
|
||||
return tank;
|
||||
}
|
||||
|
||||
public StationRocketEngineBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.STATION_ROCKET_ENGINE_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
super.serverTick(level, time, state, pos);
|
||||
burn();
|
||||
}
|
||||
|
||||
private void burn() {
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (data.getDestination() != null && data.isTraveling()) {
|
||||
if (!tank.isEmpty() && tank.getFluid().is(ModFluidTags.ROCKET_FUEL) && tank.getFluidAmount() >= FUEL_CONSUMPTION) { // has enough fuel?
|
||||
data.travel(getTravelSpeed());
|
||||
tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE);
|
||||
} else {
|
||||
// not enough fuel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
tag.put("fluid", tank.writeToNBT(registries, new CompoundTag()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.loadAdditional(tag, registries);
|
||||
if (tag.contains("fluid")) {
|
||||
tank.readFromNBT(registries, tag.getCompound("fluid"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
saveAdditional(tag, registries);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class VacuumArcFurnaceControllerEntity extends BlockEntity implements MenuProvider, IMultiblockController, TickableBlockEntity {
|
||||
public class VacuumArcFurnaceControllerEntity extends BlockEntity implements MenuProvider, IMultiblockController, TickableBlockEntity, IArcFurnaceLike {
|
||||
|
||||
private final int SIZE = 4;
|
||||
private final int ENERGY_CAPACITY = 64000;
|
||||
@@ -357,7 +357,8 @@ public class VacuumArcFurnaceControllerEntity extends BlockEntity implements Men
|
||||
return isFormed() ? ENERGY_STORAGE : null;
|
||||
}
|
||||
|
||||
public IEnergyStorage getTrueEnergyStorage(@Nullable Direction direction) {
|
||||
@Override
|
||||
public IEnergyStorage getTrueEnergyStorage() {
|
||||
return this.ENERGY_STORAGE;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom.base;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModDimensions;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class StationEngineBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
private boolean isInitialized = false;
|
||||
protected @Nullable PartitionData data;
|
||||
|
||||
/**
|
||||
* The travel speed in AU/tick.
|
||||
*/
|
||||
public abstract double getTravelSpeed();
|
||||
|
||||
protected StationEngineBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
|
||||
super(type, pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
if (level.isClientSide()) return;
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
data.addEngine(pos);
|
||||
}
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved() {
|
||||
if (data != null)
|
||||
data.removeEngine(worldPosition);
|
||||
TickableBlockEntity.super.onRemoved();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom.renderer;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.xevianlight.aphelion.block.entity.custom.RocketAssemblerBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,9 +23,42 @@ public class RocketAssemblerBlockEntityRenderer implements BlockEntityRenderer<R
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB getRenderBoundingBox(RocketAssemblerBlockEntity blockEntity) {
|
||||
// If we don't know bounds yet, fall back to default BE culling.
|
||||
RocketAssemblerBlockEntity.PadInfo pad = blockEntity.getPadBounds();
|
||||
if (pad == null) {
|
||||
return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity);
|
||||
}
|
||||
|
||||
BlockPos min = pad.min();
|
||||
BlockPos max = pad.max();
|
||||
|
||||
// Expand slightly to avoid edge precision culling
|
||||
return new AABB(
|
||||
min.getX(), min.getY(), min.getZ(),
|
||||
max.getX() + 1, max.getY() + 1, max.getZ() + 1
|
||||
).inflate(0.5);
|
||||
}
|
||||
|
||||
private static final RenderType CENTER_FACE = RenderType.create(
|
||||
"aphelion_center_face",
|
||||
DefaultVertexFormat.POSITION_COLOR,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RenderStateShard.POSITION_COLOR_SHADER)
|
||||
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(RenderStateShard.NO_CULL)
|
||||
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
@Override
|
||||
public void render(@NotNull RocketAssemblerBlockEntity be, float v, @NotNull PoseStack poseStack, @NotNull MultiBufferSource multiBufferSource, int i, int i1) {
|
||||
if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return;
|
||||
// if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return;
|
||||
|
||||
if (be.getPadBounds() == null) return;
|
||||
BlockPos min = be.getPadBounds().min();
|
||||
@@ -35,10 +72,20 @@ public class RocketAssemblerBlockEntityRenderer implements BlockEntityRenderer<R
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(-be.getBlockPos().getX(), -be.getBlockPos().getY(), -be.getBlockPos().getZ());
|
||||
|
||||
VertexConsumer vc = multiBufferSource.getBuffer(RenderType.lines());
|
||||
VertexConsumer lineVc = multiBufferSource.getBuffer(RenderType.lines());
|
||||
LevelRenderer.renderLineBox(poseStack, lineVc, box, 0f, 1f, 0f, 1f);
|
||||
|
||||
LevelRenderer.renderLineBox(poseStack, vc, box, 0.0f, 1.0f, 0.0f, 1.0f);
|
||||
VertexConsumer faceVc = multiBufferSource.getBuffer(CENTER_FACE);
|
||||
|
||||
BlockPos center = be.getPadBounds().getCenter();
|
||||
float y = center.getY() + 0.01f; // avoid z-fighting
|
||||
|
||||
LevelRenderer.renderFace(
|
||||
poseStack, faceVc, Direction.UP,
|
||||
center.getX(), y, center.getZ(),
|
||||
center.getX() + 1, y, center.getZ() + 1,
|
||||
1f, 0f, 0f, 0.5f
|
||||
);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,9 @@
|
||||
}
|
||||
|
||||
public abstract void onEnergyChanged();
|
||||
|
||||
public void setCapacity(int capacity) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@ package net.xevianlight.aphelion.client;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
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;
|
||||
@@ -15,7 +14,11 @@ 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.util.SpacePartitionHelper;
|
||||
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 {
|
||||
@@ -44,16 +47,31 @@ public class AphelionDebugOverlay {
|
||||
+ ", thickFog=" + r.hasThickFog()
|
||||
+ ", fog=" + r.hasFog());
|
||||
|
||||
int x = SpacePartitionHelper.get(Math.floor(mc.player.position().x));
|
||||
int z = SpacePartitionHelper.get(Math.floor(mc.player.position().z));
|
||||
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: " + orbitId);
|
||||
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: " + 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 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; }
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.PartitionClientState;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import net.xevianlight.aphelion.util.SpacePartition;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
@@ -61,8 +61,8 @@ public class DimensionSkyEffects extends DimensionSpecialEffects {
|
||||
|
||||
public static ResourceLocation orbitForPos(Vec3 pos) {
|
||||
|
||||
int x = SpacePartitionHelper.get(pos.x);
|
||||
int z = SpacePartitionHelper.get(pos.z);
|
||||
int x = SpacePartition.get(pos.x);
|
||||
int z = SpacePartition.get(pos.z);
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
|
||||
@@ -8,7 +8,7 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.PartitionClientState;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import net.xevianlight.aphelion.util.SpacePartition;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
@@ -67,20 +67,18 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
|
||||
|
||||
public static ResourceLocation orbitForPos(Vec3 pos) {
|
||||
|
||||
int x = SpacePartitionHelper.get(pos.x);
|
||||
int z = SpacePartitionHelper.get(pos.z);
|
||||
int x = SpacePartition.get(pos.x);
|
||||
int z = SpacePartition.get(pos.z);
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
|
||||
// int px = PartitionClientState.pxOr(0);
|
||||
// int py = PartitionClientState.pyOr(0);
|
||||
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
if (data != null) return data;
|
||||
return ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.xevianlight.aphelion.commands;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.LongArgumentType;
|
||||
@@ -22,14 +23,15 @@ import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.util.RocketStructure;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
import net.xevianlight.aphelion.util.SpacePartition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AphelionCommand {
|
||||
|
||||
@@ -42,8 +44,8 @@ public class AphelionCommand {
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.then(Commands.argument("orbit", ResourceLocationArgument.id())
|
||||
.executes(context -> {
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
int x = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
ResourceLocation orbit = ResourceLocationArgument.getId(context, "orbit");
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
@@ -80,8 +82,8 @@ public class AphelionCommand {
|
||||
.then(Commands.literal("get")
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.executes(context -> {
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
int x = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
ResourceLocation orbit = SpacePartitionSavedData.get(level).getOrbitForPartition(x, z);
|
||||
@@ -105,8 +107,8 @@ public class AphelionCommand {
|
||||
.then(Commands.literal("clear")
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.executes(context -> {
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
int x = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
|
||||
@@ -143,8 +145,8 @@ public class AphelionCommand {
|
||||
.executes(context -> {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context,"pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context,"pos").z());
|
||||
int x = SpacePartition.get(ColumnPosArgument.getColumnPos(context,"pos").x());
|
||||
int z = SpacePartition.get(ColumnPosArgument.getColumnPos(context,"pos").z());
|
||||
|
||||
long key = SpacePartitionSavedData.pack(x,z);
|
||||
|
||||
@@ -190,7 +192,7 @@ public class AphelionCommand {
|
||||
int x = ColumnPosArgument.getColumnPos(context, "pos").x();
|
||||
int z = ColumnPosArgument.getColumnPos(context, "pos").z();
|
||||
|
||||
String stationCoord = SpacePartitionHelper.get(x) + " " + SpacePartitionHelper.get(z);
|
||||
String stationCoord = SpacePartition.get(x) + " " + SpacePartition.get(z);
|
||||
|
||||
Component clickableOutput = getClickablePos(stationCoord, ChatFormatting.GREEN);
|
||||
|
||||
@@ -212,8 +214,8 @@ public class AphelionCommand {
|
||||
double x = (double) IntegerArgumentType.getInteger(context, "x");
|
||||
double z = (double) IntegerArgumentType.getInteger(context, "z");
|
||||
|
||||
int destX = (int) Math.floor(x * SpacePartitionHelper.SIZE) + (SpacePartitionHelper.SIZE / 2);
|
||||
int destZ = (int) Math.floor(z * SpacePartitionHelper.SIZE) + (SpacePartitionHelper.SIZE / 2);
|
||||
int destX = (int) Math.floor(x * SpacePartition.SIZE) + (SpacePartition.SIZE / 2);
|
||||
int destZ = (int) Math.floor(z * SpacePartition.SIZE) + (SpacePartition.SIZE / 2);
|
||||
|
||||
String stationCoord = x + ", " + z;
|
||||
|
||||
@@ -246,16 +248,21 @@ public class AphelionCommand {
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("destination")
|
||||
.then(Commands.literal("set").then(
|
||||
Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.then(Commands.literal("set")
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.then(Commands.argument("id", ResourceLocationArgument.id())
|
||||
.executes(context -> {
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
int px = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int pz = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
ResourceLocation orbit = ResourceLocationArgument.getId(context, "id");
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
SpacePartitionSavedData.get(level).getData(x,z).setDestination(orbit);
|
||||
PartitionData data = SpacePartitionSavedData.get(level).getData(px, pz);
|
||||
if (data == null) {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.station.invalid"));
|
||||
return 1;
|
||||
}
|
||||
data.setDestination(orbit);
|
||||
|
||||
return 1;
|
||||
})
|
||||
@@ -263,6 +270,67 @@ public class AphelionCommand {
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("owner")
|
||||
.then(Commands.literal("get")
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.executes(context -> {
|
||||
int px = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int pz = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PartitionData data = SpacePartitionSavedData.get(level).getData(px, pz);
|
||||
var cache = level.getServer().getProfileCache();
|
||||
if (data == null) {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.station.invalid"));
|
||||
return 1;
|
||||
}
|
||||
if (cache == null) {
|
||||
return 0;
|
||||
}
|
||||
UUID uuid = data.getOwner();
|
||||
if (uuid == null) {
|
||||
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.station.owner.unset"), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
String name = cache.get(uuid).map(GameProfile::getName).orElse(null);
|
||||
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.station.owner.get", px, pz, name), true);
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("set")
|
||||
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.then(Commands.argument("player", GameProfileArgument.gameProfile())
|
||||
.executes(context -> {
|
||||
int px = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int pz = SpacePartition.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PartitionData data = SpacePartitionSavedData.get(level).getData(px, pz);
|
||||
if (data == null) {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.station.invalid"));
|
||||
return 1;
|
||||
}
|
||||
Collection<GameProfile> profiles =
|
||||
GameProfileArgument.getGameProfiles(context, "player");
|
||||
|
||||
if (profiles.size() != 1) {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.player.invalid"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
GameProfile profile = profiles.iterator().next();
|
||||
UUID uuid = profile.getId();
|
||||
|
||||
data.setOwner(uuid);
|
||||
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.station.owner.set.success", px, pz, profile.getName()), true);
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("planet")
|
||||
.then(Commands.literal("tp")
|
||||
@@ -474,6 +542,22 @@ public class AphelionCommand {
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("disassemble")
|
||||
.executes(context -> {
|
||||
Entity entity = EntityArgument.getEntity(context, "entity");
|
||||
|
||||
if (entity instanceof RocketEntity rocket) {
|
||||
if (rocket.disassemble()) {
|
||||
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.disassemble.success"), true);
|
||||
} else {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.disassemble.failure"));
|
||||
}
|
||||
} else {
|
||||
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid"));
|
||||
}
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.VAFMultiblockDummyBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.*;
|
||||
|
||||
@@ -49,6 +50,16 @@ public class ModBlockEntities {
|
||||
|
||||
public static final Supplier<BlockEntityType<RocketAssemblerBlockEntity>> ROCKET_ASSEMBLER_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of(
|
||||
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null)
|
||||
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<StationRocketEngineBlockEntity>> STATION_ROCKET_ENGINE_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("station_rocket_engine_block_entity", () -> BlockEntityType.Builder.of(
|
||||
StationRocketEngineBlockEntity::new, ModBlocks.STATION_ROCKET_ENGINE.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<StationFlightComputerBlockEntity>> STATION_FLIGHT_COMPUTER_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("station_flight_computer_block_entity", () -> BlockEntityType.Builder.of(
|
||||
StationFlightComputerBlockEntity::new, ModBlocks.STATION_FLIGHT_COMPUTER_BLOCK.get()).build(null)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@ public class ModBlocks {
|
||||
public static final DeferredBlock<Block> VACUUM_ARC_FURNACE_CONTROLLER = BLOCKS.register("vacuum_arc_furnace_controller", () -> new VacuumArcFurnaceController(VacuumArcFurnaceController.getProperties()));
|
||||
public static final DeferredBlock<Block> VAF_MULTIBLOCK_DUMMY_BLOCK = BLOCKS.register("vaf_dummy_block", () -> new VAFMultiblockDummyBlock(VAFMultiblockDummyBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> OXYGEN_TEST_BLOCK = BLOCKS.register("oxygen_test_block", () -> new OxygenTestBlock(OxygenTestBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_ASSEMBLER_BLOCK = BLOCKS.register("rocket_assembler_block", () -> new RocketAssemblerBlock(RocketAssemblerBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_ASSEMBLER = BLOCKS.register("rocket_assembler", () -> new RocketAssembler(RocketAssembler.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_SEAT = BLOCKS.register("rocket_seat", () -> new RocketSeat(RocketSeat.getProperties()));
|
||||
public static final DeferredBlock<Block> STATION_ROCKET_ENGINE = BLOCKS.register("station_rocket_engine", () -> new StationRocketEngineBlock(StationRocketEngineBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> BASIC_ROCKET_FUEL_TANK = BLOCKS.register("basic_rocket_fuel_tank", () -> new BasicRocketFuelTank(BasicRocketFuelTank.getProperties()));
|
||||
public static final DeferredBlock<Block> BASIC_ROCKET_CONTAINER = BLOCKS.register("basic_rocket_container", () -> new BasicRocketContainer(BasicRocketContainer.getProperties()));
|
||||
public static final DeferredBlock<Block> STATION_FLIGHT_COMPUTER_BLOCK = BLOCKS.register("station_flight_computer", () -> new StationFlightComputerBlock(StationFlightComputerBlock.getProperties()));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class ModCreativeTabs {
|
||||
output.accept(ModItems.INGOT_NEODYMIUM);
|
||||
output.accept(ModItems.INGOT_IRIDIUM);
|
||||
output.accept(ModFluids.OIL_BUCKET);
|
||||
output.accept(ModFluids.ROCKET_FUEL_BUCKET);
|
||||
output.accept(ModItems.MUSIC_DISC_BIT_SHIFT);
|
||||
}).build());
|
||||
|
||||
@@ -42,6 +43,6 @@ public class ModCreativeTabs {
|
||||
output.accept(ModItems.ARC_FURNACE_CASING_BLOCK);
|
||||
output.accept(ModItems.VACUUM_ARC_FURNACE_CONTROLLER);
|
||||
output.accept(ModItems.LAUNCH_PAD);
|
||||
output.accept(ModItems.ROCKET_ASSEMBLER_BLOCK);
|
||||
output.accept(ModItems.ROCKET_ASSEMBLER);
|
||||
}).build());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.xevianlight.aphelion.core.init;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
public final class ModDimensions {
|
||||
public static final ResourceKey<Level> SPACE = ResourceKey.create(Registries.DIMENSION, Aphelion.id("space"));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.xevianlight.aphelion.core.init;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
|
||||
public class ModFluidTags {
|
||||
public static final TagKey<Fluid> ROCKET_FUEL = create("rocket_fuel");
|
||||
|
||||
private static TagKey<Fluid> create(String name) {
|
||||
return TagKey.create(Registries.FLUID, ResourceLocation.withDefaultNamespace(name));
|
||||
}
|
||||
|
||||
public static TagKey<Fluid> create(ResourceLocation name) {
|
||||
return TagKey.create(Registries.FLUID, name);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import net.neoforged.neoforge.registries.DeferredItem;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.*;
|
||||
import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock;
|
||||
import net.xevianlight.aphelion.item.*;
|
||||
|
||||
public class ModItems {
|
||||
@@ -37,6 +36,10 @@ public static final DeferredItem<Item> MUSIC_DISC_BIT_SHIFT = ITEMS.register("mu
|
||||
public static final DeferredItem<BlockItem> VACUUM_ARC_FURNACE_CONTROLLER = ITEMS.register("vacuum_arc_furnace_controller", () -> new BlockItem(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get(), VacuumArcFurnaceController.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> OXYGEN_TEST_BLOCK = ITEMS.register("oxygen_test_block", () -> new BlockItem(ModBlocks.OXYGEN_TEST_BLOCK.get(), new Item.Properties()));
|
||||
public static final DeferredItem<BlockItem> LAUNCH_PAD = ITEMS.register("launch_pad", () -> new BlockItem(ModBlocks.LAUNCH_PAD.get(), LaunchPad.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> ROCKET_ASSEMBLER_BLOCK = ITEMS.register("rocket_assembler_block", () -> new BlockItem(ModBlocks.ROCKET_ASSEMBLER_BLOCK.get(), RocketAssemblerBlock.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> ROCKET_ASSEMBLER = ITEMS.register("rocket_assembler", () -> new BlockItem(ModBlocks.ROCKET_ASSEMBLER.get(), RocketAssembler.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> ROCKET_SEAT = ITEMS.register("rocket_seat", () -> new BlockItem(ModBlocks.ROCKET_SEAT.get(), RocketSeat.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> STATION_ROCKET_ENGINE = ITEMS.register("station_rocket_engine", () -> new BlockItem(ModBlocks.STATION_ROCKET_ENGINE.get(), StationRocketEngineBlock.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> BASIC_ROCKET_FUEL_TANK = ITEMS.register("basic_rocket_fuel_tank", () -> new BlockItem(ModBlocks.BASIC_ROCKET_FUEL_TANK.get(), BasicRocketFuelTank.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> BASIC_ROCKET_CONTAINER = ITEMS.register("basic_rocket_container", () -> new BlockItem(ModBlocks.BASIC_ROCKET_CONTAINER.get(), BasicRocketContainer.getItemProperties()));
|
||||
// public static final DeferredItem<BlockItem> VAF_MULTIBLOCK_DUMMY_BLOCK = ITEMS.register("vaf_multiblock_dummy_block", () -> new BlockItem(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), VAFMultiblockDummyBlock.getItemProperties()));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.xevianlight.aphelion.core.saveddata;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
@@ -10,6 +11,8 @@ 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;
|
||||
|
||||
@@ -54,10 +57,30 @@ public class SpacePartitionSavedData extends SavedData {
|
||||
|
||||
// Distances (optional; default 0.0)
|
||||
if (e.contains("DistanceTraveled", CompoundTag.TAG_DOUBLE)) {
|
||||
pd.setDistanceTraveled(e.getDouble("DistanceTraveled"));
|
||||
pd.setDistanceTraveledAU(e.getDouble("DistanceTraveled"));
|
||||
}
|
||||
if (e.contains("DistanceToDest", CompoundTag.TAG_DOUBLE)) {
|
||||
pd.setDistanceToDest(e.getDouble("DistanceToDest"));
|
||||
pd.setTripDistanceAU(e.getDouble("DistanceToDest"));
|
||||
}
|
||||
|
||||
if (e.hasUUID("Owner")) {
|
||||
pd.setOwner(e.getUUID("Owner"));
|
||||
}
|
||||
|
||||
if (e.contains("Generated", CompoundTag.TAG_BYTE)) {
|
||||
pd.setGenerated(e.getBoolean("Generated"));
|
||||
}
|
||||
|
||||
if (e.contains("LandingPads", CompoundTag.TAG_LONG_ARRAY)) {
|
||||
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);
|
||||
@@ -90,8 +113,20 @@ public class SpacePartitionSavedData extends SavedData {
|
||||
// Traveling + distances
|
||||
e.putBoolean("Traveling", pd.isTraveling());
|
||||
|
||||
e.putDouble("DistanceTraveled", pd.getDistanceTraveled());
|
||||
e.putDouble("DistanceToDest", pd.getDistanceToDest());
|
||||
e.putDouble("DistanceTraveled", pd.getDistanceTraveledAU());
|
||||
e.putDouble("DistanceToDest", pd.recalculateTripDistAU());
|
||||
|
||||
if (pd.getOwner() != null) {
|
||||
e.putUUID("Owner", pd.getOwner());
|
||||
}
|
||||
|
||||
e.putBoolean("Generated", pd.isGenerated());
|
||||
|
||||
e.putLongArray("LandingPads", pd.getLandingPadContollersAsArray());
|
||||
|
||||
e.putLong("PosData", pd.getPosDataOrDefault().pack());
|
||||
|
||||
e.putDouble("OrbitDistance", pd.getOrbitDistance());
|
||||
|
||||
entries.add(e);
|
||||
});
|
||||
@@ -137,15 +172,87 @@ public class SpacePartitionSavedData extends SavedData {
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable PartitionData getData(int px, int pz) {
|
||||
/**
|
||||
* Returns the {@link PartitionData} stored at the given partition indices.
|
||||
*
|
||||
* <p>The returned {@code PartitionData} is mutable. Any modifications to the returned
|
||||
* object will be persisted to the server.</p>
|
||||
*
|
||||
* <p>If no {@code PartitionData} exists at the specified indices, a new instance is
|
||||
* created using {@code aphelion:orbit/default}, stored in the server cache, and returned.</p>
|
||||
*
|
||||
* @param px the partition X index
|
||||
* @param pz the partition Z index
|
||||
* @return the {@code PartitionData} associated with the specified partition indices
|
||||
*/
|
||||
public @NotNull PartitionData getData(int px, int pz) {
|
||||
long key = pack(px, pz);
|
||||
PartitionData data = map.get(key);
|
||||
if (data == null) {
|
||||
|
||||
// pick a sensible default orbit, or null if you truly allow it
|
||||
data = new PartitionData(Aphelion.id("orbit/unassigned"));
|
||||
map.put(key, data);
|
||||
setDirty();
|
||||
}
|
||||
data.setDirtyCallback(this::setDirty);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PartitionData} for the partition containing the given world position.
|
||||
*
|
||||
* <p>The returned {@code PartitionData} is mutable. Any modifications to the returned
|
||||
* object will be persisted to the server.</p>
|
||||
*
|
||||
* <p>If no {@code PartitionData} exists for the partition containing the specified
|
||||
* position, a new instance is created using {@code aphelion:orbit/default},
|
||||
* stored in the server cache, and returned.</p>
|
||||
*
|
||||
* @param x the world X coordinate
|
||||
* @param z the world Z coordinate
|
||||
* @return the {@code PartitionData} associated with the partition containing the position
|
||||
*/
|
||||
public @NotNull PartitionData getDataForPos(int x, int z) {
|
||||
int px = SpacePartition.get(x);
|
||||
int pz = SpacePartition.get(z);
|
||||
long key = pack(px, pz);
|
||||
PartitionData data = map.get(key);
|
||||
if (data == null) {
|
||||
|
||||
data = new PartitionData(Aphelion.id("orbit/default"));
|
||||
map.put(key, data);
|
||||
setDirty();
|
||||
}
|
||||
data.setDirtyCallback(this::setDirty);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PartitionData} for the partition containing the given block position.
|
||||
*
|
||||
* <p>The returned {@code PartitionData} is mutable. Any modifications to the returned
|
||||
* object will be persisted to the server.</p>
|
||||
*
|
||||
* <p>If no {@code PartitionData} exists for the partition containing the specified
|
||||
* position, a new instance is created using {@code aphelion:orbit/default},
|
||||
* stored in the server cache, and returned.</p>
|
||||
*
|
||||
* @param pos the world block position
|
||||
* @return the {@code PartitionData} associated with the partition containing the position
|
||||
*/
|
||||
public @NotNull PartitionData getDataForBlockPos(BlockPos pos) {
|
||||
int px = SpacePartition.get(pos.getX());
|
||||
int pz = SpacePartition.get(pos.getZ());
|
||||
long key = pack(px, pz);
|
||||
PartitionData data = map.get(key);
|
||||
if (data == null) {
|
||||
|
||||
data = new PartitionData(Aphelion.id("orbit/default"));
|
||||
map.put(key, data);
|
||||
setDirty();
|
||||
}
|
||||
data.setDirtyCallback(this::setDirty);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -154,7 +261,7 @@ public class SpacePartitionSavedData extends SavedData {
|
||||
|
||||
boolean changed = false;
|
||||
for (var entry : map.long2ObjectEntrySet()) {
|
||||
if(!orbit.equals(entry.getValue())) {
|
||||
if(!orbit.equals(entry.getValue().getOrbit())) {
|
||||
entry.getValue().setOrbit(orbit);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,39 +1,80 @@
|
||||
package net.xevianlight.aphelion.core.saveddata.types;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
public class PartitionData {
|
||||
public static final int MAX_PADS = 64;
|
||||
private static final StreamCodec<ByteBuf, List<BlockPos>> BLOCKPOS_LIST_CODEC = BlockPos.STREAM_CODEC.apply(ByteBufCodecs.list(MAX_PADS));
|
||||
|
||||
@Nullable private ResourceLocation orbit;
|
||||
@Nullable private ResourceLocation destination;
|
||||
@Nullable private ResourceLocation system;
|
||||
private boolean traveling;
|
||||
private double distanceTraveled;
|
||||
private double distanceToDest;
|
||||
/// How far we've already gone
|
||||
private double distanceTraveledAU;
|
||||
/// 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;
|
||||
/// 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.distanceTraveled = 0;
|
||||
this.distanceToDest = 0;
|
||||
this.distanceTraveledAU = 0;
|
||||
this.tripDistanceAU = 0;
|
||||
this.generated = false;
|
||||
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.distanceTraveled = other.distanceTraveled;
|
||||
this.distanceToDest = other.distanceToDest;
|
||||
this.distanceTraveledAU = other.distanceTraveledAU;
|
||||
this.tripDistanceAU = other.tripDistanceAU; // copy directly, no recalculation
|
||||
this.generated = other.generated;
|
||||
this.owner = other.owner;
|
||||
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 =
|
||||
StreamCodec.composite(
|
||||
BigCodec.composite(
|
||||
// orbit is nullable -> optional codec
|
||||
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
|
||||
d -> Optional.ofNullable(d.getOrbit()),
|
||||
@@ -41,40 +82,93 @@ 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,
|
||||
|
||||
// doubles -> DOUBLE codec
|
||||
ByteBufCodecs.DOUBLE,
|
||||
PartitionData::getDistanceTraveled,
|
||||
PartitionData::getDistanceTraveledAU,
|
||||
|
||||
ByteBufCodecs.DOUBLE,
|
||||
PartitionData::getDistanceToDest,
|
||||
PartitionData::recalculateTripDistAU,
|
||||
|
||||
(orbitOpt, destOpt, traveling, distTraveled, distToDest) -> {
|
||||
ByteBufCodecs.optional(UUIDUtil.STREAM_CODEC),
|
||||
d -> Optional.ofNullable(d.getOwner()),
|
||||
|
||||
ByteBufCodecs.BOOL,
|
||||
PartitionData::isGenerated,
|
||||
|
||||
BLOCKPOS_LIST_CODEC,
|
||||
PartitionData::getLandingPadControllers,
|
||||
|
||||
BLOCKPOS_LIST_CODEC,
|
||||
PartitionData::getEngines,
|
||||
|
||||
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.distanceTraveled = distTraveled;
|
||||
data.distanceToDest = distToDest;
|
||||
data.distanceTraveledAU = distTraveled;
|
||||
data.tripDistanceAU = distToDest;
|
||||
data.owner = ownerOpt.orElse(null);
|
||||
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(ResourceLocation 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() {
|
||||
@@ -82,27 +176,224 @@ public class PartitionData {
|
||||
}
|
||||
|
||||
public void setTraveling(boolean traveling) {
|
||||
if (this.traveling == traveling) return;
|
||||
this.traveling = traveling;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public double getDistanceTraveled() {
|
||||
return distanceTraveled;
|
||||
public double getDistanceTraveledAU() {
|
||||
return distanceTraveledAU;
|
||||
}
|
||||
|
||||
public void setDistanceTraveled(double distanceTraveled) {
|
||||
this.distanceTraveled = distanceTraveled;
|
||||
public void setDistanceTraveledAU(double distanceTraveledAU) {
|
||||
this.distanceTraveledAU = distanceTraveledAU;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public double getDistanceToDest() {
|
||||
return distanceToDest;
|
||||
public double recalculateTripDistAU() {
|
||||
var currentPlanet = PlanetCache.getByOrbitOrNull(orbit);
|
||||
if (currentPlanet == null) {
|
||||
markDirty();
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setDistanceToDest(double distanceToDest) {
|
||||
this.distanceToDest = distanceToDest;
|
||||
var destPlanet = PlanetCache.getOrDefault(destination);
|
||||
|
||||
var dist = destPlanet.orbitDistance() - currentPlanet.orbitDistance();
|
||||
this.tripDistanceAU = dist;
|
||||
markDirty();
|
||||
return dist;
|
||||
}
|
||||
|
||||
public void travel(double distance) {
|
||||
distanceTraveled = Math.min( distanceTraveled + distance, distanceToDest);
|
||||
public double getTripDistanceAU() {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances travel progress by the specified distance in AU.
|
||||
*
|
||||
* <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 the station has arrived at its destination,
|
||||
* {@code false} if travel is still in progress.
|
||||
*/
|
||||
public boolean travel(double distance) {
|
||||
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;
|
||||
|
||||
markDirty();
|
||||
return true;
|
||||
} else {
|
||||
distanceTraveledAU += distance;
|
||||
this.orbitDistance += step;
|
||||
|
||||
markDirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isGenerated() {
|
||||
return generated;
|
||||
}
|
||||
|
||||
public void setGenerated(boolean generated) {
|
||||
this.generated = generated;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public @Nullable UUID getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(@Nullable UUID owner) {
|
||||
this.owner = owner;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the world positions of all landing pad controllers tracked
|
||||
* by this partition.
|
||||
*
|
||||
* <p>This method returns only the stored {@link BlockPos} locations of known
|
||||
* landing pad controllers, not the controller instances or block entities
|
||||
* themselves. To interact with a controller, retrieve its block entity from
|
||||
* the world using the returned positions.</p>
|
||||
*
|
||||
* <p>The returned list is a defensive copy and may be modified without affecting
|
||||
* the underlying partition data. To persist changes, use
|
||||
* {@code setLandingPadControllers(...)}.</p>
|
||||
*
|
||||
* @return a mutable copy of the landing pad controller positions known to this partition
|
||||
*/
|
||||
public List<BlockPos> getLandingPadControllers() {
|
||||
return new ArrayList<>(landingPadControllers);
|
||||
}
|
||||
|
||||
public void setLandingPadControllers(List<BlockPos> landingPadControllers) {
|
||||
this.landingPadControllers = landingPadControllers;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a landing pad controller at the specified world position.
|
||||
*
|
||||
* <p>If a controller does not already exist at the given position, it is added
|
||||
* to the internal collection and the method returns {@code true}. If a controller
|
||||
* is already present at that position, no changes are made.</p>
|
||||
*
|
||||
* @param pos the world position of the landing pad controller to add
|
||||
* @return {@code true} if the controller was added, {@code false} if it already existed
|
||||
*/
|
||||
public boolean addLandingPadController(BlockPos pos) {
|
||||
if (!landingPadControllers.contains(pos)) {
|
||||
landingPadControllers.add(pos);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the landing pad controller at the specified world position.
|
||||
*
|
||||
* <p>If a controller exists at the given position, it is removed from the
|
||||
* internal collection and the method returns {@code true}. If no controller
|
||||
* is present at that position, no changes are made.</p>
|
||||
*
|
||||
* @param pos the world position of the landing pad controller to remove
|
||||
* @return {@code true} if a controller was removed, {@code false} otherwise
|
||||
*/
|
||||
public boolean removeLandingPadController(BlockPos pos) {
|
||||
if (landingPadControllers.remove(pos)) {
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a defensive copy of the world positions of all engines tracked by this partition.
|
||||
*
|
||||
* <p>This method returns only the stored {@link BlockPos} locations of known engines,
|
||||
* not the engine instances or their corresponding block entities. To interact with an
|
||||
* engine, retrieve the block entity from the world using the returned positions.</p>
|
||||
*
|
||||
* <p>It is not guaranteed that an engine exists at every returned position. If changes
|
||||
* fail to synchronize with this partition, the stored data may become inaccurate.
|
||||
* Always verify that the block entity at a given position is an engine before use.</p>
|
||||
*
|
||||
* <p>The returned list is a defensive copy and may be modified without affecting the
|
||||
* underlying partition data. To persist changes, use {@code setEngines(...)}.</p>
|
||||
*
|
||||
* @return a mutable list containing the tracked engine positions
|
||||
*/
|
||||
public List<BlockPos> getEngines() {
|
||||
return new ArrayList<>(engines);
|
||||
}
|
||||
|
||||
public void setEngines(List<BlockPos> engines) {
|
||||
this.engines = engines;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an engine at the specified world position.
|
||||
*
|
||||
* <p>If an engine does not already exist at the given position, it is added
|
||||
* to the internal collection and the method returns {@code true}. If an engine
|
||||
* is already present at that position, no changes are made.</p>
|
||||
*
|
||||
* @param pos the world position of the engine to add
|
||||
* @return {@code true} if the engine was added, {@code false} if it already existed
|
||||
*/
|
||||
public boolean addEngine(BlockPos pos) {
|
||||
if (!engines.contains(pos)) {
|
||||
engines.add(pos);
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean removeEngine(BlockPos pos) {
|
||||
if (engines.remove(pos)) {
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,7 +406,59 @@ public class PartitionData {
|
||||
return Objects.equals(this.orbit, that.orbit)
|
||||
&& Objects.equals(this.destination, that.destination)
|
||||
&& this.traveling == that.traveling
|
||||
&& Double.compare(this.distanceTraveled, that.distanceTraveled) == 0
|
||||
&& Double.compare(this.distanceToDest, that.distanceToDest) == 0;
|
||||
&& Double.compare(this.distanceTraveledAU, that.distanceTraveledAU) == 0
|
||||
&& Double.compare(this.orbitDistance, that.orbitDistance) == 0
|
||||
&& this.generated == that.generated
|
||||
&& 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() {
|
||||
long[] out = new long[landingPadControllers.size()];
|
||||
int i = 0;
|
||||
for (BlockPos pos : landingPadControllers) {
|
||||
out[i] = pos.asLong();
|
||||
i++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void setLandingPadContollersFromArray(long[] in) {
|
||||
List<BlockPos> newList = new java.util.ArrayList<>(List.of());
|
||||
int i = 0;
|
||||
for (Long packedPos : in) {
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -26,7 +26,8 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider {
|
||||
dropSelf(ModBlocks.OXYGEN_TEST_BLOCK.get());
|
||||
dropOther(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), ItemStack.EMPTY.getItem());
|
||||
dropSelf(ModBlocks.LAUNCH_PAD.get());
|
||||
dropSelf(ModBlocks.ROCKET_ASSEMBLER_BLOCK.get());
|
||||
dropSelf(ModBlocks.ROCKET_ASSEMBLER.get());
|
||||
dropSelf(ModBlocks.ROCKET_SEAT.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ModBlockStateProvider extends BlockStateProvider {
|
||||
// modLoc("block/test_block"),
|
||||
// mcLoc("block/furnace_front"),
|
||||
// modLoc("block/test_block")));
|
||||
blockItem(ModBlocks.ROCKET_ASSEMBLER_BLOCK);
|
||||
blockItem(ModBlocks.ROCKET_ASSEMBLER);
|
||||
|
||||
blockWithItem(ModBlocks.BLOCK_STEEL);
|
||||
blockWithItem(ModBlocks.DIMENSION_CHANGER);
|
||||
|
||||
@@ -44,5 +44,8 @@ public class ModBlockTagProvider extends BlockTagsProvider {
|
||||
tag(ModTags.Blocks.LAUNCH_PAD)
|
||||
.add(ModBlocks.LAUNCH_PAD.get());
|
||||
|
||||
tag(ModTags.Blocks.ROCKET_SEAT)
|
||||
.add(ModBlocks.ROCKET_SEAT.get());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.data.tags.FluidTagsProvider;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.neoforged.neoforge.common.data.ExistingFileHelper;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.init.ModFluidTags;
|
||||
import net.xevianlight.aphelion.fluid.ModFluids;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,5 +23,7 @@ public class ModFluidTagsProvider extends FluidTagsProvider {
|
||||
// tag(FluidTags.LAVA)
|
||||
// .add(ModFluids.SOURCE_OIL_FLUID.get())
|
||||
// .add(ModFluids.FLOWING_OIL_FLUID.get());
|
||||
tag(ModFluidTags.ROCKET_FUEL)
|
||||
.add(ModFluids.ROCKET_FUEL.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.xevianlight.aphelion.entites.vehicles;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
@@ -13,13 +14,16 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.*;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.vehicle.VehicleEntity;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
@@ -27,14 +31,20 @@ import net.minecraft.world.level.portal.DimensionTransition;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
|
||||
import net.xevianlight.aphelion.core.init.ModEntities;
|
||||
import net.xevianlight.aphelion.fluid.ModFluids;
|
||||
import net.xevianlight.aphelion.util.RocketStructure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn {
|
||||
|
||||
@@ -51,7 +61,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
private double landingPosX;
|
||||
private double landingPosZ;
|
||||
|
||||
private static final double TELEPORT_Y = 1000.0;
|
||||
private static final double TELEPORT_Y = 600.0;
|
||||
private static final double ASCEND_ACCEL = 0.0125;
|
||||
private static final double DESCEND_SPEED = 1;
|
||||
|
||||
@@ -63,6 +73,42 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
private static final EntityDataAccessor<CompoundTag> STRUCTURE_TAG =
|
||||
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.COMPOUND_TAG);
|
||||
|
||||
private final FluidTank FUEL_TANK = newFuelTank(0);
|
||||
private final FluidTank FLUID_STORAGE = newFluidTank(0);
|
||||
private final ModEnergyStorage ENERGY_STORAGE = newEnergyStorage(0, 0);
|
||||
private final ItemStackHandler INVENTORY = new ItemStackHandler(0);
|
||||
|
||||
public ItemStackHandler getInventory() {
|
||||
return INVENTORY;
|
||||
}
|
||||
|
||||
private static @NotNull FluidTank newFluidTank(int capacity) {
|
||||
return new FluidTank(capacity) {
|
||||
@Override
|
||||
public boolean isFluidValid(@NotNull FluidStack stack) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static @NotNull FluidTank newFuelTank(int capacity) {
|
||||
return new FluidTank(capacity) {
|
||||
@Override
|
||||
public boolean isFluidValid(@NotNull FluidStack stack) {
|
||||
return stack.is(FluidTags.WATER);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static @NotNull ModEnergyStorage newEnergyStorage(int capacity, int transfer) {
|
||||
return new ModEnergyStorage(capacity, transfer) {
|
||||
@Override
|
||||
public void onEnergyChanged() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static RocketEntity spawnRocket(Level level, BlockPos pos, RocketStructure structure) {
|
||||
if (level.isClientSide) return null;
|
||||
|
||||
@@ -76,12 +122,38 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
0.0f
|
||||
);
|
||||
|
||||
// rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
|
||||
// rocket.INVENTORY.setSize(rocket.INVENTORY.getSlots() + 1);
|
||||
// rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
|
||||
|
||||
// Fully initialize structure and containers
|
||||
rocket.setStructure(structure);
|
||||
|
||||
if (rocket.INVENTORY.getSlots() > 0)
|
||||
rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND), false);
|
||||
if (rocket.FUEL_TANK.getCapacity() != 0)
|
||||
rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
|
||||
if (rocket.FLUID_STORAGE.getCapacity() != 0)
|
||||
rocket.FLUID_STORAGE.setFluid(FluidStack.EMPTY);
|
||||
|
||||
|
||||
level.addFreshEntity(rocket);
|
||||
|
||||
return rocket;
|
||||
}
|
||||
|
||||
private void recalculateCapacitiesFromStructure() {
|
||||
int inv = RocketStructure.calculateInventoryCapacity(structure);
|
||||
int fuelCap = RocketStructure.calculateFuelCapacity(structure);
|
||||
int fluidCap = RocketStructure.calculateFluidCapacity(structure);
|
||||
int energyCap = RocketStructure.calculateEnergyCapacity(structure);
|
||||
|
||||
INVENTORY.setSize(inv);
|
||||
FUEL_TANK.setCapacity(fuelCap);
|
||||
FLUID_STORAGE.setCapacity(fluidCap);
|
||||
ENERGY_STORAGE.setCapacity(energyCap);
|
||||
}
|
||||
|
||||
public void launchTo(ResourceKey<Level> dim, @Nullable BlockPos pos) {
|
||||
if (level().isClientSide) return;
|
||||
|
||||
@@ -104,6 +176,9 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
if (!level().isClientSide) {
|
||||
|
||||
if (INVENTORY.getSlots() > 0)
|
||||
INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
|
||||
|
||||
switch (getPhase()) {
|
||||
case IDLE, LANDED -> tickIdle();
|
||||
case PREPARE -> tickPrepare();
|
||||
@@ -119,12 +194,14 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
}
|
||||
case DESCEND -> tickDescend();
|
||||
}
|
||||
// Simple upward movement
|
||||
|
||||
}
|
||||
|
||||
// Catch ANY edge cases which may cause a crash trying to move an entity as its chunk is unloading
|
||||
if (!this.isRemoved() && this.isAlive() && level().hasChunkAt(blockPosition())) {
|
||||
move(MoverType.SELF, getDeltaMovement());
|
||||
}
|
||||
}
|
||||
|
||||
private void tickIdle() {
|
||||
resetDeltaMovement();
|
||||
@@ -224,6 +301,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
if (isOnGround()) {
|
||||
resetDeltaMovement();
|
||||
setPhase(FlightPhase.LANDED);
|
||||
yVel = 0;
|
||||
return;
|
||||
}
|
||||
setDeltaMovement(0, -DESCEND_SPEED, 0);
|
||||
@@ -279,12 +357,13 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
this.refreshDimensions();
|
||||
this.setBoundingBox(this.makeBoundingBox());
|
||||
|
||||
// prevent any internal “snap” from sticking
|
||||
this.moveTo(x, y, z, yRot, xRot);
|
||||
|
||||
if (!level().isClientSide) {
|
||||
this.entityData.set(STRUCTURE_TAG, this.structure.save());
|
||||
}
|
||||
|
||||
recalculateCapacitiesFromStructure();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -325,9 +404,12 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
@Override
|
||||
protected void readAdditionalSaveData(CompoundTag tag) {
|
||||
HolderLookup.Provider registries = level().registryAccess();
|
||||
|
||||
if (tag.contains("RocketStructure")) {
|
||||
CompoundTag rocketTag = tag.getCompound("RocketStructure");
|
||||
structure.load(rocketTag);
|
||||
// recalculateCapacitiesFromStructure();
|
||||
|
||||
// Immediately apply correct bbox on load (server + client)
|
||||
double x = getX(), y = getY(), z = getZ();
|
||||
@@ -361,10 +443,22 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
if (tag.contains("FlightPhase", Tag.TAG_BYTE)) {
|
||||
setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]);
|
||||
}
|
||||
|
||||
if (tag.contains("Inventory", CompoundTag.TAG_COMPOUND)){
|
||||
INVENTORY.deserializeNBT(registries, tag.getCompound("Inventory"));
|
||||
}
|
||||
|
||||
FUEL_TANK.readFromNBT(registries, tag);
|
||||
FLUID_STORAGE.readFromNBT(registries, tag);
|
||||
|
||||
if (tag.contains("Energy", CompoundTag.TAG_COMPOUND)) {
|
||||
ENERGY_STORAGE.deserializeNBT(registries, tag.getCompound("Energy"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAdditionalSaveData(CompoundTag tag) {
|
||||
HolderLookup.Provider registries = level().registryAccess();
|
||||
tag.put("RocketStructure", structure.save());
|
||||
if (targetDim != null)
|
||||
tag.putString("TargetDim", targetDim.location().toString());
|
||||
@@ -375,6 +469,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
tag.putDouble("LandingX", landingPosX);
|
||||
tag.putDouble("LandingZ", landingPosZ);
|
||||
tag.putDouble("yVelocity", yVel);
|
||||
tag.put("Inventory", INVENTORY.serializeNBT(registries));
|
||||
tag = FUEL_TANK.writeToNBT(registries, tag);
|
||||
tag = FLUID_STORAGE.writeToNBT(registries, tag);
|
||||
tag.put("Energy", ENERGY_STORAGE.serializeNBT(registries));
|
||||
}
|
||||
|
||||
public @Nullable BlockPos getTargetPos() {
|
||||
@@ -436,15 +534,24 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
return EntityDimensions.scalable(w, h).withEyeHeight(Math.max(0.1f, h - 0.2f));
|
||||
}
|
||||
|
||||
|
||||
public Vec3 getSeatWorldPos(int seatIndex) {
|
||||
int packed = structure.packedSeatAt(seatIndex);
|
||||
|
||||
int dx = RocketStructure.unpackX(packed);
|
||||
int dy = RocketStructure.unpackY(packed);
|
||||
int dz = RocketStructure.unpackZ(packed);
|
||||
|
||||
return this.position().add(dx, dy, dz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void positionRider(@NotNull Entity passenger, @NotNull MoveFunction moveFunction) {
|
||||
public void positionRider(@NotNull Entity passenger, @NotNull MoveFunction move) {
|
||||
if (!this.hasPassenger(passenger)) return;
|
||||
|
||||
// Choose a stable seat position relative to the rocket.
|
||||
// Example: centered, and 1.5 blocks above your base Y.
|
||||
Vec3 seat = new Vec3(this.getX(), this.getY() + structure.computeExtents().toLocalAABB().getYsize() / 2, this.getZ());
|
||||
Vec3 seat = getSeatWorldPos(0); // primary seat
|
||||
move.accept(passenger, seat.x, seat.y, seat.z);
|
||||
|
||||
moveFunction.accept(passenger, seat.x, seat.y, seat.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -463,7 +570,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
public void applyStructureTag(CompoundTag structureTag) {
|
||||
this.structure.load(structureTag);
|
||||
this.refreshDimensions(); // if your hitbox/eye depends on structure
|
||||
this.refreshDimensions();
|
||||
}
|
||||
|
||||
private AABB computeWorldAABBFromStructure() {
|
||||
@@ -496,4 +603,72 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
return InteractionResult.sidedSuccess(level().isClientSide);
|
||||
}
|
||||
|
||||
public boolean disassemble() {
|
||||
if (level().isClientSide) return false;
|
||||
if (!(level() instanceof ServerLevel server)) return false;
|
||||
Aphelion.LOGGER.info("Disassemble called for rocket: " + getId());
|
||||
|
||||
// In rare instances we can disassemble a rocket AFTER it has been killed.
|
||||
// This usually only happens if another class instance still has a reference to this object stored and calls rocketEntity.disassemble().
|
||||
// This SHOULD fix that
|
||||
if (!this.isAlive()) return false; // dead
|
||||
if (this.isRemoved()) return false; // discarded / removed
|
||||
|
||||
ejectPassengers();
|
||||
|
||||
BlockPos origin = BlockPos.containing(getX(), getY(), getZ());
|
||||
|
||||
// Place blocks
|
||||
for (int i = 0; i < structure.size(); i++) {
|
||||
int packed = structure.packedPosAt(i);
|
||||
|
||||
int dx = RocketStructure.unpackX(packed);
|
||||
int dy = RocketStructure.unpackY(packed);
|
||||
int dz = RocketStructure.unpackZ(packed);
|
||||
|
||||
BlockPos wp = origin.offset(dx, dy, dz);
|
||||
var stateToPlace = structure.stateAt(i);
|
||||
|
||||
// Don't place air (shouldn't exist in structure anyway, but safe)
|
||||
if (stateToPlace.isAir()) continue;
|
||||
|
||||
// Safety: don't overwrite existing blocks
|
||||
if (!server.getBlockState(wp).isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
server.setBlock(wp, stateToPlace, 3);
|
||||
}
|
||||
|
||||
// Remove the rocket entity
|
||||
discard(); // preferred over kill() for "just remove this entity"
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void dropItemStackHandler(ServerLevel level, Vec3 pos, ItemStackHandler handler) {
|
||||
for (int i = 0; i < handler.getSlots(); i++) {
|
||||
ItemStack stack = handler.getStackInSlot(i);
|
||||
if (!stack.isEmpty()) {
|
||||
ItemStack toDrop = stack.copy();
|
||||
handler.setStackInSlot(i, ItemStack.EMPTY);
|
||||
|
||||
ItemEntity ent = new ItemEntity(level, pos.x, pos.y, pos.z, toDrop);
|
||||
level.addFreshEntity(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kill() {
|
||||
if (!level().isClientSide())
|
||||
dropItemStackHandler((ServerLevel) level(), position(), INVENTORY);
|
||||
super.kill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemovedFromLevel() {
|
||||
if (!level().isClientSide())
|
||||
dropItemStackHandler((ServerLevel) level(), position(), INVENTORY);
|
||||
super.onRemovedFromLevel();
|
||||
}
|
||||
}
|
||||
@@ -8,28 +8,41 @@ import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.registration.HandlerThread;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.BaseMultiblockDummyBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
|
||||
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 {
|
||||
@SubscribeEvent
|
||||
public static void registerCapabilities(RegisterCapabilitiesEvent event) {
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.TEST_BLOCK_ENTITY.get(), TestBlockEntity::getItemHandler);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getEnergyStorage);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getEnergyStorage);
|
||||
// event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), VAFMultiblockDummyBlockEntity::getItemHandler);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getEnergyStorage);
|
||||
|
||||
event.registerEntity(Capabilities.ItemHandler.ENTITY, ModEntities.ROCKET.get(), (rocket, ctx) -> rocket.getInventory());
|
||||
|
||||
event.registerBlockEntity(Capabilities.FluidHandler.BLOCK, ModBlockEntities.STATION_ROCKET_ENGINE_BLOCK_ENTITY.get(), StationRocketEngineBlockEntity::getFluidStorage);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -49,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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.FogRenderer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
|
||||
import net.neoforged.neoforge.common.SoundAction;
|
||||
import net.neoforged.neoforge.common.SoundActions;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -53,6 +57,17 @@ public class BaseFluidType extends FluidType {
|
||||
this.fogEnd = fogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SoundEvent getSound(SoundAction action) {
|
||||
if (action == SoundActions.BUCKET_FILL) {
|
||||
return SoundEvents.BUCKET_FILL;
|
||||
}
|
||||
if (action == SoundActions.BUCKET_EMPTY) {
|
||||
return SoundEvents.BUCKET_EMPTY;
|
||||
}
|
||||
return super.getSound(action);
|
||||
}
|
||||
|
||||
public IClientFluidTypeExtensions getClientFluidTypeExtensions() {
|
||||
return new IClientFluidTypeExtensions() {
|
||||
@Override
|
||||
|
||||
@@ -8,23 +8,51 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModFluidTypes {
|
||||
public static final ResourceLocation WATER_STILL_RL = ResourceLocation.parse("block/water_still");
|
||||
public static final ResourceLocation WATER_FLOWING_RL = ResourceLocation.parse("block/water_flow");
|
||||
public static final ResourceLocation WATER_OVERLAY_RL = ResourceLocation.parse("block/water_overlay");
|
||||
public static final ResourceLocation WATER_STILL_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_still");
|
||||
|
||||
public static final ResourceLocation WATER_FLOWING_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_flow");
|
||||
|
||||
public static final ResourceLocation WATER_OVERLAY_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_overlay");
|
||||
|
||||
static final Color oilColor = new Color(10, 10, 10, 255);
|
||||
static final Color rocketFuelColor = new Color(73, 59, 28, 255);
|
||||
|
||||
public static final DeferredRegister<FluidType> FLUID_TYPES =
|
||||
DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_TYPES, Aphelion.MOD_ID);
|
||||
|
||||
public static final Supplier<FluidType> OIL_FLUID_TYPE = registerFluidType("oil",
|
||||
new BaseFluidType(WATER_STILL_RL, WATER_FLOWING_RL, WATER_OVERLAY_RL, 0xFF101010,
|
||||
new Vector3f(10f / 255f, 10f / 255f, 10f / 255f),
|
||||
FluidType.Properties.create().canDrown(true), 0f, 2f
|
||||
new BaseFluidType(
|
||||
WATER_STILL_RL,
|
||||
WATER_FLOWING_RL,
|
||||
WATER_OVERLAY_RL,
|
||||
oilColor.getRGB(),
|
||||
colToVec(oilColor),
|
||||
FluidType.Properties.create().canDrown(true),
|
||||
0f,
|
||||
0.5f
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
public static final Supplier<FluidType> ROCKET_FUEL_FLUID_TYPE = registerFluidType("rocket_fuel",
|
||||
new BaseFluidType(
|
||||
WATER_STILL_RL,
|
||||
WATER_FLOWING_RL,
|
||||
WATER_OVERLAY_RL,
|
||||
rocketFuelColor.getRGB(),
|
||||
colToVec(rocketFuelColor),
|
||||
FluidType.Properties.create().canDrown(true),
|
||||
0f,
|
||||
2f)
|
||||
);
|
||||
|
||||
private static Supplier<FluidType> registerFluidType(String name, FluidType fluidType) {
|
||||
return FLUID_TYPES.register(name, () -> fluidType);
|
||||
}
|
||||
@@ -32,4 +60,8 @@ public class ModFluidTypes {
|
||||
public static void register(IEventBus eventBus) {
|
||||
FLUID_TYPES.register(eventBus);
|
||||
}
|
||||
|
||||
public static Vector3f colToVec (Color color) {
|
||||
return new Vector3f(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,19 +24,35 @@ public class ModFluids {
|
||||
public static final DeferredRegister<Fluid> FLUIDS =
|
||||
DeferredRegister.create(BuiltInRegistries.FLUID, Aphelion.MOD_ID);
|
||||
|
||||
public static final Supplier<FlowingFluid> SOURCE_OIL_FLUID = FLUIDS.register("oil",
|
||||
public static final Supplier<FlowingFluid> ROCKET_FUEL = FLUIDS.register("rocket_fuel",
|
||||
() -> new BaseFlowingFluid.Source(ModFluids.ROCKET_FUEL_PROPERTIES));
|
||||
public static final Supplier<FlowingFluid> FLOWING_ROCKET_FUEL = FLUIDS.register("flowing_rocket_fuel",
|
||||
() -> new BaseFlowingFluid.Flowing(ModFluids.ROCKET_FUEL_PROPERTIES));
|
||||
|
||||
public static final DeferredBlock<LiquidBlock> ROCKET_FUEL_BLOCK = ModBlocks.BLOCKS.register("rocket_fuel",
|
||||
() -> new LiquidBlock(ModFluids.ROCKET_FUEL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
|
||||
public static final DeferredItem<Item> ROCKET_FUEL_BUCKET = ModItems.ITEMS.registerItem("rocket_fuel_bucket",
|
||||
properties -> new BucketItem(ModFluids.ROCKET_FUEL.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
|
||||
|
||||
public static final BaseFlowingFluid.Properties ROCKET_FUEL_PROPERTIES = new BaseFlowingFluid.Properties(
|
||||
ModFluidTypes.ROCKET_FUEL_FLUID_TYPE, ROCKET_FUEL, FLOWING_ROCKET_FUEL)
|
||||
.slopeFindDistance(2).levelDecreasePerBlock(2).tickRate(10)
|
||||
.block(ModFluids.ROCKET_FUEL_BLOCK).bucket(ModFluids.ROCKET_FUEL_BUCKET);
|
||||
|
||||
public static final Supplier<FlowingFluid> OIL = FLUIDS.register("oil",
|
||||
() -> new BaseFlowingFluid.Source(ModFluids.OIL_PROPERTIES));
|
||||
public static final Supplier<FlowingFluid> FLOWING_OIL_FLUID = FLUIDS.register("flowing_oil",
|
||||
public static final Supplier<FlowingFluid> FLOWING_OIL = FLUIDS.register("flowing_oil",
|
||||
() -> new BaseFlowingFluid.Flowing(ModFluids.OIL_PROPERTIES));
|
||||
|
||||
public static final DeferredBlock<LiquidBlock> OIL_BLOCK = ModBlocks.BLOCKS.register("oil",
|
||||
() -> new LiquidBlock(ModFluids.SOURCE_OIL_FLUID.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
() -> new LiquidBlock(ModFluids.OIL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
|
||||
public static final DeferredItem<Item> OIL_BUCKET = ModItems.ITEMS.registerItem("oil_bucket",
|
||||
properties -> new BucketItem(ModFluids.SOURCE_OIL_FLUID.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
|
||||
properties -> new BucketItem(ModFluids.OIL.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
|
||||
|
||||
public static final BaseFlowingFluid.Properties OIL_PROPERTIES = new BaseFlowingFluid.Properties(
|
||||
ModFluidTypes.OIL_FLUID_TYPE, SOURCE_OIL_FLUID, FLOWING_OIL_FLUID)
|
||||
ModFluidTypes.OIL_FLUID_TYPE, OIL, FLOWING_OIL)
|
||||
.slopeFindDistance(2).levelDecreasePerBlock(2).tickRate(10)
|
||||
.block(ModFluids.OIL_BLOCK).bucket(ModFluids.OIL_BUCKET);
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import net.xevianlight.aphelion.util.SpacePartition;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -34,16 +34,23 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PartitionPayload computePartitionFor(ServerPlayer sp) {
|
||||
int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE);
|
||||
int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE);
|
||||
int px = (int)Math.floor(sp.getX() / SpacePartition.SIZE);
|
||||
int pz = (int)Math.floor(sp.getZ() / SpacePartition.SIZE);
|
||||
|
||||
PartitionData live = SpacePartitionSavedData.get(sp.serverLevel()).getData(px, pz);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -21,7 +22,7 @@ public record PartitionPayload(PartitionData partitionData) implements CustomPac
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
||||
import net.xevianlight.aphelion.util.Constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
12
src/main/java/net/xevianlight/aphelion/planet/Orbit.java
Normal file
12
src/main/java/net/xevianlight/aphelion/planet/Orbit.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.xevianlight.aphelion.planet;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
public record Orbit(
|
||||
int temp
|
||||
) {
|
||||
public static final Codec<Orbit> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
Codec.INT.fieldOf("temp").forGetter(Orbit::temp)
|
||||
).apply(inst, Orbit::new));
|
||||
}
|
||||
@@ -7,19 +7,25 @@ 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),
|
||||
ResourceKey.codec(ModRegistries.ORBIT).fieldOf("orbit").forGetter(Planet::orbit),
|
||||
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,21 +6,28 @@ 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")),
|
||||
ResourceKey.create(ModRegistries.ORBIT, Aphelion.id("orbit/overworld")),
|
||||
1,
|
||||
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")),
|
||||
true,
|
||||
1
|
||||
1,
|
||||
Optional.empty()
|
||||
);
|
||||
|
||||
public static void registerPlanets(Map<ResourceLocation, Planet> planets) {
|
||||
@@ -32,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: {}",
|
||||
@@ -48,8 +56,38 @@ public final class PlanetCache {
|
||||
return PLANETS.getOrDefault(id, DEFAULT);
|
||||
}
|
||||
|
||||
public static @Nullable Planet getOrNull(ResourceLocation id) {
|
||||
return PLANETS.getOrDefault(id, null);
|
||||
}
|
||||
|
||||
public static @Nullable Planet getByOrbitOrNull(ResourceLocation id) {
|
||||
return PLANETS.values().stream()
|
||||
.filter(planet -> planet.orbit().location().equals(id))
|
||||
.findFirst()
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.neoforged.neoforge.items.SlotItemHandler;
|
||||
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.IArcFurnaceLike;
|
||||
import net.xevianlight.aphelion.util.EnergyItemSlot;
|
||||
import net.xevianlight.aphelion.util.ExtractOnlySlot;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ElectricArcFurnaceMenu extends AbstractContainerMenu {
|
||||
|
||||
public final ElectricArcFurnaceEntity blockEntity;
|
||||
public final IArcFurnaceLike blockEntity;
|
||||
private final Level level;
|
||||
private final ContainerData data;
|
||||
|
||||
@@ -28,17 +29,17 @@ public class ElectricArcFurnaceMenu extends AbstractContainerMenu {
|
||||
|
||||
public ElectricArcFurnaceMenu(int i, Inventory inventory, BlockEntity blockEntity, ContainerData data) {
|
||||
super(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), i);
|
||||
this.blockEntity = ((ElectricArcFurnaceEntity) blockEntity);
|
||||
this.blockEntity = ((IArcFurnaceLike) blockEntity);
|
||||
this.level = inventory.player.level();
|
||||
this.data = data;
|
||||
|
||||
addPlayerInventory(inventory);
|
||||
addPlayerHotbar(inventory);
|
||||
|
||||
this.addSlot(new EnergyItemSlot(this.blockEntity.inventory, ElectricArcFurnaceEntity.ENERGY_SLOT, 8, 54));
|
||||
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, ElectricArcFurnaceEntity.INPUT_SLOT, 63, 35));
|
||||
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, ElectricArcFurnaceEntity.SECONDARY_INPUT_SLOT, 40, 35));
|
||||
this.addSlot(new ExtractOnlySlot(this.blockEntity.inventory, ElectricArcFurnaceEntity.OUTPUT_SLOT, 125, 35));
|
||||
this.addSlot(new EnergyItemSlot(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.ENERGY_SLOT, 8, 54));
|
||||
this.addSlot(new SlotItemHandler(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.INPUT_SLOT, 63, 35));
|
||||
this.addSlot(new SlotItemHandler(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.SECONDARY_INPUT_SLOT, 40, 35));
|
||||
this.addSlot(new ExtractOnlySlot(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.OUTPUT_SLOT, 125, 35));
|
||||
|
||||
addDataSlots(data);
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ public class ElectricArcFurnaceScreen extends AbstractContainerScreen<ElectricAr
|
||||
}
|
||||
|
||||
private void renderEnergyBar(GuiGraphics guiGraphics, int x, int y) {
|
||||
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
|
||||
int max = menu.blockEntity.getTrueEnergyStorage(null).getMaxEnergyStored();
|
||||
int stored = menu.blockEntity.getTrueEnergyStorage().getEnergyStored();
|
||||
int max = menu.blockEntity.getTrueEnergyStorage().getMaxEnergyStored();
|
||||
|
||||
int h = (max <= 0) ? 0 : (int) Math.round((stored / (double) max) * 42.0);
|
||||
h = Math.max(0, Math.min(42, h));
|
||||
@@ -79,7 +79,7 @@ public class ElectricArcFurnaceScreen extends AbstractContainerScreen<ElectricAr
|
||||
|
||||
private void assignEnergyInfoArea() {
|
||||
energyInfoArea = new EnergyDisplayTooltipArea(((width - imageWidth) / 2) + 9,
|
||||
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
|
||||
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(), 14, 42);
|
||||
}
|
||||
|
||||
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ public class VacuumArcFurnaceScreen extends AbstractContainerScreen<VacuumArcFur
|
||||
}
|
||||
|
||||
private void renderEnergyBar(GuiGraphics guiGraphics, int x, int y) {
|
||||
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
|
||||
int max = menu.blockEntity.getTrueEnergyStorage(null).getMaxEnergyStored();
|
||||
int stored = menu.blockEntity.getTrueEnergyStorage().getEnergyStored();
|
||||
int max = menu.blockEntity.getTrueEnergyStorage().getMaxEnergyStored();
|
||||
|
||||
int h = (max <= 0) ? 0 : (int) Math.round((stored / (double) max) * 42.0);
|
||||
h = Math.max(0, Math.min(42, h));
|
||||
@@ -79,7 +79,7 @@ public class VacuumArcFurnaceScreen extends AbstractContainerScreen<VacuumArcFur
|
||||
|
||||
private void assignEnergyInfoArea() {
|
||||
energyInfoArea = new EnergyDisplayTooltipArea(((width - imageWidth) / 2) + 9,
|
||||
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
|
||||
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(), 14, 42);
|
||||
}
|
||||
|
||||
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {
|
||||
|
||||
500
src/main/java/net/xevianlight/aphelion/util/BigCodec.java
Normal file
500
src/main/java/net/xevianlight/aphelion/util/BigCodec.java
Normal file
@@ -0,0 +1,500 @@
|
||||
package net.xevianlight.aphelion.util;
|
||||
|
||||
import com.mojang.datafixers.util.*;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Since mojang only wanted to implement codecs up to 6 types, here's all the codecs 7-16. You're welcome.
|
||||
* @param <B>
|
||||
* @param <V>
|
||||
*/
|
||||
public interface BigCodec<B, V> extends StreamCodec<B, V> {
|
||||
|
||||
// ---------- 7 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final Function7<T1, T2, T3, T4, T5, T6, T7, C> factory
|
||||
) {
|
||||
return new StreamCodec<B, C>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 8 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final Function8<T1, T2, T3, T4, T5, T6, T7, T8, C> factory
|
||||
) {
|
||||
return new StreamCodec<B, C>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 9 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
Function9<T1, T2, T3, T4, T5, T6, T7, T8, T9, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 10 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
Function10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 11 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
Function11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 12 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
final StreamCodec<? super B, T12> c12, final Function<C, T12> g12,
|
||||
Function12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
T12 t12 = (T12)c12.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
c12.encode(b, g12.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 13 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
final StreamCodec<? super B, T12> c12, final Function<C, T12> g12,
|
||||
final StreamCodec<? super B, T13> c13, final Function<C, T13> g13,
|
||||
Function13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
T12 t12 = (T12)c12.decode(b);
|
||||
T13 t13 = (T13)c13.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
c12.encode(b, g12.apply(v));
|
||||
c13.encode(b, g13.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 14 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
final StreamCodec<? super B, T12> c12, final Function<C, T12> g12,
|
||||
final StreamCodec<? super B, T13> c13, final Function<C, T13> g13,
|
||||
final StreamCodec<? super B, T14> c14, final Function<C, T14> g14,
|
||||
Function14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
T12 t12 = (T12)c12.decode(b);
|
||||
T13 t13 = (T13)c13.decode(b);
|
||||
T14 t14 = (T14)c14.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
c12.encode(b, g12.apply(v));
|
||||
c13.encode(b, g13.apply(v));
|
||||
c14.encode(b, g14.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 15 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
final StreamCodec<? super B, T12> c12, final Function<C, T12> g12,
|
||||
final StreamCodec<? super B, T13> c13, final Function<C, T13> g13,
|
||||
final StreamCodec<? super B, T14> c14, final Function<C, T14> g14,
|
||||
final StreamCodec<? super B, T15> c15, final Function<C, T15> g15,
|
||||
Function15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
T12 t12 = (T12)c12.decode(b);
|
||||
T13 t13 = (T13)c13.decode(b);
|
||||
T14 t14 = (T14)c14.decode(b);
|
||||
T15 t15 = (T15)c15.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
c12.encode(b, g12.apply(v));
|
||||
c13.encode(b, g13.apply(v));
|
||||
c14.encode(b, g14.apply(v));
|
||||
c15.encode(b, g15.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 16 ----------
|
||||
static <B, C, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> StreamCodec<B, C> composite(
|
||||
final StreamCodec<? super B, T1> c1, final Function<C, T1> g1,
|
||||
final StreamCodec<? super B, T2> c2, final Function<C, T2> g2,
|
||||
final StreamCodec<? super B, T3> c3, final Function<C, T3> g3,
|
||||
final StreamCodec<? super B, T4> c4, final Function<C, T4> g4,
|
||||
final StreamCodec<? super B, T5> c5, final Function<C, T5> g5,
|
||||
final StreamCodec<? super B, T6> c6, final Function<C, T6> g6,
|
||||
final StreamCodec<? super B, T7> c7, final Function<C, T7> g7,
|
||||
final StreamCodec<? super B, T8> c8, final Function<C, T8> g8,
|
||||
final StreamCodec<? super B, T9> c9, final Function<C, T9> g9,
|
||||
final StreamCodec<? super B, T10> c10, final Function<C, T10> g10,
|
||||
final StreamCodec<? super B, T11> c11, final Function<C, T11> g11,
|
||||
final StreamCodec<? super B, T12> c12, final Function<C, T12> g12,
|
||||
final StreamCodec<? super B, T13> c13, final Function<C, T13> g13,
|
||||
final StreamCodec<? super B, T14> c14, final Function<C, T14> g14,
|
||||
final StreamCodec<? super B, T15> c15, final Function<C, T15> g15,
|
||||
final StreamCodec<? super B, T16> c16, final Function<C, T16> g16,
|
||||
Function16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, C> factory
|
||||
) {
|
||||
return new StreamCodec<>() {
|
||||
public @NotNull C decode(@NotNull B b) {
|
||||
T1 t1 = (T1)c1.decode(b);
|
||||
T2 t2 = (T2)c2.decode(b);
|
||||
T3 t3 = (T3)c3.decode(b);
|
||||
T4 t4 = (T4)c4.decode(b);
|
||||
T5 t5 = (T5)c5.decode(b);
|
||||
T6 t6 = (T6)c6.decode(b);
|
||||
T7 t7 = (T7)c7.decode(b);
|
||||
T8 t8 = (T8)c8.decode(b);
|
||||
T9 t9 = (T9)c9.decode(b);
|
||||
T10 t10 = (T10)c10.decode(b);
|
||||
T11 t11 = (T11)c11.decode(b);
|
||||
T12 t12 = (T12)c12.decode(b);
|
||||
T13 t13 = (T13)c13.decode(b);
|
||||
T14 t14 = (T14)c14.decode(b);
|
||||
T15 t15 = (T15)c15.decode(b);
|
||||
T16 t16 = (T16)c16.decode(b);
|
||||
return (C)factory.apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16);
|
||||
}
|
||||
|
||||
public void encode(@NotNull B b, @NotNull C v) {
|
||||
c1.encode(b, g1.apply(v));
|
||||
c2.encode(b, g2.apply(v));
|
||||
c3.encode(b, g3.apply(v));
|
||||
c4.encode(b, g4.apply(v));
|
||||
c5.encode(b, g5.apply(v));
|
||||
c6.encode(b, g6.apply(v));
|
||||
c7.encode(b, g7.apply(v));
|
||||
c8.encode(b, g8.apply(v));
|
||||
c9.encode(b, g9.apply(v));
|
||||
c10.encode(b, g10.apply(v));
|
||||
c11.encode(b, g11.apply(v));
|
||||
c12.encode(b, g12.apply(v));
|
||||
c13.encode(b, g13.apply(v));
|
||||
c14.encode(b, g14.apply(v));
|
||||
c15.encode(b, g15.apply(v));
|
||||
c16.encode(b, g16.apply(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public final class FloodFill3D {
|
||||
};
|
||||
|
||||
public static Set<BlockPos> run(Level level, BlockPos start, int limit, SolidBlockPredicate predicate, boolean retainOrder) {
|
||||
level.getProfiler().push("adastra-floodfill");
|
||||
level.getProfiler().push("aphelion-floodfill");
|
||||
|
||||
LongSet positions = retainOrder ? new LongLinkedOpenHashSet(limit) : new LongOpenHashSet(limit);
|
||||
LongArrayFIFOQueue queue = new LongArrayFIFOQueue(limit);
|
||||
|
||||
@@ -20,6 +20,7 @@ public class ModTags {
|
||||
public static final TagKey<Block> LAUNCH_PAD = createTag("launch_pad");
|
||||
public static final TagKey<Block> PASSES_FLOOD_FILL = createTag("passes_flood_fill");
|
||||
public static final TagKey<Block> BLOCKS_FLOOD_FILL = createTag("blocks_flood_fill");
|
||||
public static final TagKey<Block> ROCKET_SEAT = createTag("rocket_seat");
|
||||
|
||||
private static TagKey<Block> commonTag(String name) {
|
||||
return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name));
|
||||
|
||||
@@ -5,9 +5,17 @@ import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.xevianlight.aphelion.block.custom.base.IRocketEnergyUpgrade;
|
||||
import net.xevianlight.aphelion.block.custom.base.IRocketFluidUpgrade;
|
||||
import net.xevianlight.aphelion.block.custom.base.IRocketFuelUpgrade;
|
||||
import net.xevianlight.aphelion.block.custom.base.IRocketInventoryUpgrade;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -19,6 +27,8 @@ public final class RocketStructure {
|
||||
private final IntList packedPositions = new IntArrayList();
|
||||
private final IntList paletteIndices = new IntArrayList();
|
||||
|
||||
private final IntList seatOffsets = new IntArrayList();
|
||||
|
||||
public RocketStructure(Builder builder) {
|
||||
builder.build(this);
|
||||
}
|
||||
@@ -34,6 +44,7 @@ public final class RocketStructure {
|
||||
palette.clear();
|
||||
packedPositions.clear();
|
||||
paletteIndices.clear();
|
||||
seatOffsets.clear();
|
||||
}
|
||||
|
||||
public void add(int x, int y, int z, BlockState state) {
|
||||
@@ -69,6 +80,8 @@ public final class RocketStructure {
|
||||
tag.put("pos", posArr);
|
||||
tag.put("idx", idxArr);
|
||||
|
||||
tag.put("seats", new IntArrayTag(seatOffsets.toIntArray()));
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@@ -92,6 +105,11 @@ public final class RocketStructure {
|
||||
packedPositions.add(pos[i]);
|
||||
paletteIndices.add(idx[i]);
|
||||
}
|
||||
|
||||
if (tag.contains("seats", Tag.TAG_INT_ARRAY)) {
|
||||
int[] seats = tag.getIntArray("seats");
|
||||
for (int s : seats) seatOffsets.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static int packPos (int x, int y, int z) {
|
||||
@@ -138,36 +156,118 @@ public final class RocketStructure {
|
||||
return new Extents(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public static RocketStructure capture(Level level, BlockPos origin, int rx, int ry, int rz) {
|
||||
return new RocketStructure(s -> {
|
||||
for (int dy = -ry; dy <= ry; dy++) {
|
||||
for (int dx = -rx; dx <= rx; dx++) {
|
||||
for (int dz = -rz; dz <= rz; dz++) {
|
||||
BlockPos p = origin.offset(dx, dy, dz);
|
||||
BlockState st = level.getBlockState(p);
|
||||
|
||||
// Skip air and unbreakables/forbidden blocks as you like
|
||||
if (st.isAir()) continue;
|
||||
|
||||
// Optional: ignore the assembler block itself
|
||||
// if (p.equals(origin)) continue;
|
||||
|
||||
s.add(dx, dy, dz, st);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void clearCaptured(Level level, BlockPos origin, RocketStructure struct) {
|
||||
final int flags = Block.UPDATE_CLIENTS;
|
||||
|
||||
// Pass 1: remove blocks which implement DOUBLE_BLOCK_HALF like doors to try and prevent duplication.
|
||||
for (int i = 0; i < struct.size(); i++) {
|
||||
int packed = struct.packedPosAt(i);
|
||||
int dx = RocketStructure.unpackX(packed);
|
||||
int dy = RocketStructure.unpackY(packed);
|
||||
int dz = RocketStructure.unpackZ(packed);
|
||||
BlockPos wp = origin.offset(
|
||||
RocketStructure.unpackX(packed),
|
||||
RocketStructure.unpackY(packed),
|
||||
RocketStructure.unpackZ(packed)
|
||||
);
|
||||
|
||||
BlockPos wp = origin.offset(dx, dy, dz);
|
||||
level.setBlock(wp, Blocks.AIR.defaultBlockState(), 3);
|
||||
BlockState st = level.getBlockState(wp);
|
||||
if (st.isAir()) continue;
|
||||
|
||||
if (st.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) {
|
||||
DoubleBlockHalf half = st.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF);
|
||||
BlockPos bottom = (half == DoubleBlockHalf.LOWER) ? wp : null;
|
||||
|
||||
// Break the BOTTOM block to stop potential dupes, as it seems that is the "master" block for doors.
|
||||
// If you set the top block to air, the bottom one breaks a moment later and drops.
|
||||
// If this doesn't work I declare it NOT MY FAULT!
|
||||
// DoubleBlockHalf blocks should have a way to delete the entire thing at once god damnit
|
||||
if (bottom != null && !level.getBlockState(bottom).isAir()) {
|
||||
level.setBlock(bottom, Blocks.AIR.defaultBlockState(), flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: remove likely-attached blocks first. This should stop duplication of torches/buttons/whatever else may break due to its supporting block being broken
|
||||
for (int i = 0; i < struct.size(); i++) {
|
||||
int packed = struct.packedPosAt(i);
|
||||
BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
|
||||
BlockState st = level.getBlockState(wp);
|
||||
if (st.isAir()) continue;
|
||||
|
||||
// Heuristic: if it isn't a full collision cube, it's often "attached" (buttons, torches, etc.)
|
||||
if (!st.isCollisionShapeFullBlock(level, wp)) {
|
||||
level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: remove the rest
|
||||
for (int i = 0; i < struct.size(); i++) {
|
||||
int packed = struct.packedPosAt(i);
|
||||
BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
|
||||
if (!level.getBlockState(wp).isAir()) {
|
||||
level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int seatCount() { return seatOffsets.size(); }
|
||||
public int packedSeatAt(int i) { return seatOffsets.getInt(i); }
|
||||
|
||||
public void addSeatOffset(int dx, int dy, int dz) {
|
||||
seatOffsets.add(packPos(dx, dy, dz));
|
||||
}
|
||||
|
||||
public static int calculateInventoryCapacity(RocketStructure structure) {
|
||||
int totalSlots = 0;
|
||||
for (int i = 0; i < structure.size(); i++) {
|
||||
BlockState st = structure.stateAt(i);
|
||||
Block block = st.getBlock();
|
||||
|
||||
if (block instanceof IRocketInventoryUpgrade upgrade) {
|
||||
int slots = upgrade.getSlotCapacity();
|
||||
if (slots > 0) totalSlots += slots;
|
||||
}
|
||||
}
|
||||
return totalSlots;
|
||||
}
|
||||
|
||||
public static int calculateFuelCapacity(RocketStructure structure) {
|
||||
int totalMB = 0;
|
||||
for (int i = 0; i < structure.size(); i++) {
|
||||
BlockState st = structure.stateAt(i);
|
||||
Block block = st.getBlock();
|
||||
|
||||
if (block instanceof IRocketFuelUpgrade upgrade) {
|
||||
int mb = upgrade.getFuelCapacity();
|
||||
if (mb > 0) totalMB += mb;
|
||||
}
|
||||
}
|
||||
return totalMB;
|
||||
}
|
||||
|
||||
public static int calculateFluidCapacity(RocketStructure structure) {
|
||||
int totalMB = 0;
|
||||
for (int i = 0; i < structure.size(); i++) {
|
||||
BlockState st = structure.stateAt(i);
|
||||
Block block = st.getBlock();
|
||||
|
||||
if (block instanceof IRocketFluidUpgrade upgrade) {
|
||||
int mb = upgrade.getFluidCapacity();
|
||||
if (mb > 0) totalMB += mb;
|
||||
}
|
||||
}
|
||||
return totalMB;
|
||||
}
|
||||
|
||||
public static int calculateEnergyCapacity(RocketStructure structure) {
|
||||
int totalFE = 0;
|
||||
for (int i = 0; i < structure.size(); i++) {
|
||||
BlockState st = structure.stateAt(i);
|
||||
Block block = st.getBlock();
|
||||
|
||||
if (block instanceof IRocketEnergyUpgrade upgrade) {
|
||||
int fe = upgrade.getEnergyCapacity();
|
||||
if (fe > 0) totalFE += fe;
|
||||
}
|
||||
}
|
||||
return totalFE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.xevianlight.aphelion.util;
|
||||
|
||||
public class SpacePartitionHelper {
|
||||
public class SpacePartition {
|
||||
|
||||
public static final int SIZE = 16;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user