diff --git a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/EcoBoss.java b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/EcoBoss.java index 7f255e3..1be9984 100644 --- a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/EcoBoss.java +++ b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/EcoBoss.java @@ -8,49 +8,26 @@ import com.willfp.eco.util.plugin.AbstractEcoPlugin; import com.willfp.eco.util.tuples.Pair; import com.willfp.ecobosses.bosses.util.bosstype.BossEntityUtils; import com.willfp.ecobosses.bosses.util.bosstype.BossType; -import com.willfp.ecobosses.bosses.util.obj.BossbarProperties; -import com.willfp.ecobosses.bosses.util.obj.ExperienceOptions; -import com.willfp.ecobosses.bosses.util.obj.ImmunityOptions; -import com.willfp.ecobosses.bosses.util.obj.OptionedSound; -import com.willfp.ecobosses.bosses.util.obj.SpawnTotem; -import com.willfp.ecobosses.bosses.util.obj.TeleportOptions; +import com.willfp.ecobosses.bosses.util.obj.*; import com.willfp.ecobosses.bosses.util.obj.attacks.EffectOption; import com.willfp.ecobosses.bosses.util.obj.attacks.SummonsOption; import lombok.AccessLevel; import lombok.Getter; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; -import org.bukkit.attribute.Attribute; -import org.bukkit.attribute.AttributeInstance; import org.bukkit.boss.BarColor; -import org.bukkit.boss.BarFlag; import org.bukkit.boss.BarStyle; -import org.bukkit.boss.BossBar; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.entity.Player; +import org.bukkit.entity.*; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.persistence.PersistentDataType; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class EcoBoss extends PluginDependent { @@ -239,6 +216,11 @@ public class EcoBoss extends PluginDependent { @Getter private final Map incomingMultipliers; + /** + * The currently living bosses of this type. + */ + private final Map livingBosses; + /** * Create a new Boss. * @@ -252,6 +234,7 @@ public class EcoBoss extends PluginDependent { super(plugin); this.config = config; this.name = name; + this.livingBosses = new HashMap<>(); this.displayName = this.getConfig().getString("name"); @@ -469,89 +452,21 @@ public class EcoBoss extends PluginDependent { */ public void spawn(@NotNull final Location location) { LivingEntity entity = bossType.spawnBossEntity(location); - entity.getPersistentDataContainer().set(this.getPlugin().getNamespacedKeyFactory().create("boss"), PersistentDataType.STRING, name); - entity.setPersistent(true); - - entity.setCustomName(this.getDisplayName()); - entity.setCustomNameVisible(true); - - AttributeInstance movementSpeed = entity.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED); - assert movementSpeed != null; - movementSpeed.setBaseValue(this.getMovementSpeed()); - - AttributeInstance maxHealth = entity.getAttribute(Attribute.GENERIC_MAX_HEALTH); - assert maxHealth != null; - maxHealth.setBaseValue(this.getMaxHealth()); - - entity.setHealth(maxHealth.getValue()); - - AttributeInstance attackDamage = entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE); - assert attackDamage != null; - attackDamage.setBaseValue(this.getAttackDamage()); - - for (OptionedSound sound : this.getSpawnSounds()) { - location.getWorld().playSound(location, sound.getSound(), sound.getVolume(), sound.getPitch()); - } - - for (String spawnMessage : this.getSpawnMessages()) { - Bukkit.broadcastMessage(spawnMessage - .replace("%x%", StringUtils.internalToString(location.getBlockX())) - .replace("%y%", StringUtils.internalToString(location.getBlockY())) - .replace("%z%", StringUtils.internalToString(location.getBlockZ())) - ); - } - - this.getPlugin().getRunnableFactory().create(runnable -> { - entity.setCustomName(this.getDisplayName().replace("%health%", StringUtils.internalToString(entity.getHealth()))); - if (entity.isDead()) { - runnable.cancel(); - } - }).runTaskTimer(0, 1); - - for (Entity nearbyEntity : entity.getNearbyEntities(15, 15, 15)) { - if (nearbyEntity instanceof Player && entity instanceof Mob) { - ((Mob) entity).setTarget((LivingEntity) nearbyEntity); - } - } - - if (this.isBossbarEnabled()) { - createBossBar(entity); - } + this.livingBosses.put(entity.getUniqueId(), new LivingEcoBoss( + this.getPlugin(), + entity, + this + ) + ); } - private void createBossBar(@NotNull final LivingEntity entity) { - BossBar bossBar = Bukkit.getServer().createBossBar( - entity.getCustomName(), - this.getBossbarProperties().getColor(), - this.getBossbarProperties().getStyle(), - (BarFlag) null - ); - - int radius = this.getPlugin().getConfigYml().getInt("bossbar-radius"); - - this.getPlugin().getRunnableFactory().create(runnable -> { - if (!entity.isDead()) { - bossBar.getPlayers().forEach(bossBar::removePlayer); - entity.getNearbyEntities(radius, radius, radius).forEach(entity1 -> { - if (entity1 instanceof Player) { - bossBar.addPlayer((Player) entity1); - } - }); - } else { - runnable.cancel(); - } - }).runTaskTimer(0, 40); - - this.getPlugin().getRunnableFactory().create(runnable -> { - if (!entity.isDead()) { - bossBar.setTitle(entity.getCustomName()); - bossBar.setProgress(entity.getHealth() / entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); - } else { - bossBar.getPlayers().forEach(bossBar::removePlayer); - bossBar.setVisible(false); - runnable.cancel(); - } - }).runTaskTimer(0, 1); + /** + * Remove living boss. + * + * @param uuid The entity UUID. + */ + public void removeLivingBoss(@NotNull final UUID uuid) { + this.livingBosses.remove(uuid); } /** diff --git a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/LivingEcoBoss.java b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/LivingEcoBoss.java new file mode 100644 index 0000000..6a7345b --- /dev/null +++ b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/LivingEcoBoss.java @@ -0,0 +1,135 @@ +package com.willfp.ecobosses.bosses; + +import com.willfp.eco.util.StringUtils; +import com.willfp.eco.util.bukkit.scheduling.EcoBukkitRunnable; +import com.willfp.eco.util.internal.PluginDependent; +import com.willfp.eco.util.plugin.AbstractEcoPlugin; +import com.willfp.ecobosses.bosses.tick.BossTicker; +import com.willfp.ecobosses.bosses.tick.tickers.BossBarTicker; +import com.willfp.ecobosses.bosses.tick.tickers.HealthPlaceholderTicker; +import com.willfp.ecobosses.bosses.util.obj.OptionedSound; +import org.bukkit.Bukkit; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.boss.BarFlag; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +public class LivingEcoBoss extends PluginDependent { + /** + * The entity. + */ + private final LivingEntity entity; + + /** + * The boss. + */ + private final EcoBoss boss; + + /** + * The boss tickers. + */ + private final Set tickers; + + /** + * Create new living EcoBoss. + * + * @param plugin Instance of EcoBosses. + * @param entity The entity. + * @param boss The boss. + */ + public LivingEcoBoss(@NotNull final AbstractEcoPlugin plugin, + @NotNull final LivingEntity entity, + @NotNull final EcoBoss boss) { + super(plugin); + this.entity = entity; + this.boss = boss; + + this.onSpawn(); + + + // Tickers + this.tickers = new HashSet<>(); + this.tickers.add(new HealthPlaceholderTicker()); + if (boss.isBossbarEnabled()) { + this.tickers.add( + new BossBarTicker( + Bukkit.getServer().createBossBar( + entity.getCustomName(), + boss.getBossbarProperties().getColor(), + boss.getBossbarProperties().getStyle(), + (BarFlag) null + ), + this.getPlugin().getConfigYml().getInt("bossbar-radius") + ) + ); + } + + + AtomicLong currentTick = new AtomicLong(0); + this.getPlugin().getRunnableFactory().create(runnable -> { + this.tick(currentTick.getAndAdd(1), runnable); + }).runTaskTimer(0, 1); + } + + private void onSpawn() { + entity.getPersistentDataContainer().set(this.getPlugin().getNamespacedKeyFactory().create("boss"), PersistentDataType.STRING, boss.getName()); + entity.setPersistent(true); + + entity.setCustomName(boss.getDisplayName()); + entity.setCustomNameVisible(true); + + AttributeInstance movementSpeed = entity.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED); + assert movementSpeed != null; + movementSpeed.setBaseValue(boss.getMovementSpeed()); + + AttributeInstance maxHealth = entity.getAttribute(Attribute.GENERIC_MAX_HEALTH); + assert maxHealth != null; + maxHealth.setBaseValue(boss.getMaxHealth()); + + entity.setHealth(maxHealth.getValue()); + + AttributeInstance attackDamage = entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE); + assert attackDamage != null; + attackDamage.setBaseValue(boss.getAttackDamage()); + + for (OptionedSound sound : boss.getSpawnSounds()) { + entity.getWorld().playSound(entity.getLocation(), sound.getSound(), sound.getVolume(), sound.getPitch()); + } + + for (String spawnMessage : boss.getSpawnMessages()) { + Bukkit.broadcastMessage(spawnMessage + .replace("%x%", StringUtils.internalToString(entity.getLocation().getBlockX())) + .replace("%y%", StringUtils.internalToString(entity.getLocation().getBlockY())) + .replace("%z%", StringUtils.internalToString(entity.getLocation().getBlockZ())) + ); + } + + for (Entity nearbyEntity : entity.getNearbyEntities(15, 15, 15)) { + if (nearbyEntity instanceof Player && entity instanceof Mob) { + ((Mob) entity).setTarget((LivingEntity) nearbyEntity); + } + } + } + + private void tick(final long tick, + @NotNull final EcoBukkitRunnable runnable) { + for (BossTicker ticker : tickers) { + ticker.tick(boss, entity, tick); + } + if (entity.isDead()) { + for (BossTicker ticker : tickers) { + ticker.onDeath(boss, entity, tick); + } + runnable.cancel(); + } + } +} diff --git a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/BossTicker.java b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/BossTicker.java new file mode 100644 index 0000000..a74a432 --- /dev/null +++ b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/BossTicker.java @@ -0,0 +1,31 @@ +package com.willfp.ecobosses.bosses.tick; + +import com.willfp.ecobosses.bosses.EcoBoss; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; + +public interface BossTicker { + /** + * Run on boss tick. + * + * @param boss The boss. + * @param entity The boss entity. + * @param tick The current tick: counts up from zero. + */ + void tick(@NotNull EcoBoss boss, + @NotNull LivingEntity entity, + long tick); + + /** + * Run on boss death. + * + * @param boss The boss. + * @param entity The boss entity. + * @param tick The current tick: counts up from zero. + */ + default void onDeath(@NotNull EcoBoss boss, + @NotNull LivingEntity entity, + long tick) { + // Can be overridden when needed. + } +} diff --git a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/BossBarTicker.java b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/BossBarTicker.java new file mode 100644 index 0000000..06c7b06 --- /dev/null +++ b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/BossBarTicker.java @@ -0,0 +1,57 @@ +package com.willfp.ecobosses.bosses.tick.tickers; + +import com.willfp.ecobosses.bosses.EcoBoss; +import com.willfp.ecobosses.bosses.tick.BossTicker; +import org.bukkit.attribute.Attribute; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class BossBarTicker implements BossTicker { + /** + * The boss bar. + */ + private final BossBar bossBar; + + /** + * The radius that the boss bar should be visible in. + */ + private final double radius; + + /** + * Create new boss bar ticker. + * @param bossBar The boss bar. + * @param radius The radius. + */ + public BossBarTicker(@NotNull final BossBar bossBar, + final double radius) { + this.bossBar = bossBar; + this.radius = radius; + } + + @Override + public void tick(@NotNull final EcoBoss boss, + @NotNull final LivingEntity entity, + final long tick) { + bossBar.setTitle(entity.getCustomName()); + bossBar.setProgress(entity.getHealth() / entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + + if (tick % 40 == 0) { + bossBar.getPlayers().forEach(bossBar::removePlayer); + entity.getNearbyEntities(radius, radius, radius).forEach(entity1 -> { + if (entity1 instanceof Player) { + bossBar.addPlayer((Player) entity1); + } + }); + } + } + + @Override + public void onDeath(@NotNull final EcoBoss boss, + @NotNull final LivingEntity entity, + final long tick) { + bossBar.getPlayers().forEach(bossBar::removePlayer); + bossBar.setVisible(false); + } +} diff --git a/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/HealthPlaceholderTicker.java b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/HealthPlaceholderTicker.java new file mode 100644 index 0000000..e8458d0 --- /dev/null +++ b/eco-core/core-plugin/src/main/java/com/willfp/ecobosses/bosses/tick/tickers/HealthPlaceholderTicker.java @@ -0,0 +1,16 @@ +package com.willfp.ecobosses.bosses.tick.tickers; + +import com.willfp.eco.util.StringUtils; +import com.willfp.ecobosses.bosses.EcoBoss; +import com.willfp.ecobosses.bosses.tick.BossTicker; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; + +public class HealthPlaceholderTicker implements BossTicker { + @Override + public void tick(@NotNull final EcoBoss boss, + @NotNull final LivingEntity entity, + final long tick) { + entity.setCustomName(boss.getDisplayName().replace("%health%", StringUtils.internalToString(entity.getHealth()))); + } +}