Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2540,14 +2540,57 @@ private void handleTelescope(MinecraftServer server, ICommandSender sender, Stri
zmaster587.advancedRocketry.api.AdvancedRocketryItems.itemMemoryCrystal);
// A deliberately BLANK crystal: the starter addresses would make every count a test
// asserts depend on the world's planet list rather than on what the scan resolved.
zmaster587.advancedRocketry.item.ItemMemoryCrystal.writeMemory(stack,
new zmaster587.advancedRocketry.navigation.CrystalMemory());
zmaster587.advancedRocketry.navigation.CrystalMemory seeded =
new zmaster587.advancedRocketry.navigation.CrystalMemory();
// ...unless a caller names ONE world it must hold. A test that needs the deposit to be
// the only possible source of a piece of knowledge cannot use a crystal the survey
// filled, because the survey teaches this world as it goes.
if (args.length >= 6) {
int namedDim = parseIntOr(args[5], zmaster587.advancedRocketry.api.Constants.INVALID_PLANET);
if (namedDim != zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
seeded.record(new zmaster587.advancedRocketry.navigation.CrystalEntry(
zmaster587.advancedRocketry.space.GalacticCoord.ofSectorLocal(
7000L, 0L, 0L, 0L, 0L, 0L),
"probe-named-" + namedDim,
zmaster587.advancedRocketry.universe.SystemBodyKind.PLANET,
zmaster587.advancedRocketry.universe.InfoTier.TELESCOPE, 1L, namedDim));
}
}
zmaster587.advancedRocketry.item.ItemMemoryCrystal.writeMemory(stack, seeded);
scope.setInventorySlotContents(
zmaster587.advancedRocketry.tile.multiblock.TileObservatory.SLOT_CRYSTAL, stack);
send(sender, "{\"ok\":true,\"addresses\":" + crystalAddresses(scope) + "}");
return;
}

if ("deposit".equalsIgnoreCase(verb)) {
// The Deposit button's own path: read the crystal in the machine into what THIS world
// knows. Reported as landed/total because an address of a world nobody has landed on has
// nothing for tier-1 to fly to and is skipped.
java.util.List<Integer> before = new java.util.ArrayList<>();
for (zmaster587.advancedRocketry.navigation.CrystalEntry entry
: zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(
scope.getStackInSlot(
zmaster587.advancedRocketry.tile.multiblock.TileObservatory
.SLOT_CRYSTAL)).list()) {
if (entry.namesBody()) {
before.add(entry.dimId());
}
}
int[] result = scope.uploadCrystalHere();
StringBuilder dims = new StringBuilder("[");
for (int i = 0; i < before.size(); i++) {
if (i > 0) dims.append(',');
dims.append(before.get(i));
}
dims.append(']');
// The dims are reported, not just the count: a test that could only read "3 landed" would
// have to guess WHICH worlds a pad here may now be aimed at.
send(sender, "{\"ok\":true,\"landed\":" + result[0] + ",\"total\":" + result[1]
+ ",\"dims\":" + dims + "}");
return;
}

