diff --git a/TODO.md b/TODO.md index ffb1edd..77fb4eb 100644 --- a/TODO.md +++ b/TODO.md @@ -12,18 +12,22 @@ [X] Config reload [X] Leave games [X] Join games -[ ] Lobby with instructions -[ ] Drop back to lobby on game over -[ ] Grist drops -[ ] Item shoppes +[X] Drop back to lobby on game over +[X] Grist drops +[ ] Chat colors + clickables +[ ] Title colors [ ] Command tab completion +[ ] Lobby with instructions +[ ] Item shoppes +[ ] Mob category AI +[ ] Indestructible weapons/armor +[ ] Never hungry # Scaled waves [ ] Limit ranged mobs and Ravagers to non-bomb-carrier state [ ] Weaker mobs, more of them -[ ] Mob categories -[ ] Spawnpoint categories +[ ] Mob AI categories # UX @@ -32,17 +36,19 @@ [X] Stage titles [X] EXPLOSIONS [X] Bomb model -[ ] Pretty bomb model -[ ] "Player $X is ready" message in chat -[ ] Colored titles +[X] "Player $X is ready" message in chat +[X] Clickable /ready in chat +[ ] Post-Round summary in chat [ ] Clickable join links in /list +[ ] Sidebar +[ ] Coin pickup messages in action bar +[ ] Target catches on fire more it is lit +[ ] Colored titles [ ] Clickable /leave action -[ ] Clickable /ready in chat [ ] Countdown while in warmup [ ] Countdown shrinks w/ every /ready player -[ ] Target catches on fire more it is lit -[ ] Sidebar [ ] List of mobs in next wave +[ ] Pretty bomb model # Social @@ -71,6 +77,7 @@ [X] Randomized spawn locations [X] Weighted distributions [X] Batch overlap +[ ] Spawnpoint categories [ ] Scripted batch overlap/timings [ ] Scripted spawn locations [ ] Scripted waypoint paths @@ -87,26 +94,29 @@ [X] /join games [X] /ready [X] /leave games -[ ] /restart games [X] Spectator mode on death [X] Player readiness starts countdown -[ ] Game is automatically closed some time after game over -[ ] Return to lobby on leave/close +[X] Game is automatically closed some time after game over +[X] Return to lobby on leave/close +[X] Restore health+hunger on respawn/game start +[ ] /restart games [ ] Instancing -[ ] Restore health+hunger on respawn/game start [ ] Respawn during games [ ] Player revival items +[ ] Clear inventory on join/leave # Powerups +[ ] Enchantments +[ ] Better items [ ] Coin pickup range [ ] Coin boost [ ] Knockback on weapons [ ] Damage boost [ ] Speed boost [ ] Health boost -[ ] Repair barriers # Fantasy [ ] Totems/turrets/stationary weapons +[ ] Repair barriers diff --git a/pom.xml b/pom.xml index ba4740b..6372dd8 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,12 @@ 1.18.2-R0.1-SNAPSHOT provided + + de.tr7zw + item-nbt-api-plugin + 2.9.2 + provided + diff --git a/src/main/java/gg/malloc/defense/Plugin.java b/src/main/java/gg/malloc/defense/Plugin.java index 20b4657..6277363 100644 --- a/src/main/java/gg/malloc/defense/Plugin.java +++ b/src/main/java/gg/malloc/defense/Plugin.java @@ -9,9 +9,10 @@ import org.bukkit.entity.Player; import org.bukkit.World; import org.bukkit.WorldCreator; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerQuitEvent; import java.util.logging.Logger; import java.util.logging.Level; @@ -76,6 +77,21 @@ public class Plugin extends JavaPlugin { getServer().getPluginManager().registerEvents(m_handler, this); } + public void onDisable() { + getLogger().info("Unloading games..."); + for(GameRunner runner : m_runningGames.values()) { + runner.requestTransition(GameRunner.Stage.Idle); + } + for(Player player : m_playerGames.keySet()) { + m_playerGames.get(player).removePlayer(player); + returnPlayerToLobby(player); + } + m_runningGames.clear(); + m_playerGames.clear(); + m_handler.clear(); + m_arenas.clear(); + } + public void reloadArenas() { getLogger().info("Loading arenas..."); saveDefaultConfig(); @@ -87,6 +103,7 @@ public class Plugin extends JavaPlugin { } for(Player player : m_playerGames.keySet()) { m_playerGames.get(player).removePlayer(player); + returnPlayerToLobby(player); } m_runningGames.clear(); m_playerGames.clear(); @@ -133,6 +150,15 @@ public class Plugin extends JavaPlugin { return ret; } + public void returnPlayerToLobby(Player p) { + Configuration config = new Configuration(getConfig()); + World lobby = getServer().getWorld(config.getLobbyName()); + if (lobby == null) { + lobby = new WorldCreator(config.getLobbyName()).generateStructures(false).createWorld(); + } + p.teleport(lobby.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); + } + public GameRunner getRunnerForPlayer(Player p) { GameRunner ret = null; if (m_playerGames.containsKey(p)) { diff --git a/src/main/java/gg/malloc/defense/commands/JoinGameCommand.java b/src/main/java/gg/malloc/defense/commands/JoinGameCommand.java index 588b1c9..2ed0c2b 100644 --- a/src/main/java/gg/malloc/defense/commands/JoinGameCommand.java +++ b/src/main/java/gg/malloc/defense/commands/JoinGameCommand.java @@ -49,7 +49,7 @@ public class JoinGameCommand implements TabExecutor { } m_plugin.addPlayerToArena(arenaName, player); } else { - sender.sendMessage("Only players may use htis command."); + sender.sendMessage("Only players may use this command."); } return true; } diff --git a/src/main/java/gg/malloc/defense/commands/LeaveGameCommand.java b/src/main/java/gg/malloc/defense/commands/LeaveGameCommand.java index 139757e..17934ce 100644 --- a/src/main/java/gg/malloc/defense/commands/LeaveGameCommand.java +++ b/src/main/java/gg/malloc/defense/commands/LeaveGameCommand.java @@ -26,6 +26,7 @@ public class LeaveGameCommand implements CommandExecutor { return true; } runner.removePlayer(player); + m_plugin.returnPlayerToLobby(player); } else { sender.sendMessage("Only players may use htis command."); } diff --git a/src/main/java/gg/malloc/defense/config/Configuration.java b/src/main/java/gg/malloc/defense/config/Configuration.java index 50adbed..aeea8b7 100644 --- a/src/main/java/gg/malloc/defense/config/Configuration.java +++ b/src/main/java/gg/malloc/defense/config/Configuration.java @@ -18,6 +18,11 @@ public class Configuration { m_root = rootConfig; } + public String getLobbyName() { + ConfigurationSection lobbyConf = m_root.getConfigurationSection("lobby"); + return lobbyConf.getString("world"); + } + public Collection getMapNames() { ConfigurationSection mapList = m_root.getConfigurationSection("maps"); return mapList.getKeys(false); diff --git a/src/main/java/gg/malloc/defense/engine/GameRunner.java b/src/main/java/gg/malloc/defense/engine/GameRunner.java index 0aac99a..508a228 100644 --- a/src/main/java/gg/malloc/defense/engine/GameRunner.java +++ b/src/main/java/gg/malloc/defense/engine/GameRunner.java @@ -5,8 +5,14 @@ import gg.malloc.defense.model.Game; import gg.malloc.defense.model.Spawner; import gg.malloc.defense.model.Waypoint; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ComponentBuilder; + import gg.malloc.defense.ui.BombCarrier; import gg.malloc.defense.ui.BossBars; +import gg.malloc.defense.ui.Items; import gg.malloc.defense.Plugin; @@ -31,6 +37,7 @@ import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeModifier; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; public class GameRunner { Arena m_arena; @@ -56,6 +63,7 @@ public class GameRunner { TickTask m_fuseTask; TickTask m_countdownTask; + TickTask m_lobbyReturnTask; TickTask m_bombSmokeTask; TickTask m_bombCrackleTask; @@ -91,14 +99,36 @@ public class GameRunner { if (m_warmupCountdown == 0) { requestTransition(Stage.Playing); } else { - m_bars.update(); - broadcastMessage("Starting game in " + m_warmupCountdown); + BaseComponent[] message = new ComponentBuilder() + .append("Starting game in ").color(ChatColor.LIGHT_PURPLE) + .append("" + m_warmupCountdown).color(ChatColor.AQUA).bold(true) + .create(); + broadcastMessage(message); m_warmupCountdown--; m_bars.setCountdownProgress((double)m_warmupCountdown / 10.0); m_bars.update(); } }, 20); + m_lobbyReturnTask = new TickTask(m_plugin, () -> { + if (m_gameEndCountdown == 0) { + for(Player p : new ArrayList(m_players.getPlayers())) { + removePlayer(p); + m_plugin.returnPlayerToLobby(p); + } + requestTransition(Stage.Idle); + } else { + BaseComponent[] message = new ComponentBuilder() + .append("Game is ending in ").color(ChatColor.LIGHT_PURPLE) + .append("" + m_gameEndCountdown).color(ChatColor.RED).bold(true) + .create(); + broadcastMessage(message); + m_gameEndCountdown--; + m_bars.setCountdownProgress((double)m_gameEndCountdown / 30.0); + m_bars.update(); + } + }, 20); + m_bombSmokeTask = new TickTask(m_plugin, () -> { Location targetLoc = getLocation(m_arena.bombTarget()); m_world.spawnParticle(Particle.SMOKE_LARGE, targetLoc, 35, 4, 2, 4); @@ -111,6 +141,7 @@ public class GameRunner { } int m_warmupCountdown = 0; + int m_gameEndCountdown = 0; public void handleEntityRetargeting(EntityTargetEvent evt) { m_mobs.handleEntityRetarget(evt); @@ -129,12 +160,23 @@ public class GameRunner { m_world.strikeLightningEffect(getLocation(m_arena.bombTarget())); m_world.playSound(getLocation(m_arena.bombTarget()), Sound.ENTITY_GENERIC_EXPLODE, SoundCategory.NEUTRAL, 1.3f, 1.0f); m_world.spawnParticle(Particle.EXPLOSION_HUGE, getLocation(m_arena.bombTarget()), 8, 5, 2, 5); + BaseComponent[] message = new ComponentBuilder() + .append("The bomb blew up!").color(ChatColor.RED).bold(true) + .create(); + broadcastMessage(message); requestTransition(Stage.GameOver); m_bombSmokeTask.start(); m_bombCrackleTask.start(); } } else if (evt.getEntity() instanceof Player) { Player player = (Player)evt.getEntity(); + if (evt instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt; + if (entityEvt.getDamager() instanceof Player) { + evt.setCancelled(true); + return; + } + } if (m_players.contains(player) && player.getHealth() - evt.getFinalDamage() <= 0) { evt.setCancelled(true); handlePlayerDeath(player); @@ -151,6 +193,7 @@ public class GameRunner { m_players.requestTransitionForAll(PlayerManager.State.Idle); m_fuseTask.stop(); m_countdownTask.stop(); + m_lobbyReturnTask.stop(); return true; } @@ -162,7 +205,13 @@ public class GameRunner { } m_players.setReady(p, false); } - broadcastTitle("Warmup", "Prepare yourself for wave " + m_waves.currentWaveNum()); + BaseComponent[] message = new ComponentBuilder() + .append("Click").color(ChatColor.LIGHT_PURPLE) + .append("[Here]").color(ChatColor.GOLD) + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/malloc-defense:ready")) + .append(" when ready.").color(ChatColor.LIGHT_PURPLE) + .create(); + broadcastMessage(message); m_mobs.clear(); return true; } @@ -171,8 +220,24 @@ public class GameRunner { setPlayerReady(p, !m_players.isReady(p)); } + void clearReadyState() { + for(Player p : m_players.getPlayers()) { + setPlayerReady(p, false); + } + } + public void setPlayerReady(Player p, boolean isReady) { - m_players.setReady(p, isReady); + if (isReady != m_players.isReady(p)) { + if (isReady) { + BaseComponent[] message = new ComponentBuilder() + .append(p.getName()).bold(true) + .append(" is ready!").color(ChatColor.AQUA).italic(true) + .create(); + } + m_players.setReady(p, isReady); + } + m_bars.update(); + broadcastMessage(message); if (m_players.isEveryoneReady()) { requestTransition(Stage.Countdown); } @@ -197,19 +262,15 @@ public class GameRunner { m_countdownTask.stop(); m_fuseTask.start(); spawnNextBatch(); - broadcastTitle("Wave " + m_waves.currentWaveNum()); return true; } private boolean enterGameOver() { - broadcastTitle("Game Over!"); - broadcastMessage("The bomb blew up!"); + m_gameEndCountdown = 60; + m_lobbyReturnTask.start(); m_countdownTask.stop(); m_fuseTask.stop(); m_mobs.clear(); - for(Player p : new ArrayList(m_players.getPlayers())) { - removePlayer(p); - } return true; } @@ -218,7 +279,7 @@ public class GameRunner { } private void spawnNextBatch() { - broadcastMessage("Spawning batch " + m_waves.currentBatchNum()); + m_log.info("Spawning batch " + m_waves.currentBatchNum()); Spawner spawner = new GameSpawner(m_world, m_arena, m_mobs, m_players); m_waves.currentWave().spawnBatch(spawner, m_waves.currentBatchNum()); m_bars.update(); @@ -228,7 +289,12 @@ public class GameRunner { if (m_players.requestTransition(player, PlayerManager.State.Dead)) { m_world.strikeLightningEffect(player.getLocation()); if (!m_players.isAnyoneAlive()) { - broadcastMessage("Everyone is dead :("); + BaseComponent[] message = new ComponentBuilder() + .append("Everyone is ").color(ChatColor.LIGHT_PURPLE) + .append("DEAD").color(ChatColor.RED).bold(true) + .append(" :(").color(ChatColor.LIGHT_PURPLE) + .create(); + broadcastMessage(message); requestTransition(Stage.GameOver); } else { m_log.info("Remaining players " + m_players.remainingPlayers()); @@ -239,11 +305,23 @@ public class GameRunner { public void handleEntityDeath(Entity entity) { boolean wasCarrier = m_mobs.isBombCarrier(entity); if (m_mobs.killMob(entity)) { + int coinsToDrop = 300; + while(coinsToDrop > 0) { + int droppedCoins = Math.min(coinsToDrop, 64); + ItemStack coins = Items.makeCoins(); + coins.setAmount(droppedCoins); + coinsToDrop -= 64; + m_world.dropItem(entity.getLocation(), coins); + } m_bars.update(); if (m_mobs.remainingMobs() <= 3) { m_log.info("Starting next batch!"); if (m_waves.isLastBatch()) { if (m_waves.isLastWave()) { + BaseComponent[] message = new ComponentBuilder() + .append("You Won!").color(ChatColor.LIGHT_PURPLE).bold(true) + .create(); + broadcastMessage(message); requestTransition(Stage.GameOver); } else if (m_mobs.empty()) { requestTransition(Stage.Warmup); @@ -262,7 +340,6 @@ public class GameRunner { if (m_stage == stage) { return false; } - m_log.info("Game state: " + stage); switch(stage) { case Idle: return enterIdle(); @@ -303,12 +380,28 @@ public class GameRunner { m_log.info("Game transition: " + m_stage + " -> " + stage); m_stage = stage; m_bars.update(); + for(Player p : m_players.getPlayers()) { + sendStageTitle(p); + } return true; } m_log.severe("Failed to complete transition: " + m_stage + " -> " + stage); return false; } + void sendStageTitle(Player p) { + switch(m_stage) { + case Warmup: + p.sendTitle("Warmup", "Prepare yourself for wave " + m_waves.currentWaveNum(), 10, 70, 20); + break; + case Playing: + p.sendTitle("Wave " + m_waves.currentWaveNum(), "", 10, 70, 20); + break; + case GameOver: + p.sendTitle("Game over!", "", 10, 70, 20); + } + } + public void addPlayer(Player p) { if (m_players.contains(p)) { return; @@ -319,7 +412,12 @@ public class GameRunner { if (m_players.requestTransition(p, PlayerManager.State.Playing)) { p.teleport(m_world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); } - broadcastMessage(p.getName() + " has joined the game"); + BaseComponent[] message = new ComponentBuilder() + .append(p.getName()).bold(true) + .append("Has joined the game").color(ChatColor.AQUA) + .create(); + broadcastMessage(message); + sendStageTitle(p); if (m_stage == Stage.Idle) { requestTransition(Stage.Warmup); } @@ -331,6 +429,12 @@ public class GameRunner { m_players.removePlayer(p); if (m_players.isEmpty()) { requestTransition(Stage.Idle); + } else { + BaseComponent[] message = new ComponentBuilder() + .append(p.getName()).bold(true) + .append("Has left the game").color(ChatColor.AQUA) + .create(); + broadcastMessage(message); } } @@ -344,9 +448,9 @@ public class GameRunner { } } - void broadcastMessage(String string) { + void broadcastMessage(BaseComponent[] component) { for(Player p : m_world.getPlayers()) { - p.sendMessage(string); + p.spigot().sendMessage(component); } } } diff --git a/src/main/java/gg/malloc/defense/engine/PlayerManager.java b/src/main/java/gg/malloc/defense/engine/PlayerManager.java index 37058ba..35bd426 100644 --- a/src/main/java/gg/malloc/defense/engine/PlayerManager.java +++ b/src/main/java/gg/malloc/defense/engine/PlayerManager.java @@ -62,7 +62,7 @@ public class PlayerManager { //m_log.fine("Respawning player " + player); player.setGameMode(Bukkit.getDefaultGameMode()); player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); - player.setFoodLevel(10); + player.setFoodLevel(20); return true; } @@ -109,6 +109,7 @@ public class PlayerManager { //m_log.info("Removing player " + player); requestTransition(player, State.Idle); m_playerStates.remove(player); + m_playerReadyStates.remove(player); return true; } diff --git a/src/main/java/gg/malloc/defense/ui/BombCarrier.java b/src/main/java/gg/malloc/defense/ui/BombCarrier.java index 5b9dbe7..822177e 100644 --- a/src/main/java/gg/malloc/defense/ui/BombCarrier.java +++ b/src/main/java/gg/malloc/defense/ui/BombCarrier.java @@ -15,23 +15,15 @@ public class BombCarrier { public LivingEntity inHand() { EntityEquipment equipment = m_entity.getEquipment(); - equipment.setItemInOffHand(makeBombHelmet()); + equipment.setItemInOffHand(Items.makeBombHelmet()); //equipment.setItemInOffHandDropChance(0.0f); return m_entity; } public LivingEntity onHead() { EntityEquipment equipment = m_entity.getEquipment(); - equipment.setHelmet(makeBombHelmet()); + equipment.setHelmet(Items.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 index f4c3642..1306009 100644 --- a/src/main/java/gg/malloc/defense/ui/BossBars.java +++ b/src/main/java/gg/malloc/defense/ui/BossBars.java @@ -108,7 +108,10 @@ public class BossBars { m_gameBar.setColor(BarColor.RED); m_gameBar.setProgress(1.0); m_gameBar.setTitle("Game Over!"); - m_waveBar.setVisible(false); + m_waveBar.setVisible(true); + m_waveBar.setColor(BarColor.BLUE); + m_waveBar.setTitle("Returning to lobby..."); + m_waveBar.setProgress(m_countdownProgress); 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 index 3de3af4..c169093 100644 --- a/src/main/java/gg/malloc/defense/ui/Items.java +++ b/src/main/java/gg/malloc/defense/ui/Items.java @@ -1,14 +1,29 @@ -package gg.malloc.defense.engine; +package gg.malloc.defense.ui; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.Material; +import de.tr7zw.nbtapi.NBTItem; +import de.tr7zw.nbtapi.NBTCompound; + public class Items { - static ItemStack makeBombHelmet() { + public static ItemStack makeCoins() { + ItemStack coinItem = new ItemStack(Material.IRON_NUGGET); + ItemMeta meta = coinItem.getItemMeta(); + meta.setCustomModelData(93197); + coinItem.setItemMeta(meta); + + NBTItem nbt = new NBTItem(coinItem); + nbt.addCompound("malloc").setInteger("coinValue", 1); + + return nbt.getItem(); + } + + public static ItemStack makeBombHelmet() { ItemStack bombItem = new ItemStack(Material.CARVED_PUMPKIN); ItemMeta meta = bombItem.getItemMeta(); - meta.setCustomModelData(0); + meta.setCustomModelData(33197); bombItem.setItemMeta(meta); return bombItem; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5acdce0..b6178eb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,5 @@ +lobby: + world: world maps: quarry: target: