From d0fa47b6c999304a33d2b9403f0edc09bcd04ab8 Mon Sep 17 00:00:00 2001 From: MobiusDevelopment Date: Fri, 3 Jan 2025 15:09:13 +0200 Subject: [PATCH] Implemented new Queen Ant fight mechanics. Contributed by notorionn. --- .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 30 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 22 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 22 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 18 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 18 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + .../scripts/ai/bosses/QueenAnt/QueenAnt.java | 544 +++++++++++++++--- .../dist/game/data/stats/npcs/29300-29399.xml | 18 +- .../game/data/stats/skills/32600-32699.xml | 4 +- .../game/data/stats/skills/33900-33999.xml | 114 +++- .../dist/game/data/zones/no_bookmark.xml | 13 + .../dist/game/data/zones/no_summon_friend.xml | 11 + .../dist/game/data/zones/pvp.xml | 11 + 42 files changed, 3754 insertions(+), 556 deletions(-) diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index 34020ce70e..92ec761e05 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/npcs/29300-29399.xml index 8d6cd07f54..18a5f0a572 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/npcs/29300-29399.xml @@ -5906,21 +5906,26 @@ + + + + + BUG FEMALE - + - - + + - 370 + 970 @@ -5931,14 +5936,21 @@ + + + + + - 300 - true - + QUEEN_ANT + + + + @@ -5999,9 +6011,5 @@ - - - - diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/32600-32699.xml index 828f6a0de4..ce7a281e46 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/32600-32699.xml @@ -1047,7 +1047,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/33900-33999.xml index 961d06cf07..1ad03d667c 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_bookmark.xml index ed241c0d9d..db8d00569a 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_bookmark.xml @@ -8286,4 +8286,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_summon_friend.xml index 9d81d784eb..aa5b625c6d 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/no_summon_friend.xml @@ -236,4 +236,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/pvp.xml b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/pvp.xml index 64af7137ba..bea1aeb6c5 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/data/zones/pvp.xml @@ -105,4 +105,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_10.3_MasterClass/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index 34020ce70e..92ec761e05 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/npcs/29300-29399.xml index 7637f11e82..21c0399e72 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/npcs/29300-29399.xml @@ -5438,22 +5438,27 @@ + + + + + BUG FEMALE - + - - + + - 370 + 970 @@ -5464,10 +5469,13 @@ + + + + + - 300 - true - + QUEEN_ANT diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/32600-32699.xml index bf7758b898..e92ac917a0 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/32600-32699.xml @@ -1047,7 +1047,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/33900-33999.xml index 467ddcdc7c..feb3c39ce6 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_bookmark.xml index ed241c0d9d..db8d00569a 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_bookmark.xml @@ -8286,4 +8286,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_summon_friend.xml index 02a7d95b6a..7c050fd92f 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/no_summon_friend.xml @@ -289,4 +289,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/pvp.xml b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/pvp.xml index 90e9aa503a..e7d755b62b 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_10.3_MasterClass/dist/game/data/zones/pvp.xml @@ -108,4 +108,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index f5c9d7d8d0..c51c9ff2b8 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/npcs/29300-29399.xml index 9b9e1a79d6..1928be0a30 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/npcs/29300-29399.xml @@ -5458,22 +5458,27 @@ + + + + + BUG FEMALE - + - - + + - 370 + 970 @@ -5484,10 +5489,13 @@ + + + + + - 300 - true - + QUEEN_ANT diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/32600-32699.xml index d2ccdd3bdd..8eaa7d9521 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/32600-32699.xml @@ -1049,7 +1049,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/33900-33999.xml index 1461e2c720..ec252860c6 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_bookmark.xml index 4bb953e153..b30f0c2cb1 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_bookmark.xml @@ -8292,4 +8292,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_summon_friend.xml index 02a7d95b6a..7c050fd92f 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/no_summon_friend.xml @@ -289,4 +289,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/pvp.xml b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/pvp.xml index 43d245853a..484a17db00 100644 --- a/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_11.1_TheSourceOfFlame/dist/game/data/zones/pvp.xml @@ -123,4 +123,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index f5c9d7d8d0..c51c9ff2b8 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/npcs/29300-29399.xml index 0d00bc8269..6150ba1eba 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/npcs/29300-29399.xml @@ -5490,12 +5490,17 @@ + + + + + BUG FEMALE - + @@ -5505,7 +5510,7 @@ - 370 + 970 @@ -5516,10 +5521,13 @@ + + + + + - 300 - true - + QUEEN_ANT diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/32600-32699.xml index d2ccdd3bdd..8eaa7d9521 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/32600-32699.xml @@ -1049,7 +1049,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/33900-33999.xml index 4b0350cfd8..9f57d754ca 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_bookmark.xml index 4bb953e153..b30f0c2cb1 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_bookmark.xml @@ -8292,4 +8292,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_summon_friend.xml index 02a7d95b6a..7c050fd92f 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/no_summon_friend.xml @@ -289,4 +289,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/pvp.xml b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/pvp.xml index 43d245853a..484a17db00 100644 --- a/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_11.2_AgeOfMagic/dist/game/data/zones/pvp.xml @@ -123,4 +123,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_11.3_Shinemaker/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index f5c9d7d8d0..c51c9ff2b8 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/npcs/29300-29399.xml index b62002d446..1ba50be014 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/npcs/29300-29399.xml @@ -5490,12 +5490,17 @@ + + + + + BUG FEMALE - + @@ -5505,7 +5510,7 @@ - 370 + 970 @@ -5516,10 +5521,13 @@ + + + + + - 300 - true - + QUEEN_ANT diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/32600-32699.xml index c636c8d89c..7319db0564 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/32600-32699.xml @@ -1049,7 +1049,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/33900-33999.xml index 45a372a64f..7a1fbb04dd 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_bookmark.xml index 6843d2422d..c0c48d73c7 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_bookmark.xml @@ -8293,6 +8293,19 @@ + + + + + + + + + + + + + diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_summon_friend.xml index c44ca7748d..6ac8d0a65b 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/no_summon_friend.xml @@ -289,6 +289,17 @@ + + + + + + + + + + + diff --git a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/pvp.xml b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/pvp.xml index cc2ebb3898..efd5890568 100644 --- a/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_11.3_Shinemaker/dist/game/data/zones/pvp.xml @@ -123,6 +123,17 @@ + + + + + + + + + + + diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java index f5c9d7d8d0..c51c9ff2b8 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -20,133 +20,513 @@ */ package ai.bosses.QueenAnt; -import org.l2jmobius.Config; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.instancemanager.DBSpawnManager; +import org.l2jmobius.gameserver.instancemanager.GlobalVariablesManager; import org.l2jmobius.gameserver.instancemanager.GrandBossManager; -import org.l2jmobius.gameserver.model.StatSet; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.Spawn; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.instance.GrandBoss; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.skill.SkillCaster; +import org.l2jmobius.gameserver.model.zone.type.ArenaZone; import ai.AbstractNpcAI; /** - * Queen Ant's AI - * @author Mobius + * @author Notorion */ public class QueenAnt extends AbstractNpcAI { - // NPC + // NPCs private static final int QUEEN_ANT = 29381; - // Status - private static final byte ALIVE = 0; // Queen Ant is spawned. - private static final byte DEAD = 1; // Queen Ant has been killed. - // Location - private static final int QUEEN_X = -6505; - private static final int QUEEN_Y = 183040; - private static final int QUEEN_Z = -3419; + private static final int INVISIBLE_NPC = 18919; + private static final int NPC_LIFETIME = 9000; + private static final int REQUIRED_CC_MEMBERS = 14; + // Skills + private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1); + private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1); + private static final SkillHolder COMMON_SKILL_2 = new SkillHolder(33916, 1); + private static final SkillHolder INITIAL_SKILL = new SkillHolder(33917, 1); + private static final SkillHolder LIMIT_BARRIER = new SkillHolder(29515, 1); + // Barrier + private static final int BARRIER_DURATION_MILLIS = 600000; // 10 minutes. + private static final int HIT_COUNT = 2000; // 2000 Number of attacks needed to destroy the barrier. + private static final int HIT_COUNT_RENEW = 500; // 500 hits in 60 seconds to continue without the barrier, not confirmed. + private static final int RENEW_DURATION_MILLIS = 600000; // 60 seconds of vulnerability, Not confirmed. + // Locations + private static final Location GLUDIO_LOCATION = new Location(-14608, 123920, -3123); + private static final Location SPAWN_LOCATION = new Location(-7848, 183389, -3624); + // Zone + private static final ArenaZone ARENA_ZONE = ZoneManager.getInstance().getZoneByName("Queen_Ants_Lair", ArenaZone.class); + // Misc + private static GrandBoss _spawnedMainBoss; + private boolean _barrierActivated = false; + private boolean _bossInCombat = false; + private boolean _hp85Reached = false; + private boolean _isDebuffImmunityActive = false; + private boolean _vulnerablePhase = false; + private final AtomicBoolean _canUseSkill = new AtomicBoolean(true); + private final AtomicBoolean _isUsingAreaSkill = new AtomicBoolean(false); + private long _lastAttackTime = 0; + private final Map _queenAntHits = new ConcurrentHashMap<>(); private QueenAnt() { - addKillId(QUEEN_ANT); + addAttackId(QUEEN_ANT); addSpawnId(QUEEN_ANT); + addKillId(QUEEN_ANT); + addAggroRangeEnterId(QUEEN_ANT); - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD) + initializeRespawn(); + startQuestTimer("check_arena", 5000, null, null, true); + } + + private void initializeRespawn() + { + try { - // Load the unlock date and time for queen ant from DB. - final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); - if (temp > 0) // If queen ant is locked until a certain time, mark it so and start the unlock timer the unlock time has not yet expired. + final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT); + if (status == 0) { - startQuestTimer("queen_unlock", temp, null, null); + spawnQueenAnt(); } - else // The time has already expired while the server was offline. Immediately spawn queen ant. + else if (status == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); + scheduleNextRespawn(); + } + else + { + scheduleNextRespawn(); } } - else + catch (Exception e) { - int locX = info.getInt("loc_x"); - int locY = info.getInt("loc_y"); - int locZ = info.getInt("loc_z"); - final int heading = info.getInt("heading"); - final double hp = info.getDouble("currentHP"); - final double mp = info.getDouble("currentMP"); - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, locX, locY, locZ, heading, false, 0); - queen.setCurrentHpMp(hp, mp); - spawnBoss(queen); + LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage()); } } - @Override - public String onEvent(String event, Npc npc, Player player) + private void scheduleNextRespawn() { - switch (event) + final long currentTime = System.currentTimeMillis(); + final Calendar nextRespawn = getNextRespawnTime(); + final long delay = nextRespawn.getTimeInMillis() - currentTime; + + LOGGER.info("Queen Ant: Next respawn scheduled for " + nextRespawn.getTime() + " in " + delay + "ms"); + + ThreadPool.schedule(() -> { - case "queen_unlock": + if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == 1) { - final GrandBoss queen = (GrandBoss) addSpawn(QUEEN_ANT, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE); - spawnBoss(queen); - break; + spawnQueenAnt(); } - case "DISTANCE_CHECK": - { - if ((npc == null) || npc.isDead()) - { - cancelQuestTimers("DISTANCE_CHECK"); - } - else if (npc.calculateDistance2D(npc.getSpawn()) > 6000) - { - npc.asAttackable().clearAggroList(); - npc.teleToLocation(QUEEN_X, QUEEN_Y, QUEEN_Z); - npc.setCurrentHp(npc.getMaxHp()); - npc.setCurrentMp(npc.getMaxMp()); - } - break; - } - } - return super.onEvent(event, npc, player); + }, delay); } - @Override - public String onKill(Npc npc, Player killer, boolean isSummon) + private void spawnQueenAnt() { - npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); - GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD); + try + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + return; + } + + final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT); + final Spawn spawn = new Spawn(template); + spawn.setXYZ(SPAWN_LOCATION); + spawn.setHeading(0); + spawn.setRespawnDelay(0); + + final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false); + _spawnedMainBoss = (GrandBoss) boss; + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0); + LOGGER.info("Queen Ant: Boss spawned successfully at " + SPAWN_LOCATION); + boss.setRandomWalking(false); + boss.setRandomAnimation(false); + } + catch (Exception e) + { + LOGGER.severe("Queen Ant: Error spawning boss: " + e.getMessage()); + } + } + + // Return of The Queen Ant - Monday - Tuesday is in 9pm + // Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm + // Shinemaker - Monday 8pm + private Calendar getNextRespawnTime() + { + final Calendar nextRespawn = Calendar.getInstance(); - // Calculate Min and Max respawn times randomly. - final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000; - final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000; - final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis); - startQuestTimer("queen_unlock", respawnTime, null, null); + // Spawn Queen Ant Monday 8pm Night* + nextRespawn.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + nextRespawn.set(Calendar.HOUR_OF_DAY, 20); + nextRespawn.set(Calendar.MINUTE, 0); + nextRespawn.set(Calendar.SECOND, 0); + if (nextRespawn.getTimeInMillis() < System.currentTimeMillis()) + { + nextRespawn.add(Calendar.WEEK_OF_YEAR, 1); + } - // Also save the respawn time so that the info is maintained past restarts. - final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT); - info.set("respawn_time", System.currentTimeMillis() + respawnTime); - GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info); - - // Stop distance check task. - cancelQuestTimers("DISTANCE_CHECK"); - - return super.onKill(npc, killer, isSummon); + return nextRespawn; } @Override public String onSpawn(Npc npc) { - cancelQuestTimer("DISTANCE_CHECK", npc, null); - startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true); + startQuestTimer("checkCombatStatus", 1000, npc, null, true); + return super.onSpawn(npc); } - private void spawnBoss(GrandBoss npc) + @Override + public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon) { - GrandBossManager.getInstance().addBoss(npc); - npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _bossInCombat = true; + checkCombatStatus(npc); + if (_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + activateSpecialMechanics(npc); + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onAttack(Npc npc, Player attacker, int damage, boolean isSummon, Skill skill) + { + if (!_barrierActivated) + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + + if (_vulnerablePhase) + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT_RENEW) + { + cancelQuestTimer("activate_barrier", npc, null); + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + else + { + final int hits = _queenAntHits.getOrDefault(npc, 0) + 1; + _queenAntHits.put(npc, hits); + + if (hits >= HIT_COUNT) + { + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + cancelQuestTimer("remove_barrier", npc, null); + _vulnerablePhase = true; + startQuestTimer("activate_barrier", RENEW_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + } + } + + if (!ARENA_ZONE.isInsideZone(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + _lastAttackTime = System.currentTimeMillis(); + _bossInCombat = true; + + if (!_hp85Reached && (npc.getCurrentHp() < (npc.getMaxHp() * 0.85))) + { + _hp85Reached = true; + activateSpecialMechanics(npc); + } + + if (isPlayerInValidCommandChannel(attacker)) + { + final CommandChannel cc = attacker.getParty().getCommandChannel(); + for (Player member : cc.getMembers()) + { + if (member.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + } + } + else if (attacker.getLevel() < 110) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + if (!isPlayerInValidCommandChannel(attacker)) + { + attacker.teleToLocation(GLUDIO_LOCATION, false); + return null; + } + + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + private boolean isPlayerInValidCommandChannel(Player player) + { + final Party party = player.getParty(); + if (party == null) + { + return false; + } + + final CommandChannel cc = party.getCommandChannel(); + if ((cc == null) || (cc.getMemberCount() < REQUIRED_CC_MEMBERS)) + { + return false; + } + + return true; + } + + private void activateSpecialMechanics(Npc npc) + { + startQuestTimer("useAreaSkill", 1000, npc, null); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + + private void useAreaSkill(Npc npc) + { + if (!_canUseSkill.get() || (npc == null) || npc.isDead() || !_bossInCombat) + { + return; + } + _isUsingAreaSkill.set(true); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isDebuffImmunityActive = true; + cancelDebuffs(npc); + + ThreadPool.schedule(() -> _isDebuffImmunityActive = false, 7000); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (_bossInCombat) + { + npc.disableSkill(COMMON_SKILL_1.getSkill(), 7000); + npc.disableSkill(COMMON_SKILL_2.getSkill(), 7000); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + _isUsingAreaSkill.set(false); + } + }, 6000); + } + }, 1000); + + npc.enableAllSkills(); + + final Location bossLocation = npc.getLocation(); + final List spawnedNpcs = new ArrayList<>(); + + ThreadPool.schedule(() -> + { + if (!npc.isDead() && _bossInCombat) + { + npc.doCast(INITIAL_SKILL.getSkill()); + } + + for (int i = 0; i < 10; i++) + { + if (!_bossInCombat) + { + break; + } + + final int offsetX = getRandom(-1000, 1000); + final int offsetY = getRandom(-900, 1000); + final Location targetLocation = new Location(bossLocation.getX() + offsetX, bossLocation.getY() + offsetY, bossLocation.getZ()); + final Npc invisibleNpc = addSpawn(INVISIBLE_NPC, targetLocation, false, NPC_LIFETIME); + if (invisibleNpc != null) + { + spawnedNpcs.add(invisibleNpc); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, INITIAL_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, INITIAL_SKILL.getSkill()); + } + }, 1000); + + ThreadPool.schedule(() -> + { + if (!invisibleNpc.isDead() && SkillCaster.checkUseConditions(invisibleNpc, AREA_SKILL.getSkill())) + { + SkillCaster.triggerCast(invisibleNpc, invisibleNpc, AREA_SKILL.getSkill()); + } + }, 4000); + } + } + }, 4000); + } + + private void cancelDebuffs(Npc npc) + { + if ((npc == null) || npc.isDead() || !_isDebuffImmunityActive) + { + return; + } + + npc.getEffectList().getEffects().stream().filter(effect -> isDebuff(effect.getSkill())).forEach(effect -> npc.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, effect.getSkill())); + } + + private boolean isDebuff(Skill skill) + { + return (skill != null) && skill.isDebuff(); + } + + @Override + public String onEvent(String event, Npc npc, Player player) + { + if ((npc == null) || (npc.getId() != QUEEN_ANT)) + { + return null; + } + + switch (event) + { + case "queen_ant_barrier_start": + { + break; + } + case "activate_barrier": + { + _barrierActivated = true; + LIMIT_BARRIER.getSkill().applyEffects(npc, npc); + npc.setInvul(true); + _vulnerablePhase = false; + startQuestTimer("remove_barrier", BARRIER_DURATION_MILLIS, npc, null); + _queenAntHits.put(npc, 0); + break; + } + case "remove_barrier": + { + _barrierActivated = false; + npc.stopSkillEffects(LIMIT_BARRIER.getSkill()); + npc.setInvul(false); + _queenAntHits.put(npc, 0); + break; + } + case "check_arena": + { + if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead()) + { + checkBossInArena(_spawnedMainBoss); + } + break; + } + case "useAreaSkill": + { + useAreaSkill(npc); + break; + } + case "repeatSpecialMechanics": + { + if (!npc.isDead() && _bossInCombat) + { + useAreaSkill(npc); + startQuestTimer("repeatSpecialMechanics", 30000, npc, null, true); + } + else + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + break; + } + case "checkCombatStatus": + { + checkCombatStatus(npc); + break; + } + } + + return super.onEvent(event, npc, player); + } + + private void checkCombatStatus(Npc npc) + { + if (((System.currentTimeMillis() - _lastAttackTime) > 10000)) + { + _bossInCombat = false; + _hp85Reached = false; + cancelQuestTimer("repeatSpecialMechanics", npc, null); + } + else + { + _bossInCombat = true; + } + } + + private void checkBossInArena(Npc npc) + { + if ((npc == null) || npc.isDead()) + { + return; + } + + if (!ARENA_ZONE.isInsideZone(npc)) + { + npc.teleToLocation(SPAWN_LOCATION, false); + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + } + + @Override + public String onKill(Npc npc, Player killer, boolean isSummon) + { + if (npc == _spawnedMainBoss) + { + cancelQuestTimer("repeatSpecialMechanics", npc, null); + cancelQuestTimer("checkCombatStatus", npc, null); + cancelQuestTimers("check_arena"); + GrandBossManager.getInstance().setStatus(QUEEN_ANT, 1); + _spawnedMainBoss = null; + _hp85Reached = false; + _bossInCombat = false; + + final long currentTime = System.currentTimeMillis(); + GlobalVariablesManager.getInstance().set("QUEEN_ANT_LAST_DEATH_TIME", currentTime); + LOGGER.info("Queen Ant: Boss killed. Last death time recorded: " + currentTime + " / " + new java.util.Date(currentTime)); + } + + return super.onKill(npc, killer, isSummon); } public static void main(String[] args) diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/npcs/29300-29399.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/npcs/29300-29399.xml index 2cd6d80014..6221071e60 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/npcs/29300-29399.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/npcs/29300-29399.xml @@ -5483,12 +5483,17 @@ + + + + + BUG FEMALE - + @@ -5498,7 +5503,7 @@ - 370 + 970 @@ -5509,10 +5514,13 @@ + + + + + - 300 - true - + QUEEN_ANT diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/32600-32699.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/32600-32699.xml index 3b6702b181..eafa4f1ed1 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/32600-32699.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/32600-32699.xml @@ -1049,7 +1049,9 @@ P -5 - + + AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY + 100 100 diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/33900-33999.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/33900-33999.xml index 50dc8a5357..f47e3c461b 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/33900-33999.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/stats/skills/33900-33999.xml @@ -4,20 +4,128 @@ icon.skill33915 A1 + ENEMY + 5 + 3 + 900 + 5-12 + NOT_FRIEND + 800 + SQUARE + EARTH + 2000 + NONE + 700 + 800 + -250 + 1100 + 0;0;1300;300 + 1200 + 1 + 100 + -5 + 500 + + + 6250000 + 40 + + icon.skill33916 - A1 + A2 + SELF + 1 + TURN_FLEE;KNOCKDOWN;DEPORT;SILENCE;PARALYZE;ABSORB;DISARM;SILENCE_PHYSICAL;SILENCE_ALL;CHANGEBODY;TURN_STONE;DERANGEMENT;AIRBIND;SLEEP;OBLIVION;MIRAGE;MIRAGE_TRAP;ROOT_MAGICALLY;ROOT_PHYSICALLY;STUN;PUBLIC_SLOT + 5 + STUN + STUN + NOT_FRIEND + 2000 + POINT_BLANK + EARTH + 2000 + NONE + 1000 + 1400 + -100 + 2100 + true + -5 + 127 + 21000 + SHOCK + + + 60900000 + true + + 9 + {base + (base / 100 * subIndex)} + + + + 10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190 + + icon.skill33917 - A1 + A2 + SELF + 2 + 5 + 900 + 5-12 + NOT_FRIEND + POINT_BLANK + 500 + 500 + -100 + 6000 + 7 + 5 + 105 + 25000 icon.skill33917 - A1 + A2 + SELF + 2 + 4 + 900 + -100;200 + 5-12 + NOT_FRIEND + 500 + POINT_BLANK + EARTH + 2000 + PHYSICAL + 500 + -5000 + 3000 + true + 5 + 128 + 5000 + PULL + + + 600 + + + 100 + + + 48600000 + 300 + + diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_bookmark.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_bookmark.xml index 6843d2422d..c0c48d73c7 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_bookmark.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_bookmark.xml @@ -8293,6 +8293,19 @@ + + + + + + + + + + + + + diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_summon_friend.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_summon_friend.xml index c44ca7748d..6ac8d0a65b 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_summon_friend.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/no_summon_friend.xml @@ -289,6 +289,17 @@ + + + + + + + + + + + diff --git a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/pvp.xml b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/pvp.xml index cc2ebb3898..efd5890568 100644 --- a/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/pvp.xml +++ b/L2J_Mobius_12.1_PathOfRogue/dist/game/data/zones/pvp.xml @@ -123,6 +123,17 @@ + + + + + + + + + + +