package com.asteria.game.character.player;
import java.util.Iterator;
import com.asteria.game.World;
import com.asteria.game.character.Flag;
import com.asteria.game.character.player.skill.Skills;
import com.asteria.game.item.container.Equipment;
import com.asteria.game.location.Position;
import com.asteria.net.ByteOrder;
import com.asteria.net.ValueType;
import com.asteria.net.message.MessageBuilder;
import com.asteria.utility.BitMask;
/**
* The class that provides static utility methods for updating players.
*
* @author blakeman8192
* @author lare96 <http://github.com/lare96>
*/
public final class PlayerUpdating {
/**
* The default constructor.
*
* @throws UnsupportedOperationException
* if this class is instantiated.
*/
private PlayerUpdating() {
throw new UnsupportedOperationException("This class cannot be " + "instantiated!");
}
/**
* The method that performs updating on {@code player}.
*
* @param player
* the player being updated.
* @throws Exception
* if any errors occur while updating the player.
*/
public static void update(Player player) throws Exception {
MessageBuilder out = MessageBuilder.create(16384);
MessageBuilder block = MessageBuilder.create(8192);
out.newVarShortMessage(81);
out.startBitAccess();
PlayerUpdating.updateLocalPlayerMovement(player, out);
if (player.getFlags().needsUpdate())
PlayerUpdating.updateState(player, player, block, false, true);
out.putBits(8, player.getLocalPlayers().size());
for (Iterator<Player> i = player.getLocalPlayers().iterator(); i.hasNext();) {
Player other = i.next();
if (other.getPosition().isViewableFrom(player.getPosition()) && other.getSession().getState() == IOState.LOGGED_IN && !other
.isNeedsPlacement() && other.isVisible()) {
PlayerUpdating.updateOtherPlayerMovement(other, out);
if (other.getFlags().needsUpdate()) {
PlayerUpdating.updateState(other, player, block, false, false);
}
} else {
out.putBit(true);
out.putBits(2, 3);
i.remove();
}
}
int added = 0;
for (Player other : World.getPlayers()) {
if (added == 15 || player.getLocalPlayers().size() >= 255)
break;
if (other == null || other.equals(player) || other.getSession().getState() != IOState.LOGGED_IN)
continue;
if (other.getPosition().isViewableFrom(player.getPosition()) && other.isVisible()) {
if (player.getLocalPlayers().add(other)) {
added++;
PlayerUpdating.addPlayer(out, player, other);
PlayerUpdating.updateState(other, player, block, true, false);
}
}
}
if (block.buffer().writerIndex() > 0) {
out.putBits(11, 2047);
out.endBitAccess();
out.putBytes(block.buffer());
} else {
out.endBitAccess();
}
out.endVarShortMessage();
player.getSession().queue(out);
}
/**
* Appends the state of chat to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendChat(Player player, MessageBuilder out) {
out.putShort(((player.getChatColor() & 0xff) << 8) + (player.getChatEffects() & 0xff), ByteOrder.LITTLE);
out.put(player.getRights().getProtocolValue());
out.put(player.getChatText().length, ValueType.C);
out.putBytesReverse(player.getChatText());
}
/**
* Appends the state of appearance to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendAppearance(Player player, MessageBuilder out) {
Appearance appearance = player.getAppearance();
MessageBuilder block = MessageBuilder.create(128);
block.put(appearance.getGender());
block.put(player.getHeadIcon());
block.put(player.getSkullIcon());
if (player.getPlayerNpc() == -1) {
if (player.getEquipment().getId(Equipment.HEAD_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.HEAD_SLOT));
} else {
block.put(0);
}
if (player.getEquipment().getId(Equipment.CAPE_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.CAPE_SLOT));
} else {
block.put(0);
}
if (player.getEquipment().getId(Equipment.AMULET_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.AMULET_SLOT));
} else {
block.put(0);
}
if (player.getEquipment().getId(Equipment.WEAPON_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.WEAPON_SLOT));
} else {
block.put(0);
}
if (player.getEquipment().getId(Equipment.CHEST_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.CHEST_SLOT));
} else {
block.putShort(0x100 + appearance.getChest());
}
if (player.getEquipment().getId(Equipment.SHIELD_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.SHIELD_SLOT));
} else {
block.put(0);
}
if (player.getEquipment().getId(Equipment.CHEST_SLOT) > 1) {
if (!player.getEquipment().get(Equipment.CHEST_SLOT).getDefinition().isPlatebody()) {
block.putShort(0x100 + appearance.getArms());
} else {
block.put(0);
}
} else {
block.putShort(0x100 + appearance.getArms());
}
if (player.getEquipment().getId(Equipment.LEGS_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.LEGS_SLOT));
} else {
block.putShort(0x100 + appearance.getLegs());
}
if (player.getEquipment().getId(Equipment.HEAD_SLOT) > 1 && player.getEquipment().get(Equipment.HEAD_SLOT).getDefinition()
.isFullHelm()) {
block.put(0);
} else {
block.putShort(0x100 + appearance.getHead());
}
if (player.getEquipment().getId(Equipment.HANDS_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.HANDS_SLOT));
} else {
block.putShort(0x100 + appearance.getHands());
}
if (player.getEquipment().getId(Equipment.FEET_SLOT) > 1) {
block.putShort(0x200 + player.getEquipment().getId(Equipment.FEET_SLOT));
} else {
block.putShort(0x100 + appearance.getFeet());
}
if (appearance.isMale()) {
if (player.getEquipment().getId(Equipment.HEAD_SLOT) > 1 && !player.getEquipment().get(Equipment.HEAD_SLOT).getDefinition()
.isFullHelm() || player.getEquipment().free(Equipment.HEAD_SLOT)) {
block.putShort(0x100 + appearance.getBeard());
} else {
block.put(0);
}
} else {
block.put(0);
}
} else {
block.putShort(-1);
block.putShort(player.getPlayerNpc());
}
block.put(appearance.getHairColor());
block.put(appearance.getTorsoColor());
block.put(appearance.getLegColor());
block.put(appearance.getFeetColor());
block.put(appearance.getSkinColor());
block.putShort(player.getWeaponAnimation() == null || player.getWeaponAnimation().getStanding() == -1 ? 0x328 : player
.getWeaponAnimation().getStanding());
block.putShort(0x337);
block.putShort(player.getWeaponAnimation() == null || player.getWeaponAnimation().getWalking() == -1 ? 0x333 : player
.getWeaponAnimation().getWalking());
block.putShort(0x334);
block.putShort(0x335);
block.putShort(0x336);
block.putShort(player.getWeaponAnimation() == null || player.getWeaponAnimation().getRunning() == -1 ? 0x338 : player
.getWeaponAnimation().getRunning());
block.putLong(player.getUsernameHash());
block.put(player.determineCombatLevel());
block.putShort(0);
out.put(block.buffer().writerIndex(), ValueType.C);
out.putBytes(block.buffer());
}
/**
* Puts {@code player} in the client local list of {@code other}.
*
* @param out
* the buffer to write the data to.
* @param player
* the player to add to the other player's list.
* @param other
* the player who's list will be modified.
*/
private static void addPlayer(MessageBuilder out, Player player, Player other) {
out.putBits(11, other.getSlot());
out.putBit(true);
out.putBit(true);
Position delta = Position.delta(player.getPosition(), other.getPosition());
out.putBits(5, delta.getY());
out.putBits(5, delta.getX());
}
/**
* Updates movement for this local player. The difference between this
* method and the other player method is that this will make use of sector
* 2,3 to place the player in a specific position while sector 2,3 is not
* present in updating of other players (it simply flags local list removal
* instead).
*
* @param player
* the player to update movement for.
* @param out
* the buffer to write to data to.
*/
private static void updateLocalPlayerMovement(Player player, MessageBuilder out) {
boolean updateRequired = player.getFlags().needsUpdate();
if (player.isNeedsPlacement()) {
out.putBit(true);
int posX = player.getPosition().getLocalX(player.getCurrentRegion());
int posY = player.getPosition().getLocalY(player.getCurrentRegion());
appendPlacement(out, posX, posY, player.getPosition().getZ(), player.isResetMovementQueue(), updateRequired);
} else {
int pDir = player.getPrimaryDirection();
int sDir = player.getSecondaryDirection();
if (pDir != -1) {
out.putBit(true);
if (sDir != -1) {
appendRun(out, pDir, sDir, updateRequired);
} else {
appendWalk(out, pDir, updateRequired);
}
} else {
if (updateRequired) {
out.putBit(true);
appendStand(out);
} else {
out.putBit(false);
}
}
}
}
/**
* Updates the movement of a player for another player (does not make use of
* sector 2,3).
*
* @param player
* the player to update movement for.
* @param out
* the buffer to write the data to.
*/
private static void updateOtherPlayerMovement(Player player, MessageBuilder out) {
boolean updateRequired = player.getFlags().needsUpdate();
int pDir = player.getPrimaryDirection();
int sDir = player.getSecondaryDirection();
if (pDir != -1) {
out.putBit(true);
if (sDir != -1) {
appendRun(out, pDir, sDir, updateRequired);
} else {
appendWalk(out, pDir, updateRequired);
}
} else {
if (updateRequired) {
out.putBit(true);
appendStand(out);
} else {
out.putBit(false);
}
}
}
/**
* Updates the state of {@code thisPlayer} for {@code player}.
*
* @param player
* the player to update the state for.
* @param thisPlayer
* the player who's state is being updated.
* @param block
* the buffer that the data will be written to.
* @param forceAppearance
* if the appearance block is being forced.
* @param noChat
* if the chat block is being disabled.
* @throws Exception
* if any errors occur while updating the state.
*/
private static void updateState(Player player, Player thisPlayer, MessageBuilder block, boolean forceAppearance, boolean noChat) throws Exception {
if (!player.getFlags().needsUpdate() && !forceAppearance)
return;
if (player.getCachedUpdateBlock() != null && !player.equals(thisPlayer) && !forceAppearance && !noChat) {
block.putBytes(player.getCachedUpdateBlock());
return;
}
MessageBuilder cachedBuffer = MessageBuilder.create(300);
BitMask mask = new BitMask();
if (player.getFlags().get(Flag.FORCED_MOVEMENT)) {
mask.set(0x400);
}
if (player.getFlags().get(Flag.GRAPHICS)) {
mask.set(0x100);
}
if (player.getFlags().get(Flag.ANIMATION)) {
mask.set(8);
}
if (player.getFlags().get(Flag.FORCED_CHAT)) {
mask.set(4);
}
if (player.getFlags().get(Flag.CHAT) && !noChat) {
mask.set(0x80);
}
if (player.getFlags().get(Flag.APPEARANCE) || forceAppearance) {
mask.set(0x10);
}
if (player.getFlags().get(Flag.FACE_CHARACTER)) {
mask.set(1);
}
if (player.getFlags().get(Flag.FACE_COORDINATE)) {
mask.set(2);
}
if (player.getFlags().get(Flag.HIT)) {
mask.set(0x20);
}
if (player.getFlags().get(Flag.HIT_2)) {
mask.set(0x200);
}
if (mask.get() >= 0x100) {
mask.set(0x40);
cachedBuffer.putShort(mask.get(), ByteOrder.LITTLE);
} else {
cachedBuffer.put(mask.get());
}
if (player.getFlags().get(Flag.FORCED_MOVEMENT)) {
// appendForcedMovement(player, cachedBuffer);
}
if (player.getFlags().get(Flag.GRAPHICS)) {
appendGraphic(player, cachedBuffer);
}
if (player.getFlags().get(Flag.ANIMATION)) {
appendAnimation(player, cachedBuffer);
}
if (player.getFlags().get(Flag.FORCED_CHAT)) {
appendForcedChat(player, cachedBuffer);
}
if (player.getFlags().get(Flag.CHAT) && !noChat) {
appendChat(player, cachedBuffer);
}
if (player.getFlags().get(Flag.FACE_CHARACTER)) {
appendFaceCharacter(player, cachedBuffer);
}
if (player.getFlags().get(Flag.APPEARANCE) || forceAppearance) {
appendAppearance(player, cachedBuffer);
}
if (player.getFlags().get(Flag.FACE_COORDINATE)) {
appendFaceCoordinates(player, cachedBuffer);
}
if (player.getFlags().get(Flag.HIT)) {
appendPrimaryHit(player, cachedBuffer);
}
if (player.getFlags().get(Flag.HIT_2)) {
appendSecondaryHit(player, cachedBuffer);
}
if (!player.equals(thisPlayer) && !forceAppearance && !noChat) {
player.setCachedUpdateBlock(cachedBuffer.buffer());
}
block.putBytes(cachedBuffer.buffer());
}
/**
* Appends the state of forced chat to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendForcedChat(Player player, MessageBuilder out) {
out.putString(player.getForcedText());
}
/**
* Appends the state of facing another character to {@code out} for
* {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendFaceCharacter(Player player, MessageBuilder out) {
out.putShort(player.getFaceIndex(), ByteOrder.LITTLE);
}
/**
* Appends the state of facing a set of coordinates to {@code out} for
* {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendFaceCoordinates(Player player, MessageBuilder out) {
out.putShort(player.getFacePosition().getX(), ValueType.A, ByteOrder.LITTLE);
out.putShort(player.getFacePosition().getY(), ByteOrder.LITTLE);
}
/**
* Appends the state of animation to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendAnimation(Player player, MessageBuilder out) {
out.putShort(player.getAnimation().getId(), ByteOrder.LITTLE);
out.put(player.getAnimation().getDelay(), ValueType.C);
}
/**
* Appends the state of a primary hit to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendPrimaryHit(Player player, MessageBuilder out) throws Exception {
out.put(player.getPrimaryHit().getDamage());
out.put(player.getPrimaryHit().getType().getId(), ValueType.A);
if (!player.isDead()) {
if (player.getSkills()[Skills.HITPOINTS].getLevel() <= 0) {
player.getSkills()[Skills.HITPOINTS].setLevel(0, true);
player.setDead(true);
World.submit(new PlayerDeath(player));
}
}
out.put(player.getSkills()[Skills.HITPOINTS].getLevel(), ValueType.C);
out.put(player.getSkills()[Skills.HITPOINTS].getRealLevel());
}
/**
* Appends the state of a secondary hit to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendSecondaryHit(Player player, MessageBuilder out) throws Exception {
out.put(player.getSecondaryHit().getDamage());
out.put(player.getSecondaryHit().getType().getId(), ValueType.S);
if (!player.isDead()) {
if (player.getSkills()[Skills.HITPOINTS].getLevel() <= 0) {
player.getSkills()[Skills.HITPOINTS].setLevel(0, true);
player.setDead(true);
World.submit(new PlayerDeath(player));
}
}
out.put(player.getSkills()[Skills.HITPOINTS].getLevel());
out.put(player.getSkills()[Skills.HITPOINTS].getRealLevel(), ValueType.C);
}
/**
* Appends the state of a graphic to {@code out} for {@code player}.
*
* @param player
* the player to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendGraphic(Player player, MessageBuilder out) {
out.putShort(player.getGraphic().getId(), ByteOrder.LITTLE);
out.putInt(player.getGraphic().getHeight());
}
/**
* Appends the stand version of the movement section of the update message
* (sector 2,0). Appending this (instead of just a zero bit) automatically
* assumes that there is a required attribute update afterwards.
*
* @param out
* the buffer to write the data to.
*/
private static void appendStand(MessageBuilder out) {
out.putBits(2, 0);
}
/**
* Appends the walk version of the movement section of the update message
* (sector 2,1).
*
* @param out
* the buffer to write the data to.
* @param direction
* the walking direction to append.
* @param attributesUpdate
* whether or not a player attributes update is required.
*/
private static void appendWalk(MessageBuilder out, int direction, boolean attributesUpdate) {
out.putBits(2, 1);
out.putBits(3, direction);
out.putBit(attributesUpdate);
}
/**
* Appends the walk version of the movement section of the update message
* (sector 2,2).
*
* @param out
* the buffer to write the data to.
* @param direction
* the walking direction to append.
* @param direction2
* the running direction to append.
* @param attributesUpdate
* whether or not a player attributes update is required.
*/
private static void appendRun(MessageBuilder out, int direction, int direction2, boolean attributesUpdate) {
out.putBits(2, 2);
out.putBits(3, direction);
out.putBits(3, direction2);
out.putBit(attributesUpdate);
}
/**
* Appends the player placement version of the movement section of the
* update message (sector 2,3). Note that by others this was previously
* called the "teleport update".
*
* @param out
* the buffer to write the data to.
* @param localX
* the local {@code X} coordinate.
* @param localY
* the local {@code Y} coordinate.
* @param z
* the {@code Z} coordinate.
* @param discardMovementQueue
* whether or not the client should discard the movement queue.
* @param attributesUpdate
* whether or not a plater attributes update is required.
*/
private static void appendPlacement(MessageBuilder out, int localX, int localY, int z, boolean discardMovementQueue, boolean attributesUpdate) {
out.putBits(2, 3);
out.putBits(2, z);
out.putBit(discardMovementQueue);
out.putBit(attributesUpdate);
out.putBits(7, localY);
out.putBits(7, localX);
}
}