From 1e25490d27eaed348cb6b468abfb2738d1a3f867 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Thu, 21 Sep 2023 15:53:38 +0800 Subject: [PATCH] Add basic World Property support + SetTimePacketListener --- .../java/com/moulberry/axiom/AxiomPaper.java | 28 ++++- .../axiom/event/AxiomTimeChangeEvent.java | 56 +++++++++ .../packet/SetFlySpeedPacketListener.java | 1 - .../axiom/packet/SetTimePacketListener.java | 47 ++++++++ .../WorldPropertyCategory.java | 19 +++ .../WorldPropertyDataType.java | 111 ++++++++++++++++++ .../WorldPropertyWidgetType.java | 71 +++++++++++ .../server/ServerWorldPropertiesRegistry.java | 68 +++++++++++ .../server/ServerWorldProperty.java | 63 ++++++++++ 9 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/moulberry/axiom/event/AxiomTimeChangeEvent.java create mode 100644 src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java create mode 100644 src/main/java/com/moulberry/axiom/world_properties/WorldPropertyCategory.java create mode 100644 src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java create mode 100644 src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java create mode 100644 src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java create mode 100644 src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 0b096eb..69960b3 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -2,6 +2,7 @@ package com.moulberry.axiom; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.packet.*; +import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.papermc.paper.event.player.PlayerFailMoveEvent; @@ -18,6 +19,8 @@ import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +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; @@ -38,12 +41,14 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerOutgoingPluginChannel(this, "axiom:initialize_hotbars"); 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.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_time", new SetTimePacketListener()); 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()); @@ -97,9 +102,30 @@ public class AxiomPaper extends JavaPlugin implements Listener { @EventHandler public void onFailMove(PlayerFailMoveEvent event) { if (event.getPlayer().hasPermission("axiom.*") && - event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { + event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { event.setAllowed(true); } } + 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()); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> { + World world = player.getWorld(); + ServerWorldPropertiesRegistry properties = worldProperties.computeIfAbsent(world, k -> new ServerWorldPropertiesRegistry()); + properties.registerFor(this, player); + }, 20); // Why does this need to be delayed? + } + } diff --git a/src/main/java/com/moulberry/axiom/event/AxiomTimeChangeEvent.java b/src/main/java/com/moulberry/axiom/event/AxiomTimeChangeEvent.java new file mode 100644 index 0000000..391d3d1 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/event/AxiomTimeChangeEvent.java @@ -0,0 +1,56 @@ +package com.moulberry.axiom.event; + +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; + +public class AxiomTimeChangeEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final Player player; + private final @Nullable Integer time; + private final @Nullable Boolean freezeTime; + private boolean cancelled = false; + + public AxiomTimeChangeEvent(Player player, @Nullable Integer time, @Nullable Boolean freezeTime) { + this.player = player; + this.time = time; + this.freezeTime = freezeTime; + } + + public @Nullable Integer getTime() { + return time; + } + + public @Nullable Boolean isFreezeTime() { + return freezeTime; + } + + public Player getPlayer() { + return this.player; + } + + @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/SetFlySpeedPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java index 83b5d44..5b52e4e 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java @@ -20,7 +20,6 @@ public class SetFlySpeedPacketListener implements PluginMessageListener { return; } - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); float flySpeed = friendlyByteBuf.readFloat(); diff --git a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java new file mode 100644 index 0000000..8eda344 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java @@ -0,0 +1,47 @@ +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; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceKey; +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.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; + +public class SetTimePacketListener 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)); + ResourceKey key = friendlyByteBuf.readResourceKey(Registries.DIMENSION); // Ignore + Integer time = friendlyByteBuf.readNullable(FriendlyByteBuf::readInt); + Boolean freezeTime = friendlyByteBuf.readNullable(FriendlyByteBuf::readBoolean); + + if (time == null && freezeTime == null) return; + + ServerLevel level = ((CraftWorld)player.getWorld()).getHandle(); + if (!level.dimension().equals(key)) return; + + // Call event + AxiomTimeChangeEvent timeChangeEvent = new AxiomTimeChangeEvent(player, time, freezeTime); + Bukkit.getPluginManager().callEvent(timeChangeEvent); + if (timeChangeEvent.isCancelled()) return; + + // Change time + if (time != null) level.setDayTime(time); + if (freezeTime != null) level.getGameRules().getRule(GameRules.RULE_DAYLIGHT).set(!freezeTime, null); + } + +} diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyCategory.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyCategory.java new file mode 100644 index 0000000..62cce48 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyCategory.java @@ -0,0 +1,19 @@ +package com.moulberry.axiom.world_properties; + +import net.minecraft.network.FriendlyByteBuf; + +public record WorldPropertyCategory(String name, boolean localizeName) { + + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeUtf(this.name); + friendlyByteBuf.writeBoolean(this.localizeName); + } + + public static WorldPropertyCategory read(FriendlyByteBuf friendlyByteBuf) { + return new WorldPropertyCategory( + friendlyByteBuf.readUtf(), + friendlyByteBuf.readBoolean() + ); + } + +} diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java new file mode 100644 index 0000000..eafc3fa --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java @@ -0,0 +1,111 @@ +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.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.nio.charset.StandardCharsets; + +public abstract class WorldPropertyDataType { + + public abstract int getTypeId(); + public abstract byte[] serialize(T value); + public abstract T deserialize(byte[] bytes); + + public static WorldPropertyDataType BOOLEAN = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 0; + } + + @Override + public byte[] serialize(Boolean value) { + return new byte[] { value ? (byte)1 : (byte)0 }; + } + + @Override + public Boolean deserialize(byte[] bytes) { + return bytes[0] != 0; + } + }; + + public static WorldPropertyDataType INTEGER = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 1; + } + + @Override + public byte[] serialize(Integer value) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); + buf.writeVarInt(value); + return buf.accessByteBufWithCorrectSize(); + } + + @Override + public Integer deserialize(byte[] bytes) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)); + return buf.readVarInt(); + } + }; + + public static WorldPropertyDataType STRING = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 2; + } + + @Override + public byte[] serialize(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String deserialize(byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + }; + + public static WorldPropertyDataType ITEM = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 3; + } + + @Override + public byte[] serialize(Item value) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); + buf.writeId(BuiltInRegistries.ITEM, value); + return buf.accessByteBufWithCorrectSize(); + } + + @Override + public Item deserialize(byte[] bytes) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)); + return buf.readById(BuiltInRegistries.ITEM); + } + }; + + public static WorldPropertyDataType BLOCK = new WorldPropertyDataType<>() { + @Override + public int getTypeId() { + return 4; + } + + @Override + public byte[] serialize(Block value) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); + buf.writeId(BuiltInRegistries.BLOCK, value); + return buf.accessByteBufWithCorrectSize(); + } + + @Override + public Block deserialize(byte[] bytes) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)); + return buf.readById(BuiltInRegistries.BLOCK); + } + }; + +} diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java new file mode 100644 index 0000000..3a57f53 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java @@ -0,0 +1,71 @@ +package com.moulberry.axiom.world_properties; + +import net.minecraft.network.FriendlyByteBuf; + +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() { + return WorldPropertyDataType.BOOLEAN; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(0); + } + }; + + record Slider(int min, int max) implements WorldPropertyWidgetType { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.INTEGER; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(1); + friendlyByteBuf.writeInt(this.min); + friendlyByteBuf.writeInt(this.max); + } + } + + WorldPropertyWidgetType TEXTBOX = new WorldPropertyWidgetType<>() { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.STRING; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(2); + } + }; + + WorldPropertyWidgetType TIME = new WorldPropertyWidgetType<>() { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.INTEGER; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(3); + } + }; + +} diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java new file mode 100644 index 0000000..e353970 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java @@ -0,0 +1,68 @@ +package com.moulberry.axiom.world_properties.server; + +import com.moulberry.axiom.world_properties.WorldPropertyCategory; +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 org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ServerWorldPropertiesRegistry { + + private final LinkedHashMap>> propertyList = new LinkedHashMap<>(); + private final Map> propertyMap = new HashMap<>(); + + public ServerWorldPropertiesRegistry() { + this.registerDefault(); + } + + public ServerWorldProperty getById(ResourceLocation resourceLocation) { + return propertyMap.get(resourceLocation); + } + + public void addCategory(WorldPropertyCategory category, List> properties) { + this.propertyList.put(category, properties); + + for (ServerWorldProperty property : properties) { + ResourceLocation id = property.getId(); + if (this.propertyMap.containsKey(id)) { + throw new RuntimeException("Duplicate property: " + id); + } + this.propertyMap.put(id, property); + } + } + + public void registerFor(Plugin plugin, Player bukkitPlayer) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + + buf.writeVarInt(this.propertyList.size()); + + for (Map.Entry>> entry : this.propertyList.entrySet()) { + entry.getKey().write(buf); + buf.writeCollection(entry.getValue(), (buffer, p) -> p.write(buffer)); + } + + bukkitPlayer.sendPluginMessage(plugin, "axiom:register_world_properties", + buf.accessByteBufWithCorrectSize()); + } + + public void registerDefault() { + // Time + WorldPropertyCategory timeCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.time", true); + + ServerWorldProperty time = new ServerWorldProperty<>(new ResourceLocation("axiom:time"), + "axiom.editorui.window.world_properties.time", + true, WorldPropertyWidgetType.TIME, 0, integer -> false + ); + + this.addCategory(timeCategory, List.of(time)); + } + +} 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 new file mode 100644 index 0000000..757624e --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java @@ -0,0 +1,63 @@ +package com.moulberry.axiom.world_properties.server; + +import com.moulberry.axiom.world_properties.WorldPropertyDataType; +import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; + +import java.util.function.Predicate; + +public class ServerWorldProperty { + + private final ResourceLocation id; + private final String name; + private final boolean localizeName; + private WorldPropertyWidgetType widget; + private T value; + private Predicate handler; + + public ServerWorldProperty(ResourceLocation id, String name, boolean localizeName, WorldPropertyWidgetType widget, + T value, Predicate handler) { + this.id = id; + this.name = name; + this.localizeName = localizeName; + this.widget = widget; + this.value = value; + this.handler = handler; + } + + public ResourceLocation getId() { + return this.id; + } + + public WorldPropertyDataType getType() { + return this.widget.dataType(); + } + + public void update(ServerLevel serverLevel, 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); +// } + } + } + + public void setValue(T value) { + this.value = value; + } + + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeResourceLocation(this.id); + friendlyByteBuf.writeUtf(this.name); + friendlyByteBuf.writeBoolean(this.localizeName); + this.widget.write(friendlyByteBuf); + friendlyByteBuf.writeByteArray(this.widget.dataType().serialize(this.value)); + } + +}