diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-01.html b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-01.html new file mode 100644 index 0000000000..35984fbe2f --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-01.html @@ -0,0 +1,3 @@ +Angelic Vortex:
+The Angelic Vortex emits a faint light which twinkles out. The vortex becomes inactive. It seems like, in order to go to the place where the Angelic Vortex is leading, you will need some special object. + \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-02.html b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-02.html new file mode 100644 index 0000000000..6e2996425e --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-02.html @@ -0,0 +1,5 @@ +Angelic Vortex:
+The Angelic Vortex emits a faint light, in the midst of which an image appears. A giant that normally appears as a stone statue has come alive and is fighting with many people. Even angels have joined the fray. It seems that the angels are angry at the humans who have broken the seal of the stone statue.
+It appears impossible to cross over to that place right now. You have no choice but to wait until the fight is over.
+Ask about the stone statue that has come alive. + \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-03.html b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-03.html new file mode 100644 index 0000000000..a52b7d7899 --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-03.html @@ -0,0 +1,5 @@ +Angelic Vortex:
+The Angelic Vortex emits a faint light, in the midst of which an image appears. A large, open space... but the gigantic, human-like stone statue is no longer there.
+It seems that while the statue is not visible, one could cross over to that place.
+Ask about the stone statue. + \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-04.html b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-04.html new file mode 100644 index 0000000000..bb7605551b --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862-04.html @@ -0,0 +1,7 @@ +Angelic Vortex:
+In ancient times a mighty emperor named Baium became so enamored of his own power that he began to fancy himself a god. He conscripted all of his people to build an awesome tower that reached almost to heaven.
+The gods were not amused. They struck him down, cursed him with immortality and imprisoned him in his own tower for eternity! A statue of the foolish emperor stands there to this day.
+Occasionally, the old emperor himself is woken up by those who seek his power. Unfortunately, when Baium is disturbed, so are the angels who guard him.
+Although the angels are certainly no friend of Baium, they regard Humans with equal disdain. Ultimate success against Baium depends upon the defeat of the guardian angels.
+This is all I can tell you. Good luck! + \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862.html b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862.html new file mode 100644 index 0000000000..5d2b38524e --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/31862.html @@ -0,0 +1,4 @@ +Angelic Vortex:
+The Angelic Vortex emits a faint light, and in the midst of the light an image appears. In the deepest part of a spacious area, a gigantic stone statue is visible. The statue has a human-like appearance and is in a seated position, as though it was being held by some invisible force against its will.
+Go where the Angelic Vortex leads. + \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/Baium.java b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/Baium.java new file mode 100644 index 0000000000..a70e0d4d26 --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Baium/Baium.java @@ -0,0 +1,791 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ai.bosses.Baium; + +import com.l2jmobius.Config; +import com.l2jmobius.commons.util.CommonUtil; +import com.l2jmobius.gameserver.ai.CtrlIntention; +import com.l2jmobius.gameserver.enums.CategoryType; +import com.l2jmobius.gameserver.enums.ChatType; +import com.l2jmobius.gameserver.enums.MountType; +import com.l2jmobius.gameserver.instancemanager.GrandBossManager; +import com.l2jmobius.gameserver.instancemanager.ZoneManager; +import com.l2jmobius.gameserver.model.L2World; +import com.l2jmobius.gameserver.model.Location; +import com.l2jmobius.gameserver.model.StatsSet; +import com.l2jmobius.gameserver.model.actor.L2Attackable; +import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; +import com.l2jmobius.gameserver.model.actor.L2Playable; +import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance; +import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; +import com.l2jmobius.gameserver.model.holders.SkillHolder; +import com.l2jmobius.gameserver.model.skills.Skill; +import com.l2jmobius.gameserver.model.skills.SkillCaster; +import com.l2jmobius.gameserver.model.variables.NpcVariables; +import com.l2jmobius.gameserver.model.zone.type.L2NoRestartZone; +import com.l2jmobius.gameserver.network.NpcStringId; +import com.l2jmobius.gameserver.network.serverpackets.Earthquake; +import com.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; +import com.l2jmobius.gameserver.network.serverpackets.PlaySound; +import com.l2jmobius.gameserver.network.serverpackets.SocialAction; + +import ai.AbstractNpcAI; + +/** + * Baium AI. + * @author St3eT + */ +public final class Baium extends AbstractNpcAI +{ + // NPCs + private static final int BAIUM = 29020; // Baium + private static final int BAIUM_STONE = 29025; // Baium + private static final int ANG_VORTEX = 31862; // Angelic Vortex + private static final int ARCHANGEL = 29021; // Archangel + private static final int TELE_CUBE = 31842; // Teleportation Cubic + // Skills + private static final SkillHolder BAIUM_ATTACK = new SkillHolder(4127, 1); // Baium: General Attack + private static final SkillHolder ENERGY_WAVE = new SkillHolder(4128, 1); // Wind Of Force + private static final SkillHolder EARTH_QUAKE = new SkillHolder(4129, 1); // Earthquake + private static final SkillHolder THUNDERBOLT = new SkillHolder(4130, 1); // Striking of Thunderbolt + private static final SkillHolder GROUP_HOLD = new SkillHolder(4131, 1); // Stun + private static final SkillHolder SPEAR_ATTACK = new SkillHolder(4132, 1); // Spear: Pound the Ground + private static final SkillHolder ANGEL_HEAL = new SkillHolder(4133, 1); // Angel Heal + private static final SkillHolder HEAL_OF_BAIUM = new SkillHolder(4135, 1); // Baium Heal + private static final SkillHolder BAIUM_PRESENT = new SkillHolder(4136, 1); // Baium's Gift + private static final SkillHolder ANTI_STRIDER = new SkillHolder(4258, 1); // Hinder Strider + // Items + private static final int FABRIC = 4295; // Blooded Fabric + // Zone + private static final L2NoRestartZone zone = ZoneManager.getInstance().getZoneById(70051, L2NoRestartZone.class); // Baium zone + // Status + private static final int ALIVE = 0; + private static final int WAITING = 1; + private static final int IN_FIGHT = 2; + private static final int DEAD = 3; + // Locations + private static final Location BAIUM_GIFT_LOC = new Location(115910, 17337, 10105); + private static final Location BAIUM_LOC = new Location(116033, 17447, 10107, -25348); + private static final Location TELEPORT_CUBIC_LOC = new Location(115017, 15549, 10090); + private static final Location TELEPORT_IN_LOC = new Location(114077, 15882, 10078); + private static final Location[] TELEPORT_OUT_LOC = + { + new Location(108784, 16000, -4928), + new Location(113824, 10448, -5164), + new Location(115488, 22096, -5168), + }; + private static final Location[] ARCHANGEL_LOC = + { + new Location(115792, 16608, 10136, 0), + new Location(115168, 17200, 10136, 0), + new Location(115780, 15564, 10136, 13620), + new Location(114880, 16236, 10136, 5400), + new Location(114239, 17168, 10136, -1992) + }; + // Misc + private L2GrandBossInstance _baium = null; + private static long _lastAttack = 0; + private static L2PcInstance _standbyPlayer = null; + + private Baium() + { + addFirstTalkId(ANG_VORTEX); + addTalkId(ANG_VORTEX, TELE_CUBE, BAIUM_STONE); + addStartNpc(ANG_VORTEX, TELE_CUBE, BAIUM_STONE); + addAttackId(BAIUM, ARCHANGEL); + addKillId(BAIUM); + addSeeCreatureId(BAIUM); + addSpellFinishedId(BAIUM); + + final StatsSet info = GrandBossManager.getInstance().getStatsSet(BAIUM); + + switch (getStatus()) + { + case WAITING: + { + setStatus(ALIVE); + } + case ALIVE: + { + addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0); + break; + } + case IN_FIGHT: + { + final double curr_hp = info.getDouble("currentHP"); + final double curr_mp = info.getDouble("currentMP"); + final int loc_x = info.getInt("loc_x"); + final int loc_y = info.getInt("loc_y"); + final int loc_z = info.getInt("loc_z"); + final int heading = info.getInt("heading"); + + _baium = (L2GrandBossInstance) addSpawn(BAIUM, loc_x, loc_y, loc_z, heading, false, 0); + _baium.setCurrentHpMp(curr_hp, curr_mp); + _lastAttack = System.currentTimeMillis(); + addBoss(_baium); + + for (Location loc : ARCHANGEL_LOC) + { + final L2Npc archangel = addSpawn(ARCHANGEL, loc, false, 0, true); + startQuestTimer("SELECT_TARGET", 5000, archangel, null); + } + startQuestTimer("CHECK_ATTACK", 60000, _baium, null); + break; + } + case DEAD: + { + final long remain = info.getLong("respawn_time") - System.currentTimeMillis(); + if (remain > 0) + { + startQuestTimer("CLEAR_STATUS", remain, null, null); + } + else + { + notifyEvent("CLEAR_STATUS", null, null); + } + break; + } + } + } + + @Override + public String onAdvEvent(String event, L2Npc npc, L2PcInstance player) + { + switch (event) + { + case "31862-04.html": + { + return event; + } + case "enter": + { + String htmltext = null; + if (getStatus() == DEAD) + { + htmltext = "31862-03.html"; + } + else if (getStatus() == IN_FIGHT) + { + htmltext = "31862-02.html"; + } + else if (!hasQuestItems(player, FABRIC)) + { + htmltext = "31862-01.html"; + } + else + { + takeItems(player, FABRIC, 1); + player.teleToLocation(TELEPORT_IN_LOC); + } + return htmltext; + } + case "teleportOut": + { + final Location destination = TELEPORT_OUT_LOC[getRandom(TELEPORT_OUT_LOC.length)]; + player.teleToLocation(destination.getX() + getRandom(100), destination.getY() + getRandom(100), destination.getZ()); + break; + } + case "wakeUp": + { + if (getStatus() == ALIVE) + { + npc.deleteMe(); + setStatus(IN_FIGHT); + _baium = (L2GrandBossInstance) addSpawn(BAIUM, BAIUM_LOC, false, 0); + _baium.disableCoreAI(true); + addBoss(_baium); + _lastAttack = System.currentTimeMillis(); + startQuestTimer("WAKEUP_ACTION", 50, _baium, null); + startQuestTimer("MANAGE_EARTHQUAKE", 2000, _baium, player); + startQuestTimer("CHECK_ATTACK", 60000, _baium, null); + } + break; + } + case "WAKEUP_ACTION": + { + if (npc != null) + { + zone.broadcastPacket(new SocialAction(_baium.getObjectId(), 2)); + } + break; + } + case "MANAGE_EARTHQUAKE": + { + if (npc != null) + { + zone.broadcastPacket(new Earthquake(npc.getX(), npc.getY(), npc.getZ(), 40, 10)); + zone.broadcastPacket(new PlaySound("BS02_A")); + startQuestTimer("SOCIAL_ACTION", 8000, npc, player); + } + break; + } + case "SOCIAL_ACTION": + { + if (npc != null) + { + zone.broadcastPacket(new SocialAction(npc.getObjectId(), 3)); + startQuestTimer("PLAYER_PORT", 6000, npc, player); + } + break; + } + case "PLAYER_PORT": + { + if (npc != null) + { + if ((player != null) && player.isInsideRadius(npc, 16000, true, false)) + { + player.teleToLocation(BAIUM_GIFT_LOC); + startQuestTimer("PLAYER_KILL", 3000, npc, player); + } + else if ((_standbyPlayer != null) && _standbyPlayer.isInsideRadius(npc, 16000, true, false)) + { + _standbyPlayer.teleToLocation(BAIUM_GIFT_LOC); + startQuestTimer("PLAYER_KILL", 3000, npc, _standbyPlayer); + } + } + break; + } + case "PLAYER_KILL": + { + if ((player != null) && player.isInsideRadius(npc, 16000, true, false)) + { + zone.broadcastPacket(new SocialAction(npc.getObjectId(), 1)); + npc.broadcastSay(ChatType.NPC_GENERAL, player.getName() + ", How dare you wake me! Now you shall die!"); // TODO: replace with NpcStringId when are done core support + npc.setTarget(player); + npc.doCast(BAIUM_PRESENT.getSkill()); + } + + for (L2PcInstance players : zone.getPlayersInside()) + { + if (players.isHero()) + { + zone.broadcastPacket(new ExShowScreenMessage(NpcStringId.NOT_EVEN_THE_GODS_THEMSELVES_COULD_TOUCH_ME_BUT_YOU_S1_YOU_DARE_CHALLENGE_ME_IGNORANT_MORTAL, 2, 4000, players.getName())); + break; + } + } + startQuestTimer("SPAWN_ARCHANGEL", 8000, npc, null); + break; + } + case "SPAWN_ARCHANGEL": + { + _baium.disableCoreAI(false); + + for (Location loc : ARCHANGEL_LOC) + { + final L2Npc archangel = addSpawn(ARCHANGEL, loc, false, 0, true); + startQuestTimer("SELECT_TARGET", 5000, archangel, null); + } + + if ((player != null) && !player.isDead()) + { + addAttackPlayerDesire(npc, player); + } + else if ((_standbyPlayer != null) && !_standbyPlayer.isDead()) + { + addAttackPlayerDesire(npc, _standbyPlayer); + } + else + { + for (L2Character creature : L2World.getInstance().getVisibleObjects(npc, L2PcInstance.class, 2000)) + { + if (zone.isInsideZone(creature) && !creature.isDead()) + { + addAttackPlayerDesire(npc, (L2Playable) creature); + break; + } + } + } + break; + } + case "SELECT_TARGET": + { + if (npc != null) + { + final L2Attackable mob = (L2Attackable) npc; + final L2Character mostHated = mob.getMostHated(); + + if ((_baium == null) || _baium.isDead()) + { + mob.deleteMe(); + break; + } + + if ((mostHated != null) && mostHated.isPlayer() && zone.isInsideZone(mostHated)) + { + if (mob.getTarget() != mostHated) + { + mob.clearAggroList(); + } + addAttackPlayerDesire(mob, (L2Playable) mostHated); + } + else + { + boolean found = false; + for (L2Playable creature : L2World.getInstance().getVisibleObjects(mob, L2Playable.class, 1000)) + { + if (zone.isInsideZone(creature) && !creature.isDead()) + { + if (mob.getTarget() != creature) + { + mob.clearAggroList(); + } + addAttackPlayerDesire(mob, creature); + found = true; + break; + } + } + + if (!found) + { + if (mob.isInsideRadius(_baium, 40, true, false)) + { + if (mob.getTarget() != _baium) + { + mob.clearAggroList(); + } + mob.setIsRunning(true); + mob.addDamageHate(_baium, 0, 999); + mob.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, _baium); + } + else + { + mob.getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, _baium); + } + } + } + startQuestTimer("SELECT_TARGET", 5000, npc, null); + } + break; + } + case "CHECK_ATTACK": + { + if ((npc != null) && ((_lastAttack + 1800000) < System.currentTimeMillis())) + { + notifyEvent("CLEAR_ZONE", null, null); + addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0); + setStatus(ALIVE); + } + else if (npc != null) + { + if (((_lastAttack + 300000) < System.currentTimeMillis()) && (npc.getCurrentHp() < (npc.getMaxHp() * 0.75))) + { + npc.setTarget(npc); + npc.doCast(HEAL_OF_BAIUM.getSkill()); + } + startQuestTimer("CHECK_ATTACK", 60000, npc, null); + } + break; + } + case "CLEAR_STATUS": + { + setStatus(ALIVE); + addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0); + break; + } + case "CLEAR_ZONE": + { + for (L2Character charInside : zone.getCharactersInside()) + { + if (charInside != null) + { + if (charInside.isNpc()) + { + charInside.deleteMe(); + } + else if (charInside.isPlayer()) + { + notifyEvent("teleportOut", null, (L2PcInstance) charInside); + } + } + } + break; + } + case "RESPAWN_BAIUM": + { + if (getStatus() == DEAD) + { + setRespawn(0); + cancelQuestTimer("CLEAR_STATUS", null, null); + notifyEvent("CLEAR_STATUS", null, null); + } + else + { + player.sendMessage(getClass().getSimpleName() + ": You cant respawn Baium while Baium is alive!"); + } + break; + } + case "ABORT_FIGHT": + { + if (getStatus() == IN_FIGHT) + { + _baium = null; + notifyEvent("CLEAR_ZONE", null, null); + notifyEvent("CLEAR_STATUS", null, null); + player.sendMessage(getClass().getSimpleName() + ": Aborting fight!"); + } + else + { + player.sendMessage(getClass().getSimpleName() + ": You cant abort attack right now!"); + } + cancelQuestTimers("CHECK_ATTACK"); + cancelQuestTimers("SELECT_TARGET"); + break; + } + case "DESPAWN_MINIONS": + { + if (getStatus() == IN_FIGHT) + { + for (L2Character charInside : zone.getCharactersInside()) + { + if ((charInside != null) && charInside.isNpc() && (charInside.getId() == ARCHANGEL)) + { + charInside.deleteMe(); + } + } + if (player != null) + { + player.sendMessage(getClass().getSimpleName() + ": All archangels has been deleted!"); + } + } + else if (player != null) + { + player.sendMessage(getClass().getSimpleName() + ": You cant despawn archangels right now!"); + } + break; + } + case "MANAGE_SKILLS": + { + if (npc != null) + { + manageSkills(npc); + } + break; + } + } + return super.onAdvEvent(event, npc, player); + } + + @Override + public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon, Skill skill) + { + _lastAttack = System.currentTimeMillis(); + + if (npc.getId() == BAIUM) + { + if ((attacker.getMountType() == MountType.STRIDER) && !attacker.isAffectedBySkill(ANTI_STRIDER.getSkillId())) + { + if (!npc.isSkillDisabled(ANTI_STRIDER.getSkill())) + { + npc.setTarget(attacker); + npc.doCast(ANTI_STRIDER.getSkill()); + } + } + + if (skill == null) + { + refreshAiParams(attacker, npc, (damage * 1000)); + } + else if (npc.getCurrentHp() < (npc.getMaxHp() * 0.25)) + { + refreshAiParams(attacker, npc, ((damage / 3) * 100)); + } + else if (npc.getCurrentHp() < (npc.getMaxHp() * 0.5)) + { + refreshAiParams(attacker, npc, (damage * 20)); + } + else if (npc.getCurrentHp() < (npc.getMaxHp() * 0.75)) + { + refreshAiParams(attacker, npc, (damage * 10)); + } + else + { + refreshAiParams(attacker, npc, ((damage / 3) * 20)); + } + manageSkills(npc); + } + else + { + final L2Attackable mob = (L2Attackable) npc; + final L2Character mostHated = mob.getMostHated(); + + if ((getRandom(100) < 10) && SkillCaster.checkUseConditions(mob, SPEAR_ATTACK.getSkill())) + { + if ((mostHated != null) && (npc.calculateDistance(mostHated, true, false) < 1000) && zone.isCharacterInZone(mostHated)) + { + mob.setTarget(mostHated); + mob.doCast(SPEAR_ATTACK.getSkill()); + } + else if (zone.isCharacterInZone(attacker)) + { + mob.setTarget(attacker); + mob.doCast(SPEAR_ATTACK.getSkill()); + } + } + + if ((getRandom(100) < 5) && (npc.getCurrentHp() < (npc.getMaxHp() * 0.5)) && SkillCaster.checkUseConditions(mob, ANGEL_HEAL.getSkill())) + { + npc.setTarget(npc); + npc.doCast(ANGEL_HEAL.getSkill()); + } + } + return super.onAttack(npc, attacker, damage, isSummon, skill); + } + + @Override + public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon) + { + if (zone.isCharacterInZone(killer)) + { + setStatus(DEAD); + addSpawn(TELE_CUBE, TELEPORT_CUBIC_LOC, false, 900000); + zone.broadcastPacket(new PlaySound("BS01_D")); + final long respawnTime = (Config.BAIUM_SPAWN_INTERVAL + getRandom(-Config.BAIUM_SPAWN_RANDOM, Config.BAIUM_SPAWN_RANDOM)) * 3600000; + setRespawn(respawnTime); + startQuestTimer("CLEAR_STATUS", respawnTime, null, null); + startQuestTimer("CLEAR_ZONE", 900000, null, null); + cancelQuestTimer("CHECK_ATTACK", npc, null); + } + return super.onKill(npc, killer, isSummon); + } + + @Override + public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon) + { + if (!zone.isInsideZone(creature) || (creature.isNpc() && (creature.getId() == BAIUM_STONE))) + { + return super.onSeeCreature(npc, creature, isSummon); + } + + if (creature.isPlayer() && !creature.isDead() && (_standbyPlayer == null)) + { + _standbyPlayer = (L2PcInstance) creature; + } + + if (creature.isInCategory(CategoryType.CLERIC_GROUP)) + { + if (npc.getCurrentHp() < (npc.getMaxHp() * 0.25)) + { + refreshAiParams(creature, npc, 10000); + } + else if (npc.getCurrentHp() < (npc.getMaxHp() * 0.5)) + { + refreshAiParams(creature, npc, 10000, 6000); + } + else if (npc.getCurrentHp() < (npc.getMaxHp() * 0.75)) + { + refreshAiParams(creature, npc, 10000, 3000); + } + else + { + refreshAiParams(creature, npc, 10000, 2000); + } + } + else + { + refreshAiParams(creature, npc, 10000, 1000); + } + manageSkills(npc); + return super.onSeeCreature(npc, creature, isSummon); + } + + @Override + public String onSpellFinished(L2Npc npc, L2PcInstance player, Skill skill) + { + startQuestTimer("MANAGE_SKILLS", 1000, npc, null); + + if (!zone.isCharacterInZone(npc) && (_baium != null)) + { + _baium.teleToLocation(BAIUM_LOC); + } + return super.onSpellFinished(npc, player, skill); + } + + @Override + public boolean unload(boolean removeFromList) + { + if (_baium != null) + { + _baium.deleteMe(); + } + return super.unload(removeFromList); + } + + private final void refreshAiParams(L2Character attacker, L2Npc npc, int damage) + { + refreshAiParams(attacker, npc, damage, damage); + } + + private final void refreshAiParams(L2Character attacker, L2Npc npc, int damage, int aggro) + { + final int newAggroVal = damage + getRandom(3000); + final int aggroVal = aggro + 1000; + final NpcVariables vars = npc.getVariables(); + for (int i = 0; i < 3; i++) + { + if (attacker == vars.getObject("c_quest" + i, L2Character.class)) + { + if (vars.getInt("i_quest" + i) < aggroVal) + { + vars.set("i_quest" + i, newAggroVal); + } + return; + } + } + final int index = CommonUtil.getIndexOfMinValue(vars.getInt("i_quest0"), vars.getInt("i_quest1"), vars.getInt("i_quest2")); + vars.set("i_quest" + index, newAggroVal); + vars.set("c_quest" + index, attacker); + } + + private int getStatus() + { + return GrandBossManager.getInstance().getBossStatus(BAIUM); + } + + private void addBoss(L2GrandBossInstance grandboss) + { + GrandBossManager.getInstance().addBoss(grandboss); + } + + private void setStatus(int status) + { + GrandBossManager.getInstance().setBossStatus(BAIUM, status); + } + + private void setRespawn(long respawnTime) + { + GrandBossManager.getInstance().getStatsSet(BAIUM).set("respawn_time", (System.currentTimeMillis() + respawnTime)); + } + + private void manageSkills(L2Npc npc) + { + if (npc.isCastingNow(SkillCaster::isAnyNormalType) || npc.isCoreAIDisabled() || !npc.isInCombat()) + { + return; + } + + final NpcVariables vars = npc.getVariables(); + for (int i = 0; i < 3; i++) + { + final L2Character attacker = vars.getObject("c_quest" + i, L2Character.class); + if ((attacker == null) || ((npc.calculateDistance(attacker, true, false) > 9000) || attacker.isDead())) + { + vars.set("i_quest" + i, 0); + } + } + final int index = CommonUtil.getIndexOfMaxValue(vars.getInt("i_quest0"), vars.getInt("i_quest1"), vars.getInt("i_quest2")); + final L2Character player = vars.getObject("c_quest" + index, L2Character.class); + final int i2 = vars.getInt("i_quest" + index); + if ((i2 > 0) && (getRandom(100) < 70)) + { + vars.set("i_quest" + index, 500); + } + + SkillHolder skillToCast = null; + if ((player != null) && !player.isDead()) + { + if (npc.getCurrentHp() > (npc.getMaxHp() * 0.75)) + { + if (getRandom(100) < 10) + { + skillToCast = ENERGY_WAVE; + } + else if (getRandom(100) < 10) + { + skillToCast = EARTH_QUAKE; + } + else + { + skillToCast = BAIUM_ATTACK; + } + } + else if (npc.getCurrentHp() > (npc.getMaxHp() * 0.5)) + { + if (getRandom(100) < 10) + { + skillToCast = GROUP_HOLD; + } + else if (getRandom(100) < 10) + { + skillToCast = ENERGY_WAVE; + } + else if (getRandom(100) < 10) + { + skillToCast = EARTH_QUAKE; + } + else + { + skillToCast = BAIUM_ATTACK; + } + } + else if (npc.getCurrentHp() > (npc.getMaxHp() * 0.25)) + { + if (getRandom(100) < 10) + { + skillToCast = THUNDERBOLT; + } + else if (getRandom(100) < 10) + { + skillToCast = GROUP_HOLD; + } + else if (getRandom(100) < 10) + { + skillToCast = ENERGY_WAVE; + } + else if (getRandom(100) < 10) + { + skillToCast = EARTH_QUAKE; + } + else + { + skillToCast = BAIUM_ATTACK; + } + } + else if (getRandom(100) < 10) + { + skillToCast = THUNDERBOLT; + } + else if (getRandom(100) < 10) + { + skillToCast = GROUP_HOLD; + } + else if (getRandom(100) < 10) + { + skillToCast = ENERGY_WAVE; + } + else if (getRandom(100) < 10) + { + skillToCast = EARTH_QUAKE; + } + else + { + skillToCast = BAIUM_ATTACK; + } + } + + if ((skillToCast != null) && SkillCaster.checkUseConditions(npc, skillToCast.getSkill())) + { + npc.setTarget(player); + npc.doCast(skillToCast.getSkill()); + } + } + + public static void main(String[] args) + { + new Baium(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Core/Core.java b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Core/Core.java new file mode 100644 index 0000000000..bd55f7825c --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Core/Core.java @@ -0,0 +1,228 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ai.bosses.Core; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.l2jmobius.Config; +import com.l2jmobius.gameserver.enums.ChatType; +import com.l2jmobius.gameserver.instancemanager.GrandBossManager; +import com.l2jmobius.gameserver.model.StatsSet; +import com.l2jmobius.gameserver.model.actor.L2Attackable; +import com.l2jmobius.gameserver.model.actor.L2Npc; +import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance; +import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; +import com.l2jmobius.gameserver.network.NpcStringId; +import com.l2jmobius.gameserver.network.serverpackets.PlaySound; + +import ai.AbstractNpcAI; + +/** + * Core AI. + * @author DrLecter, Emperorc + */ +public final class Core extends AbstractNpcAI +{ + private static final int CORE = 29006; + private static final int DEATH_KNIGHT = 29007; + private static final int DOOM_WRAITH = 29008; + // private static final int DICOR = 29009; + // private static final int VALIDUS = 29010; + private static final int SUSCEPTOR = 29011; + // private static final int PERUM = 29012; + // private static final int PREMO = 29013; + + // Core Status Tracking : + private static final byte ALIVE = 0; // Core is spawned. + private static final byte DEAD = 1; // Core has been killed. + + private static boolean _firstAttacked; + + private final List _minions = new CopyOnWriteArrayList<>(); + + private Core() + { + registerMobs(CORE, DEATH_KNIGHT, DOOM_WRAITH, SUSCEPTOR); + + _firstAttacked = false; + final StatsSet info = GrandBossManager.getInstance().getStatsSet(CORE); + final int status = GrandBossManager.getInstance().getBossStatus(CORE); + if (status == DEAD) + { + // load the unlock date and time for Core from DB + final long temp = (info.getLong("respawn_time") - System.currentTimeMillis()); + // if Core is locked until a certain time, mark it so and start the unlock timer + // the unlock time has not yet expired. + if (temp > 0) + { + startQuestTimer("core_unlock", temp, null, null); + } + else + { + // the time has already expired while the server was offline. Immediately spawn Core. + final L2GrandBossInstance core = (L2GrandBossInstance) addSpawn(CORE, 17726, 108915, -6480, 0, false, 0); + GrandBossManager.getInstance().setBossStatus(CORE, ALIVE); + spawnBoss(core); + } + } + else + { + final String test = loadGlobalQuestVar("Core_Attacked"); + if (test.equalsIgnoreCase("true")) + { + _firstAttacked = true; + } + final int loc_x = info.getInt("loc_x"); + final int loc_y = info.getInt("loc_y"); + final int loc_z = info.getInt("loc_z"); + final int heading = info.getInt("heading"); + final double hp = info.getDouble("currentHP"); + final double mp = info.getDouble("currentMP"); + final L2GrandBossInstance core = (L2GrandBossInstance) addSpawn(CORE, loc_x, loc_y, loc_z, heading, false, 0); + core.setCurrentHpMp(hp, mp); + spawnBoss(core); + } + } + + @Override + public void onSave() + { + saveGlobalQuestVar("Core_Attacked", Boolean.toString(_firstAttacked)); + } + + public void spawnBoss(L2GrandBossInstance npc) + { + GrandBossManager.getInstance().addBoss(npc); + npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + // Spawn minions + L2Attackable mob; + for (int i = 0; i < 5; i++) + { + final int x = 16800 + (i * 360); + mob = (L2Attackable) addSpawn(DEATH_KNIGHT, x, 110000, npc.getZ(), 280 + getRandom(40), false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + mob = (L2Attackable) addSpawn(DEATH_KNIGHT, x, 109000, npc.getZ(), 280 + getRandom(40), false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + final int x2 = 16800 + (i * 600); + mob = (L2Attackable) addSpawn(DOOM_WRAITH, x2, 109300, npc.getZ(), 280 + getRandom(40), false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + } + for (int i = 0; i < 4; i++) + { + final int x = 16800 + (i * 450); + mob = (L2Attackable) addSpawn(SUSCEPTOR, x, 110300, npc.getZ(), 280 + getRandom(40), false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + } + } + + @Override + public String onAdvEvent(String event, L2Npc npc, L2PcInstance player) + { + if (event.equalsIgnoreCase("core_unlock")) + { + final L2GrandBossInstance core = (L2GrandBossInstance) addSpawn(CORE, 17726, 108915, -6480, 0, false, 0); + GrandBossManager.getInstance().setBossStatus(CORE, ALIVE); + spawnBoss(core); + } + else if (event.equalsIgnoreCase("spawn_minion")) + { + final L2Attackable mob = (L2Attackable) addSpawn(npc.getId(), npc.getX(), npc.getY(), npc.getZ(), npc.getHeading(), false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + } + else if (event.equalsIgnoreCase("despawn_minions")) + { + _minions.forEach(L2Attackable::decayMe); + _minions.clear(); + } + return super.onAdvEvent(event, npc, player); + } + + @Override + public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon) + { + if (npc.getId() == CORE) + { + if (_firstAttacked) + { + if (getRandom(100) == 0) + { + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.REMOVING_INTRUDERS); + } + } + else + { + _firstAttacked = true; + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.A_NON_PERMITTED_TARGET_HAS_BEEN_DISCOVERED); + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.INTRUDER_REMOVAL_SYSTEM_INITIATED); + } + } + return super.onAttack(npc, attacker, damage, isSummon); + } + + @Override + public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon) + { + final int npcId = npc.getId(); + if (npcId == CORE) + { + npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.A_FATAL_ERROR_HAS_OCCURRED); + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.SYSTEM_IS_BEING_SHUT_DOWN); + npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.EMPTY); + _firstAttacked = false; + GrandBossManager.getInstance().setBossStatus(CORE, DEAD); + // Calculate Min and Max respawn times randomly. + long respawnTime = (Config.CORE_SPAWN_INTERVAL + getRandom(-Config.CORE_SPAWN_RANDOM, Config.CORE_SPAWN_RANDOM)) * 3600000; + respawnTime *= 3600000; + + startQuestTimer("core_unlock", respawnTime, null, null); + // also save the respawn time so that the info is maintained past reboots + final StatsSet info = GrandBossManager.getInstance().getStatsSet(CORE); + info.set("respawn_time", (System.currentTimeMillis() + respawnTime)); + GrandBossManager.getInstance().setStatsSet(CORE, info); + startQuestTimer("despawn_minions", 20000, null, null); + cancelQuestTimers("spawn_minion"); + } + else if ((GrandBossManager.getInstance().getBossStatus(CORE) == ALIVE) && (_minions != null) && _minions.contains(npc)) + { + _minions.remove(npc); + startQuestTimer("spawn_minion", 60000, npc, null); + } + return super.onKill(npc, killer, isSummon); + } + + @Override + public String onSpawn(L2Npc npc) + { + if (npc.getId() == CORE) + { + npc.setIsImmobilized(true); + } + return super.onSpawn(npc); + } + + public static void main(String[] args) + { + new Core(); + } +} diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Orfen/Orfen.java b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Orfen/Orfen.java new file mode 100644 index 0000000000..efc491d788 --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/Orfen/Orfen.java @@ -0,0 +1,350 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ai.bosses.Orfen; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.l2jmobius.Config; +import com.l2jmobius.gameserver.ai.CtrlIntention; +import com.l2jmobius.gameserver.enums.ChatType; +import com.l2jmobius.gameserver.instancemanager.GrandBossManager; +import com.l2jmobius.gameserver.instancemanager.ZoneManager; +import com.l2jmobius.gameserver.model.L2Object; +import com.l2jmobius.gameserver.model.L2Spawn; +import com.l2jmobius.gameserver.model.Location; +import com.l2jmobius.gameserver.model.StatsSet; +import com.l2jmobius.gameserver.model.actor.L2Attackable; +import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; +import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance; +import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; +import com.l2jmobius.gameserver.model.holders.SkillHolder; +import com.l2jmobius.gameserver.model.skills.Skill; +import com.l2jmobius.gameserver.model.skills.SkillCaster; +import com.l2jmobius.gameserver.model.zone.L2ZoneType; +import com.l2jmobius.gameserver.network.NpcStringId; +import com.l2jmobius.gameserver.network.serverpackets.PlaySound; + +import ai.AbstractNpcAI; + +/** + * Orfen's AI + * @author Emperorc + */ +public final class Orfen extends AbstractNpcAI +{ + private static final Location[] POS = + { + new Location(43728, 17220, -4342), + new Location(55024, 17368, -5412), + new Location(53504, 21248, -5486), + new Location(53248, 24576, -5262) + }; + + private static final NpcStringId[] TEXT = + { + NpcStringId.S1_STOP_KIDDING_YOURSELF_ABOUT_YOUR_OWN_POWERLESSNESS, + NpcStringId.S1_I_LL_MAKE_YOU_FEEL_WHAT_TRUE_FEAR_IS, + NpcStringId.YOU_RE_REALLY_STUPID_TO_HAVE_CHALLENGED_ME_S1_GET_READY, + NpcStringId.S1_DO_YOU_THINK_THAT_S_GOING_TO_WORK + }; + + private static final int ORFEN = 29014; + // private static final int RAIKEL = 29015; + private static final int RAIKEL_LEOS = 29016; + // private static final int RIBA = 29017; + private static final int RIBA_IREN = 29018; + + private static boolean _IsTeleported; + private static Set _minions = ConcurrentHashMap.newKeySet(); + private static L2ZoneType ZONE; + + private static final byte ALIVE = 0; + private static final byte DEAD = 1; + + private static final SkillHolder PARALYSIS = new SkillHolder(4064, 1); + private static final SkillHolder BLOW = new SkillHolder(4067, 4); + private static final SkillHolder ORFEN_HEAL = new SkillHolder(4516, 1); + + private Orfen() + { + final int[] mobs = + { + ORFEN, + RAIKEL_LEOS, + RIBA_IREN + }; + registerMobs(mobs); + _IsTeleported = false; + ZONE = ZoneManager.getInstance().getZoneById(12013); + final StatsSet info = GrandBossManager.getInstance().getStatsSet(ORFEN); + final int status = GrandBossManager.getInstance().getBossStatus(ORFEN); + if (status == DEAD) + { + // load the unlock date and time for Orfen from DB + final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); + // if Orfen is locked until a certain time, mark it so and start the unlock timer + // the unlock time has not yet expired. + if (temp > 0) + { + startQuestTimer("orfen_unlock", temp, null, null); + } + else + { + // the time has already expired while the server was offline. Immediately spawn Orfen. + final int i = getRandom(10); + Location loc; + if (i < 4) + { + loc = POS[1]; + } + else if (i < 7) + { + loc = POS[2]; + } + else + { + loc = POS[3]; + } + final L2GrandBossInstance orfen = (L2GrandBossInstance) addSpawn(ORFEN, loc, false, 0); + GrandBossManager.getInstance().setBossStatus(ORFEN, ALIVE); + spawnBoss(orfen); + } + } + else + { + final int loc_x = info.getInt("loc_x"); + final int loc_y = info.getInt("loc_y"); + final int loc_z = info.getInt("loc_z"); + final int heading = info.getInt("heading"); + final double hp = info.getDouble("currentHP"); + final double mp = info.getDouble("currentMP"); + final L2GrandBossInstance orfen = (L2GrandBossInstance) addSpawn(ORFEN, loc_x, loc_y, loc_z, heading, false, 0); + orfen.setCurrentHpMp(hp, mp); + spawnBoss(orfen); + } + } + + public void setSpawnPoint(L2Npc npc, int index) + { + ((L2Attackable) npc).clearAggroList(); + npc.getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null, null); + final L2Spawn spawn = npc.getSpawn(); + spawn.setLocation(POS[index]); + npc.teleToLocation(POS[index], false); + } + + public void spawnBoss(L2GrandBossInstance npc) + { + GrandBossManager.getInstance().addBoss(npc); + npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + startQuestTimer("check_orfen_pos", 10000, npc, null, true); + // Spawn minions + final int x = npc.getX(); + final int y = npc.getY(); + L2Attackable mob; + mob = (L2Attackable) addSpawn(RAIKEL_LEOS, x + 100, y + 100, npc.getZ(), 0, false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + mob = (L2Attackable) addSpawn(RAIKEL_LEOS, x + 100, y - 100, npc.getZ(), 0, false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + mob = (L2Attackable) addSpawn(RAIKEL_LEOS, x - 100, y + 100, npc.getZ(), 0, false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + mob = (L2Attackable) addSpawn(RAIKEL_LEOS, x - 100, y - 100, npc.getZ(), 0, false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + startQuestTimer("check_minion_loc", 10000, npc, null, true); + } + + @Override + public String onAdvEvent(String event, L2Npc npc, L2PcInstance player) + { + if (event.equalsIgnoreCase("orfen_unlock")) + { + final int i = getRandom(10); + Location loc; + if (i < 4) + { + loc = POS[1]; + } + else if (i < 7) + { + loc = POS[2]; + } + else + { + loc = POS[3]; + } + final L2GrandBossInstance orfen = (L2GrandBossInstance) addSpawn(ORFEN, loc, false, 0); + GrandBossManager.getInstance().setBossStatus(ORFEN, ALIVE); + spawnBoss(orfen); + } + else if (event.equalsIgnoreCase("check_orfen_pos")) + { + if ((_IsTeleported && (npc.getCurrentHp() > (npc.getMaxHp() * 0.95))) || (!ZONE.isInsideZone(npc) && !_IsTeleported)) + { + setSpawnPoint(npc, getRandom(3) + 1); + _IsTeleported = false; + } + else if (_IsTeleported && !ZONE.isInsideZone(npc)) + { + setSpawnPoint(npc, 0); + } + } + else if (event.equalsIgnoreCase("check_minion_loc")) + { + for (L2Attackable mob : _minions) + { + if (!npc.isInsideRadius(mob, 3000, false, false)) + { + mob.teleToLocation(npc.getLocation()); + ((L2Attackable) npc).clearAggroList(); + npc.getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null, null); + } + } + } + else if (event.equalsIgnoreCase("despawn_minions")) + { + for (L2Attackable mob : _minions) + { + mob.decayMe(); + } + _minions.clear(); + } + else if (event.equalsIgnoreCase("spawn_minion")) + { + final L2Attackable mob = (L2Attackable) addSpawn(RAIKEL_LEOS, npc.getX(), npc.getY(), npc.getZ(), 0, false, 0); + mob.setIsRaidMinion(true); + _minions.add(mob); + } + return super.onAdvEvent(event, npc, player); + } + + @Override + public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon) + { + if (npc.getId() == ORFEN) + { + final L2Character originalCaster = isSummon ? caster.getServitors().values().stream().findFirst().orElse(caster.getPet()) : caster; + if ((skill.getEffectPoint() > 0) && (getRandom(5) == 0) && npc.isInsideRadius(originalCaster, 1000, false, false)) + { + npc.broadcastSay(ChatType.NPC_GENERAL, TEXT[getRandom(4)], caster.getName()); + originalCaster.teleToLocation(npc.getLocation()); + npc.setTarget(originalCaster); + npc.doCast(PARALYSIS.getSkill()); + } + } + return super.onSkillSee(npc, caster, skill, targets, isSummon); + } + + @Override + public String onFactionCall(L2Npc npc, L2Npc caller, L2PcInstance attacker, boolean isSummon) + { + if ((caller == null) || (npc == null) || npc.isCastingNow(SkillCaster::isAnyNormalType)) + { + return super.onFactionCall(npc, caller, attacker, isSummon); + } + final int npcId = npc.getId(); + final int callerId = caller.getId(); + if ((npcId == RAIKEL_LEOS) && (getRandom(20) == 0)) + { + npc.setTarget(attacker); + npc.doCast(BLOW.getSkill()); + } + else if (npcId == RIBA_IREN) + { + int chance = 1; + if (callerId == ORFEN) + { + chance = 9; + } + if ((callerId != RIBA_IREN) && (caller.getCurrentHp() < (caller.getMaxHp() / 2.0)) && (getRandom(10) < chance)) + { + npc.getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null, null); + npc.setTarget(caller); + npc.doCast(ORFEN_HEAL.getSkill()); + } + } + return super.onFactionCall(npc, caller, attacker, isSummon); + } + + @Override + public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon) + { + final int npcId = npc.getId(); + if (npcId == ORFEN) + { + if (!_IsTeleported && ((npc.getCurrentHp() - damage) < (npc.getMaxHp() / 2))) + { + _IsTeleported = true; + setSpawnPoint(npc, 0); + } + else if (npc.isInsideRadius(attacker, 1000, false, false) && !npc.isInsideRadius(attacker, 300, false, false) && (getRandom(10) == 0)) + { + npc.broadcastSay(ChatType.NPC_GENERAL, TEXT[getRandom(3)], attacker.getName()); + attacker.teleToLocation(npc.getLocation()); + npc.setTarget(attacker); + npc.doCast(PARALYSIS.getSkill()); + } + } + else if (npcId == RIBA_IREN) + { + if (!npc.isCastingNow(SkillCaster::isAnyNormalType) && ((npc.getCurrentHp() - damage) < (npc.getMaxHp() / 2.0))) + { + npc.setTarget(attacker); + npc.doCast(ORFEN_HEAL.getSkill()); + } + } + return super.onAttack(npc, attacker, damage, isSummon); + } + + @Override + public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon) + { + if (npc.getId() == ORFEN) + { + npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + GrandBossManager.getInstance().setBossStatus(ORFEN, DEAD); + // Calculate Min and Max respawn times randomly. + long respawnTime = Config.ORFEN_SPAWN_INTERVAL + getRandom(-Config.ORFEN_SPAWN_RANDOM, Config.ORFEN_SPAWN_RANDOM); + respawnTime *= 3600000; + startQuestTimer("orfen_unlock", respawnTime, null, null); + // also save the respawn time so that the info is maintained past reboots + final StatsSet info = GrandBossManager.getInstance().getStatsSet(ORFEN); + info.set("respawn_time", System.currentTimeMillis() + respawnTime); + GrandBossManager.getInstance().setStatsSet(ORFEN, info); + cancelQuestTimer("check_minion_loc", npc, null); + cancelQuestTimer("check_orfen_pos", npc, null); + startQuestTimer("despawn_minions", 20000, null, null); + cancelQuestTimers("spawn_minion"); + } + else if ((GrandBossManager.getInstance().getBossStatus(ORFEN) == ALIVE) && (npc.getId() == RAIKEL_LEOS)) + { + _minions.remove(npc); + startQuestTimer("spawn_minion", 360000, npc, null); + } + return super.onKill(npc, killer, isSummon); + } + + public static void main(String[] args) + { + new Orfen(); + } +} diff --git a/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java new file mode 100644 index 0000000000..65c2ebbf40 --- /dev/null +++ b/L2J_Mobius_Classic/dist/game/data/scripts/ai/bosses/QueenAnt/QueenAnt.java @@ -0,0 +1,419 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ai.bosses.QueenAnt; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +import com.l2jmobius.Config; +import com.l2jmobius.gameserver.ThreadPoolManager; +import com.l2jmobius.gameserver.ai.CtrlIntention; +import com.l2jmobius.gameserver.instancemanager.GrandBossManager; +import com.l2jmobius.gameserver.instancemanager.ZoneManager; +import com.l2jmobius.gameserver.model.Location; +import com.l2jmobius.gameserver.model.StatsSet; +import com.l2jmobius.gameserver.model.actor.L2Attackable; +import com.l2jmobius.gameserver.model.actor.L2Npc; +import com.l2jmobius.gameserver.model.actor.L2Playable; +import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance; +import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance; +import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; +import com.l2jmobius.gameserver.model.holders.SkillHolder; +import com.l2jmobius.gameserver.model.skills.CommonSkill; +import com.l2jmobius.gameserver.model.skills.Skill; +import com.l2jmobius.gameserver.model.skills.SkillCaster; +import com.l2jmobius.gameserver.model.zone.L2ZoneType; +import com.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import com.l2jmobius.gameserver.network.serverpackets.PlaySound; + +import ai.AbstractNpcAI; + +/** + * Queen Ant's AI + * @author Emperorc + */ +public final class QueenAnt extends AbstractNpcAI +{ + private static final int QUEEN = 29001; + private static final int LARVA = 29002; + private static final int NURSE = 29003; + private static final int GUARD = 29004; + private static final int ROYAL = 29005; + + private static final int[] MOBS = + { + QUEEN, + LARVA, + NURSE, + GUARD, + ROYAL + }; + + private static final Location OUST_LOC_1 = new Location(-19480, 187344, -5600); + private static final Location OUST_LOC_2 = new Location(-17928, 180912, -5520); + private static final Location OUST_LOC_3 = new Location(-23808, 182368, -5600); + + private static final int QUEEN_X = -21610; + private static final int QUEEN_Y = 181594; + private static final int QUEEN_Z = -5734; + + // QUEEN Status Tracking : + private static final byte ALIVE = 0; // Queen Ant is spawned. + private static final byte DEAD = 1; // Queen Ant has been killed. + + private static L2ZoneType _zone; + + private static SkillHolder HEAL1 = new SkillHolder(4020, 1); + private static SkillHolder HEAL2 = new SkillHolder(4024, 1); + + L2MonsterInstance _queen = null; + private L2MonsterInstance _larva = null; + private final Set _nurses = ConcurrentHashMap.newKeySet(); + ScheduledFuture _task = null; + + private QueenAnt() + { + addSpawnId(MOBS); + addKillId(MOBS); + addAggroRangeEnterId(MOBS); + addFactionCallId(NURSE); + + _zone = ZoneManager.getInstance().getZoneById(12012); + final StatsSet info = GrandBossManager.getInstance().getStatsSet(QUEEN); + final int status = GrandBossManager.getInstance().getBossStatus(QUEEN); + if (status == DEAD) + { + // load the unlock date and time for queen ant from DB + final long temp = info.getLong("respawn_time") - System.currentTimeMillis(); + // if queen ant is locked until a certain time, mark it so and start the unlock timer + // the unlock time has not yet expired. + if (temp > 0) + { + startQuestTimer("queen_unlock", temp, null, null); + } + else + { + // the time has already expired while the server was offline. Immediately spawn queen ant. + final L2GrandBossInstance queen = (L2GrandBossInstance) addSpawn(QUEEN, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); + GrandBossManager.getInstance().setBossStatus(QUEEN, ALIVE); + spawnBoss(queen); + } + } + else + { + int loc_x = info.getInt("loc_x"); + int loc_y = info.getInt("loc_y"); + int loc_z = info.getInt("loc_z"); + final int heading = info.getInt("heading"); + final double hp = info.getDouble("currentHP"); + final double mp = info.getDouble("currentMP"); + if (!_zone.isInsideZone(loc_x, loc_y, loc_z)) + { + loc_x = QUEEN_X; + loc_y = QUEEN_Y; + loc_z = QUEEN_Z; + } + final L2GrandBossInstance queen = (L2GrandBossInstance) addSpawn(QUEEN, loc_x, loc_y, loc_z, heading, false, 0); + queen.setCurrentHpMp(hp, mp); + spawnBoss(queen); + } + } + + private void spawnBoss(L2GrandBossInstance npc) + { + GrandBossManager.getInstance().addBoss(npc); + if (getRandom(100) < 33) + { + _zone.movePlayersTo(OUST_LOC_1); + } + else if (getRandom(100) < 50) + { + _zone.movePlayersTo(OUST_LOC_2); + } + else + { + _zone.movePlayersTo(OUST_LOC_3); + } + GrandBossManager.getInstance().addBoss(npc); + startQuestTimer("action", 10000, npc, null, true); + startQuestTimer("heal", 1000, null, null, true); + npc.broadcastPacket(new PlaySound(1, "BS01_A", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + _queen = npc; + _larva = (L2MonsterInstance) addSpawn(LARVA, -21600, 179482, -5846, getRandom(360), false, 0); + } + + @Override + public String onAdvEvent(String event, L2Npc npc, L2PcInstance player) + { + if (event.equalsIgnoreCase("heal")) + { + boolean notCasting; + final boolean larvaNeedHeal = (_larva != null) && (_larva.getCurrentHp() < _larva.getMaxHp()); + final boolean queenNeedHeal = (_queen != null) && (_queen.getCurrentHp() < _queen.getMaxHp()); + for (L2MonsterInstance nurse : _nurses) + { + if ((nurse == null) || nurse.isDead() || nurse.isCastingNow(SkillCaster::isAnyNormalType)) + { + continue; + } + + notCasting = nurse.getAI().getIntention() != CtrlIntention.AI_INTENTION_CAST; + if (larvaNeedHeal) + { + if ((nurse.getTarget() != _larva) || notCasting) + { + nurse.setTarget(_larva); + nurse.useMagic(getRandomBoolean() ? HEAL1.getSkill() : HEAL2.getSkill()); + } + continue; + } + if (queenNeedHeal) + { + if (nurse.getLeader() == _larva) + { + continue; + } + + if ((nurse.getTarget() != _queen) || notCasting) + { + nurse.setTarget(_queen); + nurse.useMagic(HEAL1.getSkill()); + } + continue; + } + // if nurse not casting - remove target + if (notCasting && (nurse.getTarget() != null)) + { + nurse.setTarget(null); + } + } + } + else if (event.equalsIgnoreCase("action") && (npc != null)) + { + if (getRandom(3) == 0) + { + if (getRandom(2) == 0) + { + npc.broadcastSocialAction(3); + } + else + { + npc.broadcastSocialAction(4); + } + } + } + else if (event.equalsIgnoreCase("queen_unlock")) + { + final L2GrandBossInstance queen = (L2GrandBossInstance) addSpawn(QUEEN, QUEEN_X, QUEEN_Y, QUEEN_Z, 0, false, 0); + GrandBossManager.getInstance().setBossStatus(QUEEN, ALIVE); + spawnBoss(queen); + } + return super.onAdvEvent(event, npc, player); + } + + @Override + public String onSpawn(L2Npc npc) + { + final L2MonsterInstance mob = (L2MonsterInstance) npc; + switch (npc.getId()) + { + case LARVA: + { + mob.setIsImmobilized(true); + mob.setUndying(true); + mob.setIsRaidMinion(true); + break; + } + case NURSE: + { + mob.disableCoreAI(true); + mob.setIsRaidMinion(true); + _nurses.add(mob); + break; + } + case ROYAL: + case GUARD: + { + mob.setIsRaidMinion(true); + break; + } + case QUEEN: + { + if (mob.getMinionList().getSpawnedMinions().isEmpty()) + { + ((L2MonsterInstance) npc).getMinionList().spawnMinions(npc.getParameters().getMinionList("Privates")); + } + _task = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new QueenAntTask(), 5 * 1000, 5 * 1000); + break; + } + } + + return super.onSpawn(npc); + } + + @Override + public String onFactionCall(L2Npc npc, L2Npc caller, L2PcInstance attacker, boolean isSummon) + { + if ((caller == null) || (npc == null)) + { + return super.onFactionCall(npc, caller, attacker, isSummon); + } + + if (!npc.isCastingNow(SkillCaster::isAnyNormalType) && (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_CAST)) + { + if (caller.getCurrentHp() < caller.getMaxHp()) + { + npc.setTarget(caller); + ((L2Attackable) npc).useMagic(HEAL1.getSkill()); + } + } + return null; + } + + @Override + public String onAggroRangeEnter(L2Npc npc, L2PcInstance player, boolean isSummon) + { + if ((npc == null) || (player.isGM() && player.isInvisible())) + { + return null; + } + + final boolean isMage; + final L2Playable character; + if (isSummon) + { + isMage = false; + character = player.getServitors().values().stream().findFirst().orElse(player.getPet()); + } + else + { + isMage = player.isMageClass(); + character = player; + } + + if (character == null) + { + return null; + } + + if (!Config.RAID_DISABLE_CURSE && ((character.getLevel() - npc.getLevel()) > 8)) + { + Skill curse = null; + if (isMage) + { + if (!character.hasAbnormalType(CommonSkill.RAID_CURSE.getSkill().getAbnormalType()) && (getRandom(4) == 0)) + { + curse = CommonSkill.RAID_CURSE.getSkill(); + } + } + else if (!character.hasAbnormalType(CommonSkill.RAID_CURSE2.getSkill().getAbnormalType()) && (getRandom(4) == 0)) + { + curse = CommonSkill.RAID_CURSE2.getSkill(); + } + + if (curse != null) + { + npc.broadcastPacket(new MagicSkillUse(npc, character, curse.getId(), curse.getLevel(), 300, 0)); + curse.applyEffects(npc, character); + } + + ((L2Attackable) npc).stopHating(character); // for calling again + return null; + } + + return super.onAggroRangeEnter(npc, player, isSummon); + } + + @Override + public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon) + { + final int npcId = npc.getId(); + if (npcId == QUEEN) + { + npc.broadcastPacket(new PlaySound(1, "BS02_D", 1, npc.getObjectId(), npc.getX(), npc.getY(), npc.getZ())); + GrandBossManager.getInstance().setBossStatus(QUEEN, DEAD); + // Calculate Min and Max respawn times randomly. + final long respawnTime = (Config.QUEEN_ANT_SPAWN_INTERVAL + getRandom(-Config.QUEEN_ANT_SPAWN_RANDOM, Config.QUEEN_ANT_SPAWN_RANDOM)) * 3600000; + startQuestTimer("queen_unlock", respawnTime, null, null); + cancelQuestTimer("action", npc, null); + cancelQuestTimer("heal", null, null); + // also save the respawn time so that the info is maintained past reboots + final StatsSet info = GrandBossManager.getInstance().getStatsSet(QUEEN); + info.set("respawn_time", System.currentTimeMillis() + respawnTime); + GrandBossManager.getInstance().setStatsSet(QUEEN, info); + _nurses.clear(); + _larva.deleteMe(); + _larva = null; + _queen = null; + if (_task != null) + { + _task.cancel(false); + _task = null; + } + } + else if ((_queen != null) && !_queen.isAlikeDead()) + { + if (npcId == ROYAL) + { + final L2MonsterInstance mob = (L2MonsterInstance) npc; + if (mob.getLeader() != null) + { + mob.getLeader().getMinionList().onMinionDie(mob, (280 + getRandom(40)) * 1000); + } + } + else if (npcId == NURSE) + { + final L2MonsterInstance mob = (L2MonsterInstance) npc; + _nurses.remove(mob); + if (mob.getLeader() != null) + { + mob.getLeader().getMinionList().onMinionDie(mob, 10000); + } + } + } + return super.onKill(npc, killer, isSummon); + } + + private class QueenAntTask implements Runnable + { + public QueenAntTask() + { + // Constructor stub + } + + @Override + public void run() + { + if ((_queen == null) || _queen.isDead()) + { + _task.cancel(false); + _task = null; + } + else if (_queen.calculateDistance(QUEEN_X, QUEEN_Y, QUEEN_Z, false, false) > 2000.) + { + _queen.clearAggroList(); + _queen.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(QUEEN_X, QUEEN_Y, QUEEN_Z, 0)); + } + } + } + + public static void main(String[] args) + { + new QueenAnt(); + } +} diff --git a/L2J_Mobius_Classic/dist/game/data/stats/npcs/29000-29099.xml b/L2J_Mobius_Classic/dist/game/data/stats/npcs/29000-29099.xml index 330cae5a9e..caddd0b87f 100644 --- a/L2J_Mobius_Classic/dist/game/data/stats/npcs/29000-29099.xml +++ b/L2J_Mobius_Classic/dist/game/data/stats/npcs/29000-29099.xml @@ -9,7 +9,7 @@ BUG FEMALE - + @@ -48,6 +48,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BUG @@ -226,7 +330,7 @@ CONSTRUCT MALE - + @@ -261,6 +365,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -598,7 +783,7 @@ ELEMENTAL FEMALE - + @@ -637,6 +822,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -896,78 +1136,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + diff --git a/L2J_Mobius_Classic/readme.txt b/L2J_Mobius_Classic/readme.txt index 333ffe58ce..7f9830a50c 100644 --- a/L2J_Mobius_Classic/readme.txt +++ b/L2J_Mobius_Classic/readme.txt @@ -32,6 +32,8 @@ What is done -Admin menu teleport and shop cleanups -Blacksmith NPC updates -New Race Track arena zone +-Giran Luxury Shop updates +-Grandboss scripts and stats Custom work -Newbie Helper NPC location info @@ -43,7 +45,6 @@ TODO list -Retail NPC dialogs -Retail NPC buylists -Test quests and update rewards --Giran Luxury Shop updates -Floran Agricultural Area clan halls -Test castle sieges -Whisper of Dreams quests