1 package com.irtimaled.bbor.common;
3 import com.irtimaled.bbor.Logger;
4 import com.irtimaled.bbor.common.chunkProcessors.ChunkProcessor;
5 import com.irtimaled.bbor.common.chunkProcessors.EndChunkProcessor;
6 import com.irtimaled.bbor.common.chunkProcessors.NetherChunkProcessor;
7 import com.irtimaled.bbor.common.chunkProcessors.OverworldChunkProcessor;
8 import com.irtimaled.bbor.common.events.*;
9 import com.irtimaled.bbor.common.messages.AddBoundingBox;
10 import com.irtimaled.bbor.common.messages.InitializeClient;
11 import com.irtimaled.bbor.common.messages.RemoveBoundingBox;
12 import com.irtimaled.bbor.common.models.BoundingBox;
13 import com.irtimaled.bbor.common.models.BoundingBoxMobSpawner;
14 import com.irtimaled.bbor.common.models.BoundingBoxVillage;
15 import com.irtimaled.bbor.common.models.WorldData;
16 import com.irtimaled.bbor.config.ConfigManager;
17 import io.netty.channel.local.LocalAddress;
18 import net.minecraft.entity.player.EntityPlayerMP;
19 import net.minecraft.network.play.server.SPacketCustomPayload;
20 import net.minecraft.util.math.BlockPos;
21 import net.minecraft.village.Village;
22 import net.minecraft.world.World;
23 import net.minecraft.world.chunk.Chunk;
24 import net.minecraft.world.chunk.IChunkProvider;
25 import net.minecraft.world.dimension.DimensionType;
26 import net.minecraft.world.gen.ChunkProviderServer;
28 import java.util.HashMap;
29 import java.util.HashSet;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.function.Consumer;
35 public class CommonProxy {
36 private Set<EntityPlayerMP> players = new HashSet<>();
37 private Map<EntityPlayerMP, Set<BoundingBox>> playerBoundingBoxesCache = new HashMap<>();
38 private Map<Integer, BoundingBoxVillage> villageCache = new HashMap<>();
39 private Map<DimensionType, ChunkProcessor> chunkProcessors = new HashMap<>();
40 private WorldData worldData = null;
41 private final Map<DimensionType, BoundingBoxCache> dimensionCache = new ConcurrentHashMap<>();
44 EventBus.subscribe(WorldLoaded.class, e -> worldLoaded(e.getWorld()));
45 EventBus.subscribe(ChunkLoaded.class, e -> chunkLoaded(e.getChunk()));
46 EventBus.subscribe(MobSpawnerBroken.class, e -> mobSpawnerBroken(e.getDimensionType(), e.getPos()));
47 EventBus.subscribe(PlayerLoggedIn.class, e -> playerLoggedIn(e.getPlayer()));
48 EventBus.subscribe(PlayerLoggedOut.class, e -> playerLoggedOut(e.getPlayer()));
49 EventBus.subscribe(PlayerSubscribed.class, e -> sendBoundingBoxes(e.getPlayer()));
50 EventBus.subscribe(Tick.class, e -> tick());
51 if (ConfigManager.drawVillages.get()) {
52 EventBus.subscribe(VillageUpdated.class, e -> villageUpdated(e.getDimensionType(), e.getVillage()));
56 protected void setWorldData(long seed, int spawnX, int spawnZ) {
57 worldData = new WorldData(seed, spawnX, spawnZ);
60 private void worldLoaded(World world) {
61 IChunkProvider chunkProvider = world.getChunkProvider();
62 if (chunkProvider instanceof ChunkProviderServer) {
63 DimensionType dimensionType = world.dimension.getType();
64 BoundingBoxCache boundingBoxCache = getOrCreateCache(dimensionType);
65 ChunkProcessor chunkProcessor = null;
66 if (dimensionType == DimensionType.OVERWORLD) {
67 setWorldData(world.getSeed(), world.getWorldInfo().getSpawnX(), world.getWorldInfo().getSpawnZ());
68 chunkProcessor = new OverworldChunkProcessor(boundingBoxCache);
70 if (dimensionType == DimensionType.NETHER) {
71 chunkProcessor = new NetherChunkProcessor(boundingBoxCache);
73 if (dimensionType == DimensionType.THE_END) {
74 chunkProcessor = new EndChunkProcessor(boundingBoxCache);
76 Logger.info("create world dimension: %s, %s (seed: %d)", dimensionType, world.getClass().toString(), world.getSeed());
77 chunkProcessors.put(dimensionType, chunkProcessor);
81 private void chunkLoaded(Chunk chunk) {
82 DimensionType dimensionType = chunk.getWorld().dimension.getType();
83 ChunkProcessor chunkProcessor = chunkProcessors.get(dimensionType);
84 if (chunkProcessor != null) {
85 chunkProcessor.process(chunk);
89 private void playerLoggedIn(EntityPlayerMP player) {
90 if (player.connection.netManager.getRemoteAddress() instanceof LocalAddress) return;
91 player.connection.sendPacket(InitializeClient.getPayload(worldData));
94 private void playerLoggedOut(EntityPlayerMP player) {
95 players.remove(player);
96 playerBoundingBoxesCache.remove(player);
99 private void sendRemoveBoundingBox(DimensionType dimensionType, BoundingBox boundingBox) {
100 SPacketCustomPayload payload = RemoveBoundingBox.getPayload(dimensionType, boundingBox);
101 if (payload == null) return;
103 for (EntityPlayerMP player : players) {
104 if (DimensionType.getById(player.dimension) == dimensionType) {
105 player.connection.sendPacket(payload);
107 if (playerBoundingBoxesCache.containsKey(player)) {
108 playerBoundingBoxesCache.get(player).remove(boundingBox);
114 private void sendBoundingBoxes(EntityPlayerMP player) {
115 DimensionType dimensionType = DimensionType.getById(player.dimension);
117 sendToPlayer(player, getCache(dimensionType));
120 private void sendToPlayer(EntityPlayerMP player, BoundingBoxCache boundingBoxCache) {
121 if (boundingBoxCache == null) return;
123 Map<BoundingBox, Set<BoundingBox>> cacheSubset = getBoundingBoxMap(player, boundingBoxCache.getBoundingBoxes());
125 DimensionType dimensionType = DimensionType.getById(player.dimension);
127 for (BoundingBox key : cacheSubset.keySet()) {
128 Set<BoundingBox> boundingBoxes = cacheSubset.get(key);
129 SPacketCustomPayload payload = AddBoundingBox.getPayload(dimensionType, key, boundingBoxes);
131 player.connection.sendPacket(payload);
133 if (!playerBoundingBoxesCache.containsKey(player)) {
134 playerBoundingBoxesCache.put(player, new HashSet<>());
136 playerBoundingBoxesCache.get(player).add(key);
140 private Map<BoundingBox, Set<BoundingBox>> getBoundingBoxMap(EntityPlayerMP player, Map<BoundingBox, Set<BoundingBox>> boundingBoxMap) {
141 Map<BoundingBox, Set<BoundingBox>> cacheSubset = new HashMap<>();
142 for (BoundingBox key : boundingBoxMap.keySet()) {
143 if (!playerBoundingBoxesCache.containsKey(player) || !playerBoundingBoxesCache.get(player).contains(key)) {
144 cacheSubset.put(key, boundingBoxMap.get(key));
150 protected void removeBoundingBox(DimensionType dimensionType, BoundingBox key) {
151 BoundingBoxCache cache = getCache(dimensionType);
152 if (cache == null) return;
154 cache.removeBoundingBox(key);
157 private void mobSpawnerBroken(DimensionType dimensionType, BlockPos pos) {
158 BoundingBox boundingBox = BoundingBoxMobSpawner.from(pos);
159 removeBoundingBox(dimensionType, boundingBox);
160 sendRemoveBoundingBox(dimensionType, boundingBox);
163 private void tick() {
164 for (EntityPlayerMP player : players) {
165 DimensionType dimensionType = DimensionType.getById(player.dimension);
166 sendToPlayer(player, getCache(dimensionType));
170 private void villageUpdated(DimensionType dimensionType, Village village) {
171 BoundingBoxCache cache = getCache(dimensionType);
172 if (cache == null) return;
174 int villageId = village.hashCode();
175 BoundingBoxVillage oldVillage = villageCache.get(villageId);
176 if (oldVillage != null && !oldVillage.matches(village)) {
177 cache.removeBoundingBox(oldVillage);
178 sendRemoveBoundingBox(dimensionType, oldVillage);
181 if (village.isAnnihilated()) {
182 villageCache.remove(villageId);
184 BoundingBoxVillage newVillage = oldVillage == null ? BoundingBoxVillage.from(village) : oldVillage;
185 cache.addBoundingBox(newVillage);
186 villageCache.put(villageId, newVillage);
190 protected BoundingBoxCache getCache(DimensionType dimensionType) {
191 return dimensionCache.get(dimensionType);
194 protected BoundingBoxCache getOrCreateCache(DimensionType dimensionType) {
195 return dimensionCache.computeIfAbsent(dimensionType, dt -> new BoundingBoxCache());
198 protected void runOnCache(DimensionType dimensionType, Consumer<BoundingBoxCache> action) {
199 action.accept(getOrCreateCache(dimensionType));
202 protected void clearCaches() {
203 villageCache.clear();
204 for (BoundingBoxCache cache : dimensionCache.values()) {
207 dimensionCache.clear();