Edoo's code reworked and a real patch..
- todo: needs a npc of type L2ProfMaster and spawns
- todo: gm handling
http://www.pastebin.cz/30223
Code: Select all
Index: java/com/l2jserver/Config.java===================================================================--- java/com/l2jserver/Config.java (revision 3799)+++ java/com/l2jserver/Config.java (working copy)@@ -29,9 +29,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List;+import java.util.StringTokenizer; import java.util.logging.Logger; import javolution.util.FastList;+import javolution.util.FastMap; import com.l2jserver.gameserver.util.FloodProtectorConfig; import com.l2jserver.gameserver.util.StringUtil;@@ -104,6 +106,9 @@ public static boolean ALT_GAME_SHIELD_BLOCKS; public static int ALT_PERFECT_SHLD_BLOCK; public static boolean ALLOW_CLASS_MASTERS;+ public static boolean ALLOW_CLASS_MASTERS2;+ public static String CLASS_MASTER_SETTINGS_LINE2;+ public static ClassMasterSettings2 CLASS_MASTER_SETTINGS2; public static boolean ALLOW_ENTIRE_TREE; public static boolean ALTERNATE_CLASS_MASTER; public static boolean LIFE_CRYSTAL_NEEDED;@@ -1308,6 +1313,10 @@ ALT_GAME_SHIELD_BLOCKS = Boolean.parseBoolean(Character.getProperty("AltShieldBlocks", "false")); ALT_PERFECT_SHLD_BLOCK = Integer.parseInt(Character.getProperty("AltPerfectShieldBlockRate", "10")); ALLOW_CLASS_MASTERS = Boolean.parseBoolean(Character.getProperty("AllowClassMasters", "False"));+ ALLOW_CLASS_MASTERS2 = Boolean.parseBoolean(Character.getProperty("AllowClassMasters2", "False"));+ if (!Character.getProperty("ConfigClassMaster2").trim().equalsIgnoreCase("False"))+ CLASS_MASTER_SETTINGS_LINE2 = Character.getProperty("ConfigClassMaster2");+ CLASS_MASTER_SETTINGS2 = new ClassMasterSettings2(CLASS_MASTER_SETTINGS_LINE2); 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"));@@ -2856,4 +2865,86 @@ config.PUNISHMENT_TYPE = properties.getProperty(StringUtil.concat("FloodProtector", configString, "PunishmentType"), "none"); config.PUNISHMENT_TIME = Integer.parseInt(properties.getProperty(StringUtil.concat("FloodProtector", configString, "PunishmentTime"), "0")); }+ + // Class Master Config+ public static class ClassMasterSettings2+ {+ private FastMap<Integer, FastMap<Integer, Integer>> _claimItems;+ private FastMap<Integer, FastMap<Integer, Integer>> _rewardItems;+ private FastMap<Integer, Boolean> _allowedClassChange;+ + public ClassMasterSettings2(String _configLine)+ {+ _claimItems = new FastMap<Integer, FastMap<Integer, Integer>>();+ _rewardItems = new FastMap<Integer, FastMap<Integer, Integer>>();+ _allowedClassChange = new FastMap<Integer, Boolean>();+ if (_configLine != null)+ parseConfigLine(_configLine.trim());+ }+ + private void parseConfigLine(String _configLine)+ {+ StringTokenizer st = new StringTokenizer(_configLine, ";");+ while (st.hasMoreTokens())+ {+ // get allowed class change+ int job = Integer.parseInt(st.nextToken());+ _allowedClassChange.put(job, true);+ FastMap<Integer, Integer> _items = new FastMap<Integer, Integer>();+ // parse items needed for class change+ if (st.hasMoreTokens())+ {+ StringTokenizer st2 = new StringTokenizer(st.nextToken(), "[],");+ while (st2.hasMoreTokens())+ {+ StringTokenizer st3 = new StringTokenizer(st2.nextToken(), "()");+ int _itemId = Integer.parseInt(st3.nextToken());+ int _quantity = Integer.parseInt(st3.nextToken());+ _items.put(_itemId, _quantity);+ }+ }+ _claimItems.put(job, _items);+ _items = new FastMap<Integer, Integer>();+ // parse gifts after class change+ if (st.hasMoreTokens())+ {+ StringTokenizer st2 = new StringTokenizer(st.nextToken(), "[],");+ while (st2.hasMoreTokens())+ {+ StringTokenizer st3 = new StringTokenizer(st2.nextToken(), "()");+ int _itemId = Integer.parseInt(st3.nextToken());+ int _quantity = Integer.parseInt(st3.nextToken());+ _items.put(_itemId, _quantity);+ }+ }+ _rewardItems.put(job, _items);+ }+ }+ + public boolean isAllowed(int job)+ {+ if (_allowedClassChange == null)+ return false;+ if (_allowedClassChange.containsKey(job))+ return _allowedClassChange.get(job);+ else+ return false;+ }+ + public FastMap<Integer, Integer> getRewardItems(int job)+ {+ if (_rewardItems.containsKey(job))+ return _rewardItems.get(job);+ else+ return null;+ }+ + public FastMap<Integer, Integer> getRequireItems(int job)+ {+ if (_claimItems.containsKey(job))+ return _claimItems.get(job);+ else+ return null;+ }+ } }Index: java/com/l2jserver/gameserver/model/actor/instance/L2ProfMasterInstance.java===================================================================--- java/com/l2jserver/gameserver/model/actor/instance/L2ProfMasterInstance.java (revision 0)+++ java/com/l2jserver/gameserver/model/actor/instance/L2ProfMasterInstance.java (revision 0)@@ -0,0 +1,253 @@+/*+ * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>.+ */+package com.l2jserver.gameserver.model.actor.instance;++import java.util.logging.Logger;++import javolution.text.TextBuilder;++import com.l2jserver.Config;+import com.l2jserver.gameserver.ai.CtrlIntention;+import com.l2jserver.gameserver.datatables.CharTemplateTable;+import com.l2jserver.gameserver.datatables.ItemTable;+import com.l2jserver.gameserver.model.base.ClassId;+import com.l2jserver.gameserver.model.quest.Quest;+import com.l2jserver.gameserver.network.SystemMessageId;+import com.l2jserver.gameserver.network.serverpackets.ActionFailed;+import com.l2jserver.gameserver.network.serverpackets.MyTargetSelected;+import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage;+import com.l2jserver.gameserver.network.serverpackets.SystemMessage;+import com.l2jserver.gameserver.network.serverpackets.ValidateLocation;+import com.l2jserver.gameserver.templates.chars.L2NpcTemplate;++/**+ * Class Master implementation+ * the npc is used for changing character occupation+ * Reworked for by -= Hell Bound project (Edoo) =-+ **/+public final class L2ProfMasterInstance extends L2NpcInstance+{+ protected static final Logger _logClassMaster = Logger.getLogger(L2ProfMasterInstance.class.getName());+ + public L2ProfMasterInstance(int objectId, L2NpcTemplate template)+ {+ super(objectId, template);+ }+ + @Override+ public void onAction(L2PcInstance player, boolean interact)+ {+ if (!canTarget(player))+ return;+ + 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);+ + // Send a Server->Client packet MyTargetSelected to the L2PcInstance player+ player.sendPacket(new MyTargetSelected(getObjectId(), 0));+ + // Send a Server->Client packet ValidateLocation to correct the L2NpcInstance position and heading on the client+ player.sendPacket(new ValidateLocation(this));+ }+ else if (interact)+ {+ // Calculate the distance between the L2PcInstance and the L2NpcInstance+ if (!canInteract(player))+ {+ // Notify the L2PcInstance AI with AI_INTENTION_INTERACT+ player.getAI().setIntention(CtrlIntention.AI_INTENTION_INTERACT, this);+ return;+ }+ + NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());+ TextBuilder sb = new TextBuilder();+ sb.append("<html><body>");+ sb.append(getName() + ":<br>");+ sb.append("<br>");+ + ClassId classId = player.getClassId();+ int level = player.getLevel();+ int jobLevel = classId.level();+ + int newJobLevel = jobLevel + 1;+ + if (((level >= 20 && jobLevel == 0) || (level >= 40 && jobLevel == 1) || (level >= 76 && jobLevel == 2)) && Config.CLASS_MASTER_SETTINGS2.isAllowed(newJobLevel))+ {+ sb.append("You can change your occupation to following:<br>");+ + for (ClassId child : ClassId.values())+ {+ if (child.childOf(classId) && child.level() == newJobLevel)+ sb.append("<br><a action=\"bypass -h npc_" + getObjectId() + "_change_class " + (child.getId()) + "\"> " + CharTemplateTable.getInstance().getClassNameById(child.getId()) + "</a>");+ }+ + if (Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel) != null && !Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).isEmpty())+ {+ sb.append("<br><br>Item(s) required for class change:");+ sb.append("<table width=270>");+ for (Integer _itemId : Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).keySet())+ {+ int _count = Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).get(_itemId);+ sb.append("<tr><td><font color=\"LEVEL\">" + _count + "</font></td><td>" + ItemTable.getInstance().getTemplate(_itemId).getName() + "</td></tr>");+ }+ sb.append("</table>");+ }+ }+ else+ {+ switch (jobLevel)+ {+ case 0:+ if (Config.CLASS_MASTER_SETTINGS2.isAllowed(1))+ sb.append("Come back here when you reached level 20 to change your class.<br>");+ else if (Config.CLASS_MASTER_SETTINGS2.isAllowed(2))+ sb.append("Come back after your first occupation change.<br>");+ else if (Config.CLASS_MASTER_SETTINGS2.isAllowed(3))+ sb.append("Come back after your second occupation change.<br>");+ else+ sb.append("I can't change your occupation.<br>");+ break;+ case 1:+ if (Config.CLASS_MASTER_SETTINGS2.isAllowed(2))+ sb.append("Come back here when you reached level 40 to change your class.<br>");+ else if (Config.CLASS_MASTER_SETTINGS2.isAllowed(3))+ sb.append("Come back after your second occupation change.<br>");+ else+ sb.append("I can't change your occupation.<br>");+ break;+ case 2:+ if (Config.CLASS_MASTER_SETTINGS2.isAllowed(3))+ sb.append("Come back here when you reached level 76 to change your class.<br>");+ else+ sb.append("I can't change your occupation.<br>");+ break;+ case 3:+ sb.append("There is no class change available for you anymore.<br>");+ break;+ }+ sb.append("<br>");+ }+ + for (Quest q : Quest.findAllEvents())+ sb.append("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 void onBypassFeedback(L2PcInstance player, String command)+ {+ if (command.startsWith("change_class"))+ {+ int val = Integer.parseInt(command.substring(13));+ + ClassId classId = player.getClassId();+ ClassId newClassId = ClassId.values()[val];+ + int level = player.getLevel();+ int jobLevel = classId.level();+ int newJobLevel = newClassId.level();+ + // -- exploit prevention+ // prevents changing if config option disabled+ if (!Config.CLASS_MASTER_SETTINGS2.isAllowed(newJobLevel))+ return;+ + // prevents changing to class not in same class tree+ if (!newClassId.childOf(classId))+ return;+ + // prevents changing between same level jobs+ if (newJobLevel != jobLevel + 1)+ return;+ + // check for player level+ if (level < 20 && newJobLevel > 1)+ return;+ if (level < 40 && newJobLevel > 2)+ return;+ if (level < 76 && newJobLevel > 3)+ return;+ // -- prevention ends+ + // check if player have all required items for class transfer+ for (Integer _itemId : Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).keySet())+ {+ int _count = Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).get(_itemId);+ if (player.getInventory().getInventoryItemCount(_itemId, -1) < _count)+ {+ player.sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));+ return;+ }+ }+ + // get all required items for class transfer+ for (Integer _itemId : Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).keySet())+ {+ int _count = Config.CLASS_MASTER_SETTINGS2.getRequireItems(newJobLevel).get(_itemId);+ player.destroyItemByItemId("ClassMaster", _itemId, _count, player, true);+ }+ + // reward player with items+ for (Integer _itemId : Config.CLASS_MASTER_SETTINGS2.getRewardItems(newJobLevel).keySet())+ {+ int _count = Config.CLASS_MASTER_SETTINGS2.getRewardItems(newJobLevel).get(_itemId);+ player.addItem("ClassMaster", _itemId, _count, player, true);+ }+ + changeClass(player, val);+ + player.rewardSkills();+ + NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());+ TextBuilder sb = new TextBuilder();+ sb.append("<html><body>");+ sb.append(getName() + ":<br>");+ sb.append("<br>");+ sb.append("You have now become a <font color=\"LEVEL\">" + CharTemplateTable.getInstance().getClassNameById(player.getClassId().getId()) + "</font>.");+ sb.append("</body></html>");+ html.setHtml(sb.toString());+ player.sendPacket(html);+ + // Update the overloaded status of the L2PcInstance+ player.refreshOverloaded();+ // Update the expertise status of the L2PcInstance+ player.refreshExpertisePenalty();+ }+ }+ + private void changeClass(L2PcInstance player, int val)+ {+ if (Config.DEBUG)+ _logClassMaster.fine("Changing class to ClassId:" + val);+ + player.setClassId(val);+ + if (player.isSubClassActive())+ player.getSubClasses().get(player.getClassIndex()).setClassId(player.getActiveClass());+ else+ player.setBaseClass(player.getActiveClass());+ + player.broadcastUserInfo();+ }+}Index: java/config/Character.properties===================================================================--- java/config/Character.properties (revision 3799)+++ java/config/Character.properties (working copy)@@ -150,6 +150,23 @@ # Default: False AllowClassMasters = False +# Class master with configurable price and reward+AllowClassMasters2 = False+ +# Config for special Class Master npc that can change players occupation+# If you need change occupation only use quest then set this to False (Default)+# Syntax: occupation number;[required item id(count)],[],...;[reward item id(count)],[],...;occupation number...+# Examples:+# ConfigClassMaster = 1;[57(100000)];[];2;[57(1000000)];[];3;[57(10000000)],[5575(1000000)];[6622(1)]+# 1st occupation change for 100.000 Adena (item id 57)+# 2nd occupation change for 1.000.0000 Adena (item id 57)+# 3rd occupation change for 10.000.0000 Adena (item id 57) and 1.000.000 Ancient Adena (item id 5575)+# on 3rd occupation change player will be rewarded with 1 Book of Giants (item id 6622)+# ConfigClassMaster = 1;[];[];2;[];[];3;[];[]+# 1st, 2nd, 3rd occupation change for free, without rewards+ConfigClassMaster2 = 1;[57(100000)];[];2;[57(2000000)];[5575(1000)];3;[57(10000000)];[5575(10000)]+#ConfigClassMaster2 = 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.
dp part (need to enable custom_npc table in config!), e.g.
Code: Select all
INSERT INTO `custom_npc` VALUES ('90001', '31756', 'Mr. Cat', '0', 'Test Server Helper', '0', 'LineageMonster.cat_the_cat', '9.00', '16.00', '70', 'male', 'L2ProfMaster', '40', '3862', '1493', '11.85', '2.78', '40', '43', '30', '21', '20', '10', '0', '0', '1314', '470', '780', '382', '278', '0', '333', '0', '0', '0', '0', '80', '120', 'NULL', '0', '0', '0', 'LAST_HIT', '0', '0', '0', 'fighter', 'false');INSERT INTO `custom_npc` VALUES ('90002', '31757', 'Miss Queen', '0', 'Test Server Helper', '0', 'LineageMonster2.queen_of_cat', '8.00', '15.00', '70', 'female', 'L2ProfMaster', '40', '3862', '1493', '11.85', '2.78', '40', '43', '30', '21', '20', '10', '0', '0', '1314', '470', '780', '382', '278', '0', '333', '0', '0', '0', '0', '26', '120', 'NULL', '0', '0', '0', 'LAST_HIT', '0', '0', '0', 'fighter', 'false');
and then spawn the classmaster with //spawn 90001 or //spawn 90002