Ver código fonte

New Class Master, enjoy. DP update required.

_DS_ 15 anos atrás
pai
commit
2f842378ea

+ 14 - 0
L2_GameServer/java/config/Character.properties

@@ -146,6 +146,20 @@ AltPerfectShieldBlockRate = 10
 # Default: False
 # Default: False
 AllowClassMasters = False
 AllowClassMasters = False
 
 
+# Class Masters will allow changing to any occupation on any level inside class tree
+# For example, Dwarven Fighter will be able to advance to:
+# Artisan, Scavenger, Warsmith, Bounty Hunter, Maestro, Fortune Seeker.
+# But Warsmith will be able to change only to Maestro. 
+# Default = False
+AllowEntireTree = False
+
+# Then character reach levels 20,40,76 he will receive tutorial page
+# with list of the all possible variants, and can select and immediately
+# change to the new occupation, or decide to choose later (on next login).
+# Can be used with or without classic Class Masters.
+# Default = False 
+AlternateClassMaster = False
+
 # Require life crystal needed to learn clan skills.
 # Require life crystal needed to learn clan skills.
 # Default: True
 # Default: True
 LifeCrystalNeeded = True
 LifeCrystalNeeded = True

+ 6 - 0
L2_GameServer/java/net/sf/l2j/Config.java

@@ -94,6 +94,8 @@ public final class Config
 	public static boolean ALT_GAME_SHIELD_BLOCKS;
 	public static boolean ALT_GAME_SHIELD_BLOCKS;
 	public static int ALT_PERFECT_SHLD_BLOCK;
 	public static int ALT_PERFECT_SHLD_BLOCK;
 	public static boolean ALLOW_CLASS_MASTERS;
 	public static boolean ALLOW_CLASS_MASTERS;
+	public static boolean ALLOW_ENTIRE_TREE;
+	public static boolean ALTERNATE_CLASS_MASTER;
 	public static boolean LIFE_CRYSTAL_NEEDED;
 	public static boolean LIFE_CRYSTAL_NEEDED;
 	public static boolean SP_BOOK_NEEDED;
 	public static boolean SP_BOOK_NEEDED;
 	public static boolean ES_SP_BOOK_NEEDED;
 	public static boolean ES_SP_BOOK_NEEDED;
@@ -1198,6 +1200,8 @@ public final class Config
 					ALT_GAME_SHIELD_BLOCKS = Boolean.parseBoolean(Character.getProperty("AltShieldBlocks", "false"));
 					ALT_GAME_SHIELD_BLOCKS = Boolean.parseBoolean(Character.getProperty("AltShieldBlocks", "false"));
 					ALT_PERFECT_SHLD_BLOCK = Integer.parseInt(Character.getProperty("AltPerfectShieldBlockRate", "10"));
 					ALT_PERFECT_SHLD_BLOCK = Integer.parseInt(Character.getProperty("AltPerfectShieldBlockRate", "10"));
 					ALLOW_CLASS_MASTERS = Boolean.parseBoolean(Character.getProperty("AllowClassMasters", "False"));
 					ALLOW_CLASS_MASTERS = Boolean.parseBoolean(Character.getProperty("AllowClassMasters", "False"));
+					ALLOW_ENTIRE_TREE = Boolean.parseBoolean(Character.getProperty("AllowEntireTree", "False"));
+					ALTERNATE_CLASS_MASTER = Boolean.parseBoolean(Character.getProperty("AlternateClassMaster", "False"));
 					LIFE_CRYSTAL_NEEDED = Boolean.parseBoolean(Character.getProperty("LifeCrystalNeeded", "true"));
 					LIFE_CRYSTAL_NEEDED = Boolean.parseBoolean(Character.getProperty("LifeCrystalNeeded", "true"));
 					SP_BOOK_NEEDED = Boolean.parseBoolean(Character.getProperty("SpBookNeeded", "false"));
 					SP_BOOK_NEEDED = Boolean.parseBoolean(Character.getProperty("SpBookNeeded", "false"));
 					ES_SP_BOOK_NEEDED = Boolean.parseBoolean(Character.getProperty("EnchantSkillSpBookNeeded","true"));
 					ES_SP_BOOK_NEEDED = Boolean.parseBoolean(Character.getProperty("EnchantSkillSpBookNeeded","true"));
@@ -2342,6 +2346,8 @@ public final class Config
 		else if (pName.equalsIgnoreCase("AltGameExponentSp")) ALT_GAME_EXPONENT_SP = Float.parseFloat(pValue);
 		else if (pName.equalsIgnoreCase("AltGameExponentSp")) ALT_GAME_EXPONENT_SP = Float.parseFloat(pValue);
 
 
 		else if (pName.equalsIgnoreCase("AllowClassMasters")) ALLOW_CLASS_MASTERS = Boolean.parseBoolean(pValue);
 		else if (pName.equalsIgnoreCase("AllowClassMasters")) ALLOW_CLASS_MASTERS = Boolean.parseBoolean(pValue);
