Colors, basic mob AI categories, end world hunger, UX buffs

This commit is contained in:
Torrie Fischer 2022-05-15 18:16:35 +02:00
parent 9890936154
commit 99197d92bb
13 changed files with 275 additions and 103 deletions

42
TODO.md
View File

@ -14,20 +14,32 @@
[X] Join games [X] Join games
[X] Drop back to lobby on game over [X] Drop back to lobby on game over
[X] Grist drops [X] Grist drops
[ ] Chat colors + clickables [X] Chat colors + clickables
[ ] Title colors [X] Title colors
[ ] Command tab completion [X] Command tab completion
[X] Mob AI categories
[X] Never hungry
[X] Mobs don't drop bomb items
# QOL
[ ] Players can't pick up bomb items
[ ] "Get ready" nag
[ ] "Click here to leave" at end of game
[ ] Hidden armor stands
[ ] Small/nonexistent prop collision boxes
# Malloc beta map
[ ] Lobby with instructions [ ] Lobby with instructions
[ ] Item shoppes [X] Item shoppes
[ ] Mob category AI
[ ] Indestructible weapons/armor [ ] Indestructible weapons/armor
[ ] Never hungry
# Scaled waves # Scaled waves
[ ] Limit ranged mobs and Ravagers to non-bomb-carrier state [X] Limit ranged mobs and Ravagers to non-bomb-carrier state
[ ] Weaker mobs, more of them [X] Weaker mobs, more of them
[ ] Mob AI categories [X] Mob AI categories
# UX # UX
@ -39,7 +51,7 @@
[X] "Player $X is ready" message in chat [X] "Player $X is ready" message in chat
[X] Clickable /ready in chat [X] Clickable /ready in chat
[ ] Post-Round summary in chat [ ] Post-Round summary in chat
[ ] Clickable join links in /list [X] Clickable join links in /list
[ ] Sidebar [ ] Sidebar
[ ] Coin pickup messages in action bar [ ] Coin pickup messages in action bar
[ ] Target catches on fire more it is lit [ ] Target catches on fire more it is lit
@ -64,9 +76,9 @@
[X] Mob tracking should prioritize bomb [X] Mob tracking should prioritize bomb
[X] Mobs recover dropped bombs [X] Mobs recover dropped bombs
[X] Bomb carriers are slower [X] Bomb carriers are slower
[ ] Coin drops [X] Coin drops
[ ] Mob categories [X] Mob categories
[ ] Mobs split between bomb and player priorities [X] Mobs split between bomb and player priorities
[ ] Bonus coins for complete coin pickup [ ] Bonus coins for complete coin pickup
[ ] Infinite weapons + armor [ ] Infinite weapons + armor
[ ] Ammo/health spawns [ ] Ammo/health spawns
@ -77,7 +89,7 @@
[X] Randomized spawn locations [X] Randomized spawn locations
[X] Weighted distributions [X] Weighted distributions
[X] Batch overlap [X] Batch overlap
[ ] Spawnpoint categories [X] Spawnpoint categories
[ ] Scripted batch overlap/timings [ ] Scripted batch overlap/timings
[ ] Scripted spawn locations [ ] Scripted spawn locations
[ ] Scripted waypoint paths [ ] Scripted waypoint paths
@ -103,7 +115,7 @@
[ ] Instancing [ ] Instancing
[ ] Respawn during games [ ] Respawn during games
[ ] Player revival items [ ] Player revival items
[ ] Clear inventory on join/leave [X] Clear inventory on join/leave
# Powerups # Powerups

View File

@ -4,6 +4,7 @@ import gg.malloc.defense.engine.GameRunner;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.ArrayList;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -11,7 +12,10 @@ import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityCombustEvent; import org.bukkit.event.entity.EntityCombustEvent;
import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.LootGenerateEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class GameEventHandler implements Listener { public class GameEventHandler implements Listener {
@ -61,4 +65,17 @@ public class GameEventHandler implements Listener {
r.handleEntityDamage(evt); r.handleEntityDamage(evt);
} }
} }
@EventHandler
public void onLootGenerated(LootGenerateEvent evt) {
if (evt.getEntity() != null) {
ArrayList<ItemStack> emptyLoot = new ArrayList<>();
evt.setLoot(emptyLoot);
}
}
@EventHandler
public void onFoodLevelChange(FoodLevelChangeEvent evt) {
evt.setCancelled(true);
}
} }

View File

