From 8a3cce0a3a199b64cc34bdb281e7a1bdf58727f6 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 9 Sep 2023 15:19:05 +0200 Subject: [PATCH] Add RBlockServer Signed-off-by: yoyosource --- .../simulator/preview/PreviewRecord.java | 29 ++- .../bausystem/utils/RBlockServer.java | 207 ++++++++++++++++++ 2 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 BauSystem_Main/src/de/steamwar/bausystem/utils/RBlockServer.java diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/preview/PreviewRecord.java b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/preview/PreviewRecord.java index 609de031..52608793 100644 --- a/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/preview/PreviewRecord.java +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/preview/PreviewRecord.java @@ -22,10 +22,12 @@ package de.steamwar.bausystem.features.simulator.preview; import de.steamwar.bausystem.features.tracer.show.EntityShowMode; import de.steamwar.bausystem.features.tracer.show.Record; import de.steamwar.bausystem.features.tracer.show.ShowModeParameter; +import de.steamwar.bausystem.utils.RBlockServer; import lombok.Getter; import lombok.Setter; -import org.bukkit.Location; +import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -39,9 +41,10 @@ import java.util.function.Predicate; public class PreviewRecord { private static final BlockData AIR_BLOCK_DATA = Material.AIR.createBlockData(); + private static final World WORLD = Bukkit.getWorlds().get(0); - @Setter private Set destroyedBlocks; + private RBlockServer rBlockServer = new RBlockServer(); @Setter @Getter @@ -52,15 +55,20 @@ public class PreviewRecord { private Map showModeMap = new HashMap<>(); + public void setDestroyedBlocks(Set airBlocks) { + destroyedBlocks = airBlocks; + airBlocks.forEach(vector -> { + rBlockServer.setBlock(vector.toLocation(WORLD), AIR_BLOCK_DATA); + }); + } + public Set getPlayers() { return new HashSet<>(showModeMap.keySet()); } public void show(Player player) { - destroyedBlocks.forEach(vector -> { - player.sendBlockChange(vector.toLocation(player.getWorld()), AIR_BLOCK_DATA); - }); showModeMap.computeIfAbsent(player, p -> { + rBlockServer.addPlayer(p); ShowModeParameter showModeParameter = new ShowModeParameter(); showModeParameter.enableWater(); showModeParameter.enableCount(); @@ -76,12 +84,13 @@ public class PreviewRecord { EntityShowMode showMode = showModeMap.remove(player); if (showMode != null) { showMode.hide(); - destroyedBlocks.forEach(vector -> { - Location location = vector.toLocation(player.getWorld()); - player.sendBlockChange(location, location.getBlock().getBlockData()); - }); + rBlockServer.removePlayer(player); } - return showModeMap.isEmpty(); + if (showModeMap.isEmpty()) { + rBlockServer.close(); + return true; + } + return false; } public boolean has(Player player) { diff --git a/BauSystem_Main/src/de/steamwar/bausystem/utils/RBlockServer.java b/BauSystem_Main/src/de/steamwar/bausystem/utils/RBlockServer.java new file mode 100644 index 00000000..dd966c9e --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/utils/RBlockServer.java @@ -0,0 +1,207 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.utils; + +import de.steamwar.bausystem.BauSystem; +import de.steamwar.core.FlatteningWrapper; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.*; +import java.util.function.BiConsumer; + +public class RBlockServer implements Listener { + + private final HashMap> blocks = new HashMap<>(); + private final HashMap> players = new HashMap<>(); + private final HashMap lastLocation = new HashMap<>(); + private final HashMap viewDistance = new HashMap<>(); + + public RBlockServer() { + BauSystem.getInstance().getServer().getPluginManager().registerEvents(this, BauSystem.getInstance()); + } + + public void addPlayer(Player player) { + Location location = player.getLocation(); + this.lastLocation.put(player, location); + this.viewDistance.put(player, this.viewRadius(player)); + this.forChunkInView(player, location, (x, z) -> { + this.addPlayerToChunk(player, x, z); + }); + } + + public void removePlayer(Player player) { + this.forChunkInView(player, this.lastLocation.remove(player), (x, z) -> { + this.removePlayerFromChunk(player, x, z); + }); + this.viewDistance.remove(player); + } + + public void close() { + Player[] players = lastLocation.keySet().toArray(new Player[0]); + for (Player player : players) { + removePlayer(player); + } + blocks.clear(); + + HandlerList.unregisterAll(this); + } + + public void setBlock(Location location, BlockData blockData) { + long chunkId = posToId(location.getX(), location.getZ()); + if (blockData == null) { + Map chunkData = blocks.get(chunkId); + if (chunkData == null) return; + chunkData.remove(location); + if (chunkData.isEmpty()) { + blocks.remove(chunkId); + } + BlockData data = location.getBlock().getBlockData(); + players.getOrDefault(chunkId, Collections.emptySet()).forEach(player -> { + player.sendBlockChange(location, data); + }); + } else { + blocks.computeIfAbsent(chunkId, i -> new HashMap<>()).put(location, blockData); + players.getOrDefault(chunkId, Collections.emptySet()).forEach(player -> { + player.sendBlockChange(location, blockData); + }); + } + } + + @EventHandler( + ignoreCancelled = true, + priority = EventPriority.MONITOR + ) + public void onMove(PlayerMoveEvent e) { + Player player = e.getPlayer(); + Location from = this.lastLocation.get(player); + Location to = e.getTo(); + if (from != null && to != null) { + int fromX = this.posToChunk(from.getX()); + int fromZ = this.posToChunk(from.getZ()); + int toX = this.posToChunk(to.getX()); + int toZ = this.posToChunk(to.getZ()); + if (fromX != toX || fromZ != toZ) { + this.lastLocation.put(player, to); + int toViewDistance = this.viewRadius(player); + this.forChunkInView(player, from, (x, z) -> { + if (Math.abs(x - toX) > toViewDistance || Math.abs(z - toZ) > toViewDistance) { + this.removePlayerFromChunk(player, x, z); + } + + }); + this.viewDistance.put(player, toViewDistance); + this.forChunkInView(player, to, (x, z) -> { + this.addPlayerToChunk(player, x, z); + }); + } + } + } + + @EventHandler( + ignoreCancelled = true, + priority = EventPriority.MONITOR + ) + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.hasBlock()) return; + Block block = event.getClickedBlock(); + long chunkId = posToId(block.getX(), block.getZ()); + if (!blocks.containsKey(chunkId)) return; + Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { + this.addPlayerToChunk(event.getPlayer(), posToChunk(block.getX()), posToChunk(block.getZ())); + }, 1); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent e) { + Player player = e.getPlayer(); + Location location = this.lastLocation.remove(player); + if (location != null) { + this.forChunkInView(player, location, (x, z) -> { + long id = this.chunkToId(x, z); + Set playersInChunk = this.players.get(id); + playersInChunk.remove(player); + if (playersInChunk.isEmpty()) { + this.players.remove(id); + } + + }); + this.viewDistance.remove(player); + } + } + + private void forChunkInView(Player player, Location location, BiConsumer func) { + int chunkX = this.posToChunk(location.getX()); + int chunkZ = this.posToChunk(location.getZ()); + int viewDistance = this.viewDistance.get(player); + + for(int x = chunkX - viewDistance; x <= chunkX + viewDistance; ++x) { + for(int z = chunkZ - viewDistance; z <= chunkZ + viewDistance; ++z) { + func.accept(x, z); + } + } + + } + + private void addPlayerToChunk(Player player, int x, int z) { + long id = this.chunkToId(x, z); + this.players.computeIfAbsent(id, i -> new HashSet<>()).add(player); + blocks.getOrDefault(id, Collections.emptyMap()).forEach(player::sendBlockChange); + } + + private void removePlayerFromChunk(Player player, int x, int z) { + long id = this.chunkToId(x, z); + Set playersInChunk = this.players.get(id); + playersInChunk.remove(player); + if (playersInChunk.isEmpty()) { + this.players.remove(id); + } + + blocks.getOrDefault(id, Collections.emptyMap()).forEach((location, block) -> { + player.sendBlockChange(location, location.getBlock().getBlockData()); + }); + } + + private int posToChunk(double coord) { + return (int)(coord / 16.0) - (coord < 0.0 ? 1 : 0); + } + + private int viewRadius(Player player) { + return FlatteningWrapper.impl.getViewDistance(player); + } + + private long posToId(double x, double z) { + return this.chunkToId(this.posToChunk(x), this.posToChunk(z)); + } + + private long chunkToId(int x, int z) { + return ((long)x << 32) + (long)z; + } +}