Server side partition syncing and packets

This commit is contained in:
XevianLight
2026-01-17 13:48:58 -07:00
parent 79f3e2562e
commit 28639dae81
16 changed files with 666 additions and 11 deletions

View File

@@ -62,7 +62,9 @@ public class Aphelion {
NeoForge.EVENT_BUS.register(this); NeoForge.EVENT_BUS.register(this);
// Register the item to a creative tab // Register the item to a creative tab
modEventBus.addListener(this::addCreative); MOD_BUS.addListener(this::addCreative);
// MOD_BUS.addListener(this::registerCommands);
// Register our mod's ModConfigSpec so that FML can create and load the config file for us // Register our mod's ModConfigSpec so that FML can create and load the config file for us
modContainer.registerConfig(ModConfig.Type.COMMON, AphelionConfig.SPEC); modContainer.registerConfig(ModConfig.Type.COMMON, AphelionConfig.SPEC);

View File

@@ -10,6 +10,8 @@ import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.dimension.DimensionRenderer; import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects; import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
public class AphelionDebugOverlay { public class AphelionDebugOverlay {
@@ -35,10 +37,14 @@ public class AphelionDebugOverlay {
+ ", thickFog=" + r.hasThickFog() + ", thickFog=" + r.hasThickFog()
+ ", fog=" + r.hasFog()); + ", fog=" + r.hasFog());
int x = SpacePartitionHelper.get(Math.floor(mc.player.position().x));
int z = SpacePartitionHelper.get(Math.floor(mc.player.position().z));
// Left side of F3 // Left side of F3
event.getLeft().add(""); event.getLeft().add("");
event.getLeft().add("Aphelion:"); event.getLeft().add("Aphelion:");
event.getLeft().add(" Orbit: " + orbitId); event.getLeft().add(" Orbit: " + orbitId);
event.getLeft().add(" Sky: " + rendererSummary); // event.getLeft().add(" Sky: " + rendererSummary);
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
} }
} }

View File

@@ -0,0 +1,27 @@
package net.xevianlight.aphelion.client;
import net.xevianlight.aphelion.network.packet.PartitionData;
import java.util.Optional;
public final class PartitionClientState {
private static volatile PartitionData last = null;
public static void set(PartitionData d) { last = d; }
public static Optional<PartitionData> get() {
return Optional.ofNullable(last);
}
public static String idOrUnknown() {
return last != null ? last.id() : "unknown";
}
//
// public static int pxOr(int fallback) {
// return last != null ? last.px() : fallback;
// }
//
// public static int pyOr(int fallback) {
// return last != null ? last.py() : fallback;
// }
}

View File

@@ -18,7 +18,6 @@ public record DimensionRenderer(
int sunriseColor, int sunriseColor,
int sunriseAngle, int sunriseAngle,
boolean renderInRain, boolean renderInRain,
boolean renderVoidFog,
double horizonHeight, double horizonHeight,
float clearColorScale float clearColorScale
) { ) {
@@ -32,7 +31,6 @@ public record DimensionRenderer(
Codec.INT.fieldOf("sunrise_color").forGetter(DimensionRenderer::sunriseColor), Codec.INT.fieldOf("sunrise_color").forGetter(DimensionRenderer::sunriseColor),
Codec.INT.fieldOf("sunrise_angle").forGetter(DimensionRenderer::sunriseAngle), Codec.INT.fieldOf("sunrise_angle").forGetter(DimensionRenderer::sunriseAngle),
Codec.BOOL.fieldOf("render_in_rain").forGetter(DimensionRenderer::renderInRain), Codec.BOOL.fieldOf("render_in_rain").forGetter(DimensionRenderer::renderInRain),
Codec.BOOL.fieldOf("render_void_fog").forGetter(DimensionRenderer::renderVoidFog),
Codec.DOUBLE.fieldOf("horizon_height").forGetter(DimensionRenderer::horizonHeight), Codec.DOUBLE.fieldOf("horizon_height").forGetter(DimensionRenderer::horizonHeight),
Codec.FLOAT.fieldOf("clear_color_scale").forGetter(DimensionRenderer::clearColorScale) Codec.FLOAT.fieldOf("clear_color_scale").forGetter(DimensionRenderer::clearColorScale)
).apply(inst, DimensionRenderer::new)); ).apply(inst, DimensionRenderer::new));

View File

