Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.

Commit 6acdadd

Browse files
authored
Knowledge belongs to a place: what this world knows is what a pad here can aim at
- a world carries what it has learned; the pack's authored list is the floor - an observatory teaches the world it stands on what it actually resolved - a beacon announces itself to its own system instead of to the whole game - a Deposit control reads a crystal into what this world knows, one way only - realize the body that was approached: a moon stops inheriting its planet's world
1 parent 2d17843 commit 6acdadd

15 files changed

Lines changed: 1058 additions & 57 deletions

File tree

src/main/java/zmaster587/advancedRocketry/command/test/TestProbeCommand.java

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,14 +2540,57 @@ private void handleTelescope(MinecraftServer server, ICommandSender sender, Stri
25402540
zmaster587.advancedRocketry.api.AdvancedRocketryItems.itemMemoryCrystal);
25412541
// A deliberately BLANK crystal: the starter addresses would make every count a test
25422542
// asserts depend on the world's planet list rather than on what the scan resolved.
2543-
zmaster587.advancedRocketry.item.ItemMemoryCrystal.writeMemory(stack,
2544-
new zmaster587.advancedRocketry.navigation.CrystalMemory());
2543+
zmaster587.advancedRocketry.navigation.CrystalMemory seeded =
2544+
new zmaster587.advancedRocketry.navigation.CrystalMemory();
2545+
// ...unless a caller names ONE world it must hold. A test that needs the deposit to be
2546+
// the only possible source of a piece of knowledge cannot use a crystal the survey
2547+
// filled, because the survey teaches this world as it goes.
2548+
if (args.length >= 6) {
2549+
int namedDim = parseIntOr(args[5], zmaster587.advancedRocketry.api.Constants.INVALID_PLANET);
2550+
if (namedDim != zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
2551+
seeded.record(new zmaster587.advancedRocketry.navigation.CrystalEntry(
2552+
zmaster587.advancedRocketry.space.GalacticCoord.ofSectorLocal(
2553+
7000L, 0L, 0L, 0L, 0L, 0L),
2554+
"probe-named-" + namedDim,
2555+
zmaster587.advancedRocketry.universe.SystemBodyKind.PLANET,
2556+
zmaster587.advancedRocketry.universe.InfoTier.TELESCOPE, 1L, namedDim));
2557+
}
2558+
}
2559+
zmaster587.advancedRocketry.item.ItemMemoryCrystal.writeMemory(stack, seeded);
25452560
scope.setInventorySlotContents(
25462561
zmaster587.advancedRocketry.tile.multiblock.TileObservatory.SLOT_CRYSTAL, stack);
25472562
send(sender, "{\"ok\":true,\"addresses\":" + crystalAddresses(scope) + "}");
25482563
return;
25492564
}
25502565

