Package org.spout.vanilla.component.entity.player

Source Code of org.spout.vanilla.component.entity.player.VanillaPlayerNetworkComponent

/*
* This file is part of Vanilla.
*
* Copyright (c) 2011 Spout LLC <http://www.spout.org/>
* Vanilla is licensed under the Spout License Version 1.
*
* Vanilla is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* In addition, 180 days after any changes are published, you can use the
* software, incorporating those changes, under the terms of the MIT license,
* as described in the Spout License Version 1.
*
* Vanilla 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 Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License,
* the MIT license and the Spout License Version 1 along with this program.
* If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
* License and see <http://spout.in/licensev1> for the full license, including
* the MIT license.
*/
package org.spout.vanilla.component.entity.player;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import gnu.trove.set.TIntSet;

import org.spout.api.Platform;
import org.spout.api.Spout;
import org.spout.api.component.BlockComponentOwner;
import org.spout.api.component.entity.PlayerNetworkComponent;
import org.spout.api.datatable.ManagedMap;
import org.spout.api.entity.Entity;
import org.spout.api.entity.Player;
import org.spout.api.event.EventHandler;
import org.spout.api.event.Listener;
import org.spout.api.event.Order;
import org.spout.api.event.ProtocolEvent;
import org.spout.api.generator.biome.Biome;
import org.spout.api.geo.LoadOption;
import org.spout.api.geo.World;
import org.spout.api.geo.cuboid.Block;
import org.spout.api.geo.cuboid.Chunk;
import org.spout.api.geo.discrete.Point;
import org.spout.api.geo.discrete.Transform;
import org.spout.api.material.BlockMaterial;
import org.spout.api.math.IntVector3;
import org.spout.api.protocol.Message;
import org.spout.api.protocol.Session;
import org.spout.api.protocol.event.BlockUpdateEvent;
import org.spout.api.protocol.event.ChunkFreeEvent;
import org.spout.api.protocol.event.ChunkSendEvent;
import org.spout.api.protocol.event.EntityUpdateEvent;
import org.spout.api.protocol.event.WorldChangeProtocolEvent;
import org.spout.api.protocol.reposition.RepositionManager;
import org.spout.api.util.FlatIterator;
import org.spout.api.util.hashing.IntPairHashed;
import org.spout.api.util.map.concurrent.TSyncIntPairObjectHashMap;
import org.spout.api.util.set.concurrent.TSyncIntHashSet;
import org.spout.api.util.set.concurrent.TSyncIntPairHashSet;