+		else if (pName.equalsIgnoreCase("AllowEntireTree")) ALLOW_ENTIRE_TREE = Boolean.parseBoolean(pValue);
+		else if (pName.equalsIgnoreCase("AlternateClassMaster")) ALTERNATE_CLASS_MASTER = Boolean.parseBoolean(pValue);
 		else if (pName.equalsIgnoreCase("AltGameFreights")) ALT_GAME_FREIGHTS = Boolean.parseBoolean(pValue);
 		else if (pName.equalsIgnoreCase("AltGameFreights")) ALT_GAME_FREIGHTS = Boolean.parseBoolean(pValue);
 		else if (pName.equalsIgnoreCase("AltGameFreightPrice")) ALT_GAME_FREIGHT_PRICE = Integer.parseInt(pValue);
 		else if (pName.equalsIgnoreCase("AltGameFreightPrice")) ALT_GAME_FREIGHT_PRICE = Integer.parseInt(pValue);
 		else if (pName.equalsIgnoreCase("AltPartyRange")) ALT_PARTY_RANGE = Integer.parseInt(pValue);
 		else if (pName.equalsIgnoreCase("AltPartyRange")) ALT_PARTY_RANGE = Integer.parseInt(pValue);

+ 218 - 343
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2ClassMasterInstance.java

@@ -14,20 +14,14 @@
  */
  */
 package net.sf.l2j.gameserver.model.actor.instance;
 package net.sf.l2j.gameserver.model.actor.instance;
 
 
-import java.util.Collection;
 import net.sf.l2j.Config;
 import net.sf.l2j.Config;
-import net.sf.l2j.gameserver.ai.CtrlIntention;
+import net.sf.l2j.gameserver.cache.HtmCache;
 import net.sf.l2j.gameserver.datatables.CharTemplateTable;
 import net.sf.l2j.gameserver.datatables.CharTemplateTable;
 import net.sf.l2j.gameserver.model.base.ClassId;
 import net.sf.l2j.gameserver.model.base.ClassId;
-import net.sf.l2j.gameserver.model.base.ClassLevel;
-import net.sf.l2j.gameserver.model.base.PlayerClass;
-import net.sf.l2j.gameserver.model.quest.Quest;
-import net.sf.l2j.gameserver.network.SystemMessageId;
-import net.sf.l2j.gameserver.network.serverpackets.ActionFailed;
-import net.sf.l2j.gameserver.network.serverpackets.MyTargetSelected;
 import net.sf.l2j.gameserver.network.serverpackets.NpcHtmlMessage;
 import net.sf.l2j.gameserver.network.serverpackets.NpcHtmlMessage;
-import net.sf.l2j.gameserver.network.serverpackets.SystemMessage;
-import net.sf.l2j.gameserver.network.serverpackets.ValidateLocation;
+import net.sf.l2j.gameserver.network.serverpackets.TutorialCloseHtml;
+import net.sf.l2j.gameserver.network.serverpackets.TutorialShowHtml;
+import net.sf.l2j.gameserver.network.serverpackets.TutorialShowQuestionMark;
 import net.sf.l2j.gameserver.templates.chars.L2NpcTemplate;
 import net.sf.l2j.gameserver.templates.chars.L2NpcTemplate;
 import net.sf.l2j.gameserver.util.StringUtil;
 import net.sf.l2j.gameserver.util.StringUtil;
 
 
@@ -38,18 +32,6 @@ import net.sf.l2j.gameserver.util.StringUtil;
  */
  */
 public final class L2ClassMasterInstance extends L2NpcInstance
 public final class L2ClassMasterInstance extends L2NpcInstance
 {
 {
-	//private static Logger _log = Logger.getLogger(L2ClassMasterInstance.class.getName());
-        private static final int[] BASE_CLASS_IDS = {0, 10, 18, 25, 31, 38, 44,
-                49, 53};
-        private static final int[] FIRST_CLASS_IDS = {1, 4, 7, 11, 15, 19, 22,
-                26, 29, 32, 35, 39, 42, 45, 47, 50, 54, 56};
-	private static final int[] SECOND_CLASS_IDS = {2, 3, 5, 6, 9, 8, 12, 13,
-                14, 16, 17, 20, 21, 23, 24, 27, 28, 30, 33, 34, 36, 37, 40, 41,
-                43, 46, 48, 51, 52, 55, 57};
-        private static final int[] THIRD_CLASS_IDS = {88, 89, 90, 91, 92, 93,
-                94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
-                108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118};
-
 	/**
 	/**
 	 * @param template
 	 * @param template
 	 */
 	 */
@@ -59,370 +41,263 @@ public final class L2ClassMasterInstance extends L2NpcInstance
 	}
 	}
 
 
 	@Override
 	@Override
