package com.asteria.game.character.npc;
import java.util.Iterator;
import com.asteria.game.World;
import com.asteria.game.character.Flag;
import com.asteria.game.character.player.Player;
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 NPCs.
*
* @author blakeman8192
* @author lare96 <http://github.com/lare96>
*/
public final class NpcUpdating {
/**
* The default constructor.
*
* @throws UnsupportedOperationException
* if this class is instantiated.
*/
private NpcUpdating() {
throw new UnsupportedOperationException("This class cannot be " + "instantiated!");
}
/**
* The method that performs updating on NPCs for {@code player}.
*
* @param player
* the player NPCs are being updated for.
* @throws Exception
* if any errors occur while updating NPCs for the player.
*/
public static void update(Player player) throws Exception {
MessageBuilder out = MessageBuilder.create(2048);
MessageBuilder block = MessageBuilder.create(1024);
out.newVarShortMessage(65);
out.startBitAccess();
out.putBits(8, player.getLocalNpcs().size());
for (Iterator<Npc> i = player.getLocalNpcs().iterator(); i.hasNext();) {
Npc npc = i.next();
if (npc.getPosition().isViewableFrom(player.getPosition()) && npc.isVisible()) {
NpcUpdating.updateNpcMovement(out, npc);
if (npc.getFlags().needsUpdate()) {
NpcUpdating.updateState(block, npc);
}
} else {
out.putBit(true);
out.putBits(2, 3);
i.remove();
}
}
int added = 0;
for (Npc npc : World.getNpcs()) {
if (added == 15 || player.getLocalNpcs().size() >= 255)
break;
if (npc == null)
continue;
if (npc.getPosition().isViewableFrom(player.getPosition()) && npc.isVisible()) {
if (player.getLocalNpcs().add(npc)) {
npc.getFlags().set(Flag.APPEARANCE);
addNpc(out, player, npc);
if (npc.getFlags().needsUpdate()) {
NpcUpdating.updateState(block, npc);
}
added++;
}
}
}
if (block.buffer().writerIndex() > 0) {
out.putBits(14, 16383);
out.endBitAccess();
out.putBytes(block.buffer());
} else {
out.endBitAccess();
}
out.endVarShortMessage();
player.getSession().queue(out);
}
/**
* Puts {@code npc} in the client local list of {@code player}.
*
* @param out
* the buffer to write the data to.
* @param player
* the player who's list will be modified.
* @param npc
* the NPC who will be added to the player's list.
*/
private static void addNpc(MessageBuilder out, Player player, Npc npc) {
out.putBits(14, npc.getSlot());
Position delta = Position.delta(player.getPosition(), npc.getPosition());
out.putBits(5, delta.getY());
out.putBits(5, delta.getX());
out.putBit(npc.getFlags().needsUpdate());
out.putBits(12, npc.getId());
out.putBit(true);
}
/**
* Updates the movement of a NPC for this sequence.
*
* @param out
* the buffer that the data will be written to.
* @param npc
* the NPC's movement that will be updated.
*/
private static void updateNpcMovement(MessageBuilder out, Npc npc) {
if (npc.getPrimaryDirection() == -1) {
if (npc.getFlags().needsUpdate()) {
out.putBit(true);
out.putBits(2, 0);
} else {
out.putBit(false);
}
} else {
out.putBit(true);
out.putBits(2, 1);
out.putBits(3, npc.getPrimaryDirection());
if (npc.getFlags().needsUpdate()) {
out.putBit(true);
} else {
out.putBit(false);
}
}
}
/**
* Updates the state of {@code npc}.
*
* @param block
* the buffer that the data will be written to.
* @param npc
* the NPC that will have it's state updated.
* @throws Exception
* if any errors occur while updating the state.
*/
private static void updateState(MessageBuilder block, Npc npc) throws Exception {
BitMask mask = new BitMask();
if (npc.getFlags().get(Flag.ANIMATION)) {
mask.set(0x10);
}
if (npc.getFlags().get(Flag.HIT_2)) {
mask.set(8);
}
if (npc.getFlags().get(Flag.GRAPHICS)) {
mask.set(0x80);
}
if (npc.getFlags().get(Flag.FACE_CHARACTER)) {
mask.set(0x20);
}
if (npc.getFlags().get(Flag.FORCED_CHAT)) {
mask.set(1);
}
if (npc.getFlags().get(Flag.HIT)) {
mask.set(0x40);
}
if (npc.getFlags().get(Flag.TRANSFORM)) {
mask.set(2);
}
if (npc.getFlags().get(Flag.FACE_COORDINATE)) {
mask.set(4);
}
if (mask.get() >= 0x100) {
mask.set(0x40);
block.putShort(mask.get(), com.asteria.net.ByteOrder.LITTLE);
} else {
block.put(mask.get());
}
if (npc.getFlags().get(Flag.ANIMATION)) {
appendAnimation(block, npc);
}
if (npc.getFlags().get(Flag.HIT_2)) {
appendSecondaryHit(block, npc);
}
if (npc.getFlags().get(Flag.GRAPHICS)) {
appendGraphic(block, npc);
}
if (npc.getFlags().get(Flag.FACE_CHARACTER)) {
appendFaceCharacter(block, npc);
}
if (npc.getFlags().get(Flag.FORCED_CHAT)) {
appendForcedChat(block, npc);
}
if (npc.getFlags().get(Flag.HIT)) {
appendPrimaryHit(block, npc);
}
if (npc.getFlags().get(Flag.TRANSFORM)) {
appendTransformation(block, npc);
}
if (npc.getFlags().get(Flag.FACE_COORDINATE)) {
appendFaceCoordinates(block, npc);
}
}
/**
* Appends the state of a graphic to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendGraphic(MessageBuilder out, Npc npc) {
out.putShort(npc.getGraphic().getId());
out.putInt(npc.getGraphic().getHeight());
}
/**
* Appends the state of a transformation to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendTransformation(MessageBuilder out, Npc npc) {
out.putShort(npc.getTransform(), ValueType.A, ByteOrder.LITTLE);
}
/**
* Appends the state of a secondary hit to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendSecondaryHit(MessageBuilder out, Npc npc) throws Exception {
if (!npc.isDead()) {
if (npc.getCurrentHealth() <= 0) {
npc.setCurrentHealth(0);
World.submit(new NpcDeath(npc));
}
}
out.put(npc.getSecondaryHit().getDamage(), ValueType.A);
out.put(npc.getSecondaryHit().getType().getId(), ValueType.C);
out.put(npc.getCurrentHealth(), ValueType.A);
out.put(npc.getMaxHealth());
}
/**
* Appends the state of facing a character to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendFaceCharacter(MessageBuilder out, Npc npc) {
out.putShort(npc.getFaceIndex());
}
/**
* Appends the state of forced chat to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendForcedChat(MessageBuilder out, Npc npc) {
out.putString(npc.getForcedText());
}
/**
* Appends the state of a primary hit to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendPrimaryHit(MessageBuilder out, Npc npc) {
if (!npc.isDead()) {
if (npc.getCurrentHealth() <= 0) {
npc.setCurrentHealth(0);
World.submit(new NpcDeath(npc));
}
}
out.put(npc.getPrimaryHit().getDamage(), ValueType.C);
out.put(npc.getPrimaryHit().getType().getId(), ValueType.S);
out.put(npc.getCurrentHealth(), ValueType.S);
out.put(npc.getMaxHealth(), ValueType.C);
}
/**
* Appends the state of facing a set of coordinates to {@code out} for
* {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendFaceCoordinates(MessageBuilder out, Npc npc) {
out.putShort(npc.getFacePosition().getX(), ByteOrder.LITTLE);
out.putShort(npc.getFacePosition().getY(), ByteOrder.LITTLE);
}
/**
* Appends the state of an animation to {@code out} for {@code npc}.
*
* @param npc
* the npc to append the state for.
* @param out
* the buffer to append it to.
*/
private static void appendAnimation(MessageBuilder out, Npc npc) {
out.putShort(npc.getAnimation().getId(), ByteOrder.LITTLE);
out.put(npc.getAnimation().getDelay());
}
}