Skip to content

Instantly share code, notes, and snippets.

@St3eT
Last active June 27, 2017 17:45
Show Gist options
  • Save St3eT/f7dee94d4f897ff20074 to your computer and use it in GitHub Desktop.
Save St3eT/f7dee94d4f897ff20074 to your computer and use it in GitHub Desktop.
Subclass rework
diff --git a/L2J_Server/dist/game/config/Character.properties b/L2J_Server/dist/game/config/Character.properties
index 1e5bcc0..5565c1c 100644
--- a/L2J_Server/dist/game/config/Character.properties
+++ b/L2J_Server/dist/game/config/Character.properties
@@ -206,10 +206,6 @@
# Default: False
AltSubClassWithoutQuests = False
-# Allow player to add/change subclass at all village master
-# Default: False
-AltSubclassEverywhere = False
-
# Allow player to learn transformations without quest.
# Default: False
AltTransformationWithoutQuest = False
@@ -315,6 +311,7 @@
MaxAbnormalStateSuccessRate = 90
# Maximum number of allowed subclasses for every player.
+# Do not use more than 3!
# Default: 3
MaxSubclass = 3
@@ -322,6 +319,10 @@
# Default: 40
BaseSubclassLevel = 40
+# Starting level for dualclasses after reawaking.
+# Default: 85
+BaseDualclassLevel = 85
+
# Maximum subclass level.
# Default: 80
MaxSubclassLevel = 80
diff --git a/L2J_Server/java/com/l2jserver/Config.java b/L2J_Server/java/com/l2jserver/Config.java
index 374cfd2..6788ee7 100644
--- a/L2J_Server/java/com/l2jserver/Config.java
+++ b/L2J_Server/java/com/l2jserver/Config.java
@@ -155,7 +155,6 @@
public static boolean DIVINE_SP_BOOK_NEEDED;
public static boolean ALT_GAME_SKILL_LEARN;
public static boolean ALT_GAME_SUBCLASS_WITHOUT_QUESTS;
- public static boolean ALT_GAME_SUBCLASS_EVERYWHERE;
public static boolean ALLOW_TRANSFORM_WITHOUT_QUEST;
public static int FEE_DELETE_TRANSFER_SKILLS;
public static int FEE_DELETE_SUBCLASS_SKILLS;
@@ -173,6 +172,7 @@
public static int MAX_ABNORMAL_STATE_SUCCESS_RATE;
public static byte MAX_SUBCLASS;
public static byte BASE_SUBCLASS_LEVEL;
+ public static byte BASE_DUALCLASS_LEVEL;
public static byte MAX_SUBCLASS_LEVEL;
public static int MAX_PVTSTORESELL_SLOTS_DWARF;
public static int MAX_PVTSTORESELL_SLOTS_OTHER;
@@ -1499,7 +1499,6 @@
DIVINE_SP_BOOK_NEEDED = Character.getBoolean("DivineInspirationSpBookNeeded", true);
ALT_GAME_SKILL_LEARN = Character.getBoolean("AltGameSkillLearn", false);
ALT_GAME_SUBCLASS_WITHOUT_QUESTS = Character.getBoolean("AltSubClassWithoutQuests", false);
- ALT_GAME_SUBCLASS_EVERYWHERE = Character.getBoolean("AltSubclassEverywhere", false);
RESTORE_SERVITOR_ON_RECONNECT = Character.getBoolean("RestoreServitorOnReconnect", true);
RESTORE_PET_ON_RECONNECT = Character.getBoolean("RestorePetOnReconnect", true);
ALLOW_TRANSFORM_WITHOUT_QUEST = Character.getBoolean("AltTransformationWithoutQuest", false);
@@ -1524,8 +1523,9 @@
MAX_EVASION = Character.getInt("MaxEvasion", 250);
MIN_ABNORMAL_STATE_SUCCESS_RATE = Character.getInt("MinAbnormalStateSuccessRate", 10);
MAX_ABNORMAL_STATE_SUCCESS_RATE = Character.getInt("MaxAbnormalStateSuccessRate", 90);
- MAX_SUBCLASS = Character.getByte("MaxSubclass", (byte) 3);
+ MAX_SUBCLASS = (byte) Math.min(3, Character.getByte("MaxSubclass", (byte) 3));
BASE_SUBCLASS_LEVEL = Character.getByte("BaseSubclassLevel", (byte) 40);
+ BASE_DUALCLASS_LEVEL = Character.getByte("BaseDualclassLevel", (byte) 85);
MAX_SUBCLASS_LEVEL = Character.getByte("MaxSubclassLevel", (byte) 80);
MAX_PVTSTORESELL_SLOTS_DWARF = Character.getInt("MaxPvtStoreSellSlotsDwarf", 4);
MAX_PVTSTORESELL_SLOTS_OTHER = Character.getInt("MaxPvtStoreSellSlotsOther", 3);
@@ -3415,9 +3415,6 @@
break;
case "altsubclasswithoutquests":
ALT_GAME_SUBCLASS_WITHOUT_QUESTS = Boolean.parseBoolean(pValue);
- break;
- case "altsubclasseverywhere":
- ALT_GAME_SUBCLASS_EVERYWHERE = Boolean.parseBoolean(pValue);
break;
case "altmemberscanwithdrawfromclanwh":
ALT_MEMBERS_CAN_WITHDRAW_FROM_CLANWH = Boolean.parseBoolean(pValue);
diff --git a/L2J_Server/java/com/l2jserver/gameserver/data/xml/impl/SkillTreesData.java b/L2J_Server/java/com/l2jserver/gameserver/data/xml/impl/SkillTreesData.java
index 752c9d5..d23fd28 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/data/xml/impl/SkillTreesData.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/data/xml/impl/SkillTreesData.java
@@ -37,6 +37,7 @@
import com.l2jserver.Config;
import com.l2jserver.gameserver.data.xml.IXmlReader;
import com.l2jserver.gameserver.datatables.SkillData;
+import com.l2jserver.gameserver.enums.CategoryType;
import com.l2jserver.gameserver.enums.Race;
import com.l2jserver.gameserver.enums.SubclassType;
import com.l2jserver.gameserver.model.L2Clan;
@@ -657,15 +658,18 @@
return result;
}
- for (L2SkillLearn skill : skills.values())
+ final boolean isAwaken = player.isInCategory(CategoryType.AWAKEN_GROUP);
+
+ for (Entry<Integer, L2SkillLearn> entry : skills.entrySet())
{
- if (((skill.getSkillId() == CommonSkill.DIVINE_INSPIRATION.getId()) && (!Config.AUTO_LEARN_DIVINE_INSPIRATION && includeAutoGet) && !player.isGM()) || (!includeAutoGet && skill.isAutoGet()) || (!includeByFs && skill.isLearnedByFS()))
+ final L2SkillLearn skill = entry.getValue();
+
+ if (((skill.getSkillId() == CommonSkill.DIVINE_INSPIRATION.getId()) && (!Config.AUTO_LEARN_DIVINE_INSPIRATION && includeAutoGet) && !player.isGM()) || (!includeAutoGet && skill.isAutoGet()) || (!includeByFs && skill.isLearnedByFS()) || isRemoveSkill(classId, skill.getSkillId()))
{
continue;
}
- final Set<Integer> removeSkills = _removeSkillCache.get(classId);
- if ((removeSkills != null) && removeSkills.contains(skill.getSkillId()))
+ if (isAwaken && !isCurrentClassSkillNoParent(classId, entry.getKey()))
{
continue;
}
@@ -1349,6 +1353,20 @@
return result;
}
+ public void cleanSkillUponAwakening(L2PcInstance player)
+ {
+ for (Skill skill : player.getAllSkills())
+ {
+ final int maxLvl = SkillData.getInstance().getMaxLevel(skill.getId());
+ final int hashCode = SkillData.getSkillHashCode(skill.getId(), maxLvl);
+
+ if (!isCurrentClassSkillNoParent(player.getClassId(), hashCode) && !isRemoveSkill(player.getClassId(), skill.getId()))
+ {
+ player.removeSkill(skill, true, true);
+ }
+ }
+ }
+
/**
* Checks if is hero skill.
* @param skillId the Id of the skill to check
@@ -1421,6 +1439,16 @@
return _subClassChangeSkillTree.containsKey(SkillData.getSkillHashCode(skillId, skillLevel));
}
+ public boolean isRemoveSkill(ClassId classId, int skillId)
+ {
+ return _removeSkillCache.getOrDefault(classId, Collections.emptySet()).contains(skillId);
+ }
+
+ public boolean isCurrentClassSkillNoParent(ClassId classId, Integer hashCode)
+ {
+ return _classSkillTrees.getOrDefault(classId, Collections.emptyMap()).containsKey(hashCode);
+ }
+
/**
* Adds the skills.
* @param gmchar the player to add the Game Master skills
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java
index d6feeae..e2307b3 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java
@@ -2690,6 +2690,14 @@
Skill skill;
for (L2SkillLearn s : autoGetSkills)
{
+ final int maxLvl = SkillData.getInstance().getMaxLevel(s.getSkillId());
+ final int hashCode = SkillData.getSkillHashCode(s.getSkillId(), maxLvl);
+
+ if (SkillTreesData.getInstance().isCurrentClassSkillNoParent(getClassId(), hashCode) || SkillTreesData.getInstance().isRemoveSkill(getClassId(), s.getSkillId()))
+ {
+ continue;
+ }
+
skill = st.getSkill(s.getSkillId(), s.getSkillLevel());
if (skill != null)
{
@@ -10223,9 +10231,10 @@
* 2. This method no longer changes the active _classIndex of the player. This is only done by the calling of setActiveClass() method as that should be the only way to do so.
* @param classId
* @param classIndex
+ * @param isDualClass
* @return boolean subclassAdded
*/
- public boolean addSubClass(int classId, int classIndex)
+ public boolean addSubClass(int classId, int classIndex, boolean isDualClass)
{
if (!_subclassLock.tryLock())
{
@@ -10246,9 +10255,15 @@
// Note: Never change _classIndex in any method other than setActiveClass().
- SubClass newClass = new SubClass();
+ final SubClass newClass = new SubClass();
newClass.setClassId(classId);
newClass.setClassIndex(classIndex);
+ if (isDualClass)
+ {
+ newClass.setIsDualClass(true);
+ newClass.setExp(ExperienceData.getInstance().getExpForLevel(Config.BASE_DUALCLASS_LEVEL));
+ newClass.setLevel(Config.BASE_DUALCLASS_LEVEL);
+ }
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement(ADD_CHAR_SUBCLASS))
@@ -10277,12 +10292,12 @@
final Map<Integer, Skill> prevSkillList = new HashMap<>();
for (L2SkillLearn skillInfo : skillTree.values())
{
- if (skillInfo.getGetLevel() <= 40)
+ if (skillInfo.getGetLevel() <= newClass.getLevel())
{
- Skill prevSkill = prevSkillList.get(skillInfo.getSkillId());
- Skill newSkill = SkillData.getInstance().getSkill(skillInfo.getSkillId(), skillInfo.getSkillLevel());
+ final Skill prevSkill = prevSkillList.get(skillInfo.getSkillId());
+ final Skill newSkill = SkillData.getInstance().getSkill(skillInfo.getSkillId(), skillInfo.getSkillLevel());
- if ((prevSkill != null) && (prevSkill.getLevel() > newSkill.getLevel()))
+ if (((prevSkill != null) && (prevSkill.getLevel() > newSkill.getLevel())) || SkillTreesData.getInstance().isRemoveSkill(subTemplate, skillInfo.getSkillId()))
{
continue;
}
@@ -10305,9 +10320,10 @@
* 3. Upon Exception, revert the player to their BaseClass to avoid further problems.
* @param classIndex the class index to delete
* @param newClassId the new class Id
+ * @param isDualClass is subclass dualclass
* @return {@code true} if the sub-class was modified, {@code false} otherwise
*/
- public boolean modifySubClass(int classIndex, int newClassId)
+ public boolean modifySubClass(int classIndex, int newClassId, boolean isDualClass)
{
if (!_subclassLock.tryLock())
{
@@ -10363,7 +10379,7 @@
_subclassLock.unlock();
}
- return addSubClass(newClassId, classIndex);
+ return addSubClass(newClassId, classIndex, isDualClass);
}
public boolean isSubClassActive()
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterInstance.java b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterInstance.java
index 32bec3f..68d6d42 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterInstance.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterInstance.java
@@ -18,19 +18,15 @@
*/
package com.l2jserver.gameserver.model.actor.instance;
-import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import com.l2jserver.Config;
import com.l2jserver.gameserver.data.sql.impl.ClanTable;
-import com.l2jserver.gameserver.data.xml.impl.ClassListData;
import com.l2jserver.gameserver.data.xml.impl.SkillTreesData;
import com.l2jserver.gameserver.enums.InstanceType;
-import com.l2jserver.gameserver.enums.Race;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.FortManager;
import com.l2jserver.gameserver.instancemanager.FortSiegeManager;
@@ -41,12 +37,9 @@
import com.l2jserver.gameserver.model.L2SkillLearn;
import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jserver.gameserver.model.base.AcquireSkillType;
-import com.l2jserver.gameserver.model.base.ClassId;
import com.l2jserver.gameserver.model.base.PlayerClass;
-import com.l2jserver.gameserver.model.base.SubClass;
import com.l2jserver.gameserver.model.entity.Castle;
import com.l2jserver.gameserver.model.entity.Fort;
-import com.l2jserver.gameserver.model.quest.QuestState;
import com.l2jserver.gameserver.model.zone.ZoneId;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
@@ -57,7 +50,6 @@
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.network.serverpackets.UserInfo;
import com.l2jserver.gameserver.util.Util;
-import com.l2jserver.util.StringUtil;
/**
* This class ...
@@ -294,560 +286,10 @@
{
showPledgeSkillList(player);
}
- else if (command.startsWith("Subclass"))
- {
- // Subclasses may not be changed while a skill is in use.
- if (player.isCastingNow() || player.isAllSkillsDisabled())
- {
- player.sendPacket(SystemMessageId.SUBCLASSES_MAY_NOT_BE_CREATED_OR_CHANGED_WHILE_A_SKILL_IS_IN_USE);
- return;
- }
- final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
- // Subclasses may not be changed while a transformated state.
- if (player.getTransformation() != null)
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_NoTransformed.htm");
- player.sendPacket(html);
- return;
- }
- // Subclasses may not be changed while a summon is active.
- if (player.hasSummon())
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_NoSummon.htm");
- player.sendPacket(html);
- return;
- }
- // Subclasses may not be changed while you have exceeded your inventory limit.
- if (!player.isInventoryUnder90(true))
- {
- player.sendPacket(SystemMessageId.A_SUBCLASS_CANNOT_BE_CREATED_OR_CHANGED_BECAUSE_YOU_HAVE_EXCEEDED_YOUR_INVENTORY_LIMIT);
- return;
- }
- // Subclasses may not be changed while a you are over your weight limit.
- if (player.getWeightPenalty() >= 2)
- {
- player.sendPacket(SystemMessageId.A_SUBCLASS_CANNOT_BE_CREATED_OR_CHANGED_WHILE_YOU_ARE_OVER_YOUR_WEIGHT_LIMIT);
- return;
- }
-
- int cmdChoice = 0;
- int paramOne = 0;
- int paramTwo = 0;
- try
- {
- cmdChoice = Integer.parseInt(command.substring(9, 10).trim());
-
- int endIndex = command.indexOf(' ', 11);
- if (endIndex == -1)
- {
- endIndex = command.length();
- }
-
- if (command.length() > 11)
- {
- paramOne = Integer.parseInt(command.substring(11, endIndex).trim());
- if (command.length() > endIndex)
- {
- paramTwo = Integer.parseInt(command.substring(endIndex).trim());
- }
- }
- }
- catch (Exception NumberFormatException)
- {
- _log.warning(L2VillageMasterInstance.class.getName() + ": Wrong numeric values for command " + command);
- }
-
- Set<PlayerClass> subsAvailable = null;
- switch (cmdChoice)
- {
- case 0: // Subclass change menu
- html.setFile(player.getHtmlPrefix(), getSubClassMenu(player.getRace()));
- break;
- case 1: // Add Subclass - Initial
- // Avoid giving player an option to add a new sub class, if they have max sub-classes already.
- if (player.getTotalSubClasses() >= Config.MAX_SUBCLASS)
- {
- html.setFile(player.getHtmlPrefix(), getSubClassFail());
- break;
- }
-
- subsAvailable = getAvailableSubClasses(player);
- if ((subsAvailable != null) && !subsAvailable.isEmpty())
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Add.htm");
- final StringBuilder content1 = StringUtil.startAppend(200);
- for (PlayerClass subClass : subsAvailable)
- {
- StringUtil.append(content1, "<a action=\"bypass -h npc_%objectId%_Subclass 4 ", String.valueOf(subClass.ordinal()), "\" msg=\"1268;", ClassListData.getInstance().getClass(subClass.ordinal()).getClassName(), "\">", ClassListData.getInstance().getClass(subClass.ordinal()).getClientCode(), "</a><br>");
- }
- html.replace("%list%", content1.toString());
- }
- else
- {
- if ((player.getRace() == Race.ELF) || (player.getRace() == Race.DARK_ELF))
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Fail_Elves.htm");
- player.sendPacket(html);
- }
- else if (player.getRace() == Race.KAMAEL)
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Fail_Kamael.htm");
- player.sendPacket(html);
- }
- else
- {
- // TODO: Retail message
- player.sendMessage("There are no sub classes available at this time.");
- }
- return;
- }
- break;
- case 2: // Change Class - Initial
- if (player.getSubClasses().isEmpty())
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ChangeNo.htm");
- }
- else
- {
- final StringBuilder content2 = StringUtil.startAppend(200);
- if (checkVillageMaster(player.getBaseClass()))
- {
- StringUtil.append(content2, "<a action=\"bypass -h npc_%objectId%_Subclass 5 0\">", ClassListData.getInstance().getClass(player.getBaseClass()).getClientCode(), "</a><br>");
- }
-
- for (Iterator<SubClass> subList = iterSubClasses(player); subList.hasNext();)
- {
- SubClass subClass = subList.next();
- if (checkVillageMaster(subClass.getClassDefinition()))
- {
- StringUtil.append(content2, "<a action=\"bypass -h npc_%objectId%_Subclass 5 ", String.valueOf(subClass.getClassIndex()), "\">", ClassListData.getInstance().getClass(subClass.getClassId()).getClientCode(), "</a><br>");
- }
- }
-
- if (content2.length() > 0)
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Change.htm");
- html.replace("%list%", content2.toString());
- }
- else
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ChangeNotFound.htm");
- }
- }
- break;
- case 3: // Change/Cancel Subclass - Initial
- if ((player.getSubClasses() == null) || player.getSubClasses().isEmpty())
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyEmpty.htm");
- break;
- }
-
- // custom value
- if (player.getTotalSubClasses() > 3)
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyCustom.htm");
- final StringBuilder content3 = StringUtil.startAppend(200);
- int classIndex = 1;
-
- for (Iterator<SubClass> subList = iterSubClasses(player); subList.hasNext();)
- {
- SubClass subClass = subList.next();
-
- StringUtil.append(content3, "Sub-class ", String.valueOf(classIndex++), "<br>", "<a action=\"bypass -h npc_%objectId%_Subclass 6 ", String.valueOf(subClass.getClassIndex()), "\">", ClassListData.getInstance().getClass(subClass.getClassId()).getClientCode(), "</a><br>");
- }
- html.replace("%list%", content3.toString());
- }
- else
- {
- // retail html contain only 3 subclasses
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Modify.htm");
- if (player.getSubClasses().containsKey(1))
- {
- html.replace("%sub1%", ClassListData.getInstance().getClass(player.getSubClasses().get(1).getClassId()).getClientCode());
- }
- else
- {
- html.replace("<a action=\"bypass -h npc_%objectId%_Subclass 6 1\">%sub1%</a><br>", "");
- }
-
- if (player.getSubClasses().containsKey(2))
- {
- html.replace("%sub2%", ClassListData.getInstance().getClass(player.getSubClasses().get(2).getClassId()).getClientCode());
- }
- else
- {
- html.replace("<a action=\"bypass -h npc_%objectId%_Subclass 6 2\">%sub2%</a><br>", "");
- }
-
- if (player.getSubClasses().containsKey(3))
- {
- html.replace("%sub3%", ClassListData.getInstance().getClass(player.getSubClasses().get(3).getClassId()).getClientCode());
- }
- else
- {
- html.replace("<a action=\"bypass -h npc_%objectId%_Subclass 6 3\">%sub3%</a><br>", "");
- }
- }
- break;
- case 4: // Add Subclass - Action (Subclass 4 x[x])
- /**
- * If the character is less than level 75 on any of their previously chosen classes then disallow them to change to their most recently added sub-class choice.
- */
- if (!player.getFloodProtectors().getSubclass().tryPerformAction("add subclass"))
- {
- _log.warning(L2VillageMasterInstance.class.getName() + ": Player " + player.getName() + " has performed a subclass change too fast");
- return;
- }
-
- boolean allowAddition = true;
-
- if (player.getTotalSubClasses() >= Config.MAX_SUBCLASS)
- {
- allowAddition = false;
- }
-
- if (player.getLevel() < 75)
- {
- allowAddition = false;
- }
-
- if (allowAddition)
- {
- if (!player.getSubClasses().isEmpty())
- {
- for (Iterator<SubClass> subList = iterSubClasses(player); subList.hasNext();)
- {
- SubClass subClass = subList.next();
-
- if (subClass.getLevel() < 75)
- {
- allowAddition = false;
- break;
- }
- }
- }
- }
-
- /**
- * If quest checking is enabled, verify if the character has completed the Mimir's Elixir (Path to Subclass) and Fate's Whisper (A Grade Weapon) quests by checking for instances of their unique reward items. If they both exist, remove both unique items and continue with adding
- * the sub-class.
- */
- if (allowAddition && !Config.ALT_GAME_SUBCLASS_WITHOUT_QUESTS)
- {
- allowAddition = checkQuests(player);
- }
-
- if (allowAddition && isValidNewSubClass(player, paramOne))
- {
- if (!player.addSubClass(paramOne, player.getTotalSubClasses() + 1))
- {
- return;
- }
-
- player.setActiveClass(player.getTotalSubClasses());
-
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_AddOk.htm");
-
- player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED); // Subclass added.
- }
- else
- {
- html.setFile(player.getHtmlPrefix(), getSubClassFail());
- }
- break;
- case 5: // Change Class - Action
- /**
- * If the character is less than level 75 on any of their previously chosen classes then disallow them to change to their most recently added sub-class choice. Note: paramOne = classIndex
- */
- if (!player.getFloodProtectors().getSubclass().tryPerformAction("change class"))
- {
- _log.warning(L2VillageMasterInstance.class.getName() + ": Player " + player.getName() + " has performed a subclass change too fast");
- return;
- }
-
- if (player.getClassIndex() == paramOne)
- {
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_Current.htm");
- break;
- }
-
- if (paramOne == 0)
- {
- if (!checkVillageMaster(player.getBaseClass()))
- {
- return;
- }
- }
- else
- {
- try
- {
- if (!checkVillageMaster(player.getSubClasses().get(paramOne).getClassDefinition()))
- {
- return;
- }
- }
- catch (NullPointerException e)
- {
- return;
- }
- }
-
- final int fromClassId = player.getClassId().getId();
- player.setActiveClass(paramOne);
- final SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_SUCCESSFULLY_SWITCHED_S1_TO_S2);
- msg.addClassId(fromClassId);
- msg.addClassId(player.getClassId().getId());
- player.sendPacket(msg);
- return;
- case 6: // Change/Cancel Subclass - Choice
- // validity check
- if ((paramOne < 1) || (paramOne > Config.MAX_SUBCLASS))
- {
- return;
- }
-
- subsAvailable = getAvailableSubClasses(player);
- // another validity check
- if ((subsAvailable == null) || subsAvailable.isEmpty())
- {
- // TODO: Retail message
- player.sendMessage("There are no sub classes available at this time.");
- return;
- }
-
- final StringBuilder content6 = StringUtil.startAppend(200);
- for (PlayerClass subClass : subsAvailable)
- {
- StringUtil.append(content6, "<a action=\"bypass -h npc_%objectId%_Subclass 7 ", String.valueOf(paramOne), " ", String.valueOf(subClass.ordinal()), "\" msg=\"1445;", "\">", ClassListData.getInstance().getClass(subClass.ordinal()).getClientCode(), "</a><br>");
- }
-
- switch (paramOne)
- {
- case 1:
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyChoice1.htm");
- break;
- case 2:
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyChoice2.htm");
- break;
- case 3:
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyChoice3.htm");
- break;
- default:
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyChoice.htm");
- }
- html.replace("%list%", content6.toString());
- break;
- case 7: // Change Subclass - Action
- /**
- * Warning: the information about this subclass will be removed from the subclass list even if false!
- */
- if (!player.getFloodProtectors().getSubclass().tryPerformAction("change class"))
- {
- _log.warning(L2VillageMasterInstance.class.getName() + ": Player " + player.getName() + " has performed a subclass change too fast");
- return;
- }
-
- if (!isValidNewSubClass(player, paramTwo))
- {
- return;
- }
-
- if (player.modifySubClass(paramOne, paramTwo))
- {
- player.abortCast();
- player.stopAllEffectsExceptThoseThatLastThroughDeath(); // all effects from old subclass stopped!
- player.stopAllEffectsNotStayOnSubclassChange();
- player.stopCubics();
- player.setActiveClass(paramOne);
-
- html.setFile(player.getHtmlPrefix(), "data/html/villagemaster/SubClass_ModifyOk.htm");
- html.replace("%name%", ClassListData.getInstance().getClass(paramTwo).getClientCode());
-
- player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED); // Subclass added.
- }
- else
- {
- /**
- * This isn't good! modifySubClass() removed subclass from memory we must update _classIndex! Else IndexOutOfBoundsException can turn up some place down the line along with other seemingly unrelated problems.
- */
- player.setActiveClass(0); // Also updates _classIndex plus switching _classid to baseclass.
-
- player.sendMessage("The sub class could not be added, you have been reverted to your base class.");
- return;
- }
- break;
- }
- html.replace("%objectId%", String.valueOf(getObjectId()));
- player.sendPacket(html);
- }
else
{
super.onBypassFeedback(player, command);
}
- }
-
- protected String getSubClassMenu(Race race)
- {
- if (Config.ALT_GAME_SUBCLASS_EVERYWHERE || (race != Race.KAMAEL))
- {
- return "data/html/villagemaster/SubClass.htm";
- }
-
- return "data/html/villagemaster/SubClass_NoOther.htm";
- }
-
- protected String getSubClassFail()
- {
- return "data/html/villagemaster/SubClass_Fail.htm";
- }
-
- protected boolean checkQuests(L2PcInstance player)
- {
- // Noble players can add Sub-Classes without quests
- if (player.isNoble())
- {
- return true;
- }
-
- QuestState qs = player.getQuestState("234_FatesWhisper");
- if ((qs == null) || !qs.isCompleted())
- {
- return false;
- }
-
- qs = player.getQuestState("Q00235_MimirsElixir");
- if ((qs == null) || !qs.isCompleted())
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns list of available subclasses Base class and already used subclasses removed
- * @param player
- * @return
- */
- private final Set<PlayerClass> getAvailableSubClasses(L2PcInstance player)
- {
- // get player base class
- final int currentBaseId = player.getBaseClass();
- final ClassId baseCID = ClassId.getClassId(currentBaseId);
-
- // we need 2nd occupation ID
- final int baseClassId;
- if (baseCID.level() > 2)
- {
- baseClassId = baseCID.getParent().ordinal();
- }
- else
- {
- baseClassId = currentBaseId;
- }
-
- /**
- * If the race of your main class is Elf or Dark Elf, you may not select each class as a subclass to the other class. If the race of your main class is Kamael, you may not subclass any other race If the race of your main class is NOT Kamael, you may not subclass any Kamael class You may not
- * select Overlord and Warsmith class as a subclass. You may not select a similar class as the subclass. The occupations classified as similar classes are as follows: Treasure Hunter, Plainswalker and Abyss Walker Hawkeye, Silver Ranger and Phantom Ranger Paladin, Dark Avenger, Temple Knight
- * and Shillien Knight Warlocks, Elemental Summoner and Phantom Summoner Elder and Shillien Elder Swordsinger and Bladedancer Sorcerer, Spellsinger and Spellhowler Also, Kamael have a special, hidden 4 subclass, the inspector, which can only be taken if you have already completed the other
- * two Kamael subclasses
- */
- Set<PlayerClass> availSubs = PlayerClass.values()[baseClassId].getAvailableSubclasses(player);
-
- if ((availSubs != null) && !availSubs.isEmpty())
- {
- for (Iterator<PlayerClass> availSub = availSubs.iterator(); availSub.hasNext();)
- {
- PlayerClass pclass = availSub.next();
-
- // check for the village master
- if (!checkVillageMaster(pclass))
- {
- availSub.remove();
- continue;
- }
-
- // scan for already used subclasses
- int availClassId = pclass.ordinal();
- ClassId cid = ClassId.getClassId(availClassId);
- SubClass prevSubClass;
- ClassId subClassId;
- for (Iterator<SubClass> subList = iterSubClasses(player); subList.hasNext();)
- {
- prevSubClass = subList.next();
- subClassId = ClassId.getClassId(prevSubClass.getClassId());
-
- if (subClassId.equalsOrChildOf(cid))
- {
- availSub.remove();
- break;
- }
- }
- }
- }
-
- return availSubs;
- }
-
- /**
- * Check new subclass classId for validity (villagemaster race/type, is not contains in previous subclasses, is contains in allowed subclasses) Base class not added into allowed subclasses.
- * @param player
- * @param classId
- * @return
- */
- private final boolean isValidNewSubClass(L2PcInstance player, int classId)
- {
- if (!checkVillageMaster(classId))
- {
- return false;
- }
-
- final ClassId cid = ClassId.values()[classId];
- SubClass sub;
- ClassId subClassId;
- for (Iterator<SubClass> subList = iterSubClasses(player); subList.hasNext();)
- {
- sub = subList.next();
- subClassId = ClassId.values()[sub.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;
- if (baseCID.level() > 2)
- {
- baseClassId = baseCID.getParent().ordinal();
- }
- else
- {
- baseClassId = currentBaseId;
- }
-
- Set<PlayerClass> availSubs = PlayerClass.values()[baseClassId].getAvailableSubclasses(player);
- if ((availSubs == null) || availSubs.isEmpty())
- {
- return false;
- }
-
- boolean found = false;
- for (PlayerClass pclass : availSubs)
- {
- if (pclass.ordinal() == classId)
- {
- found = true;
- break;
- }
- }
- return found;
}
protected boolean checkVillageMasterRace(PlayerClass pclass)
@@ -877,17 +319,7 @@
*/
public final boolean checkVillageMaster(PlayerClass pclass)
{
- if (Config.ALT_GAME_SUBCLASS_EVERYWHERE)
- {
- return true;
- }
-
return checkVillageMasterRace(pclass) && checkVillageMasterTeachType(pclass);
- }
-
- private static final Iterator<SubClass> iterSubClasses(L2PcInstance player)
- {
- return player.getSubClasses().values().iterator();
}
private static final void dissolveClan(L2PcInstance player, int clanId)
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterKamaelInstance.java b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterKamaelInstance.java
index 3bc84af..c0dbed0 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterKamaelInstance.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/actor/instance/L2VillageMasterKamaelInstance.java
@@ -18,58 +18,15 @@
*/
package com.l2jserver.gameserver.model.actor.instance;
-import com.l2jserver.Config;
import com.l2jserver.gameserver.enums.Race;
import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jserver.gameserver.model.base.PlayerClass;
-import com.l2jserver.gameserver.model.quest.QuestState;
public final class L2VillageMasterKamaelInstance extends L2VillageMasterInstance
{
public L2VillageMasterKamaelInstance(int objectId, L2NpcTemplate template)
{
super(objectId, template);
- }
-
- @Override
- protected final String getSubClassMenu(Race race)
- {
- if (Config.ALT_GAME_SUBCLASS_EVERYWHERE || (race == Race.KAMAEL))
- {
- return "data/html/villagemaster/SubClass.htm";
- }
-
- return "data/html/villagemaster/SubClass_NoKamael.htm";
- }
-
- @Override
- protected final String getSubClassFail()
- {
- return "data/html/villagemaster/SubClass_Fail_Kamael.htm";
- }
-
- @Override
- protected final boolean checkQuests(L2PcInstance player)
- {
- // Noble players can add subbclasses without quests
- if (player.isNoble())
- {
- return true;
- }
-
- QuestState qs = player.getQuestState("234_FatesWhisper");
- if ((qs == null) || !qs.isCompleted())
- {
- return false;
- }
-
- qs = player.getQuestState("236_SeedsOfChaos");
- if ((qs == null) || !qs.isCompleted())
- {
- return false;
- }
-
- return true;
}
@Override
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/base/PlayerClass.java b/L2J_Server/java/com/l2jserver/gameserver/model/base/PlayerClass.java
index 6bdcd36..632f603 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/model/base/PlayerClass.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/base/PlayerClass.java
@@ -26,12 +26,9 @@
import static com.l2jserver.gameserver.model.base.ClassType.Mystic;
import static com.l2jserver.gameserver.model.base.ClassType.Priest;
-import java.util.EnumMap;
import java.util.EnumSet;
-import java.util.Set;
import com.l2jserver.gameserver.enums.Race;
-import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
/**
* @author luisantonioa
@@ -208,20 +205,20 @@
sigelHellKnight(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
sigelEvasTemplar(Race.ELF, Fighter, ClassLevel.AWAKEN),
sigelShilenTemplar(Race.DARK_ELF, Fighter, ClassLevel.AWAKEN),
- tyrDuelist(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
- tyrDreadnought(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
- tyrTitan(Race.ORC, Fighter, ClassLevel.AWAKEN),
- tyrGrandKhavatari(Race.ORC, Fighter, ClassLevel.AWAKEN),
- tyrMaestro(Race.DWARF, Fighter, ClassLevel.AWAKEN),
- tyrDoombringer(Race.KAMAEL, Fighter, ClassLevel.AWAKEN),
+ tyrrDuelist(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
+ tyrrDreadnought(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
+ tyrrTitan(Race.ORC, Fighter, ClassLevel.AWAKEN),
+ tyrrGrandKhavatari(Race.ORC, Fighter, ClassLevel.AWAKEN),
+ tyrrMaestro(Race.DWARF, Fighter, ClassLevel.AWAKEN),
+ tyrrDoombringer(Race.KAMAEL, Fighter, ClassLevel.AWAKEN),
othellAdventurer(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
othellWindRider(Race.ELF, Fighter, ClassLevel.AWAKEN),
othellGhostHunter(Race.DARK_ELF, Fighter, ClassLevel.AWAKEN),
othellFortuneSeeker(Race.DWARF, Fighter, ClassLevel.AWAKEN),
- yrSagittarius(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
- yrMoonlightSentinel(Race.ELF, Fighter, ClassLevel.AWAKEN),
- yrGhostSentinel(Race.DARK_ELF, Fighter, ClassLevel.AWAKEN),
- yrTrickster(Race.KAMAEL, Fighter, ClassLevel.AWAKEN),
+ yulSagittarius(Race.HUMAN, Fighter, ClassLevel.AWAKEN),
+ yulMoonlightSentinel(Race.ELF, Fighter, ClassLevel.AWAKEN),
+ yulGhostSentinel(Race.DARK_ELF, Fighter, ClassLevel.AWAKEN),
+ yulTrickster(Race.KAMAEL, Fighter, ClassLevel.AWAKEN),
feohArchmage(Race.HUMAN, Mystic, ClassLevel.AWAKEN),
feohSoultaker(Race.HUMAN, Mystic, ClassLevel.AWAKEN),
feohMysticMuse(Race.ELF, Mystic, ClassLevel.AWAKEN),
@@ -235,9 +232,9 @@
wynnArcanaLord(Race.HUMAN, Mystic, ClassLevel.AWAKEN),
wynnElementalMaster(Race.ELF, Mystic, ClassLevel.AWAKEN),
wynnSpectralMaster(Race.DARK_ELF, Mystic, ClassLevel.AWAKEN),
- eolhCardinal(Race.HUMAN, Priest, ClassLevel.AWAKEN),
- eolhEvaSaint(Race.ELF, Priest, ClassLevel.AWAKEN),
- eolhShillienSaint(Race.DARK_ELF, Priest, ClassLevel.AWAKEN),
+ aeoreCardinal(Race.HUMAN, Priest, ClassLevel.AWAKEN),
+ aeoreEvaSaint(Race.ELF, Priest, ClassLevel.AWAKEN),
+ aeoreShillienSaint(Race.DARK_ELF, Priest, ClassLevel.AWAKEN),
ertheiaFighter(Race.ERTHEIA, Fighter, ClassLevel.FIRST),
ertheiaWizzard(Race.ERTHEIA, Mystic, ClassLevel.FIRST),
@@ -255,95 +252,11 @@
private ClassLevel _level;
private ClassType _type;
- private static final Set<PlayerClass> mainSubclassSet;
- private static final Set<PlayerClass> neverSubclassed = EnumSet.of(Overlord, Warsmith);
-
- private static final Set<PlayerClass> subclasseSet1 = EnumSet.of(DarkAvenger, Paladin, TempleKnight, ShillienKnight);
- private static final Set<PlayerClass> subclasseSet2 = EnumSet.of(TreasureHunter, AbyssWalker, Plainswalker);
- private static final Set<PlayerClass> subclasseSet3 = EnumSet.of(Hawkeye, SilverRanger, PhantomRanger);
- private static final Set<PlayerClass> subclasseSet4 = EnumSet.of(Warlock, ElementalSummoner, PhantomSummoner);
- private static final Set<PlayerClass> subclasseSet5 = EnumSet.of(Sorceror, Spellsinger, Spellhowler);
-
- private static final EnumMap<PlayerClass, Set<PlayerClass>> subclassSetMap = new EnumMap<>(PlayerClass.class);
-
- static
- {
- Set<PlayerClass> subclasses = getSet(null, THIRD);
- subclasses.removeAll(neverSubclassed);
-
- mainSubclassSet = subclasses;
-
- subclassSetMap.put(DarkAvenger, subclasseSet1);
- subclassSetMap.put(Paladin, subclasseSet1);
- subclassSetMap.put(TempleKnight, subclasseSet1);
- subclassSetMap.put(ShillienKnight, subclasseSet1);
-
- subclassSetMap.put(TreasureHunter, subclasseSet2);
- subclassSetMap.put(AbyssWalker, subclasseSet2);
- subclassSetMap.put(Plainswalker, subclasseSet2);
-
- subclassSetMap.put(Hawkeye, subclasseSet3);
- subclassSetMap.put(SilverRanger, subclasseSet3);
- subclassSetMap.put(PhantomRanger, subclasseSet3);
-
- subclassSetMap.put(Warlock, subclasseSet4);
- subclassSetMap.put(ElementalSummoner, subclasseSet4);
- subclassSetMap.put(PhantomSummoner, subclasseSet4);
-
- subclassSetMap.put(Sorceror, subclasseSet5);
- subclassSetMap.put(Spellsinger, subclasseSet5);
- subclassSetMap.put(Spellhowler, subclasseSet5);
- }
-
private PlayerClass(Race race, ClassType pType, ClassLevel pLevel)
{
_race = race;
_level = pLevel;
_type = pType;
- }
-
- public final Set<PlayerClass> getAvailableSubclasses(L2PcInstance player)
- {
- Set<PlayerClass> subclasses = null;
-
- if (_level == THIRD)
- {
- subclasses = EnumSet.copyOf(mainSubclassSet);
-
- subclasses.remove(this);
-
- subclasses.removeAll(getSet(Race.ERTHEIA, THIRD));
-
- if (player.getRace() == Race.KAMAEL)
- {
- if (player.getAppearance().getSex())
- {
- subclasses.remove(femaleSoulbreaker);
- }
- else
- {
- subclasses.remove(maleSoulbreaker);
- }
-
- if (!player.getSubClasses().containsKey(2) || (player.getSubClasses().get(2).getLevel() < 75))
- {
- subclasses.remove(inspector);
- }
- }
- else
- {
- // Only Kamael can take Kamael classes as subclasses.
- subclasses.removeAll(getSet(Race.KAMAEL, THIRD));
- }
-
- Set<PlayerClass> unavailableClasses = subclassSetMap.get(this);
-
- if (unavailableClasses != null)
- {
- subclasses.removeAll(unavailableClasses);
- }
- }
- return subclasses;
}
public static final EnumSet<PlayerClass> getSet(Race race, ClassLevel level)
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/events/EventType.java b/L2J_Server/java/com/l2jserver/gameserver/model/events/EventType.java
index 3b91553..5ed3979 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/model/events/EventType.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/events/EventType.java
@@ -34,6 +34,7 @@
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcEventReceived;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcFirstTalk;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcManorBypass;
+import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMenuSelect;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMoveFinished;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMoveNodeArrived;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMoveRouteFinished;
@@ -168,6 +169,7 @@
ON_NPC_TALK(null, void.class),
ON_NPC_TELEPORT(OnNpcTeleport.class, void.class),
ON_NPC_MANOR_BYPASS(OnNpcManorBypass.class, void.class),
+ ON_NPC_MENU_SELECT(OnNpcMenuSelect.class, void.class),
// Olympiad events
ON_OLYMPIAD_MATCH_RESULT(OnOlympiadMatchResult.class, void.class),
diff --git a/L2J_Server/java/com/l2jserver/gameserver/model/events/impl/character/npc/OnNpcMenuSelect.java b/L2J_Server/java/com/l2jserver/gameserver/model/events/impl/character/npc/OnNpcMenuSelect.java
new file mode 100644
index 0000000..1ede647
--- /dev/null
+++ b/L2J_Server/java/com/l2jserver/gameserver/model/events/impl/character/npc/OnNpcMenuSelect.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004-2015 L2J Server
+ *
+ * This file is part of L2J Server.
+ *
+ * L2J Server 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 Server 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 com.l2jserver.gameserver.model.events.impl.character.npc;
+
+import com.l2jserver.gameserver.model.actor.L2Npc;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.model.events.EventType;
+import com.l2jserver.gameserver.model.events.impl.IBaseEvent;
+
+/**
+ * @author St3eT
+ */
+public final class OnNpcMenuSelect implements IBaseEvent
+{
+ private final L2Npc _npc;
+ private final L2PcInstance _talker;
+ private final int _ask;
+ private final int _reply;
+
+ public OnNpcMenuSelect(L2PcInstance talker, L2Npc npc, int ask, int reply)
+ {
+ _talker = talker;
+ _npc = npc;
+ _ask = ask;
+ _reply = reply;
+ }
+
+ public L2PcInstance getTalker()
+ {
+ return _talker;
+ }
+
+ public L2Npc getNpc()
+ {
+ return _npc;
+ }
+
+ public int getAsk()
+ {
+ return _ask;
+ }
+
+ public int getReply()
+ {
+ return _reply;
+ }
+
+ @Override
+ public EventType getType()
+ {
+ return EventType.ON_NPC_MENU_SELECT;
+ }
+}
diff --git a/L2J_Server/java/com/l2jserver/gameserver/network/clientpackets/RequestBypassToServer.java b/L2J_Server/java/com/l2jserver/gameserver/network/clientpackets/RequestBypassToServer.java
index 95a6b0e..86d20d7 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/network/clientpackets/RequestBypassToServer.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/network/clientpackets/RequestBypassToServer.java
@@ -39,6 +39,7 @@
import com.l2jserver.gameserver.model.entity.Hero;
import com.l2jserver.gameserver.model.events.EventDispatcher;
import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcManorBypass;
+import com.l2jserver.gameserver.model.events.impl.character.npc.OnNpcMenuSelect;
import com.l2jserver.gameserver.model.events.impl.character.player.OnPlayerBypass;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
import com.l2jserver.gameserver.network.SystemMessageId;
@@ -65,6 +66,7 @@
"_match",
"_diary",
"_olympiad?command",
+ "menu_select",
"manor_menu_select"
};
@@ -260,6 +262,17 @@
handler.useBypass("arenachange " + (arenaId - 1), activeChar, null);
}
}
+ else if (_command.startsWith("menu_select"))
+ {
+ final L2Npc lastNpc = activeChar.getLastFolkNPC();
+ if ((lastNpc != null) && lastNpc.canInteract(activeChar))
+ {
+ final String[] split = _command.substring(_command.indexOf("?") + 1).split("&");
+ final int ask = Integer.parseInt(split[0].split("=")[1]);
+ final int reply = Integer.parseInt(split[1].split("=")[1]);
+ EventDispatcher.getInstance().notifyEventAsync(new OnNpcMenuSelect(activeChar, lastNpc, ask, reply), lastNpc);
+ }
+ }
else if (_command.startsWith("manor_menu_select"))
{
final L2Npc lastNpc = activeChar.getLastFolkNPC();
diff --git a/L2J_Server/java/com/l2jserver/gameserver/network/serverpackets/AcquireSkillList.java b/L2J_Server/java/com/l2jserver/gameserver/network/serverpackets/AcquireSkillList.java
index 92e1093..565bae4 100644
--- a/L2J_Server/java/com/l2jserver/gameserver/network/serverpackets/AcquireSkillList.java
+++ b/L2J_Server/java/com/l2jserver/gameserver/network/serverpackets/AcquireSkillList.java
@@ -19,22 +19,26 @@
package com.l2jserver.gameserver.network.serverpackets;
import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
import com.l2jserver.gameserver.data.xml.impl.SkillTreesData;
-import com.l2jserver.gameserver.datatables.SkillData;
import com.l2jserver.gameserver.model.L2SkillLearn;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.holders.ItemHolder;
+import com.l2jserver.gameserver.model.skills.Skill;
/**
* @author Sdw
*/
public class AcquireSkillList extends L2GameServerPacket
{
+ final L2PcInstance _activeChar;
final List<L2SkillLearn> _learnable;
public AcquireSkillList(L2PcInstance activeChar)
{
+ _activeChar = activeChar;
_learnable = SkillTreesData.getInstance().getAvailableSkills(activeChar, activeChar.getClassId(), false, false);
_learnable.addAll(SkillTreesData.getInstance().getNextAvailableSkills(activeChar, activeChar.getClassId(), false, false));
}
@@ -58,11 +62,13 @@
writeQ(item.getCount());
}
- writeC(skill.getRemoveSkills().size());
- for (int skillId : skill.getRemoveSkills())
+ final List<Skill> skillRem = skill.getRemoveSkills().stream().map(_activeChar::getKnownSkill).filter(Objects::nonNull).collect(Collectors.toList());
+
+ writeC(skillRem.size());
+ for (Skill skillRemove : skillRem)
{
- writeD(skillId);
- writeH(SkillData.getInstance().getMaxLevel(skillId));
+ writeD(skillRemove.getId());
+ writeH(skillRemove.getLevel());
}
}
}
diff --git a/L2J_DataPack/dist/game/data/multisell/2027.xml b/L2J_DataPack/dist/game/data/multisell/2027.xml
new file mode 100644
index 0000000..0dedb17
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/multisell/2027.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../xsd/multisell.xsd">
+ <npcs>
+ <npc>33491</npc> <!-- Raina -->
+ </npcs>
+ <item>
+ <ingredient id="57" count="3997000"/> <!-- Adena -->
+ <production id="36187" count="1" chance="100"/> <!-- Rising Shirt -->
+ </item>
+ <item>
+ <ingredient id="57" count="11941000"/> <!-- Adena -->
+ <production id="36188" count="1" chance="100"/> <!-- Blaze Shirt -->
+ </item>
+ <item>
+ <ingredient id="57" count="1999000"/> <!-- Adena -->
+ <production id="36189" count="1" chance="100"/> <!-- Rising Belt -->
+ </item>
+ <item>
+ <ingredient id="57" count="5970000"/> <!-- Adena -->
+ <production id="36190" count="1" chance="100"/> <!-- Blaze Belt -->
+ </item>
+</list>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/multisell/857.xml b/L2J_DataPack/dist/game/data/multisell/857.xml
new file mode 100644
index 0000000..b1c3589
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/multisell/857.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../xsd/multisell.xsd">
+ <npcs>
+ <npc>33491</npc> <!-- Raina -->
+ </npcs>
+ <item>
+ <ingredient id="34741" count="10"/> <!-- XP Bottle -->
+ <production id="34742" count="1" chance="100"/> <!-- Scroll: 500,000,000 XP -->
+ </item>
+ <item>
+ <ingredient id="34741" count="1"/> <!-- XP Bottle -->
+ <production id="34855" count="1" chance="100"/> <!-- Raina's Box -->
+ </item>
+</list>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts.cfg b/L2J_DataPack/dist/game/data/scripts.cfg
index af2dd10..727d687 100644
--- a/L2J_DataPack/dist/game/data/scripts.cfg
+++ b/L2J_DataPack/dist/game/data/scripts.cfg
@@ -55,6 +55,7 @@
ai/npc/Proclaimer/Proclaimer.java
ai/npc/Rignos/Rignos.java
ai/npc/Rafforty/Rafforty.java
+ai/npc/Raina/Raina.java
ai/npc/RemembranceTower/RemembranceTower.java
ai/npc/Scarecrow/Scarecrow.java
ai/npc/Sirra/Sirra.java
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/AwakeningMaster/AwakeningMaster.java b/L2J_DataPack/dist/game/data/scripts/ai/npc/AwakeningMaster/AwakeningMaster.java
index 4d51266..3f599a9 100644
--- a/L2J_DataPack/dist/game/data/scripts/ai/npc/AwakeningMaster/AwakeningMaster.java
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/AwakeningMaster/AwakeningMaster.java
@@ -22,6 +22,7 @@
import ai.npc.AbstractNpcAI;
import com.l2jserver.gameserver.ThreadPoolManager;
+import com.l2jserver.gameserver.data.xml.impl.SkillTreesData;
import com.l2jserver.gameserver.enums.CategoryType;
import com.l2jserver.gameserver.enums.UserInfoType;
import com.l2jserver.gameserver.model.actor.L2Npc;
@@ -36,6 +37,7 @@
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
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.ExChangeToAwakenedClass;
import com.l2jserver.gameserver.network.serverpackets.ExShowUsm;
import com.l2jserver.gameserver.network.serverpackets.SocialAction;
@@ -276,7 +278,9 @@
player.broadcastPacket(new SocialAction(player.getObjectId(), socialId));
giveItems(player, itemId, 1);
- // TODO: Remove skill which does not level up later are removed
+ SkillTreesData.getInstance().cleanSkillUponAwakening(player);
+ player.sendPacket(new AcquireSkillList(player));
+ player.sendSkillList();
}
ThreadPoolManager.getInstance().scheduleGeneral(() ->
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-01.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-01.html
new file mode 100644
index 0000000..949e7ec
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-01.html
@@ -0,0 +1,9 @@
+<html><head><body>Subclass/Dual Class Master Raina:<br>
+So you need to <font color="LEVEL">create/reset a subclass</font>?<br>
+<font color="af9878">A new class subclass starts at </font><font color="LEVEL">level 40</font><font color="af9878">.<br>
+</font><Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addSubclass">"I'll add a new subclass (create new)."</Button><br>
+(<font color="af9878">Change dual classes and your level will be </font><font color="LEVEL">reset to level 40</font><font color="af9878">.)<br>
+</font><Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina removeSubclass">"I'll eliminate a subclass and add another one (reset)."</Button><br>
+<font color="LEVEL">Maintain your level while changing classes</font><font color="af9878">.<br>
+</font><Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina changeSubclass">"I'll change my subclass (requires Subclass Change Certificate)."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-02.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-02.html
new file mode 100644
index 0000000..3cf5b22
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-02.html
@@ -0,0 +1,7 @@
+<html><body>Subclass/Dual Class Master Raina:<br>
+I heard that Ertheia are curious people. But a gem that absorbs power... I still can't help thinking that it's really dangerous. You could have died. <br>
+But thanks to your efforts, even Ertheia can use the power of the giants now.<br>
+But because you're different than the race from the Material Realm, your power will not merge with the giants'. Because they do not mingle, you will not be able to use your power while using the giants' power. Of course, you can use your original power instead of the giants' power anytime you wish.<br>
+Now! Would you like to use the power of the giants?<br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina ertheiaDualClass">"Yes, on with a dual class!"</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-03.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-03.html
new file mode 100644
index 0000000..d059d2a
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-03.html
@@ -0,0 +1,7 @@
+<html><body>Subclass/Dual Class Master Raina:<br>
+Hello!<br1>
+I will be assisting you with <font color="LEVEL">reawakening an Awakened dual class</font>.<br1>
+To reawaken a dual class, you need to have an Awakened dual class. After reawakening, your level will be reduced to level 85, and all the skills you've learned will be forgotten.<br>
+(<font color="af9878">Change dual classes and your level will be </font><font color="LEVEL">reset to level 85</font><font color="af9878"></font>).<br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawekenDualclass">"I'll reawaken."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-04.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-04.html
new file mode 100644
index 0000000..52a8dc1
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-04.html
@@ -0,0 +1,6 @@
+<html><body>Subclass/Dual Class Master Raina:<br>
+So you want to know about <font color="LEVEL">items designated for a dual class</font>?<br1>
+They're for those who grow their dual class powers beyond imaginable limits.<br1>
+You can purchase as many as you want, but <font color="LEVEL">you have to be at certain level with your main and dual classes in order to use them</font>.<br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h npc_%objectId%_multisell 2027">"Let me take a look at the items for dual classes."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-05.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-05.html
new file mode 100644
index 0000000..6400845
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491-05.html
@@ -0,0 +1,6 @@
+<html><body> Subclass/Dual Class Master Raina:<br><br>
+Hello!<br>
+I'm collecting XP Bottles.<br>
+I'll give you items if you bring me XP Bottles, how's that?<br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h npc_%objectId%_multisell 857">"I have XP bottles right here."</Button>
+</body> </html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491.html
new file mode 100644
index 0000000..8790c1f
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/33491.html
@@ -0,0 +1,10 @@
+<html><body>Subclass/Dual Class Master Raina:<br>
+Hello!<br1>
+You have a question about subclass or dual class?<br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina 33491-01.html">"Yeah. How do I create or reset a subclass?"</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina 33491-02.html">"I want to add a dual class." (for Ertheia only)</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina 33491-03.html">"Can I reawaken my Awakened dual class?"</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina 33491-04.html">"So...what are the items for dual classes?" </Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina 33491-05.html">"What's with these XP Bottles?"</Button>
+<Button ALIGN=LEFT ICON="QUEST" action="bypass -h npc_%objectId%_Quest">Quest</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/Raina.java b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/Raina.java
new file mode 100644
index 0000000..3e8b117
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/Raina.java
@@ -0,0 +1,866 @@
+/*
+ * 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.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.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 "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;
+ }
+
+ final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addSuccess.html");
+ player.setActiveClass(player.getTotalSubClasses());
+ player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED);
+ player.sendPacket(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);
+
+ final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addSuccess.html");
+
+ player.sendPacket(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);
+
+ final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "reawakenSuccess.html");
+ player.sendPacket(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))
+ {
+ final NpcHtmlMessage html = getNpcHtmlMessage(player, npc, "addSuccess.html");
+ player.setActiveClass(player.getTotalSubClasses());
+ player.sendPacket(SystemMessageId.THE_NEW_SUBCLASS_HAS_BEEN_ADDED);
+ player.sendPacket(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();
+ }
+}
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm.html
new file mode 100644
index 0000000..8aee232
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm.html
@@ -0,0 +1,6 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+The choice is no easy one.<br>
+The new subclass will be a level 40, 2nd-transfer class. <br>
+Are you sure you want to go ahead with this decision? <br>
+%confirmButton%
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm2.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm2.html
new file mode 100644
index 0000000..e6a18dc
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addConfirm2.html
@@ -0,0 +1,6 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+The choice is no easy one.<br>
+<font="LEVEL">The new subclass will have none of the skill enchants or the skills you learned through codices. Think carefully.</font><br>
+Are you sure you want to go ahead with this decision? <br>
+%confirmButton%
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheia.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheia.html
new file mode 100644
index 0000000..39b7f96
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheia.html
@@ -0,0 +1,12 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+So, into which class would you like to Reawaken?<br>
+Remember that if you proceed, you will go back to <font color="LEVEL">Lv. 85, and all your skills will be reset.</font><br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_SIGEL_GROUP">"I will change to the Knight class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_TYRR_GROUP">"I will change to the Warrior class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_OTHELL_GROUP">"I will change to the Rogue class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_YUL_GROUP">"I will change to the Archer class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_FEOH_GROUP">"I will change to the Wizard class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_ISS_GROUP">"I will change to the Enchanter class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_WYNN_GROUP">"I will change to the Summoner class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina addDualClass_AEORE_GROUP">"I will change to the Healer class."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaFailed.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaFailed.html
new file mode 100644
index 0000000..d607869
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaFailed.html
@@ -0,0 +1,5 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You do not meet the conditions to add a dual class.<br>
+Come back when you're ready, or ask for something else.<br>
+(Only Ertheia characters Lv. 85 or above may add a dual class once.)
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaList.html
new file mode 100644
index 0000000..895130b
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addDualClassErtheiaList.html
@@ -0,0 +1,9 @@
+<html><body scroll="no">
+<table border=0 cellpadding=0 cellspacing=0 width=292 height=358 background="L2UI_CH3.refinewnd_back_Pattern">
+<tr><td valign="top" align="center"><br><br><br><br>
+These are the classes available to you.<br1>
+You must choose which you want to be.<br><br>
+%dualclassList%
+</td></tr>
+</table>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addFailed.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addFailed.html
new file mode 100644
index 0000000..e0800db
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addFailed.html
@@ -0,0 +1,8 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Sorry, but you're not qualified to add a subclass.<br1>
+To add a new subclass, you need the following qualifications:<br>
+First, <font color="LEVEL">you've completed the 'Red Thread of Fate' quest</font>. If you want to get on that, let me know.<br>
+Second, <font color="LEVEL">your current class has done the 2nd class transfer, and all your subclass levels are 75 or higher</font>. For example, if your subclasses are Temple Knight and Silver Ranger, they must both be level 75 for you to add a third subclass.<br>
+Third, you may only have <font color="LEVEL">up to 3 subclasses</font>, I'm afraid.<br>
+Got it?
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addSuccess.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addSuccess.html
new file mode 100644
index 0000000..83e62d3
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/addSuccess.html
@@ -0,0 +1,5 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Congratulations!<br>
+Your new subclass has been added.<br>
+I look forward to your future endeavors with your new subclass!<br>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/inventoryLimit.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/inventoryLimit.html
new file mode 100644
index 0000000..e916672
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/inventoryLimit.html
@@ -0,0 +1,3 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You need inventory space and free weight to create or renew a subclass. Go free up your weight and inventory to less than 80%, and I'll see what I can do.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noCertificate.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noCertificate.html
new file mode 100644
index 0000000..6b61fbd
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noCertificate.html
@@ -0,0 +1,5 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You need a Subclass Change Certificate in order to receive the power of the gods. This was given to heroes long before Shilen's destructive rampages came about, so you can't find new ones anymore.<br>
+But if you have the certificate, talk to me again as a subclass ready to receive the power of the gods.<br>
+<font color="af9878">But a dual class isn't ready to receive the power of the gods, so I can't change classes for that.</font>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noErtheia.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noErtheia.html
new file mode 100644
index 0000000..c633be7
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noErtheia.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Ertheia cannot add or modify subclasses.<br>
+Please talk to me when you're ready to add a dual class.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noQuest.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noQuest.html
new file mode 100644
index 0000000..8a7d365
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noQuest.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You haven't completed the 'Red Thread of Fate' quest yet.<br>
+To create a subclass, you have to <font color="LEVEL">complete the 'Red Thread of Fate' quest</font>. If you want to work on the quest, let me know.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSubChange.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSubChange.html
new file mode 100644
index 0000000..ef43d3f
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSubChange.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You don't have a subclass to change.<br>
+Make a subclass.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSummon.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSummon.html
new file mode 100644
index 0000000..77f6267
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noSummon.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You cannot create a new subclass<br>
+when there is a pet or servitor.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noTransform.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noTransform.html
new file mode 100644
index 0000000..9453146
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/noTransform.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+I cannot create a new subclass while you're transformed.<br>
+Change back to your original form, then talk to me again.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/onlyErtheia.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/onlyErtheia.html
new file mode 100644
index 0000000..d607869
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/onlyErtheia.html
@@ -0,0 +1,5 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You do not meet the conditions to add a dual class.<br>
+Come back when you're ready, or ask for something else.<br>
+(Only Ertheia characters Lv. 85 or above may add a dual class once.)
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawaken.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawaken.html
new file mode 100644
index 0000000..996e8ff
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawaken.html
@@ -0,0 +1,10 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+In order to reawaken your dual class,<br>
+<font color="LEVEL">you need the cloak you received when you first awakened it,<br>
+and some %price% Adena</font> to pay the fee.<br>
+Upon reawakening, zou will lose zour current<br>
+abilities, but gain new powers is exchange. You will be reducted to <font color="LEVEL">level 85, and all your skills will be reset.</font> You can choose the class you want, but <font color="LEVEL">you cannot receive powers from the same class as your main class</font>.<br>
+<font color="af9878">(After reawakening, the 3rd skills provided will be based on human classes.)</font><br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawakenDualclassConfirm">"I'll reawaken."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawakenCancel.html">"I will think about it."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenCancel.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenCancel.html
new file mode 100644
index 0000000..a2cd574
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenCancel.html
@@ -0,0 +1,5 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+I see.<br>
+Make a carful decision because you will lose the power you have, to get new power.<br>
+When you make up your mind, come back to me.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenClassList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenClassList.html
new file mode 100644
index 0000000..895130b
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenClassList.html
@@ -0,0 +1,9 @@
+<html><body scroll="no">
+<table border=0 cellpadding=0 cellspacing=0 width=292 height=358 background="L2UI_CH3.refinewnd_back_Pattern">
+<tr><td valign="top" align="center"><br><br><br><br>
+These are the classes available to you.<br1>
+You must choose which you want to be.<br><br>
+%dualclassList%
+</td></tr>
+</table>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenList.html
new file mode 100644
index 0000000..1caf321
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenList.html
@@ -0,0 +1,12 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+So, into which class would you like to Reawaken?<br>
+Remember that if you proceed, you will go back to <font color="LEVEL">Lv. 85, and all your skills will be reset.</font><br>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_SIGEL_GROUP">"I will change to the Knight class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_TYRR_GROUP">"I will change to the Warrior class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_OTHELL_GROUP">"I will change to the Rogue class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_YUL_GROUP">"I will change to the Archer class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_FEOH_GROUP">"I will change to the Wizard class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_ISS_GROUP">"I will change to the Enchanter class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_WYNN_GROUP">"I will change to the Summoner class."</Button>
+<Button ALIGN=LEFT ICON="NORMAL" action="bypass -h Quest Raina reawaken_AEORE_GROUP">"I will change to the Healer class."</Button>
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoDual.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoDual.html
new file mode 100644
index 0000000..3c54424
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoDual.html
@@ -0,0 +1,6 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+You don't look like a Awakened dual class.<br>
+If you want to Reawaken, you must be an <font color="LEVEL">Awakened dual class</font>.<br>
+Please come find me when you have Awakened as a dual class.<br><br>
+(You can become a specialized Awakened class through Hardin or the Giant's stone statue at the Reliquary of the Giants.)
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoFee.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoFee.html
new file mode 100644
index 0000000..b479729
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/reawakenNoFee.html
@@ -0,0 +1,6 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+To change your dual class,<br>
+<font color="LEVEL">you need the cloak you received when you awakened your dual class,<br>
+and %price% Adena</font> for a fee.<br>
+Please come back with everything you need.
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/removeSubclassList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/removeSubclassList.html
new file mode 100644
index 0000000..12ed66f
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/removeSubclassList.html
@@ -0,0 +1,7 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Which subclass would you like to change to?<br>
+Please select a subclass from the list below.<br>
+<font color="LEVEL">The new subclas will be a level 40, 2nd-transfer class with none of the skill enchants or the skills you learned through codices. Think carefully.</font><br>
+Also, dual classes can't be reset.<br>
+%subclassList%
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassList.html
new file mode 100644
index 0000000..1a5618b
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassList.html
@@ -0,0 +1,4 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Select a Subclass.<br><br>
+%subclassList%
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassRemoveList.html b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassRemoveList.html
new file mode 100644
index 0000000..ea98809
--- /dev/null
+++ b/L2J_DataPack/dist/game/data/scripts/ai/npc/Raina/subclassRemoveList.html
@@ -0,0 +1,7 @@
+<html><body>Subclass/Dual Class Master Raina:<br><br>
+Which subclass would you like to change to?<br>
+Please select a subclass from the list below.<br>
+<font color="LEVEL">The new subclas will be a level 40, 2nd-transfer class with none of the skill enchants or the skills you learned through codices. Think carefully.</font><br>
+Also, dual classes can't be reset.<br>
+%removeList%
+</body></html>
\ No newline at end of file
diff --git a/L2J_DataPack/dist/game/data/scripts/handlers/admincommandhandlers/AdminEditChar.java b/L2J_DataPack/dist/game/data/scripts/handlers/admincommandhandlers/AdminEditChar.java
index 02461de..1723cc7 100644
--- a/L2J_DataPack/dist/game/data/scripts/handlers/admincommandhandlers/AdminEditChar.java
+++ b/L2J_DataPack/dist/game/data/scripts/handlers/admincommandhandlers/AdminEditChar.java
@@ -353,12 +353,17 @@
if ((ClassId.getClassId(classidval) != null) && (player.getClassId().getId() != classidval))
{
player.setClassId(classidval);
- if (!player.isSubClassActive())
+
+ if (player.isSubClassActive())
{
- player.setBaseClass(classidval);
+ player.getSubClasses().get(player.getClassIndex()).setClassId(player.getActiveClass());
+ }
+ else
+ {
+ player.setBaseClass(player.getActiveClass());
}
- String newclass = ClassListData.getInstance().getClass(player.getClassId()).getClassName();
+ final String newclass = ClassListData.getInstance().getClass(player.getClassId()).getClassName();
player.storeMe();
player.sendMessage("A GM changed your class to " + newclass + ".");
player.broadcastUserInfo();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment