From ad2d48baa1e50624a7ff8579d630ed2300198bd6 Mon Sep 17 00:00:00 2001
From: Chris Kearney <chris.kearney@urbanairship.com>
Date: Sun, 31 Aug 2014 21:19:01 -0700
Subject: [PATCH] random npc movement across areas

---
 .../creeper/entity/EntityManager.java         |  5 ++
 .../comandante/creeper/fight/FightRun.java    |  4 +-
 .../com/comandante/creeper/npc/Derper.java    |  5 +-
 .../comandante/creeper/npc/DruggedPimp.java   |  5 +-
 .../java/com/comandante/creeper/npc/Npc.java  | 44 +++++++++-
 .../com/comandante/creeper/npc/NpcMover.java  | 88 +++++++++++++++++++
 .../com/comandante/creeper/room/Area.java     |  6 ++
 .../com/comandante/creeper/room/Room.java     | 10 ++-
 .../comandante/creeper/room/RoomBuilders.java | 29 ++++--
 .../creeper/server/command/KillCommand.java   |  1 +
 .../creeper/spawner/NpcSpawner.java           |  3 +-
 11 files changed, 184 insertions(+), 16 deletions(-)
 create mode 100644 src/main/java/com/comandante/creeper/npc/NpcMover.java
 create mode 100644 src/main/java/com/comandante/creeper/room/Area.java

diff --git a/src/main/java/com/comandante/creeper/entity/EntityManager.java b/src/main/java/com/comandante/creeper/entity/EntityManager.java
index 2ef351a7..3ca44ca9 100644
--- a/src/main/java/com/comandante/creeper/entity/EntityManager.java
+++ b/src/main/java/com/comandante/creeper/entity/EntityManager.java
@@ -97,6 +97,11 @@ public class EntityManager {
                         Map.Entry<String, Player> next = players.next();
                         ticketRunnerService.submit(next.getValue());
                     }
+                    Iterator<Map.Entry<String, Npc>> entries = npcs.entrySet().iterator();
+                    while (entries.hasNext()) {
+                        Map.Entry<String, Npc> next = entries.next();
+                        ticketRunnerService.submit(next.getValue());
+                    }
                 } catch (InterruptedException ie) {
                     throw new RuntimeException("Problem with ticker.");
                 }