import org.spout.math.imaginary.Quaternionf;
import org.spout.math.vector.Vector3f;
import org.spout.vanilla.VanillaPlugin;
import org.spout.vanilla.component.block.material.Sign;
import org.spout.vanilla.component.entity.inventory.PlayerInventory;
import org.spout.vanilla.component.entity.living.Human;
import org.spout.vanilla.component.entity.misc.Hunger;
import org.spout.vanilla.component.entity.misc.Level;
import org.spout.vanilla.component.entity.substance.test.ForceMessages;
import org.spout.vanilla.component.world.sky.Sky;
import org.spout.vanilla.data.Difficulty;
import org.spout.vanilla.data.Dimension;
import org.spout.vanilla.data.GameMode;
import org.spout.vanilla.data.VanillaData;
import org.spout.vanilla.data.Weather;
import org.spout.vanilla.data.WorldType;
import org.spout.vanilla.data.configuration.VanillaConfiguration;
import org.spout.vanilla.data.configuration.WorldConfigurationNode;
import org.spout.vanilla.event.entity.network.EntityAnimationEvent;
import org.spout.vanilla.event.entity.network.EntityCollectItemEvent;
import org.spout.vanilla.event.entity.network.EntityEffectEvent;
import org.spout.vanilla.event.entity.network.EntityEquipmentEvent;
import org.spout.vanilla.event.entity.network.EntityMetaChangeEvent;
import org.spout.vanilla.event.entity.network.EntityRemoveEffectEvent;
import org.spout.vanilla.event.entity.network.EntityStatusEvent;
import org.spout.vanilla.event.material.network.BlockActionEvent;
import org.spout.vanilla.event.material.network.BlockBreakAnimationEvent;
import org.spout.vanilla.event.material.network.EntityTileDataEvent;
import org.spout.vanilla.event.material.network.MapItemUpdateEvent;
import org.spout.vanilla.event.material.network.SignUpdateEvent;
import org.spout.vanilla.event.player.network.ListPingEvent;
import org.spout.vanilla.event.player.network.PingEvent;
import org.spout.vanilla.event.player.network.PlayerAbilityUpdateEvent;
import org.spout.vanilla.event.player.network.PlayerBedEvent;
import org.spout.vanilla.event.player.network.PlayerExperienceChangeEvent;
import org.spout.vanilla.event.player.network.PlayerGameStateEvent;
import org.spout.vanilla.event.player.network.PlayerHealthEvent;
import org.spout.vanilla.event.player.network.PlayerSelectedSlotChangeEvent;
import org.spout.vanilla.event.scoreboard.ObjectiveActionEvent;
import org.spout.vanilla.event.scoreboard.ObjectiveDisplayEvent;
import org.spout.vanilla.event.scoreboard.ScoreUpdateEvent;
import org.spout.vanilla.event.scoreboard.TeamActionEvent;
import org.spout.vanilla.event.window.WindowCloseEvent;
import org.spout.vanilla.event.window.WindowItemsEvent;
import org.spout.vanilla.event.window.WindowOpenEvent;
import org.spout.vanilla.event.window.WindowPropertyEvent;
import org.spout.vanilla.event.window.WindowSlotEvent;
import org.spout.vanilla.event.world.PlayExplosionEffectEvent;
import org.spout.vanilla.event.world.PlayParticleEffectEvent;
import org.spout.vanilla.event.world.PlaySoundEffectEvent;
import org.spout.vanilla.event.world.TimeUpdateEvent;
import org.spout.vanilla.event.world.WeatherChangeEvent;
import org.spout.vanilla.inventory.window.DefaultWindow;
import org.spout.vanilla.material.VanillaMaterials;
import org.spout.vanilla.material.block.component.TileMaterial;
import org.spout.vanilla.protocol.EntityProtocol;
import org.spout.vanilla.protocol.VanillaNetworkProtocol;
import org.spout.vanilla.protocol.container.VanillaContainer;
import org.spout.vanilla.protocol.entity.PlayerEntityProtocol;
import org.spout.vanilla.protocol.msg.VanillaBlockDataChannelMessage;
import org.spout.vanilla.protocol.msg.entity.EntityAnimationMessage;
import org.spout.vanilla.protocol.msg.entity.EntityEquipmentMessage;
import org.spout.vanilla.protocol.msg.entity.EntityItemDataMessage;
import org.spout.vanilla.protocol.msg.entity.EntityMetadataMessage;
import org.spout.vanilla.protocol.msg.entity.EntityStatusMessage;
import org.spout.vanilla.protocol.msg.entity.EntityTileDataMessage;
import org.spout.vanilla.protocol.msg.entity.effect.EntityEffectMessage;
import org.spout.vanilla.protocol.msg.entity.effect.EntityRemoveEffectMessage;
import org.spout.vanilla.protocol.msg.player.PlayerAbilityMessage;
import org.spout.vanilla.protocol.msg.player.PlayerBedMessage;
import org.spout.vanilla.protocol.msg.player.PlayerCollectItemMessage;
import org.spout.vanilla.protocol.msg.player.PlayerExperienceMessage;
import org.spout.vanilla.protocol.msg.player.PlayerGameStateMessage;
import org.spout.vanilla.protocol.msg.player.PlayerHealthMessage;
import org.spout.vanilla.protocol.msg.player.PlayerHeldItemChangeMessage;
import org.spout.vanilla.protocol.msg.player.PlayerTimeMessage;
import org.spout.vanilla.protocol.msg.player.conn.PlayerListMessage;
import org.spout.vanilla.protocol.msg.player.conn.PlayerPingMessage;
import org.spout.vanilla.protocol.msg.player.pos.PlayerPositionLookMessage;
import org.spout.vanilla.protocol.msg.player.pos.PlayerRespawnMessage;
import org.spout.vanilla.protocol.msg.player.pos.PlayerSpawnPositionMessage;
import org.spout.vanilla.protocol.msg.scoreboard.ScoreboardDisplayMessage;
import org.spout.vanilla.protocol.msg.scoreboard.ScoreboardObjectiveMessage;
import org.spout.vanilla.protocol.msg.scoreboard.ScoreboardScoreMessage;
import org.spout.vanilla.protocol.msg.scoreboard.ScoreboardTeamMessage;
import org.spout.vanilla.protocol.msg.window.WindowCloseMessage;
import org.spout.vanilla.protocol.msg.window.WindowItemsMessage;
import org.spout.vanilla.protocol.msg.window.WindowOpenMessage;
import org.spout.vanilla.protocol.msg.window.WindowPropertyMessage;
import org.spout.vanilla.protocol.msg.window.WindowSlotMessage;
import org.spout.vanilla.protocol.msg.world.EffectMessage;
import org.spout.vanilla.protocol.msg.world.ExplosionMessage;
import org.spout.vanilla.protocol.msg.world.SoundEffectMessage;
import org.spout.vanilla.protocol.msg.world.block.BlockActionMessage;
import org.spout.vanilla.protocol.msg.world.block.BlockBreakAnimationMessage;
import org.spout.vanilla.protocol.msg.world.block.BlockChangeMessage;
import org.spout.vanilla.protocol.msg.world.block.SignMessage;
import org.spout.vanilla.protocol.msg.world.chunk.ChunkDataMessage;
import org.spout.vanilla.protocol.reposition.VanillaRepositionManager;
import org.spout.vanilla.scoreboard.Objective;
import org.spout.vanilla.scoreboard.Team;
import org.spout.vanilla.world.generator.biome.VanillaBiome;
import org.spout.vanilla.world.lighting.VanillaCuboidLightBuffer;
import org.spout.vanilla.world.lighting.VanillaLighting;

import static org.spout.vanilla.material.VanillaMaterials.getMinecraftData;
import static org.spout.vanilla.material.VanillaMaterials.getMinecraftId;

public class VanillaPlayerNetworkComponent extends PlayerNetworkComponent implements VanillaNetworkProtocol, Listener {
  private EntityProtocol protocol;

  /**
   * Returns the {@link EntityProtocol} for this type of entity
   *
   * @return The entity protocol for the specified id.
   */
  @Override
  public EntityProtocol getEntityProtocol() {
    return protocol;
  }

  /**
   * Registers the {@code protocol} as the Entity's protocol
   *
   * @param protocol The protocol to set
   */
  @Override
  public void setEntityProtocol(EntityProtocol protocol) {
    this.protocol = protocol;
  }

  private static final int SOLID_BLOCK_ID = 1; // Initializer block ID
  private static final byte[] SOLID_CHUNK_DATA = new byte[Chunk.BLOCKS.HALF_VOLUME * 5];
  private static final byte[] AIR_CHUNK_DATA = new byte[Chunk.BLOCKS.HALF_VOLUME * 5];
  private static final double STANCE = 1.62001D;
  private static final int FORCE_MASK = 0xFF; // force an update to be sent every 5 seconds
  private static final int HASH_SEED = 0xB346D76A;
  public static final int WORLD_HEIGHT = 256;
  private final TSyncIntPairObjectHashMap<TSyncIntHashSet> initChunks = new TSyncIntPairObjectHashMap<TSyncIntHashSet>();
  private final ConcurrentLinkedQueue<Long> emptyColumns = new ConcurrentLinkedQueue<Long>();
  // TODO rename -> activeColumns
  private final TSyncIntPairHashSet activeColumns = new TSyncIntPairHashSet();
  private final Object initChunkLock = new Object();
  private final ChunkInit chunkInit = ChunkInit.getChunkInit(VanillaConfiguration.CHUNK_INIT.getString("client"));
  private int minY = 0;
  private int maxY = 256;
  private int stepY = 160;
  private int offsetY = 0;
  private final VanillaRepositionManager vrm = new VanillaRepositionManager();

