Add basic World Property support + SetTimePacketListener

Dieser Commit ist enthalten in:
Moulberry 2023-09-21 15:53:38 +08:00
Ursprung 95ac82cf73
Commit 1e25490d27
9 geänderte Dateien mit 462 neuen und 2 gelöschten Zeilen

Datei anzeigen

@ -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<UUID> 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<World, ServerWorldPropertiesRegistry> 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?
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -20,7 +20,6 @@ public class SetFlySpeedPacketListener implements PluginMessageListener {
return;
}
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
float flySpeed = friendlyByteBuf.readFloat();

Datei anzeigen

@ -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<Level> 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);
}
}

Datei anzeigen

@ -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()
);
}
}

Datei anzeigen

@ -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<T> {
public abstract int getTypeId();
public abstract byte[] serialize(T value);
public abstract T deserialize(byte[] bytes);
public static WorldPropertyDataType<Boolean> 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> 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> 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> 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> 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);
}
};
}

Datei anzeigen

@ -0,0 +1,71 @@
package com.moulberry.axiom.world_properties;
import net.minecraft.network.FriendlyByteBuf;
public interface WorldPropertyWidgetType<T> {
WorldPropertyDataType<T> 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<Boolean> CHECKBOX = new WorldPropertyWidgetType<>() {
@Override
public WorldPropertyDataType<Boolean> dataType() {
return WorldPropertyDataType.BOOLEAN;
}
@Override
public void write(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeVarInt(0);
}
};
record Slider(int min, int max) implements WorldPropertyWidgetType<Integer> {
@Override
public WorldPropertyDataType<Integer> dataType() {
return WorldPropertyDataType.INTEGER;
}
@Override
public void write(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeVarInt(1);
friendlyByteBuf.writeInt(this.min);
friendlyByteBuf.writeInt(this.max);
}
}
WorldPropertyWidgetType<String> TEXTBOX = new WorldPropertyWidgetType<>() {
@Override
public WorldPropertyDataType<String> dataType() {
return WorldPropertyDataType.STRING;
}
@Override
public void write(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeVarInt(2);
}
};
WorldPropertyWidgetType<Integer> TIME = new WorldPropertyWidgetType<>() {
@Override
public WorldPropertyDataType<Integer> dataType() {
return WorldPropertyDataType.INTEGER;
}
@Override
public void write(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeVarInt(3);
}
};
}

Datei anzeigen

@ -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<WorldPropertyCategory, List<ServerWorldProperty<?>>> propertyList = new LinkedHashMap<>();
private final Map<ResourceLocation, ServerWorldProperty<?>> propertyMap = new HashMap<>();
public ServerWorldPropertiesRegistry() {
this.registerDefault();
}
public ServerWorldProperty<?> getById(ResourceLocation resourceLocation) {
return propertyMap.get(resourceLocation);
}
public void addCategory(WorldPropertyCategory category, List<ServerWorldProperty<?>> 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<WorldPropertyCategory, List<ServerWorldProperty<?>>> 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<Integer> 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));
}
}

Datei anzeigen

@ -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<T> {
private final ResourceLocation id;
private final String name;
private final boolean localizeName;
private WorldPropertyWidgetType<T> widget;
private T value;
private Predicate<T> handler;
public ServerWorldProperty(ResourceLocation id, String name, boolean localizeName, WorldPropertyWidgetType<T> widget,
T value, Predicate<T> 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<T> 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));
}
}