From 57e49729ffbc5734b122685d132f8243ad9cecd6 Mon Sep 17 00:00:00 2001
From: Chris Kearney <chris@kearneymail.com>
Date: Sat, 20 Aug 2016 13:53:14 -0700
Subject: [PATCH] added an old man that can set your class, made stat modifiers
 for the various classes

---
 .../comandante/creeper/ConfigureCommands.java |   8 ++
 .../com/comandante/creeper/ConfigureNpc.java  |  18 +--
 .../com/comandante/creeper/CreeperUtils.java  |   5 +
 .../java/com/comandante/creeper/Main.java     |   3 +
 .../creeper/classes/PlayerClass.java          |  18 ++-
 .../creeper/command/TalkCommand.java          |  13 ++-
 .../managers/NewUserRegistrationManager.java  |   2 +-
 .../comandante/creeper/merchant/Merchant.java |   1 +
 .../creeper/merchant/OldWiseMan.java          |  33 ++++++
 .../ChooseClassCommand.java                   |  33 ++++++
 .../playerclass_selector/LeaveCommand.java    |  32 ++++++
 .../PlayerClassCommand.java                   | 107 ++++++++++++++++++
 .../PlayerClassCommandHandler.java            |  51 +++++++++
 .../PlayerClassCommandRegistry.java           |  31 +++++
 .../playerclass_selector/UnknownCommand.java  |  25 ++++
 .../player/BasicPlayerLevelStatsModifier.java |  18 +--
 .../com/comandante/creeper/player/Player.java |  14 ++-
 .../creeper/player/PlayerStats.java           |  12 +-
 .../creeper/player/RangerStatsModifier.java   | 101 +++++++++++++++++
 .../creeper/player/ShamanStatsModifier.java   | 101 +++++++++++++++++
 .../creeper/player/StatsModifierFactory.java  |  22 +++-
 .../creeper/player/WizardStatsModifier.java   | 102 +++++++++++++++++
 .../creeper/server/CreeperCommandHandler.java |   3 +
 .../comandante/creeper/CreeperUtilsTest.java  |   2 +-
 .../creeper/player/NpcTestHarness.java        |  22 ++--
 25 files changed, 730 insertions(+), 47 deletions(-)
 create mode 100644 src/main/java/com/comandante/creeper/merchant/OldWiseMan.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/ChooseClassCommand.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/LeaveCommand.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommand.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandHandler.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandRegistry.java
 create mode 100644 src/main/java/com/comandante/creeper/merchant/playerclass_selector/UnknownCommand.java
 create mode 100644 src/main/java/com/comandante/creeper/player/RangerStatsModifier.java
 create mode 100644 src/main/java/com/comandante/creeper/player/ShamanStatsModifier.java
 create mode 100644 src/main/java/com/comandante/creeper/player/WizardStatsModifier.java

diff --git a/src/main/java/com/comandante/creeper/ConfigureCommands.java b/src/main/java/com/comandante/creeper/ConfigureCommands.java
index 27664b1b..91ae9b78 100644
--- a/src/main/java/com/comandante/creeper/ConfigureCommands.java
+++ b/src/main/java/com/comandante/creeper/ConfigureCommands.java
@@ -9,6 +9,7 @@ import com.comandante.creeper.merchant.lockers.GetCommand;
 import com.comandante.creeper.merchant.lockers.LockerCommandRegistry;
 import com.comandante.creeper.merchant.lockers.PutCommand;
 import com.comandante.creeper.merchant.lockers.QueryCommand;