  static {
    int i = 0;
    for (int c = 0; c < Chunk.BLOCKS.VOLUME; c++) { // blocks
      SOLID_CHUNK_DATA[i] = SOLID_BLOCK_ID;
      AIR_CHUNK_DATA[i++] = 0;
    }
    for (int c = 0; c < Chunk.BLOCKS.HALF_VOLUME; c++) { // block data
      SOLID_CHUNK_DATA[i] = 0x00;
      AIR_CHUNK_DATA[i++] = 0x00;
    }
    for (int c = 0; c < Chunk.BLOCKS.HALF_VOLUME; c++) { // block light
      SOLID_CHUNK_DATA[i] = 0x00;
      AIR_CHUNK_DATA[i++] = 0x00;
    }
    for (int c = 0; c < Chunk.BLOCKS.HALF_VOLUME; c++) { // sky light
      SOLID_CHUNK_DATA[i] = 0x00;
      AIR_CHUNK_DATA[i++] = (byte) 0xFF;
    }
  }

  @Override
  public void onAttached() {
    super.onAttached();
    // The minimum block distance is a radius for sending chunks before login/respawn
    // It needs to be > 0 for reliable login and preventing falling through the world
    setRepositionManager(vrm);
    Spout.getEventManager().registerEvents(this, VanillaPlugin.getInstance());
  }

  @EventHandler (order = Order.MONITOR)
  public void onChunkFree(ChunkFreeEvent event) {
    freeChunk(event.getPoint());
  }

  private void freeChunk(Point p) {
    int x = (int) p.getX() >> Chunk.BLOCKS.BITS;
    int y = (int) p.getY() >> Chunk.BLOCKS.BITS; // + SEALEVEL_CHUNK;
    int z = (int) p.getZ() >> Chunk.BLOCKS.BITS;

    RepositionManager rm = getRepositionManager();

    int cY = rm.convertChunkY(y);

    if (cY < 0 || cY >= WORLD_HEIGHT >> Chunk.BLOCKS.BITS) {
      return;
    }

    TIntSet column = initChunks.get(x, z);
    if (column != null) {
      column.remove(y);
      if (column.isEmpty()) {
        emptyColumns.add(IntPairHashed.key(x, z));
      }
    }
  }

  /**
   * Resets all chunk stores for the client.  This method is only called during the pre-snapshot part of the tick.
   */
  protected void resetChunks() {
    super.resetChunks();
    this.emptyColumns.clear();
    this.activeColumns.clear();
    this.initChunks.clear();
  }

  private void initChunkRaw(Point p) {
    int x = p.getChunkX();
    int y = p.getChunkY();// + SEALEVEL_CHUNK;
    int z = p.getChunkZ();

    RepositionManager rm = getRepositionManager();

    int cY = rm.convertChunkY(y);

    if (cY < 0 || cY >= WORLD_HEIGHT >> Chunk.BLOCKS.BITS) {
      return;
    }

    TSyncIntHashSet column = initChunks.get(x, z);
    if (column == null) {
      column = new TSyncIntHashSet();
      synchronized (initChunkLock) {
        TSyncIntHashSet oldColumn = initChunks.putIfAbsent(x, z, column);
        if (oldColumn != null) {
          column = oldColumn;
        }
      }
    }
    column.add(y);
  }

  private static BlockMaterial[][] getColumnTopmostMaterials(Point p) {
    BlockMaterial[][] materials = new BlockMaterial[Chunk.BLOCKS.SIZE][Chunk.BLOCKS.SIZE];

    World w = p.getWorld();

    for (int xx = 0; xx < Chunk.BLOCKS.SIZE; xx++) {
      for (int zz = 0; zz < Chunk.BLOCKS.SIZE; zz++) {
        materials[xx][zz] = w.getTopmostBlock(p.getBlockX() + xx, p.getBlockZ() + zz, LoadOption.LOAD_GEN);
      }
    }
    return materials;
  }

  private static int[][] getColumnHeights(Point p) {
    int[][] heights = new int[Chunk.BLOCKS.SIZE][Chunk.BLOCKS.SIZE];

    World w = p.getWorld();

    for (int xx = 0; xx < Chunk.BLOCKS.SIZE; xx++) {
      for (int zz = 0; zz < Chunk.BLOCKS.SIZE; zz++) {
        heights[xx][zz] = w.getSurfaceHeight(p.getBlockX() + xx, p.getBlockZ() + zz, LoadOption.LOAD_GEN);
      }
    }
    return heights;
  }

  private static final byte[] emptySkyChunkData;
  private static final byte[] emptyGroundChunkData;

  static {
    emptySkyChunkData = new byte[Chunk.BLOCKS.HALF_VOLUME * 5];
    emptyGroundChunkData = new byte[Chunk.BLOCKS.HALF_VOLUME * 5];

    int j = Chunk.BLOCKS.VOLUME << 1;
    // Sky light = F
    for (int i = 0; i < Chunk.BLOCKS.HALF_VOLUME; i++) {
      emptySkyChunkData[j] = (byte) 0xFF;
    }
  }

  @EventHandler (order = Order.LATEST)
  public void onChunkSendLatest(ChunkSendEvent event) {
    event.getMessages().clear();
  }

  // We want some extra handling here; therefore we want to handle our own sending.
  @EventHandler (order = Order.MONITOR)
  public void onChunkSendMonitor(ChunkSendEvent event) {
    if (event.isForced() || canSendChunk(event.getChunk())) {
      doSendChunk(event.getChunk());
    }
  }

