From 2aecea401d0b32b03c86d87c0e85d4c05d93b1b2 Mon Sep 17 00:00:00 2001 From: Andreas Troelsen Date: Fri, 22 Jun 2018 00:00:05 +0200 Subject: [PATCH] Add support for boss health bars. Three different types of health bars are implemented behind a basic Strategy Pattern. A new per-arena setting, boss-health-bar, can be used to configure which one of the three types (if any) of health bar should be used for bosses in the given arena: - `boss-bar` creates a boss bar at the top of the screen as if the players were fighting an ender dragon or a wither. - `title` uses the Chapters/Titles API in Bukkit to display the health of the boss as a "subtitle" whenever it takes damage. - `name` sets the entitiy's health along with an optional custom name above the entity's head. --- .../com/garbagemule/MobArena/ArenaImpl.java | 9 ++++ .../garbagemule/MobArena/ArenaListener.java | 19 +++++++ .../garbagemule/MobArena/MASpawnThread.java | 8 +++ .../MobArena/healthbar/BossHealthBar.java | 49 +++++++++++++++++ .../MobArena/healthbar/CreatesHealthBar.java | 30 +++++++++++ .../healthbar/CreatesHealthString.java | 29 ++++++++++ .../MobArena/healthbar/HealthBar.java | 15 ++++++ .../MobArena/healthbar/NameHealthBar.java | 43 +++++++++++++++ .../MobArena/healthbar/NullHealthBar.java | 27 ++++++++++ .../MobArena/healthbar/TitleHealthBar.java | 53 +++++++++++++++++++ .../garbagemule/MobArena/waves/MABoss.java | 11 ++++ src/main/resources/res/settings.yml | 1 + 12 files changed, 294 insertions(+) create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/BossHealthBar.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthBar.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthString.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/HealthBar.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/NameHealthBar.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/NullHealthBar.java create mode 100644 src/main/java/com/garbagemule/MobArena/healthbar/TitleHealthBar.java diff --git a/src/main/java/com/garbagemule/MobArena/ArenaImpl.java b/src/main/java/com/garbagemule/MobArena/ArenaImpl.java index 853a067..4981395 100644 --- a/src/main/java/com/garbagemule/MobArena/ArenaImpl.java +++ b/src/main/java/com/garbagemule/MobArena/ArenaImpl.java @@ -25,6 +25,7 @@ import com.garbagemule.MobArena.util.ClassChests; import com.garbagemule.MobArena.util.inventory.InventoryManager; import com.garbagemule.MobArena.util.timer.AutoStartTimer; import com.garbagemule.MobArena.util.timer.StartDelayTimer; +import com.garbagemule.MobArena.waves.MABoss; import com.garbagemule.MobArena.waves.SheepBouncer; import com.garbagemule.MobArena.waves.WaveManager; import org.bukkit.Bukkit; @@ -1118,6 +1119,14 @@ public class ArenaImpl implements Arena private void clearPlayer(Player p) { + // Remove from boss health bar + monsterManager.getBossMonsters().forEach(entity -> { + MABoss boss = monsterManager.getBoss(entity); + if (boss != null) { + boss.getHealthBar().removePlayer(p); + } + }); + // Remove pets. monsterManager.removePets(p); diff --git a/src/main/java/com/garbagemule/MobArena/ArenaListener.java b/src/main/java/com/garbagemule/MobArena/ArenaListener.java index bf143a6..7945830 100644 --- a/src/main/java/com/garbagemule/MobArena/ArenaListener.java +++ b/src/main/java/com/garbagemule/MobArena/ArenaListener.java @@ -635,6 +635,7 @@ public class ArenaListener event.getDrops().addAll(drops); } boss.setDead(true); + boss.getHealthBar().setProgress(0); } List loot = monsters.getLoot(damagee); @@ -685,6 +686,10 @@ public class ArenaListener } } + MABoss boss = (damagee instanceof LivingEntity) + ? monsters.getBoss((LivingEntity) damagee) + : null; + // Pet wolf if (damagee instanceof Wolf && arena.hasPet(damagee)) { onPetDamage(event, (Wolf) damagee, damager); @@ -701,6 +706,10 @@ public class ArenaListener else if (damagee instanceof Snowman && event.getCause() == DamageCause.MELTING) { event.setCancelled(true); } + // Boss monster + else if (boss != null) { + onBossDamage(event, boss, damager); + } // Regular monster else if (monsters.getMonsters().contains(damagee)) { onMonsterDamage(event, damagee, damager); @@ -739,6 +748,16 @@ public class ArenaListener private void onMountDamage(EntityDamageEvent event, Horse mount, Entity damager) { event.setCancelled(true); } + + private void onBossDamage(EntityDamageEvent event, MABoss boss, Entity damager) { + onMonsterDamage(event, boss.getEntity(), damager); + if (event.isCancelled()) { + return; + } + + double progress = boss.getHealth() / boss.getMaxHealth(); + boss.getHealthBar().setProgress(progress); + } private void onMonsterDamage(EntityDamageEvent event, Entity monster, Entity damager) { if (damager instanceof Player) { diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index 48dcc71..efc8ab5 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -3,6 +3,8 @@ package com.garbagemule.MobArena; import com.garbagemule.MobArena.events.ArenaCompleteEvent; import com.garbagemule.MobArena.events.NewWaveEvent; import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.healthbar.CreatesHealthBar; +import com.garbagemule.MobArena.healthbar.HealthBar; import com.garbagemule.MobArena.region.ArenaRegion; import com.garbagemule.MobArena.things.Thing; import com.garbagemule.MobArena.waves.MABoss; @@ -31,6 +33,7 @@ public class MASpawnThread implements Runnable private RewardManager rewardManager; private WaveManager waveManager; private MonsterManager monsterManager; + private CreatesHealthBar createsHealthBar; private int playerCount, monsterLimit; private boolean waveClear, bossClear, preBossClear, wavesAsLevel; @@ -48,6 +51,7 @@ public class MASpawnThread implements Runnable this.rewardManager = arena.getRewardManager(); this.waveManager = arena.getWaveManager(); this.monsterManager = arena.getMonsterManager(); + this.createsHealthBar = new CreatesHealthBar(arena.getSettings().getString("boss-health-bar", "none")); reset(); } @@ -195,6 +199,10 @@ public class MASpawnThread implements Runnable BossWave bw = (BossWave) w; double maxHealth = bw.getMaxHealth(playerCount); MABoss boss = monsterManager.addBoss(e, maxHealth); + HealthBar healthbar = createsHealthBar.create(e, bw.getBossName()); + arena.getPlayersInArena().forEach(healthbar::addPlayer); + healthbar.setProgress(1); + boss.setHealthBar(healthbar); boss.setReward(bw.getReward()); boss.setDrops(bw.getDrops()); bw.addMABoss(boss); diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/BossHealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/BossHealthBar.java new file mode 100644 index 0000000..6c54360 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/BossHealthBar.java @@ -0,0 +1,49 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.Bukkit; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.Player; + +class BossHealthBar implements HealthBar { + + private static final double LOW_HEALTH = 0.25; + + private final BossBar bar; + + BossHealthBar(String title) { + bar = Bukkit.createBossBar( + title.isEmpty() ? "Boss" : title, + BarColor.GREEN, + BarStyle.SOLID, + BarFlag.PLAY_BOSS_MUSIC, + BarFlag.DARKEN_SKY + ); + } + + @Override + public void setProgress(double progress) { + if (progress <= LOW_HEALTH && bar.getColor() != BarColor.RED) { + bar.setColor(BarColor.RED); + } + bar.setProgress(progress); + } + + @Override + public void addPlayer(Player player) { + bar.addPlayer(player); + } + + @Override + public void removePlayer(Player player) { + bar.removePlayer(player); + } + + @Override + public void removeAll() { + bar.removeAll(); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthBar.java new file mode 100644 index 0000000..730c950 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthBar.java @@ -0,0 +1,30 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.entity.Entity; + +public class CreatesHealthBar { + + private final String type; + private final CreatesHealthString createsHealthString; + + public CreatesHealthBar(String type) { + this.type = type; + this.createsHealthString = new CreatesHealthString(); + } + + public HealthBar create(Entity entity, String title) { + String name = (title != null) ? title : ""; + + switch (type) { + case "boss-bar": + return new BossHealthBar(name); + case "title": + return new TitleHealthBar(name, createsHealthString); + case "name": + return new NameHealthBar(entity, name, createsHealthString); + default: + return new NullHealthBar(); + } + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthString.java b/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthString.java new file mode 100644 index 0000000..a85979e --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/CreatesHealthString.java @@ -0,0 +1,29 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.ChatColor; + +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +class CreatesHealthString { + + private static final int TOTAL_BARS = 20; + private static final int LOW_BARS = TOTAL_BARS / 4; + + String create(double progress) { + int bars = (int) (progress * TOTAL_BARS); + + String current = IntStream.range(0, bars) + .mapToObj(i -> "|") + .collect(Collectors.joining()); + + String lost = IntStream.range(bars, TOTAL_BARS) + .mapToObj(i -> "|") + .collect(Collectors.joining()); + + ChatColor color = (bars <= LOW_BARS) ? ChatColor.RED : ChatColor.GREEN; + + return color + current + ChatColor.GRAY + lost; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/HealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/HealthBar.java new file mode 100644 index 0000000..8efff0e --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/HealthBar.java @@ -0,0 +1,15 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.entity.Player; + +public interface HealthBar { + + void setProgress(double progress); + + void addPlayer(Player player); + + void removePlayer(Player player); + + void removeAll(); + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/NameHealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/NameHealthBar.java new file mode 100644 index 0000000..ef0a048 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/NameHealthBar.java @@ -0,0 +1,43 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +class NameHealthBar implements HealthBar { + + private final Entity entity; + private final String title; + private final CreatesHealthString createsHealthString; + + NameHealthBar(Entity entity, String title, CreatesHealthString createsHealthString) { + this.entity = entity; + this.title = title; + this.createsHealthString = createsHealthString; + + entity.setCustomNameVisible(true); + } + + @Override + public void setProgress(double progress) { + String health = createsHealthString.create(progress); + String name = title.isEmpty() ? health : title + " " + health; + + entity.setCustomName(name); + } + + @Override + public void addPlayer(Player player) { + // OK BOSS + } + + @Override + public void removePlayer(Player player) { + // OK BOSS + } + + @Override + public void removeAll() { + // OK BOSS + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/NullHealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/NullHealthBar.java new file mode 100644 index 0000000..6b10352 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/NullHealthBar.java @@ -0,0 +1,27 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.entity.Player; + +class NullHealthBar implements HealthBar { + + @Override + public void setProgress(double progress) { + // OK BOSS + } + + @Override + public void addPlayer(Player player) { + // OK BOSS + } + + @Override + public void removePlayer(Player player) { + // OK BOSS + } + + @Override + public void removeAll() { + // OK BOSS + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/healthbar/TitleHealthBar.java b/src/main/java/com/garbagemule/MobArena/healthbar/TitleHealthBar.java new file mode 100644 index 0000000..7bdf991 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/healthbar/TitleHealthBar.java @@ -0,0 +1,53 @@ +package com.garbagemule.MobArena.healthbar; + +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; + +class TitleHealthBar implements HealthBar { + + private static final int FADE_IN_TICKS = 5; + private static final int STAY_TICKS = 40; + private static final int FADE_OUT_TICKS = 10; + + private final String title; + private final CreatesHealthString createsHealthString; + private final Set players; + + TitleHealthBar(String title, CreatesHealthString createsHealthString) { + this.title = title; + this.createsHealthString = createsHealthString; + this.players = new HashSet<>(); + } + + @Override + public void setProgress(double progress) { + String health = createsHealthString.create(progress); + String message = title.isEmpty() ? health : title + " " + health; + + players.forEach(player -> player.sendTitle( + "", + message, + FADE_IN_TICKS, + STAY_TICKS, + FADE_OUT_TICKS + )); + } + + @Override + public void addPlayer(Player player) { + players.add(player); + } + + @Override + public void removePlayer(Player player) { + players.remove(player); + } + + @Override + public void removeAll() { + players.clear(); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/waves/MABoss.java b/src/main/java/com/garbagemule/MobArena/waves/MABoss.java index 06dba48..8c1873c 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/MABoss.java +++ b/src/main/java/com/garbagemule/MobArena/waves/MABoss.java @@ -1,5 +1,6 @@ package com.garbagemule.MobArena.waves; +import com.garbagemule.MobArena.healthbar.HealthBar; import com.garbagemule.MobArena.things.Thing; import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; @@ -13,6 +14,7 @@ public class MABoss private boolean dead; private Thing reward; private List drops; + private HealthBar healthbar; /** * Create an MABoss from the given entity with the given max health. @@ -73,6 +75,7 @@ public class MABoss */ public void setDead(boolean dead) { this.dead = dead; + healthbar.removeAll(); } public void setReward(Thing reward) { @@ -90,4 +93,12 @@ public class MABoss public List getDrops() { return drops; } + + public HealthBar getHealthBar() { + return healthbar; + } + + public void setHealthBar(HealthBar healthbar) { + this.healthbar = healthbar; + } } diff --git a/src/main/resources/res/settings.yml b/src/main/resources/res/settings.yml index fa5a484..e3421ba 100644 --- a/src/main/resources/res/settings.yml +++ b/src/main/resources/res/settings.yml @@ -34,6 +34,7 @@ auto-start-timer: 0 start-delay-timer: 0 auto-ready: false use-class-chests: false +boss-health-bar: boss-bar display-waves-as-level: false display-timer-as-level: false use-scoreboards: true