Implemented new Queen Ant fight mechanics.
Contributed by notorionn.
This commit is contained in:
parent
d5926215a1
commit
d0fa47b6c9
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5906,21 +5906,26 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="125" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="125" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="563983630" hpRegen="208" mp="66584" mpRegen="4.5" />
|
<vitals hp="563983630" hpRegen="208" mp="66584" mpRegen="4.5" />
|
||||||
<attack physical="406536.872499405" magical="371.044275545088" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="406536.872499405" magical="371.044275545088" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="5757.47663551402" magical="1671.22597029139" evasion="-18" />
|
<defence physical="5757.47663551402" magical="1671.22597029139" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="DARK" value="850" />
|
<attack type="EARTH" value="2550" />
|
||||||
<defence fire="850" water="850" wind="850" earth="850" holy="800" dark="850" default="850" />
|
<defence fire="2650" water="2650" wind="2600" earth="2650" holy="2650" dark="2650" />
|
||||||
</attribute>
|
</attribute>
|
||||||
<speed>
|
<speed>
|
||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5931,14 +5936,21 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
</ai>
|
</ai>
|
||||||
|
<collision>
|
||||||
|
<radius normal="90" />
|
||||||
|
<height normal="109" />
|
||||||
|
</collision>
|
||||||
<dropLists>
|
<dropLists>
|
||||||
<drop>
|
<drop>
|
||||||
<item id="81153" min="1" max="1" chance="50.14" /> <!-- Navari s Mask -->
|
<item id="81153" min="1" max="1" chance="50.14" /> <!-- Navari s Mask -->
|
||||||
@ -5999,9 +6011,5 @@
|
|||||||
<item id="46465" min="1" max="1" chance="4.646" /> <!-- Mermoden's Soul Crystal Lv. 7 -->
|
<item id="46465" min="1" max="1" chance="4.646" /> <!-- Mermoden's Soul Crystal Lv. 7 -->
|
||||||
</drop>
|
</drop>
|
||||||
</dropLists>
|
</dropLists>
|
||||||
<collision>
|
|
||||||
<radius normal="90" />
|
|
||||||
<height normal="109" />
|
|
||||||
</collision>
|
|
||||||
</npc>
|
</npc>
|
||||||
</list>
|
</list>
|
||||||
|
@ -1047,7 +1047,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Slam">
|
<skill id="33916" toLevel="1" name="Ground Slam">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Slam">
|
<skill id="33919" toLevel="2" name="Ground Slam">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8286,4 +8286,17 @@
|
|||||||
<node X="-98296" Y="-262136" />
|
<node X="-98296" Y="-262136" />
|
||||||
<node X="-65544" Y="-229384" />
|
<node X="-65544" Y="-229384" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -236,4 +236,15 @@
|
|||||||
<zone name="Fafurion_Zone" id="210110" type="NoSummonFriendZone" shape="Cylinder" minZ="-15500" maxZ="-14000" rad="6000">
|
<zone name="Fafurion_Zone" id="210110" type="NoSummonFriendZone" shape="Cylinder" minZ="-15500" maxZ="-14000" rad="6000">
|
||||||
<node X="180712" Y="210664" />
|
<node X="180712" Y="210664" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -105,4 +105,15 @@
|
|||||||
<node X="118194" Y="15790" />
|
<node X="118194" Y="15790" />
|
||||||
<node X="114825" Y="19589" />
|
<node X="114825" Y="19589" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5438,22 +5438,27 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="125" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="125" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<acquire exp="1508791539" sp="1357912" />
|
<acquire exp="1508791539" sp="1357912" />
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="56398363" hpRegen="208" mp="66584" mpRegen="4.5" />
|
<vitals hp="56398363" hpRegen="208" mp="66584" mpRegen="4.5" />
|
||||||
<attack physical="406536.872499405" magical="371.044275545088" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="406536.872499405" magical="371.044275545088" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="5757.47663551402" magical="1671.22597029139" evasion="-18" />
|
<defence physical="5757.47663551402" magical="1671.22597029139" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="DARK" value="850" />
|
<attack type="EARTH" value="2550" />
|
||||||
<defence fire="850" water="850" wind="850" earth="850" holy="800" dark="850" default="850" />
|
<defence fire="2650" water="2650" wind="2600" earth="2650" holy="2650" dark="2650" />
|
||||||
</attribute>
|
</attribute>
|
||||||
<speed>
|
<speed>
|
||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5464,10 +5469,13 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
|
@ -1047,7 +1047,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Shaker">
|
<skill id="33916" toLevel="1" name="Ground Shaker">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Shaker">
|
<skill id="33919" toLevel="2" name="Ground Shaker">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8286,4 +8286,17 @@
|
|||||||
<node X="-98296" Y="-262136" />
|
<node X="-98296" Y="-262136" />
|
||||||
<node X="-65544" Y="-229384" />
|
<node X="-65544" Y="-229384" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -289,4 +289,15 @@
|
|||||||
<node X="58487" Y="-45550" />
|
<node X="58487" Y="-45550" />
|
||||||
<node X="58407" Y="-45289" />
|
<node X="58407" Y="-45289" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -108,4 +108,15 @@
|
|||||||
<zone name="toi_middle" type="ArenaZone" shape="Cylinder" minZ="-16000" maxZ="-8000" rad="7000">
|
<zone name="toi_middle" type="ArenaZone" shape="Cylinder" minZ="-16000" maxZ="-8000" rad="7000">
|
||||||
<node X="-147646" Y="16133" />
|
<node X="-147646" Y="16133" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5458,22 +5458,27 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<acquire exp="5337882368" sp="4804094" />
|
<acquire exp="5337882368" sp="4804094" />
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="56398363" hpRegen="208" mp="66584" mpRegen="4.5" />
|
<vitals hp="56398363" hpRegen="208" mp="66584" mpRegen="4.5" />
|
||||||
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="753240" magical="364523" evasion="-18" />
|
<defence physical="753240" magical="364523" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="DARK" value="850" />
|
<attack type="EARTH" value="2550" />
|
||||||
<defence fire="850" water="850" wind="850" earth="850" holy="800" dark="850" default="850" />
|
<defence fire="2650" water="2650" wind="2600" earth="2650" holy="2650" dark="2650" />
|
||||||
</attribute>
|
</attribute>
|
||||||
<speed>
|
<speed>
|
||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5484,10 +5489,13 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
|
@ -1049,7 +1049,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Shaker">
|
<skill id="33916" toLevel="1" name="Ground Shaker">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Shaker">
|
<skill id="33919" toLevel="2" name="Ground Shaker">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8292,4 +8292,17 @@
|
|||||||
<node X="-56122" Y="-235541" />
|
<node X="-56122" Y="-235541" />
|
||||||
<node X="49569" Y="-180166" />
|
<node X="49569" Y="-180166" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -289,4 +289,15 @@
|
|||||||
<node X="58487" Y="-45550" />
|
<node X="58487" Y="-45550" />
|
||||||
<node X="58407" Y="-45289" />
|
<node X="58407" Y="-45289" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -123,4 +123,15 @@
|
|||||||
<node X="47080" Y="157208" />
|
<node X="47080" Y="157208" />
|
||||||
<node X="47988" Y="155578" />
|
<node X="47988" Y="155578" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5490,12 +5490,17 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<acquire exp="5337882368" sp="4804094" />
|
<acquire exp="5337882368" sp="4804094" />
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="800175145" hpRegen="208" mp="68076" mpRegen="4.5" />
|
<vitals hp="800175145" hpRegen="208" mp="68076" mpRegen="4.5" />
|
||||||
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="753240" magical="364523" evasion="-18" />
|
<defence physical="753240" magical="364523" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="EARTH" value="2550" />
|
<attack type="EARTH" value="2550" />
|
||||||
@ -5505,7 +5510,7 @@
|
|||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5516,10 +5521,13 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
|
@ -1049,7 +1049,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Shaker">
|
<skill id="33916" toLevel="1" name="Ground Shaker">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Shaker">
|
<skill id="33919" toLevel="2" name="Ground Shaker">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8292,4 +8292,17 @@
|
|||||||
<node X="-56122" Y="-235541" />
|
<node X="-56122" Y="-235541" />
|
||||||
<node X="49569" Y="-180166" />
|
<node X="49569" Y="-180166" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -289,4 +289,15 @@
|
|||||||
<node X="58487" Y="-45550" />
|
<node X="58487" Y="-45550" />
|
||||||
<node X="58407" Y="-45289" />
|
<node X="58407" Y="-45289" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -123,4 +123,15 @@
|
|||||||
<node X="47080" Y="157208" />
|
<node X="47080" Y="157208" />
|
||||||
<node X="47988" Y="155578" />
|
<node X="47988" Y="155578" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
</list>
|
</list>
|
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5490,12 +5490,17 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<acquire exp="5337882368" sp="4804094" />
|
<acquire exp="5337882368" sp="4804094" />
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="800175145" hpRegen="208" mp="68076" mpRegen="4.5" />
|
<vitals hp="800175145" hpRegen="208" mp="68076" mpRegen="4.5" />
|
||||||
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="753240" magical="364523" evasion="-18" />
|
<defence physical="753240" magical="364523" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="EARTH" value="2550" />
|
<attack type="EARTH" value="2550" />
|
||||||
@ -5505,7 +5510,7 @@
|
|||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5516,10 +5521,13 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
|
@ -1049,7 +1049,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Shaker">
|
<skill id="33916" toLevel="1" name="Ground Shaker">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Shaker">
|
<skill id="33919" toLevel="2" name="Ground Shaker">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8293,6 +8293,19 @@
|
|||||||
<node X="49569" Y="-180166" />
|
<node X="49569" Y="-180166" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
|
|
||||||
<zone name="coatls_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="coatls_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<stat name="NoBookmark" val="true" />
|
<stat name="NoBookmark" val="true" />
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
|
@ -289,6 +289,17 @@
|
|||||||
<node X="58487" Y="-45550" />
|
<node X="58487" Y="-45550" />
|
||||||
<node X="58407" Y="-45289" />
|
<node X="58407" Y="-45289" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
<zone name="coatls_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="coatls_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
<node X="-87131" Y="207832" />
|
<node X="-87131" Y="207832" />
|
||||||
|
@ -123,6 +123,17 @@
|
|||||||
<node X="47080" Y="157208" />
|
<node X="47080" Y="157208" />
|
||||||
<node X="47988" Y="155578" />
|
<node X="47988" Y="155578" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
<zone name="Coatls_Lair" type="ArenaZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="Coatls_Lair" type="ArenaZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
<node X="-87131" Y="207832" />
|
<node X="-87131" Y="207832" />
|
||||||
|
@ -20,133 +20,513 @@
|
|||||||
*/
|
*/
|
||||||
package ai.bosses.QueenAnt;
|
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.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.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
|
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;
|
import ai.AbstractNpcAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queen Ant's AI
|
* @author Notorion
|
||||||
* @author Mobius
|
|
||||||
*/
|
*/
|
||||||
public class QueenAnt extends AbstractNpcAI
|
public class QueenAnt extends AbstractNpcAI
|
||||||
{
|
{
|
||||||
// NPC
|
// NPCs
|
||||||
private static final int QUEEN_ANT = 29381;
|
private static final int QUEEN_ANT = 29381;
|
||||||
// Status
|
private static final int INVISIBLE_NPC = 18919;
|
||||||
private static final byte ALIVE = 0; // Queen Ant is spawned.
|
private static final int NPC_LIFETIME = 9000;
|
||||||
private static final byte DEAD = 1; // Queen Ant has been killed.
|
private static final int REQUIRED_CC_MEMBERS = 14;
|
||||||
// Location
|
// Skills
|
||||||
private static final int QUEEN_X = -6505;
|
private static final SkillHolder AREA_SKILL = new SkillHolder(33918, 1);
|
||||||
private static final int QUEEN_Y = 183040;
|
private static final SkillHolder COMMON_SKILL_1 = new SkillHolder(33915, 1);
|
||||||
private static final int QUEEN_Z = -3419;
|
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<Npc, Integer> _queenAntHits = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private QueenAnt()
|
private QueenAnt()
|
||||||
{
|
{
|
||||||
addKillId(QUEEN_ANT);
|
addAttackId(QUEEN_ANT);
|
||||||
addSpawnId(QUEEN_ANT);
|
addSpawnId(QUEEN_ANT);
|
||||||
|
addKillId(QUEEN_ANT);
|
||||||
|
addAggroRangeEnterId(QUEEN_ANT);
|
||||||
|
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
initializeRespawn();
|
||||||
if (GrandBossManager.getInstance().getStatus(QUEEN_ANT) == DEAD)
|
startQuestTimer("check_arena", 5000, null, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRespawn()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Load the unlock date and time for queen ant from DB.
|
final int status = GrandBossManager.getInstance().getStatus(QUEEN_ANT);
|
||||||
final long temp = info.getLong("respawn_time") - System.currentTimeMillis();
|
if (status == 0)
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
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);
|
scheduleNextRespawn();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
}
|
||||||
spawnBoss(queen);
|
else
|
||||||
|
{
|
||||||
|
scheduleNextRespawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
int locX = info.getInt("loc_x");
|
LOGGER.severe("Queen Ant: Error during initialization: " + e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void scheduleNextRespawn()
|
||||||
public String onEvent(String event, Npc npc, Player player)
|
|
||||||
{
|
{
|
||||||
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);
|
spawnQueenAnt();
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, ALIVE);
|
|
||||||
spawnBoss(queen);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "DISTANCE_CHECK":
|
}, delay);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void spawnQueenAnt()
|
||||||
public String onKill(Npc npc, Player killer, boolean isSummon)
|
|
||||||
{
|
{
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
try
|
||||||
GrandBossManager.getInstance().setStatus(QUEEN_ANT, DEAD);
|
{
|
||||||
|
if ((_spawnedMainBoss != null) && !_spawnedMainBoss.isDead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate Min and Max respawn times randomly.
|
final NpcTemplate template = NpcData.getInstance().getTemplate(QUEEN_ANT);
|
||||||
final long baseIntervalMillis = Config.QUEEN_ANT_SPAWN_INTERVAL * 3600000;
|
final Spawn spawn = new Spawn(template);
|
||||||
final long randomRangeMillis = Config.QUEEN_ANT_SPAWN_RANDOM * 3600000;
|
spawn.setXYZ(SPAWN_LOCATION);
|
||||||
final long respawnTime = baseIntervalMillis + getRandom(-randomRangeMillis, randomRangeMillis);
|
spawn.setHeading(0);
|
||||||
startQuestTimer("queen_unlock", respawnTime, null, null);
|
spawn.setRespawnDelay(0);
|
||||||
|
|
||||||
// Also save the respawn time so that the info is maintained past restarts.
|
final Npc boss = DBSpawnManager.getInstance().addNewSpawn(spawn, false);
|
||||||
final StatSet info = GrandBossManager.getInstance().getStatSet(QUEEN_ANT);
|
_spawnedMainBoss = (GrandBoss) boss;
|
||||||
info.set("respawn_time", System.currentTimeMillis() + respawnTime);
|
GrandBossManager.getInstance().setStatus(QUEEN_ANT, 0);
|
||||||
GrandBossManager.getInstance().setStatSet(QUEEN_ANT, info);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop distance check task.
|
// Return of The Queen Ant - Monday - Tuesday is in 9pm
|
||||||
cancelQuestTimers("DISTANCE_CHECK");
|
// Hero’s Tome time respawn 10/2022 Queen Ant Monday 9pm
|
||||||
|
// Shinemaker - Monday 8pm
|
||||||
|
private Calendar getNextRespawnTime()
|
||||||
|
{
|
||||||
|
final Calendar nextRespawn = Calendar.getInstance();
|
||||||
|
|
||||||
return super.onKill(npc, killer, isSummon);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRespawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onSpawn(Npc npc)
|
public String onSpawn(Npc npc)
|
||||||
{
|
{
|
||||||
cancelQuestTimer("DISTANCE_CHECK", npc, null);
|
startQuestTimer("checkCombatStatus", 1000, npc, null, true);
|
||||||
startQuestTimer("DISTANCE_CHECK", 5000, npc, null, true);
|
|
||||||
return super.onSpawn(npc);
|
return super.onSpawn(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBoss(GrandBoss npc)
|
@Override
|
||||||
|
public String onAggroRangeEnter(Npc npc, Player player, boolean isSummon)
|
||||||
{
|
{
|
||||||
GrandBossManager.getInstance().addBoss(npc);
|
_bossInCombat = true;
|
||||||
npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ()));
|
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<Npc> 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)
|
public static void main(String[] args)
|
||||||
|
@ -5483,12 +5483,17 @@
|
|||||||
</collision>
|
</collision>
|
||||||
</npc>
|
</npc>
|
||||||
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
<npc id="29381" level="127" type="GrandBoss" name="Queen Ant" title="Wasteland Mistress">
|
||||||
|
<parameters>
|
||||||
|
<skill name="Skill1" id="33915" level="1" />
|
||||||
|
<skill name="Skill2" id="33916" level="1" />
|
||||||
|
<param name="show_activated" value="0" />
|
||||||
|
</parameters>
|
||||||
<race>BUG</race>
|
<race>BUG</race>
|
||||||
<sex>FEMALE</sex>
|
<sex>FEMALE</sex>
|
||||||
<acquire exp="5337882368" sp="4804094" />
|
<acquire exp="5337882368" sp="4804094" />
|
||||||
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
<stats str="164" int="188" dex="55" wit="78" con="111" men="149">
|
||||||
<vitals hp="800175000" hpRegen="208" mp="68076" mpRegen="4.5" />
|
<vitals hp="800175000" hpRegen="208" mp="68076" mpRegen="4.5" />
|
||||||
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="40" distance="80" width="120" />
|
<attack physical="1599215" magical="145753" random="30" critical="4.75" accuracy="4.75" attackSpeed="598" type="SWORD" range="150" distance="80" width="120" />
|
||||||
<defence physical="753240" magical="364523" evasion="-18" />
|
<defence physical="753240" magical="364523" evasion="-18" />
|
||||||
<attribute>
|
<attribute>
|
||||||
<attack type="EARTH" value="2550" />
|
<attack type="EARTH" value="2550" />
|
||||||
@ -5498,7 +5503,7 @@
|
|||||||
<walk ground="80" />
|
<walk ground="80" />
|
||||||
<run ground="130" />
|
<run ground="130" />
|
||||||
</speed>
|
</speed>
|
||||||
<hitTime>370</hitTime>
|
<hitTime>970</hitTime>
|
||||||
<abnormalResist physical="230" magical="230" />
|
<abnormalResist physical="230" magical="230" />
|
||||||
</stats>
|
</stats>
|
||||||
<status undying="false" noSleepMode="true" />
|
<status undying="false" noSleepMode="true" />
|
||||||
@ -5509,10 +5514,13 @@
|
|||||||
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
<skill id="32675" level="1" /> <!-- Improved Immunity -->
|
||||||
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
<skill id="14765" level="4" /> <!-- Blood Siphon Resistance -->
|
||||||
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
<skill id="14804" level="10" /> <!-- Damage Reflection Resistance -->
|
||||||
|
<skill id="34814" level="9" /> <!-- Critical Rate Resistance -->
|
||||||
|
<skill id="34815" level="9" /> <!-- Critical Damage Resistance -->
|
||||||
|
<skill id="34820" level="7" /> <!-- Received Damage Resistance -->
|
||||||
|
<skill id="33915" level="1" /> <!-- Queen's Rage -->
|
||||||
|
<skill id="33916" level="1" /> <!-- Ground Shaker -->
|
||||||
</skillList>
|
</skillList>
|
||||||
<corpseTime>300</corpseTime>
|
<ai type="MAGE" clanHelpRange="2000" aggroRange="2000">
|
||||||
<exCrtEffect>true</exCrtEffect>
|
|
||||||
<ai type="BALANCED" clanHelpRange="2000" aggroRange="2000">
|
|
||||||
<clanList>
|
<clanList>
|
||||||
<clan>QUEEN_ANT</clan>
|
<clan>QUEEN_ANT</clan>
|
||||||
</clanList>
|
</clanList>
|
||||||
|
@ -1049,7 +1049,9 @@
|
|||||||
<operateType>P</operateType>
|
<operateType>P</operateType>
|
||||||
<magicCriticalRate>-5</magicCriticalRate>
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
<effects>
|
<effects>
|
||||||
<!-- TODO p_abnormal_rate_limit -->
|
<effect name="BlockAbnormalSlot">
|
||||||
|
<slot>AIRBIND;DERANGEMENT;CHANGEBODY;SLEEP;PARALYZE;KNOCKDOWN;TURN_STONE;STUN;SILENCE;SHILLIEN_STUN;SAYHAS_RING;SILENCE_PHYSICAL;ROOT_MAGICALLY</slot>
|
||||||
|
</effect>
|
||||||
<effect name="DefenceTrait">
|
<effect name="DefenceTrait">
|
||||||
<HOLD>100</HOLD>
|
<HOLD>100</HOLD>
|
||||||
<DERANGEMENT>100</DERANGEMENT>
|
<DERANGEMENT>100</DERANGEMENT>
|
||||||
|
@ -4,20 +4,128 @@
|
|||||||
<!-- The Queen Ant attacks an area at the front. -->
|
<!-- The Queen Ant attacks an area at the front. -->
|
||||||
<icon>icon.skill33915</icon>
|
<icon>icon.skill33915</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A1</operateType>
|
||||||
|
<targetType>ENEMY</targetType>
|
||||||
|
<abnormalLevel>5</abnormalLevel>
|
||||||
|
<abnormalTime>3</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>800</affectRange>
|
||||||
|
<affectScope>SQUARE</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>700</castRange>
|
||||||
|
<coolTime>800</coolTime>
|
||||||
|
<effectPoint>-250</effectPoint>
|
||||||
|
<effectRange>1100</effectRange>
|
||||||
|
<fanRange>0;0;1300;300</fanRange>
|
||||||
|
<hitTime>1200</hitTime>
|
||||||
|
<isMagic>1</isMagic>
|
||||||
|
<lvlBonusRate>100</lvlBonusRate>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<reuseDelay>500</reuseDelay>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>6250000</power>
|
||||||
|
<criticalChance>40</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33916" toLevel="1" name="Ground Shaker">
|
<skill id="33916" toLevel="1" name="Ground Shaker">
|
||||||
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- The Queen Ant attacks the enemy. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
<icon>icon.skill33916</icon>
|
<icon>icon.skill33916</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>1</abnormalLevel>
|
||||||
|
<abnormalResists>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</abnormalResists>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<abnormalType>STUN</abnormalType>
|
||||||
|
<abnormalVisualEffect>STUN</abnormalVisualEffect>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>2000</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>NONE</basicProperty>
|
||||||
|
<castRange>1000</castRange>
|
||||||
|
<coolTime>1400</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>2100</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>-5</magicCriticalRate>
|
||||||
|
<magicLevel>127</magicLevel>
|
||||||
|
<reuseDelay>21000</reuseDelay>
|
||||||
|
<trait>SHOCK</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>60900000</power>
|
||||||
|
<overHit>true</overHit>
|
||||||
|
<criticalChance>
|
||||||
|
<value fromLevel="1" toLevel="9">9</value>
|
||||||
|
<value fromLevel="1" toLevel="9" fromSubLevel="2001" toSubLevel="2020">{base + (base / 100 * subIndex)}</value>
|
||||||
|
</criticalChance>
|
||||||
|
</effect>
|
||||||
|
<effect name="BlockActions">
|
||||||
|
<allowedSkills>10279;10517;10025;10776;11770;1904;11264;11093;13314;1912;7002;18721;18722;28203;30516;35190</allowedSkills>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33917" toLevel="1" name="Ant Hole">
|
<skill id="33917" toLevel="1" name="Ant Hole">
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>5</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-100</effectPoint>
|
||||||
|
<hitTime>6000</hitTime>
|
||||||
|
<isMagic>7</isMagic>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>105</magicLevel>
|
||||||
|
<reuseDelay>25000</reuseDelay>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33918" toLevel="1" name="Ant Hole">
|
<skill id="33918" toLevel="1" name="Ant Hole">
|
||||||
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
<!-- Pulls nearby enemies to the ant hole. Inflicted status: Danger Zone. -->
|
||||||
<icon>icon.skill33917</icon>
|
<icon>icon.skill33917</icon>
|
||||||
<operateType>A1</operateType>
|
<operateType>A2</operateType>
|
||||||
|
<targetType>SELF</targetType>
|
||||||
|
<abnormalLevel>2</abnormalLevel>
|
||||||
|
<abnormalTime>4</abnormalTime>
|
||||||
|
<activateRate>900</activateRate>
|
||||||
|
<affectHeight>-100;200</affectHeight>
|
||||||
|
<affectLimit>5-12</affectLimit>
|
||||||
|
<affectObject>NOT_FRIEND</affectObject>
|
||||||
|
<affectRange>500</affectRange>
|
||||||
|
<affectScope>POINT_BLANK</affectScope>
|
||||||
|
<attributeType>EARTH</attributeType>
|
||||||
|
<attributeValue>2000</attributeValue>
|
||||||
|
<basicProperty>PHYSICAL</basicProperty>
|
||||||
|
<coolTime>500</coolTime>
|
||||||
|
<effectPoint>-5000</effectPoint>
|
||||||
|
<hitTime>3000</hitTime>
|
||||||
|
<isDebuff>true</isDebuff>
|
||||||
|
<magicCriticalRate>5</magicCriticalRate>
|
||||||
|
<magicLevel>128</magicLevel>
|
||||||
|
<reuseDelay>5000</reuseDelay>
|
||||||
|
<trait>PULL</trait>
|
||||||
|
<effects>
|
||||||
|
<effect name="PullBack">
|
||||||
|
<speed>600</speed>
|
||||||
|
</effect>
|
||||||
|
<effect name="DefenceTrait">
|
||||||
|
<PULL>100</PULL>
|
||||||
|
</effect>
|
||||||
|
<effect name="PhysicalDamage">
|
||||||
|
<power>48600000</power>
|
||||||
|
<criticalChance>300</criticalChance>
|
||||||
|
</effect>
|
||||||
|
</effects>
|
||||||
</skill>
|
</skill>
|
||||||
<skill id="33919" toLevel="2" name="Ground Shaker">
|
<skill id="33919" toLevel="2" name="Ground Shaker">
|
||||||
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
<!-- A Queen Ant's attack inflicts Stun. Inflicted status: Confusion (Fear, Aerial Yoke, Faint, Charm, Stun, Paralysis, Knockback, Knockdown). -->
|
||||||
|
@ -8293,6 +8293,19 @@
|
|||||||
<node X="49569" Y="-180166" />
|
<node X="49569" Y="-180166" />
|
||||||
</zone>
|
</zone>
|
||||||
|
|
||||||
|
<zone name="queen_ants_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<stat name="NoBookmark" val="true" />
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
|
|
||||||
<zone name="coatls_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="coatls_lair_no_bookmark" type="ConditionZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<stat name="NoBookmark" val="true" />
|
<stat name="NoBookmark" val="true" />
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
|
@ -289,6 +289,17 @@
|
|||||||
<node X="58487" Y="-45550" />
|
<node X="58487" Y="-45550" />
|
||||||
<node X="58407" Y="-45289" />
|
<node X="58407" Y="-45289" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="queen_ants_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
<zone name="coatls_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="coatls_lair_no_summon" type="NoSummonFriendZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
<node X="-87131" Y="207832" />
|
<node X="-87131" Y="207832" />
|
||||||
|
@ -123,6 +123,17 @@
|
|||||||
<node X="47080" Y="157208" />
|
<node X="47080" Y="157208" />
|
||||||
<node X="47988" Y="155578" />
|
<node X="47988" Y="155578" />
|
||||||
</zone>
|
</zone>
|
||||||
|
<zone name="Queen_Ants_Lair" type="ArenaZone" shape="NPoly" minZ="-3900" maxZ="-2200">
|
||||||
|
<node X="-8658" Y="185667" />
|
||||||
|
<node X="-10613" Y="185228" />
|
||||||
|
<node X="-11121" Y="182156" />
|
||||||
|
<node X="-10246" Y="180500" />
|
||||||
|
<node X="-8763" Y="180013" />
|
||||||
|
<node X="-6144" Y="180164" />
|
||||||
|
<node X="-4405" Y="181431" />
|
||||||
|
<node X="-4026" Y="185137" />
|
||||||
|
<node X="-7370" Y="186216" />
|
||||||
|
</zone>
|
||||||
<zone name="Coatls_Lair" type="ArenaZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
<zone name="Coatls_Lair" type="ArenaZone" shape="NPoly" minZ="-5900" maxZ="-3964">
|
||||||
<node X="-82040" Y="207704" />
|
<node X="-82040" Y="207704" />
|
||||||
<node X="-87131" Y="207832" />
|
<node X="-87131" Y="207832" />
|
||||||
|
Loading…
Reference in New Issue
Block a user