diff --git a/src/main/java/fr/openmc/core/ListenersManager.java b/src/main/java/fr/openmc/core/ListenersManager.java index 2f1db18cc..44212fd70 100644 --- a/src/main/java/fr/openmc/core/ListenersManager.java +++ b/src/main/java/fr/openmc/core/ListenersManager.java @@ -5,10 +5,15 @@ import fr.openmc.core.features.itemsadder.SpawnerExtractorListener; import fr.openmc.core.hooks.itemsadder.ItemsAdderHook; import fr.openmc.core.listeners.*; +import fr.openmc.core.registry.ambient.listeners.AmbientFixedTimeListener; import fr.openmc.core.registry.ambient.listeners.AmbientWeatherListener; import fr.openmc.core.registry.ambient.listeners.BiomesOnChunkLoad; import fr.openmc.core.registry.ambient.listeners.CustomAmbientListener; +import fr.openmc.core.registry.lootboxes.listener.DesactivateFireworkDamageListener; +import fr.openmc.core.registry.mobs.listeners.CustomMobBossbarListener; import fr.openmc.core.registry.mobs.listeners.CustomMobDeathListener; +import fr.openmc.core.registry.mobs.listeners.CustomMobLoadListener; +import fr.openmc.core.utils.nms.entity.EntityGlowNMS; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.event.Listener; @@ -34,6 +39,7 @@ public static void init() { new PlayerDeathListener(), new AsyncChatListener(OMCPlugin.getInstance()), new InteractListener(), + new DesactivateFireworkDamageListener(), new BlockPlaceListener(), new EquipableItemListener(), new NoMoreRabbit(), @@ -42,7 +48,11 @@ public static void init() { new BlockBreakListener(), new CustomAmbientListener(), new BiomesOnChunkLoad(), - new AmbientWeatherListener() + new AmbientWeatherListener(), + new AmbientFixedTimeListener(), + new EntityGlowNMS(), + new CustomMobBossbarListener(), + new CustomMobLoadListener() ); if (!OMCPlugin.isUnitTestVersion()) { diff --git a/src/main/java/fr/openmc/core/OMCRegistry.java b/src/main/java/fr/openmc/core/OMCRegistry.java index 9b439d59e..bd583d680 100644 --- a/src/main/java/fr/openmc/core/OMCRegistry.java +++ b/src/main/java/fr/openmc/core/OMCRegistry.java @@ -13,6 +13,7 @@ import io.papermc.paper.plugin.bootstrap.BootstrapContext; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -26,6 +27,7 @@ public final class OMCRegistry { public static CustomAmbientRegistry CUSTOM_AMBIENTS; public static CustomLootboxRegistry CUSTOM_LOOTBOXES; + private static final List LOADED = new ArrayList<>(); private static final List ALL = List.of( new RegistryContext( @@ -54,7 +56,7 @@ public static void bootstrapAll(BootstrapContext context) { if (Arrays.stream(ctx.loadingTypes()) .noneMatch(t -> t == RegistryLoadingType.BOOTSTRAP)) continue; - LifecycleRegistry r = ctx.registry().get(); + LifecycleRegistry r = load(ctx); try { r.bootstrap(context); } catch (IOException e) { @@ -70,7 +72,7 @@ public static void initAll() { if (Arrays.stream(ctx.loadingTypes()) .noneMatch(t -> t == RegistryLoadingType.RUNTIME)) continue; - LifecycleRegistry r = ctx.registry().get(); + LifecycleRegistry r = load(ctx); r.init(); OMCLogger.successFormatted("Registre {} chargé pendant le runtime", r.getClass().getSimpleName()); } @@ -81,17 +83,23 @@ public static void postInitAll() { if (Arrays.stream(ctx.loadingTypes()) .noneMatch(t -> t == RegistryLoadingType.AFTER_IA)) continue; - LifecycleRegistry r = ctx.registry().get(); + LifecycleRegistry r = load(ctx); r.postInit(); OMCLogger.successFormatted("Registre {} chargé après ItemsAdder", r.getClass().getSimpleName()); } } public static void stopAll() { - for (RegistryContext ctx : OMCRegistry.ALL) { - LifecycleRegistry r = ctx.registry().get(); + for (LifecycleRegistry r : LOADED) { r.stop(); OMCLogger.successFormatted("Registre {} stoppé", r.getClass().getSimpleName()); } + LOADED.clear(); + } + + private static LifecycleRegistry load(RegistryContext ctx) { + LifecycleRegistry registry = ctx.registry().get(); + LOADED.add(registry); + return registry; } -} \ No newline at end of file +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/DailyEventsManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/DailyEventsManager.java index 4d687a83f..f4e8e88f3 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/DailyEventsManager.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/DailyEventsManager.java @@ -29,6 +29,7 @@ import java.sql.SQLException; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -70,6 +71,10 @@ public void init() { @Override public void save() { + if (outgoingEvent != null) { + outgoingEvent.getDailyEvent().end(); + } + saveIncomingEventsDB(new IncomingEventsDB(incomingEvents)); } @@ -231,4 +236,16 @@ public static DailyEvent getDailyEvent(String id) { .findFirst() .orElse(null); } + + /** + * Permet d'obtenir le temps restant d'un evenement journalier en secondes + * @param event l'event + * @return le temps restant en secondes + */ + public static double getRemainingTime(DailyEvent event) { + if (!isActiveDailyEvent()) return -1; + if (getActiveDailyEvent() != event) return -1; + + return outgoingEvent.getScheduledStartDate().until(DateUtils.getLocalDateTime(), ChronoUnit.SECONDS); + } } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/commands/DailyEventCommand.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/commands/DailyEventCommand.java index 655fa44b3..94ae60761 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/commands/DailyEventCommand.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/commands/DailyEventCommand.java @@ -30,4 +30,15 @@ public static void forceStartCommand(Player player, DailyEventsManager.getDailyEvent(dailyEvent), DateUtils.getLocalDateTime()); DailyEventsManager.outgoingEvent.getDailyEvent().start(); } + + @Subcommand("forceEnd") + @CommandPermission("omc.admins.commands.dailyevent.forceend") + public static void forceEndCommand(Player player) { + if (DailyEventsManager.outgoingEvent != null) { + // * On arrete l'evenement en cours + DailyEventsManager.endEventTask.cancel(); + DailyEventsManager.endEventTask = null; + DailyEventsManager.outgoingEvent.getDailyEvent().end(); + } + } } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightEvent.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightEvent.java index c246af449..b92042544 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightEvent.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightEvent.java @@ -1,21 +1,31 @@ package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight; +import fr.openmc.api.menulib.Menu; import fr.openmc.core.OMCRegistry; +import fr.openmc.core.bootstrap.features.types.HasListeners; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.listeners.MonsterSpawnLIstener; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.listeners.PlayerKillMonsterListener; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.menu.BloodyNightMenu; import fr.openmc.core.features.events.contents.dailyevents.models.dailyevent.DailyEvent; import fr.openmc.core.features.events.contents.dailyevents.models.dailyevent.HasAmbient; import fr.openmc.core.features.events.contents.dailyevents.models.dailyevent.HasBroadcast; import fr.openmc.core.features.events.contents.dailyevents.models.dailyevent.HasToast; +import fr.openmc.core.features.events.models.HasMenu; import fr.openmc.core.registry.ambient.CustomAmbient; import fr.openmc.core.utils.nms.toast.CustomToastData; import fr.openmc.core.utils.text.messages.TranslationManager; import net.kyori.adventure.text.Component; import net.minecraft.advancements.AdvancementType; import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import java.util.List; +import java.util.Set; -public class BloodyNightEvent extends DailyEvent implements HasToast, HasAmbient, HasBroadcast { +public class BloodyNightEvent extends DailyEvent + implements HasToast, HasAmbient, HasBroadcast, HasListeners, HasMenu { @Override public String getEventId() { return "bloody_night"; @@ -33,16 +43,12 @@ public int getDuration() { @Override public Runnable onStart() { - return () -> { - System.out.println("BLOODY NIGHT START"); - }; + return () -> BloodyNightManager.start(this); } @Override public Runnable onEnd() { - return () -> { - System.out.println("BLOODY NIGHT END"); - }; + return () -> BloodyNightManager.stop(this); } @Override @@ -92,4 +98,17 @@ public Component getStartBroadcast() { public Component getEndBroadcast() { return TranslationManager.translation("feature.dailyevents.bloodynight.broadcast.end"); } + + @Override + public Set getListeners() { + return Set.of( + new PlayerKillMonsterListener(), + new MonsterSpawnLIstener() + ); + } + + @Override + public Menu getInfoMenu(Player player) { + return new BloodyNightMenu(player); + } } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightManager.java new file mode 100644 index 000000000..5f65cffc5 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightManager.java @@ -0,0 +1,189 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.AncientMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.CorruptedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.CursedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.EnragedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireSlave; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.world.LocationUtils; +import org.bukkit.*; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.scheduler.BukkitTask; + +import java.util.concurrent.ThreadLocalRandom; + +public class BloodyNightManager { + // * CONSTANTES + public static final NamespacedKey RAID_MONSTER_KEY = new NamespacedKey("omc_daily_events", "raid_monster"); + + public static final long VAMPIRE_SPAWN_TIME = 20L * 60L * 15; // 15 min + public static final long RAID_INTERVAL = 20L * 60L * 2; // 2 min + + private static BukkitTask raidTask; + private static BukkitTask vampireTask; + private static long lastTime; + + public static void start(BloodyNightEvent event) { + World world = Bukkit.getWorld(event.getWorldEvent()); + if (world == null) return; + + // * Programmation des raids + raidTask = Bukkit.getScheduler().runTaskTimer( + OMCPlugin.getInstance(), + () -> BloodyNightRaidManager.startRaid(world), + 20L * 10, + RAID_INTERVAL + ); + + // * Programmation du boss Vampire + Location vampireSpawnLocation = LocationUtils.getSafeNearbySurface( + LocationUtils.randomLocation( + world.getSpawnLocation(), + world.getWorldBorder().getSize() / 2.0, 10000 + ), + 50); + + vampireTask = Bukkit.getScheduler().runTaskLater( + OMCPlugin.getInstance(), + () -> OMCRegistry.CUSTOM_MOBS.VAMPIRE_BOSS.spawn(vampireSpawnLocation), + VAMPIRE_SPAWN_TIME + ); + + // * Modification des monstres déjà présent dans le monde (uniquement ceux chargé) + applyCorruptedMonsters(world); + + // * Gamerules personalisée + lastTime = world.getTime(); + world.setTime(21000); + world.setGameRule(GameRules.ADVANCE_TIME, false); + world.setGameRule(GameRules.NATURAL_HEALTH_REGENERATION, false); + } + + public static void stop(BloodyNightEvent event) { + World world = Bukkit.getWorld(event.getWorldEvent()); + if (world == null) return; + + // * Fin des raids + if (raidTask != null) { + raidTask.cancel(); + raidTask = null; + } + + // * Fin des raids + if (vampireTask != null) { + vampireTask.cancel(); + vampireTask = null; + } + + // * Supression des monstres devant etre supprimé (ex ceux qui vient des raids) + deleteRaidMonsters(world); + deleteVampireMonsters(world); + + // * Modification des monstres déjà présent dans le monde (uniquement ceux chargé) + desactivateCorruptedMonsters(world); + + // * Gamerules personalisée + world.setTime(lastTime); + world.setGameRule(GameRules.ADVANCE_TIME, true); + world.setGameRule(GameRules.NATURAL_HEALTH_REGENERATION, true); + } + + /** + * Désactive les mobs chargé dans le monde et les remet à leur état normal + * @param world le monde ciblé par la nuit sanglante + */ + private static void desactivateCorruptedMonsters(World world) { + for (Entity entity : world.getEntities()) { + desactivateCorruptedMonster(entity); + } + } + + /** + * Désactive le mob et le remet à son état normal + * @param entity l'entité ciblé à désactiver + */ + public static void desactivateCorruptedMonster(Entity entity) { + if (!(entity instanceof Monster monster)) return; + + CustomMob cm = OMCRegistry.CUSTOM_MOBS.getMob(monster); + if (cm instanceof CorruptedMonster corruptedMonster) { + corruptedMonster.resetToDefault(monster); + } else if (cm instanceof CursedMonster cursedMonster) { + cursedMonster.resetToDefault(monster); + } else if (cm instanceof EnragedMonster enragedMonster) { + enragedMonster.resetToDefault(monster); + } else if (cm instanceof AncientMonster ancientMonster) { + ancientMonster.resetToDefault(monster); + } + } + + /** + * Supprime tout les monstres spawné par les raids + * @param world le monde ciblé par la nuit sanglante + */ + private static void deleteRaidMonsters(World world) { + for (Entity entity : world.getEntities()) { + if (!(entity instanceof Monster monster)) continue; + if (!(entity.getPersistentDataContainer().has(RAID_MONSTER_KEY))) continue; + + monster.remove(); + } + } + + /** + * Supprime tout les monstres spawné par le vampire + * @param world le monde ciblé par la nuit sanglante + */ + private static void deleteVampireMonsters(World world) { + for (Entity entity : world.getEntities()) { + CustomMob customMob = OMCRegistry.CUSTOM_MOBS.getMob(entity); + if (customMob == null) continue; + if (customMob instanceof VampireBoss || customMob instanceof VampireSlave) + entity.remove(); + } + } + + public static final double CORRUPTED_CHANCE = 0.5; + public static final double ENRAGED_CHANCE = 0.3; + public static final double CURSED_CHANCE = 0.15; + public static final double ANCIENT_CHANCE = 0.05; + + /** + * Applique l'effet bloody sur un monstre + * - Corrompu - 60% + * - Enragé - 30% + * - Maudit - 15% + * - Ancien - 5% + * @param entity l'entité à boost + */ + public static void applyBloodyMonster(LivingEntity entity) { + double random = ThreadLocalRandom.current().nextDouble(); + if (random < CORRUPTED_CHANCE) { + OMCRegistry.CUSTOM_MOBS.CORRUPTED_MONSTER.apply(entity); + } else if (random < CORRUPTED_CHANCE + ENRAGED_CHANCE) { + OMCRegistry.CUSTOM_MOBS.ENRAGED_MONSTER.apply(entity); + } else if (random < CORRUPTED_CHANCE + ENRAGED_CHANCE + CURSED_CHANCE) { + OMCRegistry.CUSTOM_MOBS.CURSED_MONSTER.apply(entity); + } else { + OMCRegistry.CUSTOM_MOBS.ANCIENT_MONSTER.apply(entity); + } + } + + /** + * Boost touts les monstres présent dans le monde + * @param world le monde où se passe la blood moon + */ + private static void applyCorruptedMonsters(World world) { + for (Entity entity : world.getEntities()) { + if (!(entity instanceof Monster monster)) continue; + + OMCRegistry.CUSTOM_MOBS.CORRUPTED_MONSTER.apply(monster); + } + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightRaidManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightRaidManager.java new file mode 100644 index 000000000..d02f15743 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/BloodyNightRaidManager.java @@ -0,0 +1,75 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight; + +import fr.openmc.core.utils.world.LocationUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.persistence.PersistentDataType; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class BloodyNightRaidManager { + + private static final int MIN_MOBS_PER_RAID = 7; + private static final int MAX_MOBS_PER_RAID = 14; + + private static final int MAX_SPAWN_RADIUS = 15; + + private static final List BLOODY_MONSTERS_AVAILABLE = List.of( + EntityType.ZOMBIE, + EntityType.SKELETON, + EntityType.SPIDER, + EntityType.CREEPER, + EntityType.HUSK, + EntityType.STRAY, + EntityType.PARCHED, + EntityType.BOGGED + ); + + public static void startRaid(World world) { + for (Player player : world.getPlayers()) { + if (player.getGameMode() == GameMode.SPECTATOR || + player.getGameMode() == GameMode.CREATIVE) continue; + if (player.isDead()) continue; + + spawnRaidAroundPlayer(player); + } + } + + private static void spawnRaidAroundPlayer(Player player) { + World world = player.getWorld(); + + int mobAmount = ThreadLocalRandom.current().nextInt( + MIN_MOBS_PER_RAID, + MAX_MOBS_PER_RAID + 1 + ); + + for (int i = 0; i < mobAmount; i++) { + Location spawnLocation = LocationUtils.getSafeNearbySurface( + LocationUtils.randomLocation( + player.getLocation(), + MAX_SPAWN_RADIUS + ), + 20); + + world.strikeLightningEffect(spawnLocation); + spawnRandomMonster(spawnLocation); + } + } + + private static void spawnRandomMonster(Location location) { + EntityType type = getRandomRaidMonsterType(); + location.getWorld().spawnEntity(location, type, CreatureSpawnEvent.SpawnReason.CUSTOM,s -> + s.getPersistentDataContainer().set( + BloodyNightManager.RAID_MONSTER_KEY, PersistentDataType.BOOLEAN, true)); + + } + + private static EntityType getRandomRaidMonsterType() { + return BLOODY_MONSTERS_AVAILABLE.get(ThreadLocalRandom.current().nextInt(BLOODY_MONSTERS_AVAILABLE.size())); + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/contents/BloodyAmbient.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/ambient/BloodyAmbient.java similarity index 94% rename from src/main/java/fr/openmc/core/registry/ambient/contents/BloodyAmbient.java rename to src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/ambient/BloodyAmbient.java index dde6dc0e4..1de17d4ac 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/contents/BloodyAmbient.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/ambient/BloodyAmbient.java @@ -1,4 +1,4 @@ -package fr.openmc.core.registry.ambient.contents; +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.ambient; import fr.openmc.api.datapacks.builders.BiomeBuilder; import fr.openmc.api.datapacks.builders.EnvironnementAttributeBuilder; @@ -35,6 +35,9 @@ public AmbientBuilder getAmbientBuilder() { obj.addProperty("visual/moon_angle", 67); obj.addProperty("visual/star_brightness", 0.7); + + obj.addProperty("visual/cloud_fog_end_distance", 512.0032); + obj.addProperty("visual/cloud_height", 65); obj.addProperty("visual/cloud_color", "#7e8c2b2b"); @@ -67,6 +70,7 @@ public AmbientBuilder getAmbientBuilder() { .defaultClock("overworld") .timelines("#minecraft:in_overworld") + .hasFixedTime(true, 21000) .hasPrecipitation(false, WeatherType.NONE); } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/enchantments/Vampirism.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/enchantments/Vampirism.java new file mode 100644 index 000000000..44a421113 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/enchantments/Vampirism.java @@ -0,0 +1,161 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.enchantments; + +import fr.openmc.core.features.dream.models.registry.DreamEnchantment; +import fr.openmc.core.utils.text.messages.TranslationManager; +import io.papermc.paper.registry.data.EnchantmentRegistryEntry; +import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import io.papermc.paper.registry.tag.TagKey; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; + +@SuppressWarnings("UnstableApiUsage") +public class Vampirism extends DreamEnchantment implements Listener { + private static final NamespacedKey MAX_HEALTH_MODIFIER_KEY = + new NamespacedKey("omc_daily_events", "vampirism_max_health"); + + @Override + public Key getKey() { + return Key.key("omc_daily_events:vampirism"); + } + + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.enchantment.vampirism.name"); + } + + @Override + public TagKey getSupportedItems() { + return ItemTypeTagKeys.SWORDS; + } + + @Override + public int getMaxLevel() { + return 3; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public int getAnvilCost() { + return 4; + } + + @Override + public EnchantmentRegistryEntry.EnchantmentCost getMinimumCost() { + return EnchantmentRegistryEntry.EnchantmentCost.of(1, 2); + } + + @Override + public EnchantmentRegistryEntry.EnchantmentCost getMaximalmCost() { + return EnchantmentRegistryEntry.EnchantmentCost.of(4, 5); + } + + @EventHandler + public void onAttack(EntityDamageByEntityEvent event) { + if (!(event.getDamager() instanceof Player player)) return; + if (!(event.getEntity() instanceof LivingEntity)) return; + + Enchantment enchant = this.getEnchantment(); + if (enchant == null) return; + + ItemStack item = player.getInventory().getItemInMainHand(); + if (!item.getEnchantments().containsKey(enchant)) return; + int level = item.getEnchantmentLevel(enchant); + + double damageDealt = event.getFinalDamage(); + if (damageDealt <= 0) return; + + healPlayer(player, damageDealt); + + if (level >= 2) { + double max = getMaxHealthForLevel(level); + + AttributeInstance maxHealthAttr = player.getAttribute(Attribute.MAX_HEALTH); + if (maxHealthAttr == null) return; + + if (maxHealthAttr.getModifier(MAX_HEALTH_MODIFIER_KEY) != null) return; + + maxHealthAttr.addModifier(new AttributeModifier( + MAX_HEALTH_MODIFIER_KEY, + max - maxHealthAttr.getValue(), + AttributeModifier.Operation.ADD_NUMBER + )); + } + } + + @EventHandler + public void onItemHeldChange(PlayerItemHeldEvent event) { + Player player = event.getPlayer(); + + Enchantment enchant = this.getEnchantment(); + if (enchant == null) return; + + ItemStack item = player.getInventory().getItem(event.getPreviousSlot()); + if (item == null) return; + if (!item.getEnchantments().containsKey(enchant)) return; + + int level = item.getEnchantmentLevel(enchant); + + if (level < 2) return; + + AttributeInstance maxHealthAttr = player.getAttribute(Attribute.MAX_HEALTH); + if (maxHealthAttr == null) return; + maxHealthAttr.removeModifier(MAX_HEALTH_MODIFIER_KEY); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + AttributeInstance maxHealthAttr = player.getAttribute(Attribute.MAX_HEALTH); + if (maxHealthAttr == null) return; + maxHealthAttr.removeModifier(MAX_HEALTH_MODIFIER_KEY); + + } + + /** + * Donne 1/8 de vie en fonction des dégâts infligés à l'attaquant. + * @param player le joueur ciblé + * @param damageDealt le nombre de dégats mis + */ + private void healPlayer(Player player, double damageDealt) { + AttributeInstance maxHealthAttr = player.getAttribute(Attribute.MAX_HEALTH); + if (maxHealthAttr == null) return; + + double maxHealth = maxHealthAttr.getValue(); + double heal = damageDealt * 0.125; + double newHealth = Math.min(maxHealth, player.getHealth() + heal); + + player.setHealth(newHealth); + } + + /** + * Donne la vie maximale que le joueur peut avoir en fonction du niveau de l'enchantement. + * @param level le niveau de l'enchantement + * @return la vie maximale + */ + private double getMaxHealthForLevel(int level) { + return switch (level) { + case 2 -> 25.0; + case 3 -> 30.0; + default -> 0.0; + }; + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/VampireLootTable.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/VampireLootTable.java new file mode 100644 index 000000000..41b6579d0 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/VampireLootTable.java @@ -0,0 +1,40 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import org.bukkit.inventory.ItemType; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class VampireLootTable extends CustomLootTable { + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.vampire_boss"); + } + + @Override + public String getNamespace() { + return "omc_daily_events.vampire_boss"; + } + + @Override + public Set getLoots() { + return new LinkedHashSet<>(List.of( + new ItemLoot(OMCRegistry.CUSTOM_ITEMS.AYWENITE_BLOCK,1, 1, 5), + new ItemLoot(ItemType.OMINOUS_BOTTLE.createItemStack( + o -> o.setAmplifier(4)),0.6, 1), + new ItemLoot(OMCRegistry.CUSTOM_ITEMS.VAMPIRE_HEAD,0.50, 1), + new ItemLoot( + OMCRegistry.CUSTOM_ENCHANTS.VAMPIRISM.getEnchantedBookItem(1, 2), + 0.25, + 1 + ) + )); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/AncientMobLootTable.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/AncientMobLootTable.java new file mode 100644 index 000000000..795ea93b1 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/AncientMobLootTable.java @@ -0,0 +1,36 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class AncientMobLootTable extends CustomLootTable { + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.ancient_mob"); + } + + @Override + public String getNamespace() { + return "omc_daily_events:ancient_mobs"; + } + + @Override + public Set getLoots() { + return new LinkedHashSet<>(List.of( + new ItemLoot(Material.IRON_BLOCK,0.3, 3, 6), + new ItemLoot(Material.GOLD_BLOCK,0.2, 1, 2), + new ItemLoot(Material.DIAMOND_BLOCK,0.13, 1), + new ItemLoot(Material.NETHERITE_SCRAP,0.1, 2), + new ItemLoot(OMCRegistry.CUSTOM_ENCHANTS.VAMPIRISM.getEnchantedBookItem(1),0.02, 1) + )); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CorruptedMobLootTable.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CorruptedMobLootTable.java new file mode 100644 index 000000000..3f3ba45b6 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CorruptedMobLootTable.java @@ -0,0 +1,36 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class CorruptedMobLootTable extends CustomLootTable { + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.corrupted_mob"); + } + + @Override + public String getNamespace() { + return "omc_daily_events:corrupted_mobs"; + } + + @Override + public Set getLoots() { + return new LinkedHashSet<>(List.of( + new ItemLoot(Material.IRON_INGOT,0.4, 1, 4), + new ItemLoot(Material.GOLD_INGOT,0.2, 1, 3), + new ItemLoot(OMCRegistry.CUSTOM_ITEMS.AYWENITE,0.2, 1, 3), + new ItemLoot(Material.DIAMOND,0.07, 1, 2), + new ItemLoot(Material.GOLDEN_APPLE,0.06, 1, 3) + )); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CursedMobLootTable.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CursedMobLootTable.java new file mode 100644 index 000000000..1ac954395 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/CursedMobLootTable.java @@ -0,0 +1,42 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.inventory.ItemType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class CursedMobLootTable extends CustomLootTable { + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.cursed_mob"); + } + + @Override + public String getNamespace() { + return "omc_daily_events:cursed_mobs"; + } + + @Override + public Set getLoots() { + return new LinkedHashSet<>(List.of( + new ItemLoot(Material.IRON_BLOCK,0.3, 1, 2), + new ItemLoot(Material.GOLD_BLOCK,0.2, 1, 2), + new ItemLoot(OMCRegistry.CUSTOM_ITEMS.AYWENITE_BLOCK,0.2, 2, 4), + new ItemLoot(Material.DIAMOND,0.2, 1, 2), + new ItemLoot(ItemType.SPLASH_POTION.createItemStack(p -> + p.addCustomEffect(new PotionEffect(PotionEffectType.INSTANT_HEALTH, 5, 1), true)),0.13, 1), + new ItemLoot(Material.DIAMOND_BLOCK,0.1, 1), + new ItemLoot(Material.NETHERITE_SCRAP,0.04, 1) + )); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/EnragedMobLootTable.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/EnragedMobLootTable.java new file mode 100644 index 000000000..7768ce85e --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/loottable/bloodymob/EnragedMobLootTable.java @@ -0,0 +1,41 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.inventory.ItemType; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class EnragedMobLootTable extends CustomLootTable { + @Override + public Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.enraged_mob"); + } + + @Override + public String getNamespace() { + return "omc_daily_events:enraged_mobs"; + } + + @Override + public Set getLoots() { + return new LinkedHashSet<>(List.of( + new ItemLoot(OMCRegistry.CUSTOM_ITEMS.AYWENITE,0.4, 4, 8), + new ItemLoot(Material.IRON_BLOCK,0.23, 1), + new ItemLoot(Material.GOLDEN_APPLE,0.2, 1, 2), + new ItemLoot(Material.GOLD_BLOCK,0.16, 1), + new ItemLoot(ItemType.OMINOUS_BOTTLE.createItemStack(o -> + o.setAmplifier(4)),0.13, 1, 2), + new ItemLoot(Material.DIAMOND,0.1, 1, 2), + new ItemLoot(Material.DIAMOND_BLOCK,0.01, 1), + new ItemLoot(Material.NETHERITE_SCRAP,0.004, 1) + )); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/AbstractVampireBat.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/AbstractVampireBat.java new file mode 100644 index 000000000..278f0cbfc --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/AbstractVampireBat.java @@ -0,0 +1,157 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.registry.mobs.CustomMobAttribute; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.Bat; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.util.Comparator; + +public abstract class AbstractVampireBat extends CustomMob { + + private static final double SPEED = 0.55; + private static final double SEARCH_RADIUS = 30.0; + + private static final int MAX_LIFETIME_TICKS = 20 * 15; + + public AbstractVampireBat(String id, String name) { + super( + id, + name, + Bat.class, + 10.0, + 0.0, + new CustomMobAttribute(Attribute.KNOCKBACK_RESISTANCE, 1.0) + ); + } + + public Bat spawn(Location location, Player target) { + Bat bat = getPreBuildMob(location.clone()); + + bat.setAwake(true); + bat.setPersistent(false); + + startTargeting(bat, target); + + return bat; + } + + @Override + public void onDeath(CustomMob thisMob, EntityDeathEvent event) { + if (!(event.getEntity() instanceof Bat bat)) return; + + impactAndRemoveBat(bat, null); + } + + // * a override + public void onImpact(Bat bat, Player target, Location impactLocation) {} + + private void impactAndRemoveBat(Bat bat, Player target) { + Location impactLocation = bat.getLocation().clone(); + + onImpact(bat, target, impactLocation); + removeBat(bat); + } + + private void startTargeting(Bat bat, Player initialTarget) { + new BukkitRunnable() { + private Player target = initialTarget; + private int elapsedTicks; + + @Override + public void run() { + if (!isBatAlive(bat)) { + cancel(); + return; + } + + elapsedTicks += 2; + + if (elapsedTicks >= MAX_LIFETIME_TICKS) { + removeBat(bat); + cancel(); + return; + } + + if (!isValidTarget(bat, target)) { + target = findNearestTarget(bat); + + if (target == null) { + removeBat(bat); + cancel(); + return; + } + } + + Location targetLocation = target.getEyeLocation(); + Location batLocation = bat.getLocation(); + + BoundingBox impactBox = target.getBoundingBox().expand(0.2); + + if (impactBox.contains(batLocation.toVector())) { + impactAndRemoveBat(bat, target); + cancel(); + return; + } + + Vector direction = targetLocation.toVector() + .subtract(batLocation.toVector()); + + if (direction.lengthSquared() > 0.001) { + bat.setVelocity(direction.normalize().multiply(SPEED)); + } + + playTravelParticles(bat); + } + }.runTaskTimer(OMCPlugin.getInstance(), 0L, 2L); + } + + public void playTravelParticles(Bat bat) { + ParticleUtils.sendParticlePacket( + Particle.SMOKE, + bat.getLocation(), + 32 + ); + } + + private Player findNearestTarget(Bat bat) { + return bat.getWorld() + .getNearbyPlayers(bat.getLocation(), SEARCH_RADIUS) + .stream() + .filter(player -> isValidTarget(bat, player)) + .min(Comparator.comparingDouble(player -> + player.getLocation().distanceSquared(bat.getLocation()) + )) + .orElse(null); + } + + private boolean isValidTarget(Bat bat, Player player) { + return player != null + && player.isValid() + && !player.isDead() + && player.getWorld().equals(bat.getWorld()) + && player.getGameMode() != GameMode.CREATIVE + && player.getGameMode() != GameMode.SPECTATOR; + } + + private boolean isBatAlive(Bat bat) { + return bat != null + && bat.isValid() + && !bat.isDead(); + } + + private void removeBat(Bat bat) { + unregisterAsCustomMob(bat); + bat.remove(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/ExplosiveVampireBat.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/ExplosiveVampireBat.java new file mode 100644 index 000000000..967d47678 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/ExplosiveVampireBat.java @@ -0,0 +1,62 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat; + +import fr.openmc.core.utils.bukkit.ParticleUtils; +import org.bukkit.*; +import org.bukkit.entity.Bat; +import org.bukkit.entity.Player; + +public class ExplosiveVampireBat extends AbstractVampireBat { + + public ExplosiveVampireBat(String id) { + super(id, + "Chauve-souris explosive" + ); + } + + @Override + public void playTravelParticles(Bat bat) { + ParticleUtils.sendParticlePacket( + Particle.FLAME, + bat.getLocation(), + 32 + ); + + ParticleUtils.sendParticlePacket( + Particle.SMOKE, + bat.getLocation(), + 32 + ); + } + + @Override + public void onImpact(Bat bat, Player target, Location impactLocation) { + World world = impactLocation.getWorld(); + + Particle.EXPLOSION.builder() + .location(impactLocation) + .count(5) + .offset(0.5, 0.5, 0.5) + .receivers(40, true) + .spawn(); + + world.playSound( + impactLocation, + Sound.ENTITY_GENERIC_EXPLODE, + SoundCategory.HOSTILE, + 2.0F, + 0.8F + ); + + for (Player player : world.getNearbyPlayers(impactLocation, 3.0)) { + if (!canDamage(player)) continue; + + player.damage(4, bat); + } + } + + private boolean canDamage(Player player) { + return !player.isDead() + && player.getGameMode() != GameMode.CREATIVE + && player.getGameMode() != GameMode.SPECTATOR; + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/LevitationVampireBat.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/LevitationVampireBat.java new file mode 100644 index 000000000..ab8de5d95 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/LevitationVampireBat.java @@ -0,0 +1,74 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat; + +import fr.openmc.core.utils.bukkit.ParticleUtils; +import org.bukkit.*; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Bat; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class LevitationVampireBat extends AbstractVampireBat { + + public LevitationVampireBat(String id) { + super(id, + "Chauve-souris de lévitation" + ); + } + + @Override + public void playTravelParticles(Bat bat) { + ParticleUtils.sendParticlePacket( + Particle.END_ROD, + bat.getLocation(), + 32 + ); + } + + @Override + public void onImpact(Bat bat, Player target, Location impactLocation) { + impactLocation.getWorld().spawn( + impactLocation, + AreaEffectCloud.class, + c -> { + c.setRadius(4.0F); + c.setDuration(20 * 5); + c.setWaitTime(0); + + c.setRadiusPerTick(-c.getRadius() / c.getDuration()); + + c.setParticle(Particle.ENTITY_EFFECT, Color.fromRGB(60, 160, 40)); + + c.addCustomEffect( + new PotionEffect( + PotionEffectType.LEVITATION, + 20 * 5, + 1, + false, + true, + true + ), + true + ); + } + ); + + + if (target != null) + target.getWorld().playSound( + target.getLocation(), + Sound.ENTITY_SHULKER_SHOOT, + SoundCategory.HOSTILE, + 1.5F, + 0.8F + ); + + Particle.END_ROD.builder() + .location(impactLocation) + .count(40) + .offset(1.0, 1.5, 1.0) + .extra(0.1) + .receivers(40, true) + .spawn(); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/PoisonVampireBat.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/PoisonVampireBat.java new file mode 100644 index 000000000..1c017f147 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bat/PoisonVampireBat.java @@ -0,0 +1,77 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat; + +import org.bukkit.*; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Bat; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class PoisonVampireBat extends AbstractVampireBat { + + public PoisonVampireBat(String id) { + super(id, + "Chauve-souris empoisonneuse" + ); + } + + @Override + public void playTravelParticles(Bat bat) { + Particle.ENTITY_EFFECT.builder() + .location(bat.getLocation()) + .count(4) + .offset(0.2, 0.2, 0.2) + .extra(0.01) + .data(Color.fromRGB(60, 160, 40)) + .receivers(32, true) + .spawn(); + } + + @Override + public void onImpact(Bat bat, Player target, Location impactLocation) { + World world = impactLocation.getWorld(); + + world.spawn( + impactLocation, + AreaEffectCloud.class, + c -> { + c.setRadius(4.0F); + c.setDuration(20 * 5); + c.setWaitTime(0); + + c.setRadiusPerTick(-c.getRadius() / c.getDuration()); + + c.setParticle(Particle.ENTITY_EFFECT, Color.fromRGB(60, 160, 40)); + + c.addCustomEffect( + new PotionEffect( + PotionEffectType.POISON, + 20 * 4, + 1, + false, + true, + true + ), + true + ); + } + ); + + world.playSound( + impactLocation, + Sound.ENTITY_SPLASH_POTION_BREAK, + SoundCategory.HOSTILE, + 1.5F, + 0.6F + ); + + Particle.ENTITY_EFFECT.builder() + .location(impactLocation) + .count(50) + .offset(2.0, 1.0, 2.0) + .extra(0.05) + .data(Color.fromRGB(60, 160, 40)) + .receivers(40, true) + .spawn(); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/AncientMonster.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/AncientMonster.java new file mode 100644 index 000000000..abbf10087 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/AncientMonster.java @@ -0,0 +1,86 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.bukkit.EntityUtils; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import fr.openmc.core.utils.nms.entity.EntityGlowNMS; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Monster; + +public class AncientMonster extends CustomMob { + private static final AttributeModifier HEALTH_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "ancient_health"), + 5.00, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier ATTACK_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "ancient_attack"), + 1.50, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier SPEED_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "ancient_speed"), + 0.35, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier FOLLOW_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "ancient_follow"), + 1.00, + AttributeModifier.Operation.ADD_SCALAR + ); + + public AncientMonster(String id) { + super( + id, + "Ancient", + Monster.class, + 3, + 1, + OMCRegistry.CUSTOM_LOOT_TABLES.ANCIENT_MOB.rollLoots() + ); + } + + @Override + public void apply(Monster entity) { + registerAsCustomMob(entity); + + entity.setCustomNameVisible(false); + + EntityUtils.addModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.addModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + // * SFX + ParticleUtils.spawnDispersingParticles(entity.getLocation(), + Particle.EXPLOSION_EMITTER, + 5, 35, 0.1D, null); + EntityGlowNMS.setGlowingColor(entity, TeamColor.YELLOW); + } + + public void resetToDefault(Monster entity) { + unregisterAsCustomMob(entity); + + entity.setCustomNameVisible(true); + + EntityUtils.removeModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + EntityGlowNMS.removeGlowing(entity); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CorruptedMonster.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CorruptedMonster.java new file mode 100644 index 000000000..18593c7da --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CorruptedMonster.java @@ -0,0 +1,83 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.bukkit.EntityUtils; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import fr.openmc.core.utils.nms.entity.EntityGlowNMS; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Monster; + +public class CorruptedMonster extends CustomMob { + private static final AttributeModifier HEALTH_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "corrupted_health"), + 0.75, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier ATTACK_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "corrupted_attack"), + 0.25, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier SPEED_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "corrupted_speed"), + 0.15, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier FOLLOW_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "corrupted_follow"), + 0.25, + AttributeModifier.Operation.ADD_SCALAR + ); + + public CorruptedMonster(String id) { + super(id, + "Corrupted", + Monster.class, + 1, + 1, + OMCRegistry.CUSTOM_LOOT_TABLES.CORRUPTED_MOB.rollLoots() + ); + } + + @Override + public void apply(Monster entity) { + registerAsCustomMob(entity); + + entity.setCustomNameVisible(false); + + EntityUtils.addModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.addModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + // * SFX + ParticleUtils.spawnDispersingParticles(entity.getLocation(), + Particle.TRIAL_SPAWNER_DETECTION, + 10, 35, 0.1D, null); + EntityGlowNMS.setGlowingColor(entity, TeamColor.DARK_RED); + } + + public void resetToDefault(Monster entity) { + unregisterAsCustomMob(entity); + + EntityUtils.removeModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + EntityGlowNMS.removeGlowing(entity); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CursedMonster.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CursedMonster.java new file mode 100644 index 000000000..f4eb13915 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/CursedMonster.java @@ -0,0 +1,103 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.bukkit.EntityUtils; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import fr.openmc.core.utils.nms.entity.EntityGlowNMS; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Monster; + +import java.util.concurrent.ThreadLocalRandom; + +public class CursedMonster extends CustomMob { + private static final AttributeModifier HEALTH_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "cursed_health"), + 3, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier ATTACK_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "cursed_attack"), + 0.90, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier SPEED_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "cursed_speed"), + -0.10, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier FOLLOW_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "cursed_follow"), + 0.70, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier SCALE_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "cursed_scale"), + 1, + AttributeModifier.Operation.ADD_SCALAR + ); + + public CursedMonster(String id) { + super(id, + "Cursed", + Monster.class, + 1, + 1, + OMCRegistry.CUSTOM_LOOT_TABLES.CURSED_MOB.rollLoots() + ); + } + + @Override + public void apply(Monster entity) { + registerAsCustomMob(entity); + + entity.setCustomNameVisible(false); + + EntityUtils.addModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.addModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.SCALE, SCALE_MODIFIER); + + if (ThreadLocalRandom.current().nextBoolean() && entity instanceof Creeper) + entity.getWorld().strikeLightningEffect(entity.getLocation()); + + // * SFX + ParticleUtils.spawnDispersingParticles(entity.getLocation(), + Particle.REVERSE_PORTAL, + 10, 35, 0.1D, null); + EntityGlowNMS.setGlowingColor(entity, TeamColor.DARK_PURPLE); + } + + public void resetToDefault(Monster entity) { + unregisterAsCustomMob(entity); + + entity.setCustomNameVisible(true); + + EntityUtils.removeModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.removeModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.SCALE, SCALE_MODIFIER); + + EntityGlowNMS.removeGlowing(entity); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/EnragedMonster.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/EnragedMonster.java new file mode 100644 index 000000000..9afd39834 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/bloodytypes/EnragedMonster.java @@ -0,0 +1,93 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.bukkit.EntityUtils; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import fr.openmc.core.utils.nms.entity.EntityGlowNMS; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Monster; + +public class EnragedMonster extends CustomMob { + private static final AttributeModifier HEALTH_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "enraged_health"), + 1.75, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier ATTACK_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "enraged_attack"), + 0.55, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier SPEED_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "enraged_speed"), + 0.30, + AttributeModifier.Operation.ADD_SCALAR + ); + + private static final AttributeModifier FOLLOW_MODIFIER = new AttributeModifier( + new NamespacedKey("omc_daily_events", "enraged_follow"), + 0.45, + AttributeModifier.Operation.ADD_SCALAR + ); + + public EnragedMonster(String id) { + super(id, + "Enraged", + Monster.class, + 1, + 1, + OMCRegistry.CUSTOM_LOOT_TABLES.ENRAGED_MOB.rollLoots() + ); + } + + @Override + public void apply(Monster entity) { + registerAsCustomMob(entity); + + entity.setCustomNameVisible(false); + + EntityUtils.addModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.addModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.addModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + if (entity instanceof Creeper) + entity.getWorld().strikeLightningEffect(entity.getLocation()); + + // * SFX + ParticleUtils.spawnDispersingParticles(entity.getLocation(), + Particle.ANGRY_VILLAGER, + 10, 35, 0.1D, null); + EntityGlowNMS.setGlowingColor(entity, TeamColor.RED); + } + + public void resetToDefault(Monster entity) { + unregisterAsCustomMob(entity); + + entity.setCustomNameVisible(true); + + EntityUtils.removeModifierIfPresent(entity, Attribute.MAX_HEALTH, HEALTH_MODIFIER); + AttributeInstance attrInst = entity.getAttribute(Attribute.MAX_HEALTH); + if (attrInst == null) return; + entity.setHealth(attrInst.getValue()); + + EntityUtils.removeModifierIfPresent(entity, Attribute.ATTACK_DAMAGE, ATTACK_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.MOVEMENT_SPEED, SPEED_MODIFIER); + EntityUtils.removeModifierIfPresent(entity, Attribute.FOLLOW_RANGE, FOLLOW_MODIFIER); + + EntityGlowNMS.removeGlowing(entity); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBoss.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBoss.java new file mode 100644 index 000000000..4f18a812f --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBoss.java @@ -0,0 +1,238 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.attacks.BloodExplosionAttack; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.attacks.VampireBatAttack; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.tasks.SummoningEffectTask; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.tasks.VampireAttackTask; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.registry.mobs.CustomMobAttribute; +import fr.openmc.core.registry.mobs.MobAttack; +import fr.openmc.core.registry.mobs.options.MobBossbarImpl; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import fr.openmc.core.utils.text.messages.TranslationManager; +import fr.openmc.core.utils.world.LocationUtils; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import lombok.Getter; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.*; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import static fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.tasks.SummoningEffectTask.SUMMON_EFFECT_INTERVAL_TICKS; + +@SuppressWarnings("UnstableApiUsage") +@Getter +public class VampireBoss extends CustomMob implements MobBossbarImpl, Listener { + private final Random random = ThreadLocalRandom.current(); + private final List attacks; + private Mannequin mannequin; + + public VampireBoss(String id) { + super(id, + "Vampire", + Mannequin.class, + 1000, + 20, + 0.1, + new CustomMobAttribute(Attribute.SCALE, 20), + new CustomMobAttribute(Attribute.KNOCKBACK_RESISTANCE, 1) + ); + + this.attacks = new ArrayList<>(List.of( + new BloodExplosionAttack(this), + new VampireBatAttack(this) + )); + } + + @Override + public Mannequin spawn(Location spawnLocation) { + startSummoning(spawnLocation); + return null; + } + + @Override + public void onDeath(CustomMob thisMob, EntityDeathEvent event) { + LivingEntity entity = event.getEntity(); + + event.getDrops().clear(); + event.setDroppedExp(0); + + Location deathLocation = entity.getLocation().clone(); + + ParticleUtils.spawnCubeParticles( + deathLocation.clone().add(0, 4, 0), + Particle.EXPLOSION_EMITTER, + 5.0, + 6.0, + 5.0, + 25, + 100, + null + ); + + VampireBossLootManager.giveContributions(thisMob); + + VampireBossLootManager.damageContributions.clear(); + mannequin = null; + } + + private void startSummoning(Location at) { + World world = at.getWorld(); + for (Player player : world.getPlayers()) { + player.sendMessage(TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.coming" + )); + } + + new SummoningEffectTask(this, at).runTaskTimer( + OMCPlugin.getInstance(), + 0L, + SUMMON_EFFECT_INTERVAL_TICKS + ); + } + + public void summon(Location at) { + Mannequin mannequin = this.getPreBuildMob(at); + mannequin.setDescription(Component.empty()); + mannequin.setProfile(ResolvableProfile.resolvableProfile() + .uuid(UUID.fromString("2add34b4-2d09-4204-a458-6251b0d24661")) + .build() + ); + + + new VampireAttackTask(this).runTaskTimer( + OMCPlugin.getInstance(), + 20L, + 20L + ); + + World world = at.getWorld(); + + ParticleUtils.spawnCubeParticles( + at.clone().add(0, 2, 0), + Particle.EXPLOSION_EMITTER, + 1.0, + 2.0, + 1.0, + 10, + 100, + null + ); + + ParticleUtils.spawnCubeParticles( + at.clone().add(0, 2, 0), + Particle.LAVA, + 3.0, + 5.0, + 3.0, + 200, + 100, + null + ); + + world.playSound( + at, + Sound.ENTITY_WITHER_SPAWN, + SoundCategory.HOSTILE, + 5.0F, + 0.6F + ); + + for (Player player : world.getPlayers()) { + player.sendMessage( + TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.summoned", + Component.text(Math.round(at.getX()), NamedTextColor.RED), + Component.text(Math.round(at.getY()), NamedTextColor.RED), + Component.text(Math.round(at.getZ()), NamedTextColor.RED) + ) + ); + } + + this.mannequin = mannequin; + } + + @EventHandler(ignoreCancelled = true) + public void onVampireDamageByPlayer(EntityDamageByEntityEvent event) { + Entity victim = event.getEntity(); + + CustomMob mob = OMCRegistry.CUSTOM_MOBS.getMob(victim); + if (!(victim instanceof LivingEntity livingVictim)) return; + if (mob == null || !mob.getId().equals(this.getId())) return; + + Player player = getDamagerPlayer(event); + if (player == null) return; + + double effectiveDamage = Math.min(event.getFinalDamage(), livingVictim.getHealth()); + if (effectiveDamage > 0) { + double actualDamage = 0; + + if (VampireBossLootManager.damageContributions.containsKey(player.getUniqueId())) { + actualDamage = VampireBossLootManager.damageContributions.get(player.getUniqueId()); + } + + VampireBossLootManager.damageContributions.put(player.getUniqueId(), actualDamage + effectiveDamage); + } + + if (ThreadLocalRandom.current().nextDouble() < 0.25) { + Entity spawned = OMCRegistry.CUSTOM_MOBS.VAMPIRE_SLAVE.spawn( + LocationUtils.randomLocation(player.getLocation(), 3.0)); + if (spawned instanceof Zombie zombie) + zombie.setTarget(player); + } + } + + @Override + public Component getBossBarName(LivingEntity entity) { + return TranslationManager.translation("feature.dailyevents.bloody_night.vampire_boss.name"); + } + + @Override + public BossBar.Color getBossBarColor() { + return BossBar.Color.RED; + } + + @Override + public BossBar.Overlay getBossBarOverlay() { + return BossBar.Overlay.NOTCHED_6; + } + + @Override + public double getBossBarViewRadius() { + return 60; + } + + public void pickRandomAttack() { + if (attacks.isEmpty()) { + return; + } + + attacks.get(random.nextInt(attacks.size())).execute(); + } + + private Player getDamagerPlayer(EntityDamageByEntityEvent event) { + Entity damager = event.getDamager(); + if (damager instanceof Player player) return player; + + if (damager instanceof Projectile projectile && projectile.getShooter() instanceof Player player) + return player; + + event.setCancelled(true); + return null; + } +} + diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBossLootManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBossLootManager.java new file mode 100644 index 000000000..fba8687de --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireBossLootManager.java @@ -0,0 +1,173 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.economy.EconomyManager; +import fr.openmc.core.features.events.contents.dailyevents.DailyEventsManager; +import fr.openmc.core.features.leaderboards.LeaderboardManager; +import fr.openmc.core.features.mailboxes.MailboxManager; +import fr.openmc.core.registry.loottable.CustomLootTable; +import fr.openmc.core.registry.loottable.loots.CustomLoot; +import fr.openmc.core.registry.loottable.loots.ItemLoot; +import fr.openmc.core.registry.loottable.loots.MoneyLoot; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.cache.CacheOfflinePlayer; +import fr.openmc.core.utils.cache.PlayerNameCache; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +import static fr.openmc.core.features.mailboxes.utils.MailboxUtils.getHoverEvent; + +public class VampireBossLootManager { + + public static final Map damageContributions = new HashMap<>(); + private static final CustomLootTable VAMPIRE_BOSS_LOOT_TABLE = OMCRegistry.CUSTOM_LOOT_TABLES.VAMPIRE; + + public static void giveContributions(CustomMob mob) { + double totalHealth = mob.getHealth(); + + Map orderedMap = damageContributions.entrySet() + .stream() + .sorted((entry1, entry2) -> Double.compare( + entry2.getValue(), + entry1.getValue() + )) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + )); + + + World world = DailyEventsManager.BLOODY_NIGHT.getWorld(); + if (world == null) return; + + if (orderedMap.isEmpty()) { + broadcastToWorld(world, TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.defeated.no_body" + )); + return; + } + + int rankInt = 0; + + + broadcastToWorld(world, TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.defeated.start" + )); + + for (Map.Entry entry : orderedMap.entrySet()) { + if (rankInt == 1 || rankInt == 2 || rankInt == 3) { + broadcastToWorld(world, TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.defeated.rank", + Component.text("#"+ rankInt + 1, LeaderboardManager.getRankColor(rankInt + 1)), + PlayerNameCache.name(entry.getKey()).color(NamedTextColor.GOLD), + Component.text(String.format("%.1f", entry.getValue()), NamedTextColor.RED) + )); + } else { + OfflinePlayer offlinePlayer = CacheOfflinePlayer.getOfflinePlayer(entry.getKey()); + if (offlinePlayer == null) continue; + if (!offlinePlayer.isOnline()) continue; + Player player = offlinePlayer.getPlayer(); + if (player == null) continue; + + player.sendMessage(TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.defeated.midle", + Component.text(rankInt + 1, LeaderboardManager.getRankColor(rankInt + 1)), + PlayerNameCache.name(entry.getKey()).color(NamedTextColor.GOLD), + Component.text(String.format("%.1f", entry.getValue()), NamedTextColor.RED) + )); + } + + UUID playerUUID = entry.getKey(); + double damage = entry.getValue(); + double participation = damage / totalHealth; + + if (participation < 0.01) continue; + + double chanceMultiplier = getChanceMultiplier(participation); + giveLootToContributor(playerUUID, chanceMultiplier); + + rankInt++; + } + + broadcastToWorld(world, TranslationManager.translation( + "feature.dailyevents.bloody_night.vampire_boss.defeated.end")); + + Component messageMail = TranslationManager.translation("feature.dailyevents.bloody_night.vampire_boss.mail.received") + .appendNewline() + .append(TranslationManager.translation("feature.dailyevents.bloody_night.vampire_boss.mail.click")) + .clickEvent(ClickEvent.runCommand("mailbox")) + .hoverEvent(getHoverEvent(TranslationManager.translationString("feature.dailyevents.bloody_night.vampire_boss.mail.hover"))) + .append(TranslationManager.translation("feature.dailyevents.bloody_night.vampire_boss.mail.open_mailbox")); + + broadcastToWorld(world, messageMail); + } + + private static void giveLootToContributor(UUID playerUUID, double chanceMultiplier) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + OfflinePlayer offlinePlayer = CacheOfflinePlayer.getOfflinePlayer(playerUUID); + + Player player = offlinePlayer.getPlayer(); + List rewards = new ArrayList<>(); + + for (CustomLoot loot : VAMPIRE_BOSS_LOOT_TABLE.getLoots()) { + if (!(loot instanceof ItemLoot) && !(loot instanceof MoneyLoot)) continue; + + double finalChance = loot.getChance() * chanceMultiplier; + + if (finalChance > 1.0) { + finalChance = 1.0; + } else if (finalChance < 0.0) { + finalChance = 0.0; + } + + if (random.nextDouble() > finalChance) continue; + + int amount = -1; + if (loot instanceof ItemLoot itemLoot) { + ItemStack itemReward = itemLoot.getRepresentativeItem(); + amount = itemReward.getAmount(); + rewards.add(itemReward); + } else if (loot instanceof MoneyLoot moneyLoot) { + EconomyManager.addBalance(playerUUID, moneyLoot.getMoney()); + } + + if (player != null && offlinePlayer.isOnline()) { + loot.sendLootMessage(player, amount); + } + } + + MailboxManager.sendItemsToOfflinePlayer(offlinePlayer, rewards.toArray(ItemStack[]::new)); + } + + private static double getChanceMultiplier(double participation) { + if (participation >= 0.75) { + return 1.0; + } else if (participation >= 0.50) { + return 0.80; + } else if (participation >= 0.25) { + return 0.60; + } else if (participation >= 0.10) { + return 0.40; + } else { + return 0.25; + } + } + + private static void broadcastToWorld(World world, Component component) { + for (Player player : world.getPlayers()) { + player.sendMessage(component); + } + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireSlave.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireSlave.java new file mode 100644 index 000000000..50e89968e --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/VampireSlave.java @@ -0,0 +1,31 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.utils.RandomUtils; +import org.bukkit.Location; +import org.bukkit.entity.Zombie; + +public class VampireSlave extends CustomMob { + public VampireSlave(String id) { + super(id, + "Esclave du Vampire", + Zombie.class, + 16, + 8, + RandomUtils.randomBetween(0.2, 0.35) + ); + } + + @Override + public Zombie spawn(Location spawnLocation) { + Zombie zombie = this.getPreBuildMob(spawnLocation); + + zombie.getEquipment().setHelmet(OMCRegistry.CUSTOM_ITEMS.VAMPIRE_HEAD.getBest()); + zombie.getEquipment().setHelmetDropChance(0); + + zombie.setAggressive(true); + + return zombie; + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/BloodExplosionAttack.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/BloodExplosionAttack.java new file mode 100644 index 000000000..32e075c25 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/BloodExplosionAttack.java @@ -0,0 +1,159 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.attacks; + +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.registry.mobs.MobAttack; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import org.bukkit.*; +import org.bukkit.entity.Mannequin; +import org.bukkit.entity.Player; + +import java.util.Collection; + +public class BloodExplosionAttack implements MobAttack { + + private final VampireBoss boss; + + public BloodExplosionAttack(VampireBoss boss) { + this.boss = boss; + } + + @Override + public void execute() { + Mannequin mannequin = boss.getMannequin(); + + if (mannequin == null || !mannequin.isValid() || mannequin.isDead()) return; + + Location center = mannequin.getLocation() + .clone() + .add(0, mannequin.getHeight() / 3.0, 0); + + spawnRadialParticles(center); + damageNearbyPlayers(mannequin); + + World world = mannequin.getWorld(); + + world.playSound( + center, + Sound.ENTITY_GENERIC_EXPLODE, + SoundCategory.HOSTILE, + 3.0F, + 0.6F + ); + + world.playSound( + center, + Sound.ENTITY_WITHER_HURT, + SoundCategory.HOSTILE, + 2.0F, + 0.5F + ); + } + + private void damageNearbyPlayers(Mannequin mannequin) { + Collection players = mannequin.getWorld().getNearbyPlayers( + mannequin.getLocation(), + 20 + ); + + for (Player player : players) { + if (!canDamage(player)) continue; + + double newHealth = player.getHealth() * 0.7; + + player.setHealth(Math.max(1.0, newHealth)); + + player.playSound( + player.getLocation(), + Sound.ENTITY_PLAYER_HURT, + SoundCategory.PLAYERS, + 1.0F, + 0.7F + ); + } + } + + private boolean canDamage(Player player) { + return player.isValid() + && !player.isDead() + && player.getGameMode() != GameMode.CREATIVE + && player.getGameMode() != GameMode.SPECTATOR; + } + + /** + * Fait apparaitre des particules de sorte qui partent dans toutes les directions d'un cercle en 2D + * Math + * @param center le mob + */ + private void spawnRadialParticles(Location center) { + Collection receivers = + center.getNearbyEntitiesByType( + Player.class, + 50 + ); + + if (receivers.isEmpty()) return; + + int directions = 64; + double particleSpeed = 1.2; + + double[] heightOffsets = { + -2.0, + -1.0, + 0.0, + 1.0, + 2.0 + }; + + for (double heightOffset : heightOffsets) { + Location particleOrigin = center.clone().add( + 0, + heightOffset, + 0 + ); + + for (int i = 0; i < directions; i++) { + double angle = (Math.PI * 2 * i) / directions; + + double directionX = Math.cos(angle); + double directionZ = Math.sin(angle); + + for (Player player : receivers) { + ParticleUtils.sendParticlePacket( + player, + Particle.DUST, + particleOrigin, + 0, + directionX, + 0.0, + directionZ, + particleSpeed, + new Particle.DustOptions(Color.RED, 1.0f) + ); + + ParticleUtils.sendParticlePacket( + player, + Particle.LARGE_SMOKE, + particleOrigin, + 0, + directionX, + 0.0, + directionZ, + particleSpeed * 0.7, + null + ); + } + } + } + + ParticleUtils.spawnCubeParticles( + center, + Particle.EXPLOSION, + 1.5, + 2.5, + 1.5, + 20, + 50, + null + ); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/VampireBatAttack.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/VampireBatAttack.java new file mode 100644 index 000000000..c2e52a6f4 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/attacks/VampireBatAttack.java @@ -0,0 +1,98 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.attacks; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat.AbstractVampireBat; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.registry.mobs.CustomMobEntry; +import fr.openmc.core.registry.mobs.MobAttack; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Mannequin; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class VampireBatAttack implements MobAttack { + + private static final double TARGET_RADIUS = 25.0; + + private final VampireBoss boss; + + private final List BATS = new ArrayList<>(); + + public VampireBatAttack(VampireBoss boss) { + this.boss = boss; + } + + @Override + public void execute() { + if (BATS.isEmpty()) { + BATS.addAll(List.of( + OMCRegistry.CUSTOM_MOBS.EXPLOSIVE_VAMPIRE_BAT, + OMCRegistry.CUSTOM_MOBS.POISON_VAMPIRE_BAT, + OMCRegistry.CUSTOM_MOBS.LEVITATION_VAMPIRE_BAT + )); + } + + Mannequin mannequin = boss.getMannequin(); + + if (mannequin == null || !mannequin.isValid() || mannequin.isDead()) return; + + List targets = mannequin.getWorld() + .getNearbyPlayers( + mannequin.getLocation(), + TARGET_RADIUS + ) + .stream() + .filter(this::canTarget) + .toList(); + + if (targets.isEmpty()) return; + + int amount = ThreadLocalRandom.current().nextInt(3, 7); + + mannequin.getWorld().playSound( + mannequin.getLocation(), + Sound.ENTITY_BAT_TAKEOFF, + SoundCategory.HOSTILE, + 3.0F, + 0.5F + ); + + for (int i = 0; i < amount; i++) { + Player target = targets.get(ThreadLocalRandom.current().nextInt(targets.size())); + + Location spawnLocation = getRandomSpawnLocation(mannequin); + CustomMobEntry batType = BATS.get(ThreadLocalRandom.current().nextInt(BATS.size())); + + if (!(batType.getMob() instanceof AbstractVampireBat batVampire)) return; + batVampire.spawn(spawnLocation, target); + } + } + + private Location getRandomSpawnLocation(Mannequin mannequin) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + + double angle = random.nextDouble(0, Math.PI * 2); + double radius = random.nextDouble(1.0, 3.0); + + return mannequin.getLocation() + .clone() + .add( + Math.cos(angle) * radius, + mannequin.getHeight() * 0.5, + Math.sin(angle) * radius + ); + } + + private boolean canTarget(Player player) { + return player.isValid() + && !player.isDead() + && player.getGameMode() != GameMode.CREATIVE + && player.getGameMode() != GameMode.SPECTATOR; + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/SummoningEffectTask.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/SummoningEffectTask.java new file mode 100644 index 000000000..ad77cad52 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/SummoningEffectTask.java @@ -0,0 +1,112 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.tasks; + +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.utils.bukkit.ParticleUtils; +import org.bukkit.*; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.concurrent.ThreadLocalRandom; + +public class SummoningEffectTask extends BukkitRunnable { + private final VampireBoss boss; + private final Location spawnLocation; + private long elapsedTicks = 0; + + public static final long SUMMON_DURATION_TICKS = 20L * 60L; // 60 sec + public static final long SUMMON_EFFECT_INTERVAL_TICKS = 20L * 5L; // 5 sec + + public SummoningEffectTask(VampireBoss boss, Location spawnLocation) { + this.boss = boss; + this.spawnLocation = spawnLocation; + } + + @Override + public void run() { + if (elapsedTicks >= SUMMON_DURATION_TICKS) { + cancel(); + boss.summon(spawnLocation); + return; + } + + playSummoningEffects(spawnLocation); + + elapsedTicks += SUMMON_EFFECT_INTERVAL_TICKS; + } + + private void playSummoningEffects(Location center) { + World world = center.getWorld(); + + if (world == null) { + return; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < 12; i++) { + double angle = random.nextDouble(0, Math.PI * 2); + double distance = random.nextDouble(3.0, 12.0); + + double offsetX = Math.cos(angle) * distance; + double offsetZ = Math.sin(angle) * distance; + + Location lightningLocation = center.clone().add( + offsetX, + 0, + offsetZ + ); + + lightningLocation.setY(world.getHighestBlockYAt(lightningLocation) + 1); + + world.strikeLightningEffect(lightningLocation); + } + + ParticleUtils.spawnCubeParticles( + center, + Particle.EXPLOSION, + 3.0, + 3.0, + 3.0, + 15, + 20, + null + ); + + ParticleUtils.spawnCubeParticles( + center, + Particle.RAID_OMEN, + 5.0, + 6.0, + 5.0, + 200, + 40, + null + ); + + ParticleUtils.spawnCubeParticles( + center, + Particle.LARGE_SMOKE, + 4.0, + 4.0, + 4.0, + 100, + 50, + null + ); + + world.playSound( + center, + Sound.ENTITY_WITHER_AMBIENT, + SoundCategory.HOSTILE, + 10.0F, + 0.5F + ); + + world.playSound( + center, + Sound.ENTITY_LIGHTNING_BOLT_THUNDER, + SoundCategory.WEATHER, + 4.0F, + 0.3F + ); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/VampireAttackTask.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/VampireAttackTask.java new file mode 100644 index 000000000..dd529b741 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/contents/mobs/vampire/tasks/VampireAttackTask.java @@ -0,0 +1,31 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.tasks; + +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.utils.RandomUtils; +import org.bukkit.scheduler.BukkitRunnable; + +public class VampireAttackTask extends BukkitRunnable { + private final VampireBoss boss; + + private static final int MIN_ATTACK_DELAY_SECONDS = 8; + private static final int MAX_ATTACK_DELAY_SECONDS = 16; + + public VampireAttackTask(VampireBoss boss) { + this.boss = boss; + } + + private int cooldown = RandomUtils.randomBetween(MIN_ATTACK_DELAY_SECONDS, MAX_ATTACK_DELAY_SECONDS); + + @Override + public void run() { + if (!boss.getMannequin().isValid()) { + cancel(); + return; + } + cooldown--; + if (cooldown <= 0) { + boss.pickRandomAttack(); + cooldown = RandomUtils.randomBetween(MIN_ATTACK_DELAY_SECONDS, MAX_ATTACK_DELAY_SECONDS); + } + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/MonsterSpawnLIstener.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/MonsterSpawnLIstener.java new file mode 100644 index 000000000..17567a63e --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/MonsterSpawnLIstener.java @@ -0,0 +1,51 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.listeners; + +import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.core.features.events.contents.dailyevents.DailyEventsManager; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.BloodyNightEvent; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.BloodyNightManager; +import org.bukkit.Bukkit; +import org.bukkit.entity.Monster; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntitySpawnEvent; + +public class MonsterSpawnLIstener implements Listener { + @EventHandler + public void onNormalMonsterSpawn(EntitySpawnEvent event) { + if (!DailyEventsManager.isActiveDailyEvent() + || !(DailyEventsManager.getActiveDailyEvent() instanceof BloodyNightEvent bloodyEvent)) return; + if (!event.getLocation().getWorld().getName().equals(bloodyEvent.getWorldEvent())) return; + + if (!(event.getEntity() instanceof Monster monster)) return; + if (monster.getPersistentDataContainer().has(BloodyNightManager.RAID_MONSTER_KEY)) { + BloodyNightManager.applyBloodyMonster(monster); + } else { + OMCRegistry.CUSTOM_MOBS.CORRUPTED_MONSTER.apply(monster); + } + } + + @EventHandler + public void onMonsterLoaded(EntityAddToWorldEvent event) { + if (DailyEventsManager.isActiveDailyEvent() + && DailyEventsManager.getActiveDailyEvent() instanceof BloodyNightEvent) return; + + if (!(event.getEntity() instanceof Monster monster)) return; + + if (monster.getPersistentDataContainer().has(BloodyNightManager.RAID_MONSTER_KEY)) { + Bukkit.getScheduler().runTask(OMCPlugin.getInstance(), () -> { + if (monster.isValid()) { + monster.remove(); + } else { + OMCLogger.error("Impossible de supprimer le monstre (mort, delete, non chargé) " + monster.getName()); + } + }); + return; + } + + BloodyNightManager.desactivateCorruptedMonster(monster); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/PlayerKillMonsterListener.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/PlayerKillMonsterListener.java new file mode 100644 index 000000000..074e0c030 --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/listeners/PlayerKillMonsterListener.java @@ -0,0 +1,28 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.listeners; + +import org.bukkit.event.Listener; + +public class PlayerKillMonsterListener implements Listener { + +// @EventHandler +// public void onPlayerKill(EntityDeathEvent event) { +// if (!DailyEventsManager.isActiveDailyEvent() +// || !(DailyEventsManager.getActiveDailyEvent() instanceof BloodyNightEvent)) return; +// if (event.getEntity().getKiller() == null) return; +// if (!(event.getEntity() instanceof Monster)) return; +// if (!(event.getDamageSource().getDirectEntity() instanceof Player player)) return; +// +// event.getDrops().clear(); +// +// List loots = OMCRegistry.CUSTOM_LOOT_TABLES.CORRUPTED_MOB.rollLoots(player); +// +// event.getDrops().clear(); +// for (CustomLoot loot : loots) { +// if (!(loot instanceof ItemLoot itemLoot)) continue; +// +// for (ItemStack item : itemLoot.getItems()) { +// event.getDrops().add(item); +// } +// } +// } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyMonsterMenu.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyMonsterMenu.java new file mode 100644 index 000000000..e471bf56f --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyMonsterMenu.java @@ -0,0 +1,97 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.menu; + +import fr.openmc.api.menulib.Menu; +import fr.openmc.api.menulib.utils.InventorySize; +import fr.openmc.api.menulib.utils.ItemMenuBuilder; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.BloodyNightManager; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BloodyMonsterMenu extends Menu { + + public BloodyMonsterMenu(Player owner) { + super(owner); + } + + @Override + public @NotNull Component getName() { + return TranslationManager.translation("feature.dailyevents.bloody_night.menu.info.bloody_monster.name"); + } + + @Override + public String getTexture() { + return null; + } + + @Override + public @NotNull InventorySize getInventorySize() { + return InventorySize.NORMAL; + } + + @Override + public void onInventoryClick(InventoryClickEvent click) { + //empty + } + + @Override + public @NotNull Map getContent() { + Map inventory = new HashMap<>(); + + inventory.put(11, new ItemMenuBuilder(this, Material.CRIMSON_NYLIUM, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.corrupted_mob")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.bloody_monster.lore", + Component.text(BloodyNightManager.CORRUPTED_CHANCE * 100 + "%", NamedTextColor.DARK_RED))); + }).setOnClick(_ -> + OMCRegistry.CUSTOM_LOOT_TABLES.CORRUPTED_MOB.openMenu(getOwner()))); + + inventory.put(12, new ItemMenuBuilder(this, Material.RED_DYE, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.cursed_mob")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.bloody_monster.lore", + Component.text(BloodyNightManager.CURSED_CHANCE * 100 + "%", NamedTextColor.RED))); + }).setOnClick(_ -> + OMCRegistry.CUSTOM_LOOT_TABLES.CURSED_MOB.openMenu(getOwner()))); + + inventory.put(14, new ItemMenuBuilder(this, Material.PURPLE_DYE, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.enraged_mob")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.bloody_monster.lore", + Component.text(BloodyNightManager.ENRAGED_CHANCE * 100 + "%", NamedTextColor.DARK_PURPLE))); + }).setOnClick(_ -> + OMCRegistry.CUSTOM_LOOT_TABLES.ENRAGED_MOB.openMenu(getOwner()))); + + inventory.put(15, new ItemMenuBuilder(this, Material.YELLOW_DYE, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.loot_table.ancient_mob")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.bloody_monster.lore", + Component.text(BloodyNightManager.ANCIENT_CHANCE * 100 + "%", NamedTextColor.YELLOW))); + }).setOnClick(_ -> + OMCRegistry.CUSTOM_LOOT_TABLES.ANCIENT_MOB.openMenu(getOwner()))); + + inventory.put(18, new ItemMenuBuilder(this, Material.ARROW, true)); + + return inventory; + } + + @Override + public void onClose(InventoryCloseEvent event) { + //empty + } + + @Override + public List getTakableSlot() { + return List.of(); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyNightMenu.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyNightMenu.java new file mode 100644 index 000000000..0e740f1cc --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/bloodynight/menu/BloodyNightMenu.java @@ -0,0 +1,93 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.menu; + +import fr.openmc.api.menulib.Menu; +import fr.openmc.api.menulib.utils.InventorySize; +import fr.openmc.api.menulib.utils.ItemMenuBuilder; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.features.events.contents.dailyevents.DailyEventsManager; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.BloodyNightManager; +import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.MiraculousFishingEvent; +import fr.openmc.core.utils.text.messages.TranslationManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BloodyNightMenu extends Menu { + + public BloodyNightMenu(Player owner) { + super(owner); + } + + @Override + public @NotNull Component getName() { + return DailyEventsManager.BLOODY_NIGHT.getName(); + } + + @Override + public String getTexture() { + return null; + } + + @Override + public @NotNull InventorySize getInventorySize() { + return InventorySize.NORMAL; + } + + @Override + public void onInventoryClick(InventoryClickEvent click) { + //empty + } + + @Override + public @NotNull Map getContent() { + Map inventory = new HashMap<>(); + + boolean isActived = DailyEventsManager.isActiveDailyEvent() + && DailyEventsManager.getActiveDailyEvent() instanceof MiraculousFishingEvent; + + inventory.put(11, new ItemMenuBuilder(this, Material.STRIDER_SPAWN_EGG, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.menu.info.bloody_monster.name")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.bloody_monster.lore")); + itemMeta.setEnchantmentGlintOverride(isActived); + }).setOnClick(_ -> new BloodyMonsterMenu(getOwner()).open())); + + inventory.put(13, new ItemMenuBuilder(this, OMCRegistry.CUSTOM_ITEMS.VAMPIRE_HEAD, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.menu.info.vampire_boss.name")); + itemMeta.lore(TranslationManager.translationLore( + "feature.dailyevents.bloody_night.menu.info.vampire_boss.lore", + Component.text(BloodyNightManager.VAMPIRE_SPAWN_TIME / 60 / 20, NamedTextColor.RED))); + itemMeta.setEnchantmentGlintOverride(isActived); + }).setOnClick(_ -> + OMCRegistry.CUSTOM_LOOT_TABLES.VAMPIRE.openMenu(getOwner()))); + + inventory.put(15, new ItemMenuBuilder(this, Material.NETHERITE_SPEAR, itemMeta -> { + itemMeta.displayName(TranslationManager.translation("feature.dailyevents.bloody_night.menu.info.bloody_raid.name")); + itemMeta.lore(TranslationManager.translationLore("feature.dailyevents.bloody_night.menu.info.bloody_raid.lore", + Component.text(BloodyNightManager.RAID_INTERVAL / 60 / 20, NamedTextColor.RED))); + itemMeta.setEnchantmentGlintOverride(isActived); + })); + + inventory.put(18, new ItemMenuBuilder(this, Material.ARROW, true)); + + return inventory; + } + + @Override + public void onClose(InventoryCloseEvent event) { + //empty + } + + @Override + public List getTakableSlot() { + return List.of(); + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/contents/GoldenAmbient.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/goldenharvest/contents/ambient/GoldenAmbient.java similarity index 96% rename from src/main/java/fr/openmc/core/registry/ambient/contents/GoldenAmbient.java rename to src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/goldenharvest/contents/ambient/GoldenAmbient.java index 72173ff22..acfdf967c 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/contents/GoldenAmbient.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/goldenharvest/contents/ambient/GoldenAmbient.java @@ -1,4 +1,4 @@ -package fr.openmc.core.registry.ambient.contents; +package fr.openmc.core.features.events.contents.dailyevents.contents.goldenharvest.contents.ambient; import fr.openmc.api.datapacks.builders.EnvironnementAttributeBuilder; import fr.openmc.api.datapacks.builders.sounds.AmbientSoundBuilder; diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/FishingAttributeManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/FishingAttributeManager.java index a135b58b2..6972efa95 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/FishingAttributeManager.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/FishingAttributeManager.java @@ -18,6 +18,9 @@ public class FishingAttributeManager { public static final double FISHING_SPEED_MODIFIER = 0.4; public static final double DOUBLE_HOOK_MODIFIER = 0; + public static final double ARMOR_FISHING_SPEED_MODIFIER = 0.05; + public static final double ARMOR_DOUBLE_HOOK_MODIFIER = 0.03; + private static final Set FISHER_ARMOR = Set.of( OMCRegistry.CUSTOM_ITEMS.ANCIENT_FISHER_HELMET, OMCRegistry.CUSTOM_ITEMS.ANCIENT_FISHER_CHESTPLATE, @@ -56,7 +59,7 @@ public static double getFishingSpped(Player player) { Optional ci = OMCRegistry.CUSTOM_ITEMS.get(item); if (ci.isPresent() && FISHER_ARMOR.contains(ci.get())) { - base += 0.05; + base += ARMOR_FISHING_SPEED_MODIFIER; } } @@ -84,7 +87,7 @@ public static double getDoubleHookChance(Player player) { Optional ci = OMCRegistry.CUSTOM_ITEMS.get(item); if (ci.isPresent() && FISHER_ARMOR.contains(ci.get())) { - base += 0.03; + base += ARMOR_DOUBLE_HOOK_MODIFIER; } } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingEvent.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingEvent.java index 42cc0cec5..ca0f13d1f 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingEvent.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingEvent.java @@ -28,7 +28,8 @@ import java.util.List; import java.util.Set; -public class MiraculousFishingEvent extends DailyEvent implements HasToast, HasAmbient, HasBroadcast, HasListeners, HasMenu { +public class MiraculousFishingEvent extends DailyEvent + implements HasToast, HasAmbient, HasBroadcast, HasListeners, HasMenu { @Override public String getEventId() { return "miraculous_fishing"; @@ -101,7 +102,7 @@ public CustomAmbient getAmbient() { @Override public Component getStartBroadcast() { return TranslationManager.translation("feature.dailyevents.miraculousfishing.broadcast.start", - Component.text(FishingAttributeManager.FISHING_SPEED_MODIFIER * 100, NamedTextColor.AQUA)); + Component.text(FishingAttributeManager.FISHING_SPEED_MODIFIER * 100 + "%", NamedTextColor.AQUA)); } @Override diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingManager.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingManager.java index d4cae35fa..5573b4483 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingManager.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/MiraculousFishingManager.java @@ -6,8 +6,6 @@ import fr.openmc.core.registry.loottable.loots.MoneyLoot; import fr.openmc.core.registry.loottable.loots.RepresentedItem; import fr.openmc.core.registry.loottable.loots.TableLoot; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.entity.Entity; @@ -22,27 +20,6 @@ public class MiraculousFishingManager { public static final NamespacedKey NOT_PICKUP_KEY = new NamespacedKey(OMCPlugin.getInstance(), "not_pickup"); - /** - * Envoie le message de loot contenant, le nom du loot et la chance du loot - * @param player le joueur a qui envoyé le message - * @param loot le loot obtenu - * @param amount le nombre de loot - */ - public static void sendLootMessage(Player player, CustomLoot loot, int amount) { - Component base = Component.text(" - ", NamedTextColor.GRAY); - - if (amount != -1) - base = base.append(Component.text(amount + "x ")); - - if (loot.getDisplayText() != null && - !(loot instanceof TableLoot)) { - base = base.append(loot.getDisplayText()) - .append(Component.text(" ("+ Math.round(loot.getChance() * 100.0) +"% ★)", NamedTextColor.AQUA)); - - player.sendMessage(base); - } - } - /** * Simule un item qui est lancé du bouchon de pêche jusqu'au joueur, via un CustomLoot. * @param player le joueur visé @@ -53,7 +30,7 @@ public static void simulateLaunchLoot(Player player, Location hookLocation, Cust // * Gestion spécial pour les Sea Creature if (loot instanceof SeaCreatureLoot seaCreatureLoot) { // * On envoie le message de loot - sendLootMessage(player, loot, -1); + loot.sendLootMessage(player, -1); Entity entity = seaCreatureLoot.getSeaCreatureMob().spawn(hookLocation); @@ -81,7 +58,7 @@ public static void simulateLaunchLoot(Player player, Location hookLocation, Cust itemEntity.setGlowing(true); // * On envoie le message de loot - sendLootMessage(player, loot, itemEntity.getItemStack().getAmount()); + loot.sendLootMessage(player, itemEntity.getItemStack().getAmount()); applyVelocity(hookLocation, player.getEyeLocation(), itemEntity, 0.1); } @@ -118,8 +95,14 @@ private static void applyVelocity(Location origin, Location destination, Entity // * Revient à faire le vecteur vitesse entre 2 vecteur (xp - xh, yp - yh, zp - zh) Vector velocity = destination.toVector().subtract(origin.toVector()); velocity.multiply(force); - velocity.setY(velocity.getY() + Math.sqrt(Math.sqrt( - velocity.getX()*2 + velocity.getY()*2 + velocity.getZ()*2)) * 0.08); + + double m = Math.sqrt( + velocity.getX() * velocity.getX() + + velocity.getY() * velocity.getY() + + velocity.getZ() * velocity.getZ() + ); + + velocity.setY(velocity.getY() + m * 0.08); entity.setVelocity(velocity); } } diff --git a/src/main/java/fr/openmc/core/registry/ambient/contents/BlessedAmbient.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/ambient/BlessedAmbient.java similarity index 96% rename from src/main/java/fr/openmc/core/registry/ambient/contents/BlessedAmbient.java rename to src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/ambient/BlessedAmbient.java index 017d0899a..3e218cb47 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/contents/BlessedAmbient.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/ambient/BlessedAmbient.java @@ -1,4 +1,4 @@ -package fr.openmc.core.registry.ambient.contents; +package fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.ambient; import fr.openmc.api.datapacks.builders.EnvironnementAttributeBuilder; import fr.openmc.api.datapacks.builders.sounds.AmbientSoundBuilder; diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/DoubleHookChancePlaceholder.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/DoubleHookChancePlaceholder.java new file mode 100644 index 000000000..a3e1eee3c --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/DoubleHookChancePlaceholder.java @@ -0,0 +1,16 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.placeholder; + +import fr.openmc.core.hooks.itemsadder.placeholders.IAPlaceholder; + +import static fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.FishingAttributeManager.ARMOR_DOUBLE_HOOK_MODIFIER; + +public class DoubleHookChancePlaceholder implements IAPlaceholder { + + public String name() { + return "armor_double_hook_chance"; + } + + public String resolve(String idItem) { + return String.valueOf(ARMOR_DOUBLE_HOOK_MODIFIER * 100); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/FishingSpeedPlaceholder.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/FishingSpeedPlaceholder.java new file mode 100644 index 000000000..9e5d38ecf --- /dev/null +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/contents/placeholder/FishingSpeedPlaceholder.java @@ -0,0 +1,15 @@ +package fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.placeholder; + +import fr.openmc.core.hooks.itemsadder.placeholders.IAPlaceholder; + +import static fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.FishingAttributeManager.ARMOR_FISHING_SPEED_MODIFIER; + +public class FishingSpeedPlaceholder implements IAPlaceholder { + public String name() { + return "armor_fishing_speed"; + } + + public String resolve(String idItem) { + return String.valueOf(ARMOR_FISHING_SPEED_MODIFIER * 100); + } +} diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/listeners/PlayerFishListener.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/listeners/PlayerFishListener.java index 8d42c7a71..71bc55016 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/listeners/PlayerFishListener.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/contents/miraculousfishing/listeners/PlayerFishListener.java @@ -16,10 +16,12 @@ import fr.openmc.core.utils.text.messages.MessagesManager; import fr.openmc.core.utils.text.messages.Prefix; import fr.openmc.core.utils.text.messages.TranslationManager; +import io.papermc.paper.event.entity.FishHookStateChangeEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Particle; import org.bukkit.Sound; +import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.FishHook; import org.bukkit.entity.Item; @@ -44,43 +46,60 @@ public void onStartFishing(PlayerFishEvent event) { FishingAttributeManager.applyFishingSpeedModifier(player, hook); - switch (event.getState()) { - case FISHING -> { - // * SFX - ParticleUtils.sendParticlePacket(player, Particle.POOF, hook.getLocation(), 5, 0.1, 0.1, 0.1, 0.01, null); - player.playSound(player.getLocation(), Sound.ENTITY_FISHING_BOBBER_SPLASH, 1f, 0.7f); + if (event.getState().equals(PlayerFishEvent.State.CAUGHT_FISH)) { + Entity caughtEntity = event.getCaught(); + if (caughtEntity instanceof Item caughtItem) { + caughtItem.remove(); } - case CAUGHT_FISH -> { - Entity caughtEntity = event.getCaught(); - if (caughtEntity instanceof Item caughtItem) { - caughtItem.remove(); - } + CustomLootTable fishingLootTable = OMCRegistry.CUSTOM_LOOT_TABLES.MIRACULOUS_FISHING; - CustomLootTable fishingLootTable = OMCRegistry.CUSTOM_LOOT_TABLES.MIRACULOUS_FISHING; + List loots = fishingLootTable.rollLoots(); - List loots = fishingLootTable.rollLoots(player, false); + List finalLoots = FishingAttributeManager.applyDoubleHookChance(player, loots); - List finalLoots = FishingAttributeManager.applyDoubleHookChance(player, loots); + // * SFX + ParticleUtils.spawnDispersingParticles(player, + Particle.CLOUD, + hook.getLocation(), + 35, 0.1D, null); - // * SFX - ParticleUtils.spawnDispersingParticles(player, - Particle.CLOUD, - hook.getLocation(), - 35, 0.1D, null); + MessagesManager.sendMessage(player, TranslationManager.translation( + "feature.dailyevents.miraculousfishing.loot_table.get", + Component.text(finalLoots.size()).color(NamedTextColor.YELLOW) + ), Prefix.MIRACULOUS_FISHING, MessageType.INFO, false); - MessagesManager.sendMessage(player, TranslationManager.translation( - "feature.dailyevents.miraculousfishing.loot_table.get", - Component.text(finalLoots.size()).color(NamedTextColor.YELLOW) - ), Prefix.MIRACULOUS_FISHING, MessageType.INFO, false); + if (loots.size() * 2 == finalLoots.size()) { + player.sendMessage(TranslationManager.translation("feature.dailyevents.miraculousfishing.loot_table.get.double_hook")); + } - if (loots.size()*2 == finalLoots.size()) { - player.sendMessage(TranslationManager.translation("feature.dailyevents.miraculousfishing.loot_table.get.double_hook")); - } + sendLoot(player, hook, finalLoots); + } + } - sendLoot(player, hook, finalLoots); - } + @EventHandler + public void onHookOnWater(FishHookStateChangeEvent event) { + if (!DailyEventsManager.isActiveDailyEvent() + || !(DailyEventsManager.getActiveDailyEvent() instanceof MiraculousFishingEvent)) return; + + Entity hook = event.getEntity(); + World world = hook.getWorld(); + + if (event.getNewHookState().equals(FishHook.HookState.BOBBING)) { + // * SFX + ParticleUtils.sendParticlePacket( + hook.getLocation().getNearbyEntitiesByType(Player.class, 30), + Particle.POOF, hook.getLocation(), + 5, + 0.1, + 0.1, + 0.1, + 0.01, + null + ); + world.playSound(hook.getLocation(), Sound.ENTITY_FISHING_BOBBER_SPLASH, 2f, 0.7f); } + } /** diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/listeners/DailyEventAmbientListeners.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/listeners/DailyEventAmbientListeners.java index e19c7b359..658a1f47c 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/listeners/DailyEventAmbientListeners.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/listeners/DailyEventAmbientListeners.java @@ -21,7 +21,7 @@ public void onJoin(PlayerJoinEvent event) { if (!(dailyEvent instanceof HasAmbient hasAmbient)) return; - hasAmbient.apply(event.getPlayer()); + hasAmbient.apply(event.getPlayer(), true); } @EventHandler diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/DailyEvent.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/DailyEvent.java index e94afdf00..e979d26ed 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/DailyEvent.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/DailyEvent.java @@ -5,6 +5,7 @@ import fr.openmc.core.features.events.contents.dailyevents.tasks.EndEventTask; import fr.openmc.core.features.events.models.Event; import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.entity.Player; import java.util.Collection; @@ -104,4 +105,8 @@ public void end() { toast.getEndToastData().send(receivers); } } + + public World getWorld() { + return Bukkit.getWorld(this.getWorldEvent()); + } } diff --git a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/HasAmbient.java b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/HasAmbient.java index 6f93a2c09..d391885be 100644 --- a/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/HasAmbient.java +++ b/src/main/java/fr/openmc/core/features/events/contents/dailyevents/models/dailyevent/HasAmbient.java @@ -12,7 +12,11 @@ public interface HasAmbient { CustomAmbient getAmbient(); default void apply(Player player) { - this.getAmbient().apply(player); + apply(player, false); + } + + default void apply(Player player, boolean isJoining) { + this.getAmbient().apply(player, isJoining); } default void apply(Collection receivers) { diff --git a/src/main/java/fr/openmc/core/features/mailboxes/MailboxManager.java b/src/main/java/fr/openmc/core/features/mailboxes/MailboxManager.java index 2ea40f10b..9335b26e6 100644 --- a/src/main/java/fr/openmc/core/features/mailboxes/MailboxManager.java +++ b/src/main/java/fr/openmc/core/features/mailboxes/MailboxManager.java @@ -116,19 +116,20 @@ private static boolean sendLetter(Player sender, OfflinePlayer receiver, ItemSta } public static void sendItemsToAOfflinePlayerBatch(Map playerItemsMap) { - try { - for (Map.Entry entry : playerItemsMap.entrySet()) { - OfflinePlayer player = entry.getKey(); - ItemStack[] items = entry.getValue(); + for (Map.Entry entry : playerItemsMap.entrySet()) { + sendItemsToOfflinePlayer(entry.getKey(), entry.getValue()); + } + } - int numItems = Arrays.stream(items).mapToInt(ItemStack::getAmount).sum(); + public static void sendItemsToOfflinePlayer(OfflinePlayer player, ItemStack[] items) { + try { + int numItems = Arrays.stream(items).mapToInt(ItemStack::getAmount).sum(); - byte[] itemsBytes = BukkitSerializer.serializeItemStacks(changeStackItem(items)); + byte[] itemsBytes = BukkitSerializer.serializeItemStacks(changeStackItem(items)); - Letter letter = new Letter(nextLetterId++, player.getUniqueId(), player.getUniqueId(), itemsBytes, numItems, - Timestamp.valueOf(DateUtils.getLocalDateTime()), false); - letters.add(letter); - } + Letter letter = new Letter(nextLetterId++, player.getUniqueId(), player.getUniqueId(), itemsBytes, numItems, + Timestamp.valueOf(DateUtils.getLocalDateTime()), false); + letters.add(letter); } catch (IOException e) { OMCLogger.warn("Error while sending items to offline players: {}", e.getMessage(), e); } diff --git a/src/main/java/fr/openmc/core/hooks/itemsadder/placeholders/IAPlaceholderRegistry.java b/src/main/java/fr/openmc/core/hooks/itemsadder/placeholders/IAPlaceholderRegistry.java index bbe1c1da3..6cdad7580 100644 --- a/src/main/java/fr/openmc/core/hooks/itemsadder/placeholders/IAPlaceholderRegistry.java +++ b/src/main/java/fr/openmc/core/hooks/itemsadder/placeholders/IAPlaceholderRegistry.java @@ -4,6 +4,8 @@ import fr.openmc.core.features.dream.placeholders.DreamItemMaterialPlaceholder; import fr.openmc.core.features.dream.placeholders.DreamItemNamePlaceholder; import fr.openmc.core.features.dream.placeholders.DreamItemTooltipPlaceholder; +import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.placeholder.DoubleHookChancePlaceholder; +import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.placeholder.FishingSpeedPlaceholder; import java.util.HashMap; import java.util.Map; @@ -25,7 +27,9 @@ public static IAPlaceholderRegistry loadDefault() { registry.register( new DreamItemTooltipPlaceholder(), new DreamItemMaterialPlaceholder(), - new DreamItemNamePlaceholder() + new DreamItemNamePlaceholder(), + new FishingSpeedPlaceholder(), + new DoubleHookChancePlaceholder() ); return registry; } diff --git a/src/main/java/fr/openmc/core/listeners/InteractListener.java b/src/main/java/fr/openmc/core/listeners/InteractListener.java index c28899705..631c0f2cf 100644 --- a/src/main/java/fr/openmc/core/listeners/InteractListener.java +++ b/src/main/java/fr/openmc/core/listeners/InteractListener.java @@ -2,7 +2,9 @@ import fr.openmc.core.OMCRegistry; import fr.openmc.core.registry.items.CustomItem; +import fr.openmc.core.registry.items.options.LootboxBlock; import fr.openmc.core.registry.items.options.UsableItem; +import fr.openmc.core.utils.bukkit.ItemUtils; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -30,6 +32,10 @@ void onInteract(PlayerInteractEvent event) { if (player.isSneaking()) usable.onSneakClick(player, event); else if (action.isLeftClick()) usable.onLeftClick(player, event); else if (action.isRightClick()) usable.onRightClick(player, event); + } else if (item.get() instanceof LootboxBlock lootbox + && event.getAction() == Action.RIGHT_CLICK_AIR) { + ItemUtils.removeItemsFromPlayerInventory(player, item.get().getBest(), 1); + lootbox.getLootbox().open(player); } } diff --git a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java index 2b54f46ce..8e6ad10ed 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java +++ b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbient.java @@ -46,13 +46,23 @@ public abstract class CustomAmbient { * @param player Le joueur concerné */ public void apply(Player player) { + apply(player, false); + } + + /** + * Applique l'ambience sur un Joueur + * @param player Le joueur concerné + * @param isJoining Si le joueur est rejoint ou non + */ + public void apply(Player player, boolean isJoining) { ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); // * On envoie le packet respawn qui applique l'ambience PlayerRespawnNMS.sendPacket( nmsPlayer, getPlayerAmbientSpawnInfo(nmsPlayer), - getTransitionDimensionForPlayer(nmsPlayer) + getTransitionDimensionForPlayer(nmsPlayer), + isJoining ); if (this.getAmbientBuilder().utilizeBiome()) { @@ -76,7 +86,7 @@ public void apply(Player player) { */ public void apply(Collection receivers) { for (Player receiver : receivers) { - apply(receiver); + apply(receiver, false); } } @@ -86,15 +96,15 @@ public void apply(Collection receivers) { */ public void reset(Player player) { ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ACTIVE_AMBIENTS.remove(player.getUniqueId()); // * On envoie le packet respawn qui remets tout a la normale PlayerRespawnNMS.sendPacket( nmsPlayer, nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()), - getTransitionDimensionForPlayer(nmsPlayer) + getTransitionDimensionForPlayer(nmsPlayer), + false ); - - ACTIVE_AMBIENTS.remove(player.getUniqueId()); } /** diff --git a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java index cfa24dc73..802aacc7f 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java +++ b/src/main/java/fr/openmc/core/registry/ambient/CustomAmbientRegistry.java @@ -5,7 +5,11 @@ import fr.openmc.core.bootstrap.integration.OMCLogger; import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; -import fr.openmc.core.registry.ambient.contents.*; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.ambient.BloodyAmbient; +import fr.openmc.core.features.events.contents.dailyevents.contents.goldenharvest.contents.ambient.GoldenAmbient; +import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.ambient.BlessedAmbient; +import fr.openmc.core.registry.ambient.contents.DarkAmbient; +import fr.openmc.core.registry.ambient.contents.HellAmbient; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import net.minecraft.core.registries.Registries; import net.minecraft.server.MinecraftServer; diff --git a/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientFixedTimeListener.java b/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientFixedTimeListener.java new file mode 100644 index 000000000..a9e141236 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientFixedTimeListener.java @@ -0,0 +1,46 @@ +package fr.openmc.core.registry.ambient.listeners; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.ambient.CustomAmbient; +import fr.openmc.core.utils.nms.PlayerSetTimeNMS; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.world.TimeSkipEvent; + +public class AmbientFixedTimeListener implements Listener { + @EventHandler + public void onPlayerKill(PlayerDeathEvent event) { + Player player = event.getPlayer(); + reapplyTime(player); + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + reapplyTime(player); + } + + @EventHandler + public void onTimeChange(TimeSkipEvent event) { + for (Player player : event.getWorld().getPlayers()) { + reapplyTime(player); + } + } + + /** + * Réapplique le temps sur un joueur + * @param player le joueur ciblé + */ + private void reapplyTime(Player player) { + if (!CustomAmbient.ACTIVE_AMBIENTS.containsKey(player.getUniqueId())) return; + + CustomAmbient ambientApplied = OMCRegistry.CUSTOM_AMBIENTS.getOrThrow( + CustomAmbient.ACTIVE_AMBIENTS.get(player.getUniqueId())); + if (ambientApplied.getAmbientBuilder().getTimeFixed() != null) { + PlayerSetTimeNMS.sendPacketSetTime(player, ambientApplied.getAmbientBuilder().getTimeFixed()); + } + } +} diff --git a/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientWeatherListener.java b/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientWeatherListener.java index d79e483e7..07121e81c 100644 --- a/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientWeatherListener.java +++ b/src/main/java/fr/openmc/core/registry/ambient/listeners/AmbientWeatherListener.java @@ -1,27 +1,52 @@ package fr.openmc.core.registry.ambient.listeners; +import fr.openmc.core.OMCPlugin; import fr.openmc.core.OMCRegistry; import fr.openmc.core.registry.ambient.CustomAmbient; import fr.openmc.core.utils.nms.PlayerWeatherNMS; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.weather.WeatherChangeEvent; public class AmbientWeatherListener implements Listener { + @EventHandler + public void onPlayerKill(PlayerDeathEvent event) { + Player player = event.getPlayer(); + reapplyWeather(player); + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + reapplyWeather(player); + } + @EventHandler public void onWeatherChange(WeatherChangeEvent event) { - for (Player player : event.getWorld().getPlayers()) { - if (!CustomAmbient.ACTIVE_AMBIENTS.containsKey(player.getUniqueId())) continue; + Bukkit.getScheduler().runTaskLater(OMCPlugin.getInstance(), () -> { + for (Player player : event.getWorld().getPlayers()) { + reapplyWeather(player); + } + }, 1L); + } + + /** + * Réapplique la pluie/orage/temps sur un joueur + * @param player le joueur ciblé + */ + private void reapplyWeather(Player player) { + if (!CustomAmbient.ACTIVE_AMBIENTS.containsKey(player.getUniqueId())) return; - CustomAmbient ambientApplied = OMCRegistry.CUSTOM_AMBIENTS.getOrThrow( - CustomAmbient.ACTIVE_AMBIENTS.get(player.getUniqueId()) - ); + CustomAmbient ambientApplied = OMCRegistry.CUSTOM_AMBIENTS.getOrThrow( + CustomAmbient.ACTIVE_AMBIENTS.get(player.getUniqueId())); - if (ambientApplied.getAmbientBuilder().getWeatherFixed() == null) continue; + if (ambientApplied.getAmbientBuilder().getWeatherFixed() == null) return; - PlayerWeatherNMS.setWeather(player, ambientApplied.getAmbientBuilder().getWeatherFixed()); - } + PlayerWeatherNMS.setWeather(player, ambientApplied.getAmbientBuilder().getWeatherFixed()); } } \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantment.java b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantment.java index 13f6d7097..694e83e10 100644 --- a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantment.java +++ b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantment.java @@ -1,6 +1,7 @@ package fr.openmc.core.registry.enchantments; import fr.openmc.core.registry.items.CustomItem; +import fr.openmc.core.utils.RandomUtils; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; @@ -54,6 +55,11 @@ public ItemStack getVanilla() { }; } + public CustomItem getEnchantedBookItem(int minLevel, int maxLevel) { + int level = RandomUtils.randomBetween(minLevel, maxLevel); + return getEnchantedBookItem(level); + } + public Enchantment getEnchantment() { Registry<@NotNull Enchantment> enchantmentRegistry = RegistryAccess .registryAccess() diff --git a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java index b632fdb78..d6a65d4a9 100644 --- a/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java +++ b/src/main/java/fr/openmc/core/registry/enchantments/CustomEnchantmentRegistry.java @@ -7,6 +7,7 @@ import fr.openmc.core.features.dream.registries.enchantements.DreamSleeper; import fr.openmc.core.features.dream.registries.enchantements.Experientastic; import fr.openmc.core.features.dream.registries.enchantements.Soulbound; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.enchantments.Vampirism; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryComposeEvent; @@ -30,6 +31,8 @@ public Key key(CustomEnchantment registryObject) { public final CustomEnchantment EXPERIENTASTIC = register(new Experientastic()); public final CustomEnchantment DREAM_SLEEPER = register(new DreamSleeper()); + public final CustomEnchantment VAMPIRISM = register(new Vampirism()); + @Override public void bootstrap(BootstrapContext context) { context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.compose() diff --git a/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java b/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java index d34b21937..7e9e9f23e 100644 --- a/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java +++ b/src/main/java/fr/openmc/core/registry/items/CustomItemRegistry.java @@ -23,7 +23,8 @@ import java.util.Optional; -public class CustomItemRegistry extends Registry implements KeyedRegistry { +public class CustomItemRegistry extends Registry + implements KeyedRegistry { public static final NamespacedKey CUSTOM_ITEM_KEY = new NamespacedKey("openmc", "custom_item"); @@ -129,7 +130,7 @@ public class CustomItemRegistry extends Registry implements public final CustomItem POISSON_STEVE_HEAD = register("omc_daily_events:poisson_steve_head", Material.PLAYER_HEAD); public final CustomItem KRAKEN_HEAD = register("omc_daily_events:kraken_head", Material.PLAYER_HEAD); public final CustomItem LEVIATHAN_HEAD = register("omc_daily_events:leviathan_head", Material.PLAYER_HEAD); - + public final CustomItem VAMPIRE_HEAD = register("omc_daily_events:vampire_head", Material.PLAYER_HEAD); @Override public void postInit() { diff --git a/src/main/java/fr/openmc/core/registry/lootboxes/listener/DesactivateFireworkDamageListener.java b/src/main/java/fr/openmc/core/registry/lootboxes/listener/DesactivateFireworkDamageListener.java new file mode 100644 index 000000000..d6f374253 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/lootboxes/listener/DesactivateFireworkDamageListener.java @@ -0,0 +1,23 @@ +package fr.openmc.core.registry.lootboxes.listener; + +import fr.openmc.core.OMCPlugin; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Firework; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.persistence.PersistentDataType; + +public class DesactivateFireworkDamageListener implements Listener { + public static final NamespacedKey NO_DAMAGE_KEY = new NamespacedKey(OMCPlugin.getInstance(), "no_damage_firework"); + + @EventHandler + public void onFireworkDamage(EntityDamageByEntityEvent event) { + if (!(event.getDamager() instanceof Firework firework)) return; + + if (firework.getPersistentDataContainer().has(NO_DAMAGE_KEY, PersistentDataType.BOOLEAN)) { + event.setCancelled(true); + + } + } +} diff --git a/src/main/java/fr/openmc/core/registry/lootboxes/menu/LootboxOpenMenu.java b/src/main/java/fr/openmc/core/registry/lootboxes/menu/LootboxOpenMenu.java index e7742c53a..01f81eaef 100644 --- a/src/main/java/fr/openmc/core/registry/lootboxes/menu/LootboxOpenMenu.java +++ b/src/main/java/fr/openmc/core/registry/lootboxes/menu/LootboxOpenMenu.java @@ -29,6 +29,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; @@ -37,6 +38,8 @@ import java.util.List; import java.util.Map; +import static fr.openmc.core.registry.lootboxes.listener.DesactivateFireworkDamageListener.NO_DAMAGE_KEY; + public class LootboxOpenMenu extends Menu { // ** Animation @@ -234,6 +237,7 @@ public void run() { meta.setPower(2); firework.setFireworkMeta(meta); firework.detonate(); + firework.getPersistentDataContainer().set(NO_DAMAGE_KEY, PersistentDataType.BOOLEAN, true); }); MessagesManager.broadcastMessage(TranslationManager.translation("feature.tickets.loot.broadcast", diff --git a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTable.java b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTable.java index cb042adf0..fc9ea0db9 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTable.java +++ b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTable.java @@ -29,7 +29,7 @@ public double getChanceOf(ItemStack item) { .sum(); } - public List rollLoots(Player receiver, boolean giveLoots) { + public List rollLoots(Player receiver) { List result = new ArrayList<>(); double totalChance = this.getLoots().stream() @@ -42,8 +42,7 @@ public List rollLoots(Player receiver, boolean giveLoots) { for (CustomLoot loot : this.getLoots()) { sumChance += loot.getChance(); if (roll <= sumChance) { - if (giveLoots) - loot.run(receiver); + loot.run(receiver); result.add(loot); break; } @@ -51,16 +50,37 @@ public List rollLoots(Player receiver, boolean giveLoots) { if (result.isEmpty()) { CustomLoot next = this.getLoots().iterator().next(); - if (giveLoots) - next.run(receiver); + next.run(receiver); result.add(next); } return result; } - public List rollLoots(Player receiver) { - return rollLoots(receiver, true); + public List rollLoots() { + List result = new ArrayList<>(); + + double totalChance = this.getLoots().stream() + .mapToDouble(CustomLoot::getChance) + .sum(); + + double roll = Math.random() * totalChance; + double sumChance = 0.0; + + for (CustomLoot loot : this.getLoots()) { + sumChance += loot.getChance(); + if (roll <= sumChance) { + result.add(loot); + break; + } + } + + if (result.isEmpty()) { + CustomLoot next = this.getLoots().iterator().next(); + result.add(next); + } + + return result; } public List rollLootsWithAmount(Player receiver, int amountRoll) { diff --git a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java index 2b6797b86..5f4b07877 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java +++ b/src/main/java/fr/openmc/core/registry/loottable/CustomLootTableRegistry.java @@ -2,6 +2,11 @@ import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.VampireLootTable; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob.AncientMobLootTable; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob.CorruptedMobLootTable; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob.CursedMobLootTable; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.loottable.bloodymob.EnragedMobLootTable; import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.loottable.fishing.BasicFishLootTable; import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.loottable.fishing.MiraculousFishLootTable; import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.loottable.fishing.SeaCreatureLootTable; @@ -19,12 +24,17 @@ public class CustomLootTableRegistry extends Registry i public final CustomLootTable MIRACULOUS_FISHING = register(new MiraculousFishLootTable()); public final CustomLootTable BASIC_FISHING = register(new BasicFishLootTable()); public final CustomLootTable SEA_CREATURE = register(new SeaCreatureLootTable()); - public final CustomLootTable FISHING_FURNITURE = register(new FishingFurnitureLootTable()); public final CustomLootTable RARE_FISHING_TREASURE = register(new RareFishingTreasureLootTable()); public final CustomLootTable EPIC_FISHING_TREASURE = register(new EpicFishingTreasureLootTable()); public final CustomLootTable LEGENDARY_FISHING_TREASURE = register(new LegendaryFishingTreasureLootTable()); + public final CustomLootTable VAMPIRE = register(new VampireLootTable()); + public final CustomLootTable CORRUPTED_MOB = register(new CorruptedMobLootTable()); + public final CustomLootTable CURSED_MOB = register(new CursedMobLootTable()); + public final CustomLootTable ENRAGED_MOB = register(new EnragedMobLootTable()); + public final CustomLootTable ANCIENT_MOB = register(new AncientMobLootTable()); + @Override public String key(CustomLootTable registryObject) { return registryObject.getNamespace(); diff --git a/src/main/java/fr/openmc/core/registry/loottable/loots/CustomLoot.java b/src/main/java/fr/openmc/core/registry/loottable/loots/CustomLoot.java index 7722cbd8e..55d6dae7c 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/loots/CustomLoot.java +++ b/src/main/java/fr/openmc/core/registry/loottable/loots/CustomLoot.java @@ -1,6 +1,7 @@ package fr.openmc.core.registry.loottable.loots; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -18,4 +19,24 @@ default ItemStack getRepresentativeItem() { } return null; } + + /** + * Envoie le message de loot contenant, le nom du loot et la chance du loot + * @param player le joueur a qui envoyé le message + * @param amount le nombre de loot + */ + default void sendLootMessage(Player player, int amount) { + Component base = Component.text(" - ", NamedTextColor.GRAY); + + if (amount != -1) + base = base.append(Component.text(amount + "x ")); + + if (this.getDisplayText() != null && + !(this instanceof TableLoot)) { + base = base.append(this.getDisplayText()) + .append(Component.text(" ("+ Math.round(this.getChance() * 100.0) +"% ★)", NamedTextColor.AQUA)); + + player.sendMessage(base); + } + } } \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/loottable/loots/ItemLoot.java b/src/main/java/fr/openmc/core/registry/loottable/loots/ItemLoot.java index 014003291..3f60524f8 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/loots/ItemLoot.java +++ b/src/main/java/fr/openmc/core/registry/loottable/loots/ItemLoot.java @@ -53,6 +53,14 @@ public ItemLoot(ItemStack item, double chance, int minAmount, int maxAmount) { maxAmount); } + public ItemLoot(ItemStack item, double chance, int amount) { + this(Collections.singleton(item), + null, + chance, + amount, + amount); + } + public ItemLoot(Material item, double chance, int minAmount, int maxAmount) { this(ItemStack.of(item), chance, @@ -60,6 +68,16 @@ public ItemLoot(Material item, double chance, int minAmount, int maxAmount) { maxAmount); } + public ItemLoot(Material item, double chance, int amount) { + if (item == null) { + throw new IllegalArgumentException("CustomItem cannot be null"); + } + this(Collections.singleton(ItemStack.of(item)), + null, + chance, + amount, + amount); + } public ItemLoot(CustomItem item, double chance, int amount) { if (item == null) { throw new IllegalArgumentException("CustomItem cannot be null"); diff --git a/src/main/java/fr/openmc/core/registry/loottable/loots/TableLoot.java b/src/main/java/fr/openmc/core/registry/loottable/loots/TableLoot.java index f5d80c55d..983fd9dc6 100644 --- a/src/main/java/fr/openmc/core/registry/loottable/loots/TableLoot.java +++ b/src/main/java/fr/openmc/core/registry/loottable/loots/TableLoot.java @@ -53,6 +53,9 @@ public Component getDisplayText() { @Override public Set run(Player receiver) { - return Set.copyOf(lootTable.rollLoots(receiver, this.giveRewards)); + if (this.giveRewards) + return Set.copyOf(lootTable.rollLoots(receiver)); + else + return Set.copyOf(lootTable.rollLoots()); } } \ No newline at end of file diff --git a/src/main/java/fr/openmc/core/registry/mobs/CustomMob.java b/src/main/java/fr/openmc/core/registry/mobs/CustomMob.java index 02e8f72f2..ddabaafa3 100644 --- a/src/main/java/fr/openmc/core/registry/mobs/CustomMob.java +++ b/src/main/java/fr/openmc/core/registry/mobs/CustomMob.java @@ -69,8 +69,8 @@ public T spawn(Location location) { } // * peut etre Override - public void apply(LivingEntity livingEntity) { - applyStats(livingEntity); + public void apply(T entity) { + applyStats(entity); } // * peut etre Override @@ -93,6 +93,8 @@ public EntitySnapshot getMobSnapshot(Object... objects) { public T getPreBuildMob(Location spawnLocation) { T livingEntity = spawnLocation.getWorld().spawn(spawnLocation.add(0, 1, 0), entityClass, null, CreatureSpawnEvent.SpawnReason.CUSTOM); applyStats(livingEntity); + + CustomMobRegistry.HAS_BOSSBAR.add(livingEntity.getUniqueId()); return livingEntity; } @@ -113,11 +115,7 @@ public void applyStats(LivingEntity livingEntity) { .orElse(livingEntity.getHealth()) ); - livingEntity.getPersistentDataContainer().set( - CustomMobRegistry.CUSTOM_MOB_KEY, - PersistentDataType.STRING, - this.getId() - ); + registerAsCustomMob(livingEntity); } public double getHealth() { @@ -127,4 +125,16 @@ public double getHealth() { .map(CustomMobAttribute::value) .orElse(20.0); } + + public void registerAsCustomMob(LivingEntity entity) { + entity.getPersistentDataContainer().set( + CustomMobRegistry.CUSTOM_MOB_KEY, + PersistentDataType.STRING, + this.getId() + ); + } + + public void unregisterAsCustomMob(LivingEntity entity) { + entity.getPersistentDataContainer().remove(CustomMobRegistry.CUSTOM_MOB_KEY); + } } diff --git a/src/main/java/fr/openmc/core/registry/mobs/CustomMobEntry.java b/src/main/java/fr/openmc/core/registry/mobs/CustomMobEntry.java index 4d7676298..09e31e030 100644 --- a/src/main/java/fr/openmc/core/registry/mobs/CustomMobEntry.java +++ b/src/main/java/fr/openmc/core/registry/mobs/CustomMobEntry.java @@ -16,7 +16,7 @@ public CustomMob getMob() { } public void apply(LivingEntity entity) { - getMob().apply(entity); + ((CustomMob) getMob()).apply(entity); } public Entity spawn(Location spawningLocation) { diff --git a/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java b/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java index 6806d3d08..495a9fee0 100644 --- a/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java +++ b/src/main/java/fr/openmc/core/registry/mobs/CustomMobRegistry.java @@ -3,14 +3,28 @@ import fr.openmc.core.OMCPlugin; import fr.openmc.core.bootstrap.registries.KeyedRegistry; import fr.openmc.core.bootstrap.registries.Registry; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat.ExplosiveVampireBat; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat.LevitationVampireBat; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bat.PoisonVampireBat; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.AncientMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.CorruptedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.CursedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.bloodytypes.EnragedMonster; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireBoss; +import fr.openmc.core.features.events.contents.dailyevents.contents.bloodynight.contents.mobs.vampire.VampireSlave; import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.mobs.*; import fr.openmc.core.features.events.contents.dailyevents.contents.miraculousfishing.contents.mobs.kraken.Kraken; +import fr.openmc.core.registry.mobs.task.MobBossbarUpdater; import org.bukkit.NamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.event.Listener; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + public class CustomMobRegistry extends Registry implements KeyedRegistry { public static final NamespacedKey CUSTOM_MOB_KEY = @@ -57,6 +71,62 @@ public class CustomMobRegistry extends Registry implemen GiantElderGuardian::new )); + public final CustomMobEntry ANCIENT_MONSTER = register(new CustomMobEntry( + "omc_daily_events:ancient_monster", + AncientMonster::new + )); + + public final CustomMobEntry CURSED_MONSTER = register(new CustomMobEntry( + "omc_daily_events:cursed_monster", + CursedMonster::new + )); + + public final CustomMobEntry ENRAGED_MONSTER = register(new CustomMobEntry( + "omc_daily_events:enraged_monster", + EnragedMonster::new + )); + + public final CustomMobEntry CORRUPTED_MONSTER = register(new CustomMobEntry( + "omc_daily_events:corrupted_monster", + CorruptedMonster::new + )); + + public final CustomMobEntry EXPLOSIVE_VAMPIRE_BAT = register(new CustomMobEntry( + "omc_daily_events:explosive_vampire_bat", + ExplosiveVampireBat::new + )); + + public final CustomMobEntry POISON_VAMPIRE_BAT = register(new CustomMobEntry( + "omc_daily_events:poison_vampire_bat", + PoisonVampireBat::new + )); + + public final CustomMobEntry LEVITATION_VAMPIRE_BAT = register(new CustomMobEntry( + "omc_daily_events:levitation_vampire_bat", + LevitationVampireBat::new + )); + + public final CustomMobEntry VAMPIRE_BOSS = register(new CustomMobEntry( + "omc_daily_events:vampire_boss", + VampireBoss::new + )); + + public final CustomMobEntry VAMPIRE_SLAVE = register(new CustomMobEntry( + "omc_daily_events:vampire_slave", + VampireSlave::new + )); + + public final static Set HAS_BOSSBAR = new HashSet<>(); + + @Override + public void postInit() { + new MobBossbarUpdater().runTaskTimer( + OMCPlugin.getInstance(), + 0L, + 20L + ); + } + @Override public String key(CustomMobEntry registryObject) { return registryObject.id(); diff --git a/src/main/java/fr/openmc/core/registry/mobs/MobAttack.java b/src/main/java/fr/openmc/core/registry/mobs/MobAttack.java new file mode 100644 index 000000000..e85cc3803 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/mobs/MobAttack.java @@ -0,0 +1,9 @@ +package fr.openmc.core.registry.mobs; + +/** + * Une interface représentant des attaques custom de mob. + */ +public interface MobAttack { + + void execute(); +} diff --git a/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobBossbarListener.java b/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobBossbarListener.java new file mode 100644 index 000000000..24f05aebb --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobBossbarListener.java @@ -0,0 +1,44 @@ +package fr.openmc.core.registry.mobs.listeners; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.registry.mobs.options.MobBossbarImpl; +import org.bukkit.Bukkit; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class CustomMobBossbarListener implements Listener { + @EventHandler(ignoreCancelled = true) + public void onCustomMobDamage(EntityDamageEvent event) { + if (!(event.getEntity() instanceof LivingEntity entity)) return; + + CustomMob customMob = OMCRegistry.CUSTOM_MOBS.getMob(entity); + + if (!(customMob instanceof MobBossbarImpl bossBarMob)) return; + + Bukkit.getScheduler().runTask(OMCPlugin.getInstance(), () -> { + if (!entity.isValid() || entity.isDead()) { + bossBarMob.removeBossBar(entity); + return; + } + + bossBarMob.updateBossBar(entity); + bossBarMob.updateBossBarViewers(entity); + }); + } + + @EventHandler + public void onCustomMobDeath(EntityDeathEvent event) { + LivingEntity entity = event.getEntity(); + + CustomMob customMob = OMCRegistry.CUSTOM_MOBS.getMob(entity); + + if (customMob instanceof MobBossbarImpl bossBarMob) { + bossBarMob.removeBossBar(entity); + } + } +} diff --git a/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobLoadListener.java b/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobLoadListener.java new file mode 100644 index 000000000..28b26f5b8 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/mobs/listeners/CustomMobLoadListener.java @@ -0,0 +1,21 @@ +package fr.openmc.core.registry.mobs.listeners; + +import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.registry.mobs.CustomMobRegistry; +import fr.openmc.core.registry.mobs.options.MobBossbarImpl; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class CustomMobLoadListener implements Listener { + @EventHandler + public void onEntityLoad(EntityAddToWorldEvent event) { + CustomMob customMob = OMCRegistry.CUSTOM_MOBS.getMob(event.getEntity()); + if (customMob == null) return; + if (!(customMob instanceof MobBossbarImpl)) return; + if (!(event.getEntity() instanceof MobBossbarImpl)) return; + + CustomMobRegistry.HAS_BOSSBAR.add(event.getEntity().getUniqueId()); + } +} diff --git a/src/main/java/fr/openmc/core/registry/mobs/options/MobBossbarImpl.java b/src/main/java/fr/openmc/core/registry/mobs/options/MobBossbarImpl.java new file mode 100644 index 000000000..6a10462dd --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/mobs/options/MobBossbarImpl.java @@ -0,0 +1,109 @@ +package fr.openmc.core.registry.mobs.options; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public interface MobBossbarImpl { + Map ACTIVE_BOSS_BARS = new ConcurrentHashMap<>(); + + default Component getBossBarName(LivingEntity entity) { + return entity.customName() != null + ? entity.customName() + : Component.text(entity.getName()); + } + + BossBar.Color getBossBarColor(); + + BossBar.Overlay getBossBarOverlay(); + + double getBossBarViewRadius(); + + default BossBar createBossBar(LivingEntity entity) { + BossBar bossBar = BossBar.bossBar( + getBossBarName(entity), + getHealthProgress(entity), + getBossBarColor(), + getBossBarOverlay() + ); + + ACTIVE_BOSS_BARS.put(entity.getUniqueId(), bossBar); + + return bossBar; + } + + default BossBar getBossBar(LivingEntity entity) { + return ACTIVE_BOSS_BARS.get(entity.getUniqueId()); + } + + default BossBar getOrCreateBossBar(LivingEntity entity) { + BossBar bossBar = getBossBar(entity); + + if (bossBar != null) { + return bossBar; + } + + return createBossBar(entity); + } + + default void updateBossBar(LivingEntity entity) { + BossBar bossBar = getOrCreateBossBar(entity); + + bossBar.name(getBossBarName(entity)); + bossBar.progress(getHealthProgress(entity)); + } + + default void updateBossBarViewers(LivingEntity entity) { + BossBar bossBar = getOrCreateBossBar(entity); + + double radius = getBossBarViewRadius(); + double radiusSquared = radius * radius; + + Collection players = entity.getWorld().getPlayers(); + + for (Player player : players) { + boolean shouldSee = + player.getLocation().distanceSquared(entity.getLocation()) + <= radiusSquared; + + if (shouldSee) { + player.showBossBar(bossBar); + } else { + player.hideBossBar(bossBar); + } + } + } + + default void removeBossBar(LivingEntity entity) { + BossBar bossBar = ACTIVE_BOSS_BARS.remove(entity.getUniqueId()); + + if (bossBar == null) { + return; + } + + for (Player player : entity.getWorld().getPlayers()) { + player.hideBossBar(bossBar); + } + } + + private float getHealthProgress(LivingEntity entity) { + AttributeInstance maxHealthAttribute = entity.getAttribute(Attribute.MAX_HEALTH); + if (maxHealthAttribute == null) return 0; + + if (maxHealthAttribute.getValue() <= 0) { + return 0.0F; + } + + double progress = entity.getHealth() / maxHealthAttribute.getValue(); + + return (float) Math.clamp(progress, 0.0, 1.0); + } +} diff --git a/src/main/java/fr/openmc/core/registry/mobs/task/MobBossbarUpdater.java b/src/main/java/fr/openmc/core/registry/mobs/task/MobBossbarUpdater.java new file mode 100644 index 000000000..0dcf78015 --- /dev/null +++ b/src/main/java/fr/openmc/core/registry/mobs/task/MobBossbarUpdater.java @@ -0,0 +1,39 @@ +package fr.openmc.core.registry.mobs.task; + +import fr.openmc.core.OMCRegistry; +import fr.openmc.core.registry.mobs.CustomMob; +import fr.openmc.core.registry.mobs.CustomMobRegistry; +import fr.openmc.core.registry.mobs.options.MobBossbarImpl; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Iterator; +import java.util.UUID; + +public class MobBossbarUpdater extends BukkitRunnable { + + @Override + public void run() { + Iterator iterator = CustomMobRegistry.HAS_BOSSBAR.iterator(); + + while (iterator.hasNext()) { + UUID uuid = iterator.next(); + Entity entity = Bukkit.getEntity(uuid); + + if (!(entity instanceof LivingEntity livingEntity)) return; + CustomMob customMob = OMCRegistry.CUSTOM_MOBS.getMob(entity); + if (customMob == null) return; + if (!(customMob instanceof MobBossbarImpl bossBarMob)) return; + + if (!livingEntity.isValid() || livingEntity.isDead()) { + bossBarMob.removeBossBar(livingEntity); + iterator.remove(); + continue; + } + + bossBarMob.updateBossBarViewers(livingEntity); + } + } +} diff --git a/src/main/java/fr/openmc/core/utils/bukkit/EntityUtils.java b/src/main/java/fr/openmc/core/utils/bukkit/EntityUtils.java index c32354f7f..e2fb0b4ed 100644 --- a/src/main/java/fr/openmc/core/utils/bukkit/EntityUtils.java +++ b/src/main/java/fr/openmc/core/utils/bukkit/EntityUtils.java @@ -1,13 +1,15 @@ package fr.openmc.core.utils.bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; import org.bukkit.entity.LivingEntity; public class EntityUtils { /** * Ajoute un attribut si l'AttributeInstance n'est pas nulle - * @param entity l'entity + * @param entity l'entité * @param attribute l'attribut qui veut etre mis * @param value la valeur appliquée a l'attribut */ @@ -17,4 +19,51 @@ public static void setAttributeIfPresent(LivingEntity entity, Attribute attribut attr.setBaseValue(value); } } + + /** + * Ajoute un attribut si l'AttributeInstance n'est pas nulle + * @param entity l'entité + * @param attribute l'attribut qui veut etre mis + * @param modifier le modifier appliqué sur la stats de l'entité + */ + public static void addModifierIfPresent(LivingEntity entity, Attribute attribute, AttributeModifier modifier) { + AttributeInstance attr = entity.getAttribute(attribute); + if (attr == null) return; + + NamespacedKey modifierKey = modifier.getKey(); + if (attr.getModifier(modifierKey) != null) { + attr.removeModifier(modifierKey); + } + + attr.addModifier(modifier); + } + + /** + * Supprime un modifier si l'AttributeInstance n'est pas nulle et que le modifier est présent + * @param entity l'entité + * @param attribute l'attribut qui est ciblé + * @param modifier le modifier + */ + public static void removeModifierIfPresent(LivingEntity entity, Attribute attribute, AttributeModifier modifier) { + AttributeInstance attr = entity.getAttribute(attribute); + if (attr == null) return; + NamespacedKey modifierKey = modifier.getKey(); + if (attr.getModifier(modifierKey) == null) return; + + attr.removeModifier(modifierKey); + } + + /** + * Supprime un modifier si l'AttributeInstance n'est pas nulle et que le modifier est présent + * @param entity l'entité + * @param attribute l'attribut qui est ciblé + * @param modifierKey la cle du modifier + */ + public static void removeModifierIfPresent(LivingEntity entity, Attribute attribute, NamespacedKey modifierKey) { + AttributeInstance attr = entity.getAttribute(attribute); + if (attr == null) return; + if (attr.getModifier(modifierKey) == null) return; + + attr.removeModifier(modifierKey); + } } diff --git a/src/main/java/fr/openmc/core/utils/bukkit/ParticleUtils.java b/src/main/java/fr/openmc/core/utils/bukkit/ParticleUtils.java index 0d919c5a4..1f693ae36 100644 --- a/src/main/java/fr/openmc/core/utils/bukkit/ParticleUtils.java +++ b/src/main/java/fr/openmc/core/utils/bukkit/ParticleUtils.java @@ -128,6 +128,12 @@ public static void sendParticlePacket(Player player, Particle particle, Location sendParticlePacket(player, particle, loc, 3, 0.2f, 0.2f, 0.2f, 0.01f, null); } + public static void sendParticlePacket(Collection receivers, Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double speed, T data) { + for (Player player : receivers) { + sendParticlePacket(player, particle, location, count, offsetX, offsetY, offsetZ, speed, data); + } + } + public static void sendParticlePacket(Player player, Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double speed, T data) { ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); diff --git a/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java b/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java index 0cec2d3e0..d70000e21 100644 --- a/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java +++ b/src/main/java/fr/openmc/core/utils/nms/PlayerRespawnNMS.java @@ -2,14 +2,15 @@ import fr.openmc.core.OMCPlugin; import fr.openmc.core.registry.ambient.CustomAmbient; -import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket; +import net.minecraft.network.protocol.game.ClientboundRespawnPacket; +import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; -import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; -import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.storage.LevelData; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftServer; @@ -43,12 +44,12 @@ private static void sendSimplePacket(ServerPlayer nmsPlayer, CommonPlayerSpawnIn * @param nmsPlayer le joueur en NMS * @param spawnInfo Les informations de la dimension ou il est envoyé */ - public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo spawnInfo) { + public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo spawnInfo, boolean isJoining) { // ** Envoie de l'entete du packet respawn sendSimplePacket(nmsPlayer, spawnInfo); // ** Procédure afin que le packet respawn soit valide - sendPostRespawnPackets(nmsPlayer); + sendPostRespawnPackets(nmsPlayer, isJoining); resyncEntities(nmsPlayer.getBukkitEntity().getPlayer()); } @@ -62,11 +63,15 @@ public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo spaw * Note : Assurer vous que le pivot ne pointe pas vers une dimension ou le joueur est déja * {@code nmsPlayer.createCommonSpawnInfo(nmsPlayer.level()).dimension().equals(Level.OVERWORLD) ? Level.END : Level.OVERWORLD;} */ - public static void sendPacket(ServerPlayer nmsPlayer, CommonPlayerSpawnInfo targetSpawnInfo, ResourceKey pivotDimension) { + public static void sendPacket( + ServerPlayer nmsPlayer, + CommonPlayerSpawnInfo targetSpawnInfo, + ResourceKey pivotDimension, + boolean isJoining) { // changement de dimension car sinon l'ambience de la dimension n'est pas affiché // l'unique packet de repsawn pour simuler un changement de dimension + envoie du dimension type = plus rapide sendSimplePacket(nmsPlayer, createPlayerSpawnInfoWithDimension(nmsPlayer, pivotDimension)); - sendPacket(nmsPlayer, targetSpawnInfo); + sendPacket(nmsPlayer, targetSpawnInfo, isJoining); } /** @@ -94,7 +99,7 @@ public void run() { * Procédure basée sur {@link ServerPlayer#teleport} afin de corriger que le packet RESPAWN invalide * @param nmsPlayer le joueur (NMS) */ - private static void sendPostRespawnPackets(ServerPlayer nmsPlayer) { + private static void sendPostRespawnPackets(ServerPlayer nmsPlayer, boolean isJoining) { ServerLevel nmsWorld = nmsPlayer.level(); LevelData levelData = nmsWorld.getLevelData(); @@ -113,22 +118,11 @@ private static void sendPostRespawnPackets(ServerPlayer nmsPlayer) { PlayerPositionNMS.sendPos(nmsPlayer, nmsPlayer.position()); - nmsPlayer.connection.send(new ClientboundSetChunkCacheCenterPacket( - nmsPlayer.chunkPosition().x(), - nmsPlayer.chunkPosition().z() - )); - - int viewDistance = nmsWorld.getServer().getPlayerList().getViewDistance(); - ChunkPos center = nmsPlayer.chunkPosition(); - for (int cx = center.x() - viewDistance; cx <= center.x() + viewDistance; cx++) { - for (int cz = center.z() - viewDistance; cz <= center.z() + viewDistance; cz++) { - LevelChunk chunk = nmsWorld.getChunkIfLoaded(cx, cz); - if (chunk != null) { - nmsPlayer.connection.send( - new ClientboundLevelChunkWithLightPacket(chunk, nmsWorld.getLightEngine(), null, null, false) - ); - } - } + // Utilisation d'une méthode trouvé via l'utilisation de ClientboundSetChunkCacheCenterPacket. + // Refait tout simplement le systeme de chargement des chunks pour un joueur, sans avoir de défaillance + if (!isJoining) { + nmsWorld.moonrise$getPlayerChunkLoader().removePlayer(nmsPlayer); + nmsWorld.moonrise$getPlayerChunkLoader().addPlayer(nmsPlayer); } } diff --git a/src/main/java/fr/openmc/core/utils/nms/entity/EntityGlowNMS.java b/src/main/java/fr/openmc/core/utils/nms/entity/EntityGlowNMS.java new file mode 100644 index 000000000..1baf276b4 --- /dev/null +++ b/src/main/java/fr/openmc/core/utils/nms/entity/EntityGlowNMS.java @@ -0,0 +1,112 @@ +package fr.openmc.core.utils.nms.entity; + +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class EntityGlowNMS implements Listener { + private static final Scoreboard NMS_SCOREBOARD = new Scoreboard(); + private static final Map TEAM_MAP = new HashMap<>(); + private static final Map entitiesGlowing = new ConcurrentHashMap<>(); + + + public EntityGlowNMS() { + for (TeamColor color : TeamColor.values()) { + PlayerTeam team = new PlayerTeam(NMS_SCOREBOARD, "omc_glow_" + color.getSerializedName()); + + team.setColor(Optional.of(color)); + team.setSeeFriendlyInvisibles(false); + team.setNameTagVisibility(PlayerTeam.Visibility.NEVER); + + TEAM_MAP.put(color, team); + } + } + + public static void setGlowingColor(Entity entity, TeamColor color) { + TeamColor previous = entitiesGlowing.get(entity.getUniqueId()); + if (previous != null && previous != color) { + PlayerTeam previousTeam = TEAM_MAP.get(previous); + sendPacket(ClientboundSetPlayerTeamPacket.createPlayerPacket( + previousTeam, entity.getUniqueId().toString(), ClientboundSetPlayerTeamPacket.Action.REMOVE + )); + } else if (previous == color) { + entity.setGlowing(true); + return; + } + + entity.setGlowing(true); + + PlayerTeam team = TEAM_MAP.get(color); + String entry = entity.getUniqueId().toString(); + + ClientboundSetPlayerTeamPacket addEntityPacket = ClientboundSetPlayerTeamPacket.createPlayerPacket( + team, + entry, + ClientboundSetPlayerTeamPacket.Action.ADD + ); + + sendPacket(addEntityPacket); + entitiesGlowing.put(entity.getUniqueId(), color); + } + + public static void removeGlowing(Entity entity) { + entity.setGlowing(false); + TeamColor current = entitiesGlowing.remove(entity.getUniqueId()); + + if (current == null) return; + + PlayerTeam team = TEAM_MAP.get(current); + String entry = entity.getUniqueId().toString(); + + ClientboundSetPlayerTeamPacket removeEntityPacket = ClientboundSetPlayerTeamPacket.createPlayerPacket( + team, + entry, + ClientboundSetPlayerTeamPacket.Action.REMOVE + ); + + sendPacket(removeEntityPacket); + } + + private static void sendPacket(ClientboundSetPlayerTeamPacket packet) { + for (Player player : Bukkit.getOnlinePlayers()) { + ((CraftPlayer) player).getHandle().connection.send(packet); + } + } + + private static void sendPacketTo(Player player, ClientboundSetPlayerTeamPacket packet) { + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player joined = event.getPlayer(); + for (TeamColor color : TeamColor.values()) { + PlayerTeam team = TEAM_MAP.get(color); + if (team == null) continue; + + sendPacketTo(joined, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + } + + for (Map.Entry entry : entitiesGlowing.entrySet()) { + PlayerTeam team = TEAM_MAP.get(entry.getValue()); + if (team == null) continue; + sendPacketTo(joined, ClientboundSetPlayerTeamPacket.createPlayerPacket( + team, entry.getKey().toString(), ClientboundSetPlayerTeamPacket.Action.ADD + )); + } + } +} diff --git a/src/main/java/fr/openmc/core/utils/world/LocationUtils.java b/src/main/java/fr/openmc/core/utils/world/LocationUtils.java index 8d8fc52c0..7faf407fa 100644 --- a/src/main/java/fr/openmc/core/utils/world/LocationUtils.java +++ b/src/main/java/fr/openmc/core/utils/world/LocationUtils.java @@ -19,6 +19,12 @@ public static Location randomLocation(Location origin, double distance) { return target.getWorld().getHighestBlockAt(target).getLocation().add(0, 1, 0); } + public static Location randomLocation(Location origin, double minDistance, double maxDistance) { + double distance = minDistance + Math.random() * (maxDistance - minDistance); + + return randomLocation(origin, distance); + } + public static Location getSafeNearbySurface(Location location, int radius) { World world = location.getWorld(); int baseY = location.getBlockY(); diff --git a/src/main/resources/contents/omc_daily_events/armor.yml b/src/main/resources/contents/omc_daily_events/armor.yml index c8dfa6db4..3b915496f 100644 --- a/src/main/resources/contents/omc_daily_events/armor.yml +++ b/src/main/resources/contents/omc_daily_events/armor.yml @@ -15,6 +15,8 @@ items: - armor/ancient_fishing/ancient_helmet material: LEATHER_HELMET lore: + - "§8+ §b{armor_fishing_speed:a}% §7de §bvitesse de pêche" + - "§8+ §1{armor_double_hook_chance:a}% §7de chance de §1double prise" - "§7§oUn casque trouvé dans les abysses des mers" durability: unbreakable: true @@ -31,6 +33,8 @@ items: - armor/ancient_fishing/ancient_chestplate material: LEATHER_CHESTPLATE lore: + - "§8+ §b{armor_fishing_speed:a}% §7de §bvitesse de pêche" + - "§8+ §1{armor_double_hook_chance:a}% §7de chance de §1double prise" - "§7§oUn plastron trouvé dans les abysses des mers" durability: unbreakable: true @@ -46,6 +50,8 @@ items: - armor/ancient_fishing/ancient_leggings material: LEATHER_LEGGINGS lore: + - "§8+ §b{armor_fishing_speed:a}% §7de §bvitesse de pêche" + - "§8+ §1{armor_double_hook_chance:a}% §7de chance de §1double prise" - "§7§oUne jambière trouvé dans les abysses des mers" durability: unbreakable: true @@ -62,6 +68,8 @@ items: - armor/ancient_fishing/ancient_boots material: LEATHER_BOOTS lore: + - "§8+ §b{armor_fishing_speed:a}% §7de §bvitesse de pêche" + - "§8+ §1{armor_double_hook_chance:a}% §7de chance de §1double prise" - "§7§oDes bottes trouvées dans les abysses des mers" durability: unbreakable: true diff --git a/src/main/resources/contents/omc_daily_events/head.yml b/src/main/resources/contents/omc_daily_events/head.yml index 53b204b82..90d141995 100644 --- a/src/main/resources/contents/omc_daily_events/head.yml +++ b/src/main/resources/contents/omc_daily_events/head.yml @@ -41,4 +41,9 @@ items: leviathan_head: display_name: "Tete de Léviathan" components_nbt_file: "nbt/head/leviathan.json" + material: PLAYER_HEAD + + vampire_head: + display_name: "Tete de Vampire" + components_nbt_file: "nbt/head/vampire.json" material: PLAYER_HEAD \ No newline at end of file diff --git a/src/main/resources/contents/omc_daily_events/nbt/head/vampire.json b/src/main/resources/contents/omc_daily_events/nbt/head/vampire.json new file mode 100644 index 000000000..9a07e7681 --- /dev/null +++ b/src/main/resources/contents/omc_daily_events/nbt/head/vampire.json @@ -0,0 +1,12 @@ +{ + "components": { + "profile": { + "properties": [ + { + "name": "textures", + "value": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWFhMjllYTk2MTc1N2RjM2M5MGJmYWJmMzAyYzVhYmU5ZDMwOGZiNGE3ZDM4NjRlNTc4OGFkMmNjOTE2MGFhMiJ9fX0=" + } + ] + } + } +} \ No newline at end of file diff --git a/src/main/resources/contents/omc_daily_events/resourcepack.assets.minecraft.textures.environment.celestial.moon/full_moon.png b/src/main/resources/contents/omc_daily_events/resourcepack.assets.minecraft.textures.environment.celestial.moon/full_moon.png deleted file mode 100644 index a97e7272f..000000000 Binary files a/src/main/resources/contents/omc_daily_events/resourcepack.assets.minecraft.textures.environment.celestial.moon/full_moon.png and /dev/null differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.fsh b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.fsh new file mode 100644 index 000000000..9ab716101 --- /dev/null +++ b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.fsh @@ -0,0 +1,31 @@ +#version 330 +#moj_import +#moj_import + +#moj_import + +in vec2 texCoord0; +flat in int isCelestial; +flat in float textureShift; +flat in float frames; +flat in float textureHeight; +flat in vec2 atlasSize; + +uniform sampler2D Sampler0; + +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, texCoord0); + + if (isCelestial == 1) + { + vec2 uv = texCoord0 * atlasSize; + + uv = shiftTextureUV(uv, textureHeight, frames, textureShift); + + color = texture(Sampler0, uv / atlasSize); + } + + fragColor = color * ColorModulator; +} diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.vsh b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.vsh new file mode 100644 index 000000000..1edefabe5 --- /dev/null +++ b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/shaders/core/position_tex.vsh @@ -0,0 +1,101 @@ +#version 330 +#moj_import +#moj_import +#moj_import +#moj_import +#moj_import + +in vec3 Position; +in vec2 UV0; +in vec4 Color; + +uniform sampler2D Sampler0; + +out vec2 texCoord0; +flat out int isCelestial; +flat out float frames; +flat out float textureShift; +flat out float textureHeight; +flat out vec2 atlasSize; + +bool isMarker(vec4 color, vec4 compareColor) { + return all(lessThan(abs(color.rgb - compareColor.rgb), vec3(0.004))); +} + +int detectMarker(vec2 uv) { + ivec2 texelCoord = ivec2(uv * atlasSize); + vec4 c = texelFetch(Sampler0, texelCoord, 0); + + if (isMarker(c, vec4(1.0, 1.0, 1.0, 1.0) / 255.0)) return 1; // Sun + else if (isMarker(c, vec4(3.0, 3.0, 3.0, 1.0) / 255.0)) return 2; // Moon + return 0; +} + +mat3 rotateX(float a) { + float s = sin(a); + float c = cos(a); + return mat3( + 1.0, 0.0, 0.0, + 0.0, c, -s, + 0.0, s, c + ); +} + +mat3 rotateZ(float a) { + float s = sin(a); + float c = cos(a); + return mat3( + c, -s, 0.0, + s, c, 0.0, + 0.0, 0.0, 1.0 + ); +} + +mat3 rotateY(float a) { + float s = sin(a); + float c = cos(a); + return mat3( + c, 0.0, s, + 0.0, 1.0, 0.0, + -s, 0.0, c + ); +} + +void main() { + texCoord0 = UV0; + atlasSize = vec2(textureSize(Sampler0, 0)); + float size = 1.0; + float tilt = 0.0; + float SunOffset = 0.0; + + switch (detectMarker(UV0)) + { // Sun + case 1: { + isCelestial = 1; + frames = 1.0; + textureHeight = 64.0; + switch (getDimension(FogColor.rgb, FogCloudsEnd)) + { + default: textureShift = 0.0; size = 1.0; + } break; + } // Moon + case 2: { + isCelestial = 1; + frames = 2.0; + textureHeight = 64.0; + switch (getDimension(FogColor.rgb, FogCloudsEnd)) + { + case 1: textureShift = 1.0; size = 3.0; break; // texture de blood moon + default: textureShift = 0.0; size = 1.0; + } break; + } + default: { + isCelestial = 0; + } + } + vec3 rotated = rotateY(radians(tilt)) * Position; + vec4 pos = vec4(rotated, size); + pos.x += SunOffset; + + gl_Position = ProjMat * ModelViewMat * pos; +} diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/first_quarter.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/first_quarter.png new file mode 100644 index 000000000..a123cac38 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/first_quarter.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/full_moon.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/full_moon.png new file mode 100644 index 000000000..ca795ad8e Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/full_moon.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/new_moon.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/new_moon.png new file mode 100644 index 000000000..9e36bdae9 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/new_moon.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/third_quarter.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/third_quarter.png new file mode 100644 index 000000000..e971d4970 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/third_quarter.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_crescent.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_crescent.png new file mode 100644 index 000000000..8b6549e63 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_crescent.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_gibbous.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_gibbous.png new file mode 100644 index 000000000..2058eef56 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waning_gibbous.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_crescent.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_crescent.png new file mode 100644 index 000000000..ea4599f24 Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_crescent.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_gibbous.png b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_gibbous.png new file mode 100644 index 000000000..687d05f2b Binary files /dev/null and b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/minecraft/textures/environment/celestial/moon/waxing_gibbous.png differ diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/compare_float.glsl b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/compare_float.glsl new file mode 100644 index 000000000..51156beee --- /dev/null +++ b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/compare_float.glsl @@ -0,0 +1,7 @@ +bool approxEqualsVec3(vec3 v1, vec3 v2, float eps) { + return all(greaterThan(vec3(eps), abs(v1 - v2))); +} + +bool approxEquals(float f1, float f2, float eps) { + return eps >= abs(f1 - f2); +} \ No newline at end of file diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/get_dimension.glsl b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/get_dimension.glsl new file mode 100644 index 000000000..5764ba775 --- /dev/null +++ b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/get_dimension.glsl @@ -0,0 +1,5 @@ +int getDimension(vec3 biomeFog, float cloudFogDistance) { + float bloodyCFD = 512.0032; + if (approxEquals(cloudFogDistance, bloodyCFD, 0.00001)) return 1; // Bloody Ambient + return 0; +} \ No newline at end of file diff --git a/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/shift_texture.glsl b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/shift_texture.glsl new file mode 100644 index 000000000..6f1f8bf78 --- /dev/null +++ b/src/main/resources/contents/omc_daily_events_rp/resourcepack/assets/omc_daily_events_rp/shaders/include/shift_texture.glsl @@ -0,0 +1,48 @@ +vec2 shiftTextureUV(vec2 uv, float texHeight, float frameCount, float shiftAmount) +{ + // +1 because the atlas seems to adds a row of pixels around the image (not sure why +2 doesn't seem correct then tho) + float realHeight = texHeight + 1.0; + + // Calculate the static corner UV for the texture in the atlas. + // This is done by flooring the division of the current pixel coordinate with the total texture height. + // This gets the rounded amount of times the texture would fit between the current pixel and the (0,0). + // Multiply this by the height of the texture to get the corner of each fragment in the texture. + vec2 origin = vec2(0.0, (floor(uv.y / (realHeight)) * realHeight)); + + // Shift the texture to the correct frame + float shiftBy = shiftAmount * realHeight / frameCount; + + // normalize the texture coordinate, apply the transformations, move it back + uv -= origin; + uv *= vec2(1.0, 1.0 / frameCount); + uv += vec2(0.0, shiftBy); + uv += origin; + + return uv; +} + +vec2 shiftTextureUV_special(vec2 uv, float texHeight, float frameCount, float shiftAmount) +{ + // +1 because the atlas seems to adds a row of pixels around the image (not sure why +2 doesn't seem correct then tho) + float realHeight = texHeight + 1.0; + + // height of the sun texture (+1) that must be accounted for when the moon phase texture is on the same column as the sun texture on the celestial atlas + float sunHeight = 513.0; + + // Calculate the static corner UV for the texture in the atlas. + // This is done by flooring the division of the current pixel coordinate with the total texture height. + // This gets the rounded amount of times the texture would fit between the current pixel and the (0,0). + // Multiply this by the height of the texture to get the corner of each fragment in the texture. + vec2 origin = vec2(0.0, (floor((uv.y - sunHeight) / (realHeight)) * realHeight) + sunHeight); + + // Shift the texture to the correct frame + float shiftBy = shiftAmount * realHeight / frameCount; + + // normalize the texture coordinate, apply the transformations, move it back + uv -= origin; + uv *= vec2(1.0, 1.0 / frameCount); + uv += vec2(0.0, shiftBy); + uv += origin; + + return uv; +} \ No newline at end of file diff --git a/src/main/resources/translations/default/dailyevents.properties b/src/main/resources/translations/default/dailyevents.properties index a9a60699d..be13f2dca 100644 --- a/src/main/resources/translations/default/dailyevents.properties +++ b/src/main/resources/translations/default/dailyevents.properties @@ -30,11 +30,11 @@ feature.dailyevents.broadcast.soon=*un évènement commence feature.dailyevents.miraculousfishing.broadcast.start= \
\
PËCHE MIRACLEUSE ! La grande session a commencé !\ -
*sortez vous canne à pêche et découvrez les mystères des lacs*\ +
*sortez vos cannes à pêche et découvrez les mystères des lacs*\
\
Effets :\ -
+ %1$s% vitesse de pêche\ -
+ Loots spéciaux et rares\ +
+ %1$s vitesse de pêche\ +
+ Loots spéciaux et rares\
+ Monstres des Mers\
\
@@ -50,6 +50,12 @@ feature.dailyevents.bloodynight.broadcast.start=
NUIT SANGLANTE ! Le bain de sang commence !\
*sortez vos armes et combattez les vagues de monstres*\
\ +
Effets :\ +
+ Type de monstres sanglants\ +
+ Raid de monstres\ +
+ Boss Vampire\ +
+ Régénération naturelle désactivée\ +
\
feature.dailyevents.bloodynight.broadcast.end= \
\ @@ -99,4 +105,59 @@ feature.dailyevents.miraculousfishing.menu.info.sea_creature.lore=Des loots uniques et spéciaux feature.dailyevents.miraculousfishing.menu.info.loot_table.lore=Bénéficiez d'une bénédiction miraculeuse \
vous laissant des récompenses et des loots nouveaux\ -
CLIQUEZ ICI POUR VOIR LES ITEMS PECHABLE \ No newline at end of file +
CLIQUEZ ICI POUR VOIR LES ITEMS PECHABLE + +# ** Nuit sanglante ** +feature.dailyevents.bloody_night.loot_table.corrupted_mob=Loots des monstres corrompus +feature.dailyevents.bloody_night.loot_table.cursed_mob=Loots des monstres maudits +feature.dailyevents.bloody_night.loot_table.enraged_mob=Loots des monstres enragés +feature.dailyevents.bloody_night.loot_table.ancient_mob=Loots des monstres anciens + +feature.dailyevents.bloody_night.loot_table.vampire_boss=Loots du Vampire + +feature.dailyevents.bloody_night.enchantment.vampirism.name=Vampirisme +feature.dailyevents.bloody_night.vampire_boss.name=Vampire +feature.dailyevents.bloody_night.vampire_boss.coming=Une anomalie commence à se rapprocher du sol +feature.dailyevents.bloody_night.vampire_boss.summoned= \ +
\ +
BOSS VAMPIRE ! un boss vient attérir en %1$s %2$s %3$s \ +
*unissez vous afin de le battre tous ensemble !*\ +
\ +
+ +feature.dailyevents.bloody_night.vampire_boss.defeated.no_body= \ +
\ +
BOSS VAMPIRE ! Le Vampire est parti \ +
*aucune personne à participer à l'événement, dommage!*\ +
\ +
+ + +feature.dailyevents.bloody_night.vampire_boss.defeated.start= \ +
\ +
BOSS VAMPIRE ! Le boss vient d'être térrasé ! \ +
Voici le classement : + +feature.dailyevents.bloody_night.vampire_boss.defeated.midle=
(vous) %1$s %2$s - %3$s dégats \ +
\ +
Vos récompenses : +feature.dailyevents.bloody_night.vampire_boss.defeated.rank=- %1$s %2$s - %3$s dégats +feature.dailyevents.bloody_night.vampire_boss.defeated.end= + +feature.dailyevents.bloody_night.vampire_boss.mail.received=Vous avez reçu la lettre des récompenses +feature.dailyevents.bloody_night.vampire_boss.mail.click=Cliquez ici +feature.dailyevents.bloody_night.vampire_boss.mail.hover=Ouvrir la mailbox +feature.dailyevents.bloody_night.vampire_boss.mail.open_mailbox= pour ouvrir la mailbox + +feature.dailyevents.bloody_night.menu.info.bloody_monster.name=Types de monstres sanglant +feature.dailyevents.bloody_night.menu.info.bloody_monster.lore=Vous avez %1$s de l'avoir pendant un raid \ +
CLIQUEZ ICI POUR VOIR LES LOOTS + +feature.dailyevents.bloody_night.menu.info.vampire_boss.name=Le boss Vampire +feature.dailyevents.bloody_night.menu.info.vampire_boss.lore=Le boss arrive après %1$s minutes que la nuit sanglante \ +
soit arrivée. Faites place à un combat original à plusieurs \ +
CLIQUEZ ICI POUR VOIR LES LOOTS DU BOSS + +feature.dailyevents.bloody_night.menu.info.bloody_raid.name=Les raids +feature.dailyevents.bloody_night.menu.info.bloody_raid.lore=Les raids se passent toutes les %1$s minutes. \ +
C'est ici où il y a de multiples variétés de mob qui peuvent spawn ! \ No newline at end of file