From 9ea65b876967906161ddd9d1bb7fdc14e3cd5544 Mon Sep 17 00:00:00 2001
From: Chris Kearney <chris@kearneymail.com>
Date: Sat, 27 May 2017 12:51:13 -0700
Subject: [PATCH] more progress on pvp

---
 .../creeper/items/EffectsManager.java         |  223 +-
 .../items/use/DefaultApplyEffectsStats.java   |    3 +-
 .../creeper/items/use/DirtyBombUseAction.java |   17 +-
 .../npc/BasicNpcPlayerDamageProcessor.java    |   15 +
 .../java/com/comandante/creeper/npc/Npc.java  |    4 +-
 .../comandante/creeper/npc/StatsChange.java   |   98 +-
 ...geBuilder.java => StatsChangeBuilder.java} |   96 +-
 .../player/BasicPlayerDamageProcessor.java    |   31 +
 .../creeper/player/CoolDownType.java          |    1 +
 .../creeper/player/DamageProcessor.java       |    6 +
 .../com/comandante/creeper/player/Player.java | 3207 +++++++++--------
 11 files changed, 1939 insertions(+), 1762 deletions(-)
 rename src/main/java/com/comandante/creeper/npc/{NpcStatsChangeBuilder.java => StatsChangeBuilder.java} (65%)

diff --git a/src/main/java/com/comandante/creeper/items/EffectsManager.java b/src/main/java/com/comandante/creeper/items/EffectsManager.java
index 32db96d6..e47abf41 100644
--- a/src/main/java/com/comandante/creeper/items/EffectsManager.java
+++ b/src/main/java/com/comandante/creeper/items/EffectsManager.java
@@ -1,111 +1,112 @@
-package com.comandante.creeper.items;
-
-import com.comandante.creeper.core_game.GameManager;
-import com.comandante.creeper.npc.Npc;
-import com.comandante.creeper.npc.StatsChange;
-import com.comandante.creeper.npc.NpcStatsChangeBuilder;
-import com.comandante.creeper.player.Player;
-import com.comandante.creeper.player.PlayerMetadata;
-import com.comandante.creeper.server.player_communication.Color;
-import com.comandante.creeper.stats.Stats;
-import com.comandante.creeper.stats.StatsBuilder;
-import com.comandante.creeper.stats.StatsHelper;
-import org.apache.log4j.Logger;
-
-import java.text.NumberFormat;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Set;
-
-public class EffectsManager {
-
-    private final GameManager gameManager;
-
-    private static final Logger log = Logger.getLogger(EffectsManager.class);
-
-    public EffectsManager(GameManager gameManager) {
-        this.gameManager = gameManager;
-    }
-
-    public void applyEffectsToNpcs(Player player, Set<Npc> npcs, Set<Effect> effects) {
-        effects.forEach(effect ->
-                npcs.forEach(npc -> {
-                    Effect nEffect = new Effect(effect);
-                    nEffect.setPlayerId(player.getPlayerId());
-                    if (effect.getDurationStats().getCurrentHealth() < 0) {
-                        log.error("ERROR! Someone added an effect with a health modifier which won't work for various reasons.");
-                        return;
-                    }
-                    StatsHelper.combineStats(npc.getStats(), effect.getDurationStats());
-                    npc.addEffect(nEffect);
-                }));
-    }
-
-    public void applyEffectsToPlayer(Player destinationPlayer, Player player, Set<Effect> effects) {
-        for (Effect effect : effects) {
-            Effect nEffect = new Effect(effect);
-            nEffect.setPlayerId(player.getPlayerId());
-            if (effect.getDurationStats().getCurrentHealth() < 0) {
-                log.error("ERROR! Someone added an effect with a health modifier which won't work for various reasons.");
-                continue;
-            }
-            String effectApplyMessage;
-            if (destinationPlayer.addEffect(effect)) {
-                effectApplyMessage = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + nEffect.getEffectName() + " applied!" + "\r\n";
-                gameManager.getChannelUtils().write(destinationPlayer.getPlayerId(), effectApplyMessage);
-            } else {
-                effectApplyMessage = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + Color.RED + "Unable to apply " + nEffect.getEffectName() + "!" + "\r\n";
-                gameManager.getChannelUtils().write(player.getPlayerId(), effectApplyMessage);
-            }
-        }
-    }
-
-    public void application(Effect effect, Player player) {
-        // if there are effecst that modify player health, deal with it here, you can't rely on combine stats.
-        Stats applyStatsOnTick = effect.getApplyStatsOnTick();
-        if (effect.getApplyStatsOnTick() != null) {
-            if (effect.getApplyStatsOnTick().getCurrentHealth() != 0) {
-                gameManager.getPlayerManager().getPlayer(player.getPlayerId()).updatePlayerHealth(effect.getApplyStatsOnTick().getCurrentHealth(), null);
-                for (String message : effect.getEffectApplyMessages()) {
-                    if (effect.getApplyStatsOnTick().getCurrentHealth() > 0) {
-                        gameManager.getChannelUtils().write(player.getPlayerId(), Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + message + " +" + Color.GREEN + NumberFormat.getNumberInstance(Locale.US).format(effect.getApplyStatsOnTick().getCurrentHealth()) + Color.RESET + "\r\n", true);
-                    } else {
-                        gameManager.getChannelUtils().write(player.getPlayerId(), Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + message + " -" + Color.RED + NumberFormat.getNumberInstance(Locale.US).format(effect.getApplyStatsOnTick().getCurrentHealth()) + Color.RESET + "\r\n", true);
-                    }
-                }
-                //applyStatsOnTick = new StatsBuilder(applyStatsOnTick).setCurrentHealth(0).createStats();
-            }
-            //  StatsHelper.combineStats(playerMetadata.getTargetStatsChange(), applyStatsOnTick);
-        }
-    }
-
-    public void application(Effect effect, Npc npc) {
-        Player player = gameManager.getPlayerManager().getPlayer(effect.getPlayerId());
-        Stats applyStats = new Stats(effect.getApplyStatsOnTick());
-        // if there are effecst that modify npc health, deal with it here, you can't rely on combine stats.
-        if (effect.getApplyStatsOnTick().getCurrentHealth() < 0) {
-            String s = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + npc.getColorName() + " is affected by " + effect.getEffectDescription() + " " + Color.RED + applyStats.getCurrentHealth() + Color.RESET + Color.CYAN + Color.RESET;
-            StatsChange npcStatsChange = new NpcStatsChangeBuilder()
-                    .setStats(applyStats)
-                    .setDamageStrings(Arrays.asList(s))
-                    .setPlayer(player)
-                    .createNpcStatsChange();
-            npc.addNpcDamage(npcStatsChange);
-        }
-        // Remove any health mods, as it will screw things up.
-        Stats finalCombineWorthyStats = new StatsBuilder(applyStats).setCurrentHealth(0).createStats();
-        StatsHelper.combineStats(npc.getStats(), finalCombineWorthyStats);
-    }
-
-    public void removeDurationStats(Effect effect, Npc npc) {
-        Stats newStats = new Stats(effect.getDurationStats());
-        StatsHelper.inverseStats(newStats);
-        StatsHelper.combineStats(npc.getStats(), newStats);
-    }
-
-    public void removeDurationStats(Effect effect, PlayerMetadata playerMetadata) {
-        Stats newStats = new Stats(effect.getDurationStats());
-        StatsHelper.inverseStats(newStats);
-        StatsHelper.combineStats(playerMetadata.getStats(), newStats);
-    }
-}
+package com.comandante.creeper.items;
+
+import com.comandante.creeper.core_game.GameManager;
+import com.comandante.creeper.npc.Npc;
+import com.comandante.creeper.npc.StatsChange;
+import com.comandante.creeper.npc.StatsChangeBuilder;
+import com.comandante.creeper.player.Player;
+import com.comandante.creeper.player.PlayerMetadata;
+import com.comandante.creeper.server.player_communication.Color;
+import com.comandante.creeper.stats.Stats;
+import com.comandante.creeper.stats.StatsBuilder;
+import com.comandante.creeper.stats.StatsHelper;
+import org.apache.log4j.Logger;
+
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+
+public class EffectsManager {
+
+    private final GameManager gameManager;
+
+    private static final Logger log = Logger.getLogger(EffectsManager.class);
+
+    public EffectsManager(GameManager gameManager) {
+        this.gameManager = gameManager;
+    }
+
+    public void applyEffectsToNpcs(Player player, Set<Npc> npcs, Set<Effect> effects) {
+        effects.forEach(effect ->
+                npcs.forEach(npc -> {
+                    Effect nEffect = new Effect(effect);
+                    nEffect.setPlayerId(player.getPlayerId());
+                    if (effect.getDurationStats().getCurrentHealth() < 0) {
+                        log.error("ERROR! Someone added an effect with a health modifier which won't work for various reasons.");
+                        return;
+                    }
+                    StatsHelper.combineStats(npc.getStats(), effect.getDurationStats());
+                    npc.addEffect(nEffect);
+                }));
+    }
+
+    public void applyEffectsToPlayer(Player destinationPlayer, Player player, Set<Effect> effects) {
+        for (Effect effect : effects) {
+            Effect nEffect = new Effect(effect);
+            nEffect.setPlayerId(player.getPlayerId());
+            if (effect.getDurationStats().getCurrentHealth() < 0) {
+                log.error("ERROR! Someone added an effect with a health modifier which won't work for various reasons.");
+                continue;
+            }
+            String effectApplyMessage;
+            if (destinationPlayer.addEffect(effect)) {
+                effectApplyMessage = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + nEffect.getEffectName() + " applied!" + "\r\n";
+                gameManager.getChannelUtils().write(destinationPlayer.getPlayerId(), effectApplyMessage);
+            } else {
+                effectApplyMessage = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + Color.RED + "Unable to apply " + nEffect.getEffectName() + "!" + "\r\n";
+                gameManager.getChannelUtils().write(player.getPlayerId(), effectApplyMessage);
+            }
+        }
+    }
+
+    public void application(Effect effect, Player player) {
+        // if there are effecst that modify player health, deal with it here, you can't rely on combine stats.
+        Stats applyStatsOnTick = effect.getApplyStatsOnTick();
+        if (effect.getApplyStatsOnTick() != null) {
+            if (effect.getApplyStatsOnTick().getCurrentHealth() != 0) {
+                gameManager.getPlayerManager().getPlayer(player.getPlayerId()).updatePlayerHealth(effect.getApplyStatsOnTick().getCurrentHealth(), Optional.empty(), Optional.empty());
+                for (String message : effect.getEffectApplyMessages()) {
+                    if (effect.getApplyStatsOnTick().getCurrentHealth() > 0) {
+                        gameManager.getChannelUtils().write(player.getPlayerId(), Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + message + " +" + Color.GREEN + NumberFormat.getNumberInstance(Locale.US).format(effect.getApplyStatsOnTick().getCurrentHealth()) + Color.RESET + "\r\n", true);
+                    } else {
+                        gameManager.getChannelUtils().write(player.getPlayerId(), Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + message + " -" + Color.RED + NumberFormat.getNumberInstance(Locale.US).format(effect.getApplyStatsOnTick().getCurrentHealth()) + Color.RESET + "\r\n", true);
+                    }
+                }
+                //applyStatsOnTick = new StatsBuilder(applyStatsOnTick).setCurrentHealth(0).createStats();
+            }
+            //  StatsHelper.combineStats(playerMetadata.getTargetStatsChange(), applyStatsOnTick);
+        }
+    }
+
+    public void application(Effect effect, Npc npc) {
+        Player player = gameManager.getPlayerManager().getPlayer(effect.getPlayerId());
+        Stats applyStats = new Stats(effect.getApplyStatsOnTick());
+        // if there are effecst that modify npc health, deal with it here, you can't rely on combine stats.
+        if (effect.getApplyStatsOnTick().getCurrentHealth() < 0) {
+            String s = Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + npc.getColorName() + " is affected by " + effect.getEffectDescription() + " " + Color.RED + applyStats.getCurrentHealth() + Color.RESET + Color.CYAN + Color.RESET;
+            StatsChange npcStatsChange = new StatsChangeBuilder()
+                    .setStats(applyStats)
+                    .setDamageStrings(Arrays.asList(s))
+                    .setPlayer(player)
+                    .createNpcStatsChange();
+            npc.addNpcDamage(npcStatsChange);
+        }
+        // Remove any health mods, as it will screw things up.
+        Stats finalCombineWorthyStats = new StatsBuilder(applyStats).setCurrentHealth(0).createStats();
+        StatsHelper.combineStats(npc.getStats(), finalCombineWorthyStats);
+    }
+
+    public void removeDurationStats(Effect effect, Npc npc) {
+        Stats newStats = new Stats(effect.getDurationStats());
+        StatsHelper.inverseStats(newStats);
+        StatsHelper.combineStats(npc.getStats(), newStats);
+    }
+
+    public void removeDurationStats(Effect effect, PlayerMetadata playerMetadata) {
+        Stats newStats = new Stats(effect.getDurationStats());
+        StatsHelper.inverseStats(newStats);
+        StatsHelper.combineStats(playerMetadata.getStats(), newStats);
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/items/use/DefaultApplyEffectsStats.java b/src/main/java/com/comandante/creeper/items/use/DefaultApplyEffectsStats.java
index 292a70b8..2889f5e8 100644
--- a/src/main/java/com/comandante/creeper/items/use/DefaultApplyEffectsStats.java
+++ b/src/main/java/com/comandante/creeper/items/use/DefaultApplyEffectsStats.java
@@ -7,6 +7,7 @@ import com.comandante.creeper.player.Player;
 import com.comandante.creeper.stats.Stats;
 import org.apache.log4j.Logger;
 
+import java.util.Optional;
 import java.util.Set;
 
 public class DefaultApplyEffectsStats implements ItemUseAction {
@@ -39,7 +40,7 @@ public class DefaultApplyEffectsStats implements ItemUseAction {
             gameManager.getChannelUtils().write(player.getPlayerId(), itemApplyStats.getCurrentHealth() + " health is restored." + "\r\n");
         }
         player.addMana(itemApplyStats.getCurrentMana());
-        player.updatePlayerHealth(itemApplyStats.getCurrentHealth(), null);
+        player.updatePlayerHealth(itemApplyStats.getCurrentHealth(), Optional.empty(), Optional.empty());
 
         processEffects(gameManager, player, effectSet);
     }
diff --git a/src/main/java/com/comandante/creeper/items/use/DirtyBombUseAction.java b/src/main/java/com/comandante/creeper/items/use/DirtyBombUseAction.java
index 37d18642..8b34af6e 100644
--- a/src/main/java/com/comandante/creeper/items/use/DirtyBombUseAction.java
+++ b/src/main/java/com/comandante/creeper/items/use/DirtyBombUseAction.java
@@ -4,7 +4,7 @@ import com.comandante.creeper.command.commands.UseCommand;
 import com.comandante.creeper.core_game.GameManager;
 import com.comandante.creeper.items.*;
 import com.comandante.creeper.npc.Npc;
-import com.comandante.creeper.npc.NpcStatsChangeBuilder;
+import com.comandante.creeper.npc.StatsChangeBuilder;
 import com.comandante.creeper.player.Player;
 import com.comandante.creeper.server.player_communication.Color;
 import com.comandante.creeper.stats.StatsBuilder;
@@ -13,6 +13,7 @@ import com.comandante.creeper.world.model.Room;
 import java.text.NumberFormat;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.Set;
 
 public class DirtyBombUseAction implements ItemUseAction {
@@ -39,13 +40,13 @@ public class DirtyBombUseAction implements ItemUseAction {
         for (String npcId : npcIds) {
             Npc npc = gameManager.getEntityManager().getNpcEntity(npcId);
             gameManager.writeToPlayerCurrentRoom(player.getPlayerId(), npc.getColorName() + " is heavily damaged by a " + item.getItemName() + "!" + Color.YELLOW + " +" + NumberFormat.getNumberInstance(Locale.US).format(900000000) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET);
-            NpcStatsChangeBuilder npcStatsChangeBuilder = new NpcStatsChangeBuilder();
+            StatsChangeBuilder statsChangeBuilder = new StatsChangeBuilder();
             final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + " +" + NumberFormat.getNumberInstance(Locale.US).format(900000000) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + npc.getColorName();
-            npcStatsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-900000000).createStats());
-            npcStatsChangeBuilder.setDamageStrings(Arrays.asList(fightMsg));
-            npcStatsChangeBuilder.setPlayer(player);
-            npcStatsChangeBuilder.setIsItemDamage(true);
-            npc.addNpcDamage(npcStatsChangeBuilder.createNpcStatsChange());
+            statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-900000000).createStats());
+            statsChangeBuilder.setDamageStrings(Arrays.asList(fightMsg));
+            statsChangeBuilder.setPlayer(player);
+            statsChangeBuilder.setIsItemDamage(true);
+            npc.addNpcDamage(statsChangeBuilder.createNpcStatsChange());
         }
         Set<Player> presentPlayers = currentRoom.getPresentPlayers();
         for (Player presentPlayer : presentPlayers) {
@@ -54,7 +55,7 @@ public class DirtyBombUseAction implements ItemUseAction {
                 continue;
             }
             gameManager.writeToPlayerCurrentRoom(player.getPlayerId(), presentPlayer.getPlayerName() + " is heavily damaged by a " + item.getItemName() + "!");
-            presentPlayer.updatePlayerHealth(-Long.MAX_VALUE, null);
+            presentPlayer.updatePlayerHealth(-Long.MAX_VALUE, Optional.empty(), Optional.empty());
         }
     }
 
