/* * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). * * Copyright (c) 2015 contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package cubicchunks.server; import com.google.common.base.Throwables; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.management.PlayerChunkMapEntry; import net.minecraft.util.math.ChunkPos; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.ChunkWatchEvent; import java.lang.invoke.MethodHandle; import java.util.List; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import cubicchunks.CubicChunks; import cubicchunks.network.PacketColumn; import cubicchunks.network.PacketDispatcher; import cubicchunks.network.PacketUnloadColumn; import cubicchunks.server.chunkio.async.forge.AsyncWorldIOExecutor; import cubicchunks.util.CubePos; import cubicchunks.util.XZAddressable; import cubicchunks.world.column.Column; import mcp.MethodsReturnNonnullByDefault; import static cubicchunks.util.ReflectionUtil.getFieldGetterHandle; import static cubicchunks.util.ReflectionUtil.getFieldSetterHandle; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class ColumnWatcher extends PlayerChunkMapEntry implements XZAddressable { private PlayerCubeMap playerCubeMap; private static MethodHandle getPlayers = getFieldGetterHandle(PlayerChunkMapEntry.class, "field_187283_c"); private static MethodHandle setLastUpdateInhabitedTime = getFieldSetterHandle(PlayerChunkMapEntry.class, "field_187289_i"); private static MethodHandle setSentToPlayers = getFieldSetterHandle(PlayerChunkMapEntry.class, "field_187290_j"); private static MethodHandle isLoading = getFieldGetterHandle(PlayerChunkMapEntry.class, "loading");//forge field, no srg name private static MethodHandle getLoadedRunnable = getFieldGetterHandle(PlayerChunkMapEntry.class, "loadedRunnable");//forge field, no srg name private final Runnable loadedRunnable; public ColumnWatcher(PlayerCubeMap playerCubeMap, ChunkPos pos) { super(playerCubeMap, pos.chunkXPos, pos.chunkZPos); this.playerCubeMap = playerCubeMap; try { this.loadedRunnable = (Runnable) getLoadedRunnable.invoke(this); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } // CHECKED: 1.10.2-12.18.1.2092 public void addPlayer(EntityPlayerMP player) { if (this.getPlayers().contains(player)) { CubicChunks.LOGGER.debug("Failed to add player. {} already is in chunk {}, {}", player, this.getPos().chunkXPos, this.getPos().chunkZPos); return; } if (this.getPlayers().isEmpty()) { this.setLastUpdateInhabitedTime(playerCubeMap.getWorldServer().getTotalWorldTime()); } this.getPlayers().add(player); //always sent to players, no need to check it if (this.isSentToPlayers()) { //this.sendNearbySpecialEntities - done by cube entry MinecraftForge.EVENT_BUS.post(new ChunkWatchEvent.Watch(this.getPos(), player)); } } // CHECKED: 1.10.2-12.18.1.2092//TODO: remove it, the only different line is sending packet public void removePlayer(EntityPlayerMP player) { if (!this.getPlayers().contains(player)) { return; } if (this.getColumn() == null) { this.getPlayers().remove(player); if (this.getPlayers().isEmpty()) { if (isLoading()) { AsyncWorldIOExecutor.dropQueuedColumnLoad( playerCubeMap.getWorld(), getPos().chunkXPos, getPos().chunkZPos, (c) -> loadedRunnable.run()); } this.playerCubeMap.removeEntry(this); } return; } if (this.isSentToPlayers()) { PacketDispatcher.sendTo(new PacketUnloadColumn(getPos()), player); } this.getPlayers().remove(player); MinecraftForge.EVENT_BUS.post(new ChunkWatchEvent.UnWatch(this.getPos(), player)); if (this.getPlayers().isEmpty()) { playerCubeMap.removeEntry(this); } } private List<EntityPlayerMP> getPlayers() { try { return (List<EntityPlayerMP>) getPlayers.invoke(this); } catch (Throwable throwable) { throw Throwables.propagate(throwable); } } private void setLastUpdateInhabitedTime(long time) { try { setLastUpdateInhabitedTime.invoke(this, time); } catch (Throwable throwable) { throw Throwables.propagate(throwable); } } //providePlayerChunk - ok // CHECKED: 1.10.2-12.18.1.2092 @Override public boolean sendToPlayers() { if (getColumn() == null) { return false; } try { PacketColumn message = new PacketColumn(this.getColumn()); for (EntityPlayerMP player : this.getPlayers()) { PacketDispatcher.sendTo(message, player); playerCubeMap.getWorldServer() .getEntityTracker() .sendLeashedEntitiesInChunk(player, this.getColumn()); } this.setSentToPlayers.invoke(this, true); } catch (Throwable throwable) { throw new RuntimeException(throwable); } return true; } @Override @Deprecated public void sendNearbySpecialEntities(EntityPlayerMP player) { //done by cube watcher } //updateChunkInhabitedTime - ok @Override @Deprecated public void blockChanged(int x, int y, int z) { this.playerCubeMap.getCubeWatcher(CubePos.fromBlockCoords(x, y, z)).blockChanged(x, y, z); } @Override public void update() { //no-op, handles by cube entries } //containsPlayer, hasPlayerMatching, hasPlayerMatchingInRange, isAddedToChunkUpdateQueue, getChunk, getClosestPlayerDistance - ok @Nullable public Column getColumn() { return (Column) this.getChunk(); } private boolean isLoading() { try { return (boolean) isLoading.invoke(this); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } @Override public int getX() { return this.getPos().chunkXPos; } @Override public int getZ() { return this.getPos().chunkZPos; } }