@@ -1,8 +1,10 @@
package net.xevianlight.aphelion.client.dimension; package net.xevianlight.aphelion.client.dimension;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.Aphelion;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -11,6 +13,20 @@ public final class DimensionRendererCache {
public static final Map<ResourceLocation, DimensionRenderer> RENDERERS = new HashMap<>(); public static final Map<ResourceLocation, DimensionRenderer> RENDERERS = new HashMap<>();
public static final DimensionRenderer DEFAULT = new DimensionRenderer(
ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space")),
false,
false,
false,
false,
true,
14180147,
0,
false,
63,
1
);
public static void registerPlanetRenderers(Map<ResourceLocation, DimensionRenderer> renderers) { public static void registerPlanetRenderers(Map<ResourceLocation, DimensionRenderer> renderers) {
RENDERERS.clear(); RENDERERS.clear();
RENDERERS.putAll(renderers); RENDERERS.putAll(renderers);
@@ -18,6 +34,6 @@ public final class DimensionRendererCache {
} }
public static DimensionRenderer getOrDefault(ResourceLocation id) { public static DimensionRenderer getOrDefault(ResourceLocation id) {
return RENDERERS.getOrDefault(id, null); return RENDERERS.getOrDefault(id, DEFAULT);
} }
} }

View File

@@ -1,11 +1,17 @@
package net.xevianlight.aphelion.client.dimension; package net.xevianlight.aphelion.client.dimension;
import net.minecraft.client.Camera; import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.DimensionSpecialEffects; import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
import org.joml.Matrix4f; import org.joml.Matrix4f;
public class SpaceSkyEffects extends DimensionSpecialEffects { public class SpaceSkyEffects extends DimensionSpecialEffects {
@@ -42,6 +48,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
@Override @Override
public boolean isFoggyAt(int i, int i1) { public boolean isFoggyAt(int i, int i1) {
ResourceLocation id = orbitForPos(net.minecraft.client.Minecraft.getInstance() ResourceLocation id = orbitForPos(net.minecraft.client.Minecraft.getInstance()
.gameRenderer.getMainCamera().getPosition()); .gameRenderer.getMainCamera().getPosition());
@@ -49,11 +56,19 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
} }
public static ResourceLocation orbitForPos(Vec3 pos) { public static ResourceLocation orbitForPos(Vec3 pos) {
double r = Math.sqrt(pos.x * pos.x + pos.z * pos.z);
if (r < 100) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/earth"); int x = SpacePartitionHelper.get(pos.x);
if (r < 200) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/mars"); int z = SpacePartitionHelper.get(pos.z);
if (r < 300) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/venus");
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 data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
if (data != null) return data;
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
} }

View File

@@ -0,0 +1,285 @@
package net.xevianlight.aphelion.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.*;
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
import java.util.EnumSet;
public class AphelionCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("aphelion")
.requires(source -> source.hasPermission(3))
.then(Commands.literal("station")
.then(Commands.literal("orbit")
.then(Commands.literal("set")
.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());
ResourceLocation orbit = ResourceLocationArgument.getId(context, "orbit");
ServerLevel level = context.getSource().getLevel();
SpacePartitionSavedData.get(level).setOrbitForPartition(x, z, orbit);
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.set", x, z, orbit.toString()),
true
);
return Command.SINGLE_SUCCESS;
})
)
)
.then(Commands.literal("all")
.then(Commands.argument("orbit", ResourceLocationArgument.id())
.executes(context -> {
ResourceLocation orbit = ResourceLocationArgument.getId(context, "orbit");
ServerLevel level = context.getSource().getLevel();
SpacePartitionSavedData.get(level).overwriteAllExistingOrbits(orbit);
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.overwriteall", orbit.toString()),
true
);
return Command.SINGLE_SUCCESS;
})
)
)
)
.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());
ServerLevel level = context.getSource().getLevel();
ResourceLocation orbit = SpacePartitionSavedData.get(level).getOrbitForPartition(x, z);
if (orbit != null) {
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.get", x, z, orbit.toString()),
true
);
} else {
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.get.unassigned", x, z),
true
);
}
return Command.SINGLE_SUCCESS;
})
)
)
.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());
ServerLevel level = context.getSource().getLevel();
boolean success = SpacePartitionSavedData.get(level).clearOrbitForPartition(x, z);
if (success) {
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.cleared", x, z),
true
);
}
return Command.SINGLE_SUCCESS;
})
)
.then(Commands.literal("all")
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
SpacePartitionSavedData.get(level).clearAllOrbits();
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.clearall"),
true
);
return Command.SINGLE_SUCCESS;
})
)
)
.then(Commands.literal("debug")
.then(Commands.literal("posToKey")
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
.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());
long key = SpacePartitionSavedData.pack(x,z);
Component clickableOutput = Component.literal(String.valueOf(key))
.withStyle(style -> style
.withClickEvent(new ClickEvent(
ClickEvent.Action.COPY_TO_CLIPBOARD,
String.valueOf(key)
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click")))
.withColor(ChatFormatting.AQUA)
);
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.debug.posToKey", x, z, clickableOutput),
true
);
return Command.SINGLE_SUCCESS;
})
)
)
.then(Commands.literal("keyToPos")
.then(Commands.argument("key", LongArgumentType.longArg(0, Long.MAX_VALUE))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
long key = LongArgumentType.getLong(context,"key");
int x = SpacePartitionSavedData.unpackX(key);
int z = SpacePartitionSavedData.unpackZ(key);
String stationCoord = x + " " + z;
Component clickableOutput = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord))
.withStyle(style -> style
.withClickEvent(new ClickEvent(
ClickEvent.Action.COPY_TO_CLIPBOARD,
stationCoord
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click")))
.withColor(ChatFormatting.GREEN)
);
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.debug.keyToPos", key, clickableOutput),
true
);
return Command.SINGLE_SUCCESS;
})
)
)
.then(Commands.literal("getPartition")
.then(Commands.argument("pos", ColumnPosArgument.columnPos())
.executes(context -> {
int x = ColumnPosArgument.getColumnPos(context, "pos").x();
int z = ColumnPosArgument.getColumnPos(context, "pos").z();
String stationCoord = SpacePartitionHelper.get(x) + " " + SpacePartitionHelper.get(z);
Component clickableOutput = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord))
.withStyle(style -> style
.withClickEvent(new ClickEvent(
ClickEvent.Action.COPY_TO_CLIPBOARD,
stationCoord
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click")))
.withColor(ChatFormatting.GREEN)
);
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.orbit.debug.getPartition", x, z, clickableOutput),
true
);
return Command.SINGLE_SUCCESS;
})))
)
)
.then(Commands.literal("tp")
.then(Commands.argument("x", IntegerArgumentType.integer())
.then(Commands.argument("z", IntegerArgumentType.integer())
.executes(context -> {
var player = context.getSource().getEntity();
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);
String stationCoord = x + ", " + z;
long key = SpacePartitionSavedData.pack((int) x, (int) z);
Component clickablePos = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord))
.withStyle(style -> style
.withClickEvent(new ClickEvent(
ClickEvent.Action.COPY_TO_CLIPBOARD,
stationCoord
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click")))
.withColor(ChatFormatting.GREEN)
);
Component clickableId = Component.literal(String.valueOf(key))
.withStyle(style -> style
.withClickEvent(new ClickEvent(
ClickEvent.Action.COPY_TO_CLIPBOARD,
String.valueOf(key)
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click")))
.withColor(ChatFormatting.AQUA)
);
ServerLevel space = player.getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space")));
if (player != null) {
player.teleportTo(space, destX, player.position().y, destZ, EnumSet.noneOf(RelativeMovement.class), player.getYRot(), player.getXRot());
context.getSource().sendSuccess(
() -> Component.translatable("aphelion.command.station.teleport.success", player.getDisplayName(), clickablePos, clickableId),
true
);
return Command.SINGLE_SUCCESS;
}
context.getSource().sendFailure(
Component.translatable("aphelion.command.station.teleport.failure")
);
return Command.SINGLE_SUCCESS;
})
)
)
)
)
);
}
}