2566+
if ("deposit".equalsIgnoreCase(verb)) {
2567+
// The Deposit button's own path: read the crystal in the machine into what THIS world
2568+
// knows. Reported as landed/total because an address of a world nobody has landed on has
2569+
// nothing for tier-1 to fly to and is skipped.
2570+
java.util.List<Integer> before = new java.util.ArrayList<>();
2571+
for (zmaster587.advancedRocketry.navigation.CrystalEntry entry
2572+
: zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(
2573+
scope.getStackInSlot(
2574+
zmaster587.advancedRocketry.tile.multiblock.TileObservatory
2575+
.SLOT_CRYSTAL)).list()) {
2576+
if (entry.namesBody()) {
2577+
before.add(entry.dimId());
2578+
}
2579+
}
2580+
int[] result = scope.uploadCrystalHere();
2581+
StringBuilder dims = new StringBuilder("[");
2582+
for (int i = 0; i < before.size(); i++) {
2583+
if (i > 0) dims.append(',');
2584+
dims.append(before.get(i));
2585+
}
2586+
dims.append(']');
2587+
// The dims are reported, not just the count: a test that could only read "3 landed" would
2588+
// have to guess WHICH worlds a pad here may now be aimed at.
2589+
send(sender, "{\"ok\":true,\"landed\":" + result[0] + ",\"total\":" + result[1]
2590+
+ ",\"dims\":" + dims + "}");
2591+
return;
2592+
}
2593+
25512594
if ("abort".equalsIgnoreCase(verb)) {
25522595
boolean stopped = scope.abortRegionScan();
25532596
send(sender, "{\"ok\":" + stopped + telescopeScanFields(scope, scope.getActiveScan(), world) + "}");
@@ -2630,6 +2673,10 @@ private String telescopeScanFields(zmaster587.advancedRocketry.tile.multiblock.T
26302673
.append(",\"origin\":").append(origin == null ? "null" : "\"" + origin.cellKey() + "\"")
26312674
.append(",\"scanning\":").append(scan != null)
26322675
.append(",\"addresses\":").append(crystalAddresses(scope))
2676+
// WHICH worlds the crystal holds, read without touching anything. A test that had to
2677+
// call `deposit` to find out would have deposited them, and could no longer show
2678+
// that pressing the button is what teaches this world.
2679+
.append(",\"crystalDims\":").append(crystalDims(scope))
26332680
.append(",\"lastDiscoveries\":").append(scope.getLastScanDiscoveries())
26342681
// Where the OPERATOR has the instrument pointed — the tile's own pick, which is what
26352682
// a GUI click changes and what the next scan will use. Distinct from the region a
@@ -2694,6 +2741,29 @@ private int crystalAddresses(zmaster587.advancedRocketry.tile.multiblock.TileObs
26942741
return zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(stack).size();
26952742
}
26962743

2744+
/** The dimensions the crystal in that slot names, as a JSON array. Reads nothing into anything. */
2745+
private String crystalDims(zmaster587.advancedRocketry.tile.multiblock.TileObservatory scope) {
2746+
net.minecraft.item.ItemStack stack = scope.getStackInSlot(
2747+
zmaster587.advancedRocketry.tile.multiblock.TileObservatory.SLOT_CRYSTAL);
2748+
if (!zmaster587.advancedRocketry.item.ItemMemoryCrystal.isCrystal(stack)) {
2749+
return "[]";
2750+
}
2751+
StringBuilder out = new StringBuilder("[");
2752+
boolean first = true;
2753+
for (zmaster587.advancedRocketry.navigation.CrystalEntry entry
2754+
: zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(stack).list()) {
2755+
if (!entry.namesBody()) {
2756+
continue;
2757+
}
2758+
if (!first) {
2759+
out.append(',');
2760+
}
2761+
out.append(entry.dimId());
2762+
first = false;
2763+
}
2764+
return out.append(']').toString();
2765+
}
2766+
26972767
private zmaster587.advancedRocketry.tile.multiblock.TileObservatory observatoryAt(
26982768
net.minecraft.world.World world, BlockPos pos) {
26992769
net.minecraft.tileentity.TileEntity te = world.getTileEntity(pos);
@@ -5230,7 +5300,21 @@ private void handleSpace(MinecraftServer server, ICommandSender sender, String[]
52305300
zmaster587.advancedRocketry.space.GalacticCoord.ofSectorLocal(
52315301
parseIntOr(args[1], 0), parseIntOr(args[2], 0), parseIntOr(args[3], 0),
52325302
0L, 0L, 0L);
5233-
int dimId = zmaster587.advancedRocketry.universe.PlanetRealizer.realize(server, cell);
5303+
// A cell names a family - a planet and the moons that share its address - so the probe
5304+
// states WHICH of them it means. Default 0, the planet, with an optional variant arg;
5305+
// a caller that wants the moon has to say so, exactly as a descent does.
5306+
int variant = args.length >= 5 ? parseIntOr(args[4], 0) : 0;
5307+
java.util.List<zmaster587.advancedRocketry.universe.SystemBody> family =
5308+
zmaster587.advancedRocketry.universe.UniverseRegistry.get(server) == null
5309+
? java.util.Collections.<zmaster587.advancedRocketry.universe.SystemBody>emptyList()
5310+
: zmaster587.advancedRocketry.universe.UniverseRegistry.get(server)
5311+
.realizableBodiesAt(cell);
5312+
if (variant < 0 || variant >= family.size()) {
5313+
send(sender, "{\"ok\":false,\"reason\":\"no body with that variant in the cell\"}");
5314+
return;
5315+
}
5316+
int dimId = zmaster587.advancedRocketry.universe.PlanetRealizer.realize(server,
5317+
family.get(variant));
52345318
if (dimId == zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
52355319
send(sender, "{\"ok\":false,\"reason\":\"nothing landable in that cell\"}");
52365320
return;
@@ -5993,6 +6077,36 @@ private void handleDim(ICommandSender sender, String[] args) {
59936077
// Planet/weather probes ----------------------------------------------
59946078

59956079
private void handlePlanet(ICommandSender sender, String[] args) {
6080+
// What a TIER-1 launch pad standing on one world may be aimed at, asked of the production
6081+
// gate rather than re-derived here: a real rocket in the standing world answers
6082+
// IPlanetDefiner.isPlanetKnown, and the two halves are reported beside it so a red test says
6083+
// WHICH of them moved - the pack's floor or what this body has learned.
6084+
if (args.length >= 3 && "knowledge".equalsIgnoreCase(args[0])) {
6085+
int standingDim = parseIntOr(args[1], Integer.MIN_VALUE);
6086+
int targetDim = parseIntOr(args[2], Integer.MIN_VALUE);
6087+
net.minecraft.world.World here = net.minecraftforge.common.DimensionManager
6088+
.getWorld(standingDim);
6089+
DimensionProperties target = DimensionManager.getInstance()
6090+
.getDimensionPropertiesOrNull(targetDim);
6091+
DimensionProperties standing = DimensionManager.getInstance()
6092+
.getDimensionPropertiesOrNull(standingDim);
6093+
if (here == null || target == null) {
6094+
send(sender, "{\"error\":\"standing world not loaded or unknown target\"}");
6095+
return;
6096+
}
6097+
zmaster587.advancedRocketry.entity.EntityRocket rocket =
6098+
new zmaster587.advancedRocketry.entity.EntityRocket(here);
6099+
send(sender, "{\"standing\":" + standingDim + ",\"target\":" + targetDim
6100+
+ ",\"known\":" + rocket.isPlanetKnown(target)
6101+
+ ",\"global\":" + DimensionManager.getInstance().isPlanetKnown(targetDim)
6102+
+ ",\"local\":" + (standing != null && standing.isPlanetKnownHere(targetDim))
6103+
+ ",\"research\":"
6104+
+ zmaster587.advancedRocketry.api.ARConfiguration.getCurrentConfig()
6105+
.planetsMustBeDiscovered
6106+
+ "}");
6107+
return;
6108+
}
6109+
59966110
if (args.length >= 2 && "info".equalsIgnoreCase(args[0])) {
59976111
int dim = parseIntOr(args[1], Integer.MIN_VALUE);
59986112
DimensionProperties props = DimensionManager.getInstance().getDimensionProperties(dim);

src/main/java/zmaster587/advancedRocketry/dimension/DimensionProperties.java

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -767,11 +767,64 @@ public void setHasRings(boolean value) {
767767
//Adds a beacon location to the planet's surface
768768
public void addBeaconLocation(World world, HashedBlockPosition pos) {
769769
beaconLocations.add(pos);
770-
DimensionManager.getInstance().knownPlanets.add(getId());
771770

772771
//LAAZZY
773-
if (!world.isRemote)
772+
if (!world.isRemote) {
773+
for (DimensionProperties taught : teachOwnSystem()) {
774+
PacketHandler.sendToAll(new PacketDimInfo(taught.getId(), taught));
775+
}
774776
PacketHandler.sendToAll(new PacketDimInfo(getId(), this));
777+
}
778+
}
779+
780+
/**
781+
* Tell the bodies of this body's own system that this place exists, and return the ones that
782+
* did not already know.
783+
*
784+
* <p><b>A beacon is a local announcement, not a galactic one.</b> It used to add this planet to
785+
* the GLOBAL known-set, so planting one anywhere made the place selectable from every launch pad
786+
* in the game. What is true is narrower and more interesting: the neighbours know, because they
787+
* can see it. So the beacon writes into the known-set of every body of its own system, and
788+
* nothing outside that system learns anything.</p>
789+
*
790+
* <p>It can never be the thing that first reveals a place - a beacon is planted by hand, so
791+
* somebody already flew here, which means the destination was already reachable. That is why
792+
* scoping it costs nothing: a beacon spreads knowledge inside reach and never creates reach.</p>
793+
*/
794+
public List<DimensionProperties> teachOwnSystem() {
795+
List<DimensionProperties> taught = new ArrayList<>();
796+
discoverPlanet(getId()); // the body it stands on, always - the degenerate system of one
797+
StellarBody star = getStar();
798+
if (star == null) {
799+
return taught; // a world with no star of its own: the beacon teaches only its own ground
800+
}
801+
for (IDimensionProperties sibling : star.getPlanets()) {
802+
DimensionProperties props = DimensionManager.getInstance()
803+
.getDimensionPropertiesOrNull(sibling.getId());
804+
if (props == null) {
805+
continue;
806+
}
807+
teach(props, taught);
808+
// A moon is a child of its planet rather than of the star, so the star's list alone
809+
// would leave every moon of the system ignorant of a beacon in it.
810+
for (int childId : props.getChildPlanets()) {
811+
DimensionProperties child = DimensionManager.getInstance()
812+
.getDimensionPropertiesOrNull(childId);
813+
if (child != null) {
814+
teach(child, taught);
815+
}
816+
}
817+
}
818+
return taught;
819+
}
820+
821+
/** Teach {@code body} about this place, collecting it when that changed anything. */
822+
private void teach(DimensionProperties body, List<DimensionProperties> taught) {
823+
if (body.getId() == getId() || body.isPlanetKnownHere(getId())) {
824+
return;
825+
}
826+
body.discoverPlanet(getId());
827+
taught.add(body);
775828
}
776829

777830
public HashSet<HashedBlockPosition> getBeacons() {
@@ -1692,7 +1745,10 @@ private void readFromTechnicalNBT(NBTTagCompound nbt) {
16921745
int[] location = list.getIntArrayAt(i);
16931746
beaconLocations.add(new HashedBlockPosition(location[0], location[1], location[2]));
16941747
}
1695-
DimensionManager.getInstance().knownPlanets.add(getId());
1748+
// No global add on load any more. What a beacon taught is held by the bodies it taught,
1749+
// in their own saved known-sets, so re-announcing this place to the whole game at every
1750+
// load would put back exactly the reach the scoping removed. A world whose beacons
1751+
// predate the local sets simply has nothing recorded - 3.0.0 carries no old saves.
16961752
} else
16971753
beaconLocations.clear();
16981754

@@ -1734,6 +1790,13 @@ public void readFromNBT(NBTTagCompound nbt) {
17341790

17351791
NBTTagList list;
17361792

1793+
// Cleared first: this object is reused across loads, and a merge would make a body remember
1794+
// what a previous save taught it.
1795+
locallyKnownPlanets.clear();
1796+
for (int dimId : nbt.getIntArray("locallyKnownPlanets")) {
1797+
locallyKnownPlanets.add(dimId);
1798+
}
1799+
17371800
if (nbt.hasKey("skyColor")) {
17381801
list = nbt.getTagList("skyColor", NBT.TAG_FLOAT);
17391802
skyColor = new float[list.tagCount()];
@@ -2197,9 +2260,50 @@ public void write_terraforming_data(NBTTagCompound nbt) {
21972260

21982261

21992262
}
2263+
/**
2264+
* What is known ON this body: the planets a launch pad standing here may be aimed at, beyond the
2265+
* ones everybody knows.
2266+
*
2267+
* <p><b>Knowledge belongs to a place.</b> An observatory built here teaches THIS body; a beacon
2268+
* teaches the bodies of its own system; a memory crystal uploaded here deposits what somebody
2269+
* carried in. None of that reaches the global set, and none of it reaches a neighbouring world -
2270+
* a launch pad on a moon offers a different list than the pad on the planet below it.</p>
2271+
*
2272+
* <p>It is ADDITIVE over the global set rather than a replacement for it, so a pack that authors
2273+
* {@code <isKnown>} keeps authoring exactly as it did: the global set is the floor everyone
2274+
* stands on, this is what a particular world has learned since.</p>
2275+
*
2276+
* <p>Communal per world, not per player: two players on the same body see the same list.</p>
2277+
*/
2278+
private final Set<Integer> locallyKnownPlanets = new HashSet<>();
2279+
2280+
/** Teach this body about {@code dimId}. Idempotent. */
2281+
public void discoverPlanet(int dimId) {
2282+
locallyKnownPlanets.add(dimId);
2283+
}
2284+
2285+
/** Whether THIS body knows {@code dimId} - the local half of the gate, with no global fallback. */
2286+
public boolean isPlanetKnownHere(int dimId) {
2287+
return locallyKnownPlanets.contains(dimId);
2288+
}
2289+
2290+
/** What this body knows, for readers that need the whole set (GUI, sync, tests). */
2291+
public Set<Integer> getLocallyKnownPlanets() {
2292+
return Collections.unmodifiableSet(locallyKnownPlanets);
2293+
}
2294+
22002295
public void writeToNBT(NBTTagCompound nbt) {
22012296
NBTTagList list;
22022297

2298+
if (!locallyKnownPlanets.isEmpty()) {
2299+
int[] known = new int[locallyKnownPlanets.size()];
2300+
int k = 0;
2301+
for (int dimId : locallyKnownPlanets) {
2302+
known[k++] = dimId;
2303+
}
2304+
nbt.setIntArray("locallyKnownPlanets", known);
2305+
}
2306+
22032307
if (skyColor != null) {
22042308
list = new NBTTagList();
22052309
for (float f : skyColor) {

src/main/java/zmaster587/advancedRocketry/entity/EntityRocket.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3852,7 +3852,20 @@ public LinkedList<IInfrastructure> getConnectedInfrastructure() {
38523852

38533853
@Override
38543854
public boolean isPlanetKnown(IDimensionProperties properties) {
3855-
return !ARConfiguration.getCurrentConfig().planetsMustBeDiscovered || DimensionManager.getInstance().isPlanetKnown(properties.getId());
3855+
if (!ARConfiguration.getCurrentConfig().planetsMustBeDiscovered) {
3856+
return true;
3857+
}
3858+
int target = properties.getId();
3859+
// The global set is the FLOOR - what a pack authored as known, plus dim 0. Everything past it
3860+
// is learned by a particular world, so the second question is asked of the body this rocket
3861+
// is standing on and not of the game.
3862+
if (DimensionManager.getInstance().isPlanetKnown(target)) {
3863+
return true;
3864+
}
3865+
DimensionProperties here = world == null
3866+
? null
3867+
: DimensionManager.getInstance().getDimensionPropertiesOrNull(world.provider.getDimension());
3868+
return here != null && here.isPlanetKnownHere(target);
38563869
}
38573870

38583871
@Override

src/main/java/zmaster587/advancedRocketry/tile/TileAdvancedFlightComputer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,10 @@ public void update() {
594594
// descend. The scan above must never allocate a dimension.
595595
int targetDim = body.dimId();
596596
if (targetDim == zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
597+
// The BODY, not its cell: a moon shares its planet's address, so a
598+
// cell names a family and only the body says which of them was flown to.
597599
targetDim = zmaster587.advancedRocketry.universe.PlanetRealizer
598-
.realize(server, body.name());
600+
.realize(server, body);
599601
if (targetDim
600602
== zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
601603
continue; // nothing landable here after all

0 commit comments

Comments
 (0)