-	public void onAction(L2PcInstance player)
+	public String getHtmlPath(int npcId, int val)
 	{
 	{
-		if (!canTarget(player)) return;
+		String pom = "";
 
 
-		player.setLastFolkNPC(this);
-
-		// Check if the L2PcInstance already target the L2NpcInstance
-		if (getObjectId() != player.getTargetId())
-		{
-			// Set the target of the L2PcInstance player
-			player.setTarget(this);
+		if (val == 0)
+			pom = "" + npcId;
+		else
+			pom = npcId + "-" + val;
 
 
-			// Send a Server->Client packet MyTargetSelected to the L2PcInstance player
-			player.sendPacket(new MyTargetSelected(getObjectId(), 0));
+		return "data/html/classmaster/" + pom + ".htm";
+	}
 
 
-			// Send a Server->Client packet ValidateLocation to correct the L2NpcInstance position and heading on the client
-			player.sendPacket(new ValidateLocation(this));
+	@Override
+	public void onBypassFeedback(L2PcInstance player, String command)
+	{
+		if(command.startsWith("1stClass"))
+		{
+			showHtmlMenu(player, getObjectId(), 1);
 		}
 		}
-		else
+		else if(command.startsWith("2ndClass"))
 		{
 		{
-			if (!canInteract(player))
+			showHtmlMenu(player, getObjectId(), 2);
+		}
+		else if(command.startsWith("3rdClass"))
+		{
+			showHtmlMenu(player, getObjectId(), 3);
+		}
+		else if(command.startsWith("change_class"))
+		{
+			int val = Integer.parseInt(command.substring(13));
+
+			if (checkAndChangeClass(player, val))
 			{
 			{
-				player.getAI().setIntention(CtrlIntention.AI_INTENTION_INTERACT, this);
-				return;
+				NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
+				html.setFile("data/html/classmaster/ok.htm");
+				html.replace("%name%", CharTemplateTable.getInstance().getClassNameById(val));
+				player.sendPacket(html);
 			}
 			}
+		}
+		else
+		{
+			super.onBypassFeedback(player, command);
+		}
+	}
 
 
-			if (Config.DEBUG)
-				_log.fine("ClassMaster activated");
+	public static final void onTitorialLink(L2PcInstance player, String request)
+	{
+		if (!Config.ALTERNATE_CLASS_MASTER
+				|| request == null
+				|| !request.startsWith("CO"))
+			return;
 
 
-			ClassId classId = player.getClassId();
+		if (!player.getFloodProtectors().getServerBypass().tryPerformAction("changeclass"))
+			return;
 
 
-			int jobLevel = 0;
-			int level = player.getLevel();
-			ClassLevel lvl = PlayerClass.values()[classId.getId()].getLevel();
-			switch (lvl)
-			{
-				case First:
-					jobLevel = 1;
-					break;
-				case Second:
-					jobLevel = 2;
-					break;
-				default:
-					jobLevel = 3;
-			}
+		try
+		{
+			int val = Integer.parseInt(request.substring(2));
+			checkAndChangeClass(player, val);
+		}
+		catch (NumberFormatException e)
+		{
+		}
+		player.sendPacket(new TutorialCloseHtml());
+	}
 
 
-			if (!Config.ALLOW_CLASS_MASTERS)
-				jobLevel = 3;
+	public static final void onTutorialQuestionMark(L2PcInstance player, int number)
+	{
+		if (!Config.ALTERNATE_CLASS_MASTER || number != 1001)
+			return;
 
 
-			if(player.isGM())
-			{
-				showChatWindowChooseClass(player);
-			}
-			else if (((level >= 20 && jobLevel == 1 ) ||
-				(level >= 40 && jobLevel == 2 )) && Config.ALLOW_CLASS_MASTERS)
+		showTutorialHtml(player);
+	}
+
+	public static final void showQuestionMark(L2PcInstance player)
+	{
+		if (!Config.ALTERNATE_CLASS_MASTER)
+			return;
+
+		final ClassId classId = player.getClassId();
+		if (getMinLevel(classId.level()) > player.getLevel())
+			return;
+
+		player.sendPacket(new TutorialShowQuestionMark(1001));
+	}
+
+	private static final void showHtmlMenu(L2PcInstance player, int objectId, int level)
+	{
+		NpcHtmlMessage html = new NpcHtmlMessage(objectId);
+
+		if (!Config.ALLOW_CLASS_MASTERS)
+		{
+			html.setFile("data/html/classmaster/disabled.htm");
+		}
+		else
+		{
+			final ClassId currentClassId = player.getClassId();
+			if (currentClassId.level() >= level)
 			{
 			{
-				showChatWindow(player, classId.getId());
+				html.setFile("data/html/classmaster/nomore.htm");
 			}
 			}
-			else if (level >= 76 && Config.ALLOW_CLASS_MASTERS && classId.getId() < 88)
+			else
 			{
 			{
-				for (int i = 0; i < SECOND_CLASS_IDS.length; i++)
+				final int minLevel = getMinLevel(currentClassId.level());
+				if (player.getLevel() > minLevel || Config.ALLOW_ENTIRE_TREE)
 				{
 				{
-					if (classId.getId() == SECOND_CLASS_IDS[i])
+					final StringBuilder menu = new StringBuilder(100);
+					for (ClassId cid : ClassId.values())
+					{
+						if (validateClassId(currentClassId, cid) && cid.level() == level)
+						{
+							StringUtil.append(menu,
+									"<a action=\"bypass -h npc_%objectId%_change_class ",
+									String.valueOf(cid.getId()),
+									"\">",
+									CharTemplateTable.getInstance().getClassNameById(cid.getId()),
+									"</a><br>"
+									);
+						}
+					}
+
+					if (menu.length() > 0)
 					{
 					{
-                        NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-                        final String sb = StringUtil.concat(
-                                "<html><body<table width=200>" +
-                                "<tr><td><center>",
-                                String.valueOf(CharTemplateTable.getInstance().getClassNameById(player.getClassId().getId())),
-                                " Class Master:</center></td></tr>" +
-                                "<tr><td><br></td></tr>" +
-                                "<tr><td><a action=\"bypass -h npc_",
-                                String.valueOf(getObjectId()),
-                                "_change_class ",
-                                String.valueOf(88+i),
-                                "\">Advance to ",
-                                CharTemplateTable.getInstance().getClassNameById(88+i),
-                                "</a></td></tr>" +
-                                "<tr><td><br></td></tr>" +
-                                "</table></body></html>"
-                                );
-                        html.setHtml(sb);
-                        player.sendPacket(html);
-                        break;
+						html.setFile("data/html/classmaster/template.htm");
+						html.replace("%name%", CharTemplateTable.getInstance().getClassNameById(currentClassId.getId()));
+						html.replace("%menu%", menu.toString());
+					}
+					else
+					{
+						html.setFile("data/html/classmaster/comebacklater.htm");
+						html.replace("%level%", String.valueOf(getMinLevel(level - 1)));
 					}
 					}
 				}
 				}
-			}
-            else if (level >= 76 && Config.ALLOW_CLASS_MASTERS && ((classId.getId() >= 123 && classId.getId() < 131 ) || classId.getId() == 135)) // this is for Kamael Race 3rd Transfer
-            {
-                showChatWindow(player, classId.getId());
-            }
-			else
-			{
-				NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-                                final Collection<Quest> quests = Quest.findAllEvents();
-                                final StringBuilder sb = new StringBuilder(30 + 50 + quests.size() * 50);
-				sb.append("<html><body>");
-				switch (jobLevel)
+				else	
 				{
 				{
-					case 1:
-						sb.append("Come back here when you reach level 20 to change your class.<br>");
-						break;
-					case 2:
-						sb.append("Come back here when you reach level 40 to change your class.<br>");
-						break;
-					case 3:
-						sb.append("There are no more class changes for you.<br>");
-						break;
+					if (minLevel < Integer.MAX_VALUE)
+					{
+						html.setFile("data/html/classmaster/comebacklater.htm");
+						html.replace("%level%", String.valueOf(minLevel));
+					}
+					else
+						html.setFile("data/html/classmaster/nomore.htm");
 				}
 				}
-
-				for (Quest q : quests) {
-                                    StringUtil.append(sb,
-                                            "Event: <a action=\"bypass -h Quest ",
-                                            q.getName(),
-                                            "\">",
-                                            q.getDescr(),
-                                            "</a><br>"
-                                            );
-                                }
-
-				sb.append("</body></html>");
-				html.setHtml(sb.toString());
-				player.sendPacket(html);
 			}
 			}
 		}
 		}
-		player.sendPacket(ActionFailed.STATIC_PACKET);
-	}
 
 
-	@Override
-	public String getHtmlPath(int npcId, int val)
-	{
-		return "data/html/classmaster/" + val + ".htm";
+		html.replace("%objectId%", String.valueOf(objectId));
+		player.sendPacket(html);
 	}
 	}
 
 
-	@Override
-	public void onBypassFeedback(L2PcInstance player, String command)
+	private static final void showTutorialHtml(L2PcInstance player)
 	{
 	{
-		if(command.startsWith("1stClass"))
-		{
-			if(player.isGM())
-			{
-				showChatWindow1st(player);
-			}
-		}
-		else if(command.startsWith("2ndClass"))
-		{
-			if(player.isGM())
-			{
-				showChatWindow2nd(player);
-			}
-		}
-		else if(command.startsWith("3rdClass"))
-		{
-			if(player.isGM())
-			{
-				showChatWindow3rd(player);
-			}
-		}
-		else if(command.startsWith("baseClass"))
+		final ClassId currentClassId = player.getClassId();
+		if (getMinLevel(currentClassId.level()) > player.getLevel()
+				&& !Config.ALLOW_ENTIRE_TREE)
+			return;
+
+		String msg = HtmCache.getInstance().getHtm("data/html/classmaster/tutorialtemplate.htm");
+
+		msg = msg.replaceAll("%name%", CharTemplateTable.getInstance().getClassNameById(currentClassId.getId()));
+
+		final StringBuilder menu = new StringBuilder(100);
+		for (ClassId cid : ClassId.values())
 		{
 		{
-			if(player.isGM())
+			if (validateClassId(currentClassId, cid))
 			{
 			{
-				showChatWindowBase(player);
+				StringUtil.append(menu,
+						"<a action=\"link CO",
+						String.valueOf(cid.getId()),
+						"\">",
+						CharTemplateTable.getInstance().getClassNameById(cid.getId()),
+						"</a><br>"
+						);
 			}
 			}
 		}
 		}
-		else if(command.startsWith("change_class"))
-		{
-            int val = Integer.parseInt(command.substring(13));
-
-            // Exploit prevention
-            ClassId classId = player.getClassId();
-            int level = player.getLevel();
-            int jobLevel = 0;
-            int newJobLevel = 0;
-
-            ClassLevel lvlnow = PlayerClass.values()[classId.getId()].getLevel();
-
-            if(player.isGM())
-            {
-            	changeClass(player, val);
-
-                if(player.getClassId().level() == 3)
-                	player.sendPacket(new SystemMessage(SystemMessageId.THIRD_CLASS_TRANSFER)); // system sound 3rd occupation
-                else
-                	player.sendPacket(new SystemMessage(SystemMessageId.CLASS_TRANSFER));    // system sound for 1st and 2nd occupation
-
-                NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-                final String sb = StringUtil.concat(
-                        "<html><body>" +
-                        "You have now become a <font color=\"LEVEL\">",
-                        CharTemplateTable.getInstance().getClassNameById(player.getClassId().getId()),
-                        "</font>." +
-                        "</body></html>"
-                        );
-                html.setHtml(sb);
-                player.sendPacket(html);
-            	return;
-            }
-            switch (lvlnow)
-            {
-            	case First:
-            		jobLevel = 1;
-            		break;
-            	case Second:
-            		jobLevel = 2;
-            		break;
-            	case Third:
-            		jobLevel = 3;
-            		break;
-            	default:
-            		jobLevel = 4;
-            }
-
-            if(jobLevel == 4) return; // no more job changes
-
-            ClassLevel lvlnext = PlayerClass.values()[val].getLevel();
-            switch (lvlnext)
-            {
-            	case First:
-            		newJobLevel = 1;
-            		break;
-            	case Second:
-            		newJobLevel = 2;
-            		break;
-            	case Third:
-            		newJobLevel = 3;
-            		break;
-            	default:
-            		newJobLevel = 4;
-            }
-
-            // prevents changing between same level jobs
-            if(newJobLevel != jobLevel + 1) return;
-
-            if (level < 20 && newJobLevel > 1) return;
-            if (level < 40 && newJobLevel > 2) return;
-            if (level < 75 && newJobLevel > 3) return;
-            // -- prevention ends
-
-
-            changeClass(player, val);
-
-            if(player.getClassId().level() == 3)
-            	player.sendPacket(new SystemMessage(SystemMessageId.THIRD_CLASS_TRANSFER)); // system sound 3rd occupation
-            else
-            	player.sendPacket(new SystemMessage(SystemMessageId.CLASS_TRANSFER));    // system sound for 1st and 2nd occupation
-            
-            player.rewardSkills();
-
-            NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            final String sb = StringUtil.concat(
-                    "<html><body>" +
-                    "You have now become a <font color=\"LEVEL\">",
-                    CharTemplateTable.getInstance().getClassNameById(player.getClassId().getId()),
-                    "</font>." +
-                    "</body></html>"
-                    );
-            html.setHtml(sb);
-            player.sendPacket(html);
-       }
-       else
-       {
-           super.onBypassFeedback(player, command);
-       }
- }
-	private void showChatWindowChooseClass(L2PcInstance player) {
-            NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            final String objectIdString = String.valueOf(getObjectId());
-            final String sb = StringUtil.concat(
-                    "<html>" +
-                    "<body>" +
-                    "<table width=200>" +
-                    "<tr><td><center>GM Class Master:</center></td></tr>" +
-                    "<tr><td><br></td></tr>" +
-                    "<tr><td><a action=\"bypass -h npc_",
-                    objectIdString,
-                    "_baseClass\">Base Classes.</a></td></tr>" +
-                    "<tr><td><a action=\"bypass -h npc_",
-                    objectIdString,
-                    "_1stClass\">1st Classes.</a></td></tr>" +
-                    "<tr><td><a action=\"bypass -h npc_",
-                    objectIdString,
-                    "_2ndClass\">2nd Classes.</a></td></tr>" +
-                    "<tr><td><a action=\"bypass -h npc_",
-                    objectIdString,
-                    "_3rdClass\">3rd Classes.</a></td></tr>" +
-                    "<tr><td><br></td></tr>" +
-                    "</table>" +
-                    "<br><font color=\"LEVEL\">Please notice this menu is only available for Game Masters, not for normal players ;)</font>" +
-                    "</body>" +
-                    "</html>"
-                    );
-        html.setHtml(sb);
-        player.sendPacket(html);
-	}
 
 
-	private void showChatWindow1st(L2PcInstance player) {
-                NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            html.setHtml(createGMClassMasterHtml(FIRST_CLASS_IDS));
-            player.sendPacket(html);
+		msg = msg.replaceAll("%menu%", menu.toString());
+		player.sendPacket(new TutorialShowHtml(msg));
 	}
 	}
 
 
