/* * Copyright (C) 2004-2015 L2J DataPack * * This file is part of L2J DataPack. * * L2J DataPack 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. * * L2J DataPack 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 <http://www.gnu.org/licenses/>. */ package ai.npc.Raina; import static com.l2jserver.gameserver.model.base.ClassLevel.THIRD; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import ai.npc.AbstractNpcAI; import com.l2jserver.Config; import com.l2jserver.gameserver.cache.HtmCache; import com.l2jserver.gameserver.data.xml.impl.CategoryData; import com.l2jserver.gameserver.data.xml.impl.ClassListData; import com.l2jserver.gameserver.data.xml.impl.SkillTreesData; import com.l2jserver.gameserver.enums.CategoryType; import com.l2jserver.gameserver.enums.Race; import com.l2jserver.gameserver.enums.SubclassInfoType; import com.l2jserver.gameserver.model.actor.L2Npc; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.base.ClassId; import com.l2jserver.gameserver.model.base.ClassLevel; import com.l2jserver.gameserver.model.base.PlayerClass; import com.l2jserver.gameserver.model.base.SubClass; import com.l2jserver.gameserver.model.events.EventType; import com.l2jserver.gameserver.model.events.ListenerRegisterType; import com.l2jserver.gameserver.model.events.annotations.Id; import com.l2jserver.gameserver.model.events.annotations.RegisterEvent; import com.l2jserver.gameserver.model.events.annotations.RegisterType; import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMenuSelect; import com.l2jserver.gameserver.model.quest.QuestState; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.AcquireSkillList; import com.l2jserver.gameserver.network.serverpackets.ExSubjobInfo; import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage; /** * Raina AI. * @author St3eT */ public final class Raina extends AbstractNpcAI { // NPC private static final int RAINA = 33491; // Items private static final int SUBCLASS_CERTIFICATE = 30433; private final static int ABELIUS_POWER = 32264; private final static int SAPYROS_POWER = 32265; private final static int ASHAGEN_POWER = 32266; private final static int CRANIGG_POWER = 32267; private final static int SOLTKREIG_POWER = 32268; private final static int NAVIAROPE_POWER = 32269; private final static int LEISTER_POWER = 32270; private final static int LAKCIS_POWER = 32271; // Misc private static final Set<PlayerClass> mainSubclassSet; private static final Set<PlayerClass> neverSubclassed = EnumSet.of(PlayerClass.Overlord, PlayerClass.Warsmith); private static final Set<PlayerClass> subclasseSet1 = EnumSet.of(PlayerClass.DarkAvenger, PlayerClass.Paladin, PlayerClass.TempleKnight, PlayerClass.ShillienKnight); private static final Set<PlayerClass> subclasseSet2 = EnumSet.of(PlayerClass.TreasureHunter, PlayerClass.AbyssWalker, PlayerClass.Plainswalker); private static final Set<PlayerClass> subclasseSet3 = EnumSet.of(PlayerClass.Hawkeye, PlayerClass.SilverRanger, PlayerClass.PhantomRanger); private static final Set<PlayerClass> subclasseSet4 = EnumSet.of(PlayerClass.Warlock, PlayerClass.ElementalSummoner, PlayerClass.PhantomSummoner); private static final Set<PlayerClass> subclasseSet5 = EnumSet.of(PlayerClass.Sorceror, PlayerClass.Spellsinger, PlayerClass.Spellhowler); private static final EnumMap<PlayerClass, Set<PlayerClass>> subclassSetMap = new EnumMap<>(PlayerClass.class); static { final Set<PlayerClass> subclasses = PlayerClass.getSet(null, THIRD); subclasses.removeAll(neverSubclassed); mainSubclassSet = subclasses; subclassSetMap.put(PlayerClass.DarkAvenger, subclasseSet1); subclassSetMap.put(PlayerClass.Paladin, subclasseSet1); subclassSetMap.put(PlayerClass.TempleKnight, subclasseSet1); subclassSetMap.put(PlayerClass.ShillienKnight, subclasseSet1); subclassSetMap.put(PlayerClass.TreasureHunter, subclasseSet2); subclassSetMap.put(PlayerClass.AbyssWalker, subclasseSet2); subclassSetMap.put(PlayerClass.Plainswalker, subclasseSet2); subclassSetMap.put(PlayerClass.Hawkeye, subclasseSet3); subclassSetMap.put(PlayerClass.SilverRanger, subclasseSet3); subclassSetMap.put(PlayerClass.PhantomRanger, subclasseSet3); subclassSetMap.put(PlayerClass.Warlock, subclasseSet4); subclassSetMap.put(PlayerClass.ElementalSummoner, subclasseSet4); subclassSetMap.put(PlayerClass.PhantomSummoner, subclasseSet4); subclassSetMap.put(PlayerClass.Sorceror, subclasseSet5); subclassSetMap.put(PlayerClass.Spellsinger, subclasseSet5); subclassSetMap.put(PlayerClass.Spellhowler, subclasseSet5); } private static final Map<CategoryType, Integer> classCloak = new HashMap<>(); { classCloak.put(CategoryType.SIGEL_GROUP, 30310); // Abelius Cloak classCloak.put(CategoryType.TYRR_GROUP, 30311); // Sapyros Cloak Grade classCloak.put(CategoryType.OTHELL_GROUP, 30312); // Ashagen Cloak Grade classCloak.put(CategoryType.YUL_GROUP, 30313); // Cranigg Cloak Grade classCloak.put(CategoryType.FEOH_GROUP, 30314); // Soltkreig Cloak Grade classCloak.put(CategoryType.ISS_GROUP, 30315); // Naviarope Cloak Grade classCloak.put(CategoryType.WYNN_GROUP, 30316); // Leister Cloak Grade classCloak.put(CategoryType.AEORE_GROUP, 30317); // Laksis Cloak Grade } private static final List<PlayerClass> dualClassList = new ArrayList<>(); { dualClassList.addAll(Arrays.asList(PlayerClass.sigelPhoenixKnight, PlayerClass.sigelHellKnight, PlayerClass.sigelEvasTemplar, PlayerClass.sigelShilenTemplar)); dualClassList.addAll(Arrays.asList(PlayerClass.tyrrDuelist, PlayerClass.tyrrDreadnought, PlayerClass.tyrrTitan, PlayerClass.tyrrGrandKhavatari, PlayerClass.tyrrDoombringer)); dualClassList.addAll(Arrays.asList(PlayerClass.othellAdventurer, PlayerClass.othellWindRider, PlayerClass.othellGhostHunter, PlayerClass.othellFortuneSeeker)); dualClassList.addAll(Arrays.asList(PlayerClass.yulSagittarius, PlayerClass.yulMoonlightSentinel, PlayerClass.yulGhostSentinel, PlayerClass.yulTrickster)); dualClassList.addAll(Arrays.asList(PlayerClass.feohArchmage, PlayerClass.feohSoultaker, PlayerClass.feohMysticMuse, PlayerClass.feoStormScreamer, PlayerClass.feohSoulHound)); dualClassList.addAll(Arrays.asList(PlayerClass.issHierophant, PlayerClass.issSwordMuse, PlayerClass.issSpectralDancer, PlayerClass.issDoomcryer)); dualClassList.addAll(Arrays.asList(PlayerClass.wynnArcanaLord, PlayerClass.wynnElementalMaster, PlayerClass.wynnSpectralMaster)); dualClassList.addAll(Arrays.asList(PlayerClass.aeoreCardinal, PlayerClass.aeoreEvaSaint, PlayerClass.aeoreShillienSaint)); } // @formatter:off private static final int[] REAWAKEN_PRICE = { 100_000_000, 90_000_000, 80_000_000, 70_000_000, 60_000_000, 50_000_000, 40_000_000, 30_000_000, 20_000_000, 10_000_000 }; // @formatter:on private Raina() { super(Raina.class.getSimpleName(), "ai/npc"); addStartNpc(RAINA); addFirstTalkId(RAINA); addTalkId(RAINA); } @Override public String onAdvEvent(String event, L2Npc npc, L2PcInstance player) { String htmltext = null; switch (event) { case "33491-01.html": case "33491-02.html": case "33491-03.html": case "33491-04.html": case "33491-05.html": case "reawakenCancel.html": { htmltext = event; break; } case "addSubclass": { if (player.isTransformed()) { htmltext = "noTransform.html"; break; } else if (player.hasSummon()) { htmltext = "noSummon.html"; break; } else if (player.getRace() == Race.ERTHEIA) { htmltext = "noErtheia.html"; break; } else if (!haveDoneQuest(player) && Config.ALT_GAME_SUBCLASS_WITHOUT_QUESTS && !player.isGM()) { htmltext = "noQuest.html"; break; } else if (!hasAllSubclassLeveled(player) || (player.getTotalSubClasses() >= Config.MAX_SUBCLASS)) { htmltext = "addFailed.html"; break; } else if (!player.isInventoryUnder90(true) || (player.getWeightPenalty() >= 2)) { htmltext = "inventoryLimit.html"; break; } final Set<PlayerClass> availSubs = getAvailableSubClasses(player); final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "subclassList.html"); if ((availSubs == null) || availSubs.isEmpty()) { break; } for (PlayerClass subClass : availSubs) { if (subClass != null) { final int classId = subClass.ordinal(); final int npcStringId = 11170000 + classId; sb.append("<fstring p1=\"0\" p2=\"" + classId + "\">" + npcStringId + "</fstring>"); } } html.replace("%subclassList%", sb.toString()); player.sendPacket(html); break; } case "removeSubclass": { if (player.isTransformed()) { htmltext = "noTransform.html"; break; } else if (player.hasSummon()) { htmltext = "noSummon.html"; break; } else if (player.getRace() == Race.ERTHEIA) { htmltext = "noErtheia.html"; break; } else if (!player.isInventoryUnder90(true) || (player.getWeightPenalty() >= 2)) { htmltext = "inventoryLimit.html"; break; } else if (player.getSubClasses().isEmpty()) { htmltext = "noSubChange.html"; break; } final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "subclassRemoveList.html"); for (SubClass subClass : player.getSubClasses().values()) { if (subClass != null) { final int classId = subClass.getClassId(); final int npcStringId = 11170000 + classId; sb.append("<fstring p1=\"2\" p2=\"" + subClass.getClassIndex() + "\">" + npcStringId + "</fstring>"); } } html.replace("%removeList%", sb.toString()); player.sendPacket(html); break; } case "changeSubclass": { if (player.isTransformed()) { htmltext = "noTransform.html"; break; } else if (player.hasSummon()) { htmltext = "noSummon.html"; break; } else if (player.getRace() == Race.ERTHEIA) { htmltext = "noErtheia.html"; break; } else if (player.getSubClasses().isEmpty()) { htmltext = "noSubChange.html"; break; } else if (!hasQuestItems(player, SUBCLASS_CERTIFICATE)) { htmltext = "noCertificate.html"; break; } player.sendMessage("Not done yet."); break; } case "ertheiaDualClass": { if ((player.getRace() != Race.ERTHEIA) || (player.getLevel() < 85) || player.hasDualClass()) { htmltext = "addDualClassErtheiaFailed.html"; break; } htmltext = "addDualClassErtheia.html"; break; } case "addDualClass_SIGEL_GROUP": case "addDualClass_TYRR_GROUP": case "addDualClass_OTHELL_GROUP": case "addDualClass_YUL_GROUP": case "addDualClass_FEOH_GROUP": case "addDualClass_ISS_GROUP": case "addDualClass_WYNN_GROUP": case "addDualClass_AEORE_GROUP": { final CategoryType cType = CategoryType.valueOf(event.replace("addDualClass_", "")); if (cType == null) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Cannot parse CategoryType, event: " + event); } final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addDualClassErtheiaList.html"); for (PlayerClass dualClasses : getDualClasses(player, cType)) { if (dualClasses != null) { sb.append("<button value=\"" + ClassListData.getInstance().getClass(dualClasses.ordinal()).getClassName() + "\" action=\"bypass -h menu_select?ask=6&reply=" + dualClasses.ordinal() + "\" width=\"200\" height=\"31\" back=\"L2UI_CT1.HtmlWnd_DF_Awake_Down\" fore=\"L2UI_CT1.HtmlWnd_DF_Awake\"><br>"); } } html.replace("%dualclassList%", sb.toString()); player.sendPacket(html); break; } case "reawekenDualclass": { if (player.isTransformed()) { htmltext = "noTransform.html"; break; } else if (player.hasSummon()) { htmltext = "noSummon.html"; break; } else if (!player.hasDualClass() || !player.isDualClassActive() || (player.getClassId().level() != ClassLevel.AWAKEN.ordinal())) { htmltext = "reawakenNoDual.html"; break; } final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawaken.html"); final int index = player.getLevel() > 94 ? REAWAKEN_PRICE.length - 1 : player.getLevel() - 85; html.replace("%price%", REAWAKEN_PRICE[index]); player.sendPacket(html); break; } case "reawakenDualclassConfirm": { final int index = player.getLevel() > 94 ? REAWAKEN_PRICE.length - 1 : player.getLevel() - 85; if (player.isTransformed()) { htmltext = "noTransform.html"; break; } else if (player.hasSummon()) { htmltext = "noSummon.html"; break; } else if (!player.hasDualClass() || !player.isDualClassActive() || (player.getClassId().level() != ClassLevel.AWAKEN.ordinal())) { htmltext = "reawakenNoDual.html"; break; } else if ((player.getAdena() < REAWAKEN_PRICE[index]) || !hasQuestItems(player, getCloakId(player))) { final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawakenNoFee.html"); html.replace("%price%", REAWAKEN_PRICE[index]); player.sendPacket(html); break; } final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawakenList.html"); player.sendPacket(html); break; } case "reawaken_SIGEL_GROUP": case "reawaken_TYRR_GROUP": case "reawaken_OTHELL_GROUP": case "reawaken_YUL_GROUP": case "reawaken_FEOH_GROUP": case "reawaken_ISS_GROUP": case "reawaken_WYNN_GROUP": case "reawaken_AEORE_GROUP": { final CategoryType cType = CategoryType.valueOf(event.replace("reawaken_", "")); if (cType == null) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Cannot parse CategoryType, event: " + event); } final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawakenClassList.html"); for (PlayerClass dualClasses : getDualClasses(player, cType)) { if (dualClasses != null) { sb.append("<button value=\"" + ClassListData.getInstance().getClass(dualClasses.ordinal()).getClassName() + "\" action=\"bypass -h menu_select?ask=5&reply=" + dualClasses.ordinal() + "\" width=\"200\" height=\"31\" back=\"L2UI_CT1.HtmlWnd_DF_Awake_Down\" fore=\"L2UI_CT1.HtmlWnd_DF_Awake\"><br>"); } } html.replace("%dualclassList%", sb.toString()); player.sendPacket(html); break; } } return htmltext; } @RegisterEvent(EventType.ON_NPC_MENU_SELECT) @RegisterType(ListenerRegisterType.NPC) @Id(RAINA) public final void OnNpcMenuSelect(OnNpcMenuSelect event) { final L2PcInstance player = event.getTalker(); final L2Npc npc = event.getNpc(); final int ask = event.getAsk(); switch (ask) { case 0: // Add subclass confirm menu { final int classId = event.getReply(); final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addConfirm.html"); if (!isValidNewSubClass(player, classId)) { return; } final int npcStringId = 11170000 + classId; sb.append("<fstring p1=\"1\" p2=\"" + classId + "\">" + npcStringId + "</fstring>"); html.replace("%confirmButton%", sb.toString()); player.sendPacket(html); break; } case 1: // Add subclass { final int classId = event.getReply(); if (!isValidNewSubClass(player, classId)) { return; } if (!player.addSubClass(classId, player.getTotalSubClasses() + 1, false)) { return; } player.setActiveClass(player.getTotalSubClasses()); player.sendPacket(new ExSubjobInfo(player, SubclassInfoType.NEW_SLOT_USED)); player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED); player.sendPacket(getNpcHtmlMessage(player, npc, "addSuccess.html")); break; } case 2: // Remove (change) subclass list { final int subclassIndex = event.getReply(); final Set<PlayerClass> availSubs = getAvailableSubClasses(player); final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "removeSubclassList.html"); if ((availSubs == null) || availSubs.isEmpty()) { return; } for (PlayerClass subClass : availSubs) { if (subClass != null) { final int classId = subClass.ordinal(); final int npcStringId = 11170000 + classId; sb.append("<fstring p1=\"3\" p2=\"" + classId + "\">" + npcStringId + "</fstring>"); } } npc.getVariables().set("SUBCLASS_INDEX_" + player.getObjectId(), subclassIndex); html.replace("%subclassList%", sb.toString()); player.sendPacket(html); break; } case 3: // Remove (change) subclass confirm menu { final int classId = event.getReply(); final int classIndex = npc.getVariables().getInt("SUBCLASS_INDEX_" + player.getObjectId(), -1); if (classIndex < 0) { return; } final StringBuilder sb = new StringBuilder(); final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addConfirm2.html"); final int npcStringId = 11170000 + classId; sb.append("<fstring p1=\"4\" p2=\"" + classId + "\">" + npcStringId + "</fstring>"); html.replace("%confirmButton%", sb.toString()); player.sendPacket(html); break; } case 4: // Remove (change) subclass { final int classId = event.getReply(); final int classIndex = npc.getVariables().getInt("SUBCLASS_INDEX_" + player.getObjectId(), -1); if (classIndex < 0) { return; } if (player.modifySubClass(classIndex, classId, false)) { player.abortCast(); player.stopAllEffectsExceptThoseThatLastThroughDeath(); player.stopAllEffectsNotStayOnSubclassChange(); player.stopCubics(); player.setActiveClass(classIndex); player.sendPacket(new ExSubjobInfo(player, SubclassInfoType.CLASS_CHANGED)); player.sendPacket(getNpcHtmlMessage(player, npc, "addSuccess.html")); player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED); } break; } case 5: // Reawaken (change dual class) { final int classId = event.getReply(); if (player.isTransformed() || player.hasSummon() || (!player.hasDualClass() || !player.isDualClassActive() || (player.getClassId().level() != ClassLevel.AWAKEN.ordinal()))) { break; } // Validating classId if (!getDualClasses(player, null).contains(PlayerClass.values()[classId])) { break; } final int index = player.getLevel() > 94 ? REAWAKEN_PRICE.length - 1 : player.getLevel() - 85; if ((player.getAdena() < REAWAKEN_PRICE[index]) || !hasQuestItems(player, getCloakId(player))) { final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawakenNoFee.html"); html.replace("%price%", REAWAKEN_PRICE[index]); player.sendPacket(html); break; } player.reduceAdena((getClass().getSimpleName() + "_Reawaken"), REAWAKEN_PRICE[index], npc, true); takeItems(player, getCloakId(player), 1); final int classIndex = player.getClassIndex(); if (player.modifySubClass(classIndex, classId, true)) { player.abortCast(); player.stopAllEffectsExceptThoseThatLastThroughDeath(); player.stopAllEffectsNotStayOnSubclassChange(); player.stopCubics(); player.setActiveClass(classIndex); player.sendPacket(new ExSubjobInfo(player, SubclassInfoType.CLASS_CHANGED)); player.sendPacket(getNpcHtmlMessage(player, npc, "reawakenSuccess.html")); SkillTreesData.getInstance().cleanSkillUponAwakening(player); player.sendPacket(new AcquireSkillList(player)); player.sendSkillList(); addPowerItem(player); } break; } case 6: // Add dual class for ertheia { final int classId = event.getReply(); if (player.isTransformed() || player.hasSummon()) { break; } // Validating classId if (!getDualClasses(player, null).contains(PlayerClass.values()[classId])) { break; } if (player.addSubClass(classId, player.getTotalSubClasses() + 1, true)) { player.setActiveClass(player.getTotalSubClasses()); player.sendPacket(new ExSubjobInfo(player, SubclassInfoType.NEW_SLOT_USED)); player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED); player.sendPacket(getNpcHtmlMessage(player, npc, "addSuccess.html")); SkillTreesData.getInstance().cleanSkillUponAwakening(player); player.sendPacket(new AcquireSkillList(player)); player.sendSkillList(); addPowerItem(player); } break; } } } private void addPowerItem(L2PcInstance player) { int itemId = ABELIUS_POWER; // Sigel if (player.isInCategory(CategoryType.TYRR_GROUP)) { itemId = SAPYROS_POWER; } else if (player.isInCategory(CategoryType.OTHELL_GROUP)) { itemId = ASHAGEN_POWER; } else if (player.isInCategory(CategoryType.YUL_GROUP)) { itemId = CRANIGG_POWER; } else if (player.isInCategory(CategoryType.FEOH_GROUP)) { itemId = SOLTKREIG_POWER; } else if (player.isInCategory(CategoryType.ISS_GROUP)) { itemId = NAVIAROPE_POWER; } else if (player.isInCategory(CategoryType.WYNN_GROUP)) { itemId = LEISTER_POWER; } else if (player.isInCategory(CategoryType.AEORE_GROUP)) { itemId = LAKCIS_POWER; } giveItems(player, itemId, 1); } /** * Returns list of available subclasses Base class and already used subclasses removed * @param player * @return */ private Set<PlayerClass> getAvailableSubClasses(L2PcInstance player) { final int currentBaseId = player.getBaseClass(); final ClassId baseCID = ClassId.getClassId(currentBaseId); int baseClassId = (baseCID.level() > 2) ? baseCID.getParent().ordinal() : currentBaseId; final Set<PlayerClass> availSubs = getSubclasses(player, baseClassId); if ((availSubs != null) && !availSubs.isEmpty()) { for (PlayerClass pclass : availSubs) { // scan for already used subclasses final int availClassId = pclass.ordinal(); final ClassId cid = ClassId.getClassId(availClassId); for (SubClass subList : player.getSubClasses().values()) { final ClassId subId = ClassId.getClassId(subList.getClassId()); if (subId.equalsOrChildOf(cid)) { availSubs.remove(cid); break; } } } } return availSubs; } private boolean haveDoneQuest(L2PcInstance player) { final QuestState qs = player.getQuestState("Q10385_RedThreadOfFate"); // TODO: Replace with class name return qs == null ? false : qs.isCompleted(); } /** * Check new subclass classId for validity. Base class not added into allowed subclasses. * @param player * @param classId * @return */ private boolean isValidNewSubClass(L2PcInstance player, int classId) { final ClassId cid = ClassId.values()[classId]; ClassId subClassId; for (SubClass subList : player.getSubClasses().values()) { subClassId = ClassId.values()[subList.getClassId()]; if (subClassId.equalsOrChildOf(cid)) { return false; } } // get player base class final int currentBaseId = player.getBaseClass(); final ClassId baseCID = ClassId.getClassId(currentBaseId); // we need 2nd occupation ID final int baseClassId = baseCID.level() > 2 ? baseCID.getParent().ordinal() : currentBaseId; final Set<PlayerClass> availSubs = getSubclasses(player, baseClassId); if ((availSubs == null) || availSubs.isEmpty()) { return false; } boolean found = false; for (PlayerClass pclass : availSubs) { if (pclass.ordinal() == classId) { found = true; break; } } return found; } private boolean hasAllSubclassLeveled(L2PcInstance player) { boolean leveled = true; for (SubClass sub : player.getSubClasses().values()) { if ((sub != null) && (sub.getLevel() < 75)) { leveled = false; } } return leveled; } public final List<PlayerClass> getAvailableDualclasses(L2PcInstance player) { final List<PlayerClass> dualClasses = new ArrayList<>(); for (PlayerClass playerClass : PlayerClass.values()) { if (!playerClass.isOfRace(Race.ERTHEIA) && playerClass.isOfLevel(ClassLevel.AWAKEN) && (playerClass.ordinal() != player.getClassId().getId())) { dualClasses.add(playerClass); } } return dualClasses; } private List<PlayerClass> getDualClasses(L2PcInstance player, CategoryType cType) { final List<PlayerClass> tempList = new ArrayList<>(); final int baseClassId = player.getBaseClass(); final int dualClassId = player.getClassId().getId(); for (PlayerClass temp : dualClassList) { if ((temp.ordinal() != baseClassId) && (temp.ordinal() != dualClassId) && ((cType == null) || CategoryData.getInstance().isInCategory(cType, temp.ordinal()))) { tempList.add(temp); } } return tempList; } public final Set<PlayerClass> getSubclasses(L2PcInstance player, int classId) { Set<PlayerClass> subclasses = null; final PlayerClass pClass = PlayerClass.values()[classId]; if ((pClass.getLevel() == ClassLevel.THIRD) || (pClass.getLevel() == ClassLevel.FOURTH)) { subclasses = EnumSet.copyOf(mainSubclassSet); subclasses.remove(this); subclasses.removeAll(PlayerClass.getSet(Race.ERTHEIA, THIRD)); if (player.getRace() == Race.KAMAEL) { if (player.getAppearance().getSex()) { subclasses.remove(PlayerClass.femaleSoulbreaker); } else { subclasses.remove(PlayerClass.maleSoulbreaker); } if (!player.getSubClasses().containsKey(2) || (player.getSubClasses().get(2).getLevel() < 75)) { subclasses.remove(PlayerClass.inspector); } } else { // Only Kamael can take Kamael classes as subclasses. subclasses.removeAll(PlayerClass.getSet(Race.KAMAEL, THIRD)); } Set<PlayerClass> unavailableClasses = subclassSetMap.get(this); if (unavailableClasses != null) { subclasses.removeAll(unavailableClasses); } } return subclasses; } private NpcHtmlMessage getNpcHtmlMessage(L2PcInstance player, L2Npc npc, String fileName) { final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); html.setHtml(HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/scripts/ai/npc/Raina/" + fileName)); return html; } private int getCloakId(L2PcInstance player) { CategoryType catType = null; if (player.isInCategory(CategoryType.SIGEL_GROUP)) { catType = CategoryType.SIGEL_GROUP; } else if (player.isInCategory(CategoryType.TYRR_GROUP)) { catType = CategoryType.TYRR_GROUP; } else if (player.isInCategory(CategoryType.OTHELL_GROUP)) { catType = CategoryType.OTHELL_GROUP; } else if (player.isInCategory(CategoryType.YUL_GROUP)) { catType = CategoryType.YUL_GROUP; } else if (player.isInCategory(CategoryType.FEOH_GROUP)) { catType = CategoryType.FEOH_GROUP; } else if (player.isInCategory(CategoryType.ISS_GROUP)) { catType = CategoryType.ISS_GROUP; } else if (player.isInCategory(CategoryType.WYNN_GROUP)) { catType = CategoryType.WYNN_GROUP; } else if (player.isInCategory(CategoryType.AEORE_GROUP)) { catType = CategoryType.AEORE_GROUP; } return classCloak.get(catType); } public static void main(String[] args) { new Raina(); } }