diff --git a/src/main/java/com/comandante/creeper/fight/FightRun.java b/src/main/java/com/comandante/creeper/fight/FightRun.java
index 8b59b273..e1d811ab 100644
--- a/src/main/java/com/comandante/creeper/fight/FightRun.java
+++ b/src/main/java/com/comandante/creeper/fight/FightRun.java
@@ -36,11 +36,13 @@ public class FightRun implements Callable<FightResults> {
 
         gameManager.getPlayerManager().savePlayerMetadata(playerMetadata);
 
-        FightResults fightResults = new FightResultsBuilder().setNpcWon(false).setPlayerWon(false).createFightResults();;
+        FightResults fightResults = new FightResultsBuilder().setNpcWon(false).setPlayerWon(false).createFightResults();
+
 
         if (playerStats.getCurrentHealth() <= 0) {
             gameManager.getChannelUtils().write(player.getPlayerId(), "You died.");
             gameManager.getChannelUtils().writeToRoom(player.getPlayerId(), player.getPlayerName() + " is now dead.");
+            npc.setIsInFight(false);
             fightResults = new FightResultsBuilder().setNpcWon(true).setPlayerWon(false).createFightResults();
         }
 
diff --git a/src/main/java/com/comandante/creeper/npc/Derper.java b/src/main/java/com/comandante/creeper/npc/Derper.java
index 56eb33a3..b9210f72 100644
--- a/src/main/java/com/comandante/creeper/npc/Derper.java
+++ b/src/main/java/com/comandante/creeper/npc/Derper.java
@@ -2,6 +2,9 @@ package com.comandante.creeper.npc;
 
 
 import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.room.Area;
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
 
 import java.util.Arrays;
 import java.util.List;
@@ -22,7 +25,7 @@ public class Derper extends Npc {
 
 
     public Derper(GameManager gameManager, Integer roomId) {
-        super(gameManager, roomId, NAME, colorName, 0, NpcStats.DERPER.createStats(), "derper's face is melted");
+        super(gameManager, roomId, NAME, colorName, 0, NpcStats.DERPER.createStats(), "derper's face is melted", Optional.of(Sets.newHashSet(Area.NEWBIE_ZONE)));
         this.random = new Random();
     }
 
diff --git a/src/main/java/com/comandante/creeper/npc/DruggedPimp.java b/src/main/java/com/comandante/creeper/npc/DruggedPimp.java
index 15aa39d1..cc53d4a8 100644
--- a/src/main/java/com/comandante/creeper/npc/DruggedPimp.java
+++ b/src/main/java/com/comandante/creeper/npc/DruggedPimp.java
@@ -1,6 +1,9 @@
 package com.comandante.creeper.npc;
 
 import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.room.Area;
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
 
 import java.util.Random;
 
@@ -17,7 +20,7 @@ public class DruggedPimp extends Npc {
             .append(RESET).toString();
 
     public DruggedPimp(GameManager gameManager, Integer roomId) {
-        super(gameManager, roomId, NAME, colorName, 0, NpcStats.DRUGGED_PIMP.createStats(), "a drugged pimp is dead and broke");
+        super(gameManager, roomId, NAME, colorName, 0, NpcStats.DRUGGED_PIMP.createStats(), "a drugged pimp is dead and broke", Optional.of(Sets.newHashSet(Area.NEWBIE_ZONE)));
         this.random = new Random();
     }
 
diff --git a/src/main/java/com/comandante/creeper/npc/Npc.java b/src/main/java/com/comandante/creeper/npc/Npc.java
index dc85ee9a..992b889b 100644
--- a/src/main/java/com/comandante/creeper/npc/Npc.java
+++ b/src/main/java/com/comandante/creeper/npc/Npc.java
@@ -1,9 +1,15 @@
 package com.comandante.creeper.npc;
 
 
-import com.comandante.creeper.managers.GameManager;
 import com.comandante.creeper.entity.CreeperEntity;
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.room.Area;
 import com.comandante.creeper.stat.Stats;
+import com.google.common.base.Optional;
+
+import java.util.HashSet;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static com.comandante.creeper.server.Color.RED;
 import static com.comandante.creeper.server.Color.RESET;
@@ -17,24 +23,33 @@ public abstract class Npc extends CreeperEntity {
 
     private long lastPhraseTimestamp;
     private final GameManager gameManager;
-    private final Integer roomId;
+    private Integer roomId;
     private final String name;
     private final String colorName;
     private final Stats stats;
     private final String dieMessage;
+    private final Optional<HashSet<Area>> roamAreas;
+    private AtomicBoolean isInFight = new AtomicBoolean(false);
+    Random random = new Random();
 
     public abstract Npc create(GameManager gameManager, Integer roomId);
 
     @Override
     public void run() {
-        //System.out.println(getName() + " tick...");
+        if (randInt(0, 100) < 2) {
+            if (!isInFight.get() && roamAreas.isPresent()) {
+                System.out.println("roam!" + getEntityId());
+                NpcMover npcMover = new NpcMover();
+                npcMover.roam(getGameManager(), getEntityId());
+            }
+        }
     }
 
     public String getColorName() {
         return colorName;
     }
 
-    protected Npc(GameManager gameManager, Integer roomId, String name, String colorName, long lastPhraseTimestamp, Stats stats, String dieMessage) {
+    protected Npc(GameManager gameManager, Integer roomId, String name, String colorName, long lastPhraseTimestamp, Stats stats, String dieMessage, Optional<HashSet<Area>> roamAreas) {
         this.gameManager = gameManager;
         this.roomId = roomId;
         this.name = name;
@@ -42,6 +57,19 @@ public abstract class Npc extends CreeperEntity {
         this.lastPhraseTimestamp = lastPhraseTimestamp;
         this.stats = stats;
         this.dieMessage = dieMessage;
+        this.roamAreas = roamAreas;
+    }
+
+    public Optional<HashSet<Area>> getRoamAreas() {
+        return roamAreas;
+    }
+
+    public boolean getIsInFight() {
+        return this.isInFight.get();
+    }
+
+    public void setIsInFight(boolean isInFight) {
+        this.isInFight.set(isInFight);
     }
 
     public Stats getStats() {
@@ -68,6 +96,10 @@ public abstract class Npc extends CreeperEntity {
         return dieMessage;
     }
 
+    public void setRoomId(Integer roomId) {
+        this.roomId = roomId;
+    }
+
     public void npcSay(Integer roomId, String message) {
         StringBuilder sb = new StringBuilder();
         sb.append(RED);
@@ -75,4 +107,8 @@ public abstract class Npc extends CreeperEntity {
         sb.append(RESET);
     }
 
+    private int randInt(int min, int max) {
+        return random.nextInt((max - min) + 1) + min;
+    }
+
 }
diff --git a/src/main/java/com/comandante/creeper/npc/NpcMover.java b/src/main/java/com/comandante/creeper/npc/NpcMover.java
new file mode 100644
index 00000000..d3278e98
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/npc/NpcMover.java
@@ -0,0 +1,88 @@
+package com.comandante.creeper.npc;
+
+import com.comandante.creeper.managers.GameManager;
+import com.comandante.creeper.room.Area;
+import com.comandante.creeper.room.Room;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+public class NpcMover {
+
+    static Random random = new Random();
+
+    public void roam(final GameManager gameManager, String npcId) {
+        final Npc npcEntity = gameManager.getEntityManager().getNpcEntity(npcId);
+        Integer roomId = npcEntity.getRoomId();
+        Room npcCurrentRoom = gameManager.getRoomManager().getRoom(roomId);
+        Set<Integer> possibleExits = getPossibleExits(npcCurrentRoom);
+        Predicate<Integer> isRoamable = new Predicate<Integer>() {
+            @Override
+            public boolean apply(Integer roomId) {
+                Room room = gameManager.getRoomManager().getRoom(roomId);
+                for (Area roomArea : room.getAreas()) {
+                    if (npcEntity.getRoamAreas().get().contains(roomArea)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+        List<Integer> canRoam = Lists.newArrayList(Iterables.filter(possibleExits, isRoamable));
+        int i = random.nextInt(canRoam.size());
+        Integer destinationRoomId = canRoam.get(i);
+        String exitMessage = getExitMessage(npcCurrentRoom, destinationRoomId);
+        npcCurrentRoom.getNpcIds().remove(npcId);
+        gameManager.roomSay(npcCurrentRoom.getRoomId(), npcEntity.getName() + " " + exitMessage, "");
+        gameManager.getRoomManager().getRoom(destinationRoomId).getNpcIds().add(npcId);
+        npcEntity.setRoomId(destinationRoomId);
+        gameManager.roomSay(destinationRoomId, npcEntity.getName() + " has arrived.", "");
+    }
+
+    public String getExitMessage(Room room, Integer exitRoomId) {
+        if (room.getUpId().isPresent() && room.getUpId().get().equals(exitRoomId)) {
+            return "exited up.";
+        }
+        if (room.getDownId().isPresent() && room.getDownId().get().equals(exitRoomId)) {
+            return "exited down.";
+        }
+        if (room.getNorthId().isPresent() && room.getNorthId().get().equals(exitRoomId)) {
+            return "exited to the north.";
+        }
+        if (room.getSouthId().isPresent() && room.getSouthId().get().equals(exitRoomId)) {
+            return "exited to the south.";
+        }
+        if (room.getEastId().isPresent() && room.getEastId().get().equals(exitRoomId)) {
+            return "exited to the east.";
+        }
+        if (room.getWestId().isPresent() && room.getWestId().get().equals(exitRoomId)) {
+            return "exited to the west..";
+        }
+        return "";
+    }
+
+    public Set<Integer> getPossibleExits(Room room) {
+        List<Optional<Integer>> opts = Lists.newArrayList();
+        opts.add(room.getDownId());
+        opts.add(room.getUpId());
+        opts.add(room.getNorthId());
+        opts.add(room.getSouthId());
+        opts.add(room.getEastId());
+        opts.add(room.getWestId());
+
+        Set<Integer> exits = Sets.newHashSet();
+        for (Optional<Integer> opt: opts) {
+            if (opt.isPresent()){
+                exits.add(opt.get());
+            }
+        }
+        return exits;
+    }
+
+}
diff --git a/src/main/java/com/comandante/creeper/room/Area.java b/src/main/java/com/comandante/creeper/room/Area.java
new file mode 100644
index 00000000..2434eab6
--- /dev/null
+++ b/src/main/java/com/comandante/creeper/room/Area.java
@@ -0,0 +1,6 @@
+package com.comandante.creeper.room;
+
+public enum Area {
+    DEFAULT,
+    NEWBIE_ZONE
+}
diff --git a/src/main/java/com/comandante/creeper/room/Room.java b/src/main/java/com/comandante/creeper/room/Room.java
index 6d2a50be..ae3b032f 100644
--- a/src/main/java/com/comandante/creeper/room/Room.java
+++ b/src/main/java/com/comandante/creeper/room/Room.java
@@ -28,7 +28,7 @@ public abstract class Room extends CreeperEntity {
     private final Set<String> itemIds = Sets.newConcurrentHashSet();
     private List<ItemSpawner> itemSpawners = Lists.newArrayList();
     private List<NpcSpawner> npcSpawners = Lists.newArrayList();
-
+    private Set<Area> areas = Sets.newHashSet(Area.DEFAULT);
 
     public Room(Integer roomId,
                 String roomTitle,
@@ -50,6 +50,14 @@ public abstract class Room extends CreeperEntity {
         this.roomDescription = roomDescription;
     }
 
+    public Set<Area> getAreas() {
+        return areas;
+    }
+
+    public void setAreas(Set<Area> areas) {
+        this.areas = areas;
+    }
+
     public String getRoomTitle() {
         return roomTitle;
     }
diff --git a/src/main/java/com/comandante/creeper/room/RoomBuilders.java b/src/main/java/com/comandante/creeper/room/RoomBuilders.java
index 0a7456e7..e7490324 100644
--- a/src/main/java/com/comandante/creeper/room/RoomBuilders.java
+++ b/src/main/java/com/comandante/creeper/room/RoomBuilders.java
@@ -9,6 +9,7 @@ import com.comandante.creeper.spawner.ItemSpawner;
 import com.comandante.creeper.spawner.NpcSpawner;
 import com.comandante.creeper.spawner.SpawnRule;
 import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
 
 /**
  * Created by Brian Kearney on 8/24/2014.
@@ -33,10 +34,12 @@ public class RoomBuilders {
         basicRoom.addItemSpawner(new ItemSpawner(ItemType.KEY, new SpawnRule(30, 1, 10), gameManager));
         basicRoom.addNpcSpawner(new NpcSpawner(new Derper(gameManager, basicRoom.getRoomId()), gameManager, new SpawnRule(10, 5)));
         basicRoom.addNpcSpawner(new NpcSpawner(new DruggedPimp(gameManager, basicRoom.getRoomId()), gameManager, new SpawnRule(10, 5)));
+        basicRoom.setAreas(Sets.newHashSet(Area.NEWBIE_ZONE));
 
 
         entityManager.addEntity(basicRoom);
-        entityManager.addEntity(new BasicRoom(
+
+        BasicRoom room1 = new BasicRoom(
                 2,
                 "Quarter Deck",
                 Optional.of(3),
@@ -45,9 +48,10 @@ public class RoomBuilders {
                 Optional.of(5),
                 Optional.of(6),
                 Optional.<Integer>absent(),
-                "You are standing on the quarter deck of the Training Encampment. Federation flags line the walls of this large room. A statue of the Grand Marshal of the Federation sit in the back. A Private on watch is behind a desk in the center of the room. To the west you hear the sounds of gun fire. To the east a sentry stands by a door waiting to scan the credentials of anyone looking for access to the armory. A staircase leads up stairs. You get the feeling only high ranking officers are allowed on the second floor. To the north is a door leading to the training fields.\r\n"));
-
-        entityManager.addEntity(new BasicRoom(
+                "You are standing on the quarter deck of the Training Encampment. Federation flags line the walls of this large room. A statue of the Grand Marshal of the Federation sit in the back. A Private on watch is behind a desk in the center of the room. To the west you hear the sounds of gun fire. To the east a sentry stands by a door waiting to scan the credentials of anyone looking for access to the armory. A staircase leads up stairs. You get the feeling only high ranking officers are allowed on the second floor. To the north is a door leading to the training fields.\r\n");
+        room1.setAreas(Sets.newHashSet(Area.NEWBIE_ZONE));
+        entityManager.addEntity(room1);
+        BasicRoom room2 = new BasicRoom(
                 3,
                 "Training Field",
                 Optional.of(8),
@@ -56,9 +60,14 @@ public class RoomBuilders {
                 Optional.<Integer>absent(),
                 Optional.<Integer>absent(),
                 Optional.<Integer>absent(),
-                "You are standing on the center of a massive training field. You see a large field with a track surrounding it. A main pathway connects from back gate to the north to the main Federation building. Soldiers of all ranks are going about their business here.\r\n"));
+                "You are standing on the center of a massive training field. You see a large field with a track" +
+                        " surrounding it. A main pathway connects from back gate to the north to the main Federation" +
+                        " building. Soldiers of all ranks are going about their business here.\r\n");
+        room2.setAreas(Sets.newHashSet(Area.NEWBIE_ZONE));
 
-        entityManager.addEntity(new BasicRoom(
+        entityManager.addEntity(room2);
+
+        BasicRoom room3 = new BasicRoom(
                 4,
                 "Armory",
                 Optional.<Integer>absent(),
@@ -67,7 +76,13 @@ public class RoomBuilders {
                 Optional.of(2),
                 Optional.<Integer>absent(),
                 Optional.<Integer>absent(),
-                "You are standing in the Federation Training Encampment armory. A counter extends from wall to wall separating you from the stock. A Lieutenant is standing behind the counter filling out paper work. You can see shelves extending to the back of the room fully stocked with Federation issued weapons. The door closes and locks behind you.\r\n"));
+                "You are standing in the Federation Training Encampment armory. A counter extends from wall to wall" +
+                        " separating you from the stock. A Lieutenant is standing behind the counter filling out paper" +
+                        " work. You can see shelves extending to the back of the room fully stocked with Federation" +
+                        " issued weapons. The door closes and locks behind you.\r\n");
+        room3.setAreas(Sets.newHashSet(Area.NEWBIE_ZONE));
+
+        entityManager.addEntity(room3);
 
         entityManager.addEntity(new BasicRoom(
                 5,
diff --git a/src/main/java/com/comandante/creeper/server/command/KillCommand.java b/src/main/java/com/comandante/creeper/server/command/KillCommand.java
index f0f297c4..0424fb34 100644
--- a/src/main/java/com/comandante/creeper/server/command/KillCommand.java
+++ b/src/main/java/com/comandante/creeper/server/command/KillCommand.java
@@ -48,6 +48,7 @@ public class KillCommand extends Command {
             for (String npcId : npcIds) {
                 Npc npcEntity = getGameManager().getEntityManager().getNpcEntity(npcId);
                 if (npcEntity.getName().equals(target)) {
+                    npcEntity.setIsInFight(true);
                     FightRun fightRun = new FightRun(player, npcEntity, getGameManager());
                     Future<FightResults> fight = getGameManager().getFightManager().fight(fightRun);
                     creeperSession.setActiveFight(Optional.of(fight));
diff --git a/src/main/java/com/comandante/creeper/spawner/NpcSpawner.java b/src/main/java/com/comandante/creeper/spawner/NpcSpawner.java
index 651c2ae8..73dcc2dc 100644
--- a/src/main/java/com/comandante/creeper/spawner/NpcSpawner.java
+++ b/src/main/java/com/comandante/creeper/spawner/NpcSpawner.java
@@ -13,7 +13,7 @@ public class NpcSpawner extends CreeperEntity {
     private final Npc npc;
     private final GameManager gameManager;
     private final SpawnRule spawnRule;
-    private int noTicks = 0;
+    private int noTicks;
     private final Random random = new Random();
     private Integer roomId;
 
@@ -21,6 +21,7 @@ public class NpcSpawner extends CreeperEntity {
         this.npc = npc;
         this.gameManager = gameManager;
         this.spawnRule = spawnRule;
+        this.noTicks = spawnRule.getSpawnIntervalTicks();
     }
 
     public void incTicks() {
-- 
GitLab