From a078f74b4b437289f3e4ba80ca17c9e8599df401 Mon Sep 17 00:00:00 2001 From: Torrie Fischer Date: Sun, 8 May 2022 15:34:18 +0200 Subject: [PATCH] reorganize engine bits into engine package, same with commands, reimplement player state as FSM --- .../gg/malloc/defense/GameEventHandler.java | 2 + .../java/gg/malloc/defense/GameRunner.java | 381 ------------------ src/main/java/gg/malloc/defense/Plugin.java | 7 +- .../AddPlayerCommand.java} | 12 +- .../{ => commands}/SetStageCommand.java | 14 +- .../gg/malloc/defense/engine/GameRunner.java | 350 ++++++++++++++++ .../gg/malloc/defense/engine/GameSpawner.java | 33 ++ .../gg/malloc/defense/engine/GameStage.java | 7 + .../gg/malloc/defense/engine/MobManager.java | 72 ++++ .../malloc/defense/engine/PlayerManager.java | 113 ++++++ src/main/resources/plugin.yml | 2 +- 11 files changed, 601 insertions(+), 392 deletions(-) delete mode 100644 src/main/java/gg/malloc/defense/GameRunner.java rename src/main/java/gg/malloc/defense/{InitGameCommand.java => commands/AddPlayerCommand.java} (63%) rename src/main/java/gg/malloc/defense/{ => commands}/SetStageCommand.java (72%) create mode 100644 src/main/java/gg/malloc/defense/engine/GameRunner.java create mode 100644 src/main/java/gg/malloc/defense/engine/GameSpawner.java create mode 100644 src/main/java/gg/malloc/defense/engine/GameStage.java create mode 100644 src/main/java/gg/malloc/defense/engine/MobManager.java create mode 100644 src/main/java/gg/malloc/defense/engine/PlayerManager.java diff --git a/src/main/java/gg/malloc/defense/GameEventHandler.java b/src/main/java/gg/malloc/defense/GameEventHandler.java index 1b1a81a..a13bb1e 100644 --- a/src/main/java/gg/malloc/defense/GameEventHandler.java +++ b/src/main/java/gg/malloc/defense/GameEventHandler.java @@ -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; diff --git a/src/main/java/gg/malloc/defense/GameRunner.java b/src/main/java/gg/malloc/defense/GameRunner.java deleted file mode 100644 index 9c4dbdd..0000000 --- a/src/main/java/gg/malloc/defense/GameRunner.java +++ /dev/null @@ -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 m_livingMobs = new HashSet<>(); - int m_createdMobs = 0; - int m_killedMobs = 0; - Arena m_arena; - Game m_game; - State m_state; - HashSet m_players = new HashSet<>(); - HashSet 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; - } - } -} diff --git a/src/main/java/gg/malloc/defense/Plugin.java b/src/main/java/gg/malloc/defense/Plugin.java index e77f77d..ea07361 100644 --- a/src/main/java/gg/malloc/defense/Plugin.java +++ b/src/main/java/gg/malloc/defense/Plugin.java @@ -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 m_arenas = new ArrayList<>(); ArrayList 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)); } diff --git a/src/main/java/gg/malloc/defense/InitGameCommand.java b/src/main/java/gg/malloc/defense/commands/AddPlayerCommand.java similarity index 63% rename from src/main/java/gg/malloc/defense/InitGameCommand.java rename to src/main/java/gg/malloc/defense/commands/AddPlayerCommand.java index 14c549b..7c97669 100644 --- a/src/main/java/gg/malloc/defense/InitGameCommand.java +++ b/src/main/java/gg/malloc/defense/commands/AddPlayerCommand.java @@ -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; } diff --git a/src/main/java/gg/malloc/defense/SetStageCommand.java b/src/main/java/gg/malloc/defense/commands/SetStageCommand.java similarity index 72% rename from src/main/java/gg/malloc/defense/SetStageCommand.java rename to src/main/java/gg/malloc/defense/commands/SetStageCommand.java index 2fa1aa9..8d2997b 100644 --- a/src/main/java/gg/malloc/defense/SetStageCommand.java +++ b/src/main/java/gg/malloc/defense/commands/SetStageCommand.java @@ -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; diff --git a/src/main/java/gg/malloc/defense/engine/GameRunner.java b/src/main/java/gg/malloc/defense/engine/GameRunner.java new file mode 100644 index 0000000..4e212f7 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/GameRunner.java @@ -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); + } + +} diff --git a/src/main/java/gg/malloc/defense/engine/GameSpawner.java b/src/main/java/gg/malloc/defense/engine/GameSpawner.java new file mode 100644 index 0000000..2a5985b --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/GameSpawner.java @@ -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; + } +} diff --git a/src/main/java/gg/malloc/defense/engine/GameStage.java b/src/main/java/gg/malloc/defense/engine/GameStage.java new file mode 100644 index 0000000..63c8617 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/GameStage.java @@ -0,0 +1,7 @@ +package gg.malloc.defense.engine; + +public interface GameStage { + String name(); + default void onEnter() {} + default void onExit() {} +} diff --git a/src/main/java/gg/malloc/defense/engine/MobManager.java b/src/main/java/gg/malloc/defense/engine/MobManager.java new file mode 100644 index 0000000..a7ab9ff --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/MobManager.java @@ -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 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; + } +} diff --git a/src/main/java/gg/malloc/defense/engine/PlayerManager.java b/src/main/java/gg/malloc/defense/engine/PlayerManager.java new file mode 100644 index 0000000..1c1bf39 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/PlayerManager.java @@ -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 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 getPlayers() { + return m_playerStates.keySet(); + } +} + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 972b0df..663fca6 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -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