diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index d180ec6..2046acb 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -1,8 +1,13 @@ package com.moulberry.axiom; import com.moulberry.axiom.buffer.CompressedBlockEntity; +import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent; +import com.moulberry.axiom.event.AxiomTimeChangeEvent; import com.moulberry.axiom.packet.*; +import com.moulberry.axiom.world_properties.WorldPropertyCategory; +import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; +import com.moulberry.axiom.world_properties.server.ServerWorldProperty; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.papermc.paper.event.player.PlayerFailMoveEvent; @@ -15,6 +20,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; +import net.minecraft.resources.ResourceLocation; import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -24,15 +30,23 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class AxiomPaper extends JavaPlugin implements Listener { + public static AxiomPaper PLUGIN; // tsk tsk tsk + + public final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + @Override public void onEnable() { + PLUGIN = this; + Bukkit.getPluginManager().registerEvents(this, this); + // Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this); CompressedBlockEntity.initialize(this); Messenger msg = Bukkit.getMessenger(); @@ -42,13 +56,14 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerOutgoingPluginChannel(this, "axiom:set_editor_views"); msg.registerOutgoingPluginChannel(this, "axiom:block_entities"); msg.registerOutgoingPluginChannel(this, "axiom:register_world_properties"); - - final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + msg.registerOutgoingPluginChannel(this, "axiom:set_world_property"); + msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties"); msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers)); msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener()); msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener()); msg.registerIncomingPluginChannel(this, "axiom:set_world_time", new SetTimePacketListener()); + msg.registerIncomingPluginChannel(this, "axiom:set_world_property", new SetWorldPropertyListener()); msg.registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this)); msg.registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", new SetHotbarSlotPacketListener()); msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener()); @@ -99,6 +114,18 @@ public class AxiomPaper extends JavaPlugin implements Listener { }, 20, 20); } + private final WeakHashMap worldProperties = new WeakHashMap<>(); + + public @Nullable ServerWorldPropertiesRegistry getWorldProperties(World world) { + if (worldProperties.containsKey(world)) { + return worldProperties.get(world); + } else { + ServerWorldPropertiesRegistry properties = createWorldProperties(world); + worldProperties.put(world, properties); + return properties; + } + } + @EventHandler public void onFailMove(PlayerFailMoveEvent event) { if (event.getPlayer().hasPermission("axiom.*") && @@ -107,15 +134,17 @@ public class AxiomPaper extends JavaPlugin implements Listener { } } - private final WeakHashMap worldProperties = new WeakHashMap<>(); - @EventHandler public void onChangedWorld(PlayerChangedWorldEvent event) { - System.out.println("Changed world!"); - World world = event.getPlayer().getWorld(); - ServerWorldPropertiesRegistry properties = worldProperties.computeIfAbsent(world, k -> new ServerWorldPropertiesRegistry()); - properties.registerFor(this, event.getPlayer()); + + ServerWorldPropertiesRegistry properties = getWorldProperties(world); + + if (properties == null) { + event.getPlayer().sendPluginMessage(this, "axiom:register_world_properties", new byte[]{0}); + } else { + properties.registerFor(this, event.getPlayer()); + } } @EventHandler @@ -123,9 +152,25 @@ public class AxiomPaper extends JavaPlugin implements Listener { Player player = event.getPlayer(); Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> { World world = player.getWorld(); - ServerWorldPropertiesRegistry properties = worldProperties.computeIfAbsent(world, k -> new ServerWorldPropertiesRegistry()); - properties.registerFor(this, player); + + ServerWorldPropertiesRegistry properties = getWorldProperties(world); + + if (properties == null) { + player.sendPluginMessage(this, "axiom:register_world_properties", new byte[]{0}); + } else { + properties.registerFor(this, player); + } }, 20); // Why does this need to be delayed? } + private ServerWorldPropertiesRegistry createWorldProperties(World world) { + ServerWorldPropertiesRegistry registry = new ServerWorldPropertiesRegistry(); + + AxiomCreateWorldPropertiesEvent createEvent = new AxiomCreateWorldPropertiesEvent(world, registry); + Bukkit.getPluginManager().callEvent(createEvent); + if (createEvent.isCancelled()) return null; + + return registry; + } + } diff --git a/src/main/java/com/moulberry/axiom/WorldPropertiesExample.java b/src/main/java/com/moulberry/axiom/WorldPropertiesExample.java new file mode 100644 index 0000000..a9f5444 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/WorldPropertiesExample.java @@ -0,0 +1,55 @@ +package com.moulberry.axiom; + +import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent; +import com.moulberry.axiom.world_properties.WorldPropertyCategory; +import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; +import com.moulberry.axiom.world_properties.server.ServerWorldProperty; +import net.kyori.adventure.text.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Unit; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.List; + +public class WorldPropertiesExample implements Listener { + + @EventHandler + public void onCreateWorldProperties(AxiomCreateWorldPropertiesEvent event) { + WorldPropertyCategory category = new WorldPropertyCategory("Examples", false); + + World world = event.getWorld(); + + ServerWorldProperty checkbox = new ServerWorldProperty<>(new ResourceLocation("axiom:checkbox"), + "Checkbox", + false, WorldPropertyWidgetType.CHECKBOX, false, bool -> { + world.sendMessage(Component.text("Checkbox: " + bool)); // Do something with input + return true; // true to sync with client + }); + + ServerWorldProperty slider = new ServerWorldProperty<>(new ResourceLocation("axiom:slider"), + "Slider", + false, new WorldPropertyWidgetType.Slider(0, 8), 4, integer -> { + world.sendMessage(Component.text("Slider: " + integer)); // Do something with input + return true; // true to sync with client + }); + + ServerWorldProperty textbox = new ServerWorldProperty<>(new ResourceLocation("axiom:textbox"), + "Textbox", + false, WorldPropertyWidgetType.TEXTBOX, "Hello", string -> { + world.sendMessage(Component.text("Textbox: " + string)); // Do something with input + return true; // true to sync with client + }); + + ServerWorldProperty button = new ServerWorldProperty<>(new ResourceLocation("axiom:button"), + "Button", + false, WorldPropertyWidgetType.BUTTON, Unit.INSTANCE, unit -> { + world.sendMessage(Component.text("Button pressed")); // Do something with input + return true; // true to sync with client + }); + + event.addCategory(category, List.of(checkbox, slider, textbox, button)); + } + +} diff --git a/src/main/java/com/moulberry/axiom/event/AxiomCreateWorldPropertiesEvent.java b/src/main/java/com/moulberry/axiom/event/AxiomCreateWorldPropertiesEvent.java new file mode 100644 index 0000000..36cf2d9 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/event/AxiomCreateWorldPropertiesEvent.java @@ -0,0 +1,56 @@ +package com.moulberry.axiom.event; + +import com.moulberry.axiom.world_properties.WorldPropertyCategory; +import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; +import com.moulberry.axiom.world_properties.server.ServerWorldProperty; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AxiomCreateWorldPropertiesEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final World world; + private final ServerWorldPropertiesRegistry registry; + private boolean cancelled = false; + + public AxiomCreateWorldPropertiesEvent(World world, ServerWorldPropertiesRegistry registry) { + this.world = world; + this.registry = registry; + } + + public World getWorld() { + return world; + } + + public void addCategory(WorldPropertyCategory category, List> properties) { + this.registry.addCategory(category, properties); + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + +} diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index e09e520..56224d0 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -60,7 +60,6 @@ public class HelloPacketListener implements PluginMessageListener { // Enable FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeBoolean(true); - buf.writeByte(0); // todo: world properties buf.writeInt(handshakeEvent.getMaxBufferSize()); // Max Buffer Size buf.writeBoolean(false); // No source info buf.writeBoolean(false); // No source settings diff --git a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java index 8eda344..74ebb73 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java @@ -1,6 +1,5 @@ package com.moulberry.axiom.packet; -import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent; import com.moulberry.axiom.event.AxiomTimeChangeEvent; import io.netty.buffer.Unpooled; import net.minecraft.core.registries.Registries; @@ -11,7 +10,6 @@ import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -25,7 +23,7 @@ public class SetTimePacketListener implements PluginMessageListener { } FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); - ResourceKey key = friendlyByteBuf.readResourceKey(Registries.DIMENSION); // Ignore + ResourceKey key = friendlyByteBuf.readResourceKey(Registries.DIMENSION); Integer time = friendlyByteBuf.readNullable(FriendlyByteBuf::readInt); Boolean freezeTime = friendlyByteBuf.readNullable(FriendlyByteBuf::readBoolean); diff --git a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java new file mode 100644 index 0000000..d1ae1af --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java @@ -0,0 +1,53 @@ +package com.moulberry.axiom.packet; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.event.AxiomTimeChangeEvent; +import com.moulberry.axiom.world_properties.WorldPropertyCategory; +import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; +import com.moulberry.axiom.world_properties.server.ServerWorldProperty; +import io.netty.buffer.Unpooled; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +public class SetWorldPropertyListener implements PluginMessageListener { + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { + if (!player.hasPermission("axiom.*")) { + return; + } + + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); + ResourceLocation id = friendlyByteBuf.readResourceLocation(); + int type = friendlyByteBuf.readVarInt(); + byte[] data = friendlyByteBuf.readByteArray(); + int updateId = friendlyByteBuf.readVarInt(); + + ServerWorldPropertiesRegistry registry = AxiomPaper.PLUGIN.getWorldProperties(player.getWorld()); + if (registry == null) return; + + ServerWorldProperty property = registry.getById(id); + if (property != null && property.getType().getTypeId() == type) { + property.update(player.getWorld(), data); + } + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeVarInt(updateId); + player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:ack_world_properties", + buf.accessByteBufWithCorrectSize()); + } + +} diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java index eafc3fa..68e59f2 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java @@ -3,6 +3,7 @@ package com.moulberry.axiom.world_properties; import io.netty.buffer.Unpooled; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.Unit; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; @@ -108,4 +109,21 @@ public abstract class WorldPropertyDataType { } }; + public static WorldPropertyDataType EMPTY = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 5; + } + + @Override + public byte[] serialize(Unit value) { + return new byte[0]; + } + + @Override + public Unit deserialize(byte[] bytes) { + return Unit.INSTANCE; + } + }; + } diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java index 3a57f53..5ae502b 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java @@ -1,23 +1,14 @@ package com.moulberry.axiom.world_properties; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Unit; public interface WorldPropertyWidgetType { WorldPropertyDataType dataType(); void write(FriendlyByteBuf friendlyByteBuf); - static WorldPropertyWidgetType read(FriendlyByteBuf friendlyByteBuf) { - int type = friendlyByteBuf.readVarInt(); - return switch (type) { - case 0 -> CHECKBOX; - case 1 -> new Slider(friendlyByteBuf.readInt(), friendlyByteBuf.readInt()); - case 2 -> TEXTBOX; - case 3 -> TIME; - default -> throw new RuntimeException("Unknown widget type: " + type); - }; - } - WorldPropertyWidgetType CHECKBOX = new WorldPropertyWidgetType<>() { @Override public WorldPropertyDataType dataType() { @@ -68,4 +59,17 @@ public interface WorldPropertyWidgetType { } }; + + WorldPropertyWidgetType BUTTON = new WorldPropertyWidgetType<>() { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.EMPTY; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(4); + } + }; + } diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java index 757624e..cafc6df 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java @@ -1,11 +1,14 @@ package com.moulberry.axiom.world_properties.server; +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.world_properties.WorldPropertyCategory; import com.moulberry.axiom.world_properties.WorldPropertyDataType; import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; +import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; import java.util.function.Predicate; @@ -36,22 +39,37 @@ public class ServerWorldProperty { return this.widget.dataType(); } - public void update(ServerLevel serverLevel, byte[] data) { + public void update(World world, byte[] data) { this.value = this.widget.dataType().deserialize(data); if (this.handler.test(this.value)) { -// AxiomClientboundSetWorldProperty packet = new AxiomClientboundSetWorldProperty(this.id, -// this.widget.dataType().getTypeId(), this.widget.dataType().serialize(this.value)); - -// for (ServerPlayer player : serverLevel.players()) { -// if (player.hasPermissions(2)) packet.send(player); -// } + this.sync(world); } } - public void setValue(T value) { + public void setValueWithoutSyncing(T value) { this.value = value; } + public void setValue(World world, T value) { + this.value = value; + this.sync(world); + } + + public void sync(World world) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + + buf.writeResourceLocation(this.id); + buf.writeVarInt(this.widget.dataType().getTypeId()); + buf.writeByteArray(this.widget.dataType().serialize(this.value)); + + byte[] message = buf.accessByteBufWithCorrectSize(); + for (Player player : world.getPlayers()) { + if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUniqueId())) { + player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:set_world_property", message); + } + } + } + public void write(FriendlyByteBuf friendlyByteBuf) { friendlyByteBuf.writeResourceLocation(this.id); friendlyByteBuf.writeUtf(this.name);