  @Override
  protected boolean canSendChunk(Chunk c) {
    if (activeColumns.contains(c.getX(), c.getZ())) {
      return true;
    }
    Collection<Chunk> chunks = chunkInit.getChunks(c, vrm);
    if (chunks == null) {
      return false;
    }
    return true;
  }

  protected Collection<Chunk> doSendChunk(Chunk c) {

    int x = c.getX();
    int y = c.getY();// + SEALEVEL_CHUNK;
    int z = c.getZ();

    RepositionManager rm = getRepositionManager();

    int cY = rm.convertChunkY(y);

    if (cY < 0 || cY >= WORLD_HEIGHT >> Chunk.BLOCKS.BITS) {
      return null;
    }

    initChunkRaw(c.getBase());

    Collection<Chunk> chunks = null;

    List<ProtocolEvent> events = new ArrayList<ProtocolEvent>();

    if (activeColumns.add(x, z)) {
      Point p = c.getBase();
      int[][] heights = getColumnHeights(p);
      BlockMaterial[][] materials = getColumnTopmostMaterials(p);

      byte[][] packetChunkData = new byte[16][];

      for (int cube = 0; cube < 16; cube++) {
        int serverCube = rm.getInverse().convertChunkY(cube);
        Point pp = new Point(c.getWorld(), x << Chunk.BLOCKS.BITS, serverCube << Chunk.BLOCKS.BITS, z << Chunk.BLOCKS.BITS);
        packetChunkData[cube] = chunkInit.getChunkData(heights, materials, pp, events);
      }

      Chunk chunk = p.getWorld().getChunkFromBlock(p, LoadOption.LOAD_ONLY);
      byte[] biomeData = new byte[Chunk.BLOCKS.AREA];
      for (int dx = x; dx < x + Chunk.BLOCKS.SIZE; ++dx) {
        for (int dz = z; dz < z + Chunk.BLOCKS.SIZE; ++dz) {
          Biome biome = chunk.getBiome(dx & Chunk.BLOCKS.MASK, 0, dz & Chunk.BLOCKS.MASK);
          if (biome instanceof VanillaBiome) {
            biomeData[(dz & Chunk.BLOCKS.MASK) << 4 | (dx & Chunk.BLOCKS.MASK)] = (byte) ((VanillaBiome) biome).getBiomeId();
          }
        }
      }

      ChunkDataMessage CCMsg = new ChunkDataMessage(x, z, true, new boolean[16], packetChunkData, biomeData, getSession(), getRepositionManager());
      getSession().send(CCMsg);

      chunks = chunkInit.getChunks(c, vrm);
    }

    if (chunks == null || !chunks.contains(c)) {

      byte[] fullChunkData = ChunkInit.getChunkFullData(c, events);

      byte[][] packetChunkData = new byte[16][];
      packetChunkData[cY] = fullChunkData;
      ChunkDataMessage CCMsg = new ChunkDataMessage(x, z, false, new boolean[16], packetChunkData, null, getSession(), getRepositionManager());
      getSession().send(CCMsg);

      if (chunks == null) {
        chunks = new ArrayList<Chunk>(1);
      }
      chunks.add(c);
    }

    for (ProtocolEvent e : events) {
      this.callProtocolEvent(e);
    }

    return chunks;
  }

  boolean first = true;

  @EventHandler (order = Order.MONITOR)
  public void onPositionSend(EntityUpdateEvent event) {
    if (event.getAction() != EntityUpdateEvent.UpdateAction.TRANSFORM || event.isFullSync() || event.getEntity() != getOwner()) {
      return;
    }
    Transform live = event.getTransform();
    if (first) {
      PlayerSpawnPositionMessage pspMsg = new PlayerSpawnPositionMessage((int) live.getPosition().getX(), (int) live.getPosition().getY(), (int) live.getPosition().getZ(), getRepositionManager());
      event.getMessages().add(pspMsg);
      first = false;
    }
    Point p = event.getTransform().getPosition();
    Quaternionf rot = event.getTransform().getRotation();
    final Vector3f axesAngles = rot.getAxesAngleDeg();
    PlayerPositionLookMessage PPLMsg = new PlayerPositionLookMessage(p.getX(), p.getY() + STANCE, p.getZ(), p.getY(), axesAngles.getY(), axesAngles.getX(), true, VanillaBlockDataChannelMessage.CHANNEL_ID, getRepositionManager());
    event.getMessages().add(PPLMsg);
  }

  @EventHandler (order = Order.LATEST)
  public void onWorldChangeLatest(WorldChangeProtocolEvent event) {
    event.getMessages().clear();
  }

  // Same as chunk sending
  @EventHandler (order = Order.MONITOR)
  public void onWorldChangeMonitor(WorldChangeProtocolEvent event) {
    worldChanged(event.getWorld());
  }

  /**
   * Sent as the initial login process if first is true, otherwise handled as a normal world change.
   */
  private void worldChanged(World world) {
    // TODO: this needs to be re-written to either be only about world-changes, or to handle first connection completely in the if statement.
    WorldConfigurationNode node = VanillaConfiguration.WORLDS.get(world);
    maxY = node.MAX_Y.getInt() & (~Chunk.BLOCKS.MASK);
    minY = node.MIN_Y.getInt() & (~Chunk.BLOCKS.MASK);
    stepY = node.STEP_Y.getInt() & (~Chunk.BLOCKS.MASK);
    lastY = Integer.MAX_VALUE;

    final ManagedMap data = world.getData();
    final Human human = getOwner().add(Human.class);
    GameMode gamemode = null;
    Difficulty difficulty = data.get(VanillaData.DIFFICULTY);
    Dimension dimension = data.get(VanillaData.DIMENSION);
    WorldType worldType = data.get(VanillaData.WORLD_TYPE);

    if (human != null) {
      gamemode = human.getGameMode();
    }
    getSession().send(new PlayerRespawnMessage(0, difficulty.getId(), gamemode.getId(), 256, worldType.toString()));
    getSession().send(new PlayerRespawnMessage(1, difficulty.getId(), gamemode.getId(), 256, worldType.toString()));
    getSession().send(new PlayerRespawnMessage(dimension.getId(), difficulty.getId(), gamemode.getId(), 256, worldType.toString()));

    if (human != null) {
      human.updateAbilities();
    }

    getSession().send(new PlayerHeldItemChangeMessage(getOwner().add(PlayerInventory.class).getQuickbar().getSelectedSlot().getIndex()));

    // Vanilla Only, MC does not send full inventory update on world change, nor on first connect
    PlayerInventory inv = getOwner().get(PlayerInventory.class);
    if (inv != null) {
      inv.updateAll();
    }

    // TODO: Send scoreboard data here
    world.get(Sky.class).updatePlayer((Player) getOwner());
    // TODO: notify all connected players of login PacketChat 'using: server'
    // TODO: Send player all nearby players
    // TODO: Handle custom texture packs
    // TODO: Send any player effects
  }