-	private void showChatWindow2nd(L2PcInstance player) {
-            NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            html.setHtml(createGMClassMasterHtml(SECOND_CLASS_IDS));
-            player.sendPacket(html);
+	private static final boolean checkAndChangeClass(L2PcInstance player, int val)
+	{
+		final ClassId currentClassId = player.getClassId();
+		if (getMinLevel(currentClassId.level()) > player.getLevel()
+				&& !Config.ALLOW_ENTIRE_TREE)
+			return false;
+
+		if (!validateClassId(currentClassId, val))
+			return false;
+
+		player.setClassId(val);
+
+		if (player.isSubClassActive())
+			player.getSubClasses().get(player.getClassIndex()).setClassId(player.getActiveClass());
+		else
+			player.setBaseClass(player.getActiveClass());
+
+		player.broadcastUserInfo();
+		return true;
 	}
 	}
 
 
-	private void showChatWindow3rd(L2PcInstance player) {
-            NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            html.setHtml(createGMClassMasterHtml(THIRD_CLASS_IDS));
-            player.sendPacket(html);
+	/**
+	 * Returns minimum player level required for next class transfer
+	 * @param level - current skillId level (0 - start, 1 - first, etc)
+	 */
+	private static final int getMinLevel(int level)
+	{
+		switch (level)
+		{
+			case 0:
+				return 20;
+			case 1:
+				return 40;
+			case 2:
+				return 76;
+			default:
+				return Integer.MAX_VALUE;
+		}
 	}
 	}
 
 