View File

@@ -0,0 +1,15 @@
package net.xevianlight.aphelion.commands;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.xevianlight.aphelion.Aphelion;
@EventBusSubscriber(modid = Aphelion.MOD_ID)
public class ModCommands {
@SubscribeEvent
public static void onRegisterCommands(RegisterCommandsEvent event) {
AphelionCommand.register(event.getDispatcher());
}
}

View File

@@ -0,0 +1,121 @@
package net.xevianlight.aphelion.core.space;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public class SpacePartitionSavedData extends SavedData {
private static final String NAME = "aphelion_station_partitions";
private final Long2ObjectMap<ResourceLocation> map = new Long2ObjectOpenHashMap<>();
public static SpacePartitionSavedData create() {
return new SpacePartitionSavedData();
}
public static SpacePartitionSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) {
SpacePartitionSavedData data = create();
ListTag entires = tag.getList("Entries", CompoundTag.TAG_COMPOUND);
for (int i = 0; i < entires.size(); i++) {
CompoundTag e = entires.getCompound(i);
long key = e.getLong("Key");
String orbit = e.getString("Orbit"); // "aphelion/mars"
ResourceLocation orbitRL = ResourceLocation.tryParse(orbit);
if (orbitRL != null)
data.map.put(key, orbitRL);
}
return data;
}
@Override
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
ListTag entries = new ListTag();
map.long2ObjectEntrySet().forEach(entry -> {
CompoundTag e = new CompoundTag();
e.putLong("Key", entry.getLongKey());
e.putString("Orbit", entry.getValue().toString());
entries.add(e);
});
tag.put("Entries", entries);
return tag;
}
public @Nullable ResourceLocation getOrbitForPartition(int px, int pz) {
return map.get(pack(px, pz));
}
public void setOrbitForPartition(int px, int pz, ResourceLocation orbit) {
long key = pack(px, pz);
ResourceLocation prev = map.get(key);
if (!orbit.equals(prev)) {
map.put(key, orbit);
setDirty();
}
}
public boolean clearOrbitForPartition(int px, int pz) {
long key = pack(px, pz);
ResourceLocation removed = map.remove(key);
if (removed != null) {
setDirty();;
return true;
}
return false;
}
public void clearAllOrbits() {
if (!map.isEmpty()) {
map.clear();
setDirty();
}
}
public void overwriteAllExistingOrbits(ResourceLocation orbit) {
if (map.isEmpty()) return;
boolean changed = false;
for (var entry : map.long2ObjectEntrySet()) {
if(!orbit.equals(entry.getValue())) {
entry.setValue(orbit);
changed = true;
}
}
if (changed) setDirty();
}
public static SpacePartitionSavedData get(ServerLevel level) {
return level.getDataStorage().computeIfAbsent(
new Factory<>(SpacePartitionSavedData::create, SpacePartitionSavedData::load),
NAME
);
}
public static long pack(int px, int pz) {
return (((long) px) << 32) | (pz & 0xffffffffL);
}
public static int unpackX(long key) {
return (int)(key >> 32);
}
public static int unpackZ(long key) {
return (int)key;
}
}

