From 00f3e5f5705b7b75f25a031cf82ce6d3a1c1d2e5 Mon Sep 17 00:00:00 2001
From: Chris Kearney <chris.kearney@urbanairship.com>
Date: Thu, 4 Sep 2014 22:06:07 -0700
Subject: [PATCH] tagging, saving the world (rooms) to json, a world/ directory

---
 .gitignore                                    |  1 +
 .../java/com/comandante/creeper/Main.java     | 33 ++++++---
 .../creeper/managers/GameManager.java         | 16 ++++-
 .../comandante/creeper/room/FloorManager.java | 37 ++++++++++
 .../comandante/creeper/room/FloorModel.java   | 43 ++++++++++++
 .../comandante/creeper/room/MapMatrix.java    | 14 ++++
 .../comandante/creeper/room/MapsManager.java  |  4 ++
 .../comandante/creeper/room/RoomManager.java  | 12 ++++
 .../comandante/creeper/room/RoomModel.java    | 20 +++++-
 .../creeper/room/RoomModelBuilder.java        | 34 +++++++++
 .../creeper/room/WorldExporter.java           | 70 +++++++++++++++++++
 .../{ => admin}/DescriptionCommand.java       |  3 +-
 .../command/admin/SaveWorldCommand.java       | 34 +++++++++
 .../server/command/admin/TagRoomCommand.java  | 49 +++++++++++++
 .../command/{ => admin}/TitleCommand.java     |  3 +-
 15 files changed, 358 insertions(+), 15 deletions(-)
 create mode 100644 src/main/java/com/comandante/creeper/room/FloorManager.java
 create mode 100644 src/main/java/com/comandante/creeper/room/FloorModel.java
 create mode 100644 src/main/java/com/comandante/creeper/room/RoomModelBuilder.java
 create mode 100644 src/main/java/com/comandante/creeper/room/WorldExporter.java
 rename src/main/java/com/comandante/creeper/server/command/{ => admin}/DescriptionCommand.java (95%)
 create mode 100644 src/main/java/com/comandante/creeper/server/command/admin/SaveWorldCommand.java
 create mode 100644 src/main/java/com/comandante/creeper/server/command/admin/TagRoomCommand.java
 rename src/main/java/com/comandante/creeper/server/command/{ => admin}/TitleCommand.java (93%)

diff --git a/.gitignore b/.gitignore
index a3a19482..e484f324 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ creeperDb*
 *.pyc
 *.bak
 .idea/
+world/
diff --git a/src/main/java/com/comandante/creeper/Main.java b/src/main/java/com/comandante/creeper/Main.java
index 8736d1aa..96f189da 100644
--- a/src/main/java/com/comandante/creeper/Main.java
+++ b/src/main/java/com/comandante/creeper/Main.java
@@ -14,7 +14,6 @@ import com.comandante.creeper.room.RoomLayoutCsvPrototype;
 import com.comandante.creeper.room.RoomManager;
 import com.comandante.creeper.server.CreeperCommandRegistry;
 import com.comandante.creeper.server.CreeperServer;
-import com.comandante.creeper.server.command.DescriptionCommand;
 import com.comandante.creeper.server.command.DropCommand;
 import com.comandante.creeper.server.command.GossipCommand;
 import com.comandante.creeper.server.command.InventoryCommand;
@@ -24,21 +23,26 @@ import com.comandante.creeper.server.command.MovementCommand;
 import com.comandante.creeper.server.command.PickUpCommand;
 import com.comandante.creeper.server.command.SayCommand;
 import com.comandante.creeper.server.command.TellCommand;
-import com.comandante.creeper.server.command.TitleCommand;
 import com.comandante.creeper.server.command.UnknownCommand;
 import com.comandante.creeper.server.command.UseCommand;
 import com.comandante.creeper.server.command.WhoCommand;
 import com.comandante.creeper.server.command.WhoamiCommand;
+import com.comandante.creeper.server.command.admin.DescriptionCommand;
+import com.comandante.creeper.server.command.admin.SaveWorldCommand;
+import com.comandante.creeper.server.command.admin.TagRoomCommand;
+import com.comandante.creeper.server.command.admin.TitleCommand;
 import com.comandante.creeper.spawner.NpcSpawner;
 import com.comandante.creeper.spawner.SpawnRule;
 import com.comandante.creeper.stat.Stats;
 import com.comandante.creeper.stat.StatsBuilder;
 import com.google.common.collect.Sets;