-	private void showChatWindowBase(L2PcInstance player) {
-            NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
-            html.setHtml(createGMClassMasterHtml(BASE_CLASS_IDS));
-            player.sendPacket(html);
+	/**
+	 * Returns true if class change is possible
+	 * @param oldCID current player ClassId
+	 * @param val new class index
+	 * @return
+	 */
+	private static final boolean validateClassId(ClassId oldCID, int val)
+	{
+		try
+		{
+			return validateClassId(oldCID, ClassId.values()[val]);
+		}
+		catch (Exception e)
+		{
+			// possible ArrayOutOfBoundsException
+		}
+		return false;
 	}
 	}
 
 
-	private void changeClass(L2PcInstance player, int val)
+	/**
+	 * Returns true if class change is possible
+	 * @param oldCID current player ClassId
+	 * @param newCID new ClassId
+	 * @return true if class change is possible
+	 */
+	private static final boolean validateClassId(ClassId oldCID, ClassId newCID)
 	{
 	{
-		if (Config.DEBUG) _log.fine("Changing class to ClassId:"+val);
-        player.setClassId(val);
+		if (newCID == null || newCID.getRace() == null)
+			return false;
 
 
-        if (player.isSubClassActive())
-            player.getSubClasses().get(player.getClassIndex()).setClassId(player.getActiveClass());
-        else
-            player.setBaseClass(player.getActiveClass());
+		if (oldCID.equals(newCID.getParent()))
+			return true;
 
 
-		player.broadcastUserInfo();
-	}
+		if (Config.ALLOW_ENTIRE_TREE
+				&& newCID.childOf(oldCID))
+			return true;
 
 
-        private String createGMClassMasterHtml(final int[] classIds) {
-            final String objectIdString = String.valueOf(getObjectId());
-            final CharTemplateTable charTemplateTable =
-                    CharTemplateTable.getInstance();
-            final StringBuilder sbString =
-                    new StringBuilder(100 + classIds.length * 100);
-            sbString.append(
-                    "<html>" +
-                    "<body>" +
-                    "<table width=200>" +
-                    "<tr><td><center>GM Class Master:</center></td></tr>" +
-                    "<tr><td><br></td></tr>");
-
-            for (int classId : classIds) {
-                StringUtil.append(sbString,
-                        "<tr><td><a action=\"bypass -h npc_",
-                        objectIdString,
-                        "_change_class ",
-                        String.valueOf(classId),
-                        "\">Advance to ",
-                        charTemplateTable.getClassNameById(classId),
-                        "</a></td></tr>"
-                        );
-            }
-
-            sbString.append(
-                    "</table>" +
-                    "</body>" +
-                    "</html>"
-                    );
-
-            return sbString.toString();
-        }
+		return false;
+	}
 }
 }