View File

@@ -4,10 +4,16 @@ import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.handlers.ClientPayloadHandler;
import net.neoforged.neoforge.network.registration.HandlerThread;
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity; import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities; import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.network.ServerPayloadHandler;
import net.xevianlight.aphelion.network.packet.PartitionData;
@EventBusSubscriber(modid = Aphelion.MOD_ID) @EventBusSubscriber(modid = Aphelion.MOD_ID)
public class ModBusEvents { public class ModBusEvents {
@@ -17,4 +23,16 @@ public class ModBusEvents {
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::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.EnergyStorage.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getEnergyStorage);
} }
@SubscribeEvent
public static void registerPayloads(RegisterPayloadHandlersEvent event) {
final PayloadRegistrar registrar = event.registrar("1")
.executesOn(HandlerThread.MAIN);
registrar.playToClient(
PartitionData.TYPE,
PartitionData.STREAM_CODEC,
ServerPayloadHandler::handleDataOnMain);
}
} }

View File

@@ -3,12 +3,17 @@ package net.xevianlight.aphelion.mixins.common;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.LevelHeightAccessor;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -16,6 +21,24 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientLevel.ClientLevelData.class) @Mixin(ClientLevel.ClientLevelData.class)
public abstract class ClientLevelMixin { public abstract class ClientLevelMixin {
private ClientLevel this$0;
@Unique
private ResourceLocation aphelion$getRenderKey() {
var mc = Minecraft.getInstance();
ClientLevel level = mc.level;
ResourceLocation key;
if (level.dimension() == ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space"))) {
key = ResourceLocation.parse(PartitionClientState.idOrUnknown());
} else {
key = level.dimensionType().effectsLocation();
}
return key;
}
@Inject(method = "getHorizonHeight", at = @At("HEAD"), cancellable = true) @Inject(method = "getHorizonHeight", at = @At("HEAD"), cancellable = true)
private void aphelion$horizonHeight(LevelHeightAccessor level, CallbackInfoReturnable<Double> cir) { private void aphelion$horizonHeight(LevelHeightAccessor level, CallbackInfoReturnable<Double> cir) {
var mc = Minecraft.getInstance(); var mc = Minecraft.getInstance();
@@ -23,7 +46,7 @@ public abstract class ClientLevelMixin {
if (clientLevel == null) return; if (clientLevel == null) return;
// effectsLocation is what your dimension JSON sets in "effects" // effectsLocation is what your dimension JSON sets in "effects"
ResourceLocation effectsId = clientLevel.dimensionType().effectsLocation(); ResourceLocation effectsId = aphelion$getRenderKey();
var i = DimensionRendererCache.getOrDefault(effectsId); var i = DimensionRendererCache.getOrDefault(effectsId);

View File

@@ -0,0 +1,62 @@
package net.xevianlight.aphelion.network;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.network.packet.PartitionData;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@EventBusSubscriber(modid = Aphelion.MOD_ID)
public final class PartitionSync {
// send once right after join (safe: delayed to next server tick)
private static final Set<UUID> PENDING_JOIN_SEND = new HashSet<>();
@SubscribeEvent
public static void onLogin(PlayerEvent.PlayerLoggedInEvent e) {
if (e.getEntity() instanceof ServerPlayer sp) {
PENDING_JOIN_SEND.add(sp.getUUID());
}
}
private static final java.util.Map<UUID, PartitionData> LAST_SENT = new java.util.HashMap<>();
@SubscribeEvent
public static void onServerTick(ServerTickEvent.Post e) {
var server = e.getServer();
// Aphelion.LOGGER.info("WORKS!!!");
for (ServerPlayer sp : server.getPlayerList().getPlayers()) {
PartitionData now = computePartitionFor(sp); // your logic
PartitionData prev = LAST_SENT.get(sp.getUUID());
if (prev == null || !prev.equals(now)) {
PacketDistributor.sendToPlayer(sp, now);
LAST_SENT.put(sp.getUUID(), now);
}
}
}
private static PartitionData computePartitionFor(ServerPlayer sp) {
// Example: convert player position to partition coords
int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE);
int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE);
var orbit = SpacePartitionSavedData.get(sp.serverLevel()).getOrbitForPartition(px, pz);
String orbitId = (orbit != null) ? orbit.toString() : "aphelion:orbit/default";
return new PartitionData(orbitId);
}
}

View File

@@ -0,0 +1,15 @@
package net.xevianlight.aphelion.network;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.network.packet.PartitionData;
// Handle packets TO the client FROM the server
public class ServerPayloadHandler {
public static void handleDataOnMain(PartitionData data, IPayloadContext context) {
PartitionClientState.set(data);
Aphelion.LOGGER.info("Partition packet received! id={}", data.id());
}
}

View File

@@ -0,0 +1,24 @@
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;
public record PartitionData (String id) implements CustomPacketPayload {
public static final Type<PartitionData> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data"));
public static final StreamCodec<ByteBuf, PartitionData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8,
PartitionData::id,
PartitionData::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}

View File

@@ -0,0 +1,15 @@
package net.xevianlight.aphelion.util;
public class SpacePartitionHelper {
public static final int SIZE = 16;
public static int get(double pos) {
return ceilDiv(pos, SIZE);
}
private static int ceilDiv(double a, int b) {
return (int) Math.floor(a / b);
}
}

View File

@@ -24,5 +24,18 @@
"creativetab.aphelion.aphelion_blocks": "Aphelion Blocks", "creativetab.aphelion.aphelion_blocks": "Aphelion Blocks",
"tag.item.c.ingots.steel": "Steel Ingots", "tag.item.c.ingots.steel": "Steel Ingots",
"tag.item.c.ingots.aluminum": "Aluminum Ingots" "tag.item.c.ingots.aluminum": "Aluminum Ingots",
"aphelion.command.station.orbit.set": "Set station (%s, %s)'s orbit to %s",
"aphelion.command.station.orbit.get": "Station (%s, %s)'s orbit is assigned to %s",
"aphelion.command.station.orbit.get.unassigned": "Station (%s, %s)'s orbit is not assigned",
"aphelion.command.station.orbit.cleared": "Cleared station (%s, %s)'s orbit",
"aphelion.command.station.orbit.clearall": "Cleared all station orbits",
"aphelion.command.station.orbit.overwriteall": "Set all existing station orbits with %s (unassigned stations were not affected)",
"aphelion.command.station.orbit.debug.posToKey": "Key of station (%s, %s) is %s",
"aphelion.command.station.orbit.debug.keyToPos": "Key %s belongs to station %s",
"aphelion.command.station.teleport.success": "Teleported %s to station %s, id: %s",
"aphelion.command.station.teleport.failure": "Failed to teleport, entity is null",
"aphelion.command.station.orbit.debug.getPartition": "Partition of %s, %s is %s"
} }