+import com.google.common.io.Files;
 import org.apache.commons.codec.binary.Base64;
 import org.mapdb.DB;
 import org.mapdb.DBMaker;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -48,33 +52,32 @@ public class Main {
 
     public static void main(String[] args) throws Exception {
 
-        DB db = DBMaker.newFileDB(new File("creeperDb"))
-                .closeOnJvmShutdown()
-                .encryptionEnable("creepandicrawl")
-                .make();
+        checkAndCreateWorld();
+
+        DB db = DBMaker.newFileDB(new File("world/creeper.mapdb")).closeOnJvmShutdown().encryptionEnable("creepandicrawl").make();
 
         RoomManager roomManager = new RoomManager();
         PlayerManager playerManager = new PlayerManager(db, new SessionManager());
-
         EntityManager entityManager = new EntityManager(roomManager, playerManager, db);
-
         Stats chrisBrianStats = new StatsBuilder().setStrength(7).setWillpower(8).setAim(6).setAgile(5).setArmorRating(4).setMeleSkill(10).setCurrentHealth(100).setMaxHealth(100).setWeaponRatingMin(10).setWeaponRatingMax(20).setNumberweaponOfRolls(1).createStats();
         if (playerManager.getPlayerMetadata(createPlayerId("chris")) == null) {
             System.out.println("Creating Chris User.");
             playerManager.savePlayerMetadata(new PlayerMetadata("chris", "poop", new String(Base64.encodeBase64("chris".getBytes())), chrisBrianStats));
         }
-
         if (playerManager.getPlayerMetadata(createPlayerId("brian")) == null) {
             System.out.println("Creating Brian User.");
             playerManager.savePlayerMetadata(new PlayerMetadata("brian", "poop", new String(Base64.encodeBase64("brian".getBytes())), chrisBrianStats));
         }
-
-        GameManager gameManager = new GameManager(roomManager, playerManager, entityManager);
         MapMatrix floorMapMatrix = RoomLayoutCsvPrototype.buildRooms(entityManager);
         System.out.print("Building all rooms.");
         MapsManager mapsManager = new MapsManager(roomManager);
         mapsManager.addFloorMatrix(1, floorMapMatrix);
         mapsManager.generateAllMaps(9, 9);
+        GameManager gameManager = new GameManager(roomManager, playerManager, entityManager, mapsManager);
+        gameManager.getFloorManager().addFloor(1, "main");
+
+
+
         entityManager.addEntity(new NpcSpawner(new StreetHustler(gameManager), Area.NEWBIE_ZONE, gameManager, new SpawnRule(10, 100, 4, 100)));
         Iterator<Map.Entry<Integer, Room>> rooms = roomManager.getRooms();
         while (rooms.hasNext()) {
@@ -97,6 +100,8 @@ public class Main {
         creeperCommandRegistry.addCommand(new WhoCommand(gameManager));
         creeperCommandRegistry.addCommand(new DescriptionCommand(gameManager));
         creeperCommandRegistry.addCommand(new TitleCommand(gameManager));
+        creeperCommandRegistry.addCommand(new TagRoomCommand(gameManager));
+        creeperCommandRegistry.addCommand(new SaveWorldCommand(gameManager));
 
         CreeperServer creeperServer = new CreeperServer(8080, db);
         creeperServer.run(gameManager);
@@ -104,6 +109,12 @@ public class Main {
         System.out.println("Creeper started.");
     }
 
+    private static void checkAndCreateWorld() throws IOException {
+        if (!Files.isDirectory().apply(new File("world/"))) {
+            Files.createParentDirs(new File("world/creeper_world_stuff"));
+        }
+    }
+
     public static String createPlayerId(String playerName) {
         return new String(Base64.encodeBase64(playerName.getBytes()));
     }
diff --git a/src/main/java/com/comandante/creeper/managers/GameManager.java b/src/main/java/com/comandante/creeper/managers/GameManager.java
index 88cb26df..483c1ef6 100644
--- a/src/main/java/com/comandante/creeper/managers/GameManager.java
+++ b/src/main/java/com/comandante/creeper/managers/GameManager.java
@@ -9,6 +9,8 @@ import com.comandante.creeper.npc.Npc;
 import com.comandante.creeper.player.Player;
 import com.comandante.creeper.player.PlayerManager;
 import com.comandante.creeper.player.PlayerMovement;
+import com.comandante.creeper.room.FloorManager;
+import com.comandante.creeper.room.MapsManager;
 import com.comandante.creeper.room.Room;
 import com.comandante.creeper.room.RoomManager;
 import com.comandante.creeper.server.ChannelUtils;
@@ -47,8 +49,10 @@ public class GameManager {
     private final ItemDecayManager itemDecayManager;
     private final FightManager fightManager;
     private final MultiLineInputManager multiLineInputManager;
+    private final MapsManager mapsManager;
+    private final FloorManager floorManager;
 
-    public GameManager(RoomManager roomManager, PlayerManager playerManager, EntityManager entityManager) {
+    public GameManager(RoomManager roomManager, PlayerManager playerManager, EntityManager entityManager, MapsManager mapsManager) {
         this.roomManager = roomManager;
         this.playerManager = playerManager;
         this.entityManager = entityManager;
@@ -58,6 +62,16 @@ public class GameManager {
         this.channelUtils = new ChannelUtils(getPlayerManager(), getRoomManager());
         this.fightManager = new FightManager(channelUtils, entityManager, playerManager);
         this.multiLineInputManager = new MultiLineInputManager();
+        this.mapsManager = mapsManager;
+        this.floorManager = new FloorManager();
+    }
+
+    public FloorManager getFloorManager() {
+        return floorManager;
+    }
+
+    public MapsManager getMapsManager() {
+        return mapsManager;
     }
 
     public MultiLineInputManager getMultiLineInputManager() {
diff --git a/src/main/java/com/comandante/creeper/room/FloorManager.java b/src/main/java/com/comandante/creeper/room/FloorManager.java
new file mode 100644
index 00000000..b18bcec8
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/room/FloorManager.java
@@ -0,0 +1,37 @@
+package com.comandante.creeper.room;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.Map;
+import java.util.Set;
+
+public class FloorManager {
+
+    private final Map<Integer, String> floorIdLookup = Maps.newConcurrentMap();
+
+    public void addFloor(Integer fid, String s) {
+        floorIdLookup.put(fid, s);
+    }
+
+    public String getName(Integer fid) {
+        return floorIdLookup.get(fid);
+    }
+
+    public Integer getId(String name) {
+        for (Map.Entry<Integer, String> next : floorIdLookup.entrySet()) {
+            if (next.getValue().equals(name)) {
+                return next.getKey();
+            }
+        }
+        return 0;
+    }
+
+    public Set<Integer> getFloorIds() {
+        Set<Integer> ids = Sets.newHashSet();
+        for (Map.Entry<Integer, String> next : floorIdLookup.entrySet()) {
+            ids.add(next.getKey());
+        }
+        return ids;
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/room/FloorModel.java b/src/main/java/com/comandante/creeper/room/FloorModel.java
new file mode 100644
index 00000000..f41f5b49
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/room/FloorModel.java
@@ -0,0 +1,43 @@
+package com.comandante.creeper.room;
+
+import java.util.Set;
+
+public class FloorModel {
+
+    String name;
+    Integer id;
+    String rawMatrixCsv;
+    Set<RoomModel> roomModels;
+
+    public Set<RoomModel> getRoomModels() {
+        return roomModels;
+    }
+
+    public void setRoomModels(Set<RoomModel> roomModels) {
+        this.roomModels = roomModels;
+    }
+
+    public String getRawMatrixCsv() {
+        return rawMatrixCsv;
+    }
+
+    public void setRawMatrixCsv(String rawMatrixCsv) {
+        this.rawMatrixCsv = rawMatrixCsv;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/room/MapMatrix.java b/src/main/java/com/comandante/creeper/room/MapMatrix.java
index e8945255..90f904ca 100644
--- a/src/main/java/com/comandante/creeper/room/MapMatrix.java
+++ b/src/main/java/com/comandante/creeper/room/MapMatrix.java
@@ -134,6 +134,20 @@ public class MapMatrix {
         };
     }
 
+    public String getCsv() {
+        StringBuilder sb = new StringBuilder();
+        for (List<Integer> list: matrix) {
+            for (Integer roomId: list) {
+                if (!roomId.equals(0)) {
+                    sb.append(roomId);
+                }
+                sb.append(",");
+            }
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
     public static MapMatrix createMatrixFromCsv(String mapCSV) {
         List<String> rows = Arrays.asList(mapCSV.split("\n"));
         ArrayList<List<Integer>> rowsList = Lists.newArrayList();
diff --git a/src/main/java/com/comandante/creeper/room/MapsManager.java b/src/main/java/com/comandante/creeper/room/MapsManager.java
index ca9a00bb..e4cf561c 100644
--- a/src/main/java/com/comandante/creeper/room/MapsManager.java
+++ b/src/main/java/com/comandante/creeper/room/MapsManager.java
@@ -56,4 +56,8 @@ public class MapsManager {
     public void addFloorMatrix(Integer id, MapMatrix floorMatrix) {
         floorMatrixMaps.put(id, floorMatrix);
     }
+
+    public Map<Integer, MapMatrix> getFloorMatrixMaps() {
+        return floorMatrixMaps;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/room/RoomManager.java b/src/main/java/com/comandante/creeper/room/RoomManager.java
index 4c28dd5c..767331b9 100644
--- a/src/main/java/com/comandante/creeper/room/RoomManager.java
+++ b/src/main/java/com/comandante/creeper/room/RoomManager.java
@@ -53,6 +53,18 @@ public class RoomManager {
         return rooms.entrySet().iterator();
     }
 
+    public Set<Room> getRoomsByFloorId(Integer floorId) {
+        Set<Room> rooms = Sets.newHashSet();
+        Iterator<Map.Entry<Integer, Room>> rooms1 = getRooms();
+        while (rooms1.hasNext()) {
+            Map.Entry<Integer, Room> next = rooms1.next();
+            if (next.getValue().getFloorId().equals(floorId)) {
+                rooms.add(next.getValue());
+            }
+        }
+        return rooms;
+    }
+
     public Optional<Room> getPlayerCurrentRoom(Player player) {
         Iterator<Map.Entry<Integer, Room>> rooms = getRooms();
         while (rooms.hasNext()) {
diff --git a/src/main/java/com/comandante/creeper/room/RoomModel.java b/src/main/java/com/comandante/creeper/room/RoomModel.java
index 9b311d16..8c140766 100644
--- a/src/main/java/com/comandante/creeper/room/RoomModel.java
+++ b/src/main/java/com/comandante/creeper/room/RoomModel.java
@@ -2,11 +2,29 @@ package com.comandante.creeper.room;
 
 import com.google.gson.GsonBuilder;
 
+import java.util.Set;
+
 public class RoomModel {
 
     int roomId;
     String roomDescription;
     String roomTitle;
+    Set<String> roomTags;
+
+    public RoomModel(int roomId, String roomDescription, String roomTitle, Set<String> roomTags) {
+        this.roomId = roomId;
+        this.roomDescription = roomDescription;
+        this.roomTitle = roomTitle;
+        this.roomTags = roomTags;
+    }
+
+    public Set<String> getRoomTags() {
+        return roomTags;
+    }
+
+    public void setRoomTags(Set<String> roomTags) {
+        this.roomTags = roomTags;
+    }
 
     public int getRoomId() {
         return roomId;
@@ -34,7 +52,7 @@ public class RoomModel {
 
     public static void main(String[] args) {
 
-        RoomModel roomModel = new RoomModel();
+        RoomModel roomModel = new RoomModelBuilder().build();
         roomModel.setRoomId(1);
         roomModel.setRoomDescription("A large and empty area.");
         roomModel.setRoomTitle("The flimflam.");
diff --git a/src/main/java/com/comandante/creeper/room/RoomModelBuilder.java b/src/main/java/com/comandante/creeper/room/RoomModelBuilder.java
new file mode 100644
index 00000000..2b82dbdf
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/room/RoomModelBuilder.java
@@ -0,0 +1,34 @@
+package com.comandante.creeper.room;
+
+import java.util.Set;
+
+public class RoomModelBuilder {
+    private int roomId;
+    private String roomDescription;
+    private String roomTitle;
+    private Set<String> roomTags;
+
+    public RoomModelBuilder setRoomId(int roomId) {
+        this.roomId = roomId;
+        return this;
+    }
+
+    public RoomModelBuilder setRoomDescription(String roomDescription) {
+        this.roomDescription = roomDescription;
+        return this;
+    }
+
+    public RoomModelBuilder setRoomTitle(String roomTitle) {
+        this.roomTitle = roomTitle;
+        return this;
+    }
+
+    public RoomModelBuilder setRoomTags(Set<String> roomTags) {
+        this.roomTags = roomTags;
+        return this;
+    }
+
+    public RoomModel build() {
+        return new RoomModel(roomId, roomDescription, roomTitle, roomTags);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/comandante/creeper/room/WorldExporter.java b/src/main/java/com/comandante/creeper/room/WorldExporter.java
new file mode 100644
index 00000000..2ff1c0b8
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/room/WorldExporter.java
@@ -0,0 +1,70 @@
+package com.comandante.creeper.room;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterators;
+import com.google.common.io.Files;
+import com.google.gson.GsonBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class WorldExporter {
+
+    private final static String WORLD_DIR = "world/";
+
+    private final RoomManager roomManager;
+    private final MapsManager mapsManager;
+    private final FloorManager floorManager;
+
+    public WorldExporter(RoomManager roomManager, MapsManager mapsManager, FloorManager floorManager) {
+        this.roomManager = roomManager;
+        this.mapsManager = mapsManager;
+        this.floorManager = floorManager;
+    }
+
+    public void saveWorld() {
+        Set<Integer> floorIds = floorManager.getFloorIds();
+        for (Integer floorId : floorIds) {
+            writeFloor(floorId, mapsManager.getFloorMatrixMaps().get(floorId));
+        }
+    }
+
+    private void writeFloor(Integer floorId, MapMatrix mapMatrix) {
+        Set<Room> rooms = roomManager.getRoomsByFloorId(floorId);
+        FloorModel floorModel = new FloorModel();
+        floorModel.setId(floorId);
+        floorModel.setRawMatrixCsv(mapMatrix.getCsv());
+        floorModel.setRoomModels((new HashSet<RoomModel>()));
+        Iterator<RoomModel> roomModels = Iterators.transform(rooms.iterator(), getRoomModels());
+        while (roomModels.hasNext()) {
+            RoomModel next = roomModels.next();
+            floorModel.getRoomModels().add(next);
+        }
+
+        String floorjson = new GsonBuilder().setPrettyPrinting().create().toJson(floorModel, FloorModel.class);
+
+        try {
+            Files.write(floorjson.getBytes(), new File(WORLD_DIR + floorManager.getName(floorId) + ".json"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Function<Room, RoomModel> getRoomModels() {
+        return new Function<Room, RoomModel>() {
+            @Override
+            public RoomModel apply(Room room) {
+                RoomModelBuilder roomModelBuilder = new RoomModelBuilder();
+                roomModelBuilder.setRoomDescription(room.getRoomDescription());
+                roomModelBuilder.setRoomTitle(room.getRoomTitle());
+                roomModelBuilder.setRoomId(room.getRoomId());
+                roomModelBuilder.setRoomTags(room.getRoomTags());
+                return roomModelBuilder.build();
+            }
+        };
+    }
+
+}
diff --git a/src/main/java/com/comandante/creeper/server/command/DescriptionCommand.java b/src/main/java/com/comandante/creeper/server/command/admin/DescriptionCommand.java
similarity index 95%
rename from src/main/java/com/comandante/creeper/server/command/DescriptionCommand.java
rename to src/main/java/com/comandante/creeper/server/command/admin/DescriptionCommand.java
index 050f8e0d..eecf1bcc 100644
--- a/src/main/java/com/comandante/creeper/server/command/DescriptionCommand.java
+++ b/src/main/java/com/comandante/creeper/server/command/admin/DescriptionCommand.java
@@ -1,4 +1,4 @@
-package com.comandante.creeper.server.command;
+package com.comandante.creeper.server.command.admin;
 
 import com.comandante.creeper.CreeperEntry;
 import com.comandante.creeper.managers.GameManager;
@@ -6,6 +6,7 @@ import com.comandante.creeper.player.Player;
 import com.comandante.creeper.room.Room;
 import com.comandante.creeper.server.CreeperSession;
 import com.comandante.creeper.server.MultiLineInputManager;
+import com.comandante.creeper.server.command.Command;
 import com.google.common.base.Optional;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.MessageEvent;
diff --git a/src/main/java/com/comandante/creeper/server/command/admin/SaveWorldCommand.java b/src/main/java/com/comandante/creeper/server/command/admin/SaveWorldCommand.java
new file mode 100644
index 00000000..8f2820f2
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/server/command/admin/SaveWorldCommand.java
@@ -0,0 +1,34 @@
+package com.comandante.creeper.server.command.admin;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.room.WorldExporter;
+import com.comandante.creeper.server.command.Command;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SaveWorldCommand extends Command {
+
+    final static List<String> validTriggers = Arrays.asList("saveworld");
+    final static String description = "Saves the current world to disk.";
+    final static boolean isAdminOnly = true;
+
+
+    public SaveWorldCommand(GameManager gameManager) {
+        super(gameManager, validTriggers, description, isAdminOnly);
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        WorldExporter worldExporter = new WorldExporter(
+                        getGameManager().getRoomManager(),
+                        getGameManager().getMapsManager(),
+                        getGameManager().getFloorManager());
+
+        worldExporter.saveWorld();
+        getGameManager().getChannelUtils().write(getPlayerId(extractCreeperSession(e.getChannel())), "World saved.");
+        super.messageReceived(ctx, e);
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/server/command/admin/TagRoomCommand.java b/src/main/java/com/comandante/creeper/server/command/admin/TagRoomCommand.java
new file mode 100644
index 00000000..94824894
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/server/command/admin/TagRoomCommand.java
@@ -0,0 +1,49 @@
+package com.comandante.creeper.server.command.admin;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.player.Player;
+import com.comandante.creeper.room.Room;
+import com.comandante.creeper.server.command.Command;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+public class TagRoomCommand extends Command {
+
+    final static List<String> validTriggers = Arrays.asList("tr", "tagRoom");
+    final static String description = "Sets a tag on a room.";
+    final static boolean isAdminOnly = true;
+
+    public TagRoomCommand(GameManager gameManager) {
+        super(gameManager, validTriggers, description, isAdminOnly);
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        try {
+            GameManager gameManager = getGameManager();
+            Player player = gameManager.getPlayerManager().getPlayer(getPlayerId(extractCreeperSession(e.getChannel())));
+            Room playerCurrentRoom = gameManager.getRoomManager().getPlayerCurrentRoom(player).get();
+            List<String> originalMessageParts = getOriginalMessageParts(e);
+            originalMessageParts.remove(0);
+            if (originalMessageParts.get(0).equalsIgnoreCase("list")) {
+                StringBuilder sb = new StringBuilder();
+                Iterator<String> iterator = playerCurrentRoom.getRoomTags().iterator();
+                while (iterator.hasNext()) {
+                    String tag = iterator.next();
+                    sb.append(tag).append("\n");
+                }
+                gameManager.getChannelUtils().writeNoPromptNoAfterSpace(player.getPlayerId(), "tag\n---");
+                gameManager.getChannelUtils().write(player.getPlayerId(), sb.toString());
+                return;
+            }
+            playerCurrentRoom.addTag(originalMessageParts.get(0));
+            gameManager.getChannelUtils().write(player.getPlayerId(), String.format("tagged room with tag: \"%s\".", originalMessageParts.get(0)));
+        } finally {
+            super.messageReceived(ctx, e);
+        }
+    }
+}
diff --git a/src/main/java/com/comandante/creeper/server/command/TitleCommand.java b/src/main/java/com/comandante/creeper/server/command/admin/TitleCommand.java
similarity index 93%
rename from src/main/java/com/comandante/creeper/server/command/TitleCommand.java
rename to src/main/java/com/comandante/creeper/server/command/admin/TitleCommand.java
index d354f72f..5e935153 100644
--- a/src/main/java/com/comandante/creeper/server/command/TitleCommand.java
+++ b/src/main/java/com/comandante/creeper/server/command/admin/TitleCommand.java
@@ -1,9 +1,10 @@
-package com.comandante.creeper.server.command;
+package com.comandante.creeper.server.command.admin;
 
 import com.comandante.creeper.managers.GameManager;
 import com.comandante.creeper.player.Player;
 import com.comandante.creeper.room.Room;
 import com.comandante.creeper.server.CreeperSession;
+import com.comandante.creeper.server.command.Command;
 import com.google.common.base.Joiner;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.MessageEvent;
-- 
GitLab