diff --git a/src/main/java/com/comandante/creeper/npc/BasicNpcPlayerDamageProcessor.java b/src/main/java/com/comandante/creeper/npc/BasicNpcPlayerDamageProcessor.java
index 6fecba38..57598fc5 100644
--- a/src/main/java/com/comandante/creeper/npc/BasicNpcPlayerDamageProcessor.java
+++ b/src/main/java/com/comandante/creeper/npc/BasicNpcPlayerDamageProcessor.java
@@ -42,6 +42,21 @@ public class BasicNpcPlayerDamageProcessor implements DamageProcessor {
         return (int) (5 + (.20f * npc.getStats().getAim()));
     }
 
+    @Override
+    public long getAttackAmount(Player player, Player targetPlayer) {
+        throw new RuntimeException("Invalid! Npc damage processor can't calculate PVP damage.");
+    }
+
+    @Override
+    public int getChanceToHit(Player player, Player targetPlayer) {
+        throw new RuntimeException("Invalid! Npc damage processor can't calculate PVP damage.");
+    }
+
+    @Override
+    public int getCriticalChance(Player player, Player targetPlayer) {
+        throw new RuntimeException("Invalid! Npc damage processor can't calculate PVP damage.");
+    }
+
     private int randInt(int min, int max) {
         return random.nextInt((max - min) + 1) + min;
     }
diff --git a/src/main/java/com/comandante/creeper/npc/Npc.java b/src/main/java/com/comandante/creeper/npc/Npc.java
index 7e7c1f8f..2b77e01c 100644
--- a/src/main/java/com/comandante/creeper/npc/Npc.java
+++ b/src/main/java/com/comandante/creeper/npc/Npc.java
@@ -204,7 +204,7 @@ public class Npc extends CreeperEntity {
                 for (String message : npcStatsChange.getPlayerDamageStrings()) {
                     if (!npcStatsChange.getSourcePlayer().isActive(CoolDownType.DEATH)) {
                         gameManager.getChannelUtils().write(npcStatsChange.getSourcePlayer().getPlayerId(), message + "\r\n", true);
-                        npcStatsChange.getSourcePlayer().updatePlayerHealth(npcStatsChange.getSourcePlayerStatsChange().getCurrentHealth(), this);
+                        npcStatsChange.getSourcePlayer().updatePlayerHealth(npcStatsChange.getSourcePlayerStatsChange().getCurrentHealth(), Optional.empty(), Optional.of(this));
                     }
                 }
             }
@@ -423,7 +423,7 @@ public class Npc extends CreeperEntity {
 
     public void doHealthDamage(Player player, List<String> damageStrings, long amt) {
         StatsChange npcStatsChange =
-                new NpcStatsChangeBuilder().setStats(new StatsBuilder().setCurrentHealth(amt).createStats()).setDamageStrings(damageStrings).setPlayer(player).createNpcStatsChange();
+                new StatsChangeBuilder().setStats(new StatsBuilder().setCurrentHealth(amt).createStats()).setDamageStrings(damageStrings).setPlayer(player).createNpcStatsChange();
         addNpcDamage(npcStatsChange);
     }
 
diff --git a/src/main/java/com/comandante/creeper/npc/StatsChange.java b/src/main/java/com/comandante/creeper/npc/StatsChange.java
index ab7fb284..fdab7915 100644
--- a/src/main/java/com/comandante/creeper/npc/StatsChange.java
+++ b/src/main/java/com/comandante/creeper/npc/StatsChange.java
@@ -1,49 +1,49 @@
-package com.comandante.creeper.npc;
-
-import com.comandante.creeper.player.Player;
-import com.comandante.creeper.stats.Stats;
-
-import java.util.List;
-
-public class StatsChange {
-
-    private final Stats targetStatsChange;
-    private final List<String> damageStrings;
-    private final List<String> playerDamageStrings;
-    private final Player sourcePlayer;
-    private final Stats sourcePlayerStatsChange;
-    private boolean isItemDamage;
-
-    public StatsChange(Stats targetStatsChange, List<String> damageStrings, Player sourcePlayer, Stats sourcePlayerStatsChange, List<String> playerDamageStrings, boolean isItemDamage) {
-        this.targetStatsChange = targetStatsChange;
-        this.damageStrings = damageStrings;
-        this.sourcePlayer = sourcePlayer;
-        this.sourcePlayerStatsChange = sourcePlayerStatsChange;
-        this.playerDamageStrings = playerDamageStrings;
-        this.isItemDamage = isItemDamage;
-    }
-
-    public Stats getTargetStatsChange() {
-        return targetStatsChange;
-    }
-
-    public List<String> getDamageStrings() {
-        return damageStrings;
-    }
-
-    public Player getSourcePlayer() {
-        return sourcePlayer;
-    }
-
-    public Stats getSourcePlayerStatsChange() {
-        return sourcePlayerStatsChange;
-    }
-
-    public List<String> getPlayerDamageStrings() {
-        return playerDamageStrings;
-    }
-
-    public boolean isItemDamage() {
-        return isItemDamage;
-    }
-}
+package com.comandante.creeper.npc;
+
+import com.comandante.creeper.player.Player;
+import com.comandante.creeper.stats.Stats;
+
+import java.util.List;
+
+public class StatsChange {
+
+    private final Stats targetStatsChange;
+    private final List<String> damageStrings;
+    private final List<String> playerDamageStrings;
+    private final Player sourcePlayer;
+    private final Stats sourcePlayerStatsChange;
+    private boolean isItemDamage;
+
+    public StatsChange(Stats targetStatsChange, List<String> damageStrings, Player sourcePlayer, Stats sourcePlayerStatsChange, List<String> playerDamageStrings, boolean isItemDamage) {
+        this.targetStatsChange = targetStatsChange;
+        this.damageStrings = damageStrings;
+        this.sourcePlayer = sourcePlayer;
+        this.sourcePlayerStatsChange = sourcePlayerStatsChange;
+        this.playerDamageStrings = playerDamageStrings;
+        this.isItemDamage = isItemDamage;
+    }
+
+    public Stats getTargetStatsChange() {
+        return targetStatsChange;
+    }
+
+    public List<String> getDamageStrings() {
+        return damageStrings;
+    }
+
+    public Player getSourcePlayer() {
+        return sourcePlayer;
+    }
+
+    public Stats getSourcePlayerStatsChange() {
+        return sourcePlayerStatsChange;
+    }
+
+    public List<String> getPlayerDamageStrings() {
+        return playerDamageStrings;
+    }
+
+    public boolean isItemDamage() {
+        return isItemDamage;
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/npc/NpcStatsChangeBuilder.java b/src/main/java/com/comandante/creeper/npc/StatsChangeBuilder.java
similarity index 65%
rename from src/main/java/com/comandante/creeper/npc/NpcStatsChangeBuilder.java
rename to src/main/java/com/comandante/creeper/npc/StatsChangeBuilder.java
index af188a48..9788f54d 100644
--- a/src/main/java/com/comandante/creeper/npc/NpcStatsChangeBuilder.java
+++ b/src/main/java/com/comandante/creeper/npc/StatsChangeBuilder.java
@@ -1,49 +1,49 @@
-package com.comandante.creeper.npc;
-
-import com.comandante.creeper.player.Player;
-import com.comandante.creeper.stats.Stats;
-
-import java.util.List;
-
-public class NpcStatsChangeBuilder {
-    private Stats stats;
-    private List<String> damageStrings;
-    private Player player;
-    private Stats playerStatsChange;
-    private List<String> playerDamageStrings;
-    private boolean isItemDamage;
-
-    public NpcStatsChangeBuilder setStats(Stats stats) {
-        this.stats = stats;
-        return this;
-    }
-
-    public NpcStatsChangeBuilder setDamageStrings(List<String> damageStrings) {
-        this.damageStrings = damageStrings;
-        return this;
-    }
-
-    public NpcStatsChangeBuilder setPlayer(Player player) {
-        this.player = player;
-        return this;
-    }
-
-    public NpcStatsChangeBuilder setPlayerStatsChange(Stats playerStatsChange) {
-        this.playerStatsChange = playerStatsChange;
-        return this;
-    }
-
-    public NpcStatsChangeBuilder setPlayerDamageStrings(List<String> playerDamageStrings) {
-        this.playerDamageStrings = playerDamageStrings;
-        return this;
-    }
-
-    public NpcStatsChangeBuilder setIsItemDamage(boolean isItemDamage) {
-        this.isItemDamage = isItemDamage;
-        return this;
-    }
-
-    public StatsChange createNpcStatsChange() {
-        return new StatsChange(stats, damageStrings, player, playerStatsChange, playerDamageStrings, isItemDamage);
-    }
+package com.comandante.creeper.npc;
+
+import com.comandante.creeper.player.Player;
+import com.comandante.creeper.stats.Stats;
+
+import java.util.List;
+
+public class StatsChangeBuilder {
+    private Stats stats;
+    private List<String> damageStrings;
+    private Player player;
+    private Stats playerStatsChange;
+    private List<String> playerDamageStrings;
+    private boolean isItemDamage;
+
+    public StatsChangeBuilder setStats(Stats stats) {
+        this.stats = stats;
+        return this;
+    }
+
+    public StatsChangeBuilder setDamageStrings(List<String> damageStrings) {
+        this.damageStrings = damageStrings;
+        return this;
+    }
+
+    public StatsChangeBuilder setPlayer(Player player) {
+        this.player = player;
+        return this;
+    }
+
+    public StatsChangeBuilder setPlayerStatsChange(Stats playerStatsChange) {
+        this.playerStatsChange = playerStatsChange;
+        return this;
+    }
+
+    public StatsChangeBuilder setPlayerDamageStrings(List<String> playerDamageStrings) {
+        this.playerDamageStrings = playerDamageStrings;
+        return this;
+    }
+
+    public StatsChangeBuilder setIsItemDamage(boolean isItemDamage) {
+        this.isItemDamage = isItemDamage;
+        return this;
+    }
+
+    public StatsChange createNpcStatsChange() {
+        return new StatsChange(stats, damageStrings, player, playerStatsChange, playerDamageStrings, isItemDamage);
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/player/BasicPlayerDamageProcessor.java b/src/main/java/com/comandante/creeper/player/BasicPlayerDamageProcessor.java
index fac3a5e4..4e3f46af 100644
--- a/src/main/java/com/comandante/creeper/player/BasicPlayerDamageProcessor.java
+++ b/src/main/java/com/comandante/creeper/player/BasicPlayerDamageProcessor.java
@@ -40,6 +40,37 @@ public class BasicPlayerDamageProcessor implements DamageProcessor {
         return (int) (5 + (.20f * player.getPlayerStatsWithEquipmentAndLevel().getAim()));
     }
 
+    @Override
+    public long getAttackAmount(Player player, Player targetPlayer) {
+        Stats playerStats = player.getPlayerStatsWithEquipmentAndLevel();
+        Stats targetPlayerStats = targetPlayer.getPlayerStatsWithEquipmentAndLevel();
+        long rolls = 0;
+        long totDamage = 0;
+        while (rolls <= playerStats.getNumberOfWeaponRolls()) {
+            rolls++;
+            totDamage = totDamage + randInt((int) playerStats.getWeaponRatingMin(), (int) playerStats.getWeaponRatingMax());
+        }
+        long i = playerStats.getStrength() + totDamage - targetPlayerStats.getArmorRating();
+        if (i < 0) {
+            return 0;
+        } else {
+            return i;
+        }
+    }
+
+    @Override
+    public int getChanceToHit(Player player, Player targetPlayer) {
+        Stats playerStats = player.getPlayerStatsWithEquipmentAndLevel();
+        Stats targetPlayerStats = targetPlayer.getPlayerStatsWithEquipmentAndLevel();
+        return (int) ((playerStats.getStrength() + playerStats.getMeleSkill()) * 5 - targetPlayerStats.getAgile() * 5);
+    }
+
+    @Override
+    public int getCriticalChance(Player player, Player targetPlayer) {
+        //y =.20({x}) + 0
+        return (int) (5 + (.20f * player.getPlayerStatsWithEquipmentAndLevel().getAim()));
+    }
+
     private int randInt(int min, int max) {
         return random.nextInt((max - min) + 1) + min;
     }
diff --git a/src/main/java/com/comandante/creeper/player/CoolDownType.java b/src/main/java/com/comandante/creeper/player/CoolDownType.java
index 6d368dba..e019ea7b 100644
--- a/src/main/java/com/comandante/creeper/player/CoolDownType.java
+++ b/src/main/java/com/comandante/creeper/player/CoolDownType.java
@@ -10,6 +10,7 @@ public enum CoolDownType {
     FORAGE_SUPERSHORT("forage-supershort", 1),
     SPELL("",0),
     NPC_FIGHT("fight",30),
+    PVP_FIGHT("pvp-fight",30),
     NPC_ROAM("npc-roam", 1200),
     NPC_ALERTED("npc-alerted", 30),
     PLAYER_RECALL("recall", 600),
diff --git a/src/main/java/com/comandante/creeper/player/DamageProcessor.java b/src/main/java/com/comandante/creeper/player/DamageProcessor.java
index 72844f57..048257c8 100644
--- a/src/main/java/com/comandante/creeper/player/DamageProcessor.java
+++ b/src/main/java/com/comandante/creeper/player/DamageProcessor.java
@@ -9,4 +9,10 @@ public interface DamageProcessor {
     int getChanceToHit(Player player, Npc npc);
 
     int getCriticalChance(Player player, Npc npc);
+
+    long getAttackAmount(Player player, Player targetPlayer);
+
+    int getChanceToHit(Player player, Player targetPlayer);
+
+    int getCriticalChance(Player player, Player targetPlayer);
 }
diff --git a/src/main/java/com/comandante/creeper/player/Player.java b/src/main/java/com/comandante/creeper/player/Player.java
index 388d1d7e..af34ff10 100644
--- a/src/main/java/com/comandante/creeper/player/Player.java
+++ b/src/main/java/com/comandante/creeper/player/Player.java
@@ -1,1543 +1,1664 @@
-package com.comandante.creeper.player;
-
-
-import com.codahale.metrics.Meter;
-import com.comandante.creeper.Main;
-import com.comandante.creeper.common.CreeperUtils;
-import com.comandante.creeper.core_game.GameManager;
-import com.comandante.creeper.core_game.SentryManager;
-import com.comandante.creeper.entity.CreeperEntity;
-import com.comandante.creeper.items.*;
-import com.comandante.creeper.npc.Npc;
-import com.comandante.creeper.npc.StatsChange;
-import com.comandante.creeper.npc.NpcStatsChangeBuilder;
-import com.comandante.creeper.npc.Temperament;
-import com.comandante.creeper.server.player_communication.Color;
-import com.comandante.creeper.stats.Levels;
-import com.comandante.creeper.stats.Stats;
-import com.comandante.creeper.stats.StatsBuilder;
-import com.comandante.creeper.stats.StatsHelper;
-import com.comandante.creeper.world.model.Room;
-import com.google.common.collect.*;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.log4j.Logger;
-import org.jboss.netty.channel.Channel;
-import org.nocrala.tools.texttablefmt.BorderStyle;
-import org.nocrala.tools.texttablefmt.ShownBorders;
-import org.nocrala.tools.texttablefmt.Table;
-
-import java.text.NumberFormat;
-import java.util.*;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-
-public class Player extends CreeperEntity {
-
-    private static final Logger log = Logger.getLogger(Player.class);
-    private final GameManager gameManager;
-    private final String playerId;
-    private final Interner<String> interner = Interners.newWeakInterner();
-    private final Random random = new Random();
-    private String playerName;
-    private Channel channel;
-    private Optional<String> returnDirection = Optional.empty();
-    private Room currentRoom;
-    private SortedMap<Long, ActiveFight> activeFights = Collections.synchronizedSortedMap(new TreeMap<Long, ActiveFight>());
-    private int tickBucket = 0;
-    private int fightTickBucket = 0;
-    private final Set<Npc> alertedNpcs = Sets.newHashSet();
-    private Optional<Room> previousRoom = Optional.empty();
-    private final ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1000);
-    private AtomicBoolean isChatMode = new AtomicBoolean(false);
-
-    private final ArrayBlockingQueue<StatsChange> playerStatChanges = new ArrayBlockingQueue<>(3000);
-    private Map<String, Long> playerDamageMap = Maps.newHashMap();
-
-
-    public static final int FIGHT_TICK_BUCKET_SIZE = 4;
-
-
-    public Player(String playerName, GameManager gameManager) {
-        this.playerName = playerName;
-        this.playerId = new String(Base64.encodeBase64(playerName.getBytes()));
-        this.gameManager = gameManager;
-    }
-
-    @Override
-    public void run() {
-        synchronized (interner.intern(playerId)) {
-            try {
-                if (processTickBucket(10)) {
-                    processRegens();
-                    processEffects();
-                    if (activeFights.size() > 0) {
-                        writePrompt();
-                    }
-                }
-                tickAllActiveCoolDowns();
-                activateNextPrimaryActiveFight();
-                if (processFightTickBucket(FIGHT_TICK_BUCKET_SIZE)) {
-                    processFightRounds();
-                }
-            } catch (Exception e) {
-                log.error("Player ticker failed! + " + playerName, e);
-                SentryManager.logSentry(this.getClass(), e, "Player ticker problem!");
-            }
-        }
-    }
-
-    private boolean processTickBucket(int numberOfTicksToFillBucket) {
-        if (tickBucket == numberOfTicksToFillBucket) {
-            tickBucket = 0;
-            return true;
-        } else {
-            tickBucket = tickBucket + 1;
-            return false;
-        }
-    }
-
-    private boolean processFightTickBucket(int numberOfTicksToFillBucket) {
-        if (fightTickBucket == numberOfTicksToFillBucket) {
-            fightTickBucket = 0;
-            return true;
-        } else {
-            fightTickBucket = fightTickBucket + 1;
-            return false;
-        }
-    }
-
-    private void processFightRounds() {
-
-        DamageProcessor playerDamageProcesor = getPlayerClass().getDamageProcessor();
-        Set<Map.Entry<Long, ActiveFight>> entries = activeFights.entrySet();
-        for (Map.Entry<Long, ActiveFight> next : entries) {
-            Optional<String> npcIdOptional = next.getValue().getNpcId();
-            if (npcIdOptional.isPresent()) {
-                // If the NPC has died- bail out.
-                String npcId = npcIdOptional.get();
-                addCoolDown(new CoolDown(CoolDownType.NPC_FIGHT));
-                Npc npc = gameManager.getEntityManager().getNpcEntity(npcId);
-                if (npc == null) {
-                    continue;
-                }
-                doFightRound(playerDamageProcesor, npc.getDamageProcessor(), next.getValue());
-            }
-        }
-    }
-
-    private void processRegens() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Stats stats = getPlayerStatsWithEquipmentAndLevel();
-            if (isActive(CoolDownType.NPC_FIGHT) || isActive(CoolDownType.DEATH)) {
-                return;
-            }
-            if (playerMetadata.getStats().getCurrentHealth() < stats.getMaxHealth()) {
-                updatePlayerHealth((int) (stats.getMaxHealth() * .05), null);
-            }
-            if (playerMetadata.getStats().getCurrentMana() < stats.getMaxMana()) {
-                addMana((int) (stats.getMaxMana() * .03));
-            }
-        }
-    }
-
-    private void processEffects() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            List<Effect> effectsToRemove = Lists.newArrayList();
-            for (Effect effect : playerMetadata.getEffects()) {
-                if (effect.getEffectApplications() >= effect.getMaxEffectApplications()) {
-                    gameManager.getChannelUtils().write(playerId, Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + effect.getEffectName() + " has worn off.\r\n", true);
-                    effectsToRemove.add(effect);
-                    continue;
-                } else {
-                    effect.setEffectApplications(effect.getEffectApplications() + 1);
-                    gameManager.getEffectsManager().application(effect, this);
-                }
-
-            }
-            for (Effect effect : effectsToRemove) {
-                playerMetadata.removeEffect(effect);
-            }
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void killPlayer(Npc npc) {
-        resetEffects();
-        synchronized (interner.intern(playerId)) {
-            if (npc != null && doesActiveFightExist(npc)) {
-                removeAllActiveFights();
-            }
-            if (!isActive(CoolDownType.DEATH)) {
-                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-                if (!playerMetadataOptional.isPresent()) {
-                    return;
-                }
-                PlayerMetadata playerMetadata = playerMetadataOptional.get();
-                long newGold = playerMetadata.getGold() / 2;
-                playerMetadata.setGold(newGold);
-                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-                if (newGold > 0) {
-                    gameManager.getChannelUtils().write(getPlayerId(), "You just " + Color.BOLD_ON + Color.RED + "lost " + Color.RESET + newGold + Color.YELLOW + " gold" + Color.RESET + "!\r\n");
-                }
-                removeActiveAlertStatus();
-                CoolDown death = new CoolDown(CoolDownType.DEATH);
-                addCoolDown(death);
-                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " is now dead." + "\r\n");
-                PlayerMovement playerMovement = new PlayerMovement(this, gameManager.getRoomManager().getPlayerCurrentRoom(this).get().getRoomId(), GameManager.LOBBY_ID, "vanished into the ether.", "");
-                movePlayer(playerMovement);
-                String prompt = gameManager.buildPrompt(playerId);
-                gameManager.getChannelUtils().write(getPlayerId(), prompt, true);
-            }
-        }
-    }
-
-    public void killPlayer(Player sourcePlayer) {
-        resetEffects();
-        synchronized (interner.intern(playerId)) {
-            if (npc != null && doesActiveFightExist(npc)) {
-                removeAllActiveFights();
-            }
-            if (!isActive(CoolDownType.DEATH)) {
-                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-                if (!playerMetadataOptional.isPresent()) {
-                    return;
-                }
-                PlayerMetadata playerMetadata = playerMetadataOptional.get();
-                long newGold = playerMetadata.getGold() / 2;
-                playerMetadata.setGold(newGold);
-                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-                if (newGold > 0) {
-                    gameManager.getChannelUtils().write(getPlayerId(), "You just " + Color.BOLD_ON + Color.RED + "lost " + Color.RESET + newGold + Color.YELLOW + " gold" + Color.RESET + "!\r\n");
-                }
-                removeActiveAlertStatus();
-                CoolDown death = new CoolDown(CoolDownType.DEATH);
-                addCoolDown(death);
-                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " is now dead." + "\r\n");
-                PlayerMovement playerMovement = new PlayerMovement(this, gameManager.getRoomManager().getPlayerCurrentRoom(this).get().getRoomId(), GameManager.LOBBY_ID, "vanished into the ether.", "");
-                movePlayer(playerMovement);
-                String prompt = gameManager.buildPrompt(playerId);
-                gameManager.getChannelUtils().write(getPlayerId(), prompt, true);
-            }
-        }
-    }
-
-
-    public boolean updatePlayerHealth(long amount, Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            if (amount > 0) {
-                addHealth(amount, playerMetadata);
-                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-                return false;
-            } else {
-                Stats stats = playerMetadata.getStats();
-                if ((stats.getCurrentHealth() + amount) < 0) {
-                    stats.setCurrentHealth(0);
-                } else {
-                    stats.setCurrentHealth(stats.getCurrentHealth() + amount);
-                }
-                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-                if (playerMetadata.getStats().getCurrentHealth() == 0) {
-                    killPlayer(npc);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private void processStatsChange(StatsChange statsChange) {
-        try {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            Optional<PlayerMetadata> sourcePlayerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(statsChange.getSourcePlayer().getPlayerId());
-            if (!playerMetadataOptional.isPresent() || !sourcePlayerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata targetPlayerMetadata = playerMetadataOptional.get();
-            PlayerMetadata sourcePlayerMetadata = sourcePlayerMetadataOptional.get();
-            if (statsChange.getSourcePlayer().isActive(CoolDownType.DEATH) && !statsChange.isItemDamage()) {
-                return;
-            }
-            if (!isAlive.get()) {
-                return;
-            }
-            if (statsChange.getTargetStatsChange() == null) {
-                return;
-            }
-            for (String message : statsChange.getDamageStrings()) {
-                if (!statsChange.getSourcePlayer().isActive(CoolDownType.DEATH)) {
-                    gameManager.getChannelUtils().write(statsChange.getSourcePlayer().getPlayerId(), message + "\r\n", true);
-                }
-            }
-            Stats stats = targetPlayerMetadata.getStats();
-            StatsHelper.combineStats(stats, statsChange.getTargetStatsChange());
-            long amt = statsChange.getTargetStatsChange().getCurrentHealth();
-            long damageReportAmt = -statsChange.getTargetStatsChange().getCurrentHealth();
-
-            if (stats.getCurrentHealth() < 0) {
-                damageReportAmt = -amt + stats.getCurrentHealth();
-                stats.setCurrentHealth(0);
-            }
-            long damage = 0;
-            if (playerDamageMap.containsKey(statsChange.getSourcePlayer().getPlayerId())) {
-                damage = playerDamageMap.get(statsChange.getSourcePlayer().getPlayerId());
-            }
-            addDamageToMap(statsChange.getSourcePlayer().getPlayerId(), damage + damageReportAmt);
-            if (stats.getCurrentHealth() == 0) {
-                killPlayer(statsChange.getSourcePlayer());
-                return;
-            }
-            if (statsChange.getSourcePlayerStatsChange() != null) {
-                for (String message : statsChange.getPlayerDamageStrings()) {
-                    if (!statsChange.getSourcePlayer().isActive(CoolDownType.DEATH)) {
-                        gameManager.getChannelUtils().write(statsChange.getSourcePlayer().getPlayerId(), message + "\r\n", true);
-                        statsChange.getSourcePlayer().updatePlayerHealth(statsChange.getSourcePlayerStatsChange().getCurrentHealth(), this);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            SentryManager.logSentry(this.getClass(), e, "Problem processing NPC Stat Change!");
-        }
-    }
-
-    public void addDamageToMap(String playerId, long amt) {
-        playerDamageMap.put(playerId, amt);
-    }
-
-    public Optional<Room> getPreviousRoom() {
-        synchronized (interner.intern(playerId)) {
-            return previousRoom;
-        }
-    }
-
-    public void setPreviousRoom(Room previousRoom) {
-        synchronized (interner.intern(playerId)) {
-            this.previousRoom = Optional.ofNullable(previousRoom);
-        }
-    }
-
-    public void writeMessage(String msg) {
-        gameManager.getChannelUtils().write(getPlayerId(), msg);
-    }
-
-    public long getAvailableMana() {
-        return getPlayerStatsWithEquipmentAndLevel().getCurrentMana();
-    }
-
-
-    private void addHealth(long addAmt, PlayerMetadata playerMetadata) {
-        long currentHealth = playerMetadata.getStats().getCurrentHealth();
-        Stats statsModifier = getPlayerStatsWithEquipmentAndLevel();
-        long maxHealth = statsModifier.getMaxHealth();
-        long proposedNewAmt = currentHealth + addAmt;
-        if (proposedNewAmt > maxHealth) {
-            if (currentHealth < maxHealth) {
-                long adjust = proposedNewAmt - maxHealth;
-                proposedNewAmt = proposedNewAmt - adjust;
-            } else {
-                proposedNewAmt = proposedNewAmt - addAmt;
-            }
-        }
-        playerMetadata.getStats().setCurrentHealth(proposedNewAmt);
-    }
-
-    public void addMana(long addAmt) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            long currentMana = playerMetadata.getStats().getCurrentMana();
-            Stats statsModifier = getPlayerStatsWithEquipmentAndLevel();
-            long maxMana = statsModifier.getMaxMana();
-            long proposedNewAmt = currentMana + addAmt;
-            if (proposedNewAmt > maxMana) {
-                if (currentMana < maxMana) {
-                    long adjust = proposedNewAmt - maxMana;
-                    proposedNewAmt = proposedNewAmt - adjust;
-                } else {
-                    proposedNewAmt = proposedNewAmt - addAmt;
-                }
-            }
-            playerMetadata.getStats().setCurrentMana(proposedNewAmt);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void addExperience(long exp) {
-        synchronized (interner.intern(playerId)) {
-            final Meter requests = Main.metrics.meter("experience-" + playerName);
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            long currentExperience = playerMetadata.getStats().getExperience();
-            long currentLevel = Levels.getLevel(currentExperience);
-            playerMetadata.getStats().setExperience(currentExperience + exp);
-            requests.mark(exp);
-            long newLevel = Levels.getLevel(playerMetadata.getStats().getExperience());
-            if (newLevel > currentLevel) {
-                gameManager.announceLevelUp(playerName, currentLevel, newLevel);
-            }
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public long getLevel() {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        return playerMetadataOptional.map(playerMetadata -> Levels.getLevel(playerMetadata.getStats().getExperience())).orElse(0L);
-    }
-
-    private Optional<PlayerMetadata> getPlayerMetadata() {
-        return gameManager.getPlayerManager().getPlayerMetadata(playerId);
-    }
-
-    private void savePlayerMetadata(PlayerMetadata playerMetadata) {
-        gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-    }
-
-    public long getCurrentHealth() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
-            return playerMetadataOptional.map(playerMetadata -> playerMetadata.getStats().getCurrentHealth()).orElse(0L);
-        }
-    }
-
-    public void transferGoldToBank(long amt) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.transferGoldToBank(amt);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void transferBankGoldToPlayer(long amt) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.transferBankGoldToPlayer(amt);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void incrementGold(long amt) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.incrementGold(amt);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public boolean addEffect(Effect effect) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            if (playerMetadata.getEffects() != null && (playerMetadata.getEffects().size() >= playerMetadata.getStats().getMaxEffects())) {
-                return false;
-            }
-            playerMetadata.addEffect(effect);
-            savePlayerMetadata(playerMetadata);
-            return true;
-        }
-    }
-
-    public void resetEffects() {
-        synchronized (interner) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.resetEffects();
-            gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void addLearnedSpellByName(String spellName) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.addLearnedSpellByName(spellName);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public boolean doesHaveSpellLearned(String spellName) {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (!playerMetadataOptional.isPresent()) {
-            return false;
-        }
-        PlayerMetadata playerMetadata = playerMetadataOptional.get();
-        if (playerMetadata.getLearnedSpells() == null || playerMetadata.getLearnedSpells().length == 0) {
-            return false;
-        }
-        List<String> learnedSpells = Arrays.asList(playerMetadata.getLearnedSpells());
-        return learnedSpells.contains(spellName);
-    }
-
-    public void removeLearnedSpellByName(String spellName) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.removeLearnedSpellByName(spellName);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public List<String> getLearnedSpells() {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (!playerMetadataOptional.isPresent()) {
-            return Lists.newArrayList();
-        }
-        PlayerMetadata playerMetadata = playerMetadataOptional.get();
-        return Lists.newArrayList(playerMetadata.getLearnedSpells());
-    }
-
-    public boolean isActiveAlertNpcStatus(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            return alertedNpcs.contains(npc);
-        }
-    }
-
-    public boolean isActiveAlertNpcStatus() {
-        synchronized (interner.intern(playerId)) {
-            return alertedNpcs.size() > 0;
-        }
-    }
-
-    public boolean areAnyAlertedNpcsInCurrentRoom() {
-        return currentRoom.getPresentNpcs().stream().filter(this::isActiveAlertNpcStatus).count() > 0;
-    }
-
-    public boolean areInTheSameRoom(Npc npc) {
-        return currentRoom.getPresentNpcs().contains(npc);
-    }
-
-    public void setIsActiveAlertNpcStatus(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            alertedNpcs.add(npc);
-        }
-    }
-
-    public void removeActiveAlertStatus() {
-        synchronized (interner.intern(playerId)) {
-            alertedNpcs.clear();
-        }
-    }
-
-    public void removeActiveAlertStatus(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            alertedNpcs.clear();
-        }
-    }
-
-    public Set<Npc> getAlertedNpcs() {
-        return alertedNpcs;
-    }
-
-    public void addInventoryId(String inventoryId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.addInventoryEntityId(inventoryId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void transferItemToLocker(String inventoryId) {
-        synchronized (interner.intern(playerId)) {
-            removeInventoryId(inventoryId);
-            addLockerInventoryId(inventoryId);
-        }
-    }
-
-    public void removeInventoryId(String inventoryId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.removeInventoryEntityId(inventoryId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void addLockerInventoryId(String entityId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.addLockerEntityId(entityId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void addNpcKillLog(String npcName) {
-        gameManager.getEventProcessor().addEvent(() -> {
-            synchronized (interner.intern(playerId)) {
-                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-                if (!playerMetadataOptional.isPresent()) {
-                    return;
-                }
-                PlayerMetadata playerMetadata = playerMetadataOptional.get();
-                playerMetadata.addNpcKill(npcName);
-                savePlayerMetadata(playerMetadata);
-            }
-        });
-    }
-
-    public void transferItemFromLocker(String entityId) {
-        synchronized (interner.intern(playerId)) {
-            if (gameManager.acquireItem(this, entityId)) {
-                removeLockerInventoryId(entityId);
-            }
-        }
-    }
-
-    public void removeLockerInventoryId(String lockerInventoryId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.removeLockerEntityId(lockerInventoryId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void updatePlayerMana(int amount) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Stats stats = playerMetadata.getStats();
-            stats.setCurrentMana(stats.getCurrentMana() + amount);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void updatePlayerForageExperience(int amount) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Stats stats = playerMetadata.getStats();
-            stats.setForaging(stats.getForaging() + amount);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void addCoolDown(CoolDown coolDown) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.addCoolDown(coolDown);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public Set<CoolDown> getCoolDowns() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return Sets.newHashSet();
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            return playerMetadata.getCoolDowns();
-        }
-    }
-
-    public boolean isActiveCoolDown() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            return playerMetadata.getCoolDowns().size() > 0;
-        }
-    }
-
-    public boolean isActiveForageCoolDown() {
-        if (isActive(CoolDownType.FORAGE_LONG) ||
-                isActive(CoolDownType.FORAGE_MEDIUM) ||
-                isActive(CoolDownType.FORAGE_SHORT) ||
-                isActive(CoolDownType.FORAGE_SUPERSHORT)) {
-            return true;
-        }
-        return false;
-    }
-
-    public boolean isActive(CoolDownType coolDownType) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Set<CoolDown> coolDowns = playerMetadata.getCoolDowns();
-            for (CoolDown c : coolDowns) {
-                if (c.getCoolDownType().equals(coolDownType)) {
-                    if (c.isActive()) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    public boolean isActiveSpellCoolDown(String spellName) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Set<CoolDown> coolDowns = playerMetadata.getCoolDowns();
-            for (CoolDown coolDown : coolDowns) {
-                if (coolDown.getName().equalsIgnoreCase(spellName)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    private void tickAllActiveCoolDowns() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.getCoolDownMap().entrySet().removeIf(coolDownTypeCoolDownEntry -> {
-                if (coolDownTypeCoolDownEntry.getValue().isActive()) {
-                    coolDownTypeCoolDownEntry.getValue().decrementTick();
-                } else {
-                    if (coolDownTypeCoolDownEntry.getValue().equals(CoolDownType.DEATH)) {
-                        gameManager.getChannelUtils().write(playerId, "You have risen from the dead.\r\n");
-                    }
-                    return true;
-                }
-                return false;
-            });
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void writePrompt() {
-        String prompt = gameManager.buildPrompt(playerId);
-        gameManager.getChannelUtils().write(playerId, prompt, true);
-    }
-
-    public String getPlayerId() {
-        return playerId;
-    }
-
-    public Channel getChannel() {
-        return channel;
-    }
-
-    public void setChannel(Channel channel) {
-        this.channel = channel;
-    }
-
-    public Optional<String> getReturnDirection() {
-        return returnDirection;
-    }
-
-    public void setReturnDirection(Optional<String> returnDirection) {
-        this.returnDirection = returnDirection;
-    }
-
-    public Room getCurrentRoom() {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (currentRoom == null && playerMetadataOptional.isPresent()) {
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Integer currentRoomId = playerMetadata.getCurrentRoomId();
-            if (currentRoomId != null) {
-                this.currentRoom = gameManager.getRoomManager().getRoom(currentRoomId);
-            }
-        }
-        return currentRoom;
-    }
-
-    public void setCurrentRoomAndPersist(Room currentRoom) {
-        // Persisting lazily so that performance doesn't suffer.
-        setCurrentRoom(currentRoom);
-        gameManager.getEventProcessor().addEvent(() -> {
-            synchronized (interner.intern(playerId)) {
-                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-                if (!playerMetadataOptional.isPresent()) {
-                    return;
-                }
-                PlayerMetadata playerMetadata = playerMetadataOptional.get();
-                playerMetadata.setCurrentRoomId(currentRoom.getRoomId());
-                savePlayerMetadata(playerMetadata);
-            }
-        });
-    }
-
-    public void setCurrentRoom(Room room) {
-        this.currentRoom = room;
-    }
-
-    public Map<String, Long> getNpcKillLog() {
-        ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (!playerMetadataOptional.isPresent()) {
-            return Maps.newHashMap();
-        }
-        PlayerMetadata playerMetadata = playerMetadataOptional.get();
-        playerMetadata.getNpcKillLog().forEach(builder::put);
-        return builder.build();
-    }
-
-    public void removePlayerFromRoom(Room room) {
-        synchronized (interner.intern(playerId)) {
-            room.removePresentPlayer(getPlayerId());
-        }
-    }
-
-    public void movePlayer(PlayerMovement playerMovement) {
-        synchronized (interner.intern(playerId)) {
-            Optional<Room> sourceRoom = Optional.empty();
-            if (playerMovement.getSourceRoomId() != null) {
-                sourceRoom = Optional.ofNullable(gameManager.getRoomManager().getRoom(playerMovement.getSourceRoomId()));
-            }
-
-            Room destinationRoom = gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId());
-
-            if (sourceRoom.isPresent()) {
-                removePlayerFromRoom(sourceRoom.get());
-                for (Player next : sourceRoom.get().getPresentPlayers()) {
-                    StringBuilder sb = new StringBuilder();
-                    sb.append(playerMovement.getPlayer().getPlayerName());
-                    sb.append(" ").append(playerMovement.getRoomExitMessage());
-                    gameManager.getChannelUtils().write(next.getPlayerId(), sb.toString(), true);
-                }
-                setPreviousRoom(currentRoom);
-            }
-
-            destinationRoom.addPresentPlayer(playerMovement.getPlayer().getPlayerId());
-            setCurrentRoomAndPersist(destinationRoom);
-            for (Player next : destinationRoom.getPresentPlayers()) {
-                if (next.getPlayerId().equals(playerMovement.getPlayer().getPlayerId())) {
-                    continue;
-                }
-                gameManager.getChannelUtils().write(next.getPlayerId(), playerMovement.getPlayer().getPlayerName() + " arrived.", true);
-            }
-            setReturnDirection(java.util.Optional.ofNullable(playerMovement.getReturnDirection()));
-            gameManager.currentRoomLogic(playerId, gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId()));
-            gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId());
-            processNpcAggro();
-        }
-    }
-
-    public void processNpcAggro() {
-        synchronized (interner.intern(playerId)) {
-            if (isActive(CoolDownType.DEATH)) {
-                return;
-            }
-            List<Npc> aggresiveRoomNpcs = currentRoom.getNpcIds().stream()
-                    .map(npcId -> gameManager.getEntityManager().getNpcEntity(npcId))
-                    .filter(npc -> npc.getTemperament().equals(Temperament.AGGRESSIVE))
-                    .filter(npc -> {
-                        Npc.NpcLevelColor levelColor = npc.getLevelColor((int) Levels.getLevel(getPlayerStatsWithEquipmentAndLevel().getExperience()));
-                        return !levelColor.equals(Npc.NpcLevelColor.WHITE);
-                    })
-                    .collect(Collectors.toList());
-
-            aggresiveRoomNpcs.forEach(npc -> {
-                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " has alerted a " + npc.getColorName() + "\r\n");
-                gameManager.getChannelUtils().write(playerId, "You can return to your previous location by typing \"back\"" + "\r\n");
-                setIsActiveAlertNpcStatus(npc);
-                scheduledExecutor.schedule(() -> {
-                    removeActiveAlertStatus(npc);
-                    if (!areInTheSameRoom(npc)) {
-                        return;
-                    }
-                    if (!npc.getIsAlive().get()) {
-                        return;
-                    }
-                    if (isActive(CoolDownType.DEATH)) {
-                        return;
-                    }
-                    gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " has " + Color.BOLD_ON + Color.RED + "ANGERED" + Color.RESET + " a " + npc.getColorName() + "\r\n");
-                    addActiveFight(npc);
-                }, 5, TimeUnit.SECONDS);
-            });
-        }
-    }
-
-    public Optional<Item> getInventoryItem(String itemKeyword) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return Optional.empty();
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            for (String itemId : playerMetadata.getInventory()) {
-                Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
-                if (!itemOptional.isPresent()) {
-                    log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
-                    continue;
-                }
-                Item itemEntity = itemOptional.get();
-                if (itemEntity.getItemTriggers().contains(itemKeyword)) {
-                    return Optional.of(itemEntity);
-                }
-            }
-            return Optional.empty();
-        }
-    }
-
-    public String getPlayerName() {
-        return playerName;
-    }
-
-    public List<String> getRolledUpLockerInventory() {
-        synchronized (interner.intern(playerId)) {
-            List<String> rolledUp = Lists.newArrayList();
-            List<Item> inventory = getLockerInventory();
-            Map<String, Integer> itemAndCounts = Maps.newHashMap();
-            if (inventory != null) {
-                for (Item item : inventory) {
-                    StringBuilder invItem = new StringBuilder();
-                    invItem.append(item.getItemName());
-                    int maxUses = item.getMaxUses();
-                    if (item.getMaxUses() > 0) {
-                        int remainingUses = maxUses - item.getNumberOfUses();
-                        invItem.append(" - ").append(remainingUses);
-                        if (remainingUses == 1) {
-                            invItem.append(" use left.");
-                        } else {
-                            invItem.append(" uses left.");
-                        }
-                    }
-                    if (itemAndCounts.containsKey(invItem.toString())) {
-                        Integer integer = itemAndCounts.get(invItem.toString());
-                        integer = integer + 1;
-                        itemAndCounts.put(invItem.toString(), integer);
-                    } else {
-                        itemAndCounts.put(invItem.toString(), 1);
-                    }
-                }
-                StringBuilder inventoryLine = new StringBuilder();
-                for (Map.Entry<String, Integer> next : itemAndCounts.entrySet()) {
-                    if (next.getValue() > 1) {
-                        inventoryLine.append(next.getKey()).append(" (").append(next.getValue()).append(")").append("\r\n");
-                    } else {
-                        inventoryLine.append(next.getKey()).append("\r\n");
-                    }
-                }
-                rolledUp.add(inventoryLine.toString());
-            }
-            return rolledUp;
-        }
-    }
-
-    public List<Item> getLockerInventory() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptonal = getPlayerMetadata();
-            if (!playerMetadataOptonal.isPresent()) {
-                return Lists.newArrayList();
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptonal.get();
-            List<Item> inventoryItems = Lists.newArrayList();
-            List<String> inventory = playerMetadata.getLockerInventory();
-            if (inventory != null) {
-                for (String itemId : inventory) {
-                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
-                    if (!itemOptional.isPresent()) {
-                        log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
-                        continue;
-                    }
-                    inventoryItems.add(itemOptional.get());
-                }
-            }
-            inventoryItems.sort(Comparator.comparing(Item::getItemName));
-            return inventoryItems;
-        }
-    }
-
-    public List<String> getRolledUpIntentory() {
-        synchronized (interner.intern(playerId)) {
-            List<String> rolledUp = Lists.newArrayList();
-            List<Item> inventory = getInventory();
-            Map<String, Integer> itemAndCounts = Maps.newHashMap();
-            if (inventory != null) {
-                for (Item item : inventory) {
-                    StringBuilder invItem = new StringBuilder();
-                    invItem.append(item.getItemName());
-                    int maxUses = item.getMaxUses();
-                    if (maxUses > 0) {
-                        int remainingUses = maxUses - item.getNumberOfUses();
-                        invItem.append(" - ").append(remainingUses);
-                        if (remainingUses == 1) {
-                            invItem.append(" use left.");
-                        } else {
-                            invItem.append(" uses left.");
-                        }
-                    }
-                    if (itemAndCounts.containsKey(invItem.toString())) {
-                        Integer integer = itemAndCounts.get(invItem.toString());
-                        integer = integer + 1;
-                        itemAndCounts.put(invItem.toString(), integer);
-                    } else {
-                        itemAndCounts.put(invItem.toString(), 1);
-                    }
-                }
-                StringBuilder inventoryLine = new StringBuilder();
-                for (Map.Entry<String, Integer> next : itemAndCounts.entrySet()) {
-                    if (next.getValue() > 1) {
-                        inventoryLine.append(next.getKey()).append(" (").append(next.getValue()).append(")").append("\r\n");
-                    } else {
-                        inventoryLine.append(next.getKey()).append("\r\n");
-                    }
-                }
-                rolledUp.add(inventoryLine.toString());
-            }
-            return rolledUp;
-        }
-    }
-
-    public List<Item> getInventory() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return Lists.newArrayList();
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            List<Item> inventoryItems = Lists.newArrayList();
-            List<String> inventory = playerMetadata.getInventory();
-            if (inventory != null) {
-                for (String itemId : inventory) {
-                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
-                    if (!itemOptional.isPresent()) {
-                        log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
-                        continue;
-                    }
-                    inventoryItems.add(itemOptional.get());
-                }
-            }
-            inventoryItems.sort(Comparator.comparing(Item::getItemName));
-            return inventoryItems;
-        }
-    }
-
-    public Set<Item> getEquipment() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return Sets.newHashSet();
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Set<Item> equipmentItems = Sets.newHashSet();
-            String[] equipment = playerMetadata.getPlayerEquipment();
-            if (equipment != null) {
-                for (String itemId : equipment) {
-                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
-                    if (!itemOptional.isPresent()) {
-                        log.info("Orphaned equipmentId:" + itemId + " player: " + getPlayerName());
-                        continue;
-                    }
-                    equipmentItems.add(itemOptional.get());
-                }
-            }
-            return equipmentItems;
-        }
-    }
-
-    public void equip(Item item) {
-        synchronized (interner.intern(playerId)) {
-            if (item.getEquipment() == null) {
-                return;
-            }
-            Equipment equipment = item.getEquipment();
-            EquipmentSlotType equipmentSlotType = equipment.getEquipmentSlotType();
-            Optional<Item> slotItemOptional = getSlotItem(equipmentSlotType);
-            if (slotItemOptional.isPresent()) {
-                if (!unEquip(slotItemOptional.get())) {
-                    return;
-                }
-            }
-            gameManager.getChannelUtils().write(playerId, "Equipping " + item.getItemName() + "\r\n");
-            addEquipmentId(item.getItemId());
-            removeInventoryId(item.getItemId());
-        }
-    }
-
-    public Optional<Item> getSlotItem(EquipmentSlotType slot) {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (!playerMetadataOptional.isPresent()) {
-            return Optional.empty();
-        }
-        PlayerMetadata playerMetadata = playerMetadataOptional.get();
-        if (playerMetadata.getPlayerEquipment() == null) {
-            return Optional.empty();
-        }
-        for (String item : playerMetadata.getPlayerEquipment()) {
-            Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(item);
-            if (!itemOptional.isPresent()) {
-                continue;
-            }
-            Item itemEntity = itemOptional.get();
-            EquipmentSlotType equipmentSlotType = itemEntity.getEquipment().getEquipmentSlotType();
-            if (equipmentSlotType.equals(slot)) {
-                return Optional.of(itemEntity);
-            }
-        }
-        return Optional.empty();
-    }
-
-    public boolean unEquip(Item item) {
-        synchronized (interner.intern(playerId)) {
-            gameManager.getChannelUtils().write(playerId, "Un-equipping " + item.getItemName() + "\r\n");
-            if (gameManager.acquireItem(this, item.getItemId())) {
-                removeEquipmentId(item.getItemId());
-                return true;
-            }
-            return false;
-        }
-    }
-
-    public void addEquipmentId(String equipmentId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.addEquipmentEntityId(equipmentId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public void removeEquipmentId(String equipmentId) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.removeEquipmentEntityId(equipmentId);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public String getLookString() {
-        StringBuilder sb = new StringBuilder();
-        Stats origStats = gameManager.getStatsModifierFactory().getStatsModifier(this);
-        Stats modifiedStats = getPlayerStatsWithEquipmentAndLevel();
-        Stats diffStats = StatsHelper.getDifference(modifiedStats, origStats);
-        sb.append(Color.MAGENTA)
-                .append("-+=[ ").append(Color.RESET).append(playerName).append(Color.MAGENTA + " ]=+- " + Color.RESET)
-                .append("\r\n");
-        sb.append("Level ").append(Levels.getLevel(origStats.getExperience())).append(" ")
-                .append(Color.YELLOW).append("[").append(Color.RESET).append(CreeperUtils.capitalize(getPlayerClass().getIdentifier())).append(Color.YELLOW).append("]").append(Color.RESET)
-                .append("\r\n");
-        sb.append("Foraging Level ").append(ForageManager.getLevel(modifiedStats.getForaging())).append("\r\n");
-        sb.append(Color.MAGENTA + "Equip--------------------------------" + Color.RESET).append("\r\n");
-        sb.append(buildEquipmentString()).append("\r\n");
-        sb.append(Color.MAGENTA + "Stats--------------------------------" + Color.RESET).append("\r\n");
-        sb.append(gameManager.buildLookString(playerName, modifiedStats, diffStats)).append("\r\n");
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (playerMetadataOptional.isPresent()) {
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            if (playerMetadata.getEffects() != null && playerMetadata.getEffects().size() > 0) {
-                sb.append(Color.MAGENTA + "Effects--------------------------------" + Color.RESET).append("\r\n");
-                sb.append(buldEffectsString()).append("\r\n");
-            }
-        }
-        StringBuilder finalString = new StringBuilder();
-        Lists.newArrayList(sb.toString().split("[\\r\\n]+")).forEach(s -> finalString.append(CreeperUtils.trimTrailingBlanks(s)).append("\r\n"));
-        return finalString.toString();
-    }
-
-    public Stats getPlayerStatsWithEquipmentAndLevel() {
-        synchronized (interner.intern(playerId)) {
-            StatsBuilder statsBuilder = new StatsBuilder();
-            Stats newStats = statsBuilder.createStats();
-
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return newStats;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            Stats playerStats = gameManager.getStatsModifierFactory().getStatsModifier(this);
-            StatsHelper.combineStats(newStats, playerStats);
-            String[] playerEquipment = playerMetadata.getPlayerEquipment();
-            if (playerEquipment == null) {
-                return playerStats;
-            }
-            for (String equipId : playerEquipment) {
-                Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(equipId);
-                if (!itemOptional.isPresent()) {
-                    continue;
-                }
-                Item itemEntity = itemOptional.get();
-                Equipment equipment = itemEntity.getEquipment();
-                Stats stats = equipment.getStats();
-                StatsHelper.combineStats(newStats, stats);
-            }
-            if (playerMetadata.getEffects() != null) {
-                for (Effect effect : playerMetadata.getEffects()) {
-                    StatsHelper.combineStats(newStats, effect.getDurationStats());
-                }
-            }
-            return newStats;
-        }
-    }
-
-    public PlayerClass getPlayerClass() {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return PlayerClass.BASIC;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            PlayerClass playerClass = playerMetadata.getPlayerClass();
-            if (playerClass == null) {
-                return PlayerClass.BASIC;
-            }
-            return playerClass;
-        }
-    }
-
-    public void setPlayerClass(PlayerClass playerClass) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.setPlayerClass(playerClass);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public String buildEquipmentString() {
-        Table t = new Table(2, BorderStyle.CLASSIC_COMPATIBLE,
-                ShownBorders.NONE);
-        t.setColumnWidth(0, 16, 20);
-
-        List<EquipmentSlotType> all = EquipmentSlotType.getAll();
-        for (EquipmentSlotType slot : all) {
-            t.addCell(capitalize(slot.getName()));
-            Optional<Item> slotItemOptional = getSlotItem(slot);
-            if (slotItemOptional.isPresent()) {
-                t.addCell(slotItemOptional.get().getItemName());
-            } else {
-                t.addCell("");
-            }
-        }
-        return t.render();
-    }
-
-    /* FIGHT FIGHT FIGHT FIGHT */
-
-    public String buldEffectsString() {
-        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-        if (!playerMetadataOptional.isPresent()) {
-            return "";
-        }
-        PlayerMetadata playerMetadata = playerMetadataOptional.get();
-        List<Effect> effects = playerMetadata.getEffects();
-        return gameManager.renderEffectsString(effects);
-    }
-
-    private String capitalize(final String line) {
-        return Character.toUpperCase(line.charAt(0)) + line.substring(1);
-    }
-
-    public void removeAllActiveFights() {
-        synchronized (interner.intern(playerId)) {
-            Iterator<Map.Entry<Long, ActiveFight>> iterator = activeFights.entrySet().iterator();
-            while (iterator.hasNext()) {
-                Map.Entry<Long, ActiveFight> next = iterator.next();
-                iterator.remove();
-            }
-        }
-    }
-
-    public boolean setPlayerSetting(String key, String value) {
-        boolean success;
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return false;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            success = playerMetadata.setSetting(key, value);
-            savePlayerMetadata(playerMetadata);
-        }
-        return success;
-    }
-
-    public Optional<String> getPlayerSetting(String key) {
-        return getPlayerMetadata().flatMap(playerMetadata -> Optional.ofNullable(playerMetadata.getSetting(key)));
-    }
-
-    public void removePlayerSetting(String key) {
-        synchronized (interner.intern(playerId)) {
-            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
-            if (!playerMetadataOptional.isPresent()) {
-                return;
-            }
-            PlayerMetadata playerMetadata = playerMetadataOptional.get();
-            playerMetadata.deleteSetting(key);
-            savePlayerMetadata(playerMetadata);
-        }
-    }
-
-    public Map<String, String> getPlayerSettings() {
-        return getPlayerMetadata().map(PlayerMetadata::getPlayerSettings).orElseGet(Maps::newHashMap);
-    }
-
-    public boolean addActiveFight(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            if (gameManager.getEntityManager().getNpcEntity(npc.getEntityId()) != null) {
-                if (!doesActiveFightExist(npc)) {
-                    addCoolDown(new CoolDown(CoolDownType.NPC_FIGHT));
-                    ActiveFight activeFight = ActiveFight.builder()
-                            .npcId(npc.getEntityId())
-                            .isPrimary(false)
-                            .create();
-                    activeFights.put(System.nanoTime(), activeFight);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    public boolean doesActiveFightExist(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            if (gameManager.getEntityManager().getNpcEntity(npc.getEntityId()) == null) {
-                removeActiveFight(npc);
-            }
-            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
-                ActiveFight fight = entry.getValue();
-                Optional<String> npcIdOptional = fight.getNpcId();
-                if (npcIdOptional.isPresent() && npcIdOptional.get().equals(npc.getEntityId())) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    public void removeActiveFight(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            Iterator<Map.Entry<Long, ActiveFight>> iterator = activeFights.entrySet().iterator();
-            while (iterator.hasNext()) {
-                Map.Entry<Long, ActiveFight> next = iterator.next();
-                if (next.getValue().getNpcId().equals(npc.getEntityId())) {
-                    if (next.getValue().isPrimary()) {
-                    }
-                    iterator.remove();
-                }
-            }
-        }
-    }
-
-    public boolean isActiveFights() {
-        synchronized (interner.intern(playerId)) {
-            if (activeFights.size() > 0) {
-                // Remove any fights with dead NPCs that no longer exist in Entity Manager.
-                activeFights.entrySet().removeIf(next -> next.getValue().getNpcId().isPresent() && gameManager.getEntityManager().getNpcEntity(next.getValue().getNpcId().get()) == null);
-            }
-        }
-        return activeFights.size() > 0;
-    }
-
-    public boolean isValidPrimaryActiveFight(Npc npc) {
-        synchronized (interner.intern(playerId)) {
-            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
-                ActiveFight fight = entry.getValue();
-                Optional<String> npcIdOptional = fight.getNpcId();
-                if (npcIdOptional.isPresent() && fight.getNpcId().get().equals(npc.getEntityId()) && fight.isPrimary()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    public Optional<ActiveFight> getPrimaryActiveFight() {
-        synchronized (interner.intern(playerId)) {
-            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
-                ActiveFight fight = entry.getValue();
-                if (fight.isPrimary()) {
-                    return Optional.of(fight);
-                }
-            }
-            return Optional.empty();
-        }
-    }
-
-    public void activateNextPrimaryActiveFight() {
-        synchronized (interner.intern(playerId)) {
-            if (!getPrimaryActiveFight().isPresent()) {
-                if (activeFights.size() > 0) {
-                    activeFights.get(activeFights.firstKey()).setIsPrimary(true);
-                }
-            }
-        }
-    }
-
-    private void doFightRound(DamageProcessor playerDamageProcessor, DamageProcessor npcDamageProcessor, ActiveFight activeFight) {
-        removeActiveAlertStatus();
-
-        // IF FIGHTING NPC
-        Optional<String> npcIdOptional = activeFight.getNpcId();
-        if (npcIdOptional.isPresent()) {
-            String npcId = npcIdOptional.get();
-            Npc npc = gameManager.getEntityManager().getNpcEntity(npcId);
-            if (npc == null) {
-                return;
-            }
-
-            NpcStatsChangeBuilder npcStatsChangeBuilder = new NpcStatsChangeBuilder().setPlayer(this);
-            if (this.isValidPrimaryActiveFight(npc)) {
-                calculatePlayerDamageToNpc(playerDamageProcessor, npc, npcStatsChangeBuilder);
-            }
-
-            if (this.doesActiveFightExist(npc)) {
-                calculateNpcDamageToPlayer(npcDamageProcessor, npc, npcStatsChangeBuilder);
-            }
-        }
-
-        // IF FIGHTING PLAYER?
-    }
-
-    private void calculatePlayerDamageToNpc(DamageProcessor playerDamageProcessor, Npc npc, NpcStatsChangeBuilder npcStatsChangeBuilder) {
-        long damageToVictim = 0;
-        long chanceToHit = playerDamageProcessor.getChanceToHit(this, npc);
-        if (randInt(0, 100) < chanceToHit) {
-            damageToVictim = playerDamageProcessor.getAttackAmount(this, npc);
-        }
-        if (damageToVictim > 0) {
-            if (randInt(0, 100) > (100 - playerDamageProcessor.getCriticalChance(this, npc))) {
-                long criticalDamage = damageToVictim * 3;
-                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "The " + npc.getColorName() + " was caught off guard by the attack! " + "+" + NumberFormat.getNumberInstance(Locale.US).format(criticalDamage) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + npc.getColorName();
-                npcStatsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-(criticalDamage)).createStats());
-                npcStatsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
-            } else {
-                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "+" + NumberFormat.getNumberInstance(Locale.US).format(damageToVictim) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + npc.getColorName();
-                npcStatsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
-                npcStatsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
-            }
-        } else {
-            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + "You MISS " + npc.getName() + "!";
-            npcStatsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
-            npcStatsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
-        }
-    }
-
-    private void calculateNpcDamageToPlayer(DamageProcessor npcDamageProcessor, Npc npc, NpcStatsChangeBuilder npcStatsChangeBuilder) {
-        int chanceToHitBack = npcDamageProcessor.getChanceToHit(this, npc);
-        long damageBack = npcDamageProcessor.getAttackAmount(this, npc);
-        if (randInt(0, 100) < chanceToHitBack) {
-            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + npc.buildAttackMessage(this.getPlayerName()) + " -" + NumberFormat.getNumberInstance(Locale.US).format(damageBack) + Color.RESET;
-            npcStatsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(-damageBack).createStats());
-            npcStatsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
-
-        } else {
-            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + npc.getColorName() + Color.BOLD_ON + Color.CYAN + " MISSES" + Color.RESET + " you!";
-            npcStatsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(0).createStats());
-            npcStatsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
-        }
-        npc.addNpcDamage(npcStatsChangeBuilder.createNpcStatsChange());
-    }
-
-
-    public SortedMap<Long, ActiveFight> getActiveFights() {
-        return activeFights;
-    }
-
-    private int randInt(int min, int max) {
-        return random.nextInt((max - min) + 1) + min;
-    }
-
-    public Interner<String> getInterner() {
-        return interner;
-    }
-
-
-    public boolean toggleChat() {
-        synchronized (interner.intern(playerId)) {
-            if (isChatModeOn()) {
-                setNotIsChatMode();
-                return false;
-            } else {
-                setIsChatMode();
-                return true;
-            }
-        }
-    }
-
-    public void setIsChatMode() {
-        this.isChatMode.compareAndSet(false, true);
-    }
-
-    public void setNotIsChatMode() {
-        this.isChatMode.compareAndSet(true, false);
-    }
-
-    public boolean isChatModeOn() {
-        return isChatMode.get();
-    }
-}
+package com.comandante.creeper.player;
+
+
+import com.codahale.metrics.Meter;
+import com.comandante.creeper.Main;
+import com.comandante.creeper.common.CreeperUtils;
+import com.comandante.creeper.core_game.GameManager;
+import com.comandante.creeper.core_game.SentryManager;
+import com.comandante.creeper.entity.CreeperEntity;
+import com.comandante.creeper.items.*;
+import com.comandante.creeper.npc.Npc;
+import com.comandante.creeper.npc.StatsChange;
+import com.comandante.creeper.npc.StatsChangeBuilder;
+import com.comandante.creeper.npc.Temperament;
+import com.comandante.creeper.server.player_communication.Color;
+import com.comandante.creeper.stats.Levels;
+import com.comandante.creeper.stats.Stats;
+import com.comandante.creeper.stats.StatsBuilder;
+import com.comandante.creeper.stats.StatsHelper;
+import com.comandante.creeper.world.model.Room;
+import com.google.common.collect.*;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+import org.jboss.netty.channel.Channel;
+import org.nocrala.tools.texttablefmt.BorderStyle;
+import org.nocrala.tools.texttablefmt.ShownBorders;
+import org.nocrala.tools.texttablefmt.Table;
+
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+public class Player extends CreeperEntity {
+
+    private static final Logger log = Logger.getLogger(Player.class);
+    private final GameManager gameManager;
+    private final String playerId;
+    private final Interner<String> interner = Interners.newWeakInterner();
+    private final Random random = new Random();
+    private String playerName;
+    private Channel channel;
+    private Optional<String> returnDirection = Optional.empty();
+    private Room currentRoom;
+    private SortedMap<Long, ActiveFight> activeFights = Collections.synchronizedSortedMap(new TreeMap<Long, ActiveFight>());
+    private int tickBucket = 0;
+    private int fightTickBucket = 0;
+    private final Set<Npc> alertedNpcs = Sets.newHashSet();
+    private Optional<Room> previousRoom = Optional.empty();
+    private final ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1000);
+    private AtomicBoolean isChatMode = new AtomicBoolean(false);
+
+
+    private final AtomicBoolean isAlive = new AtomicBoolean(true);
+    private final ArrayBlockingQueue<StatsChange> playerStatChanges = new ArrayBlockingQueue<>(3000);
+    private Map<String, Long> playerDamageMap = Maps.newHashMap();
+
+
+    public static final int FIGHT_TICK_BUCKET_SIZE = 4;
+
+
+    public Player(String playerName, GameManager gameManager) {
+        this.playerName = playerName;
+        this.playerId = new String(Base64.encodeBase64(playerName.getBytes()));
+        this.gameManager = gameManager;
+    }
+
+    @Override
+    public void run() {
+        synchronized (interner.intern(playerId)) {
+            try {
+                if (processTickBucket(10)) {
+                    processRegens();
+                    processEffects();
+                    if (activeFights.size() > 0) {
+                        writePrompt();
+                    }
+                }
+                List<StatsChange> statsChanges = Lists.newArrayList();
+                playerStatChanges.drainTo(statsChanges);
+                for (StatsChange npcStatsChange : statsChanges) {
+                    processStatsChange(npcStatsChange);
+                }
+                tickAllActiveCoolDowns();
+                activateNextPrimaryActiveFight();
+                if (processFightTickBucket(FIGHT_TICK_BUCKET_SIZE)) {
+                    processFightRounds();
+                }
+            } catch (Exception e) {
+                log.error("Player ticker failed! + " + playerName, e);
+                SentryManager.logSentry(this.getClass(), e, "Player ticker problem!");
+            }
+        }
+    }
+
+    private boolean processTickBucket(int numberOfTicksToFillBucket) {
+        if (tickBucket == numberOfTicksToFillBucket) {
+            tickBucket = 0;
+            return true;
+        } else {
+            tickBucket = tickBucket + 1;
+            return false;
+        }
+    }
+
+    private boolean processFightTickBucket(int numberOfTicksToFillBucket) {
+        if (fightTickBucket == numberOfTicksToFillBucket) {
+            fightTickBucket = 0;
+            return true;
+        } else {
+            fightTickBucket = fightTickBucket + 1;
+            return false;
+        }
+    }
+
+    private void processFightRounds() {
+
+        DamageProcessor playerDamageProcesor = getPlayerClass().getDamageProcessor();
+        Set<Map.Entry<Long, ActiveFight>> entries = activeFights.entrySet();
+        for (Map.Entry<Long, ActiveFight> next : entries) {
+            Optional<String> npcIdOptional = next.getValue().getNpcId();
+            if (npcIdOptional.isPresent()) {
+                // If the NPC has died- bail out.
+                String npcId = npcIdOptional.get();
+                addCoolDown(new CoolDown(CoolDownType.NPC_FIGHT));
+                Npc npc = gameManager.getEntityManager().getNpcEntity(npcId);
+                if (npc == null) {
+                    continue;
+                }
+                doFightRound(playerDamageProcesor, npc.getDamageProcessor(), next.getValue());
+            }
+        }
+    }
+
+    private void processRegens() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Stats stats = getPlayerStatsWithEquipmentAndLevel();
+            if (isActive(CoolDownType.NPC_FIGHT) || isActive(CoolDownType.DEATH)) {
+                return;
+            }
+            if (playerMetadata.getStats().getCurrentHealth() < stats.getMaxHealth()) {
+                updatePlayerHealth((int) (stats.getMaxHealth() * .05), Optional.empty(), Optional.empty());
+            }
+            if (playerMetadata.getStats().getCurrentMana() < stats.getMaxMana()) {
+                addMana((int) (stats.getMaxMana() * .03));
+            }
+        }
+    }
+
+    private void processEffects() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            List<Effect> effectsToRemove = Lists.newArrayList();
+            for (Effect effect : playerMetadata.getEffects()) {
+                if (effect.getEffectApplications() >= effect.getMaxEffectApplications()) {
+                    gameManager.getChannelUtils().write(playerId, Color.BOLD_ON + Color.GREEN + "[effect] " + Color.RESET + effect.getEffectName() + " has worn off.\r\n", true);
+                    effectsToRemove.add(effect);
+                    continue;
+                } else {
+                    effect.setEffectApplications(effect.getEffectApplications() + 1);
+                    gameManager.getEffectsManager().application(effect, this);
+                }
+
+            }
+            for (Effect effect : effectsToRemove) {
+                playerMetadata.removeEffect(effect);
+            }
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void killPlayer(Npc npc) {
+        isAlive.set(false);
+        resetEffects();
+        synchronized (interner.intern(playerId)) {
+            if (npc != null && doesActiveFightExist(npc)) {
+                removeAllActiveFights();
+            }
+            if (!isActive(CoolDownType.DEATH)) {
+                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+                if (!playerMetadataOptional.isPresent()) {
+                    return;
+                }
+                PlayerMetadata playerMetadata = playerMetadataOptional.get();
+                long newGold = playerMetadata.getGold() / 2;
+                playerMetadata.setGold(newGold);
+                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+                if (newGold > 0) {
+                    gameManager.getChannelUtils().write(getPlayerId(), "You just " + Color.BOLD_ON + Color.RED + "lost " + Color.RESET + newGold + Color.YELLOW + " gold" + Color.RESET + "!\r\n");
+                }
+                removeActiveAlertStatus();
+                CoolDown death = new CoolDown(CoolDownType.DEATH);
+                addCoolDown(death);
+                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " is now dead." + "\r\n");
+                PlayerMovement playerMovement = new PlayerMovement(this, gameManager.getRoomManager().getPlayerCurrentRoom(this).get().getRoomId(), GameManager.LOBBY_ID, "vanished into the ether.", "");
+                movePlayer(playerMovement);
+                String prompt = gameManager.buildPrompt(playerId);
+                gameManager.getChannelUtils().write(getPlayerId(), prompt, true);
+            }
+        }
+    }
+
+    public void killPlayer(Player sourcePlayer) {
+        isAlive.set(false);
+        resetEffects();
+        synchronized (interner.intern(playerId)) {
+            if (doesActiveFightExist(sourcePlayer)) {
+                removeAllActiveFights();
+            }
+            if (!isActive(CoolDownType.DEATH)) {
+                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+                if (!playerMetadataOptional.isPresent()) {
+                    return;
+                }
+                PlayerMetadata playerMetadata = playerMetadataOptional.get();
+                long newGold = playerMetadata.getGold() / 2;
+                playerMetadata.setGold(newGold);
+                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+                if (newGold > 0) {
+                    gameManager.getChannelUtils().write(getPlayerId(), "You just " + Color.BOLD_ON + Color.RED + "lost " + Color.RESET + newGold + Color.YELLOW + " gold" + Color.RESET + "!\r\n");
+                }
+                removeActiveAlertStatus();
+                CoolDown death = new CoolDown(CoolDownType.DEATH);
+                addCoolDown(death);
+                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " is now dead." + "\r\n");
+                PlayerMovement playerMovement = new PlayerMovement(this, gameManager.getRoomManager().getPlayerCurrentRoom(this).get().getRoomId(), GameManager.LOBBY_ID, "vanished into the ether.", "");
+                movePlayer(playerMovement);
+                String prompt = gameManager.buildPrompt(playerId);
+                gameManager.getChannelUtils().write(getPlayerId(), prompt, true);
+            }
+        }
+    }
+
+
+    public boolean updatePlayerHealth(long amount, Optional<Player> sourcePlayer, Optional<Npc> sourceNpc) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            if (amount > 0) {
+                addHealth(amount, playerMetadata);
+                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+                return false;
+            } else {
+                Stats stats = playerMetadata.getStats();
+                if ((stats.getCurrentHealth() + amount) < 0) {
+                    stats.setCurrentHealth(0);
+                } else {
+                    stats.setCurrentHealth(stats.getCurrentHealth() + amount);
+                }
+                gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+                if (playerMetadata.getStats().getCurrentHealth() == 0) {
+                    sourceNpc.ifPresent(this::killPlayer);
+                    sourcePlayer.ifPresent(this::killPlayer);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void processStatsChange(StatsChange statsChange) {
+        try {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            Optional<PlayerMetadata> sourcePlayerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(statsChange.getSourcePlayer().getPlayerId());
+            if (!playerMetadataOptional.isPresent() || !sourcePlayerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata targetPlayerMetadata = playerMetadataOptional.get();
+            PlayerMetadata sourcePlayerMetadata = sourcePlayerMetadataOptional.get();
+            if (statsChange.getSourcePlayer().isActive(CoolDownType.DEATH) && !statsChange.isItemDamage()) {
+                return;
+            }
+            if (!isAlive.get()) {
+                return;
+            }
+            if (statsChange.getTargetStatsChange() == null) {
+                return;
+            }
+            for (String message : statsChange.getDamageStrings()) {
+                if (!statsChange.getSourcePlayer().isActive(CoolDownType.DEATH)) {
+                    gameManager.getChannelUtils().write(statsChange.getSourcePlayer().getPlayerId(), message + "\r\n", true);
+                }
+            }
+            Stats stats = targetPlayerMetadata.getStats();
+            StatsHelper.combineStats(stats, statsChange.getTargetStatsChange());
+            long amt = statsChange.getTargetStatsChange().getCurrentHealth();
+            long damageReportAmt = -statsChange.getTargetStatsChange().getCurrentHealth();
+
+            if (stats.getCurrentHealth() < 0) {
+                damageReportAmt = -amt + stats.getCurrentHealth();
+                stats.setCurrentHealth(0);
+            }
+            long damage = 0;
+            if (playerDamageMap.containsKey(statsChange.getSourcePlayer().getPlayerId())) {
+                damage = playerDamageMap.get(statsChange.getSourcePlayer().getPlayerId());
+            }
+            addDamageToMap(statsChange.getSourcePlayer().getPlayerId(), damage + damageReportAmt);
+            if (stats.getCurrentHealth() == 0) {
+                killPlayer(statsChange.getSourcePlayer());
+                return;
+            }
+            if (statsChange.getSourcePlayerStatsChange() != null) {
+                for (String message : statsChange.getPlayerDamageStrings()) {
+                    if (!statsChange.getSourcePlayer().isActive(CoolDownType.DEATH)) {
+                        gameManager.getChannelUtils().write(statsChange.getSourcePlayer().getPlayerId(), message + "\r\n", true);
+                        statsChange.getSourcePlayer().updatePlayerHealth(statsChange.getSourcePlayerStatsChange().getCurrentHealth(), Optional.of(this), Optional.empty());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            SentryManager.logSentry(this.getClass(), e, "Problem processing NPC Stat Change!");
+        }
+    }
+
+    public void addDamageToMap(String playerId, long amt) {
+        playerDamageMap.put(playerId, amt);
+    }
+
+    public Optional<Room> getPreviousRoom() {
+        synchronized (interner.intern(playerId)) {
+            return previousRoom;
+        }
+    }
+
+    public void setPreviousRoom(Room previousRoom) {
+        synchronized (interner.intern(playerId)) {
+            this.previousRoom = Optional.ofNullable(previousRoom);
+        }
+    }
+
+    public void writeMessage(String msg) {
+        gameManager.getChannelUtils().write(getPlayerId(), msg);
+    }
+
+    public long getAvailableMana() {
+        return getPlayerStatsWithEquipmentAndLevel().getCurrentMana();
+    }
+
+
+    private void addHealth(long addAmt, PlayerMetadata playerMetadata) {
+        long currentHealth = playerMetadata.getStats().getCurrentHealth();
+        Stats statsModifier = getPlayerStatsWithEquipmentAndLevel();
+        long maxHealth = statsModifier.getMaxHealth();
+        long proposedNewAmt = currentHealth + addAmt;
+        if (proposedNewAmt > maxHealth) {
+            if (currentHealth < maxHealth) {
+                long adjust = proposedNewAmt - maxHealth;
+                proposedNewAmt = proposedNewAmt - adjust;
+            } else {
+                proposedNewAmt = proposedNewAmt - addAmt;
+            }
+        }
+        playerMetadata.getStats().setCurrentHealth(proposedNewAmt);
+    }
+
+    public void addMana(long addAmt) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            long currentMana = playerMetadata.getStats().getCurrentMana();
+            Stats statsModifier = getPlayerStatsWithEquipmentAndLevel();
+            long maxMana = statsModifier.getMaxMana();
+            long proposedNewAmt = currentMana + addAmt;
+            if (proposedNewAmt > maxMana) {
+                if (currentMana < maxMana) {
+                    long adjust = proposedNewAmt - maxMana;
+                    proposedNewAmt = proposedNewAmt - adjust;
+                } else {
+                    proposedNewAmt = proposedNewAmt - addAmt;
+                }
+            }
+            playerMetadata.getStats().setCurrentMana(proposedNewAmt);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void addExperience(long exp) {
+        synchronized (interner.intern(playerId)) {
+            final Meter requests = Main.metrics.meter("experience-" + playerName);
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            long currentExperience = playerMetadata.getStats().getExperience();
+            long currentLevel = Levels.getLevel(currentExperience);
+            playerMetadata.getStats().setExperience(currentExperience + exp);
+            requests.mark(exp);
+            long newLevel = Levels.getLevel(playerMetadata.getStats().getExperience());
+            if (newLevel > currentLevel) {
+                gameManager.announceLevelUp(playerName, currentLevel, newLevel);
+            }
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public long getLevel() {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        return playerMetadataOptional.map(playerMetadata -> Levels.getLevel(playerMetadata.getStats().getExperience())).orElse(0L);
+    }
+
+    private Optional<PlayerMetadata> getPlayerMetadata() {
+        return gameManager.getPlayerManager().getPlayerMetadata(playerId);
+    }
+
+    private void savePlayerMetadata(PlayerMetadata playerMetadata) {
+        gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+    }
+
+    public long getCurrentHealth() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = gameManager.getPlayerManager().getPlayerMetadata(playerId);
+            return playerMetadataOptional.map(playerMetadata -> playerMetadata.getStats().getCurrentHealth()).orElse(0L);
+        }
+    }
+
+    public void transferGoldToBank(long amt) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.transferGoldToBank(amt);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void transferBankGoldToPlayer(long amt) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.transferBankGoldToPlayer(amt);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void incrementGold(long amt) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.incrementGold(amt);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public boolean addEffect(Effect effect) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            if (playerMetadata.getEffects() != null && (playerMetadata.getEffects().size() >= playerMetadata.getStats().getMaxEffects())) {
+                return false;
+            }
+            playerMetadata.addEffect(effect);
+            savePlayerMetadata(playerMetadata);
+            return true;
+        }
+    }
+
+    public void resetEffects() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.resetEffects();
+            gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void addLearnedSpellByName(String spellName) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.addLearnedSpellByName(spellName);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public boolean doesHaveSpellLearned(String spellName) {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (!playerMetadataOptional.isPresent()) {
+            return false;
+        }
+        PlayerMetadata playerMetadata = playerMetadataOptional.get();
+        if (playerMetadata.getLearnedSpells() == null || playerMetadata.getLearnedSpells().length == 0) {
+            return false;
+        }
+        List<String> learnedSpells = Arrays.asList(playerMetadata.getLearnedSpells());
+        return learnedSpells.contains(spellName);
+    }
+
+    public void removeLearnedSpellByName(String spellName) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.removeLearnedSpellByName(spellName);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public List<String> getLearnedSpells() {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (!playerMetadataOptional.isPresent()) {
+            return Lists.newArrayList();
+        }
+        PlayerMetadata playerMetadata = playerMetadataOptional.get();
+        return Lists.newArrayList(playerMetadata.getLearnedSpells());
+    }
+
+    public boolean isActiveAlertNpcStatus(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            return alertedNpcs.contains(npc);
+        }
+    }
+
+    public boolean isActiveAlertNpcStatus() {
+        synchronized (interner.intern(playerId)) {
+            return alertedNpcs.size() > 0;
+        }
+    }
+
+    public boolean areAnyAlertedNpcsInCurrentRoom() {
+        return currentRoom.getPresentNpcs().stream().filter(this::isActiveAlertNpcStatus).count() > 0;
+    }
+
+    public boolean areInTheSameRoom(Npc npc) {
+        return currentRoom.getPresentNpcs().contains(npc);
+    }
+
+    public void setIsActiveAlertNpcStatus(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            alertedNpcs.add(npc);
+        }
+    }
+
+    public void removeActiveAlertStatus() {
+        synchronized (interner.intern(playerId)) {
+            alertedNpcs.clear();
+        }
+    }
+
+    public void removeActiveAlertStatus(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            alertedNpcs.clear();
+        }
+    }
+
+    public Set<Npc> getAlertedNpcs() {
+        return alertedNpcs;
+    }
+
+    public void addInventoryId(String inventoryId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.addInventoryEntityId(inventoryId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void transferItemToLocker(String inventoryId) {
+        synchronized (interner.intern(playerId)) {
+            removeInventoryId(inventoryId);
+            addLockerInventoryId(inventoryId);
+        }
+    }
+
+    public void removeInventoryId(String inventoryId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.removeInventoryEntityId(inventoryId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void addLockerInventoryId(String entityId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.addLockerEntityId(entityId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void addNpcKillLog(String npcName) {
+        gameManager.getEventProcessor().addEvent(() -> {
+            synchronized (interner.intern(playerId)) {
+                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+                if (!playerMetadataOptional.isPresent()) {
+                    return;
+                }
+                PlayerMetadata playerMetadata = playerMetadataOptional.get();
+                playerMetadata.addNpcKill(npcName);
+                savePlayerMetadata(playerMetadata);
+            }
+        });
+    }
+
+    public void transferItemFromLocker(String entityId) {
+        synchronized (interner.intern(playerId)) {
+            if (gameManager.acquireItem(this, entityId)) {
+                removeLockerInventoryId(entityId);
+            }
+        }
+    }
+
+    public void removeLockerInventoryId(String lockerInventoryId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.removeLockerEntityId(lockerInventoryId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void updatePlayerMana(int amount) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Stats stats = playerMetadata.getStats();
+            stats.setCurrentMana(stats.getCurrentMana() + amount);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void updatePlayerForageExperience(int amount) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Stats stats = playerMetadata.getStats();
+            stats.setForaging(stats.getForaging() + amount);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void addCoolDown(CoolDown coolDown) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.addCoolDown(coolDown);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public Set<CoolDown> getCoolDowns() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return Sets.newHashSet();
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            return playerMetadata.getCoolDowns();
+        }
+    }
+
+    public boolean isActiveCoolDown() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            return playerMetadata.getCoolDowns().size() > 0;
+        }
+    }
+
+    public boolean isActiveForageCoolDown() {
+        if (isActive(CoolDownType.FORAGE_LONG) ||
+                isActive(CoolDownType.FORAGE_MEDIUM) ||
+                isActive(CoolDownType.FORAGE_SHORT) ||
+                isActive(CoolDownType.FORAGE_SUPERSHORT)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isActive(CoolDownType coolDownType) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Set<CoolDown> coolDowns = playerMetadata.getCoolDowns();
+            for (CoolDown c : coolDowns) {
+                if (c.getCoolDownType().equals(coolDownType)) {
+                    if (c.isActive()) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean isActiveSpellCoolDown(String spellName) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Set<CoolDown> coolDowns = playerMetadata.getCoolDowns();
+            for (CoolDown coolDown : coolDowns) {
+                if (coolDown.getName().equalsIgnoreCase(spellName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private void tickAllActiveCoolDowns() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.getCoolDownMap().entrySet().removeIf(coolDownTypeCoolDownEntry -> {
+                if (coolDownTypeCoolDownEntry.getValue().isActive()) {
+                    coolDownTypeCoolDownEntry.getValue().decrementTick();
+                } else {
+                    if (coolDownTypeCoolDownEntry.getValue().equals(CoolDownType.DEATH)) {
+                        gameManager.getChannelUtils().write(playerId, "You have risen from the dead.\r\n");
+                    }
+                    return true;
+                }
+                return false;
+            });
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void writePrompt() {
+        String prompt = gameManager.buildPrompt(playerId);
+        gameManager.getChannelUtils().write(playerId, prompt, true);
+    }
+
+    public String getPlayerId() {
+        return playerId;
+    }
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public void setChannel(Channel channel) {
+        this.channel = channel;
+    }
+
+    public Optional<String> getReturnDirection() {
+        return returnDirection;
+    }
+
+    public void setReturnDirection(Optional<String> returnDirection) {
+        this.returnDirection = returnDirection;
+    }
+
+    public Room getCurrentRoom() {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (currentRoom == null && playerMetadataOptional.isPresent()) {
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Integer currentRoomId = playerMetadata.getCurrentRoomId();
+            if (currentRoomId != null) {
+                this.currentRoom = gameManager.getRoomManager().getRoom(currentRoomId);
+            }
+        }
+        return currentRoom;
+    }
+
+    public void setCurrentRoomAndPersist(Room currentRoom) {
+        // Persisting lazily so that performance doesn't suffer.
+        setCurrentRoom(currentRoom);
+        gameManager.getEventProcessor().addEvent(() -> {
+            synchronized (interner.intern(playerId)) {
+                Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+                if (!playerMetadataOptional.isPresent()) {
+                    return;
+                }
+                PlayerMetadata playerMetadata = playerMetadataOptional.get();
+                playerMetadata.setCurrentRoomId(currentRoom.getRoomId());
+                savePlayerMetadata(playerMetadata);
+            }
+        });
+    }
+
+    public void setCurrentRoom(Room room) {
+        this.currentRoom = room;
+    }
+
+    public Map<String, Long> getNpcKillLog() {
+        ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (!playerMetadataOptional.isPresent()) {
+            return Maps.newHashMap();
+        }
+        PlayerMetadata playerMetadata = playerMetadataOptional.get();
+        playerMetadata.getNpcKillLog().forEach(builder::put);
+        return builder.build();
+    }
+
+    public void removePlayerFromRoom(Room room) {
+        synchronized (interner.intern(playerId)) {
+            room.removePresentPlayer(getPlayerId());
+        }
+    }
+
+    public void movePlayer(PlayerMovement playerMovement) {
+        synchronized (interner.intern(playerId)) {
+            Optional<Room> sourceRoom = Optional.empty();
+            if (playerMovement.getSourceRoomId() != null) {
+                sourceRoom = Optional.ofNullable(gameManager.getRoomManager().getRoom(playerMovement.getSourceRoomId()));
+            }
+
+            Room destinationRoom = gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId());
+
+            if (sourceRoom.isPresent()) {
+                removePlayerFromRoom(sourceRoom.get());
+                for (Player next : sourceRoom.get().getPresentPlayers()) {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append(playerMovement.getPlayer().getPlayerName());
+                    sb.append(" ").append(playerMovement.getRoomExitMessage());
+                    gameManager.getChannelUtils().write(next.getPlayerId(), sb.toString(), true);
+                }
+                setPreviousRoom(currentRoom);
+            }
+
+            destinationRoom.addPresentPlayer(playerMovement.getPlayer().getPlayerId());
+            setCurrentRoomAndPersist(destinationRoom);
+            for (Player next : destinationRoom.getPresentPlayers()) {
+                if (next.getPlayerId().equals(playerMovement.getPlayer().getPlayerId())) {
+                    continue;
+                }
+                gameManager.getChannelUtils().write(next.getPlayerId(), playerMovement.getPlayer().getPlayerName() + " arrived.", true);
+            }
+            setReturnDirection(java.util.Optional.ofNullable(playerMovement.getReturnDirection()));
+            gameManager.currentRoomLogic(playerId, gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId()));
+            gameManager.getRoomManager().getRoom(playerMovement.getDestinationRoomId());
+            processNpcAggro();
+        }
+    }
+
+    public void processNpcAggro() {
+        synchronized (interner.intern(playerId)) {
+            if (isActive(CoolDownType.DEATH)) {
+                return;
+            }
+            List<Npc> aggresiveRoomNpcs = currentRoom.getNpcIds().stream()
+                    .map(npcId -> gameManager.getEntityManager().getNpcEntity(npcId))
+                    .filter(npc -> npc.getTemperament().equals(Temperament.AGGRESSIVE))
+                    .filter(npc -> {
+                        Npc.NpcLevelColor levelColor = npc.getLevelColor((int) Levels.getLevel(getPlayerStatsWithEquipmentAndLevel().getExperience()));
+                        return !levelColor.equals(Npc.NpcLevelColor.WHITE);
+                    })
+                    .collect(Collectors.toList());
+
+            aggresiveRoomNpcs.forEach(npc -> {
+                gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " has alerted a " + npc.getColorName() + "\r\n");
+                gameManager.getChannelUtils().write(playerId, "You can return to your previous location by typing \"back\"" + "\r\n");
+                setIsActiveAlertNpcStatus(npc);
+                scheduledExecutor.schedule(() -> {
+                    removeActiveAlertStatus(npc);
+                    if (!areInTheSameRoom(npc)) {
+                        return;
+                    }
+                    if (!npc.getIsAlive().get()) {
+                        return;
+                    }
+                    if (isActive(CoolDownType.DEATH)) {
+                        return;
+                    }
+                    gameManager.writeToPlayerCurrentRoom(getPlayerId(), getPlayerName() + " has " + Color.BOLD_ON + Color.RED + "ANGERED" + Color.RESET + " a " + npc.getColorName() + "\r\n");
+                    addActiveFight(npc);
+                }, 5, TimeUnit.SECONDS);
+            });
+        }
+    }
+
+    public Optional<Item> getInventoryItem(String itemKeyword) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return Optional.empty();
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            for (String itemId : playerMetadata.getInventory()) {
+                Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
+                if (!itemOptional.isPresent()) {
+                    log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
+                    continue;
+                }
+                Item itemEntity = itemOptional.get();
+                if (itemEntity.getItemTriggers().contains(itemKeyword)) {
+                    return Optional.of(itemEntity);
+                }
+            }
+            return Optional.empty();
+        }
+    }
+
+    public String getPlayerName() {
+        return playerName;
+    }
+
+    public List<String> getRolledUpLockerInventory() {
+        synchronized (interner.intern(playerId)) {
+            List<String> rolledUp = Lists.newArrayList();
+            List<Item> inventory = getLockerInventory();
+            Map<String, Integer> itemAndCounts = Maps.newHashMap();
+            if (inventory != null) {
+                for (Item item : inventory) {
+                    StringBuilder invItem = new StringBuilder();
+                    invItem.append(item.getItemName());
+                    int maxUses = item.getMaxUses();
+                    if (item.getMaxUses() > 0) {
+                        int remainingUses = maxUses - item.getNumberOfUses();
+                        invItem.append(" - ").append(remainingUses);
+                        if (remainingUses == 1) {
+                            invItem.append(" use left.");
+                        } else {
+                            invItem.append(" uses left.");
+                        }
+                    }
+                    if (itemAndCounts.containsKey(invItem.toString())) {
+                        Integer integer = itemAndCounts.get(invItem.toString());
+                        integer = integer + 1;
+                        itemAndCounts.put(invItem.toString(), integer);
+                    } else {
+                        itemAndCounts.put(invItem.toString(), 1);
+                    }
+                }
+                StringBuilder inventoryLine = new StringBuilder();
+                for (Map.Entry<String, Integer> next : itemAndCounts.entrySet()) {
+                    if (next.getValue() > 1) {
+                        inventoryLine.append(next.getKey()).append(" (").append(next.getValue()).append(")").append("\r\n");
+                    } else {
+                        inventoryLine.append(next.getKey()).append("\r\n");
+                    }
+                }
+                rolledUp.add(inventoryLine.toString());
+            }
+            return rolledUp;
+        }
+    }
+
+    public List<Item> getLockerInventory() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptonal = getPlayerMetadata();
+            if (!playerMetadataOptonal.isPresent()) {
+                return Lists.newArrayList();
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptonal.get();
+            List<Item> inventoryItems = Lists.newArrayList();
+            List<String> inventory = playerMetadata.getLockerInventory();
+            if (inventory != null) {
+                for (String itemId : inventory) {
+                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
+                    if (!itemOptional.isPresent()) {
+                        log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
+                        continue;
+                    }
+                    inventoryItems.add(itemOptional.get());
+                }
+            }
+            inventoryItems.sort(Comparator.comparing(Item::getItemName));
+            return inventoryItems;
+        }
+    }
+
+    public List<String> getRolledUpIntentory() {
+        synchronized (interner.intern(playerId)) {
+            List<String> rolledUp = Lists.newArrayList();
+            List<Item> inventory = getInventory();
+            Map<String, Integer> itemAndCounts = Maps.newHashMap();
+            if (inventory != null) {
+                for (Item item : inventory) {
+                    StringBuilder invItem = new StringBuilder();
+                    invItem.append(item.getItemName());
+                    int maxUses = item.getMaxUses();
+                    if (maxUses > 0) {
+                        int remainingUses = maxUses - item.getNumberOfUses();
+                        invItem.append(" - ").append(remainingUses);
+                        if (remainingUses == 1) {
+                            invItem.append(" use left.");
+                        } else {
+                            invItem.append(" uses left.");
+                        }
+                    }
+                    if (itemAndCounts.containsKey(invItem.toString())) {
+                        Integer integer = itemAndCounts.get(invItem.toString());
+                        integer = integer + 1;
+                        itemAndCounts.put(invItem.toString(), integer);
+                    } else {
+                        itemAndCounts.put(invItem.toString(), 1);
+                    }
+                }
+                StringBuilder inventoryLine = new StringBuilder();
+                for (Map.Entry<String, Integer> next : itemAndCounts.entrySet()) {
+                    if (next.getValue() > 1) {
+                        inventoryLine.append(next.getKey()).append(" (").append(next.getValue()).append(")").append("\r\n");
+                    } else {
+                        inventoryLine.append(next.getKey()).append("\r\n");
+                    }
+                }
+                rolledUp.add(inventoryLine.toString());
+            }
+            return rolledUp;
+        }
+    }
+
+    public List<Item> getInventory() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return Lists.newArrayList();
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            List<Item> inventoryItems = Lists.newArrayList();
+            List<String> inventory = playerMetadata.getInventory();
+            if (inventory != null) {
+                for (String itemId : inventory) {
+                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
+                    if (!itemOptional.isPresent()) {
+                        log.info("Orphaned inventoryId:" + itemId + " player: " + getPlayerName());
+                        continue;
+                    }
+                    inventoryItems.add(itemOptional.get());
+                }
+            }
+            inventoryItems.sort(Comparator.comparing(Item::getItemName));
+            return inventoryItems;
+        }
+    }
+
+    public Set<Item> getEquipment() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return Sets.newHashSet();
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Set<Item> equipmentItems = Sets.newHashSet();
+            String[] equipment = playerMetadata.getPlayerEquipment();
+            if (equipment != null) {
+                for (String itemId : equipment) {
+                    Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(itemId);
+                    if (!itemOptional.isPresent()) {
+                        log.info("Orphaned equipmentId:" + itemId + " player: " + getPlayerName());
+                        continue;
+                    }
+                    equipmentItems.add(itemOptional.get());
+                }
+            }
+            return equipmentItems;
+        }
+    }
+
+    public void equip(Item item) {
+        synchronized (interner.intern(playerId)) {
+            if (item.getEquipment() == null) {
+                return;
+            }
+            Equipment equipment = item.getEquipment();
+            EquipmentSlotType equipmentSlotType = equipment.getEquipmentSlotType();
+            Optional<Item> slotItemOptional = getSlotItem(equipmentSlotType);
+            if (slotItemOptional.isPresent()) {
+                if (!unEquip(slotItemOptional.get())) {
+                    return;
+                }
+            }
+            gameManager.getChannelUtils().write(playerId, "Equipping " + item.getItemName() + "\r\n");
+            addEquipmentId(item.getItemId());
+            removeInventoryId(item.getItemId());
+        }
+    }
+
+    public Optional<Item> getSlotItem(EquipmentSlotType slot) {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (!playerMetadataOptional.isPresent()) {
+            return Optional.empty();
+        }
+        PlayerMetadata playerMetadata = playerMetadataOptional.get();
+        if (playerMetadata.getPlayerEquipment() == null) {
+            return Optional.empty();
+        }
+        for (String item : playerMetadata.getPlayerEquipment()) {
+            Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(item);
+            if (!itemOptional.isPresent()) {
+                continue;
+            }
+            Item itemEntity = itemOptional.get();
+            EquipmentSlotType equipmentSlotType = itemEntity.getEquipment().getEquipmentSlotType();
+            if (equipmentSlotType.equals(slot)) {
+                return Optional.of(itemEntity);
+            }
+        }
+        return Optional.empty();
+    }
+
+    public boolean unEquip(Item item) {
+        synchronized (interner.intern(playerId)) {
+            gameManager.getChannelUtils().write(playerId, "Un-equipping " + item.getItemName() + "\r\n");
+            if (gameManager.acquireItem(this, item.getItemId())) {
+                removeEquipmentId(item.getItemId());
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public void addEquipmentId(String equipmentId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.addEquipmentEntityId(equipmentId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public void removeEquipmentId(String equipmentId) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.removeEquipmentEntityId(equipmentId);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public String getLookString() {
+        StringBuilder sb = new StringBuilder();
+        Stats origStats = gameManager.getStatsModifierFactory().getStatsModifier(this);
+        Stats modifiedStats = getPlayerStatsWithEquipmentAndLevel();
+        Stats diffStats = StatsHelper.getDifference(modifiedStats, origStats);
+        sb.append(Color.MAGENTA)
+                .append("-+=[ ").append(Color.RESET).append(playerName).append(Color.MAGENTA + " ]=+- " + Color.RESET)
+                .append("\r\n");
+        sb.append("Level ").append(Levels.getLevel(origStats.getExperience())).append(" ")
+                .append(Color.YELLOW).append("[").append(Color.RESET).append(CreeperUtils.capitalize(getPlayerClass().getIdentifier())).append(Color.YELLOW).append("]").append(Color.RESET)
+                .append("\r\n");
+        sb.append("Foraging Level ").append(ForageManager.getLevel(modifiedStats.getForaging())).append("\r\n");
+        sb.append(Color.MAGENTA + "Equip--------------------------------" + Color.RESET).append("\r\n");
+        sb.append(buildEquipmentString()).append("\r\n");
+        sb.append(Color.MAGENTA + "Stats--------------------------------" + Color.RESET).append("\r\n");
+        sb.append(gameManager.buildLookString(playerName, modifiedStats, diffStats)).append("\r\n");
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (playerMetadataOptional.isPresent()) {
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            if (playerMetadata.getEffects() != null && playerMetadata.getEffects().size() > 0) {
+                sb.append(Color.MAGENTA + "Effects--------------------------------" + Color.RESET).append("\r\n");
+                sb.append(buldEffectsString()).append("\r\n");
+            }
+        }
+        StringBuilder finalString = new StringBuilder();
+        Lists.newArrayList(sb.toString().split("[\\r\\n]+")).forEach(s -> finalString.append(CreeperUtils.trimTrailingBlanks(s)).append("\r\n"));
+        return finalString.toString();
+    }
+
+    public Stats getPlayerStatsWithEquipmentAndLevel() {
+        synchronized (interner.intern(playerId)) {
+            StatsBuilder statsBuilder = new StatsBuilder();
+            Stats newStats = statsBuilder.createStats();
+
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return newStats;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            Stats playerStats = gameManager.getStatsModifierFactory().getStatsModifier(this);
+            StatsHelper.combineStats(newStats, playerStats);
+            String[] playerEquipment = playerMetadata.getPlayerEquipment();
+            if (playerEquipment == null) {
+                return playerStats;
+            }
+            for (String equipId : playerEquipment) {
+                Optional<Item> itemOptional = gameManager.getEntityManager().getItemEntity(equipId);
+                if (!itemOptional.isPresent()) {
+                    continue;
+                }
+                Item itemEntity = itemOptional.get();
+                Equipment equipment = itemEntity.getEquipment();
+                Stats stats = equipment.getStats();
+                StatsHelper.combineStats(newStats, stats);
+            }
+            if (playerMetadata.getEffects() != null) {
+                for (Effect effect : playerMetadata.getEffects()) {
+                    StatsHelper.combineStats(newStats, effect.getDurationStats());
+                }
+            }
+            return newStats;
+        }
+    }
+
+    public PlayerClass getPlayerClass() {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return PlayerClass.BASIC;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            PlayerClass playerClass = playerMetadata.getPlayerClass();
+            if (playerClass == null) {
+                return PlayerClass.BASIC;
+            }
+            return playerClass;
+        }
+    }
+
+    public void setPlayerClass(PlayerClass playerClass) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.setPlayerClass(playerClass);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public String buildEquipmentString() {
+        Table t = new Table(2, BorderStyle.CLASSIC_COMPATIBLE,
+                ShownBorders.NONE);
+        t.setColumnWidth(0, 16, 20);
+
+        List<EquipmentSlotType> all = EquipmentSlotType.getAll();
+        for (EquipmentSlotType slot : all) {
+            t.addCell(capitalize(slot.getName()));
+            Optional<Item> slotItemOptional = getSlotItem(slot);
+            if (slotItemOptional.isPresent()) {
+                t.addCell(slotItemOptional.get().getItemName());
+            } else {
+                t.addCell("");
+            }
+        }
+        return t.render();
+    }
+
+    /* FIGHT FIGHT FIGHT FIGHT */
+
+    public String buldEffectsString() {
+        Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+        if (!playerMetadataOptional.isPresent()) {
+            return "";
+        }
+        PlayerMetadata playerMetadata = playerMetadataOptional.get();
+        List<Effect> effects = playerMetadata.getEffects();
+        return gameManager.renderEffectsString(effects);
+    }
+
+    private String capitalize(final String line) {
+        return Character.toUpperCase(line.charAt(0)) + line.substring(1);
+    }
+
+    public void removeAllActiveFights() {
+        synchronized (interner.intern(playerId)) {
+            Iterator<Map.Entry<Long, ActiveFight>> iterator = activeFights.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry<Long, ActiveFight> next = iterator.next();
+                iterator.remove();
+            }
+        }
+    }
+
+    public boolean setPlayerSetting(String key, String value) {
+        boolean success;
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return false;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            success = playerMetadata.setSetting(key, value);
+            savePlayerMetadata(playerMetadata);
+        }
+        return success;
+    }
+
+    public Optional<String> getPlayerSetting(String key) {
+        return getPlayerMetadata().flatMap(playerMetadata -> Optional.ofNullable(playerMetadata.getSetting(key)));
+    }
+
+    public void removePlayerSetting(String key) {
+        synchronized (interner.intern(playerId)) {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()) {
+                return;
+            }
+            PlayerMetadata playerMetadata = playerMetadataOptional.get();
+            playerMetadata.deleteSetting(key);
+            savePlayerMetadata(playerMetadata);
+        }
+    }
+
+    public Map<String, String> getPlayerSettings() {
+        return getPlayerMetadata().map(PlayerMetadata::getPlayerSettings).orElseGet(Maps::newHashMap);
+    }
+
+    public boolean addActiveFight(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            if (gameManager.getEntityManager().getNpcEntity(npc.getEntityId()) != null) {
+                if (!doesActiveFightExist(npc)) {
+                    addCoolDown(new CoolDown(CoolDownType.NPC_FIGHT));
+                    ActiveFight activeFight = ActiveFight.builder()
+                            .npcId(npc.getEntityId())
+                            .isPrimary(false)
+                            .create();
+                    activeFights.put(System.nanoTime(), activeFight);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean doesActiveFightExist(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            if (gameManager.getEntityManager().getNpcEntity(npc.getEntityId()) == null) {
+                removeActiveFight(npc);
+            }
+            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
+                ActiveFight fight = entry.getValue();
+                Optional<String> npcIdOptional = fight.getNpcId();
+                if (npcIdOptional.isPresent() && npcIdOptional.get().equals(npc.getEntityId())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public boolean doesActiveFightExist(Player player) {
+        synchronized (interner.intern(playerId)) {
+//            if (gameManager.getEntityManager().getNpcEntity(npc.getEntityId()) == null) {
+//                removeActiveFight(npc);
+//            }
+            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
+                ActiveFight fight = entry.getValue();
+                Optional<String> playerIdOptional = fight.getPlayerId();
+                if (playerIdOptional.isPresent() && playerIdOptional.get().equals(player.getPlayerId())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public void removeActiveFight(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            Iterator<Map.Entry<Long, ActiveFight>> iterator = activeFights.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry<Long, ActiveFight> next = iterator.next();
+                if (next.getValue().getNpcId().equals(npc.getEntityId())) {
+                    if (next.getValue().isPrimary()) {
+                    }
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    public boolean isActiveFights() {
+        synchronized (interner.intern(playerId)) {
+            if (activeFights.size() > 0) {
+                // Remove any fights with dead NPCs that no longer exist in Entity Manager.
+                activeFights.entrySet().removeIf(next -> next.getValue().getNpcId().isPresent() && gameManager.getEntityManager().getNpcEntity(next.getValue().getNpcId().get()) == null);
+            }
+        }
+        return activeFights.size() > 0;
+    }
+
+    public boolean isValidPrimaryActiveFight(Npc npc) {
+        synchronized (interner.intern(playerId)) {
+            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
+                ActiveFight fight = entry.getValue();
+                Optional<String> npcIdOptional = fight.getNpcId();
+                if (npcIdOptional.isPresent() && fight.getNpcId().get().equals(npc.getEntityId()) && fight.isPrimary()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public boolean isValidPrimaryActiveFight(Player player) {
+        synchronized (interner.intern(playerId)) {
+            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
+                ActiveFight fight = entry.getValue();
+                Optional<String> playerIdOptional = fight.getPlayerId();
+                if (playerIdOptional.isPresent() && fight.getPlayerId().get().equals(player.getPlayerId()) && fight.isPrimary()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public Optional<ActiveFight> getPrimaryActiveFight() {
+        synchronized (interner.intern(playerId)) {
+            for (Map.Entry<Long, ActiveFight> entry : activeFights.entrySet()) {
+                ActiveFight fight = entry.getValue();
+                if (fight.isPrimary()) {
+                    return Optional.of(fight);
+                }
+            }
+            return Optional.empty();
+        }
+    }
+
+    public void activateNextPrimaryActiveFight() {
+        synchronized (interner.intern(playerId)) {
+            if (!getPrimaryActiveFight().isPresent()) {
+                if (activeFights.size() > 0) {
+                    activeFights.get(activeFights.firstKey()).setIsPrimary(true);
+                }
+            }
+        }
+    }
+
+    private void doFightRound(DamageProcessor playerDamageProcessor, DamageProcessor npcDamageProcessor, ActiveFight activeFight) {
+        removeActiveAlertStatus();
+
+        // IF FIGHTING NPC
+        {
+            Optional<String> npcIdOptional = activeFight.getNpcId();
+            if (npcIdOptional.isPresent()) {
+                String npcId = npcIdOptional.get();
+                Npc npc = gameManager.getEntityManager().getNpcEntity(npcId);
+                if (npc == null) {
+                    return;
+                }
+
+                StatsChangeBuilder statsChangeBuilder = new StatsChangeBuilder().setPlayer(this);
+                if (this.isValidPrimaryActiveFight(npc)) {
+                    calculatePlayerDamageToNpc(playerDamageProcessor, npc, statsChangeBuilder);
+                }
+
+                if (this.doesActiveFightExist(npc)) {
+                    calculateNpcDamageToPlayer(npcDamageProcessor, npc, statsChangeBuilder);
+                }
+            }
+        }
+
+        // IF FIGHTING PLAYER?
+        {
+            Optional<String> playerIdOptional = activeFight.getPlayerId();
+            if (playerIdOptional.isPresent()) {
+                String playerId = playerIdOptional.get();
+                Player targetPlayer = gameManager.getPlayerManager().getPlayer(playerId);
+//                if (player.isA) {
+//                    return;
+//                }
+
+                StatsChangeBuilder statsChangeBuilder = new StatsChangeBuilder().setPlayer(this);
+                if (this.isValidPrimaryActiveFight(targetPlayer)) {
+                    calculatePlayerDamageToTargetPlayer(playerDamageProcessor, targetPlayer, statsChangeBuilder);
+                }
+
+                if (this.doesActiveFightExist(targetPlayer)) {
+                    calculateTargetPlayerDamageToPlayer(npcDamageProcessor, targetPlayer, statsChangeBuilder);
+                }
+            }
+        }
+
+    }
+
+    private void calculatePlayerDamageToTargetPlayer(DamageProcessor playerDamageProcessor, Player targetPlayer, StatsChangeBuilder statsChangeBuilder) {
+        long damageToVictim = 0;
+        long chanceToHit = playerDamageProcessor.getChanceToHit(this, targetPlayer);
+        if (randInt(0, 100) < chanceToHit) {
+            damageToVictim = playerDamageProcessor.getAttackAmount(this, targetPlayer);
+        }
+        if (damageToVictim > 0) {
+            if (randInt(0, 100) > (100 - playerDamageProcessor.getCriticalChance(this, targetPlayer))) {
+                long criticalDamage = damageToVictim * 3;
+                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "The " + targetPlayer.getPlayerName() + " was caught off guard by the attack! " + "+" + NumberFormat.getNumberInstance(Locale.US).format(criticalDamage) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + targetPlayer.getPlayerName();
+                statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-(criticalDamage)).createStats());
+                statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+            } else {
+                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "+" + NumberFormat.getNumberInstance(Locale.US).format(damageToVictim) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + targetPlayer.getPlayerName();
+                statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
+                statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+            }
+        } else {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + "You MISS " + targetPlayer.getPlayerName() + "!";
+            statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
+            statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+        }
+    }
+
+
+    private void calculatePlayerDamageToNpc(DamageProcessor playerDamageProcessor, Npc npc, StatsChangeBuilder statsChangeBuilder) {
+        long damageToVictim = 0;
+        long chanceToHit = playerDamageProcessor.getChanceToHit(this, npc);
+        if (randInt(0, 100) < chanceToHit) {
+            damageToVictim = playerDamageProcessor.getAttackAmount(this, npc);
+        }
+        if (damageToVictim > 0) {
+            if (randInt(0, 100) > (100 - playerDamageProcessor.getCriticalChance(this, npc))) {
+                long criticalDamage = damageToVictim * 3;
+                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "The " + npc.getColorName() + " was caught off guard by the attack! " + "+" + NumberFormat.getNumberInstance(Locale.US).format(criticalDamage) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + npc.getColorName();
+                statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-(criticalDamage)).createStats());
+                statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+            } else {
+                final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + Color.YELLOW + "+" + NumberFormat.getNumberInstance(Locale.US).format(damageToVictim) + Color.RESET + Color.BOLD_ON + Color.RED + " DAMAGE" + Color.RESET + " done to " + npc.getColorName();
+                statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
+                statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+            }
+        } else {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + "You MISS " + npc.getName() + "!";
+            statsChangeBuilder.setStats(new StatsBuilder().setCurrentHealth(-damageToVictim).createStats());
+            statsChangeBuilder.setDamageStrings(Collections.singletonList(fightMsg));
+        }
+    }
+
+
+    private void calculateTargetPlayerDamageToPlayer(DamageProcessor npcDamageProcessor, Player targetPlayer, StatsChangeBuilder statsChangeBuilder) {
+        int chanceToHitBack = npcDamageProcessor.getChanceToHit(this, targetPlayer);
+        long damageBack = npcDamageProcessor.getAttackAmount(this, targetPlayer);
+        if (randInt(0, 100) < chanceToHitBack) {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + "STRIKES" + " -" + NumberFormat.getNumberInstance(Locale.US).format(damageBack) + Color.RESET;
+            statsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(-damageBack).createStats());
+            statsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
+
+        } else {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + targetPlayer.getPlayerName() + Color.BOLD_ON + Color.CYAN + " MISSES" + Color.RESET + " you!";
+            statsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(0).createStats());
+            statsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
+        }
+        targetPlayer.addDamage(statsChangeBuilder.createNpcStatsChange());
+    }
+
+
+    private void calculateNpcDamageToPlayer(DamageProcessor npcDamageProcessor, Npc npc, StatsChangeBuilder statsChangeBuilder) {
+        int chanceToHitBack = npcDamageProcessor.getChanceToHit(this, npc);
+        long damageBack = npcDamageProcessor.getAttackAmount(this, npc);
+        if (randInt(0, 100) < chanceToHitBack) {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + npc.buildAttackMessage(this.getPlayerName()) + " -" + NumberFormat.getNumberInstance(Locale.US).format(damageBack) + Color.RESET;
+            statsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(-damageBack).createStats());
+            statsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
+
+        } else {
+            final String fightMsg = Color.BOLD_ON + Color.RED + "[attack] " + Color.RESET + npc.getColorName() + Color.BOLD_ON + Color.CYAN + " MISSES" + Color.RESET + " you!";
+            statsChangeBuilder.setPlayerStatsChange(new StatsBuilder().setCurrentHealth(0).createStats());
+            statsChangeBuilder.setPlayerDamageStrings(Collections.singletonList(fightMsg));
+        }
+        npc.addNpcDamage(statsChangeBuilder.createNpcStatsChange());
+    }
+
+
+    public void addDamage(StatsChange playerStatsChange) {
+        if (!isActive(CoolDownType.PVP_FIGHT)) {
+            addCoolDown(new CoolDown(CoolDownType.PVP_FIGHT));
+        } else {
+            Optional<PlayerMetadata> playerMetadataOptional = getPlayerMetadata();
+            if (!playerMetadataOptional.isPresent()){
+                return;
+            }
+            for (CoolDown coolDown: playerMetadataOptional.get().getCoolDowns()) {
+                if (coolDown.getCoolDownType().equals(CoolDownType.NPC_FIGHT)) {
+                    coolDown.setNumberOfTicks(coolDown.getOriginalNumberOfTicks());
+                }
+            }
+        }
+        this.playerStatChanges.add(playerStatsChange);
+    }
+
+    public SortedMap<Long, ActiveFight> getActiveFights() {
+        return activeFights;
+    }
+
+    private int randInt(int min, int max) {
+        return random.nextInt((max - min) + 1) + min;
+    }
+
+    public Interner<String> getInterner() {
+        return interner;
+    }
+
+
+    public boolean toggleChat() {
+        synchronized (interner.intern(playerId)) {
+            if (isChatModeOn()) {
+                setNotIsChatMode();
+                return false;
+            } else {
+                setIsChatMode();
+                return true;
+            }
+        }
+    }
+
+    public void setIsChatMode() {
+        this.isChatMode.compareAndSet(false, true);
+    }
+
+    public void setNotIsChatMode() {
+        this.isChatMode.compareAndSet(true, false);
+    }
+
+    public boolean isChatModeOn() {
+        return isChatMode.get();
+    }
+}
-- 
GitLab