@ -24,10 +24,14 @@ public class JoinGameCommand implements TabExecutor {
if (args.length == 1) { if (args.length == 1) {
String proposal = args[0].toLowerCase(); String proposal = args[0].toLowerCase();
for(String arena : m_plugin.arenaNames()) { for(String arena : m_plugin.arenaNames()) {
if (proposal.startsWith(arena.toLowerCase())) { if (arena.toLowerCase().startsWith(proposal.toLowerCase())) {
ret.add(arena); ret.add(arena);
} }
} }
} else if (args.length == 0) {
for(String arena : m_plugin.arenaNames()) {
ret.add(arena);
}
} }
return ret; return ret;
} }

View File

@ -22,13 +22,13 @@ public class LeaveGameCommand implements CommandExecutor {
Player player = (Player)sender; Player player = (Player)sender;
GameRunner runner = m_plugin.getRunnerForPlayer(player); GameRunner runner = m_plugin.getRunnerForPlayer(player);
if (runner == null) { if (runner == null) {
sender.sendMessage("You ae not currently in a game."); sender.sendMessage("You are not currently in a game.");
return true; return true;
} }
runner.removePlayer(player); runner.removePlayer(player);
m_plugin.returnPlayerToLobby(player); m_plugin.returnPlayerToLobby(player);
} else { } else {
sender.sendMessage("Only players may use htis command."); sender.sendMessage("Only players may use this command.");
} }
return true; return true;
} }

View File

@ -5,6 +5,11 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
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.engine.GameRunner; import gg.malloc.defense.engine.GameRunner;
import gg.malloc.defense.Plugin; import gg.malloc.defense.Plugin;
@ -20,14 +25,28 @@ public class ListGamesCommand implements CommandExecutor {
public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { public boolean onCommand(CommandSender sender, Command command, String s, String[] args) {
sender.sendMessage("Available games:"); sender.sendMessage("Available games:");
for(String arenaName : m_plugin.arenaNames()) { for(String arenaName : m_plugin.arenaNames()) {
String arenaDescription = arenaName + ": "; String stageName = "Ready to play";
ChatColor stageColor = ChatColor.WHITE;
if (m_plugin.hasRunnerForArenaName(arenaName)) { if (m_plugin.hasRunnerForArenaName(arenaName)) {
GameRunner runner = m_plugin.getRunnerForArenaName(arenaName); GameRunner runner = m_plugin.getRunnerForArenaName(arenaName);
arenaDescription += runner.getStage().toString(); switch(runner.getStage()) {
} else { case Idle: stageColor = ChatColor.WHITE;stageName = "Open";break;
arenaDescription += "Loadable"; case Warmup: stageColor = ChatColor.YELLOW;stageName = "" + runner.getPlayers().size() + " players warming up";break;
case Countdown: stageColor = ChatColor.YELLOW;stageName = "" + runner.getPlayers().size() + " players warming up";break;
case Playing: stageColor = ChatColor.GOLD;stageName = "" + runner.getPlayers().size() + " players";break;
case GameOver: stageColor = ChatColor.RED;stageName = "Cleaning up...";break;
} }
sender.sendMessage(arenaDescription); }
BaseComponent[] message = new ComponentBuilder()
.append(" " + arenaName).color(ChatColor.LIGHT_PURPLE).bold(true)
.append(" - ").color(ChatColor.WHITE)
.append(stageName).color(stageColor).italic(true)
.append(" [Join] ").color(ChatColor.GOLD).bold(true)
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/malloc-defense:join " + arenaName))
.create();
sender.spigot().sendMessage(message);
} }
return true; return true;
} }

View File

@ -22,20 +22,24 @@ public class SetStageCommand implements TabExecutor {
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
ArrayList<String> ret = new ArrayList<>(); ArrayList<String> ret = new ArrayList<>();
if (args.length == 1) { if (args.length == 1) {
String proposal = args[0]; String proposal = args[0].toLowerCase();
for(String arena : m_plugin.arenaNames()) { GameRunner.Stage stages[] = GameRunner.Stage.Idle.getDeclaringClass().getEnumConstants();
if (proposal.startsWith(arena)) { for(GameRunner.Stage stage : stages) {
ret.add(arena); if (stage.toString().toLowerCase().startsWith(proposal)) {
ret.add(stage.toString());
} }
} }
} else if (args.length == 2) { } else if (args.length == 2) {
String proposal = args[1].toLowerCase(); String proposal = args[1].toLowerCase();
GameRunner.Stage stages[] = GameRunner.Stage.Idle.getDeclaringClass().getEnumConstants(); for(String arena : m_plugin.arenaNames()) {
for(GameRunner.Stage stage : stages) { if (arena.toLowerCase().startsWith(proposal)) {
if (proposal.startsWith(stage.toString().toLowerCase())) { ret.add(arena);
ret.add(stage.toString());
} }
} }
} else if (args.length == 0) {
for(String arena : m_plugin.arenaNames()) {
ret.add(arena);
}
} }
return ret; return ret;
} }

