reorganize engine bits into engine package, same with commands, reimplement player state as FSM
This commit is contained in:
		| @@ -1,5 +1,7 @@ | ||||
| package gg.malloc.defense; | ||||
|  | ||||
| import gg.malloc.defense.engine.GameRunner; | ||||
|  | ||||
| import org.bukkit.event.EventHandler; | ||||
| import org.bukkit.event.Listener; | ||||
| import org.bukkit.event.entity.EntityDeathEvent; | ||||
|   | ||||
| @@ -1,381 +0,0 @@ | ||||
| package gg.malloc.defense; | ||||
|  | ||||
| import gg.malloc.defense.model.Game; | ||||
| import gg.malloc.defense.model.Arena; | ||||
| import gg.malloc.defense.model.Spawnpoint; | ||||
| import gg.malloc.defense.model.Spawner; | ||||
| import gg.malloc.defense.model.Wave; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import org.bukkit.Bukkit; | ||||
| import org.bukkit.World; | ||||
| import org.bukkit.GameMode; | ||||
| import org.bukkit.attribute.Attribute; | ||||
| import org.bukkit.boss.BarColor; | ||||
| import org.bukkit.boss.BarStyle; | ||||
| import org.bukkit.boss.BossBar; | ||||
| import org.bukkit.event.player.PlayerTeleportEvent; | ||||
| import org.bukkit.event.entity.EntityDamageEvent; | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.entity.LivingEntity; | ||||
| import org.bukkit.entity.EntityType; | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.scheduler.BukkitTask; | ||||
|  | ||||
| public class GameRunner { | ||||
|   HashSet<Entity> m_livingMobs = new HashSet<>(); | ||||
|   int m_createdMobs = 0; | ||||
|   int m_killedMobs = 0; | ||||
|   Arena m_arena; | ||||
|   Game m_game; | ||||
|   State m_state; | ||||
|   HashSet<Player> m_players = new HashSet<>(); | ||||
|   HashSet<Player> m_livingPlayers = new HashSet<>(); | ||||
|   Plugin m_plugin; | ||||
|  | ||||
|   BossBar m_gameBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); | ||||
|   BossBar m_waveBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); | ||||
|   BukkitTask m_countdownTask; | ||||
|  | ||||
|   int m_currentWaveNum = 0; | ||||
|   int m_currentBatch = 0; | ||||
|   Wave m_currentWave = null; | ||||
|  | ||||
|   Logger m_log; | ||||
|  | ||||
|   enum State { | ||||
|     Idle, | ||||
|     Warmup, | ||||
|     Playing, | ||||
|     GameOver | ||||
|   } | ||||
|  | ||||
|   public GameRunner(Plugin plugin, Game game, Arena arena) { | ||||
|     m_plugin = plugin; | ||||
|     m_game = game; | ||||
|     m_arena = arena; | ||||
|     m_state = State.Idle; | ||||
|     m_gameBar.setVisible(true); | ||||
|     m_waveBar.setVisible(false); | ||||
|     m_log = m_plugin.getLogger(); | ||||
|   } | ||||
|  | ||||
|   int m_warmupCountdown = 0; | ||||
|  | ||||
|   public void handleEntityDamage(EntityDamageEvent evt) { | ||||
|     Entity entity = evt.getEntity(); | ||||
|     m_log.info("Damage " + entity); | ||||
|     m_log.info("Living Mobs " + m_livingMobs); | ||||
|     if (m_livingMobs.contains(entity)) { | ||||
|       m_game.onMobDamaged(entity); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void countdownTick() { | ||||
|     if (m_warmupCountdown == 0) { | ||||
|       requestTransition(State.Playing); | ||||
|     } else { | ||||
|       updateMobBars(); | ||||
|       broadcastMessage("Starting game in " + m_warmupCountdown); | ||||
|       m_warmupCountdown--; | ||||
|       m_countdownTask = m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> { | ||||
|         countdownTick(); | ||||
|       }, 20); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void clearMobs() { | ||||
|     for(Entity e : m_livingMobs) { | ||||
|       e.remove(); | ||||
|     } | ||||
|     m_livingMobs.clear(); | ||||
|     m_createdMobs = 0; | ||||
|     m_killedMobs = 0; | ||||
|   } | ||||
|  | ||||
|   private boolean enterIdle() { | ||||
|     broadcastMessage("Game state: Idle"); | ||||
|     m_currentWaveNum = 0; | ||||
|     m_livingPlayers.clear(); | ||||
|     for(Player p : m_players) { | ||||
|       m_livingPlayers.add(p); | ||||
|     } | ||||
|     if (m_countdownTask != null) { | ||||
|       m_countdownTask.cancel(); | ||||
|       m_countdownTask = null; | ||||
|     } | ||||
|     clearMobs(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterWarmup() { | ||||
|     m_log.info("Game state: Warmup"); | ||||
|     m_currentWaveNum += 1; | ||||
|     m_currentWave = m_game.getWave(m_currentWaveNum); | ||||
|     m_warmupCountdown = 10; | ||||
|     for(Player p : m_players) { | ||||
|       m_livingPlayers.add(p); | ||||
|     } | ||||
|     broadcastTitle("Warmup", "Prepare yourself for wave " + m_currentWaveNum); | ||||
|     clearMobs(); | ||||
|     countdownTick(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterPlaying() { | ||||
|     m_log.info("Game state: Playing"); | ||||
|     m_log.info("Starting wave " + m_currentWaveNum); | ||||
|     m_currentBatch = 1; | ||||
|     spawnNextBatch(); | ||||
|     broadcastTitle("Wave " + m_currentWaveNum); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterGameOver() { | ||||
|     broadcastTitle("Game Over!"); | ||||
|     if (m_countdownTask != null) { | ||||
|       m_countdownTask.cancel(); | ||||
|       m_countdownTask = null; | ||||
|     } | ||||
|     clearMobs(); | ||||
|     for(Player p : m_players) { | ||||
|       removePlayer(p); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private void updateMobBars() { | ||||
|     m_gameBar.setVisible(true); | ||||
|     switch(m_state) { | ||||
|       case Idle: | ||||
|         m_gameBar.setProgress(1.0); | ||||
|         m_gameBar.setTitle("Waiting for playres..."); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         m_waveBar.setVisible(false); | ||||
|         break; | ||||
|       case Warmup: | ||||
|         m_gameBar.setProgress((double)m_currentWaveNum / (double)m_game.getWaveCount()); | ||||
|         m_gameBar.setTitle("Wave " + m_currentWaveNum + " / " + m_game.getWaveCount()); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         m_waveBar.setVisible(true); | ||||
|         m_waveBar.setColor(BarColor.YELLOW); | ||||
|         m_waveBar.setTitle("Warmup"); | ||||
|         m_waveBar.setProgress((double)m_warmupCountdown / (double)10); | ||||
|         break; | ||||
|       case Playing: | ||||
|         m_gameBar.setProgress((double)m_currentWaveNum / (double)m_game.getWaveCount()); | ||||
|         m_gameBar.setTitle("Wave " + m_currentWaveNum + " / " + m_game.getWaveCount()); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         if (m_createdMobs > 0) { | ||||
|           m_waveBar.setVisible(true); | ||||
|           m_waveBar.setColor(BarColor.GREEN); | ||||
|           m_waveBar.setTitle("Mobs remaining: " + (m_createdMobs - m_killedMobs)); | ||||
|           m_waveBar.setProgress((double)m_killedMobs / (double)m_createdMobs); | ||||
|         } else { | ||||
|           m_waveBar.setVisible(false); | ||||
|         } | ||||
|         break; | ||||
|       case GameOver: | ||||
|         m_gameBar.setColor(BarColor.RED); | ||||
|         m_gameBar.setProgress(1.0); | ||||
|         m_gameBar.setTitle("Game Over!"); | ||||
|         m_waveBar.setVisible(false); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void spawnNextBatch() { | ||||
|     broadcastMessage("Spawning batch " + m_currentBatch); | ||||
|     Spawner spawner = new GameSpawner(m_arena.spawnpoints()); | ||||
|     m_currentWave.spawnBatch(spawner, m_currentBatch); | ||||
|     updateMobBars(); | ||||
|   } | ||||
|  | ||||
|   public void handlePlayerDeath(Player player) { | ||||
|     if (m_livingPlayers.contains(player)) { | ||||
|       m_log.info("Player has died in game" + player); | ||||
|       m_livingPlayers.remove(player); | ||||
|       m_arena.getWorld().strikeLightningEffect(player.getLocation()); | ||||
|       player.setGameMode(GameMode.SPECTATOR); | ||||
|       if (m_livingPlayers.size() == 0) { | ||||
|         broadcastMessage("Everyone is dead :("); | ||||
|         requestTransition(State.GameOver); | ||||
|       } else { | ||||
|         m_log.info("Remaining players " + m_livingPlayers.size()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public void handleEntityDeath(Entity entity) { | ||||
|     if (m_livingMobs.contains(entity)) { | ||||
|       broadcastMessage("Killed game entity " + entity); | ||||
|       m_livingMobs.remove(entity); | ||||
|       m_killedMobs += 1; | ||||
|       updateMobBars(); | ||||
|       if (m_livingMobs.size() <= 3) { | ||||
|         m_log.info("Starting next batch!"); | ||||
|         if (m_currentBatch >= m_currentWave.batchCount()) { | ||||
|           if (m_currentWaveNum >= m_game.getWaveCount()) { | ||||
|             requestTransition(State.GameOver); | ||||
|           } else if (m_livingMobs.size() == 0) { | ||||
|             requestTransition(State.Warmup); | ||||
|           } | ||||
|         } else { | ||||
|           m_currentBatch += 1; | ||||
|           spawnNextBatch(); | ||||
|         } | ||||
|       } else { | ||||
|         m_log.fine("Living mobs remaining: " + m_livingMobs.size()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private boolean syncPlayer(Player player) { | ||||
|     m_log.fine("Synchronizing player " + player); | ||||
|     World playerWorld = player.getLocation().getWorld(); | ||||
|     World gameWorld = m_arena.getWorld(); | ||||
|     m_gameBar.addPlayer(player); | ||||
|     m_waveBar.addPlayer(player); | ||||
|     if (m_livingPlayers.contains(player)) { | ||||
|       m_log.fine("Player is alive, turning into adventure mode " + player); | ||||
|       if (player.getGameMode() != Bukkit.getDefaultGameMode()) { | ||||
|         player.setGameMode(Bukkit.getDefaultGameMode()); | ||||
|         player.teleport(gameWorld.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); | ||||
|       } | ||||
|       player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); | ||||
|       player.setFoodLevel(10); | ||||
|     } else { | ||||
|       m_log.fine("Player is dead, turning into spectator " + player); | ||||
|       player.setGameMode(GameMode.SPECTATOR); | ||||
|     } | ||||
|     if (playerWorld != gameWorld) { | ||||
|       m_log.info("Teleporting player " + player); | ||||
|       return player.teleport(gameWorld.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); | ||||
|     } else { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private boolean attemptTransition(State state) { | ||||
|     if (m_state == state) { | ||||
|       return false; | ||||
|     } | ||||
|     switch(state) { | ||||
|       case Idle: | ||||
|         return enterIdle(); | ||||
|       case Warmup: | ||||
|         return enterWarmup(); | ||||
|       case Playing: | ||||
|         return enterPlaying(); | ||||
|       case GameOver: | ||||
|         return enterGameOver(); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|    | ||||
|   private boolean validateTransition(State from, State to) { | ||||
|     switch(from) { | ||||
|       case Idle: | ||||
|         return to == State.Warmup; | ||||
|       case Warmup: | ||||
|         return to == State.Playing || to == State.Idle || to == State.GameOver; | ||||
|       case Playing: | ||||
|         return to == State.Warmup || to == State.GameOver || to == State.Idle; | ||||
|       case GameOver: | ||||
|         return to == State.Idle; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public boolean requestTransition(State state) { | ||||
|     if (!validateTransition(m_state, state)) { | ||||
|       m_log.warning("Attemped illegal transition: " + m_state + " -> " + state); | ||||
|       return false; | ||||
|     } | ||||
|     if (attemptTransition(state)) { | ||||
|       updateMobBars(); | ||||
|       m_log.info("Game transition: " + m_state + " -> " + state); | ||||
|       m_state = state; | ||||
|       for(Player p : m_players) { | ||||
|         syncPlayer(p); | ||||
|       } | ||||
|       return true; | ||||
|     } | ||||
|     m_log.severe("Failed to complete transition: " + m_state + " -> " + state); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public boolean addPlayer(Player p) { | ||||
|     if (m_state == State.Idle || m_state == State.Warmup) { | ||||
|       m_log.info("Adding player " + p); | ||||
|       m_players.add(p); | ||||
|       broadcastMessage(p.getName() + " has joined the game"); | ||||
|       syncPlayer(p); | ||||
|       if (m_state == State.Idle) { | ||||
|         requestTransition(State.Warmup); | ||||
|       } | ||||
|       return true; | ||||
|     } else { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public void removePlayer(Player p) { | ||||
|     m_log.info("Removing player " + p);  | ||||
|     m_gameBar.removePlayer(p); | ||||
|     m_waveBar.removePlayer(p); | ||||
|     m_players.remove(p); | ||||
|     m_livingPlayers.remove(p); | ||||
|     p.setGameMode(Bukkit.getDefaultGameMode()); | ||||
|     if (m_players.size() == 0) { | ||||
|       requestTransition(State.Idle); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void broadcastTitle(String title) { | ||||
|     broadcastTitle(title, ""); | ||||
|   } | ||||
|  | ||||
|   void broadcastTitle(String title, String subtitle) { | ||||
|     for(Player p : m_players) { | ||||
|       p.sendTitle(title, subtitle, 10, 70, 20); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void broadcastMessage(String string) { | ||||
|     World world = m_arena.getWorld(); | ||||
|     for(Player p : world.getPlayers()) { | ||||
|       p.sendMessage(string); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void registerSpawnedMob(Entity entity) { | ||||
|     m_log.fine("Registered new mob " + entity);  | ||||
|     m_livingMobs.add(entity); | ||||
|     m_createdMobs += 1; | ||||
|   } | ||||
|  | ||||
|   private class GameSpawner implements Spawner { | ||||
|     Spawnpoint[] m_spawnpoints; | ||||
|     int m_spawnIdx = 0; | ||||
|   | ||||
|     public GameSpawner(Spawnpoint[] spawnpoints) { | ||||
|       m_spawnpoints = spawnpoints; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Entity spawnMob(EntityType type) { | ||||
|       m_log.fine("Spawning " + type + " at " + m_spawnpoints[m_spawnIdx]); | ||||
|       Entity newMob = m_arena.getWorld().spawnEntity(m_spawnpoints[m_spawnIdx].getLocation(), type); | ||||
|       LivingEntity livingMob = (LivingEntity)newMob; | ||||
|       livingMob.setRemoveWhenFarAway(false); | ||||
|       registerSpawnedMob(newMob); | ||||
|       m_spawnIdx += 1; | ||||
|       m_spawnIdx %= m_spawnpoints.length; | ||||
|       return newMob; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -20,6 +20,11 @@ import gg.malloc.defense.model.Game; | ||||
| import gg.malloc.defense.games.LinearGame; | ||||
| import gg.malloc.defense.games.ScaledWaves; | ||||
|  | ||||
| import gg.malloc.defense.engine.GameRunner; | ||||
|  | ||||
| import gg.malloc.defense.commands.AddPlayerCommand; | ||||
| import gg.malloc.defense.commands.SetStageCommand; | ||||
|  | ||||
| public class Plugin extends JavaPlugin { | ||||
|   ArrayList<Arena> m_arenas = new ArrayList<>(); | ||||
|   ArrayList<Game> m_games = new ArrayList<>(); | ||||
| @@ -58,7 +63,7 @@ public class Plugin extends JavaPlugin { | ||||
|     getLogger().setLevel(Level.FINEST); | ||||
|     setupDemoGame(); | ||||
|     getCommand("setstage").setExecutor(new SetStageCommand(this)); | ||||
|     getCommand("initgame").setExecutor(new InitGameCommand(this)); | ||||
|     getCommand("addplayer").setExecutor(new AddPlayerCommand(this)); | ||||
|     getCommand("debuginfo").setExecutor(new DebuginfoCommand(this)); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,18 @@ | ||||
| package gg.malloc.defense; | ||||
| package gg.malloc.defense.commands; | ||||
| 
 | ||||
| import org.bukkit.command.Command; | ||||
| import org.bukkit.command.CommandExecutor; | ||||
| import org.bukkit.command.CommandSender; | ||||
| import org.bukkit.entity.Player; | ||||
| 
 | ||||
| public class InitGameCommand implements CommandExecutor { | ||||
| import gg.malloc.defense.engine.GameRunner; | ||||
| 
 | ||||
| import gg.malloc.defense.Plugin; | ||||
| 
 | ||||
| public class AddPlayerCommand implements CommandExecutor { | ||||
|   Plugin m_plugin; | ||||
| 
 | ||||
|   public InitGameCommand(Plugin plugin) { | ||||
|   public AddPlayerCommand(Plugin plugin) { | ||||
|     m_plugin = plugin; | ||||
|   } | ||||
| 
 | ||||
| @@ -16,7 +20,7 @@ public class InitGameCommand implements CommandExecutor { | ||||
|   public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { | ||||
|     String worldName = args[0]; | ||||
|     GameRunner runner = m_plugin.getRunnerForWorld(m_plugin.getServer().getWorld(worldName)); | ||||
|     Player player= m_plugin.getServer().getPlayer(args[1]); | ||||
|     Player player = m_plugin.getServer().getPlayer(args[1]); | ||||
|     runner.addPlayer(player); | ||||
|     return true; | ||||
|   } | ||||
| @@ -1,10 +1,14 @@ | ||||
| package gg.malloc.defense; | ||||
| package gg.malloc.defense.commands; | ||||
| 
 | ||||
| import org.bukkit.command.Command; | ||||
| import org.bukkit.command.CommandExecutor; | ||||
| import org.bukkit.command.CommandSender; | ||||
| import org.bukkit.World; | ||||
| 
 | ||||
| import gg.malloc.defense.engine.GameRunner; | ||||
| 
 | ||||
| import gg.malloc.defense.Plugin; | ||||
| 
 | ||||
| public class SetStageCommand implements CommandExecutor { | ||||
|   Plugin m_plugin; | ||||
| 
 | ||||
| @@ -19,13 +23,13 @@ public class SetStageCommand implements CommandExecutor { | ||||
|     String stateName = args[0].toLowerCase(); | ||||
|     boolean ret = false; | ||||
|     if (stateName.equals("idle")) { | ||||
|       ret = runner.requestTransition(GameRunner.State.Idle); | ||||
|       ret = runner.requestTransition(GameRunner.Stage.Idle); | ||||
|     } else if (stateName.equals("warmup")) { | ||||
|       ret = runner.requestTransition(GameRunner.State.Warmup); | ||||
|       ret = runner.requestTransition(GameRunner.Stage.Warmup); | ||||
|     } else if (stateName.equals("playing")) { | ||||
|       ret = runner.requestTransition(GameRunner.State.Playing); | ||||
|       ret = runner.requestTransition(GameRunner.Stage.Playing); | ||||
|     } else if (stateName.equals("gameover")) { | ||||
|       ret = runner.requestTransition(GameRunner.State.GameOver); | ||||
|       ret = runner.requestTransition(GameRunner.Stage.GameOver); | ||||
|     } else { | ||||
|       sender.sendMessage("Unknown state " + stateName); | ||||
|       return false; | ||||
							
								
								
									
										350
									
								
								src/main/java/gg/malloc/defense/engine/GameRunner.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								src/main/java/gg/malloc/defense/engine/GameRunner.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,350 @@ | ||||
| package gg.malloc.defense.engine; | ||||
|  | ||||
| import gg.malloc.defense.model.Game; | ||||
| import gg.malloc.defense.model.Arena; | ||||
| import gg.malloc.defense.model.Spawner; | ||||
| import gg.malloc.defense.model.Wave; | ||||
|  | ||||
| import gg.malloc.defense.Plugin; | ||||
|  | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import org.bukkit.Bukkit; | ||||
| import org.bukkit.World; | ||||
| import org.bukkit.boss.BarColor; | ||||
| import org.bukkit.boss.BarStyle; | ||||
| import org.bukkit.boss.BossBar; | ||||
| import org.bukkit.event.player.PlayerTeleportEvent; | ||||
| import org.bukkit.event.entity.EntityDamageEvent; | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.entity.LivingEntity; | ||||
| import org.bukkit.entity.EntityType; | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.scheduler.BukkitTask; | ||||
|  | ||||
| public class GameRunner { | ||||
|   Arena m_arena; | ||||
|   Game m_game; | ||||
|   Stage m_stage; | ||||
|   Plugin m_plugin; | ||||
|  | ||||
|   BossBar m_gameBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); | ||||
|   BossBar m_waveBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); | ||||
|   BukkitTask m_countdownTask; | ||||
|  | ||||
|   MobManager m_mobs; | ||||
|   WaveManager m_waves; | ||||
|   PlayerManager m_players; | ||||
|  | ||||
|   Logger m_log; | ||||
|  | ||||
|   public enum Stage { | ||||
|     Idle, | ||||
|     Warmup, | ||||
|     Playing, | ||||
|     GameOver | ||||
|   } | ||||
|  | ||||
|   class WaveManager { | ||||
|     int m_currentWaveNum = 0; | ||||
|     int m_currentBatch = 0; | ||||
|     Wave m_currentWave = null; | ||||
|     Game m_game; | ||||
|  | ||||
|     WaveManager(Game game) { | ||||
|       m_game = game; | ||||
|     } | ||||
|  | ||||
|     void reset() { | ||||
|       m_currentWaveNum = 0; | ||||
|       m_currentBatch = 0; | ||||
|       m_currentWave = null; | ||||
|     } | ||||
|  | ||||
|     Wave currentWave() { | ||||
|       return m_currentWave; | ||||
|     } | ||||
|  | ||||
|     int currentWaveNum() { | ||||
|       return m_currentWaveNum; | ||||
|     } | ||||
|  | ||||
|     int currentBatchNum() { | ||||
|       return m_currentBatch; | ||||
|     } | ||||
|  | ||||
|     double progress() { | ||||
|       return (double)m_currentWaveNum / (double)m_game.getWaveCount(); | ||||
|     } | ||||
|  | ||||
|     boolean isLastWave() { | ||||
|       return m_currentWaveNum >= m_game.getWaveCount(); | ||||
|     } | ||||
|  | ||||
|     boolean isLastBatch() { | ||||
|       return m_currentBatch >= m_currentWave.batchCount(); | ||||
|     } | ||||
|  | ||||
|     void nextBatch() { | ||||
|       m_currentBatch += 1; | ||||
|     } | ||||
|  | ||||
|     void next() { | ||||
|       m_currentWaveNum += 1; | ||||
|       m_currentBatch = 0; | ||||
|       m_currentWave = m_game.getWave(m_currentWaveNum); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   public GameRunner(Plugin plugin, Game game, Arena arena) { | ||||
|     m_plugin = plugin; | ||||
|     m_game = game; | ||||
|     m_arena = arena; | ||||
|     m_stage = Stage.Idle; | ||||
|     m_gameBar.setVisible(true); | ||||
|     m_waveBar.setVisible(false); | ||||
|     m_mobs = new MobManager(m_game); | ||||
|     m_waves = new WaveManager(m_game); | ||||
|     m_players = new PlayerManager(); | ||||
|     m_log = m_plugin.getLogger(); | ||||
|   } | ||||
|  | ||||
|   int m_warmupCountdown = 0; | ||||
|  | ||||
|   public void handleEntityDamage(EntityDamageEvent evt) { | ||||
|     m_mobs.handleEntityDamage(evt); | ||||
|   } | ||||
|  | ||||
|   private void countdownTick() { | ||||
|     if (m_warmupCountdown == 0) { | ||||
|       requestTransition(Stage.Playing); | ||||
|     } else { | ||||
|       updateMobBars(); | ||||
|       broadcastMessage("Starting game in " + m_warmupCountdown); | ||||
|       m_warmupCountdown--; | ||||
|       m_countdownTask = m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> { | ||||
|         countdownTick(); | ||||
|       }, 20); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private boolean enterIdle() { | ||||
|     m_waves.reset(); | ||||
|     m_mobs.clear(); | ||||
|     m_players.requestTransitionForAll(PlayerManager.State.Idle); | ||||
|     if (m_countdownTask != null) { | ||||
|       m_countdownTask.cancel(); | ||||
|       m_countdownTask = null; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterWarmup() { | ||||
|     m_waves.next(); | ||||
|     m_warmupCountdown = 10; | ||||
|     for(Player p : m_players.getPlayers()) { | ||||
|         if (m_players.requestTransition(p, PlayerManager.State.Playing)) { | ||||
|           p.teleport(m_arena.getWorld().getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); | ||||
|         } | ||||
|     } | ||||
|     broadcastTitle("Warmup", "Prepare yourself for wave " + m_waves.currentWaveNum()); | ||||
|     m_mobs.clear(); | ||||
|     countdownTick(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterPlaying() { | ||||
|     m_log.info("Starting wave " + m_waves.currentWaveNum()); | ||||
|     spawnNextBatch(); | ||||
|     broadcastTitle("Wave " + m_waves.currentWaveNum()); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private boolean enterGameOver() { | ||||
|     broadcastTitle("Game Over!"); | ||||
|     if (m_countdownTask != null) { | ||||
|       m_countdownTask.cancel(); | ||||
|       m_countdownTask = null; | ||||
|     } | ||||
|     m_mobs.clear(); | ||||
|     for(Player p : m_players.getPlayers()) { | ||||
|       removePlayer(p); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   private void updateMobBars() { | ||||
|     m_gameBar.setVisible(true); | ||||
|     switch(m_stage) { | ||||
|       case Idle: | ||||
|         m_gameBar.setProgress(1.0); | ||||
|         m_gameBar.setTitle("Waiting for playres..."); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         m_waveBar.setVisible(false); | ||||
|         break; | ||||
|       case Warmup: | ||||
|         m_gameBar.setProgress(m_waves.progress()); | ||||
|         m_gameBar.setTitle("Wave " + m_waves.currentWaveNum() + " / " + m_game.getWaveCount()); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         m_waveBar.setVisible(true); | ||||
|         m_waveBar.setColor(BarColor.YELLOW); | ||||
|         m_waveBar.setTitle("Warmup"); | ||||
|         m_waveBar.setProgress((double)m_warmupCountdown / (double)10); | ||||
|         break; | ||||
|       case Playing: | ||||
|         m_gameBar.setProgress(m_waves.progress()); | ||||
|         m_gameBar.setTitle("Wave " + m_waves.currentWaveNum() + " / " + m_game.getWaveCount()); | ||||
|         m_gameBar.setColor(BarColor.PURPLE); | ||||
|         if (m_mobs.createdMobs() > 0) { | ||||
|           m_waveBar.setVisible(true); | ||||
|           m_waveBar.setColor(BarColor.GREEN); | ||||
|           m_waveBar.setTitle("Mobs remaining: " + m_mobs.remainingMobs()); | ||||
|           m_waveBar.setProgress(m_mobs.progress()); | ||||
|         } else { | ||||
|           m_waveBar.setVisible(false); | ||||
|         } | ||||
|         break; | ||||
|       case GameOver: | ||||
|         m_gameBar.setColor(BarColor.RED); | ||||
|         m_gameBar.setProgress(1.0); | ||||
|         m_gameBar.setTitle("Game Over!"); | ||||
|         m_waveBar.setVisible(false); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void spawnNextBatch() { | ||||
|     broadcastMessage("Spawning batch " + m_waves.currentBatchNum()); | ||||
|     Spawner spawner = new GameSpawner(m_arena, m_mobs); | ||||
|     m_waves.currentWave().spawnBatch(spawner, m_waves.currentBatchNum()); | ||||
|     updateMobBars(); | ||||
|   } | ||||
|  | ||||
|   public void handlePlayerDeath(Player player) { | ||||
|     if (m_players.requestTransition(player, PlayerManager.State.Dead)) { | ||||
|       m_arena.getWorld().strikeLightningEffect(player.getLocation()); | ||||
|       if (!m_players.isAnyoneAlive()) { | ||||
|         broadcastMessage("Everyone is dead :("); | ||||
|         requestTransition(Stage.GameOver); | ||||
|       } else { | ||||
|         m_log.info("Remaining players " + m_players.remainingPlayers()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public void handleEntityDeath(Entity entity) { | ||||
|     if (m_mobs.killMob(entity)) { | ||||
|       broadcastMessage("Killed game entity " + entity); | ||||
|       updateMobBars(); | ||||
|       if (m_mobs.remainingMobs() <= 3) { | ||||
|         m_log.info("Starting next batch!"); | ||||
|         if (m_waves.isLastBatch()) { | ||||
|           if (m_waves.isLastWave()) { | ||||
|             requestTransition(Stage.GameOver); | ||||
|           } else if (m_mobs.empty()) { | ||||
|             requestTransition(Stage.Warmup); | ||||
|           } | ||||
|         } else { | ||||
|           m_waves.nextBatch(); | ||||
|           spawnNextBatch(); | ||||
|         } | ||||
|       } else { | ||||
|         m_log.fine("Living mobs remaining: " + m_mobs.remainingMobs()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private boolean attemptTransition(Stage stage) { | ||||
|     if (m_stage == stage) { | ||||
|       return false; | ||||
|     } | ||||
|     m_log.info("Game state: " + stage); | ||||
|     switch(stage) { | ||||
|       case Idle: | ||||
|         return enterIdle(); | ||||
|       case Warmup: | ||||
|         return enterWarmup(); | ||||
|       case Playing: | ||||
|         return enterPlaying(); | ||||
|       case GameOver: | ||||
|         return enterGameOver(); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|    | ||||
|   private boolean validateTransition(Stage from, Stage to) { | ||||
|     switch(from) { | ||||
|       case Idle: | ||||
|         return to == Stage.Warmup; | ||||
|       case Warmup: | ||||
|         return to == Stage.Playing || to == Stage.Idle || to == Stage.GameOver; | ||||
|       case Playing: | ||||
|         return to == Stage.Warmup || to == Stage.GameOver || to == Stage.Idle; | ||||
|       case GameOver: | ||||
|         return to == Stage.Idle; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public boolean requestTransition(Stage stage) { | ||||
|     if (!validateTransition(m_stage, stage)) { | ||||
|       m_log.warning("Attemped illegal transition: " + m_stage + " -> " + stage); | ||||
|       return false; | ||||
|     } | ||||
|     if (attemptTransition(stage)) { | ||||
|       m_log.info("Game transition: " + m_stage + " -> " + stage); | ||||
|       m_stage = stage; | ||||
|       updateMobBars(); | ||||
|       return true; | ||||
|     } | ||||
|     m_log.severe("Failed to complete transition: " + m_stage + " -> " + stage); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public void addPlayer(Player p) { | ||||
|     m_players.addPlayer(p); | ||||
|     m_gameBar.addPlayer(p); | ||||
|     m_waveBar.addPlayer(p); | ||||
|     if (m_stage == Stage.Idle || m_stage == Stage.Warmup) { | ||||
|       if (m_players.requestTransition(p, PlayerManager.State.Playing)) { | ||||
|         p.teleport(m_arena.getWorld().getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); | ||||
|       } | ||||
|       broadcastMessage(p.getName() + " has joined the game"); | ||||
|       if (m_stage == Stage.Idle) { | ||||
|         requestTransition(Stage.Warmup); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public void removePlayer(Player p) { | ||||
|     m_gameBar.removePlayer(p); | ||||
|     m_waveBar.removePlayer(p); | ||||
|     m_players.removePlayer(p); | ||||
|     if (m_players.isEmpty()) { | ||||
|       requestTransition(Stage.Idle); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void broadcastTitle(String title) { | ||||
|     broadcastTitle(title, ""); | ||||
|   } | ||||
|  | ||||
|   void broadcastTitle(String title, String subtitle) { | ||||
|     for(Player p : m_players.getPlayers()) { | ||||
|       p.sendTitle(title, subtitle, 10, 70, 20); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void broadcastMessage(String string) { | ||||
|     World world = m_arena.getWorld(); | ||||
|     for(Player p : world.getPlayers()) { | ||||
|       p.sendMessage(string); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void registerSpawnedMob(Entity entity) { | ||||
|     m_mobs.addEntity(entity); | ||||
|   } | ||||
|  | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/main/java/gg/malloc/defense/engine/GameSpawner.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/main/java/gg/malloc/defense/engine/GameSpawner.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package gg.malloc.defense.engine; | ||||
|  | ||||
| import gg.malloc.defense.model.Spawner; | ||||
| import gg.malloc.defense.model.Spawnpoint; | ||||
| import gg.malloc.defense.model.Arena; | ||||
|  | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.entity.LivingEntity; | ||||
| import org.bukkit.entity.EntityType; | ||||
|  | ||||
| public class GameSpawner implements Spawner { | ||||
|   Arena m_arena; | ||||
|   MobManager m_manager; | ||||
|   int m_spawnIdx = 0; | ||||
|  | ||||
|   public GameSpawner(Arena arena, MobManager manager) { | ||||
|     m_arena = arena; | ||||
|     m_manager = manager; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public Entity spawnMob(EntityType type) { | ||||
|     Spawnpoint[] spawnpoints = m_arena.spawnpoints(); | ||||
|     m_spawnIdx %= spawnpoints.length; | ||||
|     //m_log.fine("Spawning " + type + " at " + spawnpoints[m_spawnIdx]); | ||||
|     Entity newMob = m_arena.getWorld().spawnEntity(spawnpoints[m_spawnIdx].getLocation(), type); | ||||
|     LivingEntity livingMob = (LivingEntity)newMob; | ||||
|     livingMob.setRemoveWhenFarAway(false); | ||||
|     m_manager.addEntity(newMob); | ||||
|     m_spawnIdx += 1; | ||||
|     return newMob; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/main/java/gg/malloc/defense/engine/GameStage.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/main/java/gg/malloc/defense/engine/GameStage.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package gg.malloc.defense.engine; | ||||
|  | ||||
| public interface GameStage { | ||||
|   String name(); | ||||
|   default void onEnter() {} | ||||
|   default void onExit() {} | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/main/java/gg/malloc/defense/engine/MobManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/main/java/gg/malloc/defense/engine/MobManager.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| package gg.malloc.defense.engine; | ||||
|  | ||||
| import java.util.HashSet; | ||||
|  | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.event.entity.EntityDamageEvent; | ||||
|  | ||||
| import gg.malloc.defense.model.Game; | ||||
|  | ||||
| public class MobManager { | ||||
|   HashSet<Entity> m_livingMobs = new HashSet<>(); | ||||
|   int m_createdMobs = 0; | ||||
|   int m_killedMobs = 0; | ||||
|  | ||||
|   Game m_game; | ||||
|  | ||||
|   MobManager(Game game) { | ||||
|     m_game = game; | ||||
|   } | ||||
|  | ||||
|   public void handleEntityDamage(EntityDamageEvent evt) { | ||||
|     Entity entity = evt.getEntity(); | ||||
|     //m_log.info("Damage " + entity); | ||||
|     //m_log.info("Living Mobs " + m_livingMobs); | ||||
|     if (m_livingMobs.contains(entity)) { | ||||
|       m_game.onMobDamaged(entity); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public boolean killMob(Entity entity) { | ||||
|     if (m_livingMobs.contains(entity)) { | ||||
|       m_killedMobs += 1; | ||||
|       return m_livingMobs.remove(entity); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public void clear() { | ||||
|     for(Entity e : m_livingMobs) { | ||||
|       e.remove(); | ||||
|     } | ||||
|     m_livingMobs.clear(); | ||||
|     m_createdMobs = 0; | ||||
|     m_killedMobs = 0; | ||||
|   } | ||||
|  | ||||
|   public int createdMobs() { | ||||
|     return m_createdMobs; | ||||
|   } | ||||
|  | ||||
|   public int killedMobs() { | ||||
|     return m_killedMobs; | ||||
|   } | ||||
|  | ||||
|   public int remainingMobs() { | ||||
|     return m_createdMobs - m_killedMobs; | ||||
|   } | ||||
|  | ||||
|   public double progress() { | ||||
|     return (double)m_killedMobs / (double)m_createdMobs; | ||||
|   } | ||||
|  | ||||
|   public void addEntity(Entity entity) { | ||||
|     //m_log.fine("Registered new mob " + entity);  | ||||
|     m_livingMobs.add(entity); | ||||
|     m_createdMobs += 1; | ||||
|   } | ||||
|  | ||||
|   public boolean empty() { | ||||
|     return m_livingMobs.size() == 0; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										113
									
								
								src/main/java/gg/malloc/defense/engine/PlayerManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/main/java/gg/malloc/defense/engine/PlayerManager.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| package gg.malloc.defense.engine; | ||||
|  | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.Bukkit; | ||||
| import org.bukkit.GameMode; | ||||
| import org.bukkit.attribute.Attribute; | ||||
| import org.bukkit.event.player.PlayerTeleportEvent; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Collection; | ||||
|  | ||||
| public class PlayerManager { | ||||
|   HashMap<Player, State> m_playerStates = new HashMap<>(); | ||||
|  | ||||
|   public enum State { | ||||
|     Idle, | ||||
|     Playing, | ||||
|     Dead | ||||
|   } | ||||
|  | ||||
|   public boolean requestTransitionForAll(State toState) { | ||||
|     boolean allSuccess = true; | ||||
|     for (Player p : getPlayers()) { | ||||
|       allSuccess = allSuccess && requestTransition(p, toState); | ||||
|     } | ||||
|     return allSuccess; | ||||
|   } | ||||
|  | ||||
|   public boolean requestTransition(Player player, State toState) { | ||||
|     State currentState = m_playerStates.get(player); | ||||
|     if (currentState == toState) { | ||||
|       return false; | ||||
|     } | ||||
|     boolean ret = false; | ||||
|     switch(toState) { | ||||
|       case Idle: | ||||
|         ret = enterIdle(player);break; | ||||
|       case Playing: | ||||
|         ret = enterPlaying(player);break; | ||||
|       case Dead: | ||||
|         ret = enterDead(player);break; | ||||
|     } | ||||
|     if (ret) { | ||||
|       m_playerStates.put(player, toState); | ||||
|     } | ||||
|     return ret; | ||||
|   } | ||||
|  | ||||
|   public boolean enterIdle(Player player) { | ||||
|     // Reset player to non-game state, return control back to Minecraft | ||||
|     player.setGameMode(Bukkit.getDefaultGameMode()); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   // Respawn player | ||||
|   public boolean enterPlaying(Player player) { | ||||
|     //m_log.fine("Respawning player " + player); | ||||
|     player.setGameMode(Bukkit.getDefaultGameMode()); | ||||
|     player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); | ||||
|     player.setFoodLevel(10); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   public boolean enterDead(Player player) { | ||||
|     //m_log.info("Player has died in game" + player); | ||||
|     player.setGameMode(GameMode.SPECTATOR); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   public void addPlayer(Player player) { | ||||
|     //m_log.info("Adding player " + player);  | ||||
|     m_playerStates.put(player, State.Idle); | ||||
|   } | ||||
|  | ||||
|   public boolean removePlayer(Player player) { | ||||
|     //m_log.info("Removing player " + player);  | ||||
|     requestTransition(player, State.Idle); | ||||
|     m_playerStates.remove(player); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   public boolean isEmpty() { | ||||
|     return m_playerStates.size() == 0; | ||||
|   } | ||||
|  | ||||
|   public boolean isAlive(Player player) { | ||||
|     return m_playerStates.get(player) == State.Playing; | ||||
|   } | ||||
|  | ||||
|   public boolean isAnyoneAlive() { | ||||
|     for(Player p : m_playerStates.keySet()) { | ||||
|       if (m_playerStates.get(p) == State.Playing) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   public int remainingPlayers() { | ||||
|     int aliveCount = 0; | ||||
|     for(State s : m_playerStates.values()) { | ||||
|       if (s == State.Playing) { | ||||
|         aliveCount += 1; | ||||
|       } | ||||
|     } | ||||
|     return aliveCount; | ||||
|   } | ||||
|  | ||||
|   Collection<Player> getPlayers() { | ||||
|     return m_playerStates.keySet(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -6,7 +6,7 @@ depend:  [] | ||||
| commands: | ||||
|   setstage: | ||||
|     description: Sets a game stage | ||||
|   initgame: | ||||
|   addplayer: | ||||
|     description: Adds a player to a game | ||||
|   debuginfo: | ||||
|     description: Unknowable powers | ||||
|   | ||||
		Reference in New Issue
	
	Block a user