diff --git a/src/main/java/net/xevianlight/aphelion/Aphelion.java b/src/main/java/net/xevianlight/aphelion/Aphelion.java index 4f03efa..8feb199 100644 --- a/src/main/java/net/xevianlight/aphelion/Aphelion.java +++ b/src/main/java/net/xevianlight/aphelion/Aphelion.java @@ -62,7 +62,9 @@ public class Aphelion { NeoForge.EVENT_BUS.register(this); // 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 modContainer.registerConfig(ModConfig.Type.COMMON, AphelionConfig.SPEC); diff --git a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java index 58459be..56455d9 100644 --- a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java +++ b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java @@ -10,6 +10,8 @@ import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.client.dimension.DimensionRenderer; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; 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) public class AphelionDebugOverlay { @@ -35,10 +37,14 @@ 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)); + // Left side of F3 event.getLeft().add(""); event.getLeft().add("Aphelion:"); 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)); } } \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java b/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java new file mode 100644 index 0000000..89b7869 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java @@ -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 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; +// } +} \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRenderer.java b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRenderer.java index f7a63c5..e2c0456 100644 --- a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRenderer.java +++ b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRenderer.java @@ -18,7 +18,6 @@ public record DimensionRenderer( int sunriseColor, int sunriseAngle, boolean renderInRain, - boolean renderVoidFog, double horizonHeight, float clearColorScale ) { @@ -32,7 +31,6 @@ public record DimensionRenderer( Codec.INT.fieldOf("sunrise_color").forGetter(DimensionRenderer::sunriseColor), Codec.INT.fieldOf("sunrise_angle").forGetter(DimensionRenderer::sunriseAngle), 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.FLOAT.fieldOf("clear_color_scale").forGetter(DimensionRenderer::clearColorScale) ).apply(inst, DimensionRenderer::new)); diff --git a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRendererCache.java b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRendererCache.java index 1328acf..afd0e1e 100644 --- a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRendererCache.java +++ b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionRendererCache.java @@ -1,8 +1,10 @@ package net.xevianlight.aphelion.client.dimension; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; +import net.xevianlight.aphelion.Aphelion; import java.util.HashMap; import java.util.Map; @@ -11,6 +13,20 @@ public final class DimensionRendererCache { public static final Map 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 renderers) { RENDERERS.clear(); RENDERERS.putAll(renderers); @@ -18,6 +34,6 @@ public final class DimensionRendererCache { } public static DimensionRenderer getOrDefault(ResourceLocation id) { - return RENDERERS.getOrDefault(id, null); + return RENDERERS.getOrDefault(id, DEFAULT); } } diff --git a/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java b/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java index 5d9b24e..f893d52 100644 --- a/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java +++ b/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java @@ -1,11 +1,17 @@ package net.xevianlight.aphelion.client.dimension; import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.DimensionSpecialEffects; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.phys.Vec3; 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; public class SpaceSkyEffects extends DimensionSpecialEffects { @@ -42,6 +48,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects { @Override public boolean isFoggyAt(int i, int i1) { + ResourceLocation id = orbitForPos(net.minecraft.client.Minecraft.getInstance() .gameRenderer.getMainCamera().getPosition()); @@ -49,11 +56,19 @@ public class SpaceSkyEffects extends DimensionSpecialEffects { } 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"); - if (r < 200) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/mars"); - if (r < 300) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/venus"); + int x = SpacePartitionHelper.get(pos.x); + int z = SpacePartitionHelper.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 data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z); + if (data != null) return data; return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); } diff --git a/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java new file mode 100644 index 0000000..eec37a3 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java @@ -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 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; + }) + ) + ) + ) + ) + ); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/commands/ModCommands.java b/src/main/java/net/xevianlight/aphelion/commands/ModCommands.java new file mode 100644 index 0000000..f6fa2d0 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/commands/ModCommands.java @@ -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()); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java b/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java new file mode 100644 index 0000000..d254cd9 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java @@ -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 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; + } + +} diff --git a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java index b1904a3..e49d036 100644 --- a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java +++ b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java @@ -4,10 +4,16 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.capabilities.Capabilities; 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.block.entity.custom.ElectricArcFurnaceEntity; import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; 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) 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.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); + + } } diff --git a/src/main/java/net/xevianlight/aphelion/mixins/common/ClientLevelMixin.java b/src/main/java/net/xevianlight/aphelion/mixins/common/ClientLevelMixin.java index 54cb491..a128f70 100644 --- a/src/main/java/net/xevianlight/aphelion/mixins/common/ClientLevelMixin.java +++ b/src/main/java/net/xevianlight/aphelion/mixins/common/ClientLevelMixin.java @@ -3,12 +3,17 @@ package net.xevianlight.aphelion.mixins.common; import net.minecraft.client.Minecraft; 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.world.level.LevelHeightAccessor; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.client.PartitionClientState; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import org.spongepowered.asm.mixin.Mixin; 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.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -16,6 +21,24 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientLevel.ClientLevelData.class) 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) private void aphelion$horizonHeight(LevelHeightAccessor level, CallbackInfoReturnable cir) { var mc = Minecraft.getInstance(); @@ -23,7 +46,7 @@ public abstract class ClientLevelMixin { if (clientLevel == null) return; // effectsLocation is what your dimension JSON sets in "effects" - ResourceLocation effectsId = clientLevel.dimensionType().effectsLocation(); + ResourceLocation effectsId = aphelion$getRenderKey(); var i = DimensionRendererCache.getOrDefault(effectsId); diff --git a/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java new file mode 100644 index 0000000..20d2163 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java @@ -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 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 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); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/ServerPayloadHandler.java b/src/main/java/net/xevianlight/aphelion/network/ServerPayloadHandler.java new file mode 100644 index 0000000..06fef72 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/ServerPayloadHandler.java @@ -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()); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/packet/PartitionData.java b/src/main/java/net/xevianlight/aphelion/network/packet/PartitionData.java new file mode 100644 index 0000000..2aa51f3 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/packet/PartitionData.java @@ -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 TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, + PartitionData::id, + + PartitionData::new + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/xevianlight/aphelion/util/SpacePartitionHelper.java b/src/main/java/net/xevianlight/aphelion/util/SpacePartitionHelper.java new file mode 100644 index 0000000..5d46e3c --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/util/SpacePartitionHelper.java @@ -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); + } + +} diff --git a/src/main/resources/assets/aphelion/lang/en_us.json b/src/main/resources/assets/aphelion/lang/en_us.json index 070f454..6d8bf78 100644 --- a/src/main/resources/assets/aphelion/lang/en_us.json +++ b/src/main/resources/assets/aphelion/lang/en_us.json @@ -24,5 +24,18 @@ "creativetab.aphelion.aphelion_blocks": "Aphelion Blocks", "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" }