if ("abort".equalsIgnoreCase(verb)) {
boolean stopped = scope.abortRegionScan();
send(sender, "{\"ok\":" + stopped + telescopeScanFields(scope, scope.getActiveScan(), world) + "}");
Expand Down Expand Up @@ -2630,6 +2673,10 @@ private String telescopeScanFields(zmaster587.advancedRocketry.tile.multiblock.T
.append(",\"origin\":").append(origin == null ? "null" : "\"" + origin.cellKey() + "\"")
.append(",\"scanning\":").append(scan != null)
.append(",\"addresses\":").append(crystalAddresses(scope))
// WHICH worlds the crystal holds, read without touching anything. A test that had to
// call `deposit` to find out would have deposited them, and could no longer show
// that pressing the button is what teaches this world.
.append(",\"crystalDims\":").append(crystalDims(scope))
.append(",\"lastDiscoveries\":").append(scope.getLastScanDiscoveries())
// Where the OPERATOR has the instrument pointed — the tile's own pick, which is what
// a GUI click changes and what the next scan will use. Distinct from the region a
Expand Down Expand Up @@ -2694,6 +2741,29 @@ private int crystalAddresses(zmaster587.advancedRocketry.tile.multiblock.TileObs
return zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(stack).size();
}

/** The dimensions the crystal in that slot names, as a JSON array. Reads nothing into anything. */
private String crystalDims(zmaster587.advancedRocketry.tile.multiblock.TileObservatory scope) {
net.minecraft.item.ItemStack stack = scope.getStackInSlot(
zmaster587.advancedRocketry.tile.multiblock.TileObservatory.SLOT_CRYSTAL);
if (!zmaster587.advancedRocketry.item.ItemMemoryCrystal.isCrystal(stack)) {
return "[]";
}
StringBuilder out = new StringBuilder("[");
boolean first = true;
for (zmaster587.advancedRocketry.navigation.CrystalEntry entry
: zmaster587.advancedRocketry.item.ItemMemoryCrystal.memoryOf(stack).list()) {
if (!entry.namesBody()) {
continue;
}
if (!first) {
out.append(',');
}
out.append(entry.dimId());
first = false;
}
return out.append(']').toString();
}

private zmaster587.advancedRocketry.tile.multiblock.TileObservatory observatoryAt(
net.minecraft.world.World world, BlockPos pos) {
net.minecraft.tileentity.TileEntity te = world.getTileEntity(pos);
Expand Down Expand Up @@ -5230,7 +5300,21 @@ private void handleSpace(MinecraftServer server, ICommandSender sender, String[]
zmaster587.advancedRocketry.space.GalacticCoord.ofSectorLocal(
parseIntOr(args[1], 0), parseIntOr(args[2], 0), parseIntOr(args[3], 0),
0L, 0L, 0L);
int dimId = zmaster587.advancedRocketry.universe.PlanetRealizer.realize(server, cell);
// A cell names a family - a planet and the moons that share its address - so the probe
// states WHICH of them it means. Default 0, the planet, with an optional variant arg;
// a caller that wants the moon has to say so, exactly as a descent does.
int variant = args.length >= 5 ? parseIntOr(args[4], 0) : 0;
java.util.List<zmaster587.advancedRocketry.universe.SystemBody> family =
zmaster587.advancedRocketry.universe.UniverseRegistry.get(server) == null
? java.util.Collections.<zmaster587.advancedRocketry.universe.SystemBody>emptyList()
: zmaster587.advancedRocketry.universe.UniverseRegistry.get(server)
.realizableBodiesAt(cell);
if (variant < 0 || variant >= family.size()) {
send(sender, "{\"ok\":false,\"reason\":\"no body with that variant in the cell\"}");
return;
}
int dimId = zmaster587.advancedRocketry.universe.PlanetRealizer.realize(server,
family.get(variant));
if (dimId == zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
send(sender, "{\"ok\":false,\"reason\":\"nothing landable in that cell\"}");
return;
Expand Down Expand Up @@ -5993,6 +6077,36 @@ private void handleDim(ICommandSender sender, String[] args) {
// Planet/weather probes ----------------------------------------------

private void handlePlanet(ICommandSender sender, String[] args) {
// What a TIER-1 launch pad standing on one world may be aimed at, asked of the production
// gate rather than re-derived here: a real rocket in the standing world answers
// IPlanetDefiner.isPlanetKnown, and the two halves are reported beside it so a red test says
// WHICH of them moved - the pack's floor or what this body has learned.
if (args.length >= 3 && "knowledge".equalsIgnoreCase(args[0])) {
int standingDim = parseIntOr(args[1], Integer.MIN_VALUE);
int targetDim = parseIntOr(args[2], Integer.MIN_VALUE);
net.minecraft.world.World here = net.minecraftforge.common.DimensionManager
.getWorld(standingDim);
DimensionProperties target = DimensionManager.getInstance()
.getDimensionPropertiesOrNull(targetDim);
DimensionProperties standing = DimensionManager.getInstance()
.getDimensionPropertiesOrNull(standingDim);
if (here == null || target == null) {
send(sender, "{\"error\":\"standing world not loaded or unknown target\"}");
return;
}
zmaster587.advancedRocketry.entity.EntityRocket rocket =
new zmaster587.advancedRocketry.entity.EntityRocket(here);
send(sender, "{\"standing\":" + standingDim + ",\"target\":" + targetDim
+ ",\"known\":" + rocket.isPlanetKnown(target)
+ ",\"global\":" + DimensionManager.getInstance().isPlanetKnown(targetDim)
+ ",\"local\":" + (standing != null && standing.isPlanetKnownHere(targetDim))
+ ",\"research\":"
+ zmaster587.advancedRocketry.api.ARConfiguration.getCurrentConfig()
.planetsMustBeDiscovered
+ "}");
return;
}

if (args.length >= 2 && "info".equalsIgnoreCase(args[0])) {
int dim = parseIntOr(args[1], Integer.MIN_VALUE);
DimensionProperties props = DimensionManager.getInstance().getDimensionProperties(dim);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,11 +767,64 @@ public void setHasRings(boolean value) {
//Adds a beacon location to the planet's surface
public void addBeaconLocation(World world, HashedBlockPosition pos) {
beaconLocations.add(pos);
DimensionManager.getInstance().knownPlanets.add(getId());

//LAAZZY
if (!world.isRemote)
if (!world.isRemote) {
for (DimensionProperties taught : teachOwnSystem()) {
PacketHandler.sendToAll(new PacketDimInfo(taught.getId(), taught));
}
PacketHandler.sendToAll(new PacketDimInfo(getId(), this));
}
}

/**
* Tell the bodies of this body's own system that this place exists, and return the ones that
* did not already know.
*
* <p><b>A beacon is a local announcement, not a galactic one.</b> It used to add this planet to
* the GLOBAL known-set, so planting one anywhere made the place selectable from every launch pad
* in the game. What is true is narrower and more interesting: the neighbours know, because they
* can see it. So the beacon writes into the known-set of every body of its own system, and
* nothing outside that system learns anything.</p>
*
* <p>It can never be the thing that first reveals a place - a beacon is planted by hand, so
* somebody already flew here, which means the destination was already reachable. That is why
* scoping it costs nothing: a beacon spreads knowledge inside reach and never creates reach.</p>
*/
public List<DimensionProperties> teachOwnSystem() {
List<DimensionProperties> taught = new ArrayList<>();
discoverPlanet(getId()); // the body it stands on, always - the degenerate system of one
StellarBody star = getStar();
if (star == null) {
return taught; // a world with no star of its own: the beacon teaches only its own ground
}
for (IDimensionProperties sibling : star.getPlanets()) {
DimensionProperties props = DimensionManager.getInstance()
.getDimensionPropertiesOrNull(sibling.getId());
if (props == null) {
continue;
}
teach(props, taught);
// A moon is a child of its planet rather than of the star, so the star's list alone
// would leave every moon of the system ignorant of a beacon in it.
for (int childId : props.getChildPlanets()) {
DimensionProperties child = DimensionManager.getInstance()
.getDimensionPropertiesOrNull(childId);
if (child != null) {
teach(child, taught);
}
}
}
return taught;
}

/** Teach {@code body} about this place, collecting it when that changed anything. */
private void teach(DimensionProperties body, List<DimensionProperties> taught) {
if (body.getId() == getId() || body.isPlanetKnownHere(getId())) {
return;
}
body.discoverPlanet(getId());
taught.add(body);
}

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

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

NBTTagList list;

// Cleared first: this object is reused across loads, and a merge would make a body remember
// what a previous save taught it.
locallyKnownPlanets.clear();
for (int dimId : nbt.getIntArray("locallyKnownPlanets")) {
locallyKnownPlanets.add(dimId);
}

if (nbt.hasKey("skyColor")) {
list = nbt.getTagList("skyColor", NBT.TAG_FLOAT);
skyColor = new float[list.tagCount()];
Expand Down Expand Up @@ -2197,9 +2260,50 @@ public void write_terraforming_data(NBTTagCompound nbt) {


}
/**
* What is known ON this body: the planets a launch pad standing here may be aimed at, beyond the
* ones everybody knows.
*
* <p><b>Knowledge belongs to a place.</b> An observatory built here teaches THIS body; a beacon
* teaches the bodies of its own system; a memory crystal uploaded here deposits what somebody
* carried in. None of that reaches the global set, and none of it reaches a neighbouring world -
* a launch pad on a moon offers a different list than the pad on the planet below it.</p>
*
* <p>It is ADDITIVE over the global set rather than a replacement for it, so a pack that authors
* {@code <isKnown>} keeps authoring exactly as it did: the global set is the floor everyone
* stands on, this is what a particular world has learned since.</p>
*
* <p>Communal per world, not per player: two players on the same body see the same list.</p>
*/
private final Set<Integer> locallyKnownPlanets = new HashSet<>();

/** Teach this body about {@code dimId}. Idempotent. */
public void discoverPlanet(int dimId) {
locallyKnownPlanets.add(dimId);
}

/** Whether THIS body knows {@code dimId} - the local half of the gate, with no global fallback. */
public boolean isPlanetKnownHere(int dimId) {
return locallyKnownPlanets.contains(dimId);
}

/** What this body knows, for readers that need the whole set (GUI, sync, tests). */
public Set<Integer> getLocallyKnownPlanets() {
return Collections.unmodifiableSet(locallyKnownPlanets);
}

public void writeToNBT(NBTTagCompound nbt) {
NBTTagList list;

if (!locallyKnownPlanets.isEmpty()) {
int[] known = new int[locallyKnownPlanets.size()];
int k = 0;
for (int dimId : locallyKnownPlanets) {
known[k++] = dimId;
}
nbt.setIntArray("locallyKnownPlanets", known);
}

if (skyColor != null) {
list = new NBTTagList();
for (float f : skyColor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3852,7 +3852,20 @@ public LinkedList<IInfrastructure> getConnectedInfrastructure() {

@Override
public boolean isPlanetKnown(IDimensionProperties properties) {
return !ARConfiguration.getCurrentConfig().planetsMustBeDiscovered || DimensionManager.getInstance().isPlanetKnown(properties.getId());
if (!ARConfiguration.getCurrentConfig().planetsMustBeDiscovered) {
return true;
}
int target = properties.getId();
// The global set is the FLOOR - what a pack authored as known, plus dim 0. Everything past it
// is learned by a particular world, so the second question is asked of the body this rocket
// is standing on and not of the game.
if (DimensionManager.getInstance().isPlanetKnown(target)) {
return true;
}
DimensionProperties here = world == null
? null
: DimensionManager.getInstance().getDimensionPropertiesOrNull(world.provider.getDimension());
return here != null && here.isPlanetKnownHere(target);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,10 @@ public void update() {
// descend. The scan above must never allocate a dimension.
int targetDim = body.dimId();
if (targetDim == zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
// The BODY, not its cell: a moon shares its planet's address, so a
// cell names a family and only the body says which of them was flown to.
targetDim = zmaster587.advancedRocketry.universe.PlanetRealizer
.realize(server, body.name());
.realize(server, body);
if (targetDim
== zmaster587.advancedRocketry.api.Constants.INVALID_PLANET) {
continue; // nothing landable here after all
Expand Down
Loading