  @EventHandler
  public void onBlockUpdate(BlockUpdateEvent event) {
    BlockMaterial material = event.getChunk().getBlockMaterial(event.getX(), event.getY(), event.getZ());
    short data = event.getChunk().getBlockData(event.getX(), event.getY(), event.getZ());
    short id = getMinecraftId(material);
    int x = event.getX() + event.getChunk().getBlockX();
    int y = event.getY() + event.getChunk().getBlockY();
    int z = event.getZ() + event.getChunk().getBlockZ();
    BlockChangeMessage BCM = new BlockChangeMessage(x, y, z, id, getMinecraftData(material, data), getRepositionManager());
    getSession().send(BCM);
  }

  private int lastY = Integer.MIN_VALUE;

  @Override
  public void finalizeRun(Transform live) {
    Point currentPosition = live.getPosition();
    int y = currentPosition.getBlockY();

    if (y != lastY) {

      lastY = y;
      int cY = getRepositionManager().convertY(y);

      if (cY >= maxY || cY < minY) {
        int steps = (cY - ((maxY + minY) >> 1)) / stepY;

        offsetY -= steps * stepY;
        vrm.setOffset(offsetY);
        cY = getRepositionManager().convertY(y);

        if (cY >= maxY) {
          offsetY -= stepY;
        } else if (cY < minY) {
          offsetY += stepY;
        }

        vrm.setOffset(offsetY);
      }
    }
    super.finalizeRun(live);
  }

  @Override
  public void preSnapshotRun(Transform live) {
    super.preSnapshotRun(live);

    Long key;
    while ((key = this.emptyColumns.poll()) != null) {
      int x = IntPairHashed.key1(key);
      int z = IntPairHashed.key2(key);
      TIntSet column = initChunks.get(x, z);
      if (column != null && column.isEmpty()) {
        column = initChunks.remove(x, z);
        activeColumns.remove(x, z);
        getSession().send(new ChunkDataMessage(x, z, true, null, null, null, true, getSession(), getRepositionManager()));
      }
    }
  }

  @EventHandler
  public void syncEntity(EntityUpdateEvent event) {
    if (Spout.getPlatform() != Platform.SERVER || !event.isFullSync()) {
      return;
    }
    Entity e = event.getEntity();
    if (e != null && !(e.getNetwork() instanceof VanillaNetworkProtocol)) {
      return;
    }
    Transform liveTransform = event.getTransform();
    boolean self = (e == getOwner());
    boolean spawn = event.getAction() == EntityUpdateEvent.UpdateAction.ADD;
    boolean destroy = event.getAction() == EntityUpdateEvent.UpdateAction.REMOVE;
    boolean update = !spawn && !destroy;
    EntityProtocol ep = ((VanillaNetworkProtocol) e.getNetwork()).getEntityProtocol();
    if (ep == null) {
      if (spawn) {
        // For debugging purposes: log all entities with a missing protocol
        // Only do it for spawning - update is called too often
        Spout.getLogger().warning("Failure to spawn entity due to no EntityProtocol. Dropping entity toString...");
        Spout.getLogger().warning(e.toString());
      }
      // Do not send any messages - no protocol to do so
      return;
    }
    if (self && !(ep instanceof PlayerEntityProtocol)) {
      if (spawn) {
        // For debugging purposes: Log all Player entities that can't synchronize to self
        // Only do it for spawning - update is called too often
        Spout.getLogger().warning("Failure to spawn Player entity to self: No PlayerEntityProtocol is used. Dropping entity toString...");
        Spout.getLogger().warning(e.toString());
      }
      // Do not send any messages - no protocol to do so
      return;
    }
    if (self) {
      // Sync using vanilla Player 'self' protocol
      // Note: No messages gathered because all update methods send to the Player
      PlayerEntityProtocol pep = (PlayerEntityProtocol) ep;
      Player player = (Player) e;
      if (destroy) {
        pep.doSelfDestroy(player);
      }
      if (spawn) {
        pep.doSelfSpawn(player, getRepositionManager());
      }
      if (update) {
        boolean force = shouldForce(e.getId());
        pep.doSelfUpdate(player, liveTransform, getRepositionManager(), force);
      }
    } else {
      // Sync using vanilla protocol
      List<Message> messages = new ArrayList<Message>();
      if (destroy) {
        messages.addAll(ep.getDestroyMessages(e));
      }
      if (spawn) {
        messages.addAll(ep.getSpawnMessages(e, getRepositionManager()));
      }
      if (update) {
        boolean force = shouldForce(e.getId());
        messages.addAll(ep.getUpdateMessages(e, liveTransform, getRepositionManager(), force));
      }
      // Send all messages gathered
      for (Message message : messages) {
        getSession().send(message);
      }
    }
  }

