From d2c12b479293684991299b2e28159ba1142082eb Mon Sep 17 00:00:00 2001 From: Torrie Fischer Date: Sun, 8 May 2022 01:52:25 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + pom.xml | 42 +++ .../gg/malloc/defense/DebuginfoCommand.java | 19 ++ .../gg/malloc/defense/GameEventHandler.java | 46 +++ .../java/gg/malloc/defense/GameRunner.java | 294 ++++++++++++++++++ .../gg/malloc/defense/InitGameCommand.java | 23 ++ .../java/gg/malloc/defense/MemoryArena.java | 34 ++ src/main/java/gg/malloc/defense/Plugin.java | 82 +++++ .../gg/malloc/defense/SetStageCommand.java | 38 +++ .../gg/malloc/defense/games/LinearGame.java | 54 ++++ .../java/gg/malloc/defense/model/Arena.java | 9 + .../java/gg/malloc/defense/model/Game.java | 6 + .../java/gg/malloc/defense/model/Spawner.java | 8 + .../gg/malloc/defense/model/Spawnpoint.java | 9 + .../java/gg/malloc/defense/model/Wave.java | 7 + src/main/resources/plugin.yml | 12 + 16 files changed, 686 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/gg/malloc/defense/DebuginfoCommand.java create mode 100644 src/main/java/gg/malloc/defense/GameEventHandler.java create mode 100644 src/main/java/gg/malloc/defense/GameRunner.java create mode 100644 src/main/java/gg/malloc/defense/InitGameCommand.java create mode 100644 src/main/java/gg/malloc/defense/MemoryArena.java create mode 100644 src/main/java/gg/malloc/defense/Plugin.java create mode 100644 src/main/java/gg/malloc/defense/SetStageCommand.java create mode 100644 src/main/java/gg/malloc/defense/games/LinearGame.java create mode 100644 src/main/java/gg/malloc/defense/model/Arena.java create mode 100644 src/main/java/gg/malloc/defense/model/Game.java create mode 100644 src/main/java/gg/malloc/defense/model/Spawner.java create mode 100644 src/main/java/gg/malloc/defense/model/Spawnpoint.java create mode 100644 src/main/java/gg/malloc/defense/model/Wave.java create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e56bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +test-server +*.swp +target diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ba4740b --- /dev/null +++ b/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + gg.malloc.defense + malloc-defense + 1.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + default + + + jitpack.io + https://jitpack.io + + + + + + org.spigotmc + spigot-api + 1.18.2-R0.1-SNAPSHOT + provided + + + + diff --git a/src/main/java/gg/malloc/defense/DebuginfoCommand.java b/src/main/java/gg/malloc/defense/DebuginfoCommand.java new file mode 100644 index 0000000..c31da79 --- /dev/null +++ b/src/main/java/gg/malloc/defense/DebuginfoCommand.java @@ -0,0 +1,19 @@ +package gg.malloc.defense; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class DebuginfoCommand implements CommandExecutor { + Plugin m_plugin; + + public DebuginfoCommand(Plugin plugin) { + m_plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + m_plugin.debuginfo(); + return true; + } +} diff --git a/src/main/java/gg/malloc/defense/GameEventHandler.java b/src/main/java/gg/malloc/defense/GameEventHandler.java new file mode 100644 index 0000000..9cb2fac --- /dev/null +++ b/src/main/java/gg/malloc/defense/GameEventHandler.java @@ -0,0 +1,46 @@ +package gg.malloc.defense; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerQuitEvent; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.entity.Player; + +public class GameEventHandler implements Listener { + Plugin m_plugin; + + public GameEventHandler(Plugin plugin) { + m_plugin = plugin; + } + + @EventHandler + public void onEntityDeath(EntityDeathEvent evt) { + GameRunner runner = m_plugin.getRunnerForWorld(evt.getEntity().getLocation().getWorld()); + runner.handleEntityDeath(evt.getEntity()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent evt) { + GameRunner runner = m_plugin.getRunnerForWorld(evt.getPlayer().getLocation().getWorld()); + runner.removePlayer(evt.getEntity()); + } + + @EventHandler + public void onEntityCombust(EntityCombustEvent evt) { + evt.setCancelled(true); + } + + @EventHandler + public void onEntityDamage(EntityDamageEvent evt) { + if (evt.getEntity() instanceof Player) { + Player player = (Player)evt.getEntity(); + if (player.getHealth() - evt.getFinalDamage() <= 0) { + GameRunner runner = m_plugin.getRunnerForWorld(evt.getEntity().getLocation().getWorld()); + evt.setCancelled(true); + runner.handlePlayerDeath(player); + } + } + } +} diff --git a/src/main/java/gg/malloc/defense/GameRunner.java b/src/main/java/gg/malloc/defense/GameRunner.java new file mode 100644 index 0000000..f2539fa --- /dev/null +++ b/src/main/java/gg/malloc/defense/GameRunner.java @@ -0,0 +1,294 @@ +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 org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.GameMode; +import org.bukkit.attribute.Attribute; + +public class GameRunner { + ArrayList m_spawnedMobs = new ArrayList(); + int m_killedMobs = 0; + Arena m_arena; + Game m_game; + State m_state; + ArrayList m_players = new ArrayList<>(); + ArrayList m_livingPlayers = new ArrayList<>(); + Plugin m_plugin; + + BossBar m_gameBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); + BukkitTask m_countdownTask; + + int m_currentWaveNum = 0; + int m_currentBatch = 0; + Wave m_currentWave = null; + + 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); + } + + int m_warmupCountdown = 0; + + private void countdownTick() { + if (m_warmupCountdown == 0) { + requestTransition(State.Playing); + } else { + m_gameBar.setProgress((double)m_warmupCountdown / (double)30); + 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_spawnedMobs) { + e.remove(); + } + m_spawnedMobs.clear(); + } + + private boolean enterIdle() { + broadcastMessage("Game state: Idle"); + m_currentWaveNum = 0; + m_gameBar.setColor(BarColor.PURPLE); + m_gameBar.setProgress(1.0); + m_gameBar.setTitle("Idle"); + if (m_countdownTask != null) { + m_countdownTask.cancel(); + m_countdownTask = null; + } + clearMobs(); + return true; + } + + private boolean enterWarmup() { + broadcastMessage("Game state: Warmup"); + m_currentWaveNum += 1; + m_currentWave = m_game.getWave(m_currentWaveNum); + m_gameBar.setColor(BarColor.YELLOW); + m_gameBar.setProgress(1.0); + m_gameBar.setTitle("Warmup"); + m_warmupCountdown = 30; + for(Player p : m_players) { + m_livingPlayers.add(p); + } + clearMobs(); + countdownTick(); + return true; + } + + private boolean enterPlaying() { + broadcastMessage("Game state: Playing"); + broadcastMessage("Starting wave " + m_currentWaveNum); + m_currentBatch = 1; + m_gameBar.setColor(BarColor.GREEN); + m_gameBar.setProgress(0.0); + m_gameBar.setTitle("Playing"); + spawnNextBatch(); + return true; + } + + private boolean enterGameOver() { + broadcastMessage("Game state: Game Over!"); + m_gameBar.setColor(BarColor.RED); + m_gameBar.setProgress(1.0); + m_gameBar.setTitle("Game Over!"); + if (m_countdownTask != null) { + m_countdownTask.cancel(); + m_countdownTask = null; + } + for(Player p : m_players) { + p.setGameMode(GameMode.ADVENTURE); + } + clearMobs(); + return true; + } + + private void updateMobBar() { + m_gameBar.setTitle("Mobs remaining: " + (m_currentWave.totalMobCount() - m_killedMobs)); + m_gameBar.setProgress(m_killedMobs / m_currentWave.totalMobCount()); + } + + private void spawnNextBatch() { + broadcastMessage("Spawning batch " + m_currentBatch); + Spawner spawner = new GameSpawner(m_arena.spawnpoints()[0]); + m_currentWave.spawnBatch(spawner, m_currentBatch); + updateMobBar(); + } + + public void handlePlayerDeath(Player player) { + if (m_livingPlayers.contains(player)) { + m_livingPlayers.remove(player); + player.setGameMode(GameMode.SPECTATOR); + if (m_livingPlayers.size() == 0) { + broadcastMessage("Everyone is dead :("); + requestTransition(State.GameOver); + } + } + } + + public void handleEntityDeath(Entity entity) { + if (m_spawnedMobs.contains(entity)) { + broadcastMessage("Killed game entity " + entity); + m_spawnedMobs.remove(entity); + m_killedMobs += 1; + updateMobBar(); + if (m_spawnedMobs.size() == 0) { + broadcastMessage("Batch complete!"); + if (m_currentBatch >= m_currentWave.batchCount()) { + if (m_currentWaveNum >= m_game.getWaveCount()) { + requestTransition(State.GameOver); + } else { + requestTransition(State.Warmup); + } + } else { + m_currentBatch += 1; + spawnNextBatch(); + } + } else { + broadcastMessage("Entities remaining: " + m_spawnedMobs.size()); + } + } + } + + private boolean syncPlayer(Player player) { + World playerWorld = player.getLocation().getWorld(); + World gameWorld = m_arena.getWorld(); + m_gameBar.addPlayer(player); + if (m_livingPlayers.contains(player)) { + player.setGameMode(GameMode.ADVENTURE); + player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + } else { + player.setGameMode(GameMode.SPECTATOR); + } + if (playerWorld != gameWorld) { + 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)) { + return false; + } + if (attemptTransition(state)) { + m_state = state; + for(Player p : m_players) { + syncPlayer(p); + } + return true; + } + return false; + } + + public boolean addPlayer(Player p) { + if (m_state == State.Idle || m_state == State.Warmup) { + m_players.add(p); + broadcastMessage("Added player " + p + " to game"); + syncPlayer(p); + if (m_state == State.Idle) { + requestTransition(State.Warmup); + } + return true; + } else { + return false; + } + } + + public void removePlayer(Player p) { + m_gameBar.removePlayer(p); + m_players.remove(p); + m_livingPlayers.remove(p); + if (m_players.size() == 0) { + requestTransition(State.Idle); + } + } + + void broadcastMessage(String string) { + World world = m_arena.getWorld(); + for(Player p : world.getPlayers()) { + p.sendMessage(string); + } + } + + void registerSpawnedMob(Entity entity) { + m_spawnedMobs.add(entity); + } + + private class GameSpawner implements Spawner { + Spawnpoint m_spawnpoint; + + public GameSpawner(Spawnpoint spawnpoint) { + m_spawnpoint = spawnpoint; + } + + @Override + public Entity spawnMob(EntityType type) { + Entity newMob = m_arena.getWorld().spawnEntity(m_spawnpoint.getLocation(), type); + LivingEntity livingMob = (LivingEntity)newMob; + livingMob.setRemoveWhenFarAway(false); + registerSpawnedMob(newMob); + return newMob; + } + } +} diff --git a/src/main/java/gg/malloc/defense/InitGameCommand.java b/src/main/java/gg/malloc/defense/InitGameCommand.java new file mode 100644 index 0000000..14c549b --- /dev/null +++ b/src/main/java/gg/malloc/defense/InitGameCommand.java @@ -0,0 +1,23 @@ +package gg.malloc.defense; + +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 { + Plugin m_plugin; + + public InitGameCommand(Plugin plugin) { + m_plugin = plugin; + } + + @Override + 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]); + runner.addPlayer(player); + return true; + } +} diff --git a/src/main/java/gg/malloc/defense/MemoryArena.java b/src/main/java/gg/malloc/defense/MemoryArena.java new file mode 100644 index 0000000..891b17f --- /dev/null +++ b/src/main/java/gg/malloc/defense/MemoryArena.java @@ -0,0 +1,34 @@ +package gg.malloc.defense; + +import gg.malloc.defense.model.Arena; +import gg.malloc.defense.model.Spawnpoint; + +import org.bukkit.World; + +public class MemoryArena implements Arena { + + Spawnpoint[] m_spawnpoints; + World m_world; + String m_name; + + public MemoryArena(String name, World world, Spawnpoint[] spawnpoints) { + m_world = world; + m_spawnpoints = spawnpoints; + m_name = name; + } + + @Override + public String name() { + return m_name; + } + + @Override + public Spawnpoint[] spawnpoints() { + return m_spawnpoints; + } + + @Override + public World getWorld() { + return m_world; + } +} diff --git a/src/main/java/gg/malloc/defense/Plugin.java b/src/main/java/gg/malloc/defense/Plugin.java new file mode 100644 index 0000000..28e2607 --- /dev/null +++ b/src/main/java/gg/malloc/defense/Plugin.java @@ -0,0 +1,82 @@ +package gg.malloc.defense; + +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.World; +import org.bukkit.plugin.PluginLogger; + +import java.util.ArrayList; +import java.util.HashMap; + +import gg.malloc.defense.model.Arena; +import gg.malloc.defense.model.Spawnpoint; +import gg.malloc.defense.model.Game; + +import gg.malloc.defense.games.LinearGame; + +public class Plugin extends JavaPlugin { + ArrayList m_arenas = new ArrayList<>(); + ArrayList m_games = new ArrayList<>(); + HashMap m_runningGames = new HashMap<>(); + + private class TestSpawn implements Spawnpoint { + Location m_location; + + public TestSpawn(Location location) { + m_location = location; + } + + @Override + public Location getLocation() { + return m_location; + } + + @Override + public String getName() { + return "Mob Spawner"; + } + + @Override + public String getID() { + return "mob-spawner"; + } + } + + PluginLogger m_log = new PluginLogger(this); + + public void debuginfo() { + m_log.info("Debug Info:"); + } + + @Override + public void onEnable() { + getLogger().info("Malloc Defense registered"); + setupTestGame(); + getCommand("setstage").setExecutor(new SetStageCommand(this)); + getCommand("initgame").setExecutor(new InitGameCommand(this)); + getCommand("debuginfo").setExecutor(new DebuginfoCommand(this)); + getServer().getPluginManager().registerEvents(new GameEventHandler(this), this); + } + + public GameRunner getRunnerForWorld(World world) { + GameRunner ret; + if (m_runningGames.containsKey(world)) { + ret = m_runningGames.get(world); + } else { + ret = new GameRunner(this, m_games.get(0), m_arenas.get(0)); + m_runningGames.put(world, ret); + } + return ret; + } + + void setupTestGame() { + World testWorld = getServer().getWorld("world"); + Spawnpoint[] spawnpoints = new Spawnpoint[1]; + spawnpoints[0] = new TestSpawn(testWorld.getSpawnLocation()); + m_arenas.add(new MemoryArena("Test Arena", testWorld, spawnpoints)); + m_games.add(new LinearGame()); + } +} diff --git a/src/main/java/gg/malloc/defense/SetStageCommand.java b/src/main/java/gg/malloc/defense/SetStageCommand.java new file mode 100644 index 0000000..2fa1aa9 --- /dev/null +++ b/src/main/java/gg/malloc/defense/SetStageCommand.java @@ -0,0 +1,38 @@ +package gg.malloc.defense; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.World; + +public class SetStageCommand implements CommandExecutor { + Plugin m_plugin; + + public SetStageCommand(Plugin plugin) { + m_plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + World world = m_plugin.getServer().getWorld(args[1]); + GameRunner runner = m_plugin.getRunnerForWorld(world); + String stateName = args[0].toLowerCase(); + boolean ret = false; + if (stateName.equals("idle")) { + ret = runner.requestTransition(GameRunner.State.Idle); + } else if (stateName.equals("warmup")) { + ret = runner.requestTransition(GameRunner.State.Warmup); + } else if (stateName.equals("playing")) { + ret = runner.requestTransition(GameRunner.State.Playing); + } else if (stateName.equals("gameover")) { + ret = runner.requestTransition(GameRunner.State.GameOver); + } 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/games/LinearGame.java b/src/main/java/gg/malloc/defense/games/LinearGame.java new file mode 100644 index 0000000..6fa07e9 --- /dev/null +++ b/src/main/java/gg/malloc/defense/games/LinearGame.java @@ -0,0 +1,54 @@ +package gg.malloc.defense.games; + +import gg.malloc.defense.model.Wave; +import gg.malloc.defense.model.Game; +import gg.malloc.defense.model.Spawner; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +public class LinearGame implements Game { + private class ZombieWave implements Wave { + int m_batches; + int m_zombiesPerBatch; + + public ZombieWave(int zombiesPerBatch, int batches) { + m_batches = batches; + m_zombiesPerBatch = zombiesPerBatch; + } + + @Override + public int totalMobCount() { + int ret = 0; + for(int i = 1; i <= m_batches; i++) { + ret += i * m_zombiesPerBatch; + } + return ret; + } + + @Override + public int batchCount() { + return m_batches; + } + + @Override + public void spawnBatch(Spawner spawner, int batch) { + int zombiesToSpawn = batch * m_zombiesPerBatch; + for(int i = 0; i < zombiesToSpawn; i++) { + Entity newMob = spawner.spawnMob(EntityType.ZOMBIE); + newMob.setCustomName("Zombie " + i + "/" + zombiesToSpawn); + } + } + } + + @Override + public int getWaveCount() { + return 10; + } + + @Override + public Wave getWave(int waveNumber) { + return new ZombieWave(waveNumber, 2); + } + +} diff --git a/src/main/java/gg/malloc/defense/model/Arena.java b/src/main/java/gg/malloc/defense/model/Arena.java new file mode 100644 index 0000000..1a39c59 --- /dev/null +++ b/src/main/java/gg/malloc/defense/model/Arena.java @@ -0,0 +1,9 @@ +package gg.malloc.defense.model; + +import org.bukkit.World; + +public interface Arena { + World getWorld(); + String name(); + Spawnpoint[] spawnpoints(); +} diff --git a/src/main/java/gg/malloc/defense/model/Game.java b/src/main/java/gg/malloc/defense/model/Game.java new file mode 100644 index 0000000..d264502 --- /dev/null +++ b/src/main/java/gg/malloc/defense/model/Game.java @@ -0,0 +1,6 @@ +package gg.malloc.defense.model; + +public interface Game { + int getWaveCount(); + Wave getWave(int waveNumber); +} diff --git a/src/main/java/gg/malloc/defense/model/Spawner.java b/src/main/java/gg/malloc/defense/model/Spawner.java new file mode 100644 index 0000000..0a45723 --- /dev/null +++ b/src/main/java/gg/malloc/defense/model/Spawner.java @@ -0,0 +1,8 @@ +package gg.malloc.defense.model; + +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Entity; + +public interface Spawner { + Entity spawnMob(EntityType type); +} diff --git a/src/main/java/gg/malloc/defense/model/Spawnpoint.java b/src/main/java/gg/malloc/defense/model/Spawnpoint.java new file mode 100644 index 0000000..11f0e49 --- /dev/null +++ b/src/main/java/gg/malloc/defense/model/Spawnpoint.java @@ -0,0 +1,9 @@ +package gg.malloc.defense.model; + +import org.bukkit.Location; + +public interface Spawnpoint { + Location getLocation(); + String getName(); + String getID(); +} diff --git a/src/main/java/gg/malloc/defense/model/Wave.java b/src/main/java/gg/malloc/defense/model/Wave.java new file mode 100644 index 0000000..a5757d0 --- /dev/null +++ b/src/main/java/gg/malloc/defense/model/Wave.java @@ -0,0 +1,7 @@ +package gg.malloc.defense.model; + +public interface Wave { + int batchCount(); + int totalMobCount(); + void spawnBatch(Spawner spawner, int batch); +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..972b0df --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: Malloc-Defense +version: 1.0 +api-version: 1.18 +main: gg.malloc.defense.Plugin +depend: [] +commands: + setstage: + description: Sets a game stage + initgame: + description: Adds a player to a game + debuginfo: + description: Unknowable powers