Make things less debug-y, introduce the start of scripted waves, UX improvements

This commit is contained in:
Torrie Fischer 2022-05-08 11:38:23 +02:00
parent d2c12b4792
commit 9e7ab2c0d4
8 changed files with 276 additions and 82 deletions

30
TODO.md Normal file
View File

@ -0,0 +1,30 @@
# TODO
[ ] Coin drops
[ ] Basic setup commands
[ ] Config file
[ ] Spectator mode
[ ] Commands/tools to start warmup countdown/mark 'ready'
[ ] Upgrades
[ ] Plan stats
[ ] Live map editing
[ ] Instancing
[ ] DiscordSRV
[ ] Voice Chat
[ ] Player readiness starts countdown
# Scripted waves
[ ] Bosses
[ ] Batch overlap/timing
# Game lifecycle
[ ] Return to lobby
[ ] Join game via sign/commands/something
[ ] Bonus coins for complete coin pickup
# Cleanup
[ ] Generic countdown mechanic
[ ]

View File

@ -4,27 +4,25 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.PlayerQuitEvent;
import org.bukkit.event.entity.EntityCombustEvent; import org.bukkit.event.entity.EntityCombustEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class GameEventHandler implements Listener { public class GameEventHandler implements Listener {
Plugin m_plugin; GameRunner m_runner;
public GameEventHandler(Plugin plugin) { public GameEventHandler(GameRunner runner) {
m_plugin = plugin; m_runner = runner;
} }
@EventHandler @EventHandler
public void onEntityDeath(EntityDeathEvent evt) { public void onEntityDeath(EntityDeathEvent evt) {
GameRunner runner = m_plugin.getRunnerForWorld(evt.getEntity().getLocation().getWorld()); m_runner.handleEntityDeath(evt.getEntity());
runner.handleEntityDeath(evt.getEntity());
} }
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent evt) { public void onPlayerQuit(PlayerQuitEvent evt) {
GameRunner runner = m_plugin.getRunnerForWorld(evt.getPlayer().getLocation().getWorld()); m_runner.removePlayer(evt.getPlayer());
runner.removePlayer(evt.getEntity());
} }
@EventHandler @EventHandler
@ -37,10 +35,11 @@ public class GameEventHandler implements Listener {
if (evt.getEntity() instanceof Player) { if (evt.getEntity() instanceof Player) {
Player player = (Player)evt.getEntity(); Player player = (Player)evt.getEntity();
if (player.getHealth() - evt.getFinalDamage() <= 0) { if (player.getHealth() - evt.getFinalDamage() <= 0) {
GameRunner runner = m_plugin.getRunnerForWorld(evt.getEntity().getLocation().getWorld());
evt.setCancelled(true); evt.setCancelled(true);
runner.handlePlayerDeath(player); m_runner.handlePlayerDeath(player);
} }
} else {
m_runner.handleEntityDamage(evt);
} }
} }
} }

View File