  private boolean shouldForce(int entityId) {
    int hash = HASH_SEED;
    hash += (hash << 5) + entityId;
    hash += (hash << 5) + tickCounter;
    return (hash & FORCE_MASK) == 0 || (VanillaPlugin.getInstance().getEngine().debugMode() && getOwner().get(ForceMessages.class) != null);
  }

  @Override
  public Iterator<IntVector3> getViewableVolume(int cx, int cy, int cz, int viewDistance) {
    RepositionManager rmI = this.getRepositionManager().getInverse();

    int convertY = rmI.convertChunkY(0);

    return new FlatIterator(cx, convertY, cz, 16, viewDistance);
  }

  @Override
  public boolean isInViewVolume(Point playerChunkBase, Point testChunkBase, int viewDistance) {
    if (playerChunkBase == null) {
      return false;
    }
    int distance = Math.abs(playerChunkBase.getChunkX() - testChunkBase.getChunkX()) + Math.abs(playerChunkBase.getChunkZ() - testChunkBase.getChunkZ());
    return distance <= viewDistance;
  }

  public enum ChunkInit {
    CLIENT_SEL,
    FULL_COLUMN,
    HEIGHTMAP,
    EMPTY_COLUMN;

    public static ChunkInit getChunkInit(String init) {
      if (init == null) {
        return CLIENT_SEL;
      } else if (isEqual(init, "auto", "client", "client_sel")) {
        return CLIENT_SEL;
      } else if (isEqual(init, "full", "fullcol", "full_column")) {
        return FULL_COLUMN;
      } else if (isEqual(init, "empty", "emptycol", "empty_column")) {
        return EMPTY_COLUMN;
      } else if (isEqual(init, "heightmap", "height_map")) {
        return HEIGHTMAP;
      } else {
        Spout.getLogger().info("Invalid chunk init setting, " + init + ", using default setting auto");
        Spout.getLogger().info("Valid settings are:");
        Spout.getLogger().info("client_sel Allows client selection, defaults to full columns");
        Spout.getLogger().info("fullcol    Sends full columns");
        Spout.getLogger().info("heightmap  Sends a heightmap including the topmost block");
        Spout.getLogger().info("empty      Sends empty columns");

        return CLIENT_SEL;
      }
    }

    public Collection<Chunk> getChunks(final Chunk c, VanillaRepositionManager vpm) {
      if (this.sendColumn()) {
        final int chunkX = c.getX();
        final int chunkZ = c.getZ();
        final int height = WORLD_HEIGHT >> Chunk.BLOCKS.BITS;
        List<Chunk> chunks = new ArrayList<Chunk>(height);
        List<Vector3f> ungenerated = new ArrayList<Vector3f>(height);
        for (int y = 0; y < height; y++) {
          int chunkY = vpm.convertChunkY(y);
          Chunk cc = c.getWorld().getChunk(chunkX, chunkY, chunkZ, LoadOption.LOAD_ONLY);
          if (cc == null) {
            System.out.println("Chunk is null. Cannot send.");
            c.getRegion().getTaskManager().scheduleSyncDelayedTask(VanillaPlugin.getInstance(), new Runnable() {
              public void run() {
                for (int y = 0; y < height; y++) {
                  c.getWorld().getChunk(chunkX, y, chunkZ, LoadOption.LOAD_GEN);
                }
              }
            });
            ungenerated.add(new Vector3f(chunkX, chunkY, chunkZ));
          } else {
            chunks.add(cc);
          }
        }
        if (ungenerated.isEmpty()) {
          return chunks;
        } else {
          c.getWorld().queueChunksForGeneration(ungenerated);
          return null;
        }
      } else {
        List<Chunk> chunks = new ArrayList<Chunk>(1);
        chunks.add(c);
        return chunks;
      }
    }

    public boolean sendColumn() {
      return this == CLIENT_SEL || this == FULL_COLUMN;
    }

    public byte[] getChunkData(int[][] heights, BlockMaterial[][] materials, Point p, List<ProtocolEvent> updateEvents) {
      switch (this) {
        case CLIENT_SEL:
          return getChunkFullColumn(heights, materials, p, updateEvents);
        case FULL_COLUMN:
          return getChunkFullColumn(heights, materials, p, updateEvents);
        case HEIGHTMAP:
          return getChunkHeightMap(heights, materials, p);
        case EMPTY_COLUMN:
          return getEmptyChunk(heights, materials, p);
        default:
          return getChunkFullColumn(heights, materials, p, updateEvents);
      }
    }

    private static byte[] getChunkFullColumn(int[][] heights, BlockMaterial[][] materials, Point p, List<ProtocolEvent> updateEvents) {
      Chunk c = p.getWorld().getChunkFromBlock(p, LoadOption.LOAD_ONLY);
      return getChunkFullData(c, updateEvents);
    }

    public static byte[] getChunkFullData(Chunk c, List<ProtocolEvent> updateEvents) {
      VanillaContainer container = new VanillaContainer();
      c.fillBlockContainer(container);
      c.fillBlockComponentContainer(container);

      VanillaCuboidLightBuffer blockLight = (VanillaCuboidLightBuffer) c.getLightBuffer(VanillaLighting.BLOCK_LIGHT.getId());
      VanillaCuboidLightBuffer skyLight = (VanillaCuboidLightBuffer) c.getLightBuffer(VanillaLighting.SKY_LIGHT.getId());

      container.copyLight(true, blockLight);
      container.copyLight(false, skyLight);

      int[] componentX = container.getXArray();
      int[] componentY = container.getYArray();
      int[] componentZ = container.getZArray();

      for (int i = 0; i < container.getBlockComponentCount(); i++) {
        BlockMaterial bm = c.getBlockMaterial(componentX[i], componentY[i], componentZ[i]);
        BlockComponentOwner owner = container.getBlockComponent()[i];
        if (bm instanceof TileMaterial) {
          ProtocolEvent event = ((TileMaterial) bm).getUpdate(c.getWorld(), componentX[i], componentY[i], componentZ[i], owner);
          if (event != null) {
            updateEvents.add(event);
          }
        }
      }

      return container.getChunkFullData();
    }

