diff --git a/src/main/java/com/moulberry/axiom/AxiomConstants.java b/src/main/java/com/moulberry/axiom/AxiomConstants.java index f553d14..d0da771 100644 --- a/src/main/java/com/moulberry/axiom/AxiomConstants.java +++ b/src/main/java/com/moulberry/axiom/AxiomConstants.java @@ -12,7 +12,7 @@ public class AxiomConstants { } } - public static final int API_VERSION = 5; + public static final int API_VERSION = 6; public static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index"); public static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data"); diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 0b096eb..5692863 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -1,10 +1,17 @@ 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; +import io.papermc.paper.event.world.WorldGameRuleChangeEvent; import io.papermc.paper.network.ChannelInitializeListener; import io.papermc.paper.network.ChannelInitializeListenerHolder; import net.kyori.adventure.key.Key; @@ -14,22 +21,34 @@ 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 net.minecraft.world.level.GameRules; 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; +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(); @@ -38,12 +57,15 @@ 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"); - - final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + msg.registerOutgoingPluginChannel(this, "axiom:register_world_properties"); + 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()); @@ -94,12 +116,76 @@ 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.*") && - event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { + event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { event.setAllowed(true); } } + @EventHandler + public void onChangedWorld(PlayerChangedWorldEvent event) { + World world = event.getPlayer().getWorld(); + + 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 + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> { + World world = player.getWorld(); + + 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? + } + + @EventHandler + public void onGameRuleChanged(WorldGameRuleChangeEvent event) { + if (event.getGameRule() == GameRule.DO_WEATHER_CYCLE) { + ServerWorldPropertiesRegistry properties = getWorldProperties(event.getWorld()); + if (properties != null) { + ServerWorldProperty property = properties.getById(new ResourceLocation("axiom:pause_weather")); + if (property != null) { + ((ServerWorldProperty)property).setValue(event.getWorld(), !Boolean.parseBoolean(event.getValue())); + } + } + } + } + + private ServerWorldPropertiesRegistry createWorldProperties(World world) { + ServerWorldPropertiesRegistry registry = new ServerWorldPropertiesRegistry(world); + + 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/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/integration/RegionProtection.java b/src/main/java/com/moulberry/axiom/integration/RegionProtection.java index 9b95e46..cef48d9 100644 --- a/src/main/java/com/moulberry/axiom/integration/RegionProtection.java +++ b/src/main/java/com/moulberry/axiom/integration/RegionProtection.java @@ -4,6 +4,9 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.List; + public class RegionProtection { private final RegionProtectionWorldGuard worldGuard; @@ -16,8 +19,17 @@ public class RegionProtection { } } - public boolean canBuildInSection(int cx, int cy, int cz) { - if (this.worldGuard != null && !this.worldGuard.canBuildInSection(cx, cy, cz)) return false; + public SectionProtection getSection(int cx, int cy, int cz) { + List protections = new ArrayList<>(); + if (this.worldGuard != null) { + return this.worldGuard.getSection(cx, cy, cz); + } + // todo: PlotSquared + return SectionProtection.ALLOW; + } + + public boolean canBuild(int x, int y, int z) { + if (this.worldGuard != null && !this.worldGuard.canBuild(x, y, z)) return false; // todo: PlotSquared return true; } diff --git a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java b/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java index 784fb3e..8f22020 100644 --- a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java +++ b/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java @@ -1,5 +1,7 @@ package com.moulberry.axiom.integration; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.LocalPlayer; @@ -7,16 +9,21 @@ import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.association.RegionAssociable; import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; -import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.sk89q.worldguard.protection.regions.*; import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import java.util.*; + +import static com.google.common.base.Preconditions.checkNotNull; + public class RegionProtectionWorldGuard { private final LocalPlayer player; @@ -32,12 +39,15 @@ public class RegionProtectionWorldGuard { WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform(); RegionContainer regionContainer = platform.getRegionContainer(); + if (regionContainer == null) return null; + com.sk89q.worldedit.world.World worldEditWorld = BukkitAdapter.adapt(world); LocalPlayer worldGuardPlayer = WorldGuardPlugin.inst().wrapPlayer(player); // Don't do any protection if player has bypass if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldEditWorld)) { - return null; + // todo: enable bypass +// return null; } RegionManager regionManager = regionContainer.get(worldEditWorld); @@ -46,12 +56,128 @@ public class RegionProtectionWorldGuard { return new RegionProtectionWorldGuard(worldGuardPlayer, regionManager); } - public boolean canBuildInSection(int cx, int cy, int cz) { + public SectionProtection getSection(int cx, int cy, int cz) { BlockVector3 min = BlockVector3.at(cx*16, cy*16, cz*16); BlockVector3 max = BlockVector3.at(cx*16+15, cy*16+15, cz*16+15); ProtectedRegion test = new ProtectedCuboidRegion("dummy", min, max); ApplicableRegionSet regions = this.regionManager.getApplicableRegions(test, RegionQuery.QueryOption.COMPUTE_PARENTS); - return regions.testState(this.player, Flags.BUILD); + + int minimumPriority = Integer.MIN_VALUE; + + Map consideredValues = new HashMap<>(); + Set ignoredParents = new HashSet<>(); + + for (ProtectedRegion region : regions) { + int priority = FlagValueCalculator.getPriorityOf(region); + + // todo: this logic doesn't work for us in determining ALLOW, DENY, CHECK + if (priority < minimumPriority) { + break; + } + + // todo: have to keep track of 2 booleans: partialAllow & partialDeny + + if (ignoredParents.contains(region)) { + continue; + } + + StateFlag.State value = FlagValueCalculator.getEffectiveFlagOf(region, Flags.BUILD, this.player); + if (value != null) { + minimumPriority = priority; + consideredValues.put(region, value); + } + + addParents(ignoredParents, region); + + // The BUILD flag is implicitly set on every region where + // PASSTHROUGH is not set to ALLOW + if (minimumPriority != priority && Flags.BUILD.implicitlySetWithMembership() && + FlagValueCalculator.getEffectiveFlagOf(region, Flags.PASSTHROUGH, this.player) != StateFlag.State.ALLOW) { + minimumPriority = priority; + } + } + + if (consideredValues.isEmpty()) { + if (Flags.BUILD.usesMembershipAsDefault()) { + // todo +// switch (getMembership(subject)) { +// case FAIL: +// return ImmutableList.of(); +// case SUCCESS: +// return (Collection) ImmutableList.of(StateFlag.State.ALLOW); +// } + } + + System.out.println("returning default"); + StateFlag.State fallback = Flags.BUILD.getDefault(); + return fallback == StateFlag.State.DENY ? SectionProtection.DENY : SectionProtection.ALLOW; + } + + boolean hasPartialDeny = false; + for (Map.Entry entry : consideredValues.entrySet()) { + ProtectedRegion region = entry.getKey(); + if (entry.getValue() == StateFlag.State.DENY) { + System.out.println("found region with deny!"); + if (region instanceof GlobalProtectedRegion) { + return SectionProtection.DENY; + } else if (region instanceof ProtectedCuboidRegion && doesRegionCompletelyContainSection(region, cx, cy, cz)) { + return SectionProtection.DENY; + } + hasPartialDeny = true; + } + } + + if (hasPartialDeny) { + System.out.println("returning check!"); + return new SectionProtection() { + @Override + public SectionState getSectionState() { + return SectionState.CHECK; + } + + @Override + public boolean check(int wx, int wy, int wz) { + return true; + } + }; + // return complex thing + } + + System.out.println("returning allow!"); + return SectionProtection.ALLOW; + } + + private boolean doesRegionCompletelyContainSection(ProtectedRegion region, int cx, int cy, int cz) { + BlockVector3 regionMin = region.getMinimumPoint(); + + if (regionMin.getBlockX() > cx*16) return false; + if (regionMin.getBlockY() > cy*16) return false; + if (regionMin.getBlockZ() > cz*16) return false; + + BlockVector3 regionMax = region.getMaximumPoint(); + + if (regionMax.getBlockX() < cx*16+15) return false; + if (regionMax.getBlockY() < cy*16+15) return false; + if (regionMax.getBlockZ() < cz*16+15) return false; + + return true; + } + + private void addParents(Set ignored, ProtectedRegion region) { + ProtectedRegion parent = region.getParent(); + + while (parent != null) { + ignored.add(parent); + parent = parent.getParent(); + } + } + + public boolean canBuild(int x, int y, int z) { + return this.regionManager.getApplicableRegions(BlockVector3.at(x, y, z)).testState(this.player, Flags.BUILD); + } + + public boolean isAllowed(LocalPlayer player, ProtectedRegion protectedRegion) { + return protectedRegion.isOwner(player) || protectedRegion.isMember(player); } } diff --git a/src/main/java/com/moulberry/axiom/integration/SectionProtection.java b/src/main/java/com/moulberry/axiom/integration/SectionProtection.java new file mode 100644 index 0000000..07b8d60 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/SectionProtection.java @@ -0,0 +1,38 @@ +package com.moulberry.axiom.integration; + +public interface SectionProtection { + + SectionProtection ALLOW = new SectionProtection() { + @Override + public SectionState getSectionState() { + return SectionState.ALLOW; + } + + @Override + public boolean check(int wx, int wy, int wz) { + return true; + } + }; + + SectionProtection DENY = new SectionProtection() { + @Override + public SectionState getSectionState() { + return SectionState.DENY; + } + + @Override + public boolean check(int wx, int wy, int wz) { + return false; + } + }; + + enum SectionState { + ALLOW, + DENY, + CHECK + } + + SectionState getSectionState(); + boolean check(int wx, int wy, int wz); + +} 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/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 44b7479..d433834 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -6,6 +6,7 @@ import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.integration.RegionProtection; +import com.moulberry.axiom.integration.SectionProtection; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.World; @@ -38,6 +39,7 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -135,9 +137,14 @@ public class SetBlockBufferPacketListener { continue; } - if (!regionProtection.canBuildInSection(cx, cy, cz)) { - continue; - } + SectionProtection sectionProtection = regionProtection.getSection(cx, cy, cz); +// switch (sectionProtection.getSectionState()) { +// case ALLOW -> sectionProtection = null; +// case DENY -> { +// continue; +// } +// case CHECK -> {} +// } LevelChunk chunk = world.getChunk(cx, cz); chunk.setUnsaved(true); @@ -170,10 +177,20 @@ public class SetBlockBufferPacketListener { BlockState blockState = container.get(x, y, z); if (blockState == emptyState) continue; + switch (sectionProtection.getSectionState()) { + case ALLOW -> {} + case DENY -> blockState = Blocks.REDSTONE_BLOCK.defaultBlockState(); + case CHECK -> blockState = Blocks.DIAMOND_BLOCK.defaultBlockState(); + } + int bx = cx*16 + x; int by = cy*16 + y; int bz = cz*16 + z; +// if (!regionProtection.canBuild(bx, by, bz)) { +// continue; +// } + blockPos.set(bx, by, bz); if (hasOnlyAir && blockState.isAir()) { diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index f1a5c7c..a8e417d 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -8,6 +8,8 @@ import net.minecraft.core.SectionPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; @@ -16,9 +18,14 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LightEngine; +import net.minecraft.world.phys.BlockHitResult; import org.bukkit.Bukkit; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; import xyz.jpenilla.reflectionremapper.ReflectionRemapper; @@ -61,97 +68,130 @@ public class SetBlockPacketListener implements PluginMessageListener { // Read packet FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); - BlockPos blockPos = friendlyByteBuf.readBlockPos(); - BlockState blockState = friendlyByteBuf.readById(Block.BLOCK_STATE_REGISTRY); + Map blocks = friendlyByteBuf.readMap(FriendlyByteBuf::readBlockPos, buf -> buf.readById(Block.BLOCK_STATE_REGISTRY)); boolean updateNeighbors = friendlyByteBuf.readBoolean(); - int sequenceId = friendlyByteBuf.readInt(); + + int reason = friendlyByteBuf.readVarInt(); + boolean breaking = friendlyByteBuf.readBoolean(); + BlockHitResult blockHit = friendlyByteBuf.readBlockHitResult(); + InteractionHand hand = friendlyByteBuf.readEnum(InteractionHand.class); + int sequenceId = friendlyByteBuf.readVarInt(); ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + org.bukkit.inventory.ItemStack heldItem; + if (hand == InteractionHand.MAIN_HAND) { + heldItem = bukkitPlayer.getInventory().getItemInMainHand(); + } else { + heldItem = bukkitPlayer.getInventory().getItemInOffHand(); + } + + org.bukkit.block.Block blockClicked = bukkitPlayer.getWorld().getBlockAt(blockHit.getBlockPos().getX(), + blockHit.getBlockPos().getY(), blockHit.getBlockPos().getZ()); + + BlockFace blockFace = CraftBlock.notchToBlockFace(blockHit.getDirection()); + + // Call interact event + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(bukkitPlayer, + breaking ? Action.LEFT_CLICK_BLOCK : Action.RIGHT_CLICK_BLOCK, heldItem, blockClicked, blockFace); + if (!playerInteractEvent.callEvent()) { + if (sequenceId >= 0) { + player.connection.ackBlockChangesUpTo(sequenceId); + } + return; + } + // Update blocks if (updateNeighbors) { - player.level().setBlock(blockPos, blockState, 3); + for (Map.Entry entry : blocks.entrySet()) { + player.level().setBlock(entry.getKey(), entry.getValue(), 3); + } } else { - int bx = blockPos.getX(); - int by = blockPos.getY(); - int bz = blockPos.getZ(); - int x = bx & 0xF; - int y = by & 0xF; - int z = bz & 0xF; - int cx = bx >> 4; - int cy = by >> 4; - int cz = bz >> 4; + for (Map.Entry entry : blocks.entrySet()) { + BlockPos blockPos = entry.getKey(); + BlockState blockState = entry.getValue(); - ServerLevel level = player.serverLevel(); - LevelChunk chunk = level.getChunk(cx, cz); - chunk.setUnsaved(true); + int bx = blockPos.getX(); + int by = blockPos.getY(); + int bz = blockPos.getZ(); + int x = bx & 0xF; + int y = by & 0xF; + int z = bz & 0xF; + int cx = bx >> 4; + int cy = by >> 4; + int cz = bz >> 4; - LevelChunkSection section = chunk.getSection(level.getSectionIndexFromSectionY(cy)); - boolean hasOnlyAir = section.hasOnlyAir(); + ServerLevel level = player.serverLevel(); + LevelChunk chunk = level.getChunk(cx, cz); + chunk.setUnsaved(true); - Heightmap worldSurface = null; - Heightmap oceanFloor = null; - Heightmap motionBlocking = null; - Heightmap motionBlockingNoLeaves = null; - for (Map.Entry heightmap : chunk.getHeightmaps()) { - switch (heightmap.getKey()) { - case WORLD_SURFACE -> worldSurface = heightmap.getValue(); - case OCEAN_FLOOR -> oceanFloor = heightmap.getValue(); - case MOTION_BLOCKING -> motionBlocking = heightmap.getValue(); - case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue(); - default -> {} - } - } + LevelChunkSection section = chunk.getSection(level.getSectionIndexFromSectionY(cy)); + boolean hasOnlyAir = section.hasOnlyAir(); - BlockState old = section.setBlockState(x, y, z, blockState, false); - if (blockState != old) { - Block block = blockState.getBlock(); - motionBlocking.update(x, by, z, blockState); - motionBlockingNoLeaves.update(x, by, z, blockState); - oceanFloor.update(x, by, z, blockState); - worldSurface.update(x, by, z, blockState); - - if (blockState.hasBlockEntity()) { - BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); - - if (blockEntity == null) { - // There isn't a block entity here, create it! - blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); - if (blockEntity != null) { - chunk.addAndRegisterBlockEntity(blockEntity); - } - } else if (blockEntity.getType().isValid(blockState)) { - // Block entity is here and the type is correct - // Just update the state and ticker and move on - blockEntity.setBlockState(blockState); - - try { - this.updateBlockEntityTicker.invoke(chunk, blockEntity); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } else { - // Block entity type isn't correct, we need to recreate it - chunk.removeBlockEntity(blockPos); - - blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); - if (blockEntity != null) { - chunk.addAndRegisterBlockEntity(blockEntity); - } + Heightmap worldSurface = null; + Heightmap oceanFloor = null; + Heightmap motionBlocking = null; + Heightmap motionBlockingNoLeaves = null; + for (Map.Entry heightmap : chunk.getHeightmaps()) { + switch (heightmap.getKey()) { + case WORLD_SURFACE -> worldSurface = heightmap.getValue(); + case OCEAN_FLOOR -> oceanFloor = heightmap.getValue(); + case MOTION_BLOCKING -> motionBlocking = heightmap.getValue(); + case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue(); + default -> {} } - } else if (old.hasBlockEntity()) { - chunk.removeBlockEntity(blockPos); } - level.getChunkSource().blockChanged(blockPos); - if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { - level.getChunkSource().getLightEngine().checkBlock(blockPos); - } - } + BlockState old = section.setBlockState(x, y, z, blockState, false); + if (blockState != old) { + Block block = blockState.getBlock(); + motionBlocking.update(x, by, z, blockState); + motionBlockingNoLeaves.update(x, by, z, blockState); + oceanFloor.update(x, by, z, blockState); + worldSurface.update(x, by, z, blockState); - boolean nowHasOnlyAir = section.hasOnlyAir(); - if (hasOnlyAir != nowHasOnlyAir) { - level.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir); + if (blockState.hasBlockEntity()) { + BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); + + if (blockEntity == null) { + // There isn't a block entity here, create it! + blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); + if (blockEntity != null) { + chunk.addAndRegisterBlockEntity(blockEntity); + } + } else if (blockEntity.getType().isValid(blockState)) { + // Block entity is here and the type is correct + // Just update the state and ticker and move on + blockEntity.setBlockState(blockState); + + try { + this.updateBlockEntityTicker.invoke(chunk, blockEntity); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + // Block entity type isn't correct, we need to recreate it + chunk.removeBlockEntity(blockPos); + + blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); + if (blockEntity != null) { + chunk.addAndRegisterBlockEntity(blockEntity); + } + } + } else if (old.hasBlockEntity()) { + chunk.removeBlockEntity(blockPos); + } + + level.getChunkSource().blockChanged(blockPos); + if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { + level.getChunkSource().getLightEngine().checkBlock(blockPos); + } + } + + boolean nowHasOnlyAir = section.hasOnlyAir(); + if (hasOnlyAir != nowHasOnlyAir) { + level.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir); + } } } 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..74ebb73 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java @@ -0,0 +1,45 @@ +package com.moulberry.axiom.packet; + +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.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); + 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/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/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..68e59f2 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java @@ -0,0 +1,129 @@ +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; + +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); + } + }; + + 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 new file mode 100644 index 0000000..547aefb --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyWidgetType.java @@ -0,0 +1,90 @@ +package com.moulberry.axiom.world_properties; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Unit; + +import java.util.List; + +public interface WorldPropertyWidgetType { + + WorldPropertyDataType dataType(); + void write(FriendlyByteBuf friendlyByteBuf); + + 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); + } + }; + + + WorldPropertyWidgetType BUTTON = new WorldPropertyWidgetType<>() { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.EMPTY; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(4); + } + }; + + record ButtonArray(List otherButtons) implements WorldPropertyWidgetType { + @Override + public WorldPropertyDataType dataType() { + return WorldPropertyDataType.INTEGER; + } + + @Override + public void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeVarInt(5); + friendlyByteBuf.writeCollection(this.otherButtons, FriendlyByteBuf::writeUtf); + } + } + +} 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..91414d2 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java @@ -0,0 +1,102 @@ +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 net.minecraft.world.level.GameRules; +import org.bukkit.GameRule; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +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(World world) { + this.registerDefault(world); + } + + 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(World world) { + ServerLevel serverLevel = ((CraftWorld)world).getHandle(); + + // 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)); + + // Weather + WorldPropertyCategory weatherCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.weather", + true); + + ServerWorldProperty pauseWeather = new ServerWorldProperty<>(new ResourceLocation("axiom:pause_weather"), + "axiom.editorui.window.world_properties.pause_weather", + true, WorldPropertyWidgetType.CHECKBOX, !world.getGameRuleValue(GameRule.DO_WEATHER_CYCLE), bool -> { + world.setGameRule(GameRule.DO_WEATHER_CYCLE, !bool); + return false; + }); + + ServerWorldProperty weatherType = new ServerWorldProperty<>(new ResourceLocation("axiom:weather_type"), + "axiom.editorui.window.world_properties.clear_weather", + true, new WorldPropertyWidgetType.ButtonArray( + List.of("axiom.editorui.window.world_properties.rain_weather", "axiom.editorui.window.world_properties.thunder_weather") + ), 0, index -> { + if (index == 0) { + serverLevel.setWeatherParameters(ServerLevel.RAIN_DELAY.sample(serverLevel.random), 0, false, false); + } else if (index == 1) { + serverLevel.setWeatherParameters(0, ServerLevel.RAIN_DURATION.sample(serverLevel.random), true, false); + } else if (index == 2) { + serverLevel.setWeatherParameters(0, ServerLevel.THUNDER_DURATION.sample(serverLevel.random), true, true); + } + return false; + }); + + this.addCategory(weatherCategory, List.of(pauseWeather, weatherType)); + } + +} 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..5f861f7 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java @@ -0,0 +1,80 @@ +package com.moulberry.axiom.world_properties.server; + +import com.moulberry.axiom.AxiomPaper; +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 org.bukkit.World; +import org.bukkit.entity.Player; + +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(World world, byte[] data) { + this.value = this.widget.dataType().deserialize(data); + if (this.handler.test(this.value)) { + this.sync(world); + } + } + + 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); + friendlyByteBuf.writeBoolean(this.localizeName); + this.widget.write(friendlyByteBuf); + friendlyByteBuf.writeByteArray(this.widget.dataType().serialize(this.value)); + } + +}