View File

@ -19,9 +19,12 @@ import gg.malloc.defense.Plugin;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Collection;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.SoundCategory; import org.bukkit.SoundCategory;
@ -32,11 +35,11 @@ import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; 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.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class GameRunner { public class GameRunner {
@ -75,6 +78,24 @@ public class GameRunner {
return new Location(m_world, waypoint.getX(), waypoint.getY(), waypoint.getZ()); return new Location(m_world, waypoint.getX(), waypoint.getY(), waypoint.getZ());
} }
void validateGameRule(GameRule rule, boolean value) {
if (!m_world.getGameRuleValue(rule).equals(value)) {
m_log.warning("Game rule " + rule + " is not " + value);
}
}
void validateGameRules() {
validateGameRule(GameRule.DO_MOB_LOOT, false);
validateGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false);
validateGameRule(GameRule.DISABLE_RAIDS, true);
validateGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
validateGameRule(GameRule.DO_ENTITY_DROPS, false);
validateGameRule(GameRule.DO_FIRE_TICK, false);
validateGameRule(GameRule.DO_INSOMNIA, false);
validateGameRule(GameRule.DO_WEATHER_CYCLE, false);
validateGameRule(GameRule.SPECTATORS_GENERATE_CHUNKS, false);
}
public GameRunner(Plugin plugin, Game game, Arena arena, World world) { public GameRunner(Plugin plugin, Game game, Arena arena, World world) {
m_plugin = plugin; m_plugin = plugin;
m_game = game; m_game = game;
@ -124,15 +145,15 @@ public class GameRunner {
.create(); .create();
broadcastMessage(message); broadcastMessage(message);
m_gameEndCountdown--; m_gameEndCountdown--;
m_bars.setCountdownProgress((double)m_gameEndCountdown / 30.0); m_bars.setCountdownProgress((double)m_gameEndCountdown / 60.0);
m_bars.update(); m_bars.update();
} }
}, 20); }, 20);
m_bombSmokeTask = new TickTask(m_plugin, () -> { m_bombSmokeTask = new TickTask(m_plugin, () -> {
Location targetLoc = getLocation(m_arena.bombTarget()); Location targetLoc = getLocation(m_arena.bombTarget());
m_world.spawnParticle(Particle.SMOKE_LARGE, targetLoc, 35, 4, 2, 4); m_world.spawnParticle(Particle.SMOKE_LARGE, targetLoc, 135, 4, 2, 4, 1);
m_world.spawnParticle(Particle.SMALL_FLAME, targetLoc, 30, 3, 2, 3); m_world.spawnParticle(Particle.SMALL_FLAME, targetLoc, 130, 3, 2, 3, 1);
}, 5); }, 5);
m_bombCrackleTask = new TickTask(m_plugin, () -> { m_bombCrackleTask = new TickTask(m_plugin, () -> {
Location targetLoc = getLocation(m_arena.bombTarget()); Location targetLoc = getLocation(m_arena.bombTarget());
@ -149,7 +170,7 @@ public class GameRunner {
public void handleEntityDamage(EntityDamageEvent evt) { public void handleEntityDamage(EntityDamageEvent evt) {
m_mobs.handleEntityDamage(evt); m_mobs.handleEntityDamage(evt);
if (m_mobs.bombWasHit()) { if (m_mobs.bombWasHit() && !m_bombFuse.isExploded()) {
EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt; EntityDamageByEntityEvent entityEvt = (EntityDamageByEntityEvent)evt;
m_log.info("Target attacked!"); m_log.info("Target attacked!");
entityEvt.getDamager().setGlowing(true); entityEvt.getDamager().setGlowing(true);
@ -175,9 +196,14 @@ public class GameRunner {
if (entityEvt.getDamager() instanceof Player) { if (entityEvt.getDamager() instanceof Player) {
evt.setCancelled(true); evt.setCancelled(true);
return; return;
} else if (entityEvt.getDamager() instanceof Projectile) {
Projectile asProjectile = (Projectile)entityEvt.getDamager();
if (asProjectile.getShooter() instanceof Player) {
evt.setCancelled(true);
} }
} }
if (m_players.contains(player) && player.getHealth() - evt.getFinalDamage() <= 0) { }
if (m_players.contains(player) && player.getHealth() - evt.getFinalDamage() <= 0 && m_stage == Stage.Playing) {
evt.setCancelled(true); evt.setCancelled(true);
handlePlayerDeath(player); handlePlayerDeath(player);
} }
@ -197,6 +223,10 @@ public class GameRunner {
return true; return true;
} }
public Collection<Player> getPlayers() {
return m_players.getPlayers();
}
private boolean enterWarmup() { private boolean enterWarmup() {
m_waves.next(); m_waves.next();
for(Player p : m_players.getPlayers()) { for(Player p : m_players.getPlayers()) {
@ -204,12 +234,13 @@ public class GameRunner {
p.teleport(m_world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); p.teleport(m_world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
} }
m_players.setReady(p, false); m_players.setReady(p, false);
m_players.healPlayer(p);
} }
BaseComponent[] message = new ComponentBuilder() BaseComponent[] message = new ComponentBuilder()
.append("Click").color(ChatColor.LIGHT_PURPLE) .append("Click").color(ChatColor.LIGHT_PURPLE)
.append("[Here]").color(ChatColor.GOLD) .append(" [Here] ").color(ChatColor.GOLD).bold(true)
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/malloc-defense:ready")) .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/malloc-defense:ready"))
.append(" when ready.").color(ChatColor.LIGHT_PURPLE) .append("when ready.").color(ChatColor.LIGHT_PURPLE)
.create(); .create();
broadcastMessage(message); broadcastMessage(message);
m_mobs.clear(); m_mobs.clear();
@ -233,11 +264,11 @@ public class GameRunner {
.append(p.getName()).bold(true) .append(p.getName()).bold(true)
.append(" is ready!").color(ChatColor.AQUA).italic(true) .append(" is ready!").color(ChatColor.AQUA).italic(true)
.create(); .create();
broadcastMessage(message);
} }
m_players.setReady(p, isReady); m_players.setReady(p, isReady);
} }
m_bars.update(); m_bars.update();
broadcastMessage(message);
if (m_players.isEveryoneReady()) { if (m_players.isEveryoneReady()) {
requestTransition(Stage.Countdown); requestTransition(Stage.Countdown);
} }
@ -270,7 +301,7 @@ public class GameRunner {
m_lobbyReturnTask.start(); m_lobbyReturnTask.start();
m_countdownTask.stop(); m_countdownTask.stop();
m_fuseTask.stop(); m_fuseTask.stop();
m_mobs.clear(); //m_mobs.clear();
return true; return true;
} }
@ -280,7 +311,7 @@ public class GameRunner {
private void spawnNextBatch() { private void spawnNextBatch() {
m_log.info("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); Spawner spawner = new GameSpawner(m_world, m_arena, m_mobs);
m_waves.currentWave().spawnBatch(spawner, m_waves.currentBatchNum()); m_waves.currentWave().spawnBatch(spawner, m_waves.currentBatchNum());
m_bars.update(); m_bars.update();
} }
@ -305,9 +336,9 @@ public class GameRunner {
public void handleEntityDeath(Entity entity) { public void handleEntityDeath(Entity entity) {
boolean wasCarrier = m_mobs.isBombCarrier(entity); boolean wasCarrier = m_mobs.isBombCarrier(entity);
if (m_mobs.killMob(entity)) { if (m_mobs.killMob(entity)) {
int coinsToDrop = 300; int coinsToDrop = 60;
while(coinsToDrop > 0) { while(coinsToDrop > 0) {
int droppedCoins = Math.min(coinsToDrop, 64); int droppedCoins = Math.min(coinsToDrop, 5);
ItemStack coins = Items.makeCoins(); ItemStack coins = Items.makeCoins();
coins.setAmount(droppedCoins); coins.setAmount(droppedCoins);
coinsToDrop -= 64; coinsToDrop -= 64;
@ -360,9 +391,9 @@ public class GameRunner {
case Idle: case Idle:
return !m_players.isEmpty() && to == Stage.Warmup; return !m_players.isEmpty() && to == Stage.Warmup;
case Warmup: case Warmup:
return to == Stage.Playing || to == Stage.Idle || to == Stage.Countdown; return to == Stage.Playing || to == Stage.Idle || to == Stage.Countdown || to == Stage.GameOver;
case Countdown: case Countdown:
return to == Stage.Playing || to == Stage.Idle || to == Stage.Warmup; return to == Stage.Playing || to == Stage.Idle || to == Stage.Warmup || to == Stage.GameOver;
case Playing: case Playing:
return to == Stage.Warmup || to == Stage.GameOver || to == Stage.Idle; return to == Stage.Warmup || to == Stage.GameOver || to == Stage.Idle;
case GameOver: case GameOver:
@ -392,13 +423,13 @@ public class GameRunner {
void sendStageTitle(Player p) { void sendStageTitle(Player p) {
switch(m_stage) { switch(m_stage) {
case Warmup: case Warmup:
p.sendTitle("Warmup", "Prepare yourself for wave " + m_waves.currentWaveNum(), 10, 70, 20); p.sendTitle(ChatColor.YELLOW.toString() + "Warmup", ChatColor.LIGHT_PURPLE.toString() + "Prepare yourself for wave " + m_waves.currentWaveNum());
break; break;
case Playing: case Playing:
p.sendTitle("Wave " + m_waves.currentWaveNum(), "", 10, 70, 20); p.sendTitle(ChatColor.AQUA.toString() + "Wave " + m_waves.currentWaveNum(), "");
break; break;
case GameOver: case GameOver:
p.sendTitle("Game over!", "", 10, 70, 20); p.sendTitle(ChatColor.RED.toString() + "Game over!", "");
} }
} }
@ -406,6 +437,7 @@ public class GameRunner {
if (m_players.contains(p)) { if (m_players.contains(p)) {
return; return;
} }
p.getInventory().clear();
m_players.addPlayer(p); m_players.addPlayer(p);
m_bars.addPlayer(p); m_bars.addPlayer(p);
if (m_stage == Stage.Idle || m_stage == Stage.Warmup) { if (m_stage == Stage.Idle || m_stage == Stage.Warmup) {
@ -414,7 +446,7 @@ public class GameRunner {
} }
BaseComponent[] message = new ComponentBuilder() BaseComponent[] message = new ComponentBuilder()
.append(p.getName()).bold(true) .append(p.getName()).bold(true)
.append("Has joined the game").color(ChatColor.AQUA) .append(" has joined the game").color(ChatColor.AQUA)
.create(); .create();
broadcastMessage(message); broadcastMessage(message);
sendStageTitle(p); sendStageTitle(p);
@ -425,6 +457,7 @@ public class GameRunner {
} }
public void removePlayer(Player p) { public void removePlayer(Player p) {
p.getInventory().clear();
m_bars.removePlayer(p); m_bars.removePlayer(p);
m_players.removePlayer(p); m_players.removePlayer(p);
if (m_players.isEmpty()) { if (m_players.isEmpty()) {
@ -444,7 +477,7 @@ public class GameRunner {
void broadcastTitle(String title, String subtitle) { void broadcastTitle(String title, String subtitle) {
for(Player p : m_players.getPlayers()) { for(Player p : m_players.getPlayers()) {
p.sendTitle(title, subtitle, 10, 70, 20); p.sendTitle(title, subtitle);
} }
} }

View File

@ -20,38 +20,39 @@ import org.bukkit.attribute.AttributeModifier;
public class GameSpawner implements Spawner { public class GameSpawner implements Spawner {
Arena m_arena; Arena m_arena;
MobManager m_manager; MobManager m_manager;
PlayerManager m_players;
int m_spawnIdx = 0; int m_spawnIdx = 0;
World m_world; World m_world;
public GameSpawner(World world, Arena arena, MobManager manager, PlayerManager players) { public GameSpawner(World world, Arena arena, MobManager manager) {
m_world = world; m_world = world;
m_arena = arena; m_arena = arena;
m_manager = manager; m_manager = manager;
m_players = players;
} }
@Override @Override
public LivingEntity spawnBombCarrier(EntityType type) { public LivingEntity spawnBombCarrier(EntityType type) {
if (m_manager.bombCount() == 0) { if (m_manager.bombCount() == 0) {
return m_manager.addBombCarrier(spawnMob(type)); return m_manager.addBombCarrier(spawnMob(type, MobManager.Goal.BombCarrier));
} else { } else {
return spawnMob(type); return spawnMob(type, MobManager.Goal.BombCarrier);
} }
} }
@Override @Override
public LivingEntity spawnMob(EntityType type) { public LivingEntity spawnMob(EntityType type) {
return spawnMob(type, MobManager.Goal.PlayerHarassment);
}
public Mob spawnMob(EntityType type, MobManager.Goal goal) {
Waypoint[] spawnpoints = m_arena.spawnpoints(); Waypoint[] spawnpoints = m_arena.spawnpoints();
m_spawnIdx %= spawnpoints.length; m_spawnIdx %= spawnpoints.length;
//m_log.fine("Spawning " + type + " at " + spawnpoints[m_spawnIdx]); //m_log.fine("Spawning " + type + " at " + spawnpoints[m_spawnIdx]);
Waypoint thisSpawner = spawnpoints[m_spawnIdx]; Waypoint thisSpawner = spawnpoints[m_spawnIdx];
Location loc = new Location(m_world, thisSpawner.getX(), thisSpawner.getY(), thisSpawner.getZ()); Location loc = new Location(m_world, thisSpawner.getX(), thisSpawner.getY(), thisSpawner.getZ());
Entity newMob = m_world.spawnEntity(loc, type); Mob newMob = (Mob)m_world.spawnEntity(loc, type);
LivingEntity livingMob = (LivingEntity)newMob; newMob.setRemoveWhenFarAway(false);
livingMob.setRemoveWhenFarAway(false); m_manager.addEntity(newMob, goal);
m_manager.addEntity(livingMob);
m_spawnIdx += 1; m_spawnIdx += 1;
return livingMob; return newMob;
} }
} }

View File

@ -1,25 +1,37 @@
package gg.malloc.defense.engine; package gg.malloc.defense.engine;
import java.util.HashSet; import java.util.HashSet;
import java.util.HashMap;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.GameMode;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Mob; import org.bukkit.entity.Mob;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.inventory.EntityEquipment;
import gg.malloc.defense.model.Game; import gg.malloc.defense.model.Game;
import gg.malloc.defense.ui.BombCarrier; import gg.malloc.defense.ui.BombCarrier;
import gg.malloc.defense.ui.Items;
public class MobManager { public class MobManager {
HashSet<LivingEntity> m_livingMobs = new HashSet<>(); LivingEntity m_bombTarget;
HashSet<LivingEntity> m_bombCarriers = new HashSet<>(); HashSet<Mob> m_livingMobs = new HashSet<>();
HashSet<Mob> m_bombCarriers = new HashSet<>();
HashSet<LivingEntity> m_droppedBombs = new HashSet<>(); HashSet<LivingEntity> m_droppedBombs = new HashSet<>();
HashMap<Mob, Goal> m_mobGoals = new HashMap<>();
public enum Goal {
BombCarrier,
PlayerHarassment
}
int m_createdMobs = 0; int m_createdMobs = 0;
int m_killedMobs = 0; int m_killedMobs = 0;
@ -44,7 +56,7 @@ public class MobManager {
} }
public void clear() { public void clear() {
for(LivingEntity e : m_livingMobs) { for(Mob e : m_livingMobs) {
e.remove(); e.remove();
} }
for(LivingEntity e : m_droppedBombs) { for(LivingEntity e : m_droppedBombs) {
@ -77,14 +89,76 @@ public class MobManager {
return (double)m_killedMobs / (double)m_createdMobs; return (double)m_killedMobs / (double)m_createdMobs;
} }
public void addEntity(LivingEntity entity) { Player getNearestPlayer(Location location) {
m_livingMobs.add(entity); Player nearestPlayer = null;
Mob asMob = (Mob)entity; for(Player p : location.getWorld().getPlayers()) {
if (m_droppedBombs.size() > 0) { if (p.getGameMode() == GameMode.SPECTATOR) {
asMob.setTarget(m_droppedBombs.iterator().next()); continue;
} else { } else if (nearestPlayer == null) {
asMob.setTarget(m_bombTarget); nearestPlayer = p;
} else if (location.distance(p.getLocation()) < location.distance(nearestPlayer.getLocation())) {
nearestPlayer = p;
} }
}
return nearestPlayer;
}
LivingEntity getNearestBombObjective(Location location) {
LivingEntity nearest = null;
if (m_droppedBombs.size() > 0) {
for(LivingEntity ent : m_droppedBombs) {
if (nearest == null) {
nearest = ent;
} else if (location.distance(ent.getLocation()) < location.distance(nearest.getLocation())) {
nearest = ent;
}
}
} else if (m_bombCarriers.size() > 0) {
for(LivingEntity ent : m_bombCarriers) {
if (nearest == null) {
nearest = ent;
} else if (location.distance(ent.getLocation()) < location.distance(nearest.getLocation())) {
nearest = ent;
}
}
}
if (nearest == null) {
nearest = m_bombTarget;
}
return nearest;
}
Player getBiggestPlayerThreat(Location location) {
Entity mostImportantObjective = getNearestBombObjective(location);
return getNearestPlayer(mostImportantObjective.getLocation());
}
LivingEntity getMobTarget(Mob mob) {
switch(m_mobGoals.get(mob)) {
case PlayerHarassment:
if (mob.getTarget() instanceof Player) {
return mob.getTarget();
} else {
Player nearestThreat = getBiggestPlayerThreat(mob.getLocation());
return nearestThreat;
}
case BombCarrier:
if (m_bombCarriers.contains(mob)) {
return m_bombTarget;
} else if (m_droppedBombs.size() > 0) {
return m_droppedBombs.iterator().next();
} else {
return getBiggestPlayerThreat(mob.getLocation());
}
default:
return m_bombTarget;
}
}
public void addEntity(Mob entity, Goal goal) {
m_livingMobs.add(entity);
m_mobGoals.put(entity, goal);
entity.setTarget(getMobTarget(entity));
m_createdMobs += 1; m_createdMobs += 1;
} }
@ -92,13 +166,13 @@ public class MobManager {
return m_bombCarriers.size() + m_droppedBombs.size(); return m_bombCarriers.size() + m_droppedBombs.size();
} }
public LivingEntity addBombCarrier(LivingEntity entity) { public Mob addBombCarrier(Mob entity) {
m_bombCarriers.add(entity); m_bombCarriers.add(entity);
((Mob)entity).setTarget(m_bombTarget); entity.setTarget(m_bombTarget);
return new BombCarrier(entity).inHand(); return (Mob)(new BombCarrier(entity).inHand());
} }
public void removeBombCarrier(LivingEntity entity) { public void removeBombCarrier(Mob entity) {
m_bombCarriers.remove(entity); m_bombCarriers.remove(entity);
} }
@ -109,13 +183,13 @@ public class MobManager {
public LivingEntity dropBomb(Location location) { public LivingEntity dropBomb(Location location) {
ArmorStand droppedBomb = (ArmorStand)(new BombCarrier((LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND)).onHead()); ArmorStand droppedBomb = (ArmorStand)(new BombCarrier((LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND)).onHead());
droppedBomb.setCustomName("The Bomb"); droppedBomb.setCustomName("The Bomb");
droppedBomb.setVisible(true); droppedBomb.setVisible(false);
droppedBomb.setCustomNameVisible(true); droppedBomb.setCustomNameVisible(true);
droppedBomb.setGlowing(true); droppedBomb.setGlowing(true);
m_droppedBombs.add(droppedBomb); m_droppedBombs.add(droppedBomb);
for(LivingEntity ent : m_livingMobs) { for(Mob ent : m_livingMobs) {
if (!m_bombCarriers.contains(ent)) { if (!m_bombCarriers.contains(ent)) {
((Mob)ent).setTarget(droppedBomb); ent.setTarget(getMobTarget(ent));
} }
} }
return droppedBomb; return droppedBomb;
@ -143,11 +217,9 @@ public class MobManager {
if (m_livingMobs.contains(entityEvt.getDamager())) { if (m_livingMobs.contains(entityEvt.getDamager())) {
evt.getEntity().remove(); evt.getEntity().remove();
m_droppedBombs.remove(evt.getEntity()); m_droppedBombs.remove(evt.getEntity());
addBombCarrier((LivingEntity)entityEvt.getDamager()); addBombCarrier((Mob)entityEvt.getDamager());
for(LivingEntity ent : m_livingMobs) { for(Mob ent : m_livingMobs) {
if (!m_bombCarriers.contains(ent)) { ent.setTarget(getMobTarget(ent));
((Mob)ent).setTarget(m_bombTarget);
}
} }
} }
} else if (m_livingMobs.contains(evt.getEntity())) { } else if (m_livingMobs.contains(evt.getEntity())) {
@ -155,28 +227,22 @@ public class MobManager {
} }
} }
LivingEntity m_bombTarget;
public LivingEntity spawnTarget(Location location) { public LivingEntity spawnTarget(Location location) {
assert(m_bombTarget == null); assert(m_bombTarget == null);
m_bombTarget = (LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND); m_bombTarget = (LivingEntity)location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND);
ArmorStand bombStand = (ArmorStand)m_bombTarget; ArmorStand bombStand = (ArmorStand)m_bombTarget;
bombStand.setCustomName("Bomb Target"); bombStand.setCustomName("Bomb Target");
bombStand.setVisible(true); bombStand.setVisible(false);
bombStand.setCustomNameVisible(true); bombStand.setCustomNameVisible(true);
bombStand.setGlowing(true); bombStand.setGlowing(true);
EntityEquipment equipment = bombStand.getEquipment();
equipment.setHelmet(Items.makeBombTarget());
return m_bombTarget; return m_bombTarget;
} }
public void handleEntityRetarget(EntityTargetEvent evt) { public void handleEntityRetarget(EntityTargetEvent evt) {
if (m_livingMobs.contains(evt.getEntity())) { if (m_livingMobs.contains(evt.getEntity())) {
if (m_bombCarriers.contains(evt.getEntity())) { evt.setTarget(getMobTarget((Mob)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);
}
} }
} }
} }

View File

@ -57,12 +57,16 @@ public class PlayerManager {
return true; return true;
} }
public void healPlayer(Player player) {
player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue());
player.setFoodLevel(40);
}
// Respawn player // Respawn player
public boolean enterPlaying(Player player) { public boolean enterPlaying(Player player) {
//m_log.fine("Respawning player " + player); //m_log.fine("Respawning player " + player);
player.setGameMode(Bukkit.getDefaultGameMode()); player.setGameMode(Bukkit.getDefaultGameMode());
player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); healPlayer(player);
player.setFoodLevel(20);
return true; return true;
} }
@ -107,6 +111,7 @@ public class PlayerManager {
public boolean removePlayer(Player player) { public boolean removePlayer(Player player) {
//m_log.info("Removing player " + player); //m_log.info("Removing player " + player);
healPlayer(player);
requestTransition(player, State.Idle); requestTransition(player, State.Idle);
m_playerStates.remove(player); m_playerStates.remove(player);
m_playerReadyStates.remove(player); m_playerReadyStates.remove(player);

View File

@ -46,7 +46,7 @@ public class ScaledWaves implements Game {
MobWave(HashMap<EntityType, Double> weights, int totalCount, int batches, boolean hasRavager) { MobWave(HashMap<EntityType, Double> weights, int totalCount, int batches, boolean hasRavager) {
m_batches = batches; m_batches = batches;
m_mobsPerBatch = Math.max(1, totalCount / batches); m_mobsPerBatch = Math.max(1, totalCount / batches) * 3;
m_spawnWeights = weights; m_spawnWeights = weights;
m_hasRavager = hasRavager; m_hasRavager = hasRavager;
} }
@ -58,7 +58,7 @@ public class ScaledWaves implements Game {
@Override @Override
public void spawnBatch(Spawner spawner, int batch) { public void spawnBatch(Spawner spawner, int batch) {
double healthScale = -0.5; double healthScale = -0.7;
AttributeModifier modifier = new AttributeModifier("Scaled Health", healthScale, AttributeModifier.Operation.MULTIPLY_SCALAR_1); AttributeModifier modifier = new AttributeModifier("Scaled Health", healthScale, AttributeModifier.Operation.MULTIPLY_SCALAR_1);
assert(m_mobsPerBatch > 0); assert(m_mobsPerBatch > 0);
for(int i = 0; i < m_mobsPerBatch; i++) { for(int i = 0; i < m_mobsPerBatch; i++) {
@ -72,9 +72,14 @@ public class ScaledWaves implements Game {
} }
} }
assert(selectedType != null); assert(selectedType != null);
Mob newMob = (Mob)spawner.spawnBombCarrier(selectedType); Mob newMob;
newMob.setCustomName("Mob " + i + "/" + m_mobsPerBatch); if (selectedType == EntityType.ZOMBIE) {
newMob = (Mob)spawner.spawnBombCarrier(selectedType);
newMob.getAttribute(Attribute.GENERIC_MAX_HEALTH).addModifier(modifier); newMob.getAttribute(Attribute.GENERIC_MAX_HEALTH).addModifier(modifier);
} else {
newMob = (Mob)spawner.spawnMob(selectedType);
}
newMob.setCustomName("Mob " + i + "/" + m_mobsPerBatch);
} }
if (m_hasRavager) { if (m_hasRavager) {
Entity newMob = spawner.spawnMob(EntityType.RAVAGER); Entity newMob = spawner.spawnMob(EntityType.RAVAGER);

View File

@ -16,14 +16,12 @@ public class BombCarrier {
public LivingEntity inHand() { public LivingEntity inHand() {
EntityEquipment equipment = m_entity.getEquipment(); EntityEquipment equipment = m_entity.getEquipment();
equipment.setItemInOffHand(Items.makeBombHelmet()); equipment.setItemInOffHand(Items.makeBombHelmet());
//equipment.setItemInOffHandDropChance(0.0f);
return m_entity; return m_entity;
} }
public LivingEntity onHead() { public LivingEntity onHead() {
EntityEquipment equipment = m_entity.getEquipment(); EntityEquipment equipment = m_entity.getEquipment();
equipment.setHelmet(Items.makeBombHelmet()); equipment.setHelmet(Items.makeBombHelmet());
//equipment.setHelmetDropChance(0.0f);
return m_entity; return m_entity;
} }
} }

View File

@ -27,4 +27,12 @@ public class Items {
bombItem.setItemMeta(meta); bombItem.setItemMeta(meta);
return bombItem; return bombItem;
} }
public static ItemStack makeBombTarget() {
ItemStack bombItem = new ItemStack(Material.CARVED_PUMPKIN);
ItemMeta meta = bombItem.getItemMeta();
meta.setCustomModelData(35197);
bombItem.setItemMeta(meta);
return bombItem;
}
} }