+import com.comandante.creeper.merchant.playerclass_selector.PlayerClassCommandRegistry;
 import com.comandante.creeper.server.CreeperCommandRegistry;
 
 public class ConfigureCommands {
@@ -34,6 +35,13 @@ public class ConfigureCommands {
         lockerCommandRegistry.addCommand(new QueryCommand(gameManager));
         lockerCommandRegistry.addCommand(new com.comandante.creeper.merchant.lockers.DoneCommand(gameManager));
     }
+    public static PlayerClassCommandRegistry playerClassCommandRegistry;
+
+    public static void configurePlayerClassSelector(GameManager gameManager) {
+       playerClassCommandRegistry = new PlayerClassCommandRegistry(gameManager);
+    }
+
+
 
     public static void configure(GameManager gameManager) {
         creeperCommandRegistry = new CreeperCommandRegistry(new UnknownCommand(gameManager));
diff --git a/src/main/java/com/comandante/creeper/ConfigureNpc.java b/src/main/java/com/comandante/creeper/ConfigureNpc.java
index 16a5f7f0..3ad35941 100644
--- a/src/main/java/com/comandante/creeper/ConfigureNpc.java
+++ b/src/main/java/com/comandante/creeper/ConfigureNpc.java
@@ -1,6 +1,8 @@
 package com.comandante.creeper;
 
-import com.comandante.creeper.Items.*;
+import com.comandante.creeper.Items.ForageBuilder;
+import com.comandante.creeper.Items.ItemType;
+import com.comandante.creeper.Items.Loot;
 import com.comandante.creeper.entity.EntityManager;
 import com.comandante.creeper.managers.GameManager;
 import com.comandante.creeper.merchant.*;
@@ -10,7 +12,6 @@ import com.comandante.creeper.spawner.ItemSpawner;
 import com.comandante.creeper.spawner.NpcSpawner;
 import com.comandante.creeper.spawner.SpawnRule;
 import com.comandante.creeper.spawner.SpawnRuleBuilder;
-import com.comandante.creeper.spells.*;
 import com.comandante.creeper.world.Area;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -25,11 +26,11 @@ public class ConfigureNpc {
     public static void configureAllNpcs(GameManager gameManager) throws IOException {
         EntityManager entityManager = gameManager.getEntityManager();
         List<Npc> npcsFromFile = NpcExporter.getNpcsFromFile(gameManager);
-        for (Npc npc: npcsFromFile) {
+        for (Npc npc : npcsFromFile) {
             Main.startUpMessage("Added " + npc.getName());
             entityManager.addEntity(npc);
             Set<SpawnRule> spawnRules = npc.getSpawnRules();
-            for (SpawnRule spawnRule: spawnRules) {
+            for (SpawnRule spawnRule : spawnRules) {
                 entityManager.addEntity(new NpcSpawner(npc, gameManager, spawnRule));
             }
         }
@@ -43,7 +44,7 @@ public class ConfigureNpc {
         ItemSpawner itemSpawner = new ItemSpawner(ItemType.SMALL_HEALTH_POTION, new SpawnRuleBuilder().setArea(Area.NEWBIE_ZONE).setSpawnIntervalTicks(600).setMaxInstances(100).setMaxPerRoom(5).setRandomPercent(40).createSpawnRule(), gameManager);
         ItemSpawner itemSpawner1 = new ItemSpawner(ItemType.SMALL_HEALTH_POTION, new SpawnRuleBuilder().setArea(Area.FANCYHOUSE_ZONE).setSpawnIntervalTicks(600).setMaxInstances(12).setMaxPerRoom(2).setRandomPercent(50).createSpawnRule(), gameManager);
         ItemSpawner itemSpawner2 = new ItemSpawner(ItemType.SMALL_HEALTH_POTION, new SpawnRuleBuilder().setArea(Area.HOUSE_ZONE).setSpawnIntervalTicks(600).setMaxInstances(12).setMaxPerRoom(2).setRandomPercent(50).createSpawnRule(), gameManager);
-         ItemSpawner itemSpawner5 = new ItemSpawner(ItemType.KEY, new SpawnRuleBuilder().setArea(Area.LOBBY).setSpawnIntervalTicks(600).setMaxInstances(1).setMaxPerRoom(1).setRandomPercent(5).createSpawnRule(), gameManager);
+        ItemSpawner itemSpawner5 = new ItemSpawner(ItemType.KEY, new SpawnRuleBuilder().setArea(Area.LOBBY).setSpawnIntervalTicks(600).setMaxInstances(1).setMaxPerRoom(1).setRandomPercent(5).createSpawnRule(), gameManager);
 
         entityManager.addEntity(itemSpawner);
         entityManager.addEntity(itemSpawner1);
@@ -57,10 +58,10 @@ public class ConfigureNpc {
 
         LloydBartender lloydBartender = new LloydBartender(gameManager, new Loot(18, 26, Sets.<ItemType>newHashSet()), itemsForSale);
         gameManager.getRoomManager().addMerchant(64, lloydBartender);
-        
+
         Map<Integer, MerchantItemForSale> nigelForSale = Maps.newLinkedHashMap();
         nigelForSale.put(1, new MerchantItemForSale(ItemType.SMALL_HEALTH_POTION, 6));
-        
+
         NigelBartender nigelBartender = new NigelBartender(gameManager, new Loot(18, 26, Sets.<ItemType>newHashSet()), nigelForSale);
         gameManager.getRoomManager().addMerchant(377, nigelBartender);
 
@@ -87,6 +88,9 @@ public class ConfigureNpc {
         gameManager.getRoomManager().addMerchant(65, jimBanker);
         gameManager.getRoomManager().addMerchant(209, jimBanker);
 
+        OldWiseMan oldWiseMan = new OldWiseMan(gameManager, new Loot(18, 26, Sets.<ItemType>newHashSet()), null);
+        gameManager.getRoomManager().addMerchant(2, oldWiseMan);
+
         LockerRoomGuy lockerRoomGuy = new LockerRoomGuy(gameManager, new Loot(18, 26, Sets.<ItemType>newHashSet()), null);
         gameManager.getRoomManager().addMerchant(63, lockerRoomGuy);
 
diff --git a/src/main/java/com/comandante/creeper/CreeperUtils.java b/src/main/java/com/comandante/creeper/CreeperUtils.java
index 536fdfd2..941dd833 100644
--- a/src/main/java/com/comandante/creeper/CreeperUtils.java
+++ b/src/main/java/com/comandante/creeper/CreeperUtils.java
@@ -138,4 +138,9 @@ Bag             25        (+15)       | Bag             25        (+15)
     public static int randInt(int min, int max) {
         return random.nextInt((max - min) + 1) + min;
     }
+
+    public static String capitalize(String s) {
+        if (s.length() == 0) return s;
+        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
+    }
 }
diff --git a/src/main/java/com/comandante/creeper/Main.java b/src/main/java/com/comandante/creeper/Main.java
index 5f720a60..eb46797b 100644
--- a/src/main/java/com/comandante/creeper/Main.java
+++ b/src/main/java/com/comandante/creeper/Main.java
@@ -107,6 +107,9 @@ public class Main {
         startUpMessage("Configure Locker commands");
         ConfigureCommands.configureLockerCommands(gameManager);
 
+        startUpMessage("Configure Player Class Selection commands");
+        ConfigureCommands.configurePlayerClassSelector(gameManager);
+
         startUpMessage("Configuring npcs and merchants");
         ConfigureNpc.configure(entityManager, gameManager);
         CreeperServer creeperServer = new CreeperServer(creeperConfiguration.telnetPort);
diff --git a/src/main/java/com/comandante/creeper/classes/PlayerClass.java b/src/main/java/com/comandante/creeper/classes/PlayerClass.java
index b70259a7..79b626ee 100644
--- a/src/main/java/com/comandante/creeper/classes/PlayerClass.java
+++ b/src/main/java/com/comandante/creeper/classes/PlayerClass.java
@@ -2,18 +2,20 @@ package com.comandante.creeper.classes;
 
 public enum PlayerClass {
 
-    WARRIOR(1, "warrior"),
-    WIZARD(2,"wizard"),
-    RANGER(3, "ranger"),
-    SHAMAN(4, "shaman"),
-    NOCLASS(0, "noclass");
+    WARRIOR(1, "warrior", "A warrior is skilled in mele combat and feats of strength."),
+    WIZARD(2,"wizard", "A wizard is a master of the deadly side of the arcane arts with a high IQ."),
+    RANGER(3, "ranger", "A ranger moves quickly and is deadly from a distance"),
+    SHAMAN(4, "shaman", "A shaman possesses a mastery of the restoratitve arcane arts."),
+    BASIC(0, "basic", "A master of nothing.");
 
     private final int id;
     private final String identifier;
+    private final String description;
 
-    PlayerClass(int id, String identifier) {
+    PlayerClass(int id, String identifier, String description) {
         this.id = id;
         this.identifier = identifier;
+        this.description = description;
     }
 
     public int getId() {
@@ -23,4 +25,8 @@ public enum PlayerClass {
     public String getIdentifier() {
         return identifier;
     }
+
+    public String getDescription() {
+        return description;
+    }
 }
diff --git a/src/main/java/com/comandante/creeper/command/TalkCommand.java b/src/main/java/com/comandante/creeper/command/TalkCommand.java
index 6abae374..553a9225 100644
--- a/src/main/java/com/comandante/creeper/command/TalkCommand.java
+++ b/src/main/java/com/comandante/creeper/command/TalkCommand.java
@@ -2,15 +2,16 @@ package com.comandante.creeper.command;
 
 
 import com.comandante.creeper.CreeperEntry;
+import com.comandante.creeper.classes.PlayerClass;
 import com.comandante.creeper.managers.GameManager;
 import com.comandante.creeper.merchant.Merchant;
 import com.comandante.creeper.merchant.MerchantCommandHandler;
 import com.comandante.creeper.merchant.bank.commands.BankCommand;
 import com.comandante.creeper.merchant.lockers.LockerCommand;
+import com.comandante.creeper.merchant.playerclass_selector.PlayerClassCommand;
 import com.google.common.base.Joiner;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.MessageEvent;
-import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
 
 import java.util.Arrays;
 import java.util.List;
@@ -48,6 +49,16 @@ public class TalkCommand extends Command {
                         write(BankCommand.getPrompt());
                     } else if (merchant.getMerchantType() == Merchant.MerchantType.LOCKER) {
                         write(LockerCommand.getPrompt());
+                    } else if (merchant.getMerchantType() == Merchant.MerchantType.PLAYERCLASS_SELECTOR) {
+                        if (player.getLevel() < 4) {
+                            write("You must be at least level 4 before you can engage in conversation with the old wise man.");
+                            return;
+                        }
+                        if (!player.getPlayerClass().equals(PlayerClass.BASIC)) {
+                            write("You have nothing to discuss with the old wise man.  Perhaps presenting him with a gift would change his mind?");
+                            return;
+                        }
+                        write(PlayerClassCommand.getPrompt());
                     }
                     creeperSession.setGrabMerchant(Optional.of(
                             new CreeperEntry<>(merchant, talkCommand)));
diff --git a/src/main/java/com/comandante/creeper/managers/NewUserRegistrationManager.java b/src/main/java/com/comandante/creeper/managers/NewUserRegistrationManager.java
index 679efaac..efeba779 100644
--- a/src/main/java/com/comandante/creeper/managers/NewUserRegistrationManager.java
+++ b/src/main/java/com/comandante/creeper/managers/NewUserRegistrationManager.java
@@ -65,7 +65,7 @@ public class NewUserRegistrationManager {
             return;
         }
         session.setPassword(Optional.of(password));
-        PlayerMetadata playerMetadata = new PlayerMetadata(session.getUsername().get(), session.getPassword().get(), Main.createPlayerId(session.getUsername().get()), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), new String[0], 0, new String[0], Maps.newHashMap(), PlayerClass.NOCLASS);
+        PlayerMetadata playerMetadata = new PlayerMetadata(session.getUsername().get(), session.getPassword().get(), Main.createPlayerId(session.getUsername().get()), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), new String[0], 0, new String[0], Maps.newHashMap(), PlayerClass.BASIC);
         playerManager.savePlayerMetadata(playerMetadata);
         e.getChannel().write("User created.\r\n");
         session.setState(CreeperSession.State.newUserRegCompleted);
diff --git a/src/main/java/com/comandante/creeper/merchant/Merchant.java b/src/main/java/com/comandante/creeper/merchant/Merchant.java
index e70bb13e..452e8b52 100644
--- a/src/main/java/com/comandante/creeper/merchant/Merchant.java
+++ b/src/main/java/com/comandante/creeper/merchant/Merchant.java
@@ -98,6 +98,7 @@ public abstract class Merchant extends CreeperEntity {
     public enum MerchantType {
         BANK,
         LOCKER,
+        PLAYERCLASS_SELECTOR,
         BASIC
     }
 }
diff --git a/src/main/java/com/comandante/creeper/merchant/OldWiseMan.java b/src/main/java/com/comandante/creeper/merchant/OldWiseMan.java
new file mode 100644
index 00000000..1801f492
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/OldWiseMan.java
@@ -0,0 +1,33 @@
+package com.comandante.creeper.merchant;
+
+import com.comandante.creeper.Items.Loot;
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.server.Color;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.comandante.creeper.server.Color.BOLD_ON;
+
+public class OldWiseMan extends Merchant {
+    private final static long phraseIntervalMs = 300000;
+    private final static String NAME = "old wise man";
+    private final static String welcomeMessage = "Hello, let me guide you towards making wise life decisions.\r\n";
+    private final static Set<String> validTriggers = new HashSet<String>(Arrays.asList(new String[]
+            {"wise", "man", "old", "old wise man", "m", "w", NAME}
+    ));
+
+    private final static String colorName = BOLD_ON + Color.CYAN + NAME + Color.RESET;
+
+    public OldWiseMan(GameManager gameManager, Loot loot, Map<Integer, MerchantItemForSale> merchantItemForSales) {
+        super(gameManager, NAME, colorName, validTriggers, merchantItemForSales, welcomeMessage, MerchantType.PLAYERCLASS_SELECTOR);
+    }
+
+    @Override
+    public String getMenu() {
+        return null;
+    }
+}
+
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/ChooseClassCommand.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/ChooseClassCommand.java
new file mode 100644
index 00000000..5df23808
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/ChooseClassCommand.java
@@ -0,0 +1,33 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+import com.comandante.creeper.CreeperUtils;
+import com.comandante.creeper.classes.PlayerClass;
+import com.comandante.creeper.managers.GameManager;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+
+
+public class ChooseClassCommand extends PlayerClassCommand {
+
+    private final PlayerClass playerClass;
+
+    public ChooseClassCommand(PlayerClass playerClass, GameManager gameManager) {
+        super(gameManager);
+        this.playerClass = playerClass;
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        try {
+            configure(e);
+            player.setPlayerClass(playerClass);
+            write("You are now and forever, a " + CreeperUtils.capitalize(playerClass.getIdentifier()) + "\r\n");
+            e.getChannel().getPipeline().remove("executed_command");
+            e.getChannel().getPipeline().remove("executed_playerclass_command");
+            String s = gameManager.buildPrompt(playerId);
+            write(s);
+        } finally {
+            super.messageReceived(ctx, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/LeaveCommand.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/LeaveCommand.java
new file mode 100644
index 00000000..1dd23b39
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/LeaveCommand.java
@@ -0,0 +1,32 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+import com.comandante.creeper.CreeperEntry;
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.merchant.Merchant;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public class LeaveCommand extends PlayerClassCommand {
+
+    final static List<String> validTriggers = Arrays.asList("leave");
+    final static String description = "Leave the discussion.";
+
+    public LeaveCommand(GameManager gameManager) {
+        super(gameManager);
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        configure(e);
+        creeperSession.setGrabMerchant(Optional.<CreeperEntry<Merchant, SimpleChannelUpstreamHandler>>empty());
+        e.getChannel().getPipeline().remove("executed_command");
+        e.getChannel().getPipeline().remove("executed_playerclass_command");
+        String s = gameManager.buildPrompt(playerId);
+        write(s);
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommand.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommand.java
new file mode 100644
index 00000000..2023bedc
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommand.java
@@ -0,0 +1,107 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+import com.comandante.creeper.CreeperUtils;
+import com.comandante.creeper.Main;
+import com.comandante.creeper.classes.PlayerClass;
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.player.Player;
+import com.comandante.creeper.player.PlayerManager;
+import com.comandante.creeper.server.ChannelCommunicationUtils;
+import com.comandante.creeper.server.Color;
+import com.comandante.creeper.server.CreeperSession;
+import com.comandante.creeper.world.Room;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class PlayerClassCommand extends SimpleChannelUpstreamHandler {
+
+    public final GameManager gameManager;
+    public final PlayerManager playerManager;
+    public final ChannelCommunicationUtils channelUtils;
+    public CreeperSession creeperSession;
+    public Player player;
+    public String playerId;
+    public Room currentRoom;
+    public List<String> originalMessageParts;
+    public String rootCommand;
+    public String description;
+
+    public PlayerClassCommand(GameManager gameManager) {
+        this.gameManager = gameManager;
+        this.playerManager = gameManager.getPlayerManager();
+        this.channelUtils = gameManager.getChannelUtils();
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        try {
+            CreeperSession creeperSession = extractCreeperSession(e.getChannel());
+            e.getChannel().getPipeline().remove("executed_command");
+            e.getChannel().getPipeline().remove("executed_playerclass_command");
+            gameManager.getChannelUtils().write(playerId, PlayerClassCommand.getPrompt(), true);
+            if (creeperSession.getGrabMerchant().isPresent()) {
+                return;
+            }
+        } finally {
+            super.messageReceived(ctx, e);
+        }
+    }
+
+    public void configure(MessageEvent e) {
+        this.creeperSession = extractCreeperSession(e.getChannel());
+        this.player = playerManager.getPlayer(extractPlayerId(creeperSession));
+        this.playerId = player.getPlayerId();
+        this.currentRoom = gameManager.getRoomManager().getPlayerCurrentRoom(player).get();
+        this.originalMessageParts = getOriginalMessageParts(e);
+        rootCommand = getRootCommand(e);
+    }
+
+    public CreeperSession extractCreeperSession(Channel channel) {
+        return (CreeperSession) channel.getAttachment();
+    }
+
+
+    public String extractPlayerId(CreeperSession creeperSession) {
+        return Main.createPlayerId(creeperSession.getUsername().get());
+    }
+
+    public String getRootCommand(MessageEvent e) {
+        String origMessage = (String) e.getMessage();
+        return origMessage.split(" ")[0].toLowerCase();
+    }
+
+    public List<String> getOriginalMessageParts(MessageEvent e) {
+        String origMessage = (String) e.getMessage();
+        return new ArrayList<>(Arrays.asList(origMessage.split(" ")));
+    }
+
+    public void write(String msg) {
+        channelUtils.write(playerId, msg);
+    }
+
+    public static String getPrompt() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Choose from one of the following classes.  Make your choice wisely, as you won't be able to change your mind.").append("\r\n").append("\r\n");
+        Arrays.stream(PlayerClass.values()).forEach(playerClass -> sb.append(CreeperUtils.capitalize(playerClass.getIdentifier())).append(" - ").append(playerClass.getDescription()).append("\r\n"));
+        sb.append("\r\n");
+        sb.append("[").append(Color.GREEN).append("Player Class Selection (Type \"leave\" to decide later)").append(Color.RESET).append("] ");
+        return sb.toString();
+    }
+
+    public <T> T createObj(String nameclass) throws ClassNotFoundException,
+            InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+
+        Class<T> clazz = (Class<T>) Class.forName(nameclass);
+
+        // assumes the target class has a no-args Constructor
+        return clazz.getConstructor(GameManager.class).newInstance(gameManager);
+    }
+
+}
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandHandler.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandHandler.java
new file mode 100644
index 00000000..b90c6344
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandHandler.java
@@ -0,0 +1,51 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+import com.comandante.creeper.ConfigureCommands;
+import com.comandante.creeper.command.CommandAuditLog;
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.merchant.Merchant;
+import com.comandante.creeper.server.CreeperSession;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+
+public class PlayerClassCommandHandler extends SimpleChannelUpstreamHandler {
+
+    private final GameManager gameManager;
+    private final Merchant merchant;
+
+    public PlayerClassCommandHandler(GameManager gameManager, Merchant merchant) {
+        this.gameManager = gameManager;
+        this.merchant = merchant;
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        String rootCommand = getRootCommand(e);
+        CreeperSession session = (CreeperSession) e.getChannel().getAttachment();
+        CommandAuditLog.logCommand(rootCommand, session.getUsername().get());
+        PlayerClassCommand commandByTrigger = ConfigureCommands.playerClassCommandRegistry.getCommandByTrigger(rootCommand);
+        e.getChannel().getPipeline().addLast("executed_playerclass_command", commandByTrigger);
+        super.messageReceived(ctx, e);
+    }
+
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
+        e.getCause().printStackTrace();
+        e.getChannel().close();
+        CreeperSession creeperSession = (CreeperSession) e.getChannel().getAttachment();
+        gameManager.getPlayerManager().removePlayer(creeperSession.getUsername().get());
+    }
+
+    private String getRootCommand(MessageEvent e) {
+        String origMessage = (String) e.getMessage();
+        String[] split = origMessage.split(" ");
+        if (split.length > 0) {
+            return split[0].toLowerCase();
+        } else {
+            return origMessage;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandRegistry.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandRegistry.java
new file mode 100644
index 00000000..ffb9c092
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/PlayerClassCommandRegistry.java
@@ -0,0 +1,31 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+import com.comandante.creeper.classes.PlayerClass;
+import com.comandante.creeper.managers.GameManager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class PlayerClassCommandRegistry {
+
+    private final PlayerClassCommand unknownCommand;
+    private final GameManager gameManager;
+
+    public PlayerClassCommandRegistry(GameManager gameManager) {
+        this.gameManager = gameManager;
+        this.unknownCommand = new UnknownCommand(gameManager);
+    }
+
+    public PlayerClassCommand getCommandByTrigger(String trigger) {
+        if (trigger.equalsIgnoreCase("leave")) {
+            return new LeaveCommand(gameManager);
+        }
+        List<PlayerClass> matchedClasses = Arrays.stream(PlayerClass.values()).filter(playerClass -> playerClass.getIdentifier().equalsIgnoreCase(trigger)).collect(Collectors.toList());
+        if (matchedClasses.size() > 0) {
+            return new ChooseClassCommand(matchedClasses.get(0), gameManager);
+        }
+        return unknownCommand;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/merchant/playerclass_selector/UnknownCommand.java b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/UnknownCommand.java
new file mode 100644
index 00000000..8f2a689c
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/merchant/playerclass_selector/UnknownCommand.java
@@ -0,0 +1,25 @@
+package com.comandante.creeper.merchant.playerclass_selector;
+
+
+import com.comandante.creeper.managers.GameManager;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+
+public class UnknownCommand extends PlayerClassCommand {
+
+    public UnknownCommand(GameManager gameManager) {
+        super(gameManager);
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        configure(e);
+        try {
+            write(getPrompt());
+            e.getChannel().getPipeline().remove("executed_command");
+            e.getChannel().getPipeline().remove("executed_playerclass_command");
+        } finally {
+
+        }
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/player/BasicPlayerLevelStatsModifier.java b/src/main/java/com/comandante/creeper/player/BasicPlayerLevelStatsModifier.java
index 2bd549e5..38b63802 100644
--- a/src/main/java/com/comandante/creeper/player/BasicPlayerLevelStatsModifier.java
+++ b/src/main/java/com/comandante/creeper/player/BasicPlayerLevelStatsModifier.java
@@ -15,15 +15,15 @@ public class BasicPlayerLevelStatsModifier implements StatsModifier {
         this.gameManager = gameManager;
     }
 
-    private static double MELE_CONSTANT_MODIFIER = .2;
-    private static double WILLPOWER_CONSTANT_MODIFIER = .2;
-    private static double INTELLIGENCE_CONSTANT_MODIFIER = .2;
-    private static double AGILE_CONSTANT_MODIFIER = .2;
-    private static double AIM_CONSTANT_MODIFIER = .2;
-    private static double HEALTH_CONSTANT_MODIFIER = 4;
-    private static double ARMOR_CONSTANT_MODIFIER = .2;
-    private static double STRENGTH_CONSTANT_MODIFIER = .2;
-    private static double MANA_CONSTANT_MODIFIER = 2;
+    private static double MELE_CONSTANT_MODIFIER = .02;
+    private static double WILLPOWER_CONSTANT_MODIFIER = .02;
+    private static double INTELLIGENCE_CONSTANT_MODIFIER = .02;
+    private static double AGILE_CONSTANT_MODIFIER = .02;
+    private static double AIM_CONSTANT_MODIFIER = .02;
+    private static double HEALTH_CONSTANT_MODIFIER = 2;
+    private static double ARMOR_CONSTANT_MODIFIER = .02;
+    private static double STRENGTH_CONSTANT_MODIFIER = .02;
+    private static double MANA_CONSTANT_MODIFIER = 1;
 
     public static long getMeleForLevel(long baseStat, long level) {
         double v = (level) * sqrt(pow(level, MELE_CONSTANT_MODIFIER));
diff --git a/src/main/java/com/comandante/creeper/player/Player.java b/src/main/java/com/comandante/creeper/player/Player.java
index 5e42b251..42eaa8a8 100644
--- a/src/main/java/com/comandante/creeper/player/Player.java
+++ b/src/main/java/com/comandante/creeper/player/Player.java
@@ -877,8 +877,12 @@ public class Player extends CreeperEntity {
         Stats origStats = gameManager.getStatsModifierFactory().getStatsModifier(this);
         Stats modifiedStats = getPlayerStatsWithEquipmentAndLevel();
         Stats diffStats = StatsHelper.getDifference(modifiedStats, origStats);
-        sb.append(Color.MAGENTA + "-+=[ " + Color.RESET).append(playerName).append(Color.MAGENTA + " ]=+- " + Color.RESET).append("\r\n");
-        sb.append("Level ").append(Levels.getLevel(origStats.getExperience())).append("\r\n");
+        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");
@@ -925,7 +929,11 @@ public class Player extends CreeperEntity {
 
     public PlayerClass getPlayerClass() {
         synchronized (interner.intern(playerId)) {
-            return getPlayerMetadata().getPlayerClass();
+            PlayerClass playerClass = getPlayerMetadata().getPlayerClass();
+            if (playerClass == null) {
+                return PlayerClass.BASIC;
+            }
+            return playerClass;
         }
     }
 
diff --git a/src/main/java/com/comandante/creeper/player/PlayerStats.java b/src/main/java/com/comandante/creeper/player/PlayerStats.java
index aa284f6a..0501e379 100644
--- a/src/main/java/com/comandante/creeper/player/PlayerStats.java
+++ b/src/main/java/com/comandante/creeper/player/PlayerStats.java
@@ -6,17 +6,17 @@ import com.comandante.creeper.stat.StatsBuilder;
 public class PlayerStats {
 
     public final static StatsBuilder DEFAULT_PLAYER = new StatsBuilder()
-            .setStrength(10)
-            .setIntelligence(5)
+            .setStrength(1)
+            .setIntelligence(1)
             .setWillpower(1)
             .setAim(1)
             .setAgile(1)
-            .setArmorRating(2)
-            .setMeleSkill(10)
+            .setArmorRating(1)
+            .setMeleSkill(1)
             .setCurrentHealth(100)
             .setMaxHealth(100)
-            .setWeaponRatingMin(3)
-            .setWeaponRatingMax(5)
+            .setWeaponRatingMin(1)
+            .setWeaponRatingMax(2)
             .setNumberOfWeaponRolls(1)
             .setExperience(0)
             .setCurrentMana(100)
diff --git a/src/main/java/com/comandante/creeper/player/RangerStatsModifier.java b/src/main/java/com/comandante/creeper/player/RangerStatsModifier.java
new file mode 100644
index 00000000..5f55de27
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/player/RangerStatsModifier.java
@@ -0,0 +1,101 @@
+package com.comandante.creeper.player;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.stat.Stats;
+import com.comandante.creeper.stat.StatsBuilder;
+
+import static java.lang.Math.pow;
+import static java.lang.StrictMath.sqrt;
+
+public class RangerStatsModifier implements StatsModifier {
+
+    private final GameManager gameManager;
+
+    public RangerStatsModifier(GameManager gameManager) {
+        this.gameManager = gameManager;
+    }
+
+    private static double MELE_CONSTANT_MODIFIER = .3;
+    private static double WILLPOWER_CONSTANT_MODIFIER = .5;
+    private static double INTELLIGENCE_CONSTANT_MODIFIER = .8;
+    private static double AGILE_CONSTANT_MODIFIER = .3;
+    private static double AIM_CONSTANT_MODIFIER = .5;
+    private static double HEALTH_CONSTANT_MODIFIER = 3;
+    private static double ARMOR_CONSTANT_MODIFIER = .2;
+    private static double STRENGTH_CONSTANT_MODIFIER = .3;
+    private static double MANA_CONSTANT_MODIFIER = 4;
+
+    public static long getMeleForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MELE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getWillpowerForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, WILLPOWER_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getIntelligenceForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, INTELLIGENCE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAgileForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AGILE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAimForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AIM_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getStrengthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, STRENGTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getArmorForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, ARMOR_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getHealthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, HEALTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getManaForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MANA_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    @Override
+    public Stats modify(Player player) {
+        PlayerMetadata playerMetadata = gameManager.getPlayerManager().getPlayerMetadata(player.getPlayerId());
+        Stats baseStats = playerMetadata.getStats();
+        long level = Levels.getLevel(baseStats.getExperience());
+        long newMaxHealth = getHealthForLevel(baseStats.getMaxHealth(), level);
+        long newArmorRating = getArmorForLevel(baseStats.getArmorRating(), level);
+        long newStrengthRating = getStrengthForLevel(baseStats.getStrength(), level);
+        long newMaxMana = getManaForLevel(baseStats.getMaxMana(), level);
+        long newAimRating = getAimForLevel(baseStats.getAim(), level);
+        long newWillpowerRating = getWillpowerForLevel(baseStats.getWillpower(), level);
+        long newIntelligenceRating = getIntelligenceForLevel(baseStats.getIntelligence(), level);
+        long newAgileRating = getAgileForLevel(baseStats.getAgile(), level);
+        long newMeleRating = getMeleForLevel(baseStats.getMeleSkill(), level);
+        StatsBuilder statsBuilder = new StatsBuilder(baseStats);
+        statsBuilder.setMaxHealth(newMaxHealth);
+        statsBuilder.setArmorRating(newArmorRating);
+        statsBuilder.setStrength(newStrengthRating);
+        statsBuilder.setIntelligence(newIntelligenceRating);
+        statsBuilder.setMaxMana(newMaxMana);
+        statsBuilder.setAim(newAimRating);
+        statsBuilder.setWillpower(newWillpowerRating);
+        statsBuilder.setAgile(newAgileRating);
+        statsBuilder.setMeleSkill(newMeleRating);
+        statsBuilder.setCurrentHealth(baseStats.getCurrentHealth());
+        statsBuilder.setCurrentMana(baseStats.getCurrentMana());
+        return statsBuilder.createStats();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/player/ShamanStatsModifier.java b/src/main/java/com/comandante/creeper/player/ShamanStatsModifier.java
new file mode 100644
index 00000000..c79f11fe
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/player/ShamanStatsModifier.java
@@ -0,0 +1,101 @@
+package com.comandante.creeper.player;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.stat.Stats;
+import com.comandante.creeper.stat.StatsBuilder;
+
+import static java.lang.Math.pow;
+import static java.lang.StrictMath.sqrt;
+
+public class ShamanStatsModifier implements StatsModifier {
+
+    private final GameManager gameManager;
+
+    public ShamanStatsModifier(GameManager gameManager) {
+        this.gameManager = gameManager;
+    }
+
+    private static double MELE_CONSTANT_MODIFIER = .4;
+    private static double WILLPOWER_CONSTANT_MODIFIER = .9;
+    private static double INTELLIGENCE_CONSTANT_MODIFIER = .7;
+    private static double AGILE_CONSTANT_MODIFIER = .3;
+    private static double AIM_CONSTANT_MODIFIER = .4;
+    private static double HEALTH_CONSTANT_MODIFIER = 2;
+    private static double ARMOR_CONSTANT_MODIFIER = .4;
+    private static double STRENGTH_CONSTANT_MODIFIER = .4;
+    private static double MANA_CONSTANT_MODIFIER = 5;
+
+    public static long getMeleForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MELE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getWillpowerForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, WILLPOWER_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getIntelligenceForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, INTELLIGENCE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAgileForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AGILE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAimForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AIM_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getStrengthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, STRENGTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getArmorForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, ARMOR_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getHealthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, HEALTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getManaForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MANA_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    @Override
+    public Stats modify(Player player) {
+        PlayerMetadata playerMetadata = gameManager.getPlayerManager().getPlayerMetadata(player.getPlayerId());
+        Stats baseStats = playerMetadata.getStats();
+        long level = Levels.getLevel(baseStats.getExperience());
+        long newMaxHealth = getHealthForLevel(baseStats.getMaxHealth(), level);
+        long newArmorRating = getArmorForLevel(baseStats.getArmorRating(), level);
+        long newStrengthRating = getStrengthForLevel(baseStats.getStrength(), level);
+        long newMaxMana = getManaForLevel(baseStats.getMaxMana(), level);
+        long newAimRating = getAimForLevel(baseStats.getAim(), level);
+        long newWillpowerRating = getWillpowerForLevel(baseStats.getWillpower(), level);
+        long newIntelligenceRating = getIntelligenceForLevel(baseStats.getIntelligence(), level);
+        long newAgileRating = getAgileForLevel(baseStats.getAgile(), level);
+        long newMeleRating = getMeleForLevel(baseStats.getMeleSkill(), level);
+        StatsBuilder statsBuilder = new StatsBuilder(baseStats);
+        statsBuilder.setMaxHealth(newMaxHealth);
+        statsBuilder.setArmorRating(newArmorRating);
+        statsBuilder.setStrength(newStrengthRating);
+        statsBuilder.setIntelligence(newIntelligenceRating);
+        statsBuilder.setMaxMana(newMaxMana);
+        statsBuilder.setAim(newAimRating);
+        statsBuilder.setWillpower(newWillpowerRating);
+        statsBuilder.setAgile(newAgileRating);
+        statsBuilder.setMeleSkill(newMeleRating);
+        statsBuilder.setCurrentHealth(baseStats.getCurrentHealth());
+        statsBuilder.setCurrentMana(baseStats.getCurrentMana());
+        return statsBuilder.createStats();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/player/StatsModifierFactory.java b/src/main/java/com/comandante/creeper/player/StatsModifierFactory.java
index 4a321c0e..5dca85d4 100644
--- a/src/main/java/com/comandante/creeper/player/StatsModifierFactory.java
+++ b/src/main/java/com/comandante/creeper/player/StatsModifierFactory.java
@@ -13,7 +13,25 @@ public class StatsModifierFactory {
     }
 
     public Stats getStatsModifier(Player player) {
-        BasicPlayerLevelStatsModifier basicPlayerLevelStatsModifier = new BasicPlayerLevelStatsModifier(gameManager);
-        return basicPlayerLevelStatsModifier.modify(player);
+        StatsModifier modifer = new BasicPlayerLevelStatsModifier(gameManager);
+        switch (player.getPlayerClass()) {
+            case WARRIOR:
+                modifer = new WarriorStatsModifier(gameManager);
+                break;
+            case WIZARD:
+                modifer = new WizardStatsModifier(gameManager);
+                break;
+            case RANGER:
+                modifer = new RangerStatsModifier(gameManager);
+                break;
+            case SHAMAN:
+                modifer = new ShamanStatsModifier(gameManager);
+                break;
+            default:
+                modifer = new BasicPlayerLevelStatsModifier(gameManager);
+                break;
+
+        }
+        return modifer.modify(player);
     }
 }
diff --git a/src/main/java/com/comandante/creeper/player/WizardStatsModifier.java b/src/main/java/com/comandante/creeper/player/WizardStatsModifier.java
new file mode 100644
index 00000000..3dd56b97
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/player/WizardStatsModifier.java
@@ -0,0 +1,102 @@
+package com.comandante.creeper.player;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.stat.Stats;
+import com.comandante.creeper.stat.StatsBuilder;
+
+import static java.lang.Math.pow;
+import static java.lang.StrictMath.sqrt;
+
+
+public class WizardStatsModifier implements StatsModifier {
+
+    private final GameManager gameManager;
+
+    public WizardStatsModifier(GameManager gameManager) {
+        this.gameManager = gameManager;
+    }
+
+    private static double MELE_CONSTANT_MODIFIER = .3;
+    private static double WILLPOWER_CONSTANT_MODIFIER = .5;
+    private static double INTELLIGENCE_CONSTANT_MODIFIER = .8;
+    private static double AGILE_CONSTANT_MODIFIER = .3;
+    private static double AIM_CONSTANT_MODIFIER = .5;
+    private static double HEALTH_CONSTANT_MODIFIER = 3;
+    private static double ARMOR_CONSTANT_MODIFIER = .2;
+    private static double STRENGTH_CONSTANT_MODIFIER = .3;
+    private static double MANA_CONSTANT_MODIFIER = 4;
+
+    public static long getMeleForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MELE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getWillpowerForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, WILLPOWER_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getIntelligenceForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, INTELLIGENCE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAgileForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AGILE_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getAimForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, AIM_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getStrengthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, STRENGTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getArmorForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, ARMOR_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getHealthForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, HEALTH_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    public static long getManaForLevel(long baseStat, long level) {
+        double v = (level) * sqrt(pow(level, MANA_CONSTANT_MODIFIER));
+        return (long) Math.floor(v) + baseStat;
+    }
+
+    @Override
+    public Stats modify(Player player) {
+        PlayerMetadata playerMetadata = gameManager.getPlayerManager().getPlayerMetadata(player.getPlayerId());
+        Stats baseStats = playerMetadata.getStats();
+        long level = Levels.getLevel(baseStats.getExperience());
+        long newMaxHealth = getHealthForLevel(baseStats.getMaxHealth(), level);
+        long newArmorRating = getArmorForLevel(baseStats.getArmorRating(), level);
+        long newStrengthRating = getStrengthForLevel(baseStats.getStrength(), level);
+        long newMaxMana = getManaForLevel(baseStats.getMaxMana(), level);
+        long newAimRating = getAimForLevel(baseStats.getAim(), level);
+        long newWillpowerRating = getWillpowerForLevel(baseStats.getWillpower(), level);
+        long newIntelligenceRating = getIntelligenceForLevel(baseStats.getIntelligence(), level);
+        long newAgileRating = getAgileForLevel(baseStats.getAgile(), level);
+        long newMeleRating = getMeleForLevel(baseStats.getMeleSkill(), level);
+        StatsBuilder statsBuilder = new StatsBuilder(baseStats);
+        statsBuilder.setMaxHealth(newMaxHealth);
+        statsBuilder.setArmorRating(newArmorRating);
+        statsBuilder.setStrength(newStrengthRating);
+        statsBuilder.setIntelligence(newIntelligenceRating);
+        statsBuilder.setMaxMana(newMaxMana);
+        statsBuilder.setAim(newAimRating);
+        statsBuilder.setWillpower(newWillpowerRating);
+        statsBuilder.setAgile(newAgileRating);
+        statsBuilder.setMeleSkill(newMeleRating);
+        statsBuilder.setCurrentHealth(baseStats.getCurrentHealth());
+        statsBuilder.setCurrentMana(baseStats.getCurrentMana());
+        return statsBuilder.createStats();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/server/CreeperCommandHandler.java b/src/main/java/com/comandante/creeper/server/CreeperCommandHandler.java
index 968074cc..b5d84c4b 100644
--- a/src/main/java/com/comandante/creeper/server/CreeperCommandHandler.java
+++ b/src/main/java/com/comandante/creeper/server/CreeperCommandHandler.java
@@ -13,6 +13,7 @@ import com.comandante.creeper.merchant.Merchant;
 import com.comandante.creeper.merchant.MerchantCommandHandler;
 import com.comandante.creeper.merchant.bank.commands.BankCommandHandler;
 import com.comandante.creeper.merchant.lockers.LockerCommandHandler;
+import com.comandante.creeper.merchant.playerclass_selector.PlayerClassCommandHandler;
 import com.comandante.creeper.player.Player;
 import org.apache.log4j.Logger;
 import org.jboss.netty.channel.*;
@@ -43,6 +44,8 @@ public class CreeperCommandHandler extends SimpleChannelUpstreamHandler {
                 addLastHandler(e, new BankCommandHandler(gameManager, merchant));
             } else if (merchant.getMerchantType() == Merchant.MerchantType.LOCKER) {
                 addLastHandler(e, new LockerCommandHandler(gameManager, merchant));
+            } else if (merchant.getMerchantType() == Merchant.MerchantType.PLAYERCLASS_SELECTOR) {
+                addLastHandler(e, new PlayerClassCommandHandler(gameManager, merchant));
             } else {
                 addLastHandler(e, new MerchantCommandHandler(gameManager, merchant));
             }
diff --git a/src/test/com/comandante/creeper/CreeperUtilsTest.java b/src/test/com/comandante/creeper/CreeperUtilsTest.java
index b400dc4e..da2f7ab6 100644
--- a/src/test/com/comandante/creeper/CreeperUtilsTest.java
+++ b/src/test/com/comandante/creeper/CreeperUtilsTest.java
@@ -21,7 +21,7 @@ public class CreeperUtilsTest {
         String[] strings = new String[2];
         strings[0] = "feet";
         strings[1] = "hand";
-        PlayerMetadata playerMetadata = new PlayerMetadata("usertest", "Testtest", Main.createPlayerId("usertest"), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), strings, 0, new String[0], Maps.newHashMap(), PlayerClass.NOCLASS);
+        PlayerMetadata playerMetadata = new PlayerMetadata("usertest", "Testtest", Main.createPlayerId("usertest"), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), strings, 0, new String[0], Maps.newHashMap(), PlayerClass.BASIC);
         GameManager gameManager = mock(GameManager.class);
         StatsModifierFactory statsModifierFactory = mock(StatsModifierFactory.class);
         when(statsModifierFactory.getStatsModifier(Matchers.any())).thenReturn(PlayerStats.DEFAULT_PLAYER.createStats());
diff --git a/src/test/com/comandante/creeper/player/NpcTestHarness.java b/src/test/com/comandante/creeper/player/NpcTestHarness.java
index c139400b..dbf1c1fc 100644
--- a/src/test/com/comandante/creeper/player/NpcTestHarness.java
+++ b/src/test/com/comandante/creeper/player/NpcTestHarness.java
@@ -91,12 +91,11 @@ public class NpcTestHarness {
     @Test
     public void testCombat() throws Exception {
         List<Npc> npcsFromFile = NpcExporter.getNpcsFromFile(gameManager);
-        Npc treeBerseker = npcsFromFile.stream().filter(npc -> npc.getName().equals("swamp bear")).collect(Collectors.toList()).get(0);
+        Npc treeBerseker = npcsFromFile.stream().filter(npc -> npc.getName().equals("red-eyed bear")).collect(Collectors.toList()).get(0);
         int totalIterations = 100;
         Player player;
         Npc npc = null;
-        Table t = new Table(8, BorderStyle.BLANKS,
-                ShownBorders.NONE);
+        Table t = new Table(8, BorderStyle.BLANKS, ShownBorders.NONE);
         t.setColumnWidth(0, 20, 20);
         t.setColumnWidth(1, 15, 20);
         t.setColumnWidth(2, 13, 16);
@@ -115,14 +114,14 @@ public class NpcTestHarness {
         t.addCell("XP Earned");
         t.addCell("Drops");
         Set<Item> equipment = Sets.newHashSet();
-        equipment.add(ItemType.BERSEKER_BOOTS.create());
-        equipment.add(ItemType.BERSERKER_BATON.create());
-        equipment.add(ItemType.BERSERKER_CHEST.create());
-        equipment.add(ItemType.BERSEKER_SHORTS.create());
-        equipment.add(ItemType.BERSERKER_BRACERS.create());
-        equipment.add(ItemType.BERSEKER_HELM.create());
+       // equipment.add(ItemType.BERSEKER_BOOTS.create());
+       // equipment.add(ItemType.BERSERKER_BATON.create());
+       // equipment.add(ItemType.BERSERKER_CHEST.create());
+       // equipment.add(ItemType.BERSEKER_SHORTS.create());
+       // equipment.add(ItemType.BERSERKER_BRACERS.create());
+       // equipment.add(ItemType.BERSEKER_HELM.create());
 
-        for (int level = 0; level < 10; level++) {
+        for (int level = 20; level < 30; level++) {
             int playerWins = 0;
             int npcWins = 0;
             int totalGold = 0;
@@ -213,6 +212,7 @@ public class NpcTestHarness {
         gameManager.placePlayerInLobby(player);
         gameManager.getPlayerManager().getSessionManager().putSession(creeperSession);
         player.addExperience(Levels.getXp(level));
+        player.setPlayerClass(PlayerClass.WARRIOR);
         return player;
     }
 
@@ -225,7 +225,7 @@ public class NpcTestHarness {
     }
 
     private void createUser(String username, String password) {
-        PlayerMetadata playerMetadata = new PlayerMetadata(username, password, Main.createPlayerId(username), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), new String[0], 0, new String[0], Maps.newHashMap(), PlayerClass.NOCLASS);
+        PlayerMetadata playerMetadata = new PlayerMetadata(username, password, Main.createPlayerId(username), PlayerStats.DEFAULT_PLAYER.createStats(), 0, Sets.newHashSet(PlayerRole.MORTAL), new String[0], 0, new String[0], Maps.newHashMap(), PlayerClass.BASIC);
         gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
     }
 
-- 
GitLab