Storybook announce + AOE Mobskills & Autopot & Inventory check Patch

Implemented storybook detection for the skillbook announcer.
Fixed an issue with AOE mob skills not locating players properly in certain situations.
Reviewed interaction of players within same remote network on multiclient system.
Patched messages when leaving the expedition area showing up for leaving players.
Patched some skill questline item chances unexpectedly too low.
Reviewed Rush questline-exclusive area not allowing multiple players in the room.
Fixed meso capacity check on shop owner happening after item transaction.
Patched server-side autopot settings, now using threshold cooling method to determine a player's updated settings.
Reviewed locking flow regarding world/channel deployment on main class.
Fixed a scenario where items collectable by party members would not show up in case those were the only items to be dropped and none needed by the last-hitter.
Patched an issue in the inventory space checking algorithm that would allow a transaction for multiple of the same equipments.
Refactored Dojo entry/exit modules.
Patched a check on Maker skill.
Fixed Cygnus FA buff getting reapplied each time the skill procs.
This commit is contained in:
ronancpl
2019-11-26 01:13:15 -03:00
parent 661dcf0a96
commit bb586a7c0b
49 changed files with 658 additions and 372 deletions
+30 -23
View File
@@ -144,6 +144,7 @@ import client.processor.action.PetAutopotProcessor;
import constants.game.ExpTable;
import constants.game.GameConstants;
import constants.inventory.ItemConstants;
import constants.net.ServerConstants;
import constants.skills.Aran;
import constants.skills.Beginner;
import constants.skills.Bishop;
@@ -279,8 +280,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
private Map<Integer, MapleSummon> summons = new LinkedHashMap<>();
private Map<Integer, MapleCoolDownValueHolder> coolDowns = new LinkedHashMap<>();
private EnumMap<MapleDisease, Pair<MapleDiseaseValueHolder, MobSkill>> diseases = new EnumMap<>(MapleDisease.class);
public byte[] m_aQuickslotLoaded;
public MapleQuickslotBinding m_pQuickslotKeyMapped;
private byte[] m_aQuickslotLoaded;
private MapleQuickslotBinding m_pQuickslotKeyMapped;
private MapleDoor pdoor = null;
private Map<MapleQuest, Long> questExpirations = new LinkedHashMap<>();
private ScheduledFuture<?> dragonBloodSchedule;
@@ -678,7 +679,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
int pts = 0;
if (dojoPoints < 17000) {
pts = 1 + ((mapid - 1) / 100 % 100) / 6;
if (!getDojoParty()) {
if (!GameConstants.isDojoPartyArea(this.getMapId())) {
pts++;
}
this.dojoPoints += pts;
@@ -1322,6 +1323,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
public void changeQuickslotKeybinding(byte[] aQuickslotKeyMapped) {
this.m_pQuickslotKeyMapped = new MapleQuickslotBinding(aQuickslotKeyMapped);
}
public void broadcastStance(int newStance) {
setStance(newStance);
broadcastStance();
@@ -1820,6 +1825,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
} else {
FilePrinter.printError(FilePrinter.MAPLE_MAP, "Character " + this.getName() + " got stuck when moving to map " + map.getId() + ".");
client.disconnect(true, false); // thanks BHB for noticing a player storage stuck case here
return;
}
notifyMapTransferToPartner(map.getId());
@@ -2804,9 +2811,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
dispelDebuff(MapleDisease.POISON);
dispelDebuff(MapleDisease.SEAL);
dispelDebuff(MapleDisease.WEAKEN);
dispelDebuff(MapleDisease.SLOW);
dispelDebuff(MapleDisease.ZOMBIFY);
dispelDebuff(MapleDisease.CONFUSE);
dispelDebuff(MapleDisease.SLOW); // thanks Conrad for noticing ZOMBIFY isn't dispellable
}
public void cancelAllDebuffs() {
@@ -4772,10 +4777,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
return dojoEnergy;
}
public boolean getDojoParty() {
return mapid >= 925030100 && mapid < 925040000;
}
public int getDojoPoints() {
return dojoPoints;
}
@@ -5322,6 +5323,14 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
public MapleRing getMarriageRing() {
return partnerId > 0 ? marriageRing : null;
}
public int getMasterLevel(int skill) {
SkillEntry ret = skills.get(SkillFactory.getSkill(skill));
if (ret == null) {
return 0;
}
return ret.masterlevel;
}
public int getMasterLevel(Skill skill) {
if (skills.get(skill) == null) {
@@ -8351,9 +8360,9 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
ps.close();
// No quickslots, or no change.
boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.m_aQuickslotKeyMapped, this.m_aQuickslotLoaded));
boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.GetKeybindings(), this.m_aQuickslotLoaded));
if (!bQuickslotEquals) {
long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.m_aQuickslotKeyMapped);
long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.GetKeybindings());
try (final PreparedStatement pInsertStatement = con.prepareStatement("INSERT INTO quickslotkeymapped (accountid, keymap) VALUES (?, ?) ON DUPLICATE KEY UPDATE keymap = ?;")) {
pInsertStatement.setInt(1, this.getAccountID());
@@ -8616,9 +8625,9 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
ps.close();
// No quickslots, or no change.
boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.m_aQuickslotKeyMapped, this.m_aQuickslotLoaded));
boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.GetKeybindings(), this.m_aQuickslotLoaded));
if (!bQuickslotEquals) {
long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.m_aQuickslotKeyMapped);
long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.GetKeybindings());
try (final PreparedStatement pInsertStatement = con.prepareStatement("INSERT INTO quickslotkeymapped (accountid, keymap) VALUES (?, ?) ON DUPLICATE KEY UPDATE keymap = ?;")) {
pInsertStatement.setInt(1, this.getAccountID());
@@ -9214,9 +9223,11 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
MapleKeyBinding autohpPot = this.getKeymap().get(91);
if (autohpPot != null) {
int autohpItemid = autohpPot.getAction();
if (((float) this.getHp()) / this.getCurrentMaxHp() <= this.getAutopotHpAlert()) { // try within user settings... thanks Lame, Optimist, Stealth2800
float autohpAlert = this.getAutopotHpAlert();
if (((float) this.getHp()) / this.getCurrentMaxHp() <= autohpAlert) { // try within user settings... thanks Lame, Optimist, Stealth2800
Item autohpItem = this.getInventory(MapleInventoryType.USE).findById(autohpItemid);
if (autohpItem != null) {
this.setAutopotHpAlert(0.9f * autohpAlert);
PetAutopotProcessor.runAutopotAction(client, autohpItem.getPosition(), autohpItemid);
}
}
@@ -9227,9 +9238,11 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
MapleKeyBinding autompPot = this.getKeymap().get(92);
if (autompPot != null) {
int autompItemid = autompPot.getAction();
if (((float) this.getMp()) / this.getCurrentMaxMp() <= this.getAutopotMpAlert()) {
float autompAlert = this.getAutopotMpAlert();
if (((float) this.getMp()) / this.getCurrentMaxMp() <= autompAlert) {
Item autompItem = this.getInventory(MapleInventoryType.USE).findById(autompItemid);
if (autompItem != null) {
this.setAutopotMpAlert(0.9f * autompAlert); // autoMP would stick to using pots at every depletion in some cases... thanks Rohenn
PetAutopotProcessor.runAutopotAction(client, autompItem.getPosition(), autompItemid);
}
}
@@ -9689,17 +9702,11 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
public void showDojoClock() {
if (map.isDojoFightMap()) {
if (GameConstants.isDojoBossArea(map.getId())) {
client.announce(MaplePacketCreator.getClock((int) (getDojoTimeLeft() / 1000)));
}
}
public void timeoutFromDojo() {
if(map.isDojoMap()) {
client.getPlayer().changeMap(client.getChannelServer().getMapFactory().getMap(925020002));
}
}
public void showUnderleveledInfo(MapleMonster mob) {
long curTime = Server.getInstance().getCurrentTime();
if(nextWarningTime < curTime) {
+26 -2
View File
@@ -25,12 +25,14 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
@@ -448,6 +450,20 @@ public class MapleInventory implements Iterable<Item> {
}
}
private static boolean checkItemRestricted(List<Pair<Item, MapleInventoryType>> items) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
Set<Integer> itemids = new HashSet<>();
for (Pair<Item, MapleInventoryType> p : items) {
int itemid = p.getLeft().getItemId();
if (ii.isPickupRestricted(itemid) && (p.getLeft().getQuantity() > 1 || !itemids.add(itemid))) {
return false;
}
}
return true;
}
public static boolean checkSpot(MapleCharacter chr, Item item) { // thanks Vcoc for noticing pshops not checking item stacks when taking item back
return checkSpot(chr, Collections.singletonList(item));
}
@@ -476,6 +492,10 @@ public class MapleInventory implements Iterable<Item> {
public static boolean checkSpots(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items, List<Integer> typesSlotsUsed, boolean useProofInv) {
// assumption: no "UNDEFINED" or "EQUIPPED" items shall be tested here, all counts are >= 0.
if (!checkItemRestricted(items)) {
return false;
}
Map<Integer, List<Integer>> rcvItems = new LinkedHashMap<>();
Map<Integer, Byte> rcvTypes = new LinkedHashMap<>();
@@ -490,7 +510,7 @@ public class MapleInventory implements Iterable<Item> {
rcvItems.put(itemId, itemQtyList);
rcvTypes.put(itemId, item.right.getType());
} else {
if (!ItemConstants.isRechargeable(itemId)) {
if (!ItemConstants.isEquipment(itemId) && !ItemConstants.isRechargeable(itemId)) {
qty.set(0, qty.get(0) + item.left.getQuantity());
} else {
qty.add((int) item.left.getQuantity());
@@ -548,6 +568,10 @@ public class MapleInventory implements Iterable<Item> {
public static boolean checkSpotsAndOwnership(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items, List<Integer> typesSlotsUsed, boolean useProofInv) {
//assumption: no "UNDEFINED" or "EQUIPPED" items shall be tested here, all counts are >= 0 and item list to be checked is a legal one.
if (!checkItemRestricted(items)) {
return false;
}
Map<Long, List<Integer>> rcvItems = new LinkedHashMap<>();
Map<Long, Byte> rcvTypes = new LinkedHashMap<>();
Map<Long, String> rcvOwners = new LinkedHashMap<>();
@@ -565,7 +589,7 @@ public class MapleInventory implements Iterable<Item> {
rcvOwners.put(itemHash, item.left.getOwner());
} else {
// thanks BHB88 for pointing out an issue with rechargeable items being stacked on inventory check
if (!ItemConstants.isRechargeable(item.left.getItemId())) {
if (!ItemConstants.isEquipment(item.left.getItemId()) && !ItemConstants.isRechargeable(item.left.getItemId())) {
qty.set(0, qty.get(0) + item.left.getQuantity());
} else {
qty.add((int) item.left.getQuantity());
@@ -106,7 +106,7 @@ public class MapleInventoryManipulator {
}
}
boolean sandboxItem = (flag & ItemConstants.SANDBOX) == ItemConstants.SANDBOX;
while (quantity > 0 || ItemConstants.isRechargeable(itemId)) {
while (quantity > 0) {
short newQ = (short) Math.min(quantity, slotMax);
if (newQ != 0) {
quantity -= newQ;
@@ -124,9 +124,6 @@ public class MapleInventoryManipulator {
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem))));
if(sandboxItem) chr.setHasSandboxItem();
if ((ItemConstants.isRechargeable(itemId)) && quantity == 0) {
break;
}
} else {
c.announce(MaplePacketCreator.enableActions());
return false;
@@ -189,8 +186,8 @@ public class MapleInventoryManipulator {
private static boolean addFromDropInternal(MapleClient c, MapleCharacter chr, MapleInventoryType type, MapleInventory inv, Item item, boolean show, int petId) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if (ii.isPickupRestricted(item.getItemId()) && chr.haveItemWithId(item.getItemId(), true)) {
int itemid = item.getItemId();
if (ii.isPickupRestricted(itemid) && chr.haveItemWithId(itemid, true)) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.showItemUnavailable());
return false;
@@ -198,9 +195,9 @@ public class MapleInventoryManipulator {
short quantity = item.getQuantity();
if (!type.equals(MapleInventoryType.EQUIP)) {
short slotMax = ii.getSlotMax(c, item.getItemId());
List<Item> existing = inv.listById(item.getItemId());
if (!ItemConstants.isRechargeable(item.getItemId()) && petId == -1) {
short slotMax = ii.getSlotMax(c, itemid);
List<Item> existing = inv.listById(itemid);
if (!ItemConstants.isRechargeable(itemid) && petId == -1) {
if (existing.size() > 0) { // first update all existing slots to slotMax
Iterator<Item> i = existing.iterator();
while (quantity > 0) {
@@ -222,7 +219,7 @@ public class MapleInventoryManipulator {
while (quantity > 0) {
short newQ = (short) Math.min(quantity, slotMax);
quantity -= newQ;
Item nItem = new Item(item.getItemId(), (short) 0, newQ, petId);
Item nItem = new Item(itemid, (short) 0, newQ, petId);
nItem.setExpiration(item.getExpiration());
nItem.setOwner(item.getOwner());
nItem.setFlag(item.getFlag());
@@ -239,7 +236,7 @@ public class MapleInventoryManipulator {
if (MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem();
}
} else {
Item nItem = new Item(item.getItemId(), (short) 0, quantity, petId);
Item nItem = new Item(itemid, (short) 0, quantity, petId);
nItem.setExpiration(item.getExpiration());
nItem.setFlag(item.getFlag());
@@ -266,13 +263,13 @@ public class MapleInventoryManipulator {
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, item))));
if (MapleInventoryManipulator.isSandboxItem(item)) chr.setHasSandboxItem();
} else {
FilePrinter.printError(FilePrinter.ITEM, "Tried to pickup Equip id " + item.getItemId() + " containing more than 1 quantity --> " + quantity);
FilePrinter.printError(FilePrinter.ITEM, "Tried to pickup Equip id " + itemid + " containing more than 1 quantity --> " + quantity);
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.showItemUnavailable());
return false;
}
if (show) {
c.announce(MaplePacketCreator.getShowItemGain(item.getItemId(), item.getQuantity()));
c.announce(MaplePacketCreator.getShowItemGain(itemid, item.getQuantity()));
}
return true;
}
@@ -17,7 +17,7 @@ public class MapleQuickslotBinding
0x2A, 0x52, 0x47, 0x49, 0x1D, 0x53, 0x4F, 0x51
};
public byte[] m_aQuickslotKeyMapped;
private byte[] m_aQuickslotKeyMapped;
// Initializes quickslot object for the user.
// aKeys' length has to be 8.
@@ -52,4 +52,9 @@ public class MapleQuickslotBinding
oPacket.writeInt(nKey);
}
}
public byte[] GetKeybindings() {
return m_aQuickslotKeyMapped;
}
}
@@ -308,7 +308,7 @@ public class MakerProcessor {
}
private static short getCreateStatus(MapleClient c, MakerItemCreateEntry recipe) {
if(recipe == null) {
if(recipe.isInvalid()) {
return -1;
}
+9
View File
@@ -585,6 +585,15 @@ public class GameConstants {
return mapid >= 925020000 && mapid < 925040000;
}
public static boolean isDojoPartyArea(int mapid) {
return mapid >= 925030100 && mapid < 925040000;
}
public static boolean isDojoBossArea(int mapid) {
return isDojo(mapid) && (((mapid / 100) % 100) % 6) > 0;
}
public static boolean isPyramid(int mapid) {
return mapid >= 926010010 & mapid <= 930010000;
}
+100 -69
View File
@@ -320,7 +320,7 @@ public class Server {
private void dumpData() {
wldWLock.lock();
wldRLock.lock();
try {
System.out.println(worlds);
System.out.println(channels);
@@ -328,34 +328,44 @@ public class Server {
System.out.println();
System.out.println("---------------------");
} finally {
wldWLock.unlock();
wldRLock.unlock();
}
}
public int addChannel(int worldid) {
wldWLock.lock();
World world;
Map<Integer, String> channelInfo;
int channelid;
wldRLock.lock();
try {
if(worldid >= worlds.size()) return -3;
Map<Integer, String> worldChannels = channels.get(worldid);
if(worldChannels == null) return -3;
channelInfo = channels.get(worldid);
if(channelInfo == null) return -3;
int channelid = worldChannels.size();
channelid = channelInfo.size();
if(channelid >= YamlConfig.config.server.CHANNEL_SIZE) return -2;
channelid++;
World world = this.getWorld(worldid);
Channel channel = new Channel(worldid, channelid, getCurrentTime());
channel.setServerMessage(YamlConfig.config.worlds.get(worldid).why_am_i_recommended);
world.addChannel(channel);
worldChannels.put(channelid, channel.getIP());
return channelid;
world = this.getWorld(worldid);
} finally {
wldWLock.unlock();
wldRLock.unlock();
}
Channel channel = new Channel(worldid, channelid, getCurrentTime());
channel.setServerMessage(YamlConfig.config.worlds.get(worldid).why_am_i_recommended);
if (world.addChannel(channel)) {
wldWLock.lock();
try {
channelInfo.put(channelid, channel.getIP());
} finally {
wldWLock.unlock();
}
}
return channelid;
}
public int addWorld() {
@@ -380,73 +390,96 @@ public class Server {
}
private int initWorld() {
wldWLock.lock();
int i;
wldRLock.lock();
try {
int i = worlds.size();
i = worlds.size();
if(i >= YamlConfig.config.server.WLDLIST_SIZE) {
return -1;
}
System.out.println("Starting world " + i);
} finally {
wldRLock.unlock();
}
System.out.println("Starting world " + i);
int exprate = YamlConfig.config.worlds.get(i).exp_rate;
int mesorate = YamlConfig.config.worlds.get(i).meso_rate;
int droprate = YamlConfig.config.worlds.get(i).drop_rate;
int bossdroprate = YamlConfig.config.worlds.get(i).boss_drop_rate;
int questrate = YamlConfig.config.worlds.get(i).quest_rate;
int travelrate = YamlConfig.config.worlds.get(i).travel_rate;
int fishingrate = YamlConfig.config.worlds.get(i).fishing_rate;
int exprate = YamlConfig.config.worlds.get(i).exp_rate;
int mesorate = YamlConfig.config.worlds.get(i).meso_rate;
int droprate = YamlConfig.config.worlds.get(i).drop_rate;
int bossdroprate = YamlConfig.config.worlds.get(i).boss_drop_rate;
int questrate = YamlConfig.config.worlds.get(i).quest_rate;
int travelrate = YamlConfig.config.worlds.get(i).travel_rate;
int fishingrate = YamlConfig.config.worlds.get(i).fishing_rate;
int flag = YamlConfig.config.worlds.get(i).flag;
String event_message = YamlConfig.config.worlds.get(i).event_message;
String why_am_i_recommended = YamlConfig.config.worlds.get(i).why_am_i_recommended;
World world = new World(i,
flag,
event_message,
exprate, droprate, bossdroprate, mesorate, questrate, travelrate, fishingrate);
int flag = YamlConfig.config.worlds.get(i).flag;
String event_message = YamlConfig.config.worlds.get(i).event_message;
String why_am_i_recommended = YamlConfig.config.worlds.get(i).why_am_i_recommended;
worldRecommendedList.add(new Pair<>(i, why_am_i_recommended));
worlds.add(world);
World world = new World(i,
flag,
event_message,
exprate, droprate, bossdroprate, mesorate, questrate, travelrate, fishingrate);
Map<Integer, String> channelInfo = new HashMap<>();
long bootTime = getCurrentTime();
for (int j = 1; j <= YamlConfig.config.worlds.get(i).channels; j++) {
int channelid = j;
Channel channel = new Channel(i, channelid, bootTime);
Map<Integer, String> channelInfo = new HashMap<>();
long bootTime = getCurrentTime();
for (int j = 1; j <= YamlConfig.config.worlds.get(i).channels; j++) {
int channelid = j;
Channel channel = new Channel(i, channelid, bootTime);
world.addChannel(channel);
channelInfo.put(channelid, channel.getIP());
world.addChannel(channel);
channelInfo.put(channelid, channel.getIP());
}
boolean canDeploy;
wldWLock.lock(); // thanks Ashen for noticing a deadlock issue when trying to deploy a channel
try {
canDeploy = world.getId() == worlds.size();
if (canDeploy) {
worldRecommendedList.add(new Pair<>(i, why_am_i_recommended));
worlds.add(world);
channels.add(i, channelInfo);
}
channels.add(i, channelInfo);
world.setServerMessage(YamlConfig.config.worlds.get(i).server_message);
System.out.println("Finished loading world " + i + "\r\n");
return i;
} finally {
wldWLock.unlock();
}
if (canDeploy) {
world.setServerMessage(YamlConfig.config.worlds.get(i).server_message);
System.out.println("Finished loading world " + i + "\r\n");
return i;
} else {
System.out.println("Could not load world " + i + "...\r\n");
world.shutdown();
return -2;
}
}
public boolean removeChannel(int worldid) { //lol don't!
wldWLock.lock();
World world;
wldRLock.lock();
try {
if(worldid >= worlds.size()) return false;
World world = worlds.get(worldid);
if (world != null) {
int channel = world.removeChannel();
world = worlds.get(worldid);
} finally {
wldRLock.unlock();
}
if (world != null) {
int channel = world.removeChannel();
wldWLock.lock();
try {
Map<Integer, String> m = channels.get(worldid);
if(m != null) m.remove(channel);
return channel > -1;
} finally {
wldWLock.unlock();
}
} finally {
wldWLock.unlock();
return channel > -1;
}
return false;
@@ -472,17 +505,15 @@ public class Server {
return false;
}
removeWorldPlayerRanking();
w.shutdown();
wldWLock.lock();
try {
if(worldid == worlds.size() - 1) {
removeWorldPlayerRanking();
w.shutdown();
if (worldid == worlds.size() - 1) {
worlds.remove(worldid);
channels.remove(worldid);
worldRecommendedList.remove(worldid);
} else {
return false;
}
} finally {
wldWLock.unlock();
@@ -714,7 +745,7 @@ public class Server {
if (!YamlConfig.config.server.USE_WHOLE_SERVER_RANKING) {
wldWLock.lock();
try {
if(playerRanking.size() < this.getWorldsSize()) {
if(playerRanking.size() < worlds.size()) {
return;
}
+101 -67
View File
@@ -67,6 +67,7 @@ import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import client.MapleCharacter;
import constants.game.GameConstants;
import net.server.services.ServicesManager;
import net.server.services.BaseService;
import net.server.services.type.ChannelServices;
@@ -97,18 +98,18 @@ public final class Channel {
private final Map<Integer, Integer> storedVars = new HashMap<>();
private Set<Integer> playersAway = new HashSet<>();
private Map<MapleExpeditionType, MapleExpedition> expeditions = new HashMap<>();
private Map<Integer, MapleMiniDungeon> dungeons = new HashMap<>();
private List<MapleExpeditionType> expedType = new ArrayList<>();
private Set<MapleMap> ownedMaps = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<MapleMap, Boolean>()));
private MapleEvent event;
private boolean finishedShutdown = false;
private int usedDojo = 0;
private Set<Integer> usedMC = new HashSet<>();
private int usedDojo = 0;
private int[] dojoStage;
private long[] dojoFinishTime;
private ScheduledFuture<?>[] dojoTask;
private Map<Integer, Integer> dojoParty = new HashMap<>();
private Map<Integer, MapleMiniDungeon> dungeons = new HashMap<>();
private List<Integer> chapelReservationQueue = new LinkedList<>();
private List<Integer> cathedralReservationQueue = new LinkedList<>();
@@ -226,11 +227,16 @@ public final class Channel {
}
private void closeChannelSchedules() {
for(int i = 0; i < 20; i++) {
if(dojoTask[i] != null) {
dojoTask[i].cancel(false);
dojoTask[i] = null;
lock.lock();
try {
for(int i = 0; i < dojoTask.length; i++) {
if(dojoTask[i] != null) {
dojoTask[i].cancel(false);
dojoTask[i] = null;
}
}
} finally {
lock.unlock();
}
closeChannelServices();
@@ -493,43 +499,54 @@ public final class Channel {
this.storedVars.put(key, val);
}
public synchronized int lookupPartyDojo(MapleParty party) {
public int lookupPartyDojo(MapleParty party) {
if(party == null) return -1;
Integer i = dojoParty.get(party.hashCode());
return (i != null) ? i : -1;
}
public int getAvailableDojo(boolean isPartyDojo) {
return getAvailableDojo(isPartyDojo, null);
public int ingressDojo(boolean isPartyDojo, int fromStage) {
return ingressDojo(isPartyDojo, null, fromStage);
}
public synchronized int getAvailableDojo(boolean isPartyDojo, MapleParty party) {
int dojoList = this.usedDojo;
int range, slot = 0;
if(!isPartyDojo) {
dojoList = dojoList >> 5;
range = 15;
} else {
range = 5;
}
while((dojoList & 1) != 0) {
dojoList = (dojoList >> 1);
slot++;
}
if(slot < range) {
if(party != null) {
if(dojoParty.containsKey(party.hashCode())) return -2;
dojoParty.put(party.hashCode(), slot);
public int ingressDojo(boolean isPartyDojo, MapleParty party, int fromStage) {
lock.lock();
try {
int dojoList = this.usedDojo;
int range, slot = 0;
if(!isPartyDojo) {
dojoList = dojoList >> 5;
range = 15;
} else {
range = 5;
}
while((dojoList & 1) != 0) {
dojoList = (dojoList >> 1);
slot++;
}
if(slot < range) {
int slotMapid = (isPartyDojo ? 925030000 : 925020000) + (100 * (fromStage + 1)) + slot;
int dojoSlot = getDojoSlot(slotMapid);
this.usedDojo |= (1 << ((!isPartyDojo ? 5 : 0) + slot));
return slot;
} else {
return -1;
if(party != null) {
if(dojoParty.containsKey(party.hashCode())) return -2;
dojoParty.put(party.hashCode(), dojoSlot);
}
this.usedDojo |= (1 << dojoSlot);
this.resetDojo(slotMapid);
this.startDojoSchedule(slotMapid);
return slot;
} else {
return -1;
}
} finally {
lock.unlock();
}
}
@@ -537,13 +554,19 @@ public final class Channel {
int mask = 0b11111111111111111111;
mask ^= (1 << slot);
usedDojo &= mask;
lock.lock();
try {
usedDojo &= mask;
} finally {
lock.unlock();
}
if(party != null) {
if(dojoParty.remove(party.hashCode()) != null) return;
}
if(dojoParty.containsValue(slot)) { // strange case, no party there!
Set<Entry<Integer, Integer>> es = Collections.unmodifiableSet(dojoParty.entrySet());
Set<Entry<Integer, Integer>> es = new HashSet<>(dojoParty.entrySet());
for(Entry<Integer, Integer> e: es) {
if(e.getValue() == slot) {
@@ -565,17 +588,12 @@ public final class Channel {
}
public void resetDojo(int dojoMapId) {
resetDojo(dojoMapId, 0);
resetDojo(dojoMapId, -1);
}
public void resetDojo(int dojoMapId, int thisStg) {
private void resetDojo(int dojoMapId, int thisStg) {
int slot = getDojoSlot(dojoMapId);
this.dojoStage[slot] = thisStg;
if(this.dojoTask[slot] != null) {
this.dojoTask[slot].cancel(false);
this.dojoTask[slot] = null;
}
}
public void freeDojoSectionIfEmpty(int dojoMapId) {
@@ -595,34 +613,45 @@ public final class Channel {
freeDojoSlot(slot, null);
}
public void startDojoSchedule(final int dojoMapId) {
private void startDojoSchedule(final int dojoMapId) {
final int slot = getDojoSlot(dojoMapId);
final int stage = (dojoMapId / 100) % 100;
if(stage <= dojoStage[slot]) return;
long clockTime = (stage > 36 ? 15 : (stage / 6) + 5) * 60000;
this.dojoTask[slot] = TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
final int delta = (dojoMapId) % 100;
final int dojoBaseMap = (slot < 5) ? 925030000 : 925020000;
MapleParty party = null;
for (int i = 0; i < 5; i++) { //only 32 stages, but 38 maps
if (stage + i > 38) {
break;
}
for(MapleCharacter chr: getMapFactory().getMap(dojoBaseMap + (100 * (stage + i)) + delta).getAllPlayers()) {
if(chr.getMap().isDojoMap()) {
chr.timeoutFromDojo();
}
party = chr.getParty();
}
}
freeDojoSlot(slot, party);
lock.lock();
try {
if (this.dojoTask[slot] != null) {
this.dojoTask[slot].cancel(false);
}
}, clockTime + 3000); // let the TIMES UP display for 3 seconds, then warp
this.dojoTask[slot] = TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
final int delta = (dojoMapId) % 100;
final int dojoBaseMap = (slot < 5) ? 925030000 : 925020000;
MapleParty party = null;
for (int i = 0; i < 5; i++) { //only 32 stages, but 38 maps
if (stage + i > 38) {
break;
}
MapleMap dojoExit = getMapFactory().getMap(925020002);
for(MapleCharacter chr: getMapFactory().getMap(dojoBaseMap + (100 * (stage + i)) + delta).getAllPlayers()) {
if(GameConstants.isDojo(chr.getMap().getId())) {
chr.changeMap(dojoExit);
}
party = chr.getParty();
}
}
freeDojoSlot(slot, party);
}
}, clockTime + 3000); // let the TIMES UP display for 3 seconds, then warp
} finally {
lock.unlock();
}
dojoFinishTime[slot] = Server.getInstance().getCurrentTime() + clockTime;
}
@@ -632,9 +661,14 @@ public final class Channel {
int stage = (dojoMapId / 100) % 100;
if(stage <= dojoStage[slot]) return;
if(this.dojoTask[slot] != null) {
this.dojoTask[slot].cancel(false);
this.dojoTask[slot] = null;
lock.lock();
try {
if(this.dojoTask[slot] != null) {
this.dojoTask[slot].cancel(false);
this.dojoTask[slot] = null;
}
} finally {
lock.unlock();
}
freeDojoSlot(slot, party);
@@ -56,7 +56,6 @@ import client.autoban.AutobanFactory;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import constants.game.GameConstants;
import constants.net.ServerConstants;
import constants.skills.Aran;
import constants.skills.Assassin;
import constants.skills.Bandit;
@@ -102,6 +101,7 @@ import constants.skills.SuperGM;
import constants.skills.ThunderBreaker;
import constants.skills.WhiteKnight;
import constants.skills.WindArcher;
import net.server.PlayerBuffValueHolder;
import scripting.AbstractPlayerInteraction;
public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandler {
@@ -166,14 +166,17 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
if (player.isAlive()) {
if(attack.skill == Aran.BODY_PRESSURE || attack.skill == Marauder.ENERGY_CHARGE || attack.skill == ThunderBreaker.ENERGY_CHARGE) { // thanks IxianMace for noticing Energy Charge skills refreshing on touch, leading to misleading buff applies
// prevent touch dmg skills refreshing
} else if(attack.skill == DawnWarrior.FINAL_ATTACK || attack.skill == WindArcher.FINAL_ATTACK) {
// prevent cygnus FA refreshing
mobCount = 15;
} else if(attack.skill == NightWalker.POISON_BOMB) {// Poison Bomb
attackEffect.applyTo(player, new Point(attack.position.x, attack.position.y));
} else {
attackEffect.applyTo(player);
if (attack.skill == DawnWarrior.FINAL_ATTACK || attack.skill == Page.FINAL_ATTACK_BW || attack.skill == Page.FINAL_ATTACK_SWORD || attack.skill == Fighter.FINAL_ATTACK_SWORD
|| attack.skill == Fighter.FINAL_ATTACK_AXE || attack.skill == Spearman.FINAL_ATTACK_SPEAR || attack.skill == Spearman.FINAL_ATTACK_POLEARM || attack.skill == WindArcher.FINAL_ATTACK
|| attack.skill == DawnWarrior.FINAL_ATTACK || attack.skill == Hunter.FINAL_ATTACK || attack.skill == Crossbowman.FINAL_ATTACK) {
if (attack.skill == Page.FINAL_ATTACK_BW || attack.skill == Page.FINAL_ATTACK_SWORD || attack.skill == Fighter.FINAL_ATTACK_SWORD
|| attack.skill == Fighter.FINAL_ATTACK_AXE || attack.skill == Spearman.FINAL_ATTACK_SPEAR || attack.skill == Spearman.FINAL_ATTACK_POLEARM
|| attack.skill == Hunter.FINAL_ATTACK || attack.skill == Crossbowman.FINAL_ATTACK) {
mobCount = 15;//:(
} else if (attack.skill == Aran.HIDDEN_FULL_DOUBLE || attack.skill == Aran.HIDDEN_FULL_TRIPLE || attack.skill == Aran.HIDDEN_OVER_DOUBLE || attack.skill == Aran.HIDDEN_OVER_TRIPLE) {
@@ -753,6 +756,17 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
calcDmgMax *= (100 + ceffect.getDamage()) / 100;
}
int bonusDmgBuff = 100;
for (PlayerBuffValueHolder pbvh : chr.getAllBuffs()) {
int bonusDmg = pbvh.effect.getDamage() - 100;
bonusDmgBuff += bonusDmg;
}
if (bonusDmgBuff != 100) {
float dmgBuff = bonusDmgBuff / 100.0f;
calcDmgMax = (long) Math.ceil(calcDmgMax * dmgBuff);
}
if(chr.getMapId() >= 914000000 && chr.getMapId() <= 914000500) {
calcDmgMax += 80000; // Aran Tutorial.
}
@@ -789,7 +803,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
if(chr.getBuffEffect(MapleBuffStat.WK_CHARGE) != null) {
// Charge, so now we need to check elemental effectiveness
int sourceID = chr.getBuffSource(MapleBuffStat.WK_CHARGE);
int level = chr.getBuffedValue(MapleBuffStat.WK_CHARGE);
int level = chr.getBuffedValue(MapleBuffStat.WK_CHARGE);
if(monster != null) {
if(sourceID == WhiteKnight.BW_FIRE_CHARGE || sourceID == WhiteKnight.SWORD_FIRE_CHARGE) {
if(monster.getStats().getEffectiveness(Element.FIRE) == ElementalEffectiveness.WEAK) {
@@ -880,7 +894,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
long maxWithCrit = hitDmgMax;
if(canCrit) // They can crit, so up the max.
maxWithCrit *= 2;
// Warn if the damage is over 1.5x what we calculated above.
if(damage > maxWithCrit * 1.5) {
AutobanFactory.DAMAGE_HACK.alert(chr, "DMG: " + damage + " MaxDMG: " + maxWithCrit + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")");
@@ -68,7 +68,7 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
if (chr.getDojoEnergy() < 10000 && (attack.skill == 1009 || attack.skill == 10001009 || attack.skill == 20001009)) // PE hacking or maybe just lagging
return;
if (chr.getMap().isDojoMap() && attack.numAttacked > 0) {
if (GameConstants.isDojo(chr.getMap().getId()) && attack.numAttacked > 0) {
chr.setDojoEnergy(chr.getDojoEnergy() + YamlConfig.config.server.DOJO_ENERGY_ATK);
c.announce(MaplePacketCreator.getEnergy("energy", chr.getDojoEnergy()));
}
@@ -27,6 +27,7 @@ import client.MapleClient;
import client.Skill;
import client.SkillFactory;
import config.YamlConfig;
import constants.game.GameConstants;
import constants.skills.Bishop;
import constants.skills.Evan;
import constants.skills.FPArchMage;
@@ -56,7 +57,7 @@ public final class MagicDamageHandler extends AbstractDealDamageHandler {
}
}
if (chr.getMap().isDojoMap() && attack.numAttacked > 0) {
if (GameConstants.isDojo(chr.getMap().getId()) && attack.numAttacked > 0) {
chr.setDojoEnergy(chr.getDojoEnergy() + + YamlConfig.config.server.DOJO_ENERGY_ATK);
c.announce(MaplePacketCreator.getEnergy("energy", chr.getDojoEnergy()));
}
@@ -130,7 +130,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
String remoteHwid;
if (player == null) {
remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
remoteHwid = MapleSessionCoordinator.getInstance().pickLoginSessionHwid(session);
if (remoteHwid == null) {
c.disconnect(true, false);
return;
@@ -29,6 +29,6 @@ public class QuickslotKeyMappedModifiedHandler extends AbstractMaplePacketHandle
aQuickslotKeyMapped[i] = (byte) slea.readInt();
}
c.getPlayer().m_pQuickslotKeyMapped = new MapleQuickslotBinding(aQuickslotKeyMapped);
c.getPlayer().changeQuickslotKeybinding(aQuickslotKeyMapped);
}
}
@@ -32,6 +32,7 @@ import client.inventory.MapleInventoryType;
import client.inventory.MapleWeaponType;
import client.inventory.manipulator.MapleInventoryManipulator;
import config.YamlConfig;
import constants.game.GameConstants;
import constants.inventory.ItemConstants;
import constants.skills.Aran;
import constants.skills.Buccaneer;
@@ -69,7 +70,7 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
}
}
if (chr.getMap().isDojoMap() && attack.numAttacked > 0) {
if (GameConstants.isDojo(chr.getMap().getId()) && attack.numAttacked > 0) {
chr.setDojoEnergy(chr.getDojoEnergy() + YamlConfig.config.server.DOJO_ENERGY_ATK);
c.announce(MaplePacketCreator.getEnergy("energy", chr.getDojoEnergy()));
}
@@ -104,7 +105,7 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
}
short slot = -1;
int projectile = 0;
byte bulletCount = 1;
short bulletCount = 1;
MapleStatEffect effect = null;
if (attack.skill != 0) {
effect = attack.getAttackEffect(chr, null);
@@ -168,7 +169,7 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
boolean shadowClaw = chr.getBuffedValue(MapleBuffStat.SHADOW_CLAW) != null;
if (projectile != 0) {
if (!soulArrow && !shadowClaw && attack.skill != 11101004 && attack.skill != 15111007 && attack.skill != 14101006) {
byte bulletConsume = bulletCount;
short bulletConsume = bulletCount;
if (effect != null && effect.getBulletConsume() != 0) {
bulletConsume = (byte) (effect.getBulletConsume() * (hasShadowPartner ? 2 : 1));
@@ -197,7 +197,7 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
}
//in dojo player cannot use pot, so deadly attacks should be turned off as well
if(is_deadly && chr.getMap().isDojoMap() && !YamlConfig.config.server.USE_DEADLY_DOJO) {
if(is_deadly && GameConstants.isDojo(chr.getMap().getId()) && !YamlConfig.config.server.USE_DEADLY_DOJO) {
damage = 0;
mpattack = 0;
}
@@ -319,10 +319,14 @@ public class MapleMatchCheckerCoordinator {
}
}
private void acceptMatchElement(MapleMatchCheckingElement mmce, int cid) {
private boolean acceptMatchElement(MapleMatchCheckingElement mmce, int cid) {
if (mmce.acceptEntry(cid)) {
unpoolMatchPlayer(cid);
disposeMatchElement(mmce);
return true;
} else {
return false;
}
}
@@ -355,7 +359,11 @@ public class MapleMatchCheckerCoordinator {
mmce = null;
} else {
if (accept) {
acceptMatchElement(mmce, cid);
if (!acceptMatchElement(mmce, cid)) {
mmce = null;
}
break; // thanks Rohenn for noticing loop scenario here
} else {
denyMatchElement(mmce, cid);
matchEntries.remove(cid);
@@ -547,6 +547,11 @@ public class MapleSessionCoordinator {
// session.removeAttribute(MapleClient.CLIENT_REMOTE_ADDRESS); No real need for removing String property on closed sessions
}
public String pickLoginSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteAddress(session);
return cachedHostHwids.remove(remoteHost); // thanks BHB, resinate for noticing players from same network not being able to login
}
public String getGameSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteHost(session);
return cachedHostHwids.get(remoteHost);
@@ -88,8 +88,9 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
slea.skip(6); // localhost masked the initial part with zeroes...
byte[] hwidNibbles = slea.read(4);
int loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
String nibbleHwid = HexTool.toCompressedString(hwidNibbles);
int loginok = c.login(login, pwd, nibbleHwid);
Connection con = null;
PreparedStatement ps = null;
@@ -112,7 +113,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
e.printStackTrace();
} finally {
disposeSql(con, ps);
loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
loginok = c.login(login, pwd, nibbleHwid);
}
}
+7 -2
View File
@@ -260,10 +260,15 @@ public class World {
}
}
public void addChannel(Channel channel) {
public boolean addChannel(Channel channel) {
chnWLock.lock();
try {
channels.add(channel);
if (channel.getId() == channels.size() + 1) {
channels.add(channel);
return true;
} else {
return false;
}
} finally {
chnWLock.unlock();
}
+17 -2
View File
@@ -588,7 +588,10 @@ public class NPCConversationManager extends AbstractPlayerInteraction {
}
public Object[] getAvailableSkillBooks() {
return MapleItemInformationProvider.getInstance().usableSkillBooks(this.getPlayer()).toArray();
List<Integer> ret = MapleItemInformationProvider.getInstance().usableSkillBooks(this.getPlayer());
ret.addAll(MapleSkillbookInformationProvider.getInstance().getTeachableSkills(this.getPlayer()));
return ret.toArray();
}
public Object[] getNamesWhoDropsItem(Integer itemId) {
@@ -597,7 +600,19 @@ public class NPCConversationManager extends AbstractPlayerInteraction {
public String getSkillBookInfo(int itemid) {
SkillBookEntry sbe = MapleSkillbookInformationProvider.getInstance().getSkillbookAvailability(itemid);
return sbe != SkillBookEntry.UNAVAILABLE ? " Obtainable through #rquestline#k." : "";
switch (sbe) {
case UNAVAILABLE:
return "";
case QUEST_BOOK:
return " Obtainable through #rquestline#k (collecting book).";
case QUEST_REWARD:
return " Obtainable through #rquestline#k (quest reward).";
default:
return " Obtainable through #rquestline#k.";
}
}
// By Drago/Dragohe4rt CPQ + WED
+6 -2
View File
@@ -38,8 +38,8 @@ public class MakerItemFactory {
public static MakerItemCreateEntry getItemCreateEntry(int toCreate, int stimulantid, Map<Integer, Short> reagentids) {
MakerItemCreateEntry makerEntry = ii.getMakerItemEntry(toCreate);
if(makerEntry == null) {
return null;
if(makerEntry.isInvalid()) {
return makerEntry;
}
// THEY DECIDED FOR SOME BIZARRE PATTERN ON THE FEE THING, ALMOST RANDOMIZED.
@@ -206,5 +206,9 @@ public class MakerItemFactory {
reqCost = (int) (cost / 1000);
reqCost *= 1000;
}
public boolean isInvalid() { // thanks Rohenn, Wh1SK3Y for noticing some items not getting checked properly
return reqLevel < 0;
}
}
}
+4 -4
View File
@@ -1981,10 +1981,10 @@ public class MapleItemInformationProvider {
PreparedStatement ps = con.prepareStatement("SELECT req_level, req_maker_level, req_meso, quantity FROM makercreatedata WHERE itemid = ?");
ps.setInt(1, toCreate);
ResultSet rs = ps.executeQuery();
int reqLevel = 0;
int reqMakerLevel = 0;
int cost = 0;
int toGive = 0;
int reqLevel = -1;
int reqMakerLevel = -1;
int cost = -1;
int toGive = -1;
if (rs.next()) {
reqLevel = rs.getInt("req_level");
reqMakerLevel = rs.getInt("req_maker_level");
@@ -35,7 +35,9 @@ import java.util.Scanner;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import client.MapleCharacter;
import provider.MapleData;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
import tools.DatabaseConnection;
@@ -57,45 +59,106 @@ public class MapleSkillbookInformationProvider {
public enum SkillBookEntry {
UNAVAILABLE,
QUEST,
QUEST_BOOK,
QUEST_REWARD,
REACTOR,
SCRIPT
}
static String host = "jdbc:mysql://localhost:3306/heavenms";
static String driver = "com.mysql.jdbc.Driver";
static String username = "root";
static String password = "";
private static String host = "jdbc:mysql://localhost:3306/heavenms";
private static String driver = "com.mysql.jdbc.Driver";
private static String username = "root";
private static String password = "";
static String rootDirectory = ".";
private static String rootDirectory = ".";
static int skillbookMinItemid = 2280000;
static int skillbookMaxItemid = 2300000; // exclusively
private static int skillbookMinItemid = 2280000;
private static int skillbookMaxItemid = 2300000; // exclusively
private static Set<Integer> questSkills = new HashSet<>();
static {
loadSkillbooks();
}
public static boolean isSkillBook(int itemid) {
private static boolean is4thJobSkill(int itemid) {
return itemid / 10000 % 10 == 2;
}
private static boolean isSkillBook(int itemid) {
return itemid >= skillbookMinItemid && itemid < skillbookMaxItemid;
}
private static boolean isQuestBook(int itemid) {
return itemid >= 4001107 && itemid <= 4001114 || itemid >= 4161015 && itemid <= 4161023;
}
private static int fetchQuestbook(MapleData checkData, String quest) {
MapleData questStartData = checkData.getChildByPath(quest).getChildByPath("0");
MapleData startReqItemData = questStartData.getChildByPath("item");
if (startReqItemData != null) {
for (MapleData itemData : startReqItemData.getChildren()) {
int itemid = MapleDataTool.getInt("id", itemData, 0);
if (isQuestBook(itemid)) {
return itemid;
}
}
}
MapleData startReqQuestData = questStartData.getChildByPath("quest");
if (startReqQuestData != null) {
Set<Integer> reqQuests = new HashSet<>();
for (MapleData questStatusData : startReqQuestData.getChildren()) {
int reqQuest = MapleDataTool.getInt("id", questStatusData, 0);
if (reqQuest > 0) {
reqQuests.add(reqQuest);
}
}
for (Integer reqQuest : reqQuests) {
int book = fetchQuestbook(checkData, Integer.toString(reqQuest));
if (book > -1) {
return book;
}
}
}
return -1;
}
private static void fetchSkillbooksFromQuests() {
MapleData actData = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "Quest.wz")).getData("Act.img");
MapleDataProvider questDataProvider = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "Quest.wz"));
MapleData actData = questDataProvider.getData("Act.img");
MapleData checkData = questDataProvider.getData("Check.img");
for (MapleData questData : actData.getChildren()) {
for (MapleData questStatusData : questData.getChildren()) {
for (MapleData questNodeData : questStatusData.getChildren()) {
if (questNodeData.getName().contentEquals("item")) {
String actNodeName = questNodeData.getName();
if (actNodeName.contentEquals("item")) {
for (MapleData questItemData : questNodeData.getChildren()) {
int itemid = MapleDataTool.getInt("id", questItemData, 0);
int itemcount = MapleDataTool.getInt("count", questItemData, 0);
if (isSkillBook(itemid) && itemcount > 0) {
foundSkillbooks.put(itemid, SkillBookEntry.QUEST);
int questbook = fetchQuestbook(checkData, questData.getName());
if (questbook < 0) {
foundSkillbooks.put(itemid, SkillBookEntry.QUEST);
} else {
foundSkillbooks.put(itemid, SkillBookEntry.QUEST_BOOK);
}
}
}
} else if (actNodeName.contentEquals("skill")) {
for (MapleData questSkillData : questNodeData.getChildren()) {
int skillid = MapleDataTool.getInt("id", questSkillData, 0);
if (is4thJobSkill(skillid)) {
// negative itemids are skill rewards
foundSkillbooks.put(-skillid, SkillBookEntry.QUEST_REWARD);
}
}
break;
}
}
}
@@ -212,4 +275,23 @@ public class MapleSkillbookInformationProvider {
return sbe != null ? sbe : SkillBookEntry.UNAVAILABLE;
}
public List<Integer> getTeachableSkills(MapleCharacter chr) {
List<Integer> list = new ArrayList<>();
for (Integer book : foundSkillbooks.keySet()) {
if (book >= 0) {
continue;
}
int skillid = -book;
if (skillid / 10000 == chr.getJob().getId()) {
if (chr.getMasterLevel(skillid) == 0) {
list.add(-skillid);
}
}
}
return list;
}
}
+10 -7
View File
@@ -146,7 +146,7 @@ public class MapleStatEffect {
private int itemCon, itemConNo;
private int damage, attackCount, fixdamage;
private Point lt, rb;
private byte bulletCount, bulletConsume;
private short bulletCount, bulletConsume;
private byte mapProtection;
private CardItemupStats cardStats;
@@ -484,8 +484,8 @@ public class MapleStatEffect {
ret.damage = MapleDataTool.getIntConvert("damage", source, 100);
ret.fixdamage = MapleDataTool.getIntConvert("fixdamage", source, -1);
ret.attackCount = MapleDataTool.getIntConvert("attackCount", source, 1);
ret.bulletCount = (byte) MapleDataTool.getIntConvert("bulletCount", source, 1);
ret.bulletConsume = (byte) MapleDataTool.getIntConvert("bulletConsume", source, 0);
ret.bulletCount = (short) MapleDataTool.getIntConvert("bulletCount", source, 1);
ret.bulletConsume = (short) MapleDataTool.getIntConvert("bulletConsume", source, 0);
ret.moneyCon = MapleDataTool.getIntConvert("moneyCon", source, 0);
ret.itemCon = MapleDataTool.getInt("itemCon", source, 0);
ret.itemConNo = MapleDataTool.getInt("itemConNo", source, 0);
@@ -961,6 +961,7 @@ public class MapleStatEffect {
} else if (isCureAllAbnormalStatus()) {
applyto.dispelDebuff(MapleDisease.SEDUCE);
applyto.dispelDebuff(MapleDisease.ZOMBIFY);
applyto.dispelDebuff(MapleDisease.CONFUSE);
applyto.dispelDebuffs();
} else if (isComboReset()) {
applyto.setCombo((short) 0);
@@ -1008,6 +1009,8 @@ public class MapleStatEffect {
}
}
if (isShadowClaw()) {
short projectileConsume = this.getBulletConsume(); // noticed by shavit
MapleInventory use = applyto.getInventory(MapleInventoryType.USE);
use.lockInventory();
try {
@@ -1015,7 +1018,7 @@ public class MapleStatEffect {
for (int i = 1; i <= use.getSlotLimit(); i++) { // impose order...
Item item = use.getItem((short) i);
if (item != null) {
if (ItemConstants.isThrowingStar(item.getItemId()) && item.getQuantity() >= 200) {
if (ItemConstants.isThrowingStar(item.getItemId()) && item.getQuantity() >= projectileConsume) {
projectile = item;
break;
}
@@ -1024,7 +1027,7 @@ public class MapleStatEffect {
if (projectile == null) {
return false;
} else {
MapleInventoryManipulator.removeFromSlot(applyto.getClient(), MapleInventoryType.USE, projectile.getPosition(), (short) 200, false, true);
MapleInventoryManipulator.removeFromSlot(applyto.getClient(), MapleInventoryType.USE, projectile.getPosition(), (short) projectileConsume, false, true);
}
} finally {
use.unlockInventory();
@@ -1938,11 +1941,11 @@ public class MapleStatEffect {
return fixdamage;
}
public byte getBulletCount() {
public short getBulletCount() {
return bulletCount;
}
public byte getBulletConsume() {
public short getBulletConsume() {
return bulletConsume;
}
+11 -12
View File
@@ -180,7 +180,7 @@ public class MobSkill {
break;
case 127:
if (lt != null && rb != null && skill) {
for (MapleCharacter character : getPlayersInRange(monster, player)) {
for (MapleCharacter character : getPlayersInRange(monster)) {
character.dispel();
}
} else {
@@ -192,7 +192,7 @@ public class MobSkill {
break;
case 129: // Banish
if (lt != null && rb != null && skill) {
for (MapleCharacter chr : getPlayersInRange(monster, player)) {
for (MapleCharacter chr : getPlayersInRange(monster)) {
banishPlayers.add(chr);
}
} else {
@@ -200,7 +200,7 @@ public class MobSkill {
}
break;
case 131: // Mist
monster.getMap().spawnMist(new MapleMist(calculateBoundingBox(monster.getPosition(), monster.isFacingLeft()), monster, this), x * 100, false, false, false);
monster.getMap().spawnMist(new MapleMist(calculateBoundingBox(monster.getPosition()), monster, this), x * 100, false, false, false);
break;
case 132:
disease = MapleDisease.CONFUSE;
@@ -248,7 +248,7 @@ public class MobSkill {
int skillLimit = this.getLimit();
MapleMap map = monster.getMap();
if (map.isDojoMap()) { // spawns in dojo should be unlimited
if (GameConstants.isDojo(map.getId())) { // spawns in dojo should be unlimited
skillLimit = Integer.MAX_VALUE;
}
@@ -333,7 +333,7 @@ public class MobSkill {
if (disease != null) {
if (lt != null && rb != null && skill) {
int i = 0;
for (MapleCharacter character : getPlayersInRange(monster, player)) {
for (MapleCharacter character : getPlayersInRange(monster)) {
if (!character.hasActiveBuff(2321005)) { // holy shield
if (disease.equals(MapleDisease.SEDUCE)) {
if (i < 10) {
@@ -351,8 +351,8 @@ public class MobSkill {
}
}
private List<MapleCharacter> getPlayersInRange(MapleMonster monster, MapleCharacter player) {
return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition(), monster.isFacingLeft()), Collections.singletonList(player));
private List<MapleCharacter> getPlayersInRange(MapleMonster monster) {
return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition()));
}
public int getSkillId() {
@@ -411,15 +411,14 @@ public class MobSkill {
return prop == 1.0 || Math.random() < prop;
}
private Rectangle calculateBoundingBox(Point posFrom, boolean facingLeft) {
int multiplier = facingLeft ? 1 : -1;
Point mylt = new Point(lt.x * multiplier + posFrom.x, lt.y + posFrom.y);
Point myrb = new Point(rb.x * multiplier + posFrom.x, rb.y + posFrom.y);
private Rectangle calculateBoundingBox(Point posFrom) {
Point mylt = new Point(lt.x + posFrom.x, lt.y + posFrom.y);
Point myrb = new Point(rb.x + posFrom.x, rb.y + posFrom.y);
Rectangle bounds = new Rectangle(mylt.x, mylt.y, myrb.x - mylt.x, myrb.y - mylt.y);
return bounds;
}
private List<MapleMapObject> getObjectsInRange(MapleMonster monster, MapleMapObjectType objectType) {
return monster.getMap().getMapObjectsInBox(calculateBoundingBox(monster.getPosition(), monster.isFacingLeft()), Collections.singletonList(objectType));
return monster.getMap().getMapObjectsInBox(calculateBoundingBox(monster.getPosition()), Collections.singletonList(objectType));
}
}
+8 -16
View File
@@ -758,9 +758,11 @@ public class MapleMap {
final List<MonsterDropEntry> dropEntry = new ArrayList<>();
final List<MonsterDropEntry> visibleQuestEntry = new ArrayList<>();
final List<MonsterDropEntry> otherQuestEntry = new ArrayList<>();
sortDropEntries(YamlConfig.config.server.USE_SPAWN_RELEVANT_LOOT ? mob.retrieveRelevantDrops() : mi.retrieveEffectiveDrop(mob.getId()), dropEntry, visibleQuestEntry, otherQuestEntry, chr);
if (dropEntry.isEmpty() && visibleQuestEntry.isEmpty()) { // thanks resinate
List<MonsterDropEntry> lootEntry = YamlConfig.config.server.USE_SPAWN_RELEVANT_LOOT ? mob.retrieveRelevantDrops() : mi.retrieveEffectiveDrop(mob.getId());
sortDropEntries(lootEntry, dropEntry, visibleQuestEntry, otherQuestEntry, chr); // thanks Articuno, Limit, Rohenn for noticing quest loots not showing up in only-quest item drops scenario
if (lootEntry.isEmpty()) { // thanks resinate
return;
}
@@ -1309,15 +1311,13 @@ public class MapleMap {
return pchars;
}
public List<MapleCharacter> getPlayersInRange(Rectangle box, List<MapleCharacter> targets) {
public List<MapleCharacter> getPlayersInRange(Rectangle box) {
List<MapleCharacter> character = new LinkedList<>();
chrRLock.lock();
try {
for (MapleCharacter chr : targets) {
if (characters.contains(chr)) {
if (box.contains(chr.getPosition())) {
character.add(chr);
}
for (MapleCharacter chr : characters) {
if (box.contains(chr.getPosition())) {
character.add(chr);
}
}
} finally {
@@ -4173,14 +4173,6 @@ public class MapleMap {
this.setDocked(state);
}
public boolean isDojoMap() {
return mapid >= 925020000 && mapid < 925040000;
}
public boolean isDojoFightMap() {
return isDojoMap() && (((mapid / 100) % 100) % 6) > 0;
}
public boolean isHorntailDefeated() { // all parts of dead horntail can be found here?
for(int i = 8810010; i <= 8810017; i++) {
if (getMonsterById(i) == null) {
+6 -6
View File
@@ -272,13 +272,13 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
int price = (int) Math.min((float)pItem.getPrice() * quantity, Integer.MAX_VALUE);
if (c.getPlayer().getMeso() >= price) {
if (!owner.canHoldMeso(price)) { // thanks Rohenn for noticing owner hold check misplaced
c.getPlayer().dropMessage(1, "Transaction failed since the shop owner can't hold any more mesos.");
c.announce(MaplePacketCreator.enableActions());
return false;
}
if (canBuy(c, newItem)) {
if (!owner.canHoldMeso(price)) {
owner.dropMessage(1, "Transaction failed since the shop owner can't hold any more mesos.");
c.announce(MaplePacketCreator.enableActions());
return false;
}
c.getPlayer().gainMeso(-price, false);
price -= MapleTrade.getFee(price); // thanks BHB for pointing out trade fees not applying here
owner.gainMeso(price, true);
+27 -17
View File
@@ -61,6 +61,7 @@ public class ItemAction extends MapleQuestAction {
for (MapleData iEntry : data.getChildren()) {
int id = MapleDataTool.getInt(iEntry.getChildByPath("id"));
int count = MapleDataTool.getInt(iEntry.getChildByPath("count"), 1);
int period = MapleDataTool.getInt(iEntry.getChildByPath("period"), 0);
Integer prop = null;
MapleData propData = iEntry.getChildByPath("prop");
@@ -75,7 +76,7 @@ public class ItemAction extends MapleQuestAction {
if (iEntry.getChildByPath("job") != null)
job = MapleDataTool.getInt(iEntry.getChildByPath("job"));
items.add(new ItemData(Integer.parseInt(iEntry.getName()), id, count, prop, job, gender));
items.add(new ItemData(Integer.parseInt(iEntry.getName()), id, count, prop, job, gender, period));
}
Collections.sort(items, new Comparator<ItemData>()
@@ -90,8 +91,8 @@ public class ItemAction extends MapleQuestAction {
@Override
public void run(MapleCharacter chr, Integer extSelection) {
List<Pair<Integer, Integer>> takeItem = new LinkedList<>();
List<Pair<Integer, Integer>> giveItem = new LinkedList<>();
List<ItemData> takeItem = new LinkedList<>();
List<ItemData> giveItem = new LinkedList<>();
int props = 0, rndProps = 0, accProps = 0;
for(ItemData item : items) {
@@ -125,34 +126,38 @@ public class ItemAction extends MapleQuestAction {
}
if(iEntry.getCount() < 0) { // Remove Item
takeItem.add(new Pair<>(iEntry.getId(), iEntry.getCount()));
takeItem.add(iEntry);
} else { // Give Item
giveItem.add(new Pair<>(iEntry.getId(), iEntry.getCount()));
giveItem.add(iEntry);
}
}
// must take all needed items before giving others
for(Pair<Integer, Integer> iPair: takeItem) {
MapleInventoryType type = ItemConstants.getInventoryType(iPair.getLeft());
int quantity = iPair.getRight() * -1; // Invert
for(ItemData iEntry: takeItem) {
int itemid = iEntry.getId(), count = iEntry.getCount();
MapleInventoryType type = ItemConstants.getInventoryType(itemid);
int quantity = count * -1; // Invert
if(type.equals(MapleInventoryType.EQUIP)) {
if(chr.getInventory(type).countById(iPair.getLeft()) < quantity) {
if(chr.getInventory(type).countById(itemid) < quantity) {
// Not enough in the equip inventoty, so check Equipped...
if(chr.getInventory(MapleInventoryType.EQUIPPED).countById(iPair.getLeft()) > quantity) {
if(chr.getInventory(MapleInventoryType.EQUIPPED).countById(itemid) > quantity) {
// Found it equipped, so change the type to equipped.
type = MapleInventoryType.EQUIPPED;
}
}
}
MapleInventoryManipulator.removeById(chr.getClient(), type, iPair.getLeft(), quantity, true, false);
chr.announce(MaplePacketCreator.getShowItemGain(iPair.getLeft(), (short) iPair.getRight().shortValue(), true));
MapleInventoryManipulator.removeById(chr.getClient(), type, itemid, quantity, true, false);
chr.announce(MaplePacketCreator.getShowItemGain(itemid, (short) count, true));
}
for(Pair<Integer, Integer> iPair: giveItem) {
MapleInventoryManipulator.addById(chr.getClient(), iPair.getLeft(), (short) iPair.getRight().shortValue(), "", -1);
chr.announce(MaplePacketCreator.getShowItemGain(iPair.getLeft(), (short) iPair.getRight().shortValue(), true));
for(ItemData iEntry: giveItem) {
int itemid = iEntry.getId(), count = iEntry.getCount(), period = iEntry.getPeriod(); // thanks Vcoc for noticing quest milestone item not getting removed from inventory after a while
MapleInventoryManipulator.addById(chr.getClient(), itemid, (short) count, "", -1, period > 0 ? (System.currentTimeMillis() + period * 60 * 1000) : -1);
chr.announce(MaplePacketCreator.getShowItemGain(itemid, (short) count, true));
}
}
@@ -320,16 +325,17 @@ public class ItemAction extends MapleQuestAction {
}
private class ItemData {
private final int map, id, count, job, gender;
private final int map, id, count, job, gender, period;
private final Integer prop;
public ItemData(int map, int id, int count, Integer prop, int job, int gender) {
public ItemData(int map, int id, int count, Integer prop, int job, int gender, int period) {
this.map = map;
this.id = id;
this.count = count;
this.prop = prop;
this.job = job;
this.gender = gender;
this.period = period;
}
public int getId() {
@@ -351,5 +357,9 @@ public class ItemAction extends MapleQuestAction {
public int getGender() {
return gender;
}
public int getPeriod() {
return period;
}
}
}