+ 3 - 0
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/stat/PcStat.java

@@ -17,6 +17,7 @@ package net.sf.l2j.gameserver.model.actor.stat;
 import net.sf.l2j.Config;
 import net.sf.l2j.Config;
 import net.sf.l2j.gameserver.model.L2PetDataTable;
 import net.sf.l2j.gameserver.model.L2PetDataTable;
 import net.sf.l2j.gameserver.model.actor.L2Character;
 import net.sf.l2j.gameserver.model.actor.L2Character;
+import net.sf.l2j.gameserver.model.actor.instance.L2ClassMasterInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PetInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PetInstance;
 import net.sf.l2j.gameserver.model.base.Experience;
 import net.sf.l2j.gameserver.model.base.Experience;
@@ -209,6 +210,8 @@ public class PcStat extends PlayableStat
         	getActiveChar().setCurrentCp(getMaxCp());
         	getActiveChar().setCurrentCp(getMaxCp());
             getActiveChar().broadcastPacket(new SocialAction(getActiveChar().getObjectId(), SocialAction.LEVEL_UP));
             getActiveChar().broadcastPacket(new SocialAction(getActiveChar().getObjectId(), SocialAction.LEVEL_UP));
             getActiveChar().sendPacket(new SystemMessage(SystemMessageId.YOU_INCREASED_YOUR_LEVEL));
             getActiveChar().sendPacket(new SystemMessage(SystemMessageId.YOU_INCREASED_YOUR_LEVEL));
+
+            L2ClassMasterInstance.showQuestionMark(getActiveChar());
         }
         }
 
 
         getActiveChar().rewardSkills(); // Give Expertise skill of this level
         getActiveChar().rewardSkills(); // Give Expertise skill of this level

+ 24 - 3
L2_GameServer/java/net/sf/l2j/gameserver/model/itemcontainer/Inventory.java

@@ -595,6 +595,7 @@ public abstract class Inventory extends ItemContainer
 				return;
 				return;
 
 
 			boolean update = false;
 			boolean update = false;
+			boolean updateTimeStamp = false;
 			// Checks if equiped item is part of set
 			// Checks if equiped item is part of set
 			if (armorSet.containItem(slot, item.getItemId()))
 			if (armorSet.containItem(slot, item.getItemId()))
 			{
 			{
@@ -628,6 +629,21 @@ public abstract class Inventory extends ItemContainer
 									if (itemSkill != null)
 									if (itemSkill != null)
 									{
 									{
 										player.addSkill(itemSkill, false);
 										player.addSkill(itemSkill, false);
+
+										if (itemSkill.isActive())
+										{
+											if (player.getReuseTimeStamp().isEmpty() || !player.getReuseTimeStamp().containsKey(itemSkill.getId()))
+											{
+												int equipDelay = itemSkill.getEquipDelay();
+
+												if (equipDelay > 0)
+												{
+													player.addTimeStamp(itemSkill.getId(), itemSkill.getEquipDelay());
+													player.disableSkill(itemSkill.getId(), itemSkill.getEquipDelay());
+												}
+											}
+											updateTimeStamp = true;
+										}
 										update = true;
 										update = true;
 									}
 									}
 									else
 									else
@@ -688,7 +704,12 @@ public abstract class Inventory extends ItemContainer
 			}
 			}
 
 
 			if (update)
 			if (update)
+			{
 				player.sendSkillList();
 				player.sendSkillList();
+
+				if (updateTimeStamp)
+					player.sendPacket(new SkillCoolTime(player));
+			}
 		}
 		}
 
 
 		public void notifyUnequiped(int slot, L2ItemInstance item)
 		public void notifyUnequiped(int slot, L2ItemInstance item)
