diff --git a/game/src/main/org/apollo/game/model/entity/Player.java b/game/src/main/org/apollo/game/model/entity/Player.java index 86e8a549..abe02680 100644 --- a/game/src/main/org/apollo/game/model/entity/Player.java +++ b/game/src/main/org/apollo/game/model/entity/Player.java @@ -6,6 +6,7 @@ import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.apollo.game.message.impl.ConfigMessage; import org.apollo.game.message.impl.IdAssignmentMessage; @@ -19,6 +20,7 @@ import org.apollo.game.message.impl.UpdateRunEnergyMessage; import org.apollo.game.model.Appearance; import org.apollo.game.model.Position; import org.apollo.game.model.World; +import org.apollo.game.model.WorldConstants; import org.apollo.game.model.entity.attr.Attribute; import org.apollo.game.model.entity.attr.AttributeDefinition; import org.apollo.game.model.entity.attr.AttributeMap; @@ -47,6 +49,7 @@ import org.apollo.game.model.skill.LevelUpSkillListener; import org.apollo.game.model.skill.SynchronizationSkillListener; import org.apollo.game.session.GameSession; import org.apollo.game.sync.block.SynchronizationBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; import org.apollo.net.message.Message; import org.apollo.util.CollectionUtil; import org.apollo.util.Point; @@ -71,6 +74,15 @@ public final class Player extends Mob { AttributeMap.define("run_energy", AttributeDefinition.forInt(100, AttributePersistence.PERSISTENT)); } + /** + * The current amount of appearance tickets. + */ + private static final AtomicInteger appearanceTicketCounter = new AtomicInteger(0); + + /** + * This appearance tickets for this Player. + */ + private final int[] appearanceTickets = new int[WorldConstants.MAXIMUM_PLAYERS]; /** * This player's bank. @@ -207,6 +219,11 @@ public final class Player extends Mob { */ private int worldId = 1; + /** + * This Players appearance ticket. + */ + private int appearanceTicket = nextAppearanceTicket(); + /** * Creates the Player. * @@ -641,6 +658,36 @@ public final class Player extends Mob { localObjects.forEach(object -> object.removeFrom(this)); } + /** + * Generates the next appearance ticket. + * + * @return The next available appearance ticket. + */ + private static int nextAppearanceTicket() { + if (appearanceTicketCounter.incrementAndGet() == 0) { + appearanceTicketCounter.set(1); + } + return appearanceTicketCounter.get(); + } + + /** + * Gets all of this Players appearance tickets. + * + * @return All of this Players appearance tickets. + */ + public int[] getAppearanceTickets() { + return appearanceTickets; + } + + /** + * Gets this Players appearance ticket. + * + * @return This Players appearance ticket. + */ + public int getAppearanceTicket() { + return appearanceTicket; + } + /** * Indicates whether the message filter is enabled. * @@ -735,7 +782,7 @@ public final class Player extends Mob { * Sends the initial messages. */ public void sendInitialMessages() { - blockSet.add(SynchronizationBlock.createAppearanceBlock(this)); + updateAppearance(); send(new IdAssignmentMessage(index, members)); sendMessage("Welcome to RuneScape."); @@ -813,7 +860,7 @@ public final class Player extends Mob { */ public void setAppearance(Appearance appearance) { this.appearance = appearance; - blockSet.add(SynchronizationBlock.createAppearanceBlock(this)); + updateAppearance(); } /** @@ -1025,4 +1072,12 @@ public final class Player extends Mob { skillSet.addListener(new LevelUpSkillListener(this)); } + /** + * Updates the appearance for this Player. + */ + public void updateAppearance() { + appearanceTicket = nextAppearanceTicket(); + blockSet.add(SynchronizationBlock.createAppearanceBlock(this)); + } + } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java b/game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java index a977ec6f..a59a883a 100644 --- a/game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java +++ b/game/src/main/org/apollo/game/model/inv/AppearanceInventoryListener.java @@ -39,7 +39,7 @@ public final class AppearanceInventoryListener extends InventoryAdapter { * Updates the player's appearance. */ private void update() { - player.getBlockSet().add(SynchronizationBlock.createAppearanceBlock(player)); + player.updateAppearance(); } } \ No newline at end of file diff --git a/game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java b/game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java index 8f599f7a..f6555ec2 100644 --- a/game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java +++ b/game/src/main/org/apollo/game/model/skill/SynchronizationSkillListener.java @@ -32,7 +32,7 @@ public final class SynchronizationSkillListener extends SkillAdapter { @Override public void levelledUp(SkillSet set, int id, Skill skill) { if (Skill.isCombatSkill(id)) { - player.getBlockSet().add(SynchronizationBlock.createAppearanceBlock(player)); + player.updateAppearance(); } } diff --git a/game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java b/game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java index 06290056..be7f30d1 100644 --- a/game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java +++ b/game/src/main/org/apollo/game/sync/task/PlayerSynchronizationTask.java @@ -53,6 +53,7 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { public void run() { Position lastKnownRegion = player.getLastKnownRegion(); boolean regionChanged = player.hasRegionChanged(); + int[] appearanceTickets = player.getAppearanceTickets(); SynchronizationBlockSet blockSet = player.getBlockSet(); @@ -101,12 +102,15 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { added++; blockSet = other.getBlockSet(); - if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance + + int index = other.getIndex(); + + if (!blockSet.contains(AppearanceBlock.class) && !hasCachedAppearance(appearanceTickets, index - 1, other.getAppearanceTicket())) { blockSet = blockSet.clone(); blockSet.add(SynchronizationBlock.createAppearanceBlock(other)); } - segments.add(new AddPlayerSegment(blockSet, other.getIndex(), local)); + segments.add(new AddPlayerSegment(blockSet, index, local)); } } @@ -115,6 +119,25 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { player.send(message); } + /** + * Tests whether or not the specified Player has a cached appearance within + * the specified appearance ticket array. + * + * @param appearanceTickets The appearance tickets. + * @param index The index of the Player. + * @param appearanceTicket The current appearance ticket for the Player. + * @return {@code true} if the specified Player has a cached appearance + * otherwise {@code false}. + */ + private boolean hasCachedAppearance(int[] appearanceTickets, int index, int appearanceTicket) { + if (appearanceTickets[index] != appearanceTicket) { + appearanceTickets[index] = appearanceTicket; + return false; + } + + return true; + } + /** * Returns whether or not the specified {@link Player} should be removed. *