@ -5,39 +5,47 @@ import gg.malloc.defense.model.Arena;
import gg.malloc.defense.model.Spawnpoint; import gg.malloc.defense.model.Spawnpoint;
import gg.malloc.defense.model.Spawner; import gg.malloc.defense.model.Spawner;
import gg.malloc.defense.model.Wave; import gg.malloc.defense.model.Wave;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.logging.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; 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.player.PlayerTeleportEvent;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.boss.BarColor; import org.bukkit.scheduler.BukkitTask;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.GameMode;
import org.bukkit.attribute.Attribute;
public class GameRunner { public class GameRunner {
ArrayList<Entity> m_spawnedMobs = new ArrayList<Entity>(); HashSet<Entity> m_livingMobs = new HashSet<>();
int m_createdMobs = 0;
int m_killedMobs = 0; int m_killedMobs = 0;
Arena m_arena; Arena m_arena;
Game m_game; Game m_game;
State m_state; State m_state;
ArrayList<Player> m_players = new ArrayList<>(); HashSet<Player> m_players = new HashSet<>();
ArrayList<Player> m_livingPlayers = new ArrayList<>(); HashSet<Player> m_livingPlayers = new HashSet<>();
Plugin m_plugin; Plugin m_plugin;
BossBar m_gameBar = Bukkit.createBossBar("Malloc Defense", BarColor.PURPLE, BarStyle.SOLID); 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; BukkitTask m_countdownTask;
int m_currentWaveNum = 0; int m_currentWaveNum = 0;
int m_currentBatch = 0; int m_currentBatch = 0;
Wave m_currentWave = null; Wave m_currentWave = null;
Logger m_log;
enum State { enum State {
Idle, Idle,
Warmup, Warmup,
@ -51,15 +59,26 @@ public class GameRunner {
m_arena = arena; m_arena = arena;
m_state = State.Idle; m_state = State.Idle;
m_gameBar.setVisible(true); m_gameBar.setVisible(true);
m_waveBar.setVisible(false);
m_log = m_plugin.getLogger();
} }
int m_warmupCountdown = 0; 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() { private void countdownTick() {
if (m_warmupCountdown == 0) { if (m_warmupCountdown == 0) {
requestTransition(State.Playing); requestTransition(State.Playing);
} else { } else {
m_gameBar.setProgress((double)m_warmupCountdown / (double)30); updateMobBars();
broadcastMessage("Starting game in " + m_warmupCountdown); broadcastMessage("Starting game in " + m_warmupCountdown);
m_warmupCountdown--; m_warmupCountdown--;
m_countdownTask = m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> { m_countdownTask = m_plugin.getServer().getScheduler().runTaskLater(m_plugin, () -> {
@ -69,18 +88,21 @@ public class GameRunner {
} }
private void clearMobs() { private void clearMobs() {
for(Entity e : m_spawnedMobs) { for(Entity e : m_livingMobs) {
e.remove(); e.remove();
} }
m_spawnedMobs.clear(); m_livingMobs.clear();
m_createdMobs = 0;
m_killedMobs = 0;
} }
private boolean enterIdle() { private boolean enterIdle() {
broadcastMessage("Game state: Idle"); broadcastMessage("Game state: Idle");
m_currentWaveNum = 0; m_currentWaveNum = 0;
m_gameBar.setColor(BarColor.PURPLE); m_livingPlayers.clear();
m_gameBar.setProgress(1.0); for(Player p : m_players) {
m_gameBar.setTitle("Idle"); m_livingPlayers.add(p);
}
if (m_countdownTask != null) { if (m_countdownTask != null) {
m_countdownTask.cancel(); m_countdownTask.cancel();
m_countdownTask = null; m_countdownTask = null;
@ -90,83 +112,115 @@ public class GameRunner {
} }
private boolean enterWarmup() { private boolean enterWarmup() {
broadcastMessage("Game state: Warmup"); m_log.info("Game state: Warmup");
m_currentWaveNum += 1; m_currentWaveNum += 1;
m_currentWave = m_game.getWave(m_currentWaveNum); m_currentWave = m_game.getWave(m_currentWaveNum);
m_gameBar.setColor(BarColor.YELLOW); m_warmupCountdown = 10;
m_gameBar.setProgress(1.0);
m_gameBar.setTitle("Warmup");
m_warmupCountdown = 30;
for(Player p : m_players) { for(Player p : m_players) {
m_livingPlayers.add(p); m_livingPlayers.add(p);
} }
broadcastTitle("Warmup", "Prepare yourself for wave " + m_currentWaveNum);
clearMobs(); clearMobs();
countdownTick(); countdownTick();
return true; return true;
} }
private boolean enterPlaying() { private boolean enterPlaying() {
broadcastMessage("Game state: Playing"); m_log.info("Game state: Playing");
broadcastMessage("Starting wave " + m_currentWaveNum); m_log.info("Starting wave " + m_currentWaveNum);
m_currentBatch = 1; m_currentBatch = 1;
m_gameBar.setColor(BarColor.GREEN);
m_gameBar.setProgress(0.0);
m_gameBar.setTitle("Playing");
spawnNextBatch(); spawnNextBatch();
broadcastTitle("Wave " + m_currentWaveNum);
return true; return true;
} }
private boolean enterGameOver() { private boolean enterGameOver() {
broadcastMessage("Game state: Game Over!"); broadcastTitle("Game Over!");
m_gameBar.setColor(BarColor.RED);
m_gameBar.setProgress(1.0);
m_gameBar.setTitle("Game Over!");
if (m_countdownTask != null) { if (m_countdownTask != null) {
m_countdownTask.cancel(); m_countdownTask.cancel();
m_countdownTask = null; m_countdownTask = null;
} }
for(Player p : m_players) {
p.setGameMode(GameMode.ADVENTURE);
}
clearMobs(); clearMobs();
for(Player p : m_players) {
removePlayer(p);
}
return true; return true;
} }
private void updateMobBar() { private void updateMobBars() {
m_gameBar.setTitle("Mobs remaining: " + (m_currentWave.totalMobCount() - m_killedMobs)); m_gameBar.setVisible(true);
m_gameBar.setProgress(m_killedMobs / m_currentWave.totalMobCount()); 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() { private void spawnNextBatch() {
broadcastMessage("Spawning batch " + m_currentBatch); broadcastMessage("Spawning batch " + m_currentBatch);
Spawner spawner = new GameSpawner(m_arena.spawnpoints()[0]); Spawner spawner = new GameSpawner(m_arena.spawnpoints());
m_currentWave.spawnBatch(spawner, m_currentBatch); m_currentWave.spawnBatch(spawner, m_currentBatch);
updateMobBar(); updateMobBars();
} }
public void handlePlayerDeath(Player player) { public void handlePlayerDeath(Player player) {
if (m_livingPlayers.contains(player)) { if (m_livingPlayers.contains(player)) {
m_log.info("Player has died in game" + player);
m_livingPlayers.remove(player); m_livingPlayers.remove(player);
m_arena.getWorld().strikeLightningEffect(player.getLocation());
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.SPECTATOR);
if (m_livingPlayers.size() == 0) { if (m_livingPlayers.size() == 0) {
broadcastMessage("Everyone is dead :("); broadcastMessage("Everyone is dead :(");
requestTransition(State.GameOver); requestTransition(State.GameOver);
} else {
m_log.info("Remaining players " + m_livingPlayers.size());
} }
} }
} }
public void handleEntityDeath(Entity entity) { public void handleEntityDeath(Entity entity) {
if (m_spawnedMobs.contains(entity)) { if (m_livingMobs.contains(entity)) {
broadcastMessage("Killed game entity " + entity); broadcastMessage("Killed game entity " + entity);
m_spawnedMobs.remove(entity); m_livingMobs.remove(entity);
m_killedMobs += 1; m_killedMobs += 1;
updateMobBar(); updateMobBars();
if (m_spawnedMobs.size() == 0) { if (m_livingMobs.size() <= 3) {
broadcastMessage("Batch complete!"); m_log.info("Starting next batch!");
if (m_currentBatch >= m_currentWave.batchCount()) { if (m_currentBatch >= m_currentWave.batchCount()) {
if (m_currentWaveNum >= m_game.getWaveCount()) { if (m_currentWaveNum >= m_game.getWaveCount()) {
requestTransition(State.GameOver); requestTransition(State.GameOver);
} else { } else if (m_livingMobs.size() == 0) {
requestTransition(State.Warmup); requestTransition(State.Warmup);
} }
} else { } else {
@ -174,22 +228,31 @@ public class GameRunner {
spawnNextBatch(); spawnNextBatch();
} }
} else { } else {
broadcastMessage("Entities remaining: " + m_spawnedMobs.size()); m_log.fine("Living mobs remaining: " + m_livingMobs.size());
} }
} }
} }
private boolean syncPlayer(Player player) { private boolean syncPlayer(Player player) {
m_log.fine("Synchronizing player " + player);
World playerWorld = player.getLocation().getWorld(); World playerWorld = player.getLocation().getWorld();
World gameWorld = m_arena.getWorld(); World gameWorld = m_arena.getWorld();
m_gameBar.addPlayer(player); m_gameBar.addPlayer(player);
m_waveBar.addPlayer(player);
if (m_livingPlayers.contains(player)) { if (m_livingPlayers.contains(player)) {
player.setGameMode(GameMode.ADVENTURE); 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.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue());
player.setFoodLevel(10);
} else { } else {
m_log.fine("Player is dead, turning into spectator " + player);
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.SPECTATOR);
} }
if (playerWorld != gameWorld) { if (playerWorld != gameWorld) {
m_log.info("Teleporting player " + player);
return player.teleport(gameWorld.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); return player.teleport(gameWorld.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
} else { } else {
return false; return false;
@ -229,22 +292,27 @@ public class GameRunner {
public boolean requestTransition(State state) { public boolean requestTransition(State state) {
if (!validateTransition(m_state, state)) { if (!validateTransition(m_state, state)) {
m_log.warning("Attemped illegal transition: " + m_state + " -> " + state);
return false; return false;
} }
if (attemptTransition(state)) { if (attemptTransition(state)) {
updateMobBars();
m_log.info("Game transition: " + m_state + " -> " + state);
m_state = state; m_state = state;
for(Player p : m_players) { for(Player p : m_players) {
syncPlayer(p); syncPlayer(p);
} }
return true; return true;
} }
m_log.severe("Failed to complete transition: " + m_state + " -> " + state);
return false; return false;
} }
public boolean addPlayer(Player p) { public boolean addPlayer(Player p) {
if (m_state == State.Idle || m_state == State.Warmup) { if (m_state == State.Idle || m_state == State.Warmup) {
m_log.info("Adding player " + p);
m_players.add(p); m_players.add(p);
broadcastMessage("Added player " + p + " to game"); broadcastMessage(p.getName() + " has joined the game");
syncPlayer(p); syncPlayer(p);
if (m_state == State.Idle) { if (m_state == State.Idle) {
requestTransition(State.Warmup); requestTransition(State.Warmup);
@ -256,14 +324,27 @@ public class GameRunner {
} }
public void removePlayer(Player p) { public void removePlayer(Player p) {
m_log.info("Removing player " + p);
m_gameBar.removePlayer(p); m_gameBar.removePlayer(p);
m_waveBar.removePlayer(p);
m_players.remove(p); m_players.remove(p);
m_livingPlayers.remove(p); m_livingPlayers.remove(p);
p.setGameMode(Bukkit.getDefaultGameMode());
if (m_players.size() == 0) { if (m_players.size() == 0) {
requestTransition(State.Idle); 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) { void broadcastMessage(String string) {
World world = m_arena.getWorld(); World world = m_arena.getWorld();
for(Player p : world.getPlayers()) { for(Player p : world.getPlayers()) {
@ -272,22 +353,28 @@ public class GameRunner {
} }
void registerSpawnedMob(Entity entity) { void registerSpawnedMob(Entity entity) {
m_spawnedMobs.add(entity); m_log.fine("Registered new mob " + entity);
m_livingMobs.add(entity);
m_createdMobs += 1;
} }
private class GameSpawner implements Spawner { private class GameSpawner implements Spawner {
Spawnpoint m_spawnpoint; Spawnpoint[] m_spawnpoints;
int m_spawnIdx = 0;
public GameSpawner(Spawnpoint spawnpoint) { public GameSpawner(Spawnpoint[] spawnpoints) {
m_spawnpoint = spawnpoint; m_spawnpoints = spawnpoints;
} }
@Override @Override
public Entity spawnMob(EntityType type) { public Entity spawnMob(EntityType type) {
Entity newMob = m_arena.getWorld().spawnEntity(m_spawnpoint.getLocation(), 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; LivingEntity livingMob = (LivingEntity)newMob;
livingMob.setRemoveWhenFarAway(false); livingMob.setRemoveWhenFarAway(false);
registerSpawnedMob(newMob); registerSpawnedMob(newMob);
m_spawnIdx += 1;
m_spawnIdx %= m_spawnpoints.length;
return newMob; return newMob;
} }
} }

View File

@ -6,7 +6,9 @@ import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.plugin.PluginLogger;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -16,6 +18,7 @@ import gg.malloc.defense.model.Spawnpoint;
import gg.malloc.defense.model.Game; import gg.malloc.defense.model.Game;
import gg.malloc.defense.games.LinearGame; import gg.malloc.defense.games.LinearGame;
import gg.malloc.defense.games.ScaledWaves;
public class Plugin extends JavaPlugin { public class Plugin extends JavaPlugin {
ArrayList<Arena> m_arenas = new ArrayList<>(); ArrayList<Arena> m_arenas = new ArrayList<>();
@ -45,20 +48,18 @@ public class Plugin extends JavaPlugin {
} }
} }
PluginLogger m_log = new PluginLogger(this);
public void debuginfo() { public void debuginfo() {
m_log.info("Debug Info:"); getLogger().info("Debug Info:");
} }
@Override @Override
public void onEnable() { public void onEnable() {
getLogger().info("Malloc Defense registered"); getLogger().info("Malloc Defense registered");
setupTestGame(); getLogger().setLevel(Level.FINEST);
setupDemoGame();
getCommand("setstage").setExecutor(new SetStageCommand(this)); getCommand("setstage").setExecutor(new SetStageCommand(this));
getCommand("initgame").setExecutor(new InitGameCommand(this)); getCommand("initgame").setExecutor(new InitGameCommand(this));
getCommand("debuginfo").setExecutor(new DebuginfoCommand(this)); getCommand("debuginfo").setExecutor(new DebuginfoCommand(this));
getServer().getPluginManager().registerEvents(new GameEventHandler(this), this);
} }
public GameRunner getRunnerForWorld(World world) { public GameRunner getRunnerForWorld(World world) {
@ -68,15 +69,21 @@ public class Plugin extends JavaPlugin {
} else { } else {
ret = new GameRunner(this, m_games.get(0), m_arenas.get(0)); ret = new GameRunner(this, m_games.get(0), m_arenas.get(0));
m_runningGames.put(world, ret); m_runningGames.put(world, ret);
getServer().getPluginManager().registerEvents(new GameEventHandler(ret), this);
} }
return ret; return ret;
} }
void setupTestGame() { void setupDemoGame() {
getLogger().info("Setting up demo data'");
World testWorld = getServer().getWorld("world"); World testWorld = getServer().getWorld("world");
Spawnpoint[] spawnpoints = new Spawnpoint[1]; Spawnpoint[] spawnpoints = new Spawnpoint[4];
spawnpoints[0] = new TestSpawn(testWorld.getSpawnLocation()); Location spawnCenter = testWorld.getSpawnLocation();
for(int i = 0; i < 4; i++) {
Location spawnLocation = spawnCenter.add(Math.random() * 3, 0, Math.random() * 3);
spawnpoints[i] = new TestSpawn(spawnLocation);
}
m_arenas.add(new MemoryArena("Test Arena", testWorld, spawnpoints)); m_arenas.add(new MemoryArena("Test Arena", testWorld, spawnpoints));
m_games.add(new LinearGame()); m_games.add(new ScaledWaves());
} }
} }

View File

@ -17,15 +17,6 @@ public class LinearGame implements Game {
m_zombiesPerBatch = zombiesPerBatch; 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 @Override
public int batchCount() { public int batchCount() {
return m_batches; return m_batches;

View File

@ -0,0 +1,79 @@
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;
import java.util.HashMap;
public class ScaledWaves implements Game {
@Override
public int getWaveCount() {
return 10;
}
@Override
public Wave getWave(int waveNumber) {
HashMap<EntityType, Double> weights = new HashMap<>();
if (waveNumber <= 3) {
weights.put(EntityType.ZOMBIE, 1.0);
} else if (waveNumber <= 5) {
weights.put(EntityType.ZOMBIE, 0.6);
weights.put(EntityType.SPIDER, 0.1);
weights.put(EntityType.SKELETON, 0.3);
} else if (waveNumber <= 7) {
weights.put(EntityType.ZOMBIE, 0.7);
weights.put(EntityType.SKELETON, 0.3);
} else {
weights.put(EntityType.PILLAGER, 0.2);
weights.put(EntityType.ZOMBIE, 0.8);
}
boolean hasRavager = waveNumber >= 9;
return new MobWave(weights, waveNumber * 3, 3, hasRavager);
}
private class MobWave implements Wave {
HashMap<EntityType, Double> m_spawnWeights;
int m_batches;
int m_mobsPerBatch;
boolean m_hasRavager;
MobWave(HashMap<EntityType, Double> weights, int totalCount, int batches, boolean hasRavager) {
m_batches = batches;
m_mobsPerBatch = Math.max(1, totalCount / batches);
m_spawnWeights = weights;
m_hasRavager = hasRavager;
}
@Override
public int batchCount() {
return m_batches;
}
@Override
public void spawnBatch(Spawner spawner, int batch) {
assert(m_mobsPerBatch > 0);
for(int i = 0; i < m_mobsPerBatch; i++) {
EntityType selectedType = null;
double random = Math.random() * 1.0;
for(EntityType type : m_spawnWeights.keySet()) {
random -= m_spawnWeights.get(type);
if (random <= 0.0d) {
selectedType = type;
break;
}
}
assert(selectedType != null);
Entity newMob = spawner.spawnMob(selectedType);
newMob.setCustomName("Mob " + i + "/" + m_mobsPerBatch);
}
if (m_hasRavager) {
Entity newMob = spawner.spawnMob(EntityType.RAVAGER);
newMob.setCustomName("RAVAGER");
}
}
}
}

View File

@ -1,6 +1,8 @@
package gg.malloc.defense.model; package gg.malloc.defense.model;
import org.bukkit.entity.Entity;
public interface Game { public interface Game {
int getWaveCount(); int getWaveCount();
Wave getWave(int waveNumber); Wave getWave(int waveNumber);
default void onMobDamaged(Entity entity) {}
} }

View File

@ -2,6 +2,5 @@ package gg.malloc.defense.model;
public interface Wave { public interface Wave {
int batchCount(); int batchCount();
int totalMobCount();
void spawnBatch(Spawner spawner, int batch); void spawnBatch(Spawner spawner, int batch);
} }