@@ -764,7 +785,7 @@ public abstract class Inventory extends ItemContainer
 							{
 							{
 								itemSkill = SkillTable.getInstance().getInfo(skillId, skillLvl);
 								itemSkill = SkillTable.getInstance().getInfo(skillId, skillLvl);
 								if (itemSkill != null)
 								if (itemSkill != null)
-									player.removeSkill(itemSkill, false);
+									player.removeSkill(itemSkill, false, itemSkill.isPassive());
 								else
 								else
 									_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+skillInfo+".");
 									_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+skillInfo+".");
 							}
 							}
@@ -776,7 +797,7 @@ public abstract class Inventory extends ItemContainer
 				{
 				{
 					L2Skill skill = SkillTable.getInstance().getInfo(shieldSkill,1);
 					L2Skill skill = SkillTable.getInstance().getInfo(shieldSkill,1);
 					if (skill != null)
 					if (skill != null)
-						player.removeSkill(skill);
+						player.removeSkill(skill, false, skill.isPassive());
 					else
 					else
 						_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+shieldSkill+".");
 						_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+shieldSkill+".");
 				}
 				}
@@ -785,7 +806,7 @@ public abstract class Inventory extends ItemContainer
 				{
 				{
 					L2Skill skill = SkillTable.getInstance().getInfo(skillId6,1);
 					L2Skill skill = SkillTable.getInstance().getInfo(skillId6,1);
 					if (skill != null)
 					if (skill != null)
-						player.removeSkill(skill);
+						player.removeSkill(skill, false, skill.isPassive());
 					else
 					else
 						_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+skillId6+".");
 						_log.warning("Inventory.ArmorSetListener: Incorrect skill: "+skillId6+".");
 				}
 				}

+ 3 - 0
L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java

@@ -48,6 +48,7 @@ import net.sf.l2j.gameserver.model.L2Clan;
 import net.sf.l2j.gameserver.model.L2ItemInstance;
 import net.sf.l2j.gameserver.model.L2ItemInstance;
 import net.sf.l2j.gameserver.model.L2World;
 import net.sf.l2j.gameserver.model.L2World;
 import net.sf.l2j.gameserver.model.actor.L2Character;
 import net.sf.l2j.gameserver.model.actor.L2Character;
+import net.sf.l2j.gameserver.model.actor.instance.L2ClassMasterInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.entity.ClanHall;
 import net.sf.l2j.gameserver.model.entity.ClanHall;
 import net.sf.l2j.gameserver.model.entity.Couple;
 import net.sf.l2j.gameserver.model.entity.Couple;
@@ -438,6 +439,8 @@ public class EnterWorld extends L2GameClientPacket
 		RegionBBSManager.getInstance().changeCommunityBoard();
 		RegionBBSManager.getInstance().changeCommunityBoard();
 
 
 		TvTEvent.onLogin(activeChar);
 		TvTEvent.onLogin(activeChar);
+
+		L2ClassMasterInstance.showQuestionMark(activeChar);
 	}
 	}
 
 
 	/**
 	/**

+ 3 - 0
L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/RequestTutorialLinkHtml.java

@@ -14,6 +14,7 @@
  */
  */
 package net.sf.l2j.gameserver.network.clientpackets;
 package net.sf.l2j.gameserver.network.clientpackets;
 
 
+import net.sf.l2j.gameserver.model.actor.instance.L2ClassMasterInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 
 
@@ -34,6 +35,8 @@ public class RequestTutorialLinkHtml extends L2GameClientPacket
 		if(player == null)
 		if(player == null)
 			return;
 			return;
 
 
+		L2ClassMasterInstance.onTitorialLink(player, _bypass);
+
 		QuestState qs = player.getQuestState("255_Tutorial");
 		QuestState qs = player.getQuestState("255_Tutorial");
 		if(qs != null)
 		if(qs != null)
 			qs.getQuest().notifyEvent(_bypass, null, player);
 			qs.getQuest().notifyEvent(_bypass, null, player);

+ 3 - 0
L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/RequestTutorialQuestionMark.java

@@ -14,6 +14,7 @@
  */
  */
 package net.sf.l2j.gameserver.network.clientpackets;
 package net.sf.l2j.gameserver.network.clientpackets;
 
 
+import net.sf.l2j.gameserver.model.actor.instance.L2ClassMasterInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 
 
@@ -33,6 +34,8 @@ public class RequestTutorialQuestionMark extends L2GameClientPacket
 		if(player == null)
 		if(player == null)
 			return;
 			return;
 
 
+		L2ClassMasterInstance.onTutorialQuestionMark(player, _number);
+
 		QuestState qs = player.getQuestState("255_Tutorial");
 		QuestState qs = player.getQuestState("255_Tutorial");
 		if(qs != null)
 		if(qs != null)
 			qs.getQuest().notifyEvent("QM" + _number + "",null,player);
 			qs.getQuest().notifyEvent("QM" + _number + "",null,player);