    private static byte[] getEmptyChunk(int[][] heights, BlockMaterial[][] materials, Point p) {
      int chunkY = p.getChunkY();
      return chunkY <= 4 ? emptyGroundChunkData : emptySkyChunkData;
    }

    private static byte[] getChunkHeightMap(int[][] heights, BlockMaterial[][] materials, Point p) {
      int chunkY = p.getChunkY();
      byte[] packetChunkData = new byte[Chunk.BLOCKS.HALF_VOLUME * 5];
      int baseY = chunkY << Chunk.BLOCKS.BITS;

      for (int xx = 0; xx < Chunk.BLOCKS.SIZE; xx++) {
        for (int zz = 0; zz < Chunk.BLOCKS.SIZE; zz++) {
          int dataOffset = xx | (zz << Chunk.BLOCKS.BITS);
          int threshold = heights[xx][zz] - baseY;
          if (chunkY == 0 && threshold < 0) {
            threshold = 0;
          }
          int yy;
          // Set blocks below height to the solid block
          for (yy = 0; yy < Chunk.BLOCKS.SIZE && yy <= threshold; yy++) {
            if (yy == threshold) {
              BlockMaterial bm = materials[xx][zz];
              if (bm == null) {
                bm = VanillaMaterials.STONE;
              }
              int converted = getMinecraftId(bm.getId());
              packetChunkData[dataOffset] = (byte) converted;
            } else {
              packetChunkData[dataOffset] = SOLID_BLOCK_ID;
            }
            dataOffset += Chunk.BLOCKS.AREA;
          }
          // Set sky light of blocks above height to 15
          // Use half of start offset and add the block id and data length (2 volumes)
          byte mask = (xx & 0x1) == 0 ? (byte) 0x0F : (byte) 0xF0;
          dataOffset = Chunk.BLOCKS.DOUBLE_VOLUME + (dataOffset >> 1);
          for (; yy < Chunk.BLOCKS.SIZE; yy++) {
            packetChunkData[dataOffset] |= mask;
            dataOffset += Chunk.BLOCKS.HALF_AREA;
          }
        }
      }
      return packetChunkData;
    }

    private static boolean isEqual(String in, String... args) {

      if (in == null) {
        return false;
      }
      for (String arg : args) {
        if (arg.toLowerCase().equals(in.toLowerCase())) {
          return true;
        }
      }
      return false;
    }
  }

  @EventHandler
  public void onEntityTileData(EntityTileDataEvent event) {
    Block b = event.getBlock();
    event.getMessages().add(new EntityTileDataMessage(b.getX(), b.getY(), b.getZ(), event.getAction(), event.getData(), getRepositionManager()));
  }

  @EventHandler
  public void onMapItemUpdate(MapItemUpdateEvent event) {
    event.getMessages().add(new EntityItemDataMessage(VanillaMaterials.MAP, (short) event.getItemData(), event.getData()));
  }

  @EventHandler
  public void onPlayerAbilityUpdate(PlayerAbilityUpdateEvent event) {
    event.getMessages().add(new PlayerAbilityMessage(event.getGodMode(), event.isFlying(), event.canFly(), event.isCreativeMode(), event.getFlyingSpeed(), event.getWalkingSpeed()));
  }

  @EventHandler
  public void onEntityEquipment(EntityEquipmentEvent event) {
    event.getMessages().add(new EntityEquipmentMessage(event.getEntity().getId(), event.getSlot(), event.getItem()));
  }

  @EventHandler
  public void onWindowOpen(WindowOpenEvent event) {
    if (event.getWindow() instanceof DefaultWindow) {
      return;
    }
    PlayerInventory inventory = event.getWindow().getPlayerInventory();
    int size = event.getWindow().getSize() - (inventory.getMain().size() + inventory.getQuickbar().size());
    event.getMessages().add(new WindowOpenMessage(event.getWindow(), size));
  }

  @EventHandler
  public void onWindowClose(WindowCloseEvent event) {
    if (event.getWindow() instanceof DefaultWindow) {
      return;
    }
    event.getMessages().add(new WindowCloseMessage(event.getWindow()));
  }

  @EventHandler
  public void onWindowSetSlot(WindowSlotEvent event) {
    //TODO: investigate why this happens (12-1-2013)
    if (event.getItem() != null && event.getItem().getMaterial() == BlockMaterial.AIR) {
      return;
    }
    event.getMessages().add(new WindowSlotMessage(event.getWindow(), event.getSlot(), event.getItem()));
  }

  @EventHandler
  public void onWindowItems(WindowItemsEvent event) {
    event.getMessages().add(new WindowItemsMessage(event.getWindow(), event.getItems()));
  }

  @EventHandler
  public void onWindowProperty(WindowPropertyEvent event) {
    event.getMessages().add(new WindowPropertyMessage(event.getWindow(), event.getId(), event.getValue()));
  }

  @EventHandler
  public void onSoundEffect(PlaySoundEffectEvent event) {
    event.getMessages().add(new SoundEffectMessage(event.getSound().getName(), event.getPosition(), event.getVolume(), event.getPitch(), getRepositionManager()));
  }

  @EventHandler
  public void onExplosionEffect(PlayExplosionEffectEvent event) {
    event.getMessages().add(new ExplosionMessage(event.getPosition(), event.getSize(), new byte[0], getRepositionManager()));
  }

  @EventHandler
  public void onParticleEffect(PlayParticleEffectEvent event) {
    int x = event.getPosition().getBlockX();
    int y = event.getPosition().getBlockY();
    int z = event.getPosition().getBlockZ();
    event.getMessages().add(new EffectMessage(event.getEffect().getId(), x, y, z, event.getData(), getRepositionManager()));
  }

  @EventHandler
  public void onBlockAction(BlockActionEvent event) {
    int id = VanillaMaterials.getMinecraftId(event.getMaterial());
    if (id == -1) {
      event.getMessages().add(null);
    } else {
      event.getMessages().add(new BlockActionMessage(event.getBlock(), (short) id, event.getData1(), event.getData2(), getRepositionManager()));
    }
  }

  @EventHandler
  public void onPlayerKeepAlive(PingEvent event) {
    event.getMessages().add(new PlayerPingMessage(event.getHash()));
  }

  @EventHandler
  public void onPlayerUpdateUserList(ListPingEvent event) {
    String name = event.getPlayerDisplayName();
    event.getMessages().add(new PlayerListMessage(name, event.getOnline(), (short) event.getPingDelay()));
  }

  @EventHandler
  public void onPlayerBed(PlayerBedEvent event) {
    event.getMessages().add(new PlayerBedMessage(event.getPlayer(), event.getBed(), getRepositionManager()));
  }

  @EventHandler
  public void onEntityAnimation(EntityAnimationEvent event) {
    event.getMessages().add(new EntityAnimationMessage(event.getEntity().getId(), (byte) event.getAnimation().getId()));
  }

  @EventHandler
  public void onEntityStatus(EntityStatusEvent event) {
    event.getMessages().add(new EntityStatusMessage(event.getEntity().getId(), event.getStatus()));
  }

  @EventHandler
  public void onPlayerUpdateStats(PlayerHealthEvent event) {
    Hunger hunger = getOwner().get(Hunger.class);
    event.getMessages().add(new PlayerHealthMessage(getOwner().get(Human.class).getHealth().getHealth(), (short) hunger.getHunger(), hunger.getFoodSaturation()));
  }

  @EventHandler
  public void onEntityMetaChange(EntityMetaChangeEvent event) {
    event.getMessages().add(new EntityMetadataMessage(event.getEntity().getId(), event.getParameters()));
  }

  @EventHandler
  public void onSignUpdate(SignUpdateEvent event) {
    Sign sign = event.getSign();
    event.getMessages().add(new SignMessage(sign.getOwner().getX(), sign.getOwner().getY(), sign.getOwner().getZ(), event.getLines(), getRepositionManager()));
  }

  @EventHandler
  public void onEntityCollectItem(EntityCollectItemEvent event) {
    event.getMessages().add(new PlayerCollectItemMessage(event.getCollected().getId(), event.getEntity().getId()));
  }

  @EventHandler
  public void onPlayerGameState(PlayerGameStateEvent event) {
    event.getMessages().add(new PlayerGameStateMessage(event.getReason(), event.getGameMode()));
  }

  @EventHandler
  public void onWeatherChanged(WeatherChangeEvent event) {
    Weather newWeather = event.getNewWeather();
    if (newWeather.equals(Weather.RAIN) || newWeather.equals(Weather.THUNDERSTORM)) {
      event.getMessages().add(new PlayerGameStateMessage(PlayerGameStateMessage.BEGIN_RAINING));
    } else {
      event.getMessages().add(new PlayerGameStateMessage(PlayerGameStateMessage.END_RAINING));
    }
  }

  @EventHandler
  public void onTimeUpdate(TimeUpdateEvent event) {
    event.getMessages().add(new PlayerTimeMessage(event.getWorld().getAge(), event.getNewTime()));
    event.setSendType(Session.SendType.GAME_ONLY);
  }

  @EventHandler
  public void onEntityRemoveEffect(EntityRemoveEffectEvent event) {
    event.getMessages().add(new EntityRemoveEffectMessage(event.getEntity().getId(), (byte) event.getEffect().getId()));
  }

  @EventHandler
  public void onBlockBreakAnimation(BlockBreakAnimationEvent event) {
    event.getMessages().add(new BlockBreakAnimationMessage(event.getEntity().getId(), (int) event.getPoint().getX(), (int) event.getPoint().getY(), (int) event.getPoint().getZ(), event.getLevel(), getRepositionManager()));
  }

  @EventHandler
  public void onEntityEffect(EntityEffectEvent event) {
    event.getMessages().add(new EntityEffectMessage(event.getEntity().getId(), (byte) event.getEffect().getType().getId(), (byte) 0, (short) (event.getEffect().getDuration() * 20)));
  }

  @EventHandler
  public void onExperienceChange(PlayerExperienceChangeEvent event) {
    Player player = event.getPlayer();
    Level level = player.add(Level.class);
    event.getMessages().add(new PlayerExperienceMessage(level.getProgress(), level.getLevel(), event.getNewExp()));
  }

  @EventHandler
  public void onPlayerSelectedSlotChange(PlayerSelectedSlotChangeEvent event) {
    event.getMessages().add(new PlayerHeldItemChangeMessage(event.getSelectedSlot()));
  }

  @EventHandler
  public void onObjectiveAction(ObjectiveActionEvent event) {
    Objective obj = event.getObjective();
    event.getMessages().add(new ScoreboardObjectiveMessage(obj.getName(), obj.getDisplayName(), event.getAction()));
  }

  @EventHandler
  public void onObjectiveDisplay(ObjectiveDisplayEvent event) {
    event.getMessages().add(new ScoreboardDisplayMessage((byte) event.getSlot().ordinal(), event.getObjectiveName()));
  }

  @EventHandler
  public void onScoreUpdate(ScoreUpdateEvent event) {
    event.getMessages().add(new ScoreboardScoreMessage(event.getKey(), event.isRemove(), event.getObjectiveName(), event.getValue()));
  }

  @EventHandler
  public void onTeamAction(TeamActionEvent event) {
    Team team = event.getTeam();
    event.getMessages().add(new ScoreboardTeamMessage(
        team.getName(), event.getAction(),
        team.getDisplayName(),
        team.getPrefix(), team.getSuffix(),
        team.isFriendlyFire(), event.getPlayers()
    ));
  }
}
TOP

Related Classes of org.spout.vanilla.component.entity.player.VanillaPlayerNetworkComponent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.