From 75debe1905faff744e9dea49907b290088cd60cf Mon Sep 17 00:00:00 2001 From: Torrie Fischer Date: Tue, 10 May 2022 05:28:01 +0200 Subject: [PATCH] Reimplement wave AI, split out some UI code into a ui module, update TODO --- TODO.md | 24 +- .../gg/malloc/defense/GameEventHandler.java | 10 +- .../defense/commands/SetStageCommand.java | 27 +- .../gg/malloc/defense/engine/BombFuse.java | 30 ++ .../gg/malloc/defense/engine/GameRunner.java | 295 ++++++------------ .../gg/malloc/defense/engine/GameSpawner.java | 26 +- .../gg/malloc/defense/engine/MobManager.java | 144 +++++++-- .../malloc/defense/engine/PlayerManager.java | 4 + .../gg/malloc/defense/engine/TickTask.java | 40 +++ .../gg/malloc/defense/engine/WaveManager.java | 55 ++++ .../gg/malloc/defense/games/ScaledWaves.java | 8 +- .../java/gg/malloc/defense/model/Spawner.java | 5 +- .../gg/malloc/defense/ui/BombCarrier.java | 37 +++ .../java/gg/malloc/defense/ui/BossBars.java | 116 +++++++ src/main/java/gg/malloc/defense/ui/Items.java | 15 + 15 files changed, 588 insertions(+), 248 deletions(-) create mode 100644 src/main/java/gg/malloc/defense/engine/BombFuse.java create mode 100644 src/main/java/gg/malloc/defense/engine/TickTask.java create mode 100644 src/main/java/gg/malloc/defense/engine/WaveManager.java create mode 100644 src/main/java/gg/malloc/defense/ui/BombCarrier.java create mode 100644 src/main/java/gg/malloc/defense/ui/BossBars.java create mode 100644 src/main/java/gg/malloc/defense/ui/Items.java diff --git a/TODO.md b/TODO.md index 7e04103..53ee6a0 100644 --- a/TODO.md +++ b/TODO.md @@ -2,19 +2,33 @@ [X] Mobs spawn in waves [X] Mobs move towards goal -[ ] Mobs carry bomb to goal +[X] Mobs carry bomb to goal [X] Mobs arm bomb [X] Bomb explodes +# Malloc Beta + +[ ] Join games +[ ] Leave games +[ ] One arena config + +# Scaled waves + +[ ] Weaker mobs, more of them +[ ] Mob categories +[ ] Spawnpoint categories + # UX [X] Wave boss bar [X] Mob count boss bar [X] Stage titles -[ ] EXPLOSIONS +[X] EXPLOSIONS +[ ] Target catches on fire more it is lit [ ] Colored titles [ ] Clickable /ready in chat [ ] Sidebar +[ ] List of mobs in next wave # Social @@ -27,9 +41,9 @@ # Mechanics [ ] Coin drops -[ ] Mob tracking should prioritize bomb -[ ] Mobs recover dropped bombs -[ ] Bomb carriers are slower +[X] Mob tracking should prioritize bomb +[X] Mobs recover dropped bombs +[X] Bomb carriers are slower [ ] Bonus coins for complete coin pickup [ ] Infinite weapons + armor [ ] Ammo/health spawns diff --git a/src/main/java/gg/malloc/defense/GameEventHandler.java b/src/main/java/gg/malloc/defense/GameEventHandler.java index 2dd2007..bb817d2 100644 --- a/src/main/java/gg/malloc/defense/GameEventHandler.java +++ b/src/main/java/gg/malloc/defense/GameEventHandler.java @@ -40,14 +40,6 @@ public class GameEventHandler implements Listener { @EventHandler public void onEntityDamage(EntityDamageEvent evt) { - if (evt.getEntity() instanceof Player) { - Player player = (Player)evt.getEntity(); - if (player.getHealth() - evt.getFinalDamage() <= 0) { - evt.setCancelled(true); - m_runner.handlePlayerDeath(player); - } - } else { - m_runner.handleEntityDamage(evt); - } + m_runner.handleEntityDamage(evt); } } diff --git a/src/main/java/gg/malloc/defense/commands/SetStageCommand.java b/src/main/java/gg/malloc/defense/commands/SetStageCommand.java index 8d2997b..5517d84 100644 --- a/src/main/java/gg/malloc/defense/commands/SetStageCommand.java +++ b/src/main/java/gg/malloc/defense/commands/SetStageCommand.java @@ -22,21 +22,24 @@ public class SetStageCommand implements CommandExecutor { GameRunner runner = m_plugin.getRunnerForWorld(world); String stateName = args[0].toLowerCase(); boolean ret = false; - if (stateName.equals("idle")) { - ret = runner.requestTransition(GameRunner.Stage.Idle); - } else if (stateName.equals("warmup")) { - ret = runner.requestTransition(GameRunner.Stage.Warmup); - } else if (stateName.equals("playing")) { - ret = runner.requestTransition(GameRunner.Stage.Playing); - } else if (stateName.equals("gameover")) { - ret = runner.requestTransition(GameRunner.Stage.GameOver); + GameRunner.Stage decodedStage = null; + GameRunner.Stage stages[] = GameRunner.Stage.Idle.getDeclaringClass().getEnumConstants(); + for(GameRunner.Stage stage : stages) { + if (stage.toString().toLowerCase().equals(stateName)) { + decodedStage = stage; + break; + } + } + if (decodedStage != null) { + sender.sendMessage("Requesting transition to " + decodedStage); + if (!runner.requestTransition(decodedStage)) { + sender.sendMessage("Could not transition to " + decodedStage); + return false; + } + return true; } else { sender.sendMessage("Unknown state " + stateName); return false; } - if (!ret) { - sender.sendMessage("Could not set state to " + stateName); - } - return ret; } } diff --git a/src/main/java/gg/malloc/defense/engine/BombFuse.java b/src/main/java/gg/malloc/defense/engine/BombFuse.java new file mode 100644 index 0000000..7ffbd42 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/BombFuse.java @@ -0,0 +1,30 @@ +package gg.malloc.defense.engine; + +public class BombFuse { + int m_bombFuseCount = 0; + int m_bombFuseTarget = 10; + + public double getProgress() { + return Math.max(0.0, Math.min(1.0, (double)m_bombFuseCount / (double)m_bombFuseTarget)); + } + + public boolean isLit() { + return m_bombFuseCount > 0; + } + + public boolean isExploded() { + return m_bombFuseCount > m_bombFuseTarget; + } + + public void reset() { + m_bombFuseCount = 0; + } + + public void tickLit() { + m_bombFuseCount += 1; + } + + public void tickDecay() { + m_bombFuseCount -= 1; + } +} diff --git a/src/main/java/gg/malloc/defense/engine/GameRunner.java b/src/main/java/gg/malloc/defense/engine/GameRunner.java index e8e827d..ab209de 100644 --- a/src/main/java/gg/malloc/defense/engine/GameRunner.java +++ b/src/main/java/gg/malloc/defense/engine/GameRunner.java @@ -1,30 +1,35 @@ package gg.malloc.defense.engine; -import gg.malloc.defense.model.Game; import gg.malloc.defense.model.Arena; +import gg.malloc.defense.model.Game; import gg.malloc.defense.model.Spawner; -import gg.malloc.defense.model.Wave; + +import gg.malloc.defense.ui.BombCarrier; +import gg.malloc.defense.ui.BossBars; import gg.malloc.defense.Plugin; import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.HashSet; import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Particle; import org.bukkit.Sound; +import org.bukkit.SoundCategory; 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.EntityTargetEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.ArmorStand; import org.bukkit.entity.EntityType; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; public class GameRunner { Arena m_arena; @@ -32,18 +37,11 @@ public class GameRunner { 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); - BossBar m_bombBar = Bukkit.createBossBar("Bomb HP", BarColor.RED, BarStyle.SOLID); - - BukkitTask m_countdownTask; - MobManager m_mobs; WaveManager m_waves; PlayerManager m_players; - LivingEntity m_bombTarget = null; - int m_bombHP = 100; + BombFuse m_bombFuse; Logger m_log; @@ -55,126 +53,96 @@ public class GameRunner { 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); - } - } + TickTask m_fuseTask; + TickTask m_countdownTask; + TickTask m_bombSmokeTask; + TickTask m_bombCrackleTask; + BossBars m_bars; 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_bombBar.setVisible(false); - m_mobs = new MobManager(m_game); + m_mobs = new MobManager(); m_waves = new WaveManager(m_game); m_players = new PlayerManager(); + m_bombFuse = new BombFuse(); + m_bars = new BossBars(this, m_game, m_waves, m_players, m_bombFuse, m_mobs); m_log = m_plugin.getLogger(); + + m_fuseTask = new TickTask(m_plugin, () -> { + if (m_bombFuse.isLit()) { + m_bombFuse.tickDecay(); + m_bars.update(); + } + }, 80); + + m_countdownTask = new TickTask(m_plugin, () -> { + if (m_warmupCountdown == 0) { + requestTransition(Stage.Playing); + } else { + m_bars.update(); + broadcastMessage("Starting game in " + m_warmupCountdown); + m_warmupCountdown--; + m_bars.setCountdownProgress((double)m_warmupCountdown / 10.0); + m_bars.update(); + } + }, 20); + + m_bombSmokeTask = new TickTask(m_plugin, () -> { + Location targetLoc = m_arena.bombTarget().getLocation(); + m_arena.getWorld().spawnParticle(Particle.SMOKE_LARGE, targetLoc, 35, 4, 2, 4); + m_arena.getWorld().spawnParticle(Particle.SMALL_FLAME, targetLoc, 30, 3, 2, 3); + }, 5); + m_bombCrackleTask = new TickTask(m_plugin, () -> { + Location targetLoc = m_arena.bombTarget().getLocation(); + m_arena.getWorld().playSound(targetLoc, Sound.BLOCK_CAMPFIRE_CRACKLE, SoundCategory.NEUTRAL, 1.0f, 1.0f); + }, 35); } int m_warmupCountdown = 0; public void handleEntityRetargeting(EntityTargetEvent evt) { - if (m_mobs.contains(evt.getEntity()) && evt.getReason() == EntityTargetEvent.TargetReason.FORGOT_TARGET) { - evt.setTarget(m_bombTarget); - } + m_mobs.handleEntityRetarget(evt); } public void handleEntityDamage(EntityDamageEvent evt) { - if (evt.getEntity() == m_bombTarget && evt instanceof EntityDamageByEntityEvent) { + m_mobs.handleEntityDamage(evt); + if (m_mobs.bombWasHit()) { EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt; - if (m_mobs.contains(entityEvt.getDamager())) { - entityEvt.getDamager().setGlowing(true); - m_bombHP -= 1; - broadcastMessage("The bomb has been struck!"); - m_arena.getWorld().playSound(m_bombTarget.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 1.5f, 0.9f); - updateMobBars(); - if (m_bombHP <= 0) { - m_arena.getWorld().strikeLightningEffect(m_bombTarget.getLocation()); - requestTransition(Stage.GameOver); - } + m_log.info("Target attacked!"); + entityEvt.getDamager().setGlowing(true); + m_bombFuse.tickLit(); + m_arena.getWorld().playSound(m_arena.bombTarget().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 1.5f, 0.9f); + m_bars.update(); + if (m_bombFuse.isExploded()) { + m_arena.getWorld().strikeLightningEffect(m_arena.bombTarget().getLocation()); + m_arena.getWorld().playSound(m_arena.bombTarget().getLocation(), Sound.ENTITY_GENERIC_EXPLODE, SoundCategory.NEUTRAL, 1.3f, 1.0f); + m_arena.getWorld().spawnParticle(Particle.EXPLOSION_HUGE, m_arena.bombTarget().getLocation(), 8, 5, 2, 5); + requestTransition(Stage.GameOver); + m_bombSmokeTask.start(); + m_bombCrackleTask.start(); + } + } else if (evt.getEntity() instanceof Player) { + Player player = (Player)evt.getEntity(); + if (m_players.contains(player) && player.getHealth() - evt.getFinalDamage() <= 0) { + evt.setCancelled(true); + handlePlayerDeath(player); } - evt.setCancelled(true); - } else { - 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_bombHP = 100; + m_bombFuse.reset(); m_waves.reset(); m_mobs.clear(); + m_bombSmokeTask.stop(); + m_bombCrackleTask.stop(); m_players.requestTransitionForAll(PlayerManager.State.Idle); - if (m_bombTarget != null) { - m_bombTarget.remove(); - m_bombTarget = null; - } - if (m_countdownTask != null) { - m_countdownTask.cancel(); - m_countdownTask = null; - } + m_fuseTask.stop(); + m_countdownTask.stop(); return true; } @@ -204,25 +172,22 @@ public class GameRunner { private boolean enterCountdown() { m_warmupCountdown = 10; - countdownTick(); + m_bars.setCountdownProgress(1.0); + m_countdownTask.start(); return true; } private boolean enterPlaying() { m_log.info("Starting wave " + m_waves.currentWaveNum()); - if (m_bombTarget == null) { - m_bombTarget = (LivingEntity)m_arena.getWorld().spawnEntity(m_arena.bombTarget().getLocation(), EntityType.ARMOR_STAND); - } - ArmorStand bombStand = (ArmorStand)m_bombTarget; - bombStand.setCustomName("Bomb Target"); - bombStand.setVisible(true); - bombStand.setCustomNameVisible(true); - bombStand.setGlowing(true); + m_mobs.spawnTarget(m_arena.bombTarget().getLocation()); + // TODO: Set helmet with custom model data for(Player p : m_players.getPlayers()) { if (m_players.requestTransition(p, PlayerManager.State.Playing)) { p.teleport(m_arena.getWorld().getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); } } + m_countdownTask.stop(); + m_fuseTask.start(); spawnNextBatch(); broadcastTitle("Wave " + m_waves.currentWaveNum()); return true; @@ -230,80 +195,28 @@ public class GameRunner { private boolean enterGameOver() { broadcastTitle("Game Over!"); - if (m_countdownTask != null) { - m_countdownTask.cancel(); - m_countdownTask = null; - } + broadcastMessage("The bomb blew up!"); + m_countdownTask.stop(); + m_fuseTask.stop(); m_mobs.clear(); - for(Player p : m_players.getPlayers()) { + for(Player p : new ArrayList(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 players..."); - m_gameBar.setColor(BarColor.PURPLE); - m_waveBar.setVisible(false); - m_bombBar.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.BLUE); - m_waveBar.setTitle("Warmup - Waiting for players to get ready..."); - m_waveBar.setProgress(m_players.readyProgress()); - m_bombBar.setVisible(false); - break; - case Countdown: - 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("Wave starting!"); - m_waveBar.setProgress((double)m_warmupCountdown / (double)10); - m_bombBar.setVisible(false); - 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); - } - m_bombBar.setVisible(true); - m_bombBar.setProgress((double)m_bombHP / (double)100); - break; - case GameOver: - m_gameBar.setColor(BarColor.RED); - m_gameBar.setProgress(1.0); - m_gameBar.setTitle("Game Over!"); - m_waveBar.setVisible(false); - m_bombBar.setVisible(false); - break; - } + public Stage getStage() { + return m_stage; } private void spawnNextBatch() { broadcastMessage("Spawning batch " + m_waves.currentBatchNum()); - Spawner spawner = new GameSpawner(m_arena, m_mobs, m_players, m_bombTarget); + Spawner spawner = new GameSpawner(m_arena, m_mobs, m_players); m_waves.currentWave().spawnBatch(spawner, m_waves.currentBatchNum()); - updateMobBars(); + m_bars.update(); } - public void handlePlayerDeath(Player player) { + private void handlePlayerDeath(Player player) { if (m_players.requestTransition(player, PlayerManager.State.Dead)) { m_arena.getWorld().strikeLightningEffect(player.getLocation()); if (!m_players.isAnyoneAlive()) { @@ -316,9 +229,9 @@ public class GameRunner { } public void handleEntityDeath(Entity entity) { + boolean wasCarrier = m_mobs.isBombCarrier(entity); if (m_mobs.killMob(entity)) { - broadcastMessage("Killed game entity " + entity); - updateMobBars(); + m_bars.update(); if (m_mobs.remainingMobs() <= 3) { m_log.info("Starting next batch!"); if (m_waves.isLastBatch()) { @@ -381,7 +294,7 @@ public class GameRunner { if (attemptTransition(stage)) { m_log.info("Game transition: " + m_stage + " -> " + stage); m_stage = stage; - updateMobBars(); + m_bars.update(); return true; } m_log.severe("Failed to complete transition: " + m_stage + " -> " + stage); @@ -389,10 +302,11 @@ public class GameRunner { } public void addPlayer(Player p) { + if (m_players.contains(p)) { + return; + } m_players.addPlayer(p); - m_gameBar.addPlayer(p); - m_waveBar.addPlayer(p); - m_bombBar.addPlayer(p); + m_bars.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); @@ -405,8 +319,7 @@ public class GameRunner { } public void removePlayer(Player p) { - m_gameBar.removePlayer(p); - m_waveBar.removePlayer(p); + m_bars.removePlayer(p); m_players.removePlayer(p); if (m_players.isEmpty()) { requestTransition(Stage.Idle); @@ -429,8 +342,4 @@ public class GameRunner { 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 index a5fc118..0a01293 100644 --- a/src/main/java/gg/malloc/defense/engine/GameSpawner.java +++ b/src/main/java/gg/malloc/defense/engine/GameSpawner.java @@ -8,32 +8,44 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Mob; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; public class GameSpawner implements Spawner { Arena m_arena; MobManager m_manager; PlayerManager m_players; - LivingEntity m_bombTarget; int m_spawnIdx = 0; - public GameSpawner(Arena arena, MobManager manager, PlayerManager players, LivingEntity bombTarget) { + public GameSpawner(Arena arena, MobManager manager, PlayerManager players) { m_arena = arena; m_manager = manager; m_players = players; - m_bombTarget = bombTarget; } @Override - public Entity spawnMob(EntityType type) { + public LivingEntity spawnBombCarrier(EntityType type) { + if (m_manager.bombCount() == 0) { + return m_manager.addBombCarrier(spawnMob(type)); + } else { + return spawnMob(type); + } + } + + @Override + public LivingEntity 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_manager.addEntity(livingMob); m_spawnIdx += 1; - ((Mob)newMob).setTarget(m_bombTarget); - return newMob; + return livingMob; } } diff --git a/src/main/java/gg/malloc/defense/engine/MobManager.java b/src/main/java/gg/malloc/defense/engine/MobManager.java index b5de507..1ae1807 100644 --- a/src/main/java/gg/malloc/defense/engine/MobManager.java +++ b/src/main/java/gg/malloc/defense/engine/MobManager.java @@ -2,48 +2,61 @@ package gg.malloc.defense.engine; import java.util.HashSet; +import org.bukkit.Location; +import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityTargetEvent; import gg.malloc.defense.model.Game; +import gg.malloc.defense.ui.BombCarrier; + public class MobManager { - HashSet m_livingMobs = new HashSet<>(); + HashSet m_livingMobs = new HashSet<>(); + HashSet m_bombCarriers = new HashSet<>(); + HashSet m_droppedBombs = 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 contains(Entity entity) { return m_livingMobs.contains(entity); } + public boolean isBombCarrier(Entity entity) { + return m_bombCarriers.contains(entity); + } + public boolean killMob(Entity entity) { if (m_livingMobs.contains(entity)) { m_killedMobs += 1; + if (m_bombCarriers.remove(entity)) { + dropBomb(entity.getLocation()); + } return m_livingMobs.remove(entity); } return false; } public void clear() { - for(Entity e : m_livingMobs) { + for(LivingEntity e : m_livingMobs) { e.remove(); } + for(LivingEntity e : m_droppedBombs) { + e.remove(); + } + if (m_bombTarget != null) { + m_bombTarget.remove(); + } + m_bombTarget = null; + m_droppedBombs.clear(); m_livingMobs.clear(); + m_bombCarriers.clear(); m_createdMobs = 0; m_killedMobs = 0; } @@ -64,13 +77,106 @@ public class MobManager { return (double)m_killedMobs / (double)m_createdMobs; } - public void addEntity(Entity entity) { - //m_log.fine("Registered new mob " + entity); + public void addEntity(LivingEntity entity) { m_livingMobs.add(entity); + Mob asMob = (Mob)entity; + if (m_droppedBombs.size() > 0) { + asMob.setTarget(m_droppedBombs.iterator().next()); + } else { + asMob.setTarget(m_bombTarget); + } m_createdMobs += 1; } + public int bombCount() { + return m_bombCarriers.size() + m_droppedBombs.size(); + } + + public LivingEntity addBombCarrier(LivingEntity entity) { + m_bombCarriers.add(entity); + ((Mob)entity).setTarget(m_bombTarget); + return new BombCarrier(entity).inHand(); + } + + public void removeBombCarrier(LivingEntity entity) { + m_bombCarriers.remove(entity); + } + public boolean empty() { return m_livingMobs.size() == 0; } + + public LivingEntity dropBomb(Location location) { + ArmorStand droppedBomb = (ArmorStand)(new BombCarrier((LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND)).onHead()); + droppedBomb.setCustomName("The Bomb"); + droppedBomb.setVisible(true); + droppedBomb.setCustomNameVisible(true); + droppedBomb.setGlowing(true); + m_droppedBombs.add(droppedBomb); + for(LivingEntity ent : m_livingMobs) { + if (!m_bombCarriers.contains(ent)) { + ((Mob)ent).setTarget(droppedBomb); + } + } + return droppedBomb; + } + + boolean m_bombWasHit = false; + + public boolean bombWasHit() { + return m_bombWasHit; + } + + public void handleEntityDamage(EntityDamageEvent evt) { + Entity entity = evt.getEntity(); + m_bombWasHit = false; + + if (entity == m_bombTarget && evt instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt; + evt.setCancelled(true); + if (m_bombCarriers.contains(entityEvt.getDamager())) { + m_bombWasHit = true; + } + } if (m_droppedBombs.contains(entity)) { + evt.setCancelled(true); + EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt; + if (m_livingMobs.contains(entityEvt.getDamager())) { + evt.getEntity().remove(); + m_droppedBombs.remove(evt.getEntity()); + addBombCarrier((LivingEntity)entityEvt.getDamager()); + for(LivingEntity ent : m_livingMobs) { + if (!m_bombCarriers.contains(ent)) { + ((Mob)ent).setTarget(m_bombTarget); + } + } + } + } else if (m_livingMobs.contains(evt.getEntity())) { + //m_game.onMobDamaged(evt.getEntity()); + } + } + + LivingEntity m_bombTarget; + + public LivingEntity spawnTarget(Location location) { + assert(m_bombTarget == null); + m_bombTarget = (LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND); + ArmorStand bombStand = (ArmorStand)m_bombTarget; + bombStand.setCustomName("Bomb Target"); + bombStand.setVisible(true); + bombStand.setCustomNameVisible(true); + bombStand.setGlowing(true); + return m_bombTarget; + } + + public void handleEntityRetarget(EntityTargetEvent evt) { + if (m_livingMobs.contains(evt.getEntity())) { + if (m_bombCarriers.contains(evt.getEntity())) { + evt.setTarget(m_bombTarget); + } if (m_droppedBombs.size() > 0) { + evt.setTarget(m_droppedBombs.iterator().next()); + } else if (evt.getReason() == EntityTargetEvent.TargetReason.FORGOT_TARGET) { + evt.setTarget(m_bombTarget); + } + } + } } diff --git a/src/main/java/gg/malloc/defense/engine/PlayerManager.java b/src/main/java/gg/malloc/defense/engine/PlayerManager.java index 3023561..37058ba 100644 --- a/src/main/java/gg/malloc/defense/engine/PlayerManager.java +++ b/src/main/java/gg/malloc/defense/engine/PlayerManager.java @@ -27,6 +27,10 @@ public class PlayerManager { return allSuccess; } + public boolean contains(Player player) { + return m_playerStates.containsKey(player); + } + public boolean requestTransition(Player player, State toState) { State currentState = m_playerStates.get(player); if (currentState == toState) { diff --git a/src/main/java/gg/malloc/defense/engine/TickTask.java b/src/main/java/gg/malloc/defense/engine/TickTask.java new file mode 100644 index 0000000..d810a93 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/TickTask.java @@ -0,0 +1,40 @@ +package gg.malloc.defense.engine; + +import gg.malloc.defense.Plugin; +import org.bukkit.scheduler.BukkitTask; + +public class TickTask { + Runnable m_runnable; + int m_interval; + BukkitTask m_task = null; + Plugin m_plugin; + boolean m_cancelled = false; + + public TickTask(Plugin plugin, Runnable runnable, int interval) { + m_plugin = plugin; + m_runnable = runnable; + m_interval = interval; + } + + public void start() { + m_cancelled = false; + if (m_task == null) { + tick(); + } + } + + private void tick() { + m_runnable.run(); + if (!m_cancelled) { + m_task = m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {tick();}, m_interval); + } + } + + void stop() { + m_cancelled = true; + if (m_task != null) { + m_task.cancel(); + m_task = null; + } + } +} diff --git a/src/main/java/gg/malloc/defense/engine/WaveManager.java b/src/main/java/gg/malloc/defense/engine/WaveManager.java new file mode 100644 index 0000000..78d32a0 --- /dev/null +++ b/src/main/java/gg/malloc/defense/engine/WaveManager.java @@ -0,0 +1,55 @@ +package gg.malloc.defense.engine; + +import gg.malloc.defense.model.Wave; +import gg.malloc.defense.model.Game; + +public class WaveManager { + int m_currentWaveNum = 0; + int m_currentBatch = 0; + Wave m_currentWave = null; + Game m_game; + + public WaveManager(Game game) { + m_game = game; + } + + public void reset() { + m_currentWaveNum = 0; + m_currentBatch = 0; + m_currentWave = null; + } + + public Wave currentWave() { + return m_currentWave; + } + + public int currentWaveNum() { + return m_currentWaveNum; + } + + public int currentBatchNum() { + return m_currentBatch; + } + + public double progress() { + return (double)m_currentWaveNum / (double)m_game.getWaveCount(); + } + + public boolean isLastWave() { + return m_currentWaveNum >= m_game.getWaveCount(); + } + + public boolean isLastBatch() { + return m_currentBatch >= m_currentWave.batchCount(); + } + + public void nextBatch() { + m_currentBatch += 1; + } + + public void next() { + m_currentWaveNum += 1; + m_currentBatch = 0; + m_currentWave = m_game.getWave(m_currentWaveNum); + } +} diff --git a/src/main/java/gg/malloc/defense/games/ScaledWaves.java b/src/main/java/gg/malloc/defense/games/ScaledWaves.java index 8391f58..c6e0112 100644 --- a/src/main/java/gg/malloc/defense/games/ScaledWaves.java +++ b/src/main/java/gg/malloc/defense/games/ScaledWaves.java @@ -6,6 +6,9 @@ import gg.malloc.defense.model.Spawner; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Mob; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.attribute.Attribute; import java.util.HashMap; @@ -55,6 +58,8 @@ public class ScaledWaves implements Game { @Override public void spawnBatch(Spawner spawner, int batch) { + double healthScale = -0.5; + AttributeModifier modifier = new AttributeModifier("Scaled Health", healthScale, AttributeModifier.Operation.MULTIPLY_SCALAR_1); assert(m_mobsPerBatch > 0); for(int i = 0; i < m_mobsPerBatch; i++) { EntityType selectedType = null; @@ -67,8 +72,9 @@ public class ScaledWaves implements Game { } } assert(selectedType != null); - Entity newMob = spawner.spawnMob(selectedType); + Mob newMob = (Mob)spawner.spawnBombCarrier(selectedType); newMob.setCustomName("Mob " + i + "/" + m_mobsPerBatch); + newMob.getAttribute(Attribute.GENERIC_MAX_HEALTH).addModifier(modifier); } if (m_hasRavager) { Entity newMob = spawner.spawnMob(EntityType.RAVAGER); diff --git a/src/main/java/gg/malloc/defense/model/Spawner.java b/src/main/java/gg/malloc/defense/model/Spawner.java index 0a45723..b447b1e 100644 --- a/src/main/java/gg/malloc/defense/model/Spawner.java +++ b/src/main/java/gg/malloc/defense/model/Spawner.java @@ -1,8 +1,9 @@ package gg.malloc.defense.model; import org.bukkit.entity.EntityType; -import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; public interface Spawner { - Entity spawnMob(EntityType type); + LivingEntity spawnMob(EntityType type); + LivingEntity spawnBombCarrier(EntityType type); } diff --git a/src/main/java/gg/malloc/defense/ui/BombCarrier.java b/src/main/java/gg/malloc/defense/ui/BombCarrier.java new file mode 100644 index 0000000..5b9dbe7 --- /dev/null +++ b/src/main/java/gg/malloc/defense/ui/BombCarrier.java @@ -0,0 +1,37 @@ +package gg.malloc.defense.ui; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.Material; + +public class BombCarrier { + LivingEntity m_entity; + + public BombCarrier(LivingEntity entity) { + m_entity = entity; + } + + public LivingEntity inHand() { + EntityEquipment equipment = m_entity.getEquipment(); + equipment.setItemInOffHand(makeBombHelmet()); + //equipment.setItemInOffHandDropChance(0.0f); + return m_entity; + } + + public LivingEntity onHead() { + EntityEquipment equipment = m_entity.getEquipment(); + equipment.setHelmet(makeBombHelmet()); + //equipment.setHelmetDropChance(0.0f); + return m_entity; + } + + static ItemStack makeBombHelmet() { + ItemStack bombItem = new ItemStack(Material.CARVED_PUMPKIN); + ItemMeta meta = bombItem.getItemMeta(); + meta.setCustomModelData(33197); + bombItem.setItemMeta(meta); + return bombItem; + } +} diff --git a/src/main/java/gg/malloc/defense/ui/BossBars.java b/src/main/java/gg/malloc/defense/ui/BossBars.java new file mode 100644 index 0000000..f4c3642 --- /dev/null +++ b/src/main/java/gg/malloc/defense/ui/BossBars.java @@ -0,0 +1,116 @@ +package gg.malloc.defense.ui; + +import gg.malloc.defense.engine.GameRunner; +import gg.malloc.defense.engine.WaveManager; +import gg.malloc.defense.engine.PlayerManager; +import gg.malloc.defense.engine.MobManager; +import gg.malloc.defense.engine.BombFuse; + +import gg.malloc.defense.model.Game; + +import org.bukkit.boss.BossBar; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BarColor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class BossBars { + BossBar m_gameBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); + BossBar m_waveBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); + BossBar m_bombBar = Bukkit.createBossBar("Bomb Fuse", BarColor.RED, BarStyle.SOLID); + + GameRunner m_runner; + MobManager m_mobs; + WaveManager m_waves; + PlayerManager m_players; + BombFuse m_fuse; + Game m_game; + + double m_countdownProgress; + + public void setCountdownProgress(double v) { + m_countdownProgress = v; + } + + public BossBars(GameRunner runner, Game game, WaveManager waves, PlayerManager players, BombFuse fuse, MobManager mobs) { + m_runner = runner; + m_game = game; + m_waves = waves; + m_players = players; + m_fuse = fuse; + m_mobs = mobs; + m_gameBar.setVisible(true); + m_waveBar.setVisible(false); + m_bombBar.setVisible(false); + } + + public void addPlayer(Player p) { + m_gameBar.addPlayer(p); + m_waveBar.addPlayer(p); + m_bombBar.addPlayer(p); + } + + public void removePlayer(Player p) { + m_gameBar.removePlayer(p); + m_waveBar.removePlayer(p); + m_bombBar.removePlayer(p); + } + + public void update() { + switch(m_runner.getStage()) { + case Idle: + m_gameBar.setProgress(1.0); + m_gameBar.setTitle("Waiting for players..."); + m_gameBar.setColor(BarColor.PURPLE); + m_waveBar.setVisible(false); + m_bombBar.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.BLUE); + m_waveBar.setTitle("Warmup - Waiting for players to get ready..."); + m_waveBar.setProgress(m_players.readyProgress()); + m_bombBar.setVisible(false); + break; + case Countdown: + 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("Wave starting!"); + m_waveBar.setProgress(m_countdownProgress); + m_bombBar.setVisible(false); + 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); + } + if (m_fuse.isLit()) { + m_bombBar.setVisible(true); + m_bombBar.setProgress(1.0 - m_fuse.getProgress()); + } else { + m_bombBar.setVisible(false); + } + break; + case GameOver: + m_gameBar.setColor(BarColor.RED); + m_gameBar.setProgress(1.0); + m_gameBar.setTitle("Game Over!"); + m_waveBar.setVisible(false); + m_bombBar.setVisible(false); + break; + } + } +} diff --git a/src/main/java/gg/malloc/defense/ui/Items.java b/src/main/java/gg/malloc/defense/ui/Items.java new file mode 100644 index 0000000..3de3af4 --- /dev/null +++ b/src/main/java/gg/malloc/defense/ui/Items.java @@ -0,0 +1,15 @@ +package gg.malloc.defense.engine; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.Material; + +public class Items { + static ItemStack makeBombHelmet() { + ItemStack bombItem = new ItemStack(Material.CARVED_PUMPKIN); + ItemMeta meta = bombItem.getItemMeta(); + meta.setCustomModelData(0); + bombItem.setItemMeta(meta); + return bombItem; + } +}