Switch to Maven file structure

This commit is contained in:
P0nk
2021-03-30 21:07:35 +02:00
parent 4acc5675d6
commit 813643036b
817 changed files with 16 additions and 0 deletions

View File

@@ -0,0 +1,668 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import client.MapleClient;
import config.YamlConfig;
import constants.game.ExpTable;
import constants.inventory.ItemConstants;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import server.MapleItemInformationProvider;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.Randomizer;
public class Equip extends Item {
public static enum ScrollResult {
FAIL(0), SUCCESS(1), CURSE(2);
private int value = -1;
private ScrollResult(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static enum StatUpgrade {
incDEX(0), incSTR(1), incINT(2), incLUK(3),
incMHP(4), incMMP(5), incPAD(6), incMAD(7),
incPDD(8), incMDD(9), incEVA(10), incACC(11),
incSpeed(12), incJump(13), incVicious(14), incSlot(15);
private int value = -1;
private StatUpgrade(int value) {
this.value = value;
}
}
private byte upgradeSlots;
private byte level, itemLevel;
private short flag;
private short str, dex, _int, luk, hp, mp, watk, matk, wdef, mdef, acc, avoid, hands, speed, jump, vicious;
private float itemExp;
private int ringid = -1;
private boolean wear = false;
private boolean isUpgradeable, isElemental = false; // timeless or reverse, or any equip that could levelup on GMS for all effects
public Equip(int id, short position) {
this(id, position, 0);
}
public Equip(int id, short position, int slots) {
super(id, position, (short) 1);
this.upgradeSlots = (byte) slots;
this.itemExp = 0;
this.itemLevel = 1;
this.isElemental = (MapleItemInformationProvider.getInstance().getEquipLevel(id, false) > 1);
}
@Override
public Item copy() {
Equip ret = new Equip(getItemId(), getPosition(), getUpgradeSlots());
ret.str = str;
ret.dex = dex;
ret._int = _int;
ret.luk = luk;
ret.hp = hp;
ret.mp = mp;
ret.matk = matk;
ret.mdef = mdef;
ret.watk = watk;
ret.wdef = wdef;
ret.acc = acc;
ret.avoid = avoid;
ret.hands = hands;
ret.speed = speed;
ret.jump = jump;
ret.flag = flag;
ret.vicious = vicious;
ret.upgradeSlots = upgradeSlots;
ret.itemLevel = itemLevel;
ret.itemExp = itemExp;
ret.level = level;
ret.log = new LinkedList<>(log);
ret.setOwner(getOwner());
ret.setQuantity(getQuantity());
ret.setExpiration(getExpiration());
ret.setGiftFrom(getGiftFrom());
return ret;
}
@Override
public short getFlag() {
return flag;
}
@Override
public byte getItemType() {
return 1;
}
public byte getUpgradeSlots() {
return upgradeSlots;
}
public short getStr() {
return str;
}
public short getDex() {
return dex;
}
public short getInt() {
return _int;
}
public short getLuk() {
return luk;
}
public short getHp() {
return hp;
}
public short getMp() {
return mp;
}
public short getWatk() {
return watk;
}
public short getMatk() {
return matk;
}
public short getWdef() {
return wdef;
}
public short getMdef() {
return mdef;
}
public short getAcc() {
return acc;
}
public short getAvoid() {
return avoid;
}
public short getHands() {
return hands;
}
public short getSpeed() {
return speed;
}
public short getJump() {
return jump;
}
public short getVicious() {
return vicious;
}
@Override
public void setFlag(short flag) {
this.flag = flag;
}
public void setStr(short str) {
this.str = str;
}
public void setDex(short dex) {
this.dex = dex;
}
public void setInt(short _int) {
this._int = _int;
}
public void setLuk(short luk) {
this.luk = luk;
}
public void setHp(short hp) {
this.hp = hp;
}
public void setMp(short mp) {
this.mp = mp;
}
public void setWatk(short watk) {
this.watk = watk;
}
public void setMatk(short matk) {
this.matk = matk;
}
public void setWdef(short wdef) {
this.wdef = wdef;
}
public void setMdef(short mdef) {
this.mdef = mdef;
}
public void setAcc(short acc) {
this.acc = acc;
}
public void setAvoid(short avoid) {
this.avoid = avoid;
}
public void setHands(short hands) {
this.hands = hands;
}
public void setSpeed(short speed) {
this.speed = speed;
}
public void setJump(short jump) {
this.jump = jump;
}
public void setVicious(short vicious) {
this.vicious = vicious;
}
public void setUpgradeSlots(byte upgradeSlots) {
this.upgradeSlots = upgradeSlots;
}
public byte getLevel() {
return level;
}
public void setLevel(byte level) {
this.level = level;
}
private static int getStatModifier(boolean isAttribute) {
// each set of stat points grants a chance for a bonus stat point upgrade at equip level up.
if(YamlConfig.config.server.USE_EQUIPMNT_LVLUP_POWER) {
if(isAttribute) return 2;
else return 4;
}
else {
if(isAttribute) return 4;
else return 16;
}
}
private static int randomizeStatUpgrade(int top) {
int limit = Math.min(top, YamlConfig.config.server.MAX_EQUIPMNT_LVLUP_STAT_UP);
int poolCount = (limit * (limit + 1) / 2) + limit;
int rnd = Randomizer.rand(0, poolCount);
int stat = 0;
if(rnd >= limit) {
rnd -= limit;
stat = 1 + (int)Math.floor((-1 + Math.sqrt((8 * rnd) + 1)) / 2); // optimized randomizeStatUpgrade author: David A.
}
return stat;
}
private static boolean isPhysicalWeapon(int itemid) {
Equip eqp = (Equip) MapleItemInformationProvider.getInstance().getEquipById(itemid);
return eqp.getWatk() >= eqp.getMatk();
}
private boolean isNotWeaponAffinity(StatUpgrade name) {
// Vcoc's idea - WATK/MATK expected gains lessens outside of weapon affinity (physical/magic)
if (ItemConstants.isWeapon(this.getItemId())) {
if (name.equals(StatUpgrade.incPAD)) {
if (!isPhysicalWeapon(this.getItemId())) {
return true;
}
} else if (name.equals(StatUpgrade.incMAD)) {
if (isPhysicalWeapon(this.getItemId())) {
return true;
}
}
}
return false;
}
private void getUnitStatUpgrade(List<Pair<StatUpgrade, Integer>> stats, StatUpgrade name, int curStat, boolean isAttribute) {
isUpgradeable = true;
int maxUpgrade = randomizeStatUpgrade((int)(1 + (curStat / (getStatModifier(isAttribute) * (isNotWeaponAffinity(name) ? 2.7 : 1)))));
if(maxUpgrade == 0) return;
stats.add(new Pair<>(name, maxUpgrade));
}
private static void getUnitSlotUpgrade(List<Pair<StatUpgrade, Integer>> stats, StatUpgrade name) {
if(Math.random() < 0.1) {
stats.add(new Pair<>(name, 1)); // 10% success on getting a slot upgrade.
}
}
private void improveDefaultStats(List<Pair<StatUpgrade, Integer>> stats) {
if(dex > 0) getUnitStatUpgrade(stats, StatUpgrade.incDEX, dex, true);
if(str > 0) getUnitStatUpgrade(stats, StatUpgrade.incSTR, str, true);
if(_int > 0) getUnitStatUpgrade(stats, StatUpgrade.incINT,_int, true);
if(luk > 0) getUnitStatUpgrade(stats, StatUpgrade.incLUK, luk, true);
if(hp > 0) getUnitStatUpgrade(stats, StatUpgrade.incMHP, hp, false);
if(mp > 0) getUnitStatUpgrade(stats, StatUpgrade.incMMP, mp, false);
if(watk > 0) getUnitStatUpgrade(stats, StatUpgrade.incPAD, watk, false);
if(matk > 0) getUnitStatUpgrade(stats, StatUpgrade.incMAD, matk, false);
if(wdef > 0) getUnitStatUpgrade(stats, StatUpgrade.incPDD, wdef, false);
if(mdef > 0) getUnitStatUpgrade(stats, StatUpgrade.incMDD, mdef, false);
if(avoid > 0) getUnitStatUpgrade(stats, StatUpgrade.incEVA, avoid, false);
if(acc > 0) getUnitStatUpgrade(stats, StatUpgrade.incACC, acc, false);
if(speed > 0) getUnitStatUpgrade(stats, StatUpgrade.incSpeed, speed, false);
if(jump > 0) getUnitStatUpgrade(stats, StatUpgrade.incJump, jump, false);
}
public Map<StatUpgrade, Short> getStats() {
Map<StatUpgrade, Short> stats = new HashMap<>(5);
if(dex > 0) stats.put(StatUpgrade.incDEX, dex);
if(str > 0) stats.put(StatUpgrade.incSTR, str);
if(_int > 0) stats.put(StatUpgrade.incINT,_int);
if(luk > 0) stats.put(StatUpgrade.incLUK, luk);
if(hp > 0) stats.put(StatUpgrade.incMHP, hp);
if(mp > 0) stats.put(StatUpgrade.incMMP, mp);
if(watk > 0) stats.put(StatUpgrade.incPAD, watk);
if(matk > 0) stats.put(StatUpgrade.incMAD, matk);
if(wdef > 0) stats.put(StatUpgrade.incPDD, wdef);
if(mdef > 0) stats.put(StatUpgrade.incMDD, mdef);
if(avoid > 0) stats.put(StatUpgrade.incEVA, avoid);
if(acc > 0) stats.put(StatUpgrade.incACC, acc);
if(speed > 0) stats.put(StatUpgrade.incSpeed, speed);
if(jump > 0) stats.put(StatUpgrade.incJump, jump);
return stats;
}
public Pair<String, Pair<Boolean, Boolean>> gainStats(List<Pair<StatUpgrade, Integer>> stats) {
boolean gotSlot = false, gotVicious = false;
String lvupStr = "";
Integer statUp, maxStat = YamlConfig.config.server.MAX_EQUIPMNT_STAT;
for (Pair<StatUpgrade, Integer> stat : stats) {
switch (stat.getLeft()) {
case incDEX:
statUp = Math.min(stat.getRight(), maxStat - dex);
dex += statUp;
lvupStr += "+" + statUp + "DEX ";
break;
case incSTR:
statUp = Math.min(stat.getRight(), maxStat - str);
str += statUp;
lvupStr += "+" + statUp + "STR ";
break;
case incINT:
statUp = Math.min(stat.getRight(), maxStat - _int);
_int += statUp;
lvupStr += "+" + statUp + "INT ";
break;
case incLUK:
statUp = Math.min(stat.getRight(), maxStat - luk);
luk += statUp;
lvupStr += "+" + statUp + "LUK ";
break;
case incMHP:
statUp = Math.min(stat.getRight(), maxStat - hp);
hp += statUp;
lvupStr += "+" + statUp + "HP ";
break;
case incMMP:
statUp = Math.min(stat.getRight(), maxStat - mp);
mp += statUp;
lvupStr += "+" + statUp + "MP ";
break;
case incPAD:
statUp = Math.min(stat.getRight(), maxStat - watk);
watk += statUp;
lvupStr += "+" + statUp + "WATK ";
break;
case incMAD:
statUp = Math.min(stat.getRight(), maxStat - matk);
matk += statUp;
lvupStr += "+" + statUp + "MATK ";
break;
case incPDD:
statUp = Math.min(stat.getRight(), maxStat - wdef);
wdef += statUp;
lvupStr += "+" + statUp + "WDEF ";
break;
case incMDD:
statUp = Math.min(stat.getRight(), maxStat - mdef);
mdef += statUp;
lvupStr += "+" + statUp + "MDEF ";
break;
case incEVA:
statUp = Math.min(stat.getRight(), maxStat - avoid);
avoid += statUp;
lvupStr += "+" + statUp + "AVOID ";
break;
case incACC:
statUp = Math.min(stat.getRight(), maxStat - acc);
acc += statUp;
lvupStr += "+" + statUp + "ACC ";
break;
case incSpeed:
statUp = Math.min(stat.getRight(), maxStat - speed);
speed += statUp;
lvupStr += "+" + statUp + "SPEED ";
break;
case incJump:
statUp = Math.min(stat.getRight(), maxStat - jump);
jump += statUp;
lvupStr += "+" + statUp + "JUMP ";
break;
case incVicious:
vicious -= stat.getRight();
gotVicious = true;
break;
case incSlot:
upgradeSlots += stat.getRight();
gotSlot = true;
break;
}
}
return new Pair<>(lvupStr, new Pair<>(gotSlot, gotVicious));
}
private void gainLevel(MapleClient c) {
List<Pair<StatUpgrade, Integer>> stats = new LinkedList<>();
if(isElemental) {
List<Pair<String, Integer>> elementalStats = MapleItemInformationProvider.getInstance().getItemLevelupStats(getItemId(), itemLevel);
for(Pair<String, Integer> p: elementalStats) {
if(p.getRight() > 0) stats.add(new Pair<>(StatUpgrade.valueOf(p.getLeft()), p.getRight()));
}
}
if(!stats.isEmpty()) {
if(YamlConfig.config.server.USE_EQUIPMNT_LVLUP_SLOTS) {
if(vicious > 0) getUnitSlotUpgrade(stats, StatUpgrade.incVicious);
getUnitSlotUpgrade(stats, StatUpgrade.incSlot);
}
} else {
isUpgradeable = false;
improveDefaultStats(stats);
if(YamlConfig.config.server.USE_EQUIPMNT_LVLUP_SLOTS) {
if(vicious > 0) getUnitSlotUpgrade(stats, StatUpgrade.incVicious);
getUnitSlotUpgrade(stats, StatUpgrade.incSlot);
}
if(isUpgradeable) {
while(stats.isEmpty()) {
improveDefaultStats(stats);
if(YamlConfig.config.server.USE_EQUIPMNT_LVLUP_SLOTS) {
if(vicious > 0) getUnitSlotUpgrade(stats, StatUpgrade.incVicious);
getUnitSlotUpgrade(stats, StatUpgrade.incSlot);
}
}
}
}
itemLevel++;
String lvupStr = "'" + MapleItemInformationProvider.getInstance().getName(this.getItemId()) + "' is now level " + itemLevel + "! ";
String showStr = "#e'" + MapleItemInformationProvider.getInstance().getName(this.getItemId()) + "'#b is now #elevel #r" + itemLevel + "#k#b!";
Pair<String, Pair<Boolean, Boolean>> res = this.gainStats(stats);
lvupStr += res.getLeft();
boolean gotSlot = res.getRight().getLeft();
boolean gotVicious = res.getRight().getRight();
if (gotVicious) {
//c.getPlayer().dropMessage(6, "A new Vicious Hammer opportunity has been found on the '" + MapleItemInformationProvider.getInstance().getName(getItemId()) + "'!");
lvupStr += "+VICIOUS ";
}
if (gotSlot) {
//c.getPlayer().dropMessage(6, "A new upgrade slot has been found on the '" + MapleItemInformationProvider.getInstance().getName(getItemId()) + "'!");
lvupStr += "+UPGSLOT ";
}
c.getPlayer().equipChanged();
showLevelupMessage(showStr, c); // thanks to Polaris dev team !
c.getPlayer().dropMessage(6, lvupStr);
c.announce(MaplePacketCreator.showEquipmentLevelUp());
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), MaplePacketCreator.showForeignEffect(c.getPlayer().getId(), 15));
c.getPlayer().forceUpdateItem(this);
}
public int getItemExp() {
return (int) itemExp;
}
private static double normalizedMasteryExp(int reqLevel) {
// Conversion factor between mob exp and equip exp gain. Through many calculations, the expected for equipment levelup
// from level 1 to 2 is killing about 100~200 mobs of the same level range, on a 1x EXP rate scenario.
if(reqLevel < 5) {
return 42;
} else if(reqLevel >= 78) {
return Math.max((10413.648 * Math.exp(reqLevel * 0.03275)), 15);
} else if(reqLevel >= 38) {
return Math.max(( 4985.818 * Math.exp(reqLevel * 0.02007)), 15);
} else if(reqLevel >= 18) {
return Math.max(( 248.219 * Math.exp(reqLevel * 0.11093)), 15);
} else {
return Math.max(((1334.564 * Math.log(reqLevel)) - 1731.976), 15);
}
}
public synchronized void gainItemExp(MapleClient c, int gain) { // Ronan's Equip Exp gain method
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if(!ii.isUpgradeable(this.getItemId())) {
return;
}
int equipMaxLevel = Math.min(30, Math.max(ii.getEquipLevel(this.getItemId(), true), YamlConfig.config.server.USE_EQUIPMNT_LVLUP));
if (itemLevel >= equipMaxLevel) {
return;
}
int reqLevel = ii.getEquipLevelReq(this.getItemId());
float masteryModifier = (float)(YamlConfig.config.server.EQUIP_EXP_RATE * ExpTable.getExpNeededForLevel(1)) / (float)normalizedMasteryExp(reqLevel);
float elementModifier = (isElemental) ? 0.85f : 0.6f;
float baseExpGain = gain * elementModifier * masteryModifier;
itemExp += baseExpGain;
int expNeeded = ExpTable.getEquipExpNeededForLevel(itemLevel);
if(YamlConfig.config.server.USE_DEBUG_SHOW_INFO_EQPEXP) System.out.println("'" + ii.getName(this.getItemId()) + "' -> EXP Gain: " + gain + " Mastery: " + masteryModifier + " Base gain: " + baseExpGain + " exp: " + itemExp + " / " + expNeeded + ", Kills TNL: " + expNeeded / (baseExpGain / c.getPlayer().getExpRate()));
if (itemExp >= expNeeded) {
while(itemExp >= expNeeded) {
itemExp -= expNeeded;
gainLevel(c);
if(itemLevel >= equipMaxLevel) {
itemExp = 0.0f;
break;
}
expNeeded = ExpTable.getEquipExpNeededForLevel(itemLevel);
}
}
c.getPlayer().forceUpdateItem(this);
//if(YamlConfig.config.server.USE_DEBUG) c.getPlayer().dropMessage("'" + ii.getName(this.getItemId()) + "': " + itemExp + " / " + expNeeded);
}
private boolean reachedMaxLevel() {
if (isElemental) {
if (itemLevel < MapleItemInformationProvider.getInstance().getEquipLevel(getItemId(), true)) {
return false;
}
}
return itemLevel >= YamlConfig.config.server.USE_EQUIPMNT_LVLUP;
}
public String showEquipFeatures(MapleClient c) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if(!ii.isUpgradeable(this.getItemId())) return "";
String eqpName = ii.getName(getItemId());
String eqpInfo = reachedMaxLevel() ? " #e#rMAX LEVEL#k#n" : (" EXP: #e#b" + (int)itemExp + "#k#n / " + ExpTable.getEquipExpNeededForLevel(itemLevel));
return "'" + eqpName + "' -> LV: #e#b" + itemLevel + "#k#n " + eqpInfo + "\r\n";
}
private static void showLevelupMessage(String msg, MapleClient c) {
c.getPlayer().showHint(msg, 300);
}
public void setItemExp(int exp) {
this.itemExp = exp;
}
public void setItemLevel(byte level) {
this.itemLevel = level;
}
@Override
public void setQuantity(short quantity) {
if (quantity < 0 || quantity > 1) {
throw new RuntimeException("Setting the quantity to " + quantity + " on an equip (itemid: " + getItemId() + ")");
}
super.setQuantity(quantity);
}
public void setUpgradeSlots(int i) {
this.upgradeSlots = (byte) i;
}
public void setVicious(int i) {
this.vicious = (short) i;
}
public int getRingId() {
return ringid;
}
public void setRingId(int id) {
this.ringid = id;
}
public boolean isWearing() {
return wear;
}
public void wear(boolean yes) {
wear = yes;
}
public byte getItemLevel() {
return itemLevel;
}
}

View File

@@ -0,0 +1,193 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import constants.inventory.ItemConstants;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import client.inventory.manipulator.MapleKarmaManipulator;
import server.MapleItemInformationProvider;
public class Item implements Comparable<Item> {
private static AtomicInteger runningCashId = new AtomicInteger(777000000); // pets & rings shares cashid values
private int id, cashId, sn;
private short position;
private short quantity;
private int petid = -1;
private MaplePet pet = null;
private String owner = "";
protected List<String> log;
private short flag;
private long expiration = -1;
private String giftFrom = "";
public Item(int id, short position, short quantity) {
this.id = id;
this.position = position;
this.quantity = quantity;
this.log = new LinkedList<>();
this.flag = 0;
}
public Item(int id, short position, short quantity, int petid) {
this.id = id;
this.position = position;
this.quantity = quantity;
if (petid > -1) { // issue with null "pet" having petid > -1 found thanks to MedicOP
this.pet = MaplePet.loadFromDb(id, position, petid);
if (this.pet == null) {
petid = -1;
}
}
this.petid = petid;
this.flag = 0;
this.log = new LinkedList<>();
}
public Item copy() {
Item ret = new Item(id, position, quantity, petid);
ret.flag = flag;
ret.owner = owner;
ret.expiration = expiration;
ret.log = new LinkedList<>(log);
return ret;
}
public void setPosition(short position) {
this.position = position;
if (this.pet != null) this.pet.setPosition(position);
}
public void setQuantity(short quantity) {
this.quantity = quantity;
}
public int getItemId() {
return id;
}
public int getCashId() {
if (cashId == 0) {
cashId = runningCashId.getAndIncrement();
}
return cashId;
}
public short getPosition() {
return position;
}
public short getQuantity() {
return quantity;
}
public MapleInventoryType getInventoryType() {
return ItemConstants.getInventoryType(id);
}
public byte getItemType() { // 1: equip, 3: pet, 2: other
if (getPetId() > -1) {
return 3;
}
return 2;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public int getPetId() {
return petid;
}
@Override
public int compareTo(Item other) {
if (this.id < other.getItemId()) {
return -1;
} else if (this.id > other.getItemId()) {
return 1;
}
return 0;
}
@Override
public String toString() {
return "Item: " + id + " quantity: " + quantity;
}
public List<String> getLog() {
return Collections.unmodifiableList(log);
}
public short getFlag() {
return flag;
}
public void setFlag(short b) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if (ii.isAccountRestricted(id)) {
b |= ItemConstants.ACCOUNT_SHARING; // thanks Shinigami15 for noticing ACCOUNT_SHARING flag not being applied properly to items server-side
}
this.flag = b;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expire) {
this.expiration = !ItemConstants.isPermanentItem(id) ? expire : ItemConstants.isPet(id) ? Long.MAX_VALUE : -1;
}
public int getSN() {
return sn;
}
public void setSN(int sn) {
this.sn = sn;
}
public String getGiftFrom() {
return giftFrom;
}
public void setGiftFrom(String giftFrom) {
this.giftFrom = giftFrom;
}
public MaplePet getPet() {
return pet;
}
public boolean isUntradeable() {
return ((this.getFlag() & ItemConstants.UNTRADEABLE) == ItemConstants.UNTRADEABLE) || (MapleItemInformationProvider.getInstance().isDropRestricted(this.getItemId()) && !MapleKarmaManipulator.hasKarmaFlag(this));
}
}

View File

@@ -0,0 +1,487 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation. You may not use, modify
or distribute this program under any other version of the
GNU Affero General Public License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import tools.DatabaseConnection;
import tools.Pair;
/**
*
* @author Flav
*/
public enum ItemFactory {
INVENTORY(1, false),
STORAGE(2, true),
CASH_EXPLORER(3, true),
CASH_CYGNUS(4, true),
CASH_ARAN(5, true),
MERCHANT(6, false),
CASH_OVERALL(7, true),
MARRIAGE_GIFTS(8, false),
DUEY(9, false);
private final int value;
private final boolean account;
private static final int lockCount = 400;
private static final Lock locks[] = new Lock[lockCount]; // thanks Masterrulax for pointing out a bottleneck issue here
static {
for (int i = 0; i < lockCount; i++) {
locks[i] = MonitoredReentrantLockFactory.createLock(MonitoredLockType.ITEM, true);
}
}
private ItemFactory(int value, boolean account) {
this.value = value;
this.account = account;
}
public int getValue() {
return value;
}
public List<Pair<Item, MapleInventoryType>> loadItems(int id, boolean login) throws SQLException {
if(value != 6) return loadItemsCommon(id, login);
else return loadItemsMerchant(id, login);
}
public void saveItems(List<Pair<Item, MapleInventoryType>> items, int id, Connection con) throws SQLException {
saveItems(items, null, id, con);
}
public void saveItems(List<Pair<Item, MapleInventoryType>> items, List<Short> bundlesList, int id, Connection con) throws SQLException {
// thanks Arufonsu, MedicOP, BHB for pointing a "synchronized" bottleneck here
if(value != 6) saveItemsCommon(items, id, con);
else saveItemsMerchant(items, bundlesList, id, con);
}
private static Equip loadEquipFromResultSet(ResultSet rs) throws SQLException {
Equip equip = new Equip(rs.getInt("itemid"), (short) rs.getInt("position"));
equip.setOwner(rs.getString("owner"));
equip.setQuantity((short) rs.getInt("quantity"));
equip.setAcc((short) rs.getInt("acc"));
equip.setAvoid((short) rs.getInt("avoid"));
equip.setDex((short) rs.getInt("dex"));
equip.setHands((short) rs.getInt("hands"));
equip.setHp((short) rs.getInt("hp"));
equip.setInt((short) rs.getInt("int"));
equip.setJump((short) rs.getInt("jump"));
equip.setVicious((short) rs.getInt("vicious"));
equip.setFlag((short) rs.getInt("flag"));
equip.setLuk((short) rs.getInt("luk"));
equip.setMatk((short) rs.getInt("matk"));
equip.setMdef((short) rs.getInt("mdef"));
equip.setMp((short) rs.getInt("mp"));
equip.setSpeed((short) rs.getInt("speed"));
equip.setStr((short) rs.getInt("str"));
equip.setWatk((short) rs.getInt("watk"));
equip.setWdef((short) rs.getInt("wdef"));
equip.setUpgradeSlots((byte) rs.getInt("upgradeslots"));
equip.setLevel((byte) rs.getByte("level"));
equip.setItemExp(rs.getInt("itemexp"));
equip.setItemLevel(rs.getByte("itemlevel"));
equip.setExpiration(rs.getLong("expiration"));
equip.setGiftFrom(rs.getString("giftFrom"));
equip.setRingId(rs.getInt("ringid"));
return equip;
}
public static List<Pair<Item, Integer>> loadEquippedItems(int id, boolean isAccount, boolean login) throws SQLException {
List<Pair<Item, Integer>> items = new ArrayList<>();
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM ");
query.append("(SELECT id, accountid FROM characters) AS accountterm ");
query.append("RIGHT JOIN ");
query.append("(SELECT * FROM (`inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`))) AS equipterm");
query.append(" ON accountterm.id=equipterm.characterid ");
query.append("WHERE accountterm.`");
query.append(isAccount ? "accountid" : "characterid");
query.append("` = ?");
query.append(login ? " AND `inventorytype` = " + MapleInventoryType.EQUIPPED.getType() : "");
try (Connection con = DatabaseConnection.getConnection()) {
try (PreparedStatement ps = con.prepareStatement(query.toString())) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Integer cid = rs.getInt("characterid");
items.add(new Pair<Item, Integer>(loadEquipFromResultSet(rs), cid));
}
}
}
}
return items;
}
private List<Pair<Item, MapleInventoryType>> loadItemsCommon(int id, boolean login) throws SQLException {
List<Pair<Item, MapleInventoryType>> items = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
Connection con = DatabaseConnection.getConnection();
try {
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
if (login) {
query.append(" AND `inventorytype` = ").append(MapleInventoryType.EQUIPPED.getType());
}
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
rs = ps.executeQuery();
while (rs.next()) {
MapleInventoryType mit = MapleInventoryType.getByType(rs.getByte("inventorytype"));
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
items.add(new Pair<Item, MapleInventoryType>(loadEquipFromResultSet(rs), mit));
} else {
int petid = rs.getInt("petid");
if (rs.wasNull()) {
petid = -1;
}
Item item = new Item(rs.getInt("itemid"), (byte) rs.getInt("position"), (short) rs.getInt("quantity"), petid);
item.setOwner(rs.getString("owner"));
item.setExpiration(rs.getLong("expiration"));
item.setGiftFrom(rs.getString("giftFrom"));
item.setFlag((short) rs.getInt("flag"));
items.add(new Pair<>(item, mit));
}
}
rs.close();
ps.close();
con.close();
} finally {
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (con != null && !con.isClosed()) {
con.close();
}
}
return items;
}
private void saveItemsCommon(List<Pair<Item, MapleInventoryType>> items, int id, Connection con) throws SQLException {
PreparedStatement ps = null;
PreparedStatement pse = null;
ResultSet rs = null;
Lock lock = locks[id % lockCount];
lock.lock();
try {
StringBuilder query = new StringBuilder();
query.append("DELETE `inventoryitems`, `inventoryequipment` FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
ps.executeUpdate();
ps.close();
ps = con.prepareStatement("INSERT INTO `inventoryitems` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
if (!items.isEmpty()) {
for (Pair<Item, MapleInventoryType> pair : items) {
Item item = pair.getLeft();
MapleInventoryType mit = pair.getRight();
ps.setInt(1, value);
ps.setString(2, account ? null : String.valueOf(id));
ps.setString(3, account ? String.valueOf(id) : null);
ps.setInt(4, item.getItemId());
ps.setInt(5, mit.getType());
ps.setInt(6, item.getPosition());
ps.setInt(7, item.getQuantity());
ps.setString(8, item.getOwner());
ps.setInt(9, item.getPetId()); // thanks Daddy Egg for alerting a case of unique petid constraint breach getting raised
ps.setInt(10, item.getFlag());
ps.setLong(11, item.getExpiration());
ps.setString(12, item.getGiftFrom());
ps.executeUpdate();
pse = con.prepareStatement("INSERT INTO `inventoryequipment` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
rs = ps.getGeneratedKeys();
if (!rs.next()) {
throw new RuntimeException("Inserting item failed.");
}
pse.setInt(1, rs.getInt(1));
rs.close();
Equip equip = (Equip) item;
pse.setInt(2, equip.getUpgradeSlots());
pse.setInt(3, equip.getLevel());
pse.setInt(4, equip.getStr());
pse.setInt(5, equip.getDex());
pse.setInt(6, equip.getInt());
pse.setInt(7, equip.getLuk());
pse.setInt(8, equip.getHp());
pse.setInt(9, equip.getMp());
pse.setInt(10, equip.getWatk());
pse.setInt(11, equip.getMatk());
pse.setInt(12, equip.getWdef());
pse.setInt(13, equip.getMdef());
pse.setInt(14, equip.getAcc());
pse.setInt(15, equip.getAvoid());
pse.setInt(16, equip.getHands());
pse.setInt(17, equip.getSpeed());
pse.setInt(18, equip.getJump());
pse.setInt(19, 0);
pse.setInt(20, equip.getVicious());
pse.setInt(21, equip.getItemLevel());
pse.setInt(22, equip.getItemExp());
pse.setInt(23, equip.getRingId());
pse.executeUpdate();
}
pse.close();
}
}
ps.close();
} finally {
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (pse != null && !pse.isClosed()) {
pse.close();
}
if(rs != null && !rs.isClosed()) {
rs.close();
}
lock.unlock();
}
}
private List<Pair<Item, MapleInventoryType>> loadItemsMerchant(int id, boolean login) throws SQLException {
List<Pair<Item, MapleInventoryType>> items = new ArrayList<>();
PreparedStatement ps = null, ps2 = null;
ResultSet rs = null, rs2 = null;
Connection con = DatabaseConnection.getConnection();
try {
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
if (login) {
query.append(" AND `inventorytype` = ").append(MapleInventoryType.EQUIPPED.getType());
}
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
rs = ps.executeQuery();
while (rs.next()) {
ps2 = con.prepareStatement("SELECT `bundles` FROM `inventorymerchant` WHERE `inventoryitemid` = ?");
ps2.setInt(1, rs.getInt("inventoryitemid"));
rs2 = ps2.executeQuery();
short bundles = 0;
if(rs2.next()) {
bundles = rs2.getShort("bundles");
}
MapleInventoryType mit = MapleInventoryType.getByType(rs.getByte("inventorytype"));
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
items.add(new Pair<Item, MapleInventoryType>(loadEquipFromResultSet(rs), mit));
} else {
if(bundles > 0) {
int petid = rs.getInt("petid");
if (rs.wasNull()) {
petid = -1;
}
Item item = new Item(rs.getInt("itemid"), (byte) rs.getInt("position"), (short)(bundles * rs.getInt("quantity")), petid);
item.setOwner(rs.getString("owner"));
item.setExpiration(rs.getLong("expiration"));
item.setGiftFrom(rs.getString("giftFrom"));
item.setFlag((short) rs.getInt("flag"));
items.add(new Pair<>(item, mit));
}
}
rs2.close();
ps2.close();
}
rs.close();
ps.close();
con.close();
} finally {
if (rs2 != null && !rs2.isClosed()) {
rs2.close();
}
if (ps2 != null && !ps2.isClosed()) {
ps2.close();
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (con != null && !con.isClosed()) {
con.close();
}
}
return items;
}
private void saveItemsMerchant(List<Pair<Item, MapleInventoryType>> items, List<Short> bundlesList, int id, Connection con) throws SQLException {
PreparedStatement ps = null;
PreparedStatement pse = null;
ResultSet rs = null;
Lock lock = locks[id % lockCount];
lock.lock();
try {
ps = con.prepareStatement("DELETE FROM `inventorymerchant` WHERE `characterid` = ?");
ps.setInt(1, id);
ps.executeUpdate();
ps.close();
StringBuilder query = new StringBuilder();
query.append("DELETE `inventoryitems`, `inventoryequipment` FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
ps.executeUpdate();
ps.close();
ps = con.prepareStatement("INSERT INTO `inventoryitems` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
if (!items.isEmpty()) {
int i = 0;
for (Pair<Item, MapleInventoryType> pair : items) {
Item item = pair.getLeft();
Short bundles = bundlesList.get(i);
MapleInventoryType mit = pair.getRight();
i++;
ps.setInt(1, value);
ps.setString(2, account ? null : String.valueOf(id));
ps.setString(3, account ? String.valueOf(id) : null);
ps.setInt(4, item.getItemId());
ps.setInt(5, mit.getType());
ps.setInt(6, item.getPosition());
ps.setInt(7, item.getQuantity());
ps.setString(8, item.getOwner());
ps.setInt(9, item.getPetId());
ps.setInt(10, item.getFlag());
ps.setLong(11, item.getExpiration());
ps.setString(12, item.getGiftFrom());
ps.executeUpdate();
rs = ps.getGeneratedKeys();
if (!rs.next()) {
throw new RuntimeException("Inserting item failed.");
}
int genKey = rs.getInt(1);
rs.close();
pse = con.prepareStatement("INSERT INTO `inventorymerchant` VALUES (DEFAULT, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
pse.setInt(1, genKey);
pse.setInt(2, id);
pse.setInt(3, bundles);
pse.executeUpdate();
pse.close();
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
pse = con.prepareStatement("INSERT INTO `inventoryequipment` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
pse.setInt(1, genKey);
Equip equip = (Equip) item;
pse.setInt(2, equip.getUpgradeSlots());
pse.setInt(3, equip.getLevel());
pse.setInt(4, equip.getStr());
pse.setInt(5, equip.getDex());
pse.setInt(6, equip.getInt());
pse.setInt(7, equip.getLuk());
pse.setInt(8, equip.getHp());
pse.setInt(9, equip.getMp());
pse.setInt(10, equip.getWatk());
pse.setInt(11, equip.getMatk());
pse.setInt(12, equip.getWdef());
pse.setInt(13, equip.getMdef());
pse.setInt(14, equip.getAcc());
pse.setInt(15, equip.getAvoid());
pse.setInt(16, equip.getHands());
pse.setInt(17, equip.getSpeed());
pse.setInt(18, equip.getJump());
pse.setInt(19, 0);
pse.setInt(20, equip.getVicious());
pse.setInt(21, equip.getItemLevel());
pse.setInt(22, equip.getItemExp());
pse.setInt(23, equip.getRingId());
pse.executeUpdate();
pse.close();
}
}
}
ps.close();
} finally {
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (pse != null && !pse.isClosed()) {
pse.close();
}
if(rs != null && !rs.isClosed()) {
rs.close();
}
lock.unlock();
}
}
}

View File

@@ -0,0 +1,677 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.concurrent.locks.Lock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import tools.Pair;
import client.MapleCharacter;
import client.MapleClient;
import constants.inventory.ItemConstants;
import server.MapleItemInformationProvider;
import client.inventory.manipulator.MapleInventoryManipulator;
import tools.FilePrinter;
import net.server.audit.locks.MonitoredLockType;
import server.ThreadManager;
/**
*
* @author Matze, Ronan
*/
public class MapleInventory implements Iterable<Item> {
protected MapleCharacter owner;
protected Map<Short, Item> inventory = new LinkedHashMap<>();
protected byte slotLimit;
protected MapleInventoryType type;
protected boolean checked = false;
protected Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.INVENTORY, true);
public MapleInventory(MapleCharacter mc, MapleInventoryType type, byte slotLimit) {
this.owner = mc;
this.inventory = new LinkedHashMap<>();
this.type = type;
this.slotLimit = slotLimit;
}
public boolean isExtendableInventory() { // not sure about cash, basing this on the previous one.
return !(type.equals(MapleInventoryType.UNDEFINED) || type.equals(MapleInventoryType.EQUIPPED) || type.equals(MapleInventoryType.CASH));
}
public boolean isEquipInventory() {
return type.equals(MapleInventoryType.EQUIP) || type.equals(MapleInventoryType.EQUIPPED);
}
public byte getSlotLimit() {
lock.lock();
try {
return slotLimit;
} finally {
lock.unlock();
}
}
public void setSlotLimit(int newLimit) {
lock.lock();
try {
if (newLimit < slotLimit) {
List<Short> toRemove = new LinkedList<>();
for (Item it : list()) {
if (it.getPosition() > newLimit) {
toRemove.add(it.getPosition());
}
}
for (Short slot : toRemove) {
removeSlot(slot);
}
}
slotLimit = (byte) newLimit;
} finally {
lock.unlock();
}
}
public Collection<Item> list() {
lock.lock();
try {
return Collections.unmodifiableCollection(inventory.values());
} finally {
lock.unlock();
}
}
public Item findById(int itemId) {
for (Item item : list()) {
if (item.getItemId() == itemId) {
return item;
}
}
return null;
}
public Item findByName(String name) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
for (Item item : list()) {
String itemName = ii.getName(item.getItemId());
if(itemName == null) {
FilePrinter.printError(FilePrinter.EXCEPTION, "[CRITICAL] Item " + item.getItemId() + " has no name.");
continue;
}
if (name.compareToIgnoreCase(itemName) == 0) {
return item;
}
}
return null;
}
public int countById(int itemId) {
int qty = 0;
for (Item item : list()) {
if (item.getItemId() == itemId) {
qty += item.getQuantity();
}
}
return qty;
}
public int countNotOwnedById(int itemId) {
int qty = 0;
for (Item item : list()) {
if (item.getItemId() == itemId && item.getOwner().equals("")) {
qty += item.getQuantity();
}
}
return qty;
}
public int freeSlotCountById(int itemId, int required) {
List<Item> itemList = listById(itemId);
int openSlot = 0;
if(!ItemConstants.isRechargeable(itemId)) {
for (Item item : itemList) {
required -= item.getQuantity();
if(required >= 0) {
openSlot++;
if(required == 0) return openSlot;
} else {
return openSlot;
}
}
} else {
for (Item item : itemList) {
required -= 1;
if(required >= 0) {
openSlot++;
if(required == 0) return openSlot;
} else {
return openSlot;
}
}
}
return -1;
}
public List<Item> listById(int itemId) {
List<Item> ret = new ArrayList<>();
for (Item item : list()) {
if (item.getItemId() == itemId) {
ret.add(item);
}
}
if (ret.size() > 1) {
Collections.sort(ret, new Comparator<Item>() {
@Override
public int compare(Item i1, Item i2) {
return i1.getPosition() - i2.getPosition();
}
});
}
return ret;
}
public List<Item> linkedListById(int itemId) {
List<Item> ret = new LinkedList<>();
for (Item item : list()) {
if (item.getItemId() == itemId) {
ret.add(item);
}
}
if (ret.size() > 1) {
Collections.sort(ret, new Comparator<Item>() {
@Override
public int compare(Item i1, Item i2) {
return i1.getPosition() - i2.getPosition();
}
});
}
return ret;
}
public short addItem(Item item) {
short slotId = addSlot(item);
if (slotId == -1) {
return -1;
}
item.setPosition(slotId);
return slotId;
}
public void addItemFromDB(Item item) {
if (item.getPosition() < 0 && !type.equals(MapleInventoryType.EQUIPPED)) {
return;
}
addSlotFromDB(item.getPosition(), item);
}
private static boolean isSameOwner(Item source, Item target) {
return source.getOwner().equals(target.getOwner());
}
public void move(short sSlot, short dSlot, short slotMax) {
lock.lock();
try {
Item source = (Item) inventory.get(sSlot);
Item target = (Item) inventory.get(dSlot);
if (source == null) {
return;
}
if (target == null) {
source.setPosition(dSlot);
inventory.put(dSlot, source);
inventory.remove(sSlot);
} else if (target.getItemId() == source.getItemId() && !ItemConstants.isRechargeable(source.getItemId()) && isSameOwner(source, target)) {
if (type.getType() == MapleInventoryType.EQUIP.getType() || type.getType() == MapleInventoryType.CASH.getType()) {
swap(target, source);
} else if (source.getQuantity() + target.getQuantity() > slotMax) {
short rest = (short) ((source.getQuantity() + target.getQuantity()) - slotMax);
source.setQuantity(rest);
target.setQuantity(slotMax);
} else {
target.setQuantity((short) (source.getQuantity() + target.getQuantity()));
inventory.remove(sSlot);
}
} else {
swap(target, source);
}
} finally {
lock.unlock();
}
}
private void swap(Item source, Item target) {
inventory.remove(source.getPosition());
inventory.remove(target.getPosition());
short swapPos = source.getPosition();
source.setPosition(target.getPosition());
target.setPosition(swapPos);
inventory.put(source.getPosition(), source);
inventory.put(target.getPosition(), target);
}
public Item getItem(short slot) {
lock.lock();
try {
return inventory.get(slot);
} finally {
lock.unlock();
}
}
public void removeItem(short slot) {
removeItem(slot, (short) 1, false);
}
public void removeItem(short slot, short quantity, boolean allowZero) {
Item item = getItem(slot);
if (item == null) {// TODO is it ok not to throw an exception here?
return;
}
item.setQuantity((short) (item.getQuantity() - quantity));
if (item.getQuantity() < 0) {
item.setQuantity((short) 0);
}
if (item.getQuantity() == 0 && !allowZero) {
removeSlot(slot);
}
}
protected short addSlot(Item item) {
if(item == null) {
return -1;
}
short slotId;
lock.lock();
try {
slotId = getNextFreeSlot();
if (slotId < 0) {
return -1;
}
inventory.put(slotId, item);
} finally {
lock.unlock();
}
if (ItemConstants.isRateCoupon(item.getItemId())) {
ThreadManager.getInstance().newTask(new Runnable() { // deadlocks with coupons rates found thanks to GabrielSin & Masterrulax
@Override
public void run() {
owner.updateCouponRates();
}
});
}
return slotId;
}
protected void addSlotFromDB(short slot, Item item) {
lock.lock();
try {
inventory.put(slot, item);
} finally {
lock.unlock();
}
if (ItemConstants.isRateCoupon(item.getItemId())) {
ThreadManager.getInstance().newTask(new Runnable() {
@Override
public void run() {
owner.updateCouponRates();
}
});
}
}
public void removeSlot(short slot) {
Item item;
lock.lock();
try {
item = inventory.remove(slot);
} finally {
lock.unlock();
}
if (item != null && ItemConstants.isRateCoupon(item.getItemId())) {
ThreadManager.getInstance().newTask(new Runnable() {
@Override
public void run() {
owner.updateCouponRates();
}
});
}
}
public boolean isFull() {
lock.lock();
try {
return inventory.size() >= slotLimit;
} finally {
lock.unlock();
}
}
public boolean isFull(int margin) {
lock.lock();
try {
//System.out.print("(" + inventory.size() + " " + margin + " <> " + slotLimit + ")");
return inventory.size() + margin >= slotLimit;
} finally {
lock.unlock();
}
}
public boolean isFullAfterSomeItems(int margin, int used) {
lock.lock();
try {
//System.out.print("(" + inventory.size() + " " + margin + " <> " + slotLimit + " -" + used + ")");
return inventory.size() + margin >= slotLimit - used;
} finally {
lock.unlock();
}
}
public short getNextFreeSlot() {
if (isFull()) {
return -1;
}
lock.lock();
try {
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.containsKey(i)) {
return i;
}
}
return -1;
} finally {
lock.unlock();
}
}
public short getNumFreeSlot() {
if (isFull()) {
return 0;
}
lock.lock();
try {
short free = 0;
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.containsKey(i)) {
free++;
}
}
return free;
} finally {
lock.unlock();
}
}
private static boolean checkItemRestricted(List<Pair<Item, MapleInventoryType>> items) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
// thanks Shavit for noticing set creation that would be only effective in rare situations
for (Pair<Item, MapleInventoryType> p : items) {
int itemid = p.getLeft().getItemId();
if (ii.isPickupRestricted(itemid) && p.getLeft().getQuantity() > 1) {
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));
}
public static boolean checkSpot(MapleCharacter chr, List<Item> items) {
List<Pair<Item, MapleInventoryType>> listItems = new LinkedList<>();
for (Item item : items) {
listItems.add(new Pair<>(item, item.getInventoryType()));
}
return checkSpotsAndOwnership(chr, listItems);
}
public static boolean checkSpots(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items) {
return checkSpots(chr, items, false);
}
public static boolean checkSpots(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items, boolean useProofInv) {
int invTypesSize = MapleInventoryType.values().length;
List<Integer> zeroedList = new ArrayList<>(invTypesSize);
for(byte i = 0; i < invTypesSize; i++) zeroedList.add(0);
return checkSpots(chr, items, zeroedList, useProofInv);
}
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<>();
for (Pair<Item, MapleInventoryType> item : items) {
Integer itemId = item.left.getItemId();
List<Integer> qty = rcvItems.get(itemId);
if(qty == null) {
List<Integer> itemQtyList = new LinkedList<>();
itemQtyList.add((int) item.left.getQuantity());
rcvItems.put(itemId, itemQtyList);
rcvTypes.put(itemId, item.right.getType());
} else {
if (!ItemConstants.isEquipment(itemId) && !ItemConstants.isRechargeable(itemId)) {
qty.set(0, qty.get(0) + item.left.getQuantity());
} else {
qty.add((int) item.left.getQuantity());
}
}
}
MapleClient c = chr.getClient();
for(Entry<Integer, List<Integer>> it: rcvItems.entrySet()) {
int itemType = rcvTypes.get(it.getKey()) - 1;
for (Integer itValue : it.getValue()) {
int usedSlots = typesSlotsUsed.get(itemType);
int result = MapleInventoryManipulator.checkSpaceProgressively(c, it.getKey(), itValue, "", usedSlots, useProofInv);
boolean hasSpace = ((result % 2) != 0);
if(!hasSpace) return false;
typesSlotsUsed.set(itemType, (result >> 1));
}
}
return true;
}
private static long fnvHash32(final String k) {
final int FNV_32_INIT = 0x811c9dc5;
final int FNV_32_PRIME = 0x01000193;
int rv = FNV_32_INIT;
final int len = k.length();
for(int i = 0; i < len; i++) {
rv ^= k.charAt(i);
rv *= FNV_32_PRIME;
}
return rv >= 0 ? rv : (2L * Integer.MAX_VALUE) + rv;
}
private static Long hashKey(Integer itemId, String owner) {
return (itemId.longValue() << 32L) + fnvHash32(owner);
}
public static boolean checkSpotsAndOwnership(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items) {
return checkSpotsAndOwnership(chr, items, false);
}
public static boolean checkSpotsAndOwnership(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items, boolean useProofInv) {
List<Integer> zeroedList = new ArrayList<>(5);
for(byte i = 0; i < 5; i++) zeroedList.add(0);
return checkSpotsAndOwnership(chr, items, zeroedList, useProofInv);
}
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<>();
for (Pair<Item, MapleInventoryType> item : items) {
Long itemHash = hashKey(item.left.getItemId(), item.left.getOwner());
List<Integer> qty = rcvItems.get(itemHash);
if(qty == null) {
List<Integer> itemQtyList = new LinkedList<>();
itemQtyList.add((int) item.left.getQuantity());
rcvItems.put(itemHash, itemQtyList);
rcvTypes.put(itemHash, item.right.getType());
rcvOwners.put(itemHash, item.left.getOwner());
} else {
// thanks BHB88 for pointing out an issue with rechargeable items being stacked on inventory check
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());
}
}
}
MapleClient c = chr.getClient();
for(Entry<Long, List<Integer>> it: rcvItems.entrySet()) {
int itemType = rcvTypes.get(it.getKey()) - 1;
int itemId = (int) (it.getKey() >> 32L);
for (Integer itValue : it.getValue()) {
int usedSlots = typesSlotsUsed.get(itemType);
//System.out.print("inserting " + itemId.intValue() + " with type " + itemType + " qty " + it.getValue() + " owner '" + rcvOwners.get(it.getKey()) + "' current usedSlots:");
//for(Integer i : typesSlotsUsed) System.out.print(" " + i);
int result = MapleInventoryManipulator.checkSpaceProgressively(c, itemId, itValue, rcvOwners.get(it.getKey()), usedSlots, useProofInv);
boolean hasSpace = ((result % 2) != 0);
//System.out.print(" -> hasSpace: " + hasSpace + " RESULT : " + result + "\n");
if(!hasSpace) return false;
typesSlotsUsed.set(itemType, (result >> 1));
}
}
return true;
}
public MapleInventoryType getType() {
return type;
}
@Override
public Iterator<Item> iterator() {
return Collections.unmodifiableCollection(list()).iterator();
}
public Collection<MapleInventory> allInventories() {
return Collections.singletonList(this);
}
public Item findByCashId(int cashId) {
boolean isRing = false;
Equip equip = null;
for (Item item : list()) {
if (item.getInventoryType().equals(MapleInventoryType.EQUIP)) {
equip = (Equip) item;
isRing = equip.getRingId() > -1;
}
if ((item.getPetId() > -1 ? item.getPetId() : isRing ? equip.getRingId() : item.getCashId()) == cashId)
return item;
}
return null;
}
public boolean checked() {
lock.lock();
try {
return checked;
} finally {
lock.unlock();
}
}
public void checked(boolean yes) {
lock.lock();
try {
checked = yes;
} finally {
lock.unlock();
}
}
public void lockInventory() {
lock.lock();
}
public void unlockInventory() {
lock.unlock();
}
public void dispose() {
owner = null;
}
}

View File

@@ -0,0 +1,99 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import client.MapleCharacter;
/**
*
* @author Ronan
*/
public class MapleInventoryProof extends MapleInventory {
public MapleInventoryProof(MapleCharacter mc) {
super(mc, MapleInventoryType.CANHOLD, (byte) 0);
}
public void cloneContents(MapleInventory inv) {
inv.lockInventory();
lock.lock();
try {
inventory.clear();
this.setSlotLimit(inv.getSlotLimit());
for(Item it : inv.list()) {
Item item = new Item(it.getItemId(), it.getPosition(), it.getQuantity());
inventory.put(item.getPosition(), item);
}
} finally {
lock.unlock();
inv.unlockInventory();
}
}
public void flushContents() {
lock.lock();
try {
inventory.clear();
} finally {
lock.unlock();
}
}
@Override
protected short addSlot(Item item) {
if(item == null) {
return -1;
}
lock.lock();
try {
short slotId = getNextFreeSlot();
if (slotId < 0) {
return -1;
}
inventory.put(slotId, item);
return slotId;
} finally {
lock.unlock();
}
}
@Override
protected void addSlotFromDB(short slot, Item item) {
lock.lock();
try {
inventory.put(slot, item);
} finally {
lock.unlock();
}
}
@Override
public void removeSlot(short slot) {
lock.lock();
try {
inventory.remove(slot);
} finally {
lock.unlock();
}
}
}

View File

@@ -0,0 +1,73 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
/**
* @author Matze
*/
public enum MapleInventoryType {
UNDEFINED(0),
EQUIP(1),
USE(2),
SETUP(3),
ETC(4),
CASH(5),
CANHOLD(6), //Proof-guard for inserting after removal checks
EQUIPPED(-1); //Seems nexon screwed something when removing an item T_T
final byte type;
private MapleInventoryType(int type) {
this.type = (byte) type;
}
public byte getType() {
return type;
}
public short getBitfieldEncoding() {
return (short) (2 << type);
}
public static MapleInventoryType getByType(byte type) {
for (MapleInventoryType l : MapleInventoryType.values()) {
if (l.getType() == type) {
return l;
}
}
return null;
}
public static MapleInventoryType getByWZName(String name) {
if (name.equals("Install")) {
return SETUP;
} else if (name.equals("Consume")) {
return USE;
} else if (name.equals("Etc")) {
return ETC;
} else if (name.equals("Cash")) {
return CASH;
} else if (name.equals("Pet")) {
return CASH;
}
return UNDEFINED;
}
}

View File

@@ -0,0 +1,330 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import constants.game.ExpTable;
import java.awt.Point;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import tools.DatabaseConnection;
import server.MapleItemInformationProvider;
import server.movement.AbsoluteLifeMovement;
import server.movement.LifeMovement;
import server.movement.LifeMovementFragment;
import client.MapleCharacter;
import client.inventory.manipulator.MapleCashidGenerator;
import java.sql.Connection;
import tools.MaplePacketCreator;
import tools.Pair;
/**
*
* @author Matze
*/
public class MaplePet extends Item {
private String name;
private int uniqueid;
private int closeness = 0;
private byte level = 1;
private int fullness = 100;
private int Fh;
private Point pos;
private int stance;
private boolean summoned;
private int petFlag = 0;
public enum PetFlag {
OWNER_SPEED(0x01);
private int i;
private PetFlag(int i) {
this.i = i;
}
public int getValue() {
return i;
}
}
private MaplePet(int id, short position, int uniqueid) {
super(id, position, (short) 1);
this.uniqueid = uniqueid;
this.pos = new Point(0, 0);
}
public static MaplePet loadFromDb(int itemid, short position, int petid) {
try {
MaplePet ret = new MaplePet(itemid, position, petid);
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT name, level, closeness, fullness, summoned, flag FROM pets WHERE petid = ?"); // Get pet details..
ps.setInt(1, petid);
ResultSet rs = ps.executeQuery();
rs.next();
ret.setName(rs.getString("name"));
ret.setCloseness(Math.min(rs.getInt("closeness"), 30000));
ret.setLevel((byte) Math.min(rs.getByte("level"), 30));
ret.setFullness(Math.min(rs.getInt("fullness"), 100));
ret.setSummoned(rs.getInt("summoned") == 1);
ret.setPetFlag(rs.getInt("flag"));
rs.close();
ps.close();
con.close();
return ret;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static void deleteFromDb(MapleCharacter owner, int petid) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM pets WHERE `petid` = ?"); // thanks Vcoc for detecting petignores remaining after deletion
ps.setInt(1, petid);
ps.executeUpdate();
ps.close();
con.close();
owner.resetExcluded(petid);
MapleCashidGenerator.freeCashId(petid);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public void saveToDb() {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE pets SET name = ?, level = ?, closeness = ?, fullness = ?, summoned = ?, flag = ? WHERE petid = ?");
ps.setString(1, getName());
ps.setInt(2, getLevel());
ps.setInt(3, getCloseness());
ps.setInt(4, getFullness());
ps.setInt(5, isSummoned() ? 1 : 0);
ps.setInt(6, getPetFlag());
ps.setInt(7, getUniqueId());
ps.executeUpdate();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static int createPet(int itemid) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO pets (petid, name, level, closeness, fullness, summoned, flag) VALUES (?, ?, 1, 0, 100, 0, 0)");
int ret = MapleCashidGenerator.generateCashId();
ps.setInt(1, ret);
ps.setString(2, MapleItemInformationProvider.getInstance().getName(itemid));
ps.executeUpdate();
ps.close();
con.close();
return ret;
} catch (SQLException e) {
e.printStackTrace();
return -1;
}
}
public static int createPet(int itemid, byte level, int closeness, int fullness) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO pets (petid, name, level, closeness, fullness, summoned, flag) VALUES (?, ?, ?, ?, ?, 0, 0)");
int ret = MapleCashidGenerator.generateCashId();
ps.setInt(1, ret);
ps.setString(2, MapleItemInformationProvider.getInstance().getName(itemid));
ps.setByte(3, level);
ps.setInt(4, closeness);
ps.setInt(5, fullness);
ps.executeUpdate();
ps.close();
con.close();
return ret;
} catch (SQLException e) {
e.printStackTrace();
return -1;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getUniqueId() {
return uniqueid;
}
public void setUniqueId(int id) {
this.uniqueid = id;
}
public int getCloseness() {
return closeness;
}
public void setCloseness(int closeness) {
this.closeness = closeness;
}
public byte getLevel() {
return level;
}
public void gainClosenessFullness(MapleCharacter owner, int incCloseness, int incFullness, int type) {
byte slot = owner.getPetIndex(this);
boolean enjoyed;
//will NOT increase pet's closeness if tried to feed pet with 100% fullness
if (fullness < 100 || incFullness == 0) { //incFullness == 0: command given
int newFullness = fullness + incFullness;
if (newFullness > 100) newFullness = 100;
fullness = newFullness;
if (incCloseness > 0 && closeness < 30000) {
int newCloseness = closeness + incCloseness;
if (newCloseness > 30000) newCloseness = 30000;
closeness = newCloseness;
while(newCloseness >= ExpTable.getClosenessNeededForLevel(level)) {
level += 1;
owner.getClient().announce(MaplePacketCreator.showOwnPetLevelUp(slot));
owner.getMap().broadcastMessage(MaplePacketCreator.showPetLevelUp(owner, slot));
}
}
enjoyed = true;
} else {
int newCloseness = closeness - 1;
if (newCloseness < 0) newCloseness = 0;
closeness = newCloseness;
if (level > 1 && newCloseness < ExpTable.getClosenessNeededForLevel(level - 1)) {
level -= 1;
}
enjoyed = false;
}
owner.getMap().broadcastMessage(MaplePacketCreator.petFoodResponse(owner.getId(), slot, enjoyed, false));
saveToDb();
Item petz = owner.getInventory(MapleInventoryType.CASH).getItem(getPosition());
if (petz != null)
owner.forceUpdateItem(petz);
}
public void setLevel(byte level) {
this.level = level;
}
public int getFullness() {
return fullness;
}
public void setFullness(int fullness) {
this.fullness = fullness;
}
public int getFh() {
return Fh;
}
public void setFh(int Fh) {
this.Fh = Fh;
}
public Point getPos() {
return pos;
}
public void setPos(Point pos) {
this.pos = pos;
}
public int getStance() {
return stance;
}
public void setStance(int stance) {
this.stance = stance;
}
public boolean isSummoned() {
return summoned;
}
public void setSummoned(boolean yes) {
this.summoned = yes;
}
public int getPetFlag() {
return this.petFlag;
}
private void setPetFlag(int flag) {
this.petFlag = flag;
}
public void addPetFlag(MapleCharacter owner, PetFlag flag) {
this.petFlag |= flag.getValue();
saveToDb();
Item petz = owner.getInventory(MapleInventoryType.CASH).getItem(getPosition());
if (petz != null)
owner.forceUpdateItem(petz);
}
public void removePetFlag(MapleCharacter owner, PetFlag flag) {
this.petFlag &= 0xFFFFFFFF ^ flag.getValue();
saveToDb();
Item petz = owner.getInventory(MapleInventoryType.CASH).getItem(getPosition());
if (petz != null)
owner.forceUpdateItem(petz);
}
public Pair<Integer, Boolean> canConsume(int itemId) {
return MapleItemInformationProvider.getInstance().canPetConsume(this.getItemId(), itemId);
}
public void updatePosition(List<LifeMovementFragment> movement) {
for (LifeMovementFragment move : movement) {
if (move instanceof LifeMovement) {
if (move instanceof AbsoluteLifeMovement) {
this.setPos(((LifeMovement) move).getPosition());
}
this.setStance(((LifeMovement) move).getNewstate());
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
public enum MapleWeaponType {
NOT_A_WEAPON(0),
GENERAL1H_SWING(4.4),
GENERAL1H_STAB(3.2),
GENERAL2H_SWING(4.8),
GENERAL2H_STAB(3.4),
BOW(3.4),
CLAW(3.6),
CROSSBOW(3.6),
DAGGER_THIEVES(3.6),
DAGGER_OTHER(4),
GUN(3.6),
KNUCKLE(4.8),
POLE_ARM_SWING(5.0),
POLE_ARM_STAB(3.0),
SPEAR_STAB(5.0),
SPEAR_SWING(3.0),
STAFF(3.6),
SWORD1H(4.0),
SWORD2H(4.6),
WAND(3.6);
private double damageMultiplier;
private MapleWeaponType(double maxDamageMultiplier) {
this.damageMultiplier = maxDamageMultiplier;
}
public double getMaxDamageMultiplier() {
return damageMultiplier;
}
}

View File

@@ -0,0 +1,51 @@
package client.inventory;
/**
*
* @author kevin
*/
public class ModifyInventory {
private int mode;
private Item item;
private short oldPos;
public ModifyInventory(final int mode, final Item item) {
this.mode = mode;
this.item = item.copy();
}
public ModifyInventory(final int mode, final Item item, final short oldPos) {
this.mode = mode;
this.item = item.copy();
this.oldPos = oldPos;
}
public final int getMode() {
return mode;
}
public final int getInventoryType() {
return item.getInventoryType().type;
}
public final short getPosition() {
return item.getPosition();
}
public final short getOldPosition() {
return oldPos;
}
public final short getQuantity() {
return item.getQuantity();
}
public final Item getItem() {
return item;
}
public final void clear() {
this.item = null;
}
}

View File

@@ -0,0 +1,52 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
/*
* @author Leifde
*/
public class PetCommand {
private int petId, skillId, prob, inc;
public PetCommand(int petId, int skillId, int prob, int inc) {
this.petId = petId;
this.skillId = skillId;
this.prob = prob;
this.inc = inc;
}
public int getPetId() {
return petId;
}
public int getSkillId() {
return skillId;
}
public int getProbability() {
return prob;
}
public int getIncrease() {
return inc;
}
}

View File

@@ -0,0 +1,76 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import provider.MapleData;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
/**
*
* @author Danny (Leifde)
*/
public class PetDataFactory {
private static MapleDataProvider dataRoot = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/Item.wz"));
private static Map<String, PetCommand> petCommands = new HashMap<String, PetCommand>();
private static Map<Integer, Integer> petHunger = new HashMap<Integer, Integer>();
public static PetCommand getPetCommand(int petId, int skillId) {
PetCommand ret = petCommands.get(Integer.valueOf(petId) + "" + skillId);
if (ret != null) {
return ret;
}
synchronized (petCommands) {
ret = petCommands.get(petId + "" + skillId);
if (ret == null) {
MapleData skillData = dataRoot.getData("Pet/" + petId + ".img");
int prob = 0;
int inc = 0;
if (skillData != null) {
prob = MapleDataTool.getInt("interact/" + skillId + "/prob", skillData, 0);
inc = MapleDataTool.getInt("interact/" + skillId + "/inc", skillData, 0);
}
ret = new PetCommand(petId, skillId, prob, inc);
petCommands.put(petId + "" + skillId, ret);
}
return ret;
}
}
public static int getHunger(int petId) {
Integer ret = petHunger.get(Integer.valueOf(petId));
if (ret != null) {
return ret;
}
synchronized (petHunger) {
ret = petHunger.get(Integer.valueOf(petId));
if (ret == null) {
ret = Integer.valueOf(MapleDataTool.getInt(dataRoot.getData("Pet/" + petId + ".img").getChildByPath("info/hungry"), 1));
}
return ret;
}
}
}

View File

@@ -0,0 +1,107 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory.manipulator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import tools.DatabaseConnection;
/**
*
* @author RonanLana
*/
public class MapleCashidGenerator {
private final static Set<Integer> existentCashids = new HashSet<>(10000);
private static Integer runningCashid = 0;
private static void loadExistentCashIdsFromQuery(Connection con, String query) throws SQLException {
PreparedStatement ps = con.prepareStatement(query);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
if (!rs.wasNull()) {
existentCashids.add(id);
}
}
rs.close();
ps.close();
}
public static synchronized void loadExistentCashIdsFromDb() {
Connection con = null;
try {
con = DatabaseConnection.getConnection();
loadExistentCashIdsFromQuery(con, "SELECT id FROM rings");
loadExistentCashIdsFromQuery(con, "SELECT petid FROM pets");
con.close();
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (con != null && !con.isClosed()) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
runningCashid = 0;
do {
runningCashid++; // hopefully the id will never surpass the allotted amount for pets/rings?
} while (existentCashids.contains(runningCashid));
}
private static void getNextAvailableCashId() {
runningCashid++;
if (runningCashid >= 777000000) {
existentCashids.clear();
loadExistentCashIdsFromDb();
}
}
public static synchronized int generateCashId() {
while (true) {
if (!existentCashids.contains(runningCashid)) {
int ret = runningCashid;
getNextAvailableCashId();
// existentCashids.add(ret)... no need to do this since the wrap over already refetches already used cashids from the DB
return ret;
}
getNextAvailableCashId();
}
}
public static synchronized void freeCashId(int cashId) {
existentCashids.remove(cashId);
}
}

View File

@@ -0,0 +1,793 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory.manipulator;
import client.MapleBuffStat;
import client.MapleCharacter;
import client.MapleClient;
import client.inventory.Equip;
import client.inventory.Item;
import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import client.inventory.MaplePet;
import client.inventory.ModifyInventory;
import client.newyear.NewYearCardRecord;
import config.YamlConfig;
import constants.inventory.ItemConstants;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import server.MapleItemInformationProvider;
import server.maps.MapleMap;
import tools.FilePrinter;
import tools.MaplePacketCreator;
/**
*
* @author Matze
* @author Ronan - improved check space feature and removed redundant object calls
*/
public class MapleInventoryManipulator {
public static boolean addById(MapleClient c, int itemId, short quantity) {
return addById(c, itemId, quantity, null, -1, -1);
}
public static boolean addById(MapleClient c, int itemId, short quantity, long expiration) {
return addById(c, itemId, quantity, null, -1, (byte) 0, expiration);
}
public static boolean addById(MapleClient c, int itemId, short quantity, String owner, int petid) {
return addById(c, itemId, quantity, owner, petid, -1);
}
public static boolean addById(MapleClient c, int itemId, short quantity, String owner, int petid, long expiration) {
return addById(c, itemId, quantity, owner, petid, (byte) 0, expiration);
}
public static boolean addById(MapleClient c, int itemId, short quantity, String owner, int petid, short flag, long expiration) {
MapleCharacter chr = c.getPlayer();
MapleInventoryType type = ItemConstants.getInventoryType(itemId);
MapleInventory inv = chr.getInventory(type);
inv.lockInventory();
try {
return addByIdInternal(c, chr, type, inv, itemId, quantity, owner, petid, flag, expiration);
} finally {
inv.unlockInventory();
}
}
private static boolean addByIdInternal(MapleClient c, MapleCharacter chr, MapleInventoryType type, MapleInventory inv, int itemId, short quantity, String owner, int petid, short flag, long expiration) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if (!type.equals(MapleInventoryType.EQUIP)) {
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) {
if (i.hasNext()) {
Item eItem = (Item) i.next();
short oldQ = eItem.getQuantity();
if (oldQ < slotMax && ((eItem.getOwner().equals(owner) || owner == null) && eItem.getFlag() == flag)) {
short newQ = (short) Math.min(oldQ + quantity, slotMax);
quantity -= (newQ - oldQ);
eItem.setQuantity(newQ);
eItem.setExpiration(expiration);
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(1, eItem))));
}
} else {
break;
}
}
}
boolean sandboxItem = (flag & ItemConstants.SANDBOX) == ItemConstants.SANDBOX;
while (quantity > 0) {
short newQ = (short) Math.min(quantity, slotMax);
if (newQ != 0) {
quantity -= newQ;
Item nItem = new Item(itemId, (short) 0, newQ, petid);
nItem.setFlag(flag);
nItem.setExpiration(expiration);
short newSlot = inv.addItem(nItem);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return false;
}
if (owner != null) {
nItem.setOwner(owner);
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem))));
if(sandboxItem) chr.setHasSandboxItem();
} else {
c.announce(MaplePacketCreator.enableActions());
return false;
}
}
} else {
Item nItem = new Item(itemId, (short) 0, quantity, petid);
nItem.setFlag(flag);
nItem.setExpiration(expiration);
short newSlot = inv.addItem(nItem);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return false;
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem))));
if(MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem();
}
} else if (quantity == 1) {
Item nEquip = ii.getEquipById(itemId);
nEquip.setFlag(flag);
nEquip.setExpiration(expiration);
if (owner != null) {
nEquip.setOwner(owner);
}
short newSlot = inv.addItem(nEquip);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return false;
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nEquip))));
if(MapleInventoryManipulator.isSandboxItem(nEquip)) chr.setHasSandboxItem();
} else {
throw new RuntimeException("Trying to create equip with non-one quantity");
}
return true;
}
public static boolean addFromDrop(MapleClient c, Item item) {
return addFromDrop(c, item, true);
}
public static boolean addFromDrop(MapleClient c, Item item, boolean show) {
return addFromDrop(c, item, show, item.getPetId());
}
public static boolean addFromDrop(MapleClient c, Item item, boolean show, int petId) {
MapleCharacter chr = c.getPlayer();
MapleInventoryType type = item.getInventoryType();
MapleInventory inv = chr.getInventory(type);
inv.lockInventory();
try {
return addFromDropInternal(c, chr, type, inv, item, show, petId);
} finally {
inv.unlockInventory();
}
}
private static boolean addFromDropInternal(MapleClient c, MapleCharacter chr, MapleInventoryType type, MapleInventory inv, Item item, boolean show, int petId) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
int itemid = item.getItemId();
if (ii.isPickupRestricted(itemid) && chr.haveItemWithId(itemid, true)) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.showItemUnavailable());
return false;
}
short quantity = item.getQuantity();
if (!type.equals(MapleInventoryType.EQUIP)) {
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) {
if (i.hasNext()) {
Item eItem = (Item) i.next();
short oldQ = eItem.getQuantity();
if (oldQ < slotMax && item.getFlag() == eItem.getFlag() && item.getOwner().equals(eItem.getOwner())) {
short newQ = (short) Math.min(oldQ + quantity, slotMax);
quantity -= (newQ - oldQ);
eItem.setQuantity(newQ);
item.setPosition(eItem.getPosition());
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(1, eItem))));
}
} else {
break;
}
}
}
while (quantity > 0) {
short newQ = (short) Math.min(quantity, slotMax);
quantity -= newQ;
Item nItem = new Item(itemid, (short) 0, newQ, petId);
nItem.setExpiration(item.getExpiration());
nItem.setOwner(item.getOwner());
nItem.setFlag(item.getFlag());
short newSlot = inv.addItem(nItem);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
item.setQuantity((short) (quantity + newQ));
return false;
}
nItem.setPosition(newSlot);
item.setPosition(newSlot);
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem))));
if (MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem();
}
} else {
Item nItem = new Item(itemid, (short) 0, quantity, petId);
nItem.setExpiration(item.getExpiration());
nItem.setFlag(item.getFlag());
short newSlot = inv.addItem(nItem);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return false;
}
nItem.setPosition(newSlot);
item.setPosition(newSlot);
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem))));
if (MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem();
c.announce(MaplePacketCreator.enableActions());
}
} else if (quantity == 1) {
short newSlot = inv.addItem(item);
if (newSlot == -1) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return false;
}
item.setPosition(newSlot);
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 " + itemid + " containing more than 1 quantity --> " + quantity);
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.showItemUnavailable());
return false;
}
if (show) {
c.announce(MaplePacketCreator.getShowItemGain(itemid, item.getQuantity()));
}
return true;
}
private static boolean haveItemWithId(MapleInventory inv, int itemid) {
return inv.findById(itemid) != null;
}
public static boolean checkSpace(MapleClient c, int itemid, int quantity, String owner) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
MapleInventoryType type = ItemConstants.getInventoryType(itemid);
MapleCharacter chr = c.getPlayer();
MapleInventory inv = chr.getInventory(type);
if (ii.isPickupRestricted(itemid)) {
if (haveItemWithId(inv, itemid)) {
return false;
} else if (ItemConstants.isEquipment(itemid) && haveItemWithId(chr.getInventory(MapleInventoryType.EQUIPPED), itemid)) {
return false;
}
}
if (!type.equals(MapleInventoryType.EQUIP)) {
short slotMax = ii.getSlotMax(c, itemid);
List<Item> existing = inv.listById(itemid);
final int numSlotsNeeded;
if (ItemConstants.isRechargeable(itemid)) {
numSlotsNeeded = 1;
} else {
if (existing.size() > 0) // first update all existing slots to slotMax
{
for (Item eItem : existing) {
short oldQ = eItem.getQuantity();
if (oldQ < slotMax && owner.equals(eItem.getOwner())) {
short newQ = (short) Math.min(oldQ + quantity, slotMax);
quantity -= (newQ - oldQ);
}
if (quantity <= 0) {
break;
}
}
}
if (slotMax > 0) {
numSlotsNeeded = (int) (Math.ceil(((double) quantity) / slotMax));
} else {
numSlotsNeeded = 1;
}
}
return !inv.isFull(numSlotsNeeded - 1);
} else {
return !inv.isFull();
}
}
public static int checkSpaceProgressively(MapleClient c, int itemid, int quantity, String owner, int usedSlots, boolean useProofInv) {
// return value --> bit0: if has space for this one;
// value after: new slots filled;
// assumption: equipments always have slotMax == 1.
int returnValue;
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
MapleInventoryType type = !useProofInv ? ItemConstants.getInventoryType(itemid) : MapleInventoryType.CANHOLD;
MapleCharacter chr = c.getPlayer();
MapleInventory inv = chr.getInventory(type);
if (ii.isPickupRestricted(itemid)) {
if (haveItemWithId(inv, itemid)) {
return 0;
} else if (ItemConstants.isEquipment(itemid) && haveItemWithId(chr.getInventory(MapleInventoryType.EQUIPPED), itemid)) {
return 0; // thanks Captain & Aika & Vcoc for pointing out inventory checkup on player trades missing out one-of-a-kind items.
}
}
if (!type.equals(MapleInventoryType.EQUIP)) {
short slotMax = ii.getSlotMax(c, itemid);
final int numSlotsNeeded;
if (ItemConstants.isRechargeable(itemid)) {
numSlotsNeeded = 1;
} else {
List<Item> existing = inv.listById(itemid);
if (existing.size() > 0) // first update all existing slots to slotMax
{
for (Item eItem : existing) {
short oldQ = eItem.getQuantity();
if (oldQ < slotMax && owner.equals(eItem.getOwner())) {
short newQ = (short) Math.min(oldQ + quantity, slotMax);
quantity -= (newQ - oldQ);
}
if (quantity <= 0) {
break;
}
}
}
if (slotMax > 0) {
numSlotsNeeded = (int) (Math.ceil(((double) quantity) / slotMax));
} else {
numSlotsNeeded = 1;
}
}
returnValue = ((numSlotsNeeded + usedSlots) << 1);
returnValue += (numSlotsNeeded == 0 || !inv.isFullAfterSomeItems(numSlotsNeeded - 1, usedSlots)) ? 1 : 0;
//System.out.print(" needed " + numSlotsNeeded + " used " + usedSlots + " rval " + returnValue);
} else {
returnValue = ((quantity + usedSlots) << 1);
returnValue += (!inv.isFullAfterSomeItems(0, usedSlots)) ? 1 : 0;
//System.out.print(" eqpneeded " + 1 + " used " + usedSlots + " rval " + returnValue);
}
return returnValue;
}
public static void removeFromSlot(MapleClient c, MapleInventoryType type, short slot, short quantity, boolean fromDrop) {
removeFromSlot(c, type, slot, quantity, fromDrop, false);
}
public static void removeFromSlot(MapleClient c, MapleInventoryType type, short slot, short quantity, boolean fromDrop, boolean consume) {
MapleCharacter chr = c.getPlayer();
MapleInventory inv = chr.getInventory(type);
Item item = inv.getItem(slot);
boolean allowZero = consume && ItemConstants.isRechargeable(item.getItemId());
if(type == MapleInventoryType.EQUIPPED) {
inv.lockInventory();
try {
chr.unequippedItem((Equip) item);
inv.removeItem(slot, quantity, allowZero);
} finally {
inv.unlockInventory();
}
announceModifyInventory(c, item, fromDrop, allowZero);
} else {
int petid = item.getPetId();
if (petid > -1) { // thanks Vcoc for finding a d/c issue with equipped pets and pets remaining on DB here
int petIdx = chr.getPetIndex(petid);
if(petIdx > -1) {
MaplePet pet = chr.getPet(petIdx);
chr.unequipPet(pet, true);
}
inv.removeItem(slot, quantity, allowZero);
if(type != MapleInventoryType.CANHOLD) {
announceModifyInventory(c, item, fromDrop, allowZero);
}
// thanks Robin Schulz for noticing pet issues when moving pets out of inventory
} else {
inv.removeItem(slot, quantity, allowZero);
if(type != MapleInventoryType.CANHOLD) {
announceModifyInventory(c, item, fromDrop, allowZero);
}
}
}
}
private static void announceModifyInventory(MapleClient c, Item item, boolean fromDrop, boolean allowZero) {
if (item.getQuantity() == 0 && !allowZero) {
c.announce(MaplePacketCreator.modifyInventory(fromDrop, Collections.singletonList(new ModifyInventory(3, item))));
} else {
c.announce(MaplePacketCreator.modifyInventory(fromDrop, Collections.singletonList(new ModifyInventory(1, item))));
}
}
public static void removeById(MapleClient c, MapleInventoryType type, int itemId, int quantity, boolean fromDrop, boolean consume) {
int removeQuantity = quantity;
MapleInventory inv = c.getPlayer().getInventory(type);
int slotLimit = type == MapleInventoryType.EQUIPPED ? 128 : inv.getSlotLimit();
for (short i = 0; i <= slotLimit; i++) {
Item item = inv.getItem((short) (type == MapleInventoryType.EQUIPPED ? -i : i));
if (item != null) {
if (item.getItemId() == itemId || item.getCashId() == itemId) {
if (removeQuantity <= item.getQuantity()) {
removeFromSlot(c, type, item.getPosition(), (short) removeQuantity, fromDrop, consume);
removeQuantity = 0;
break;
} else {
removeQuantity -= item.getQuantity();
removeFromSlot(c, type, item.getPosition(), item.getQuantity(), fromDrop, consume);
}
}
}
}
if (removeQuantity > 0 && type != MapleInventoryType.CANHOLD) {
throw new RuntimeException("[Hack] Not enough items available of Item:" + itemId + ", Quantity (After Quantity/Over Current Quantity): " + (quantity - removeQuantity) + "/" + quantity);
}
}
private static boolean isSameOwner(Item source, Item target) {
return source.getOwner().equals(target.getOwner());
}
public static void move(MapleClient c, MapleInventoryType type, short src, short dst) {
MapleInventory inv = c.getPlayer().getInventory(type);
if (src < 0 || dst < 0) {
return;
}
if(dst > inv.getSlotLimit()) {
return;
}
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
Item source = inv.getItem(src);
Item initialTarget = inv.getItem(dst);
if (source == null) {
return;
}
short olddstQ = -1;
if (initialTarget != null) {
olddstQ = initialTarget.getQuantity();
}
short oldsrcQ = source.getQuantity();
short slotMax = ii.getSlotMax(c, source.getItemId());
inv.move(src, dst, slotMax);
final List<ModifyInventory> mods = new ArrayList<>();
if (!(type.equals(MapleInventoryType.EQUIP) || type.equals(MapleInventoryType.CASH)) && initialTarget != null && initialTarget.getItemId() == source.getItemId() && !ItemConstants.isRechargeable(source.getItemId()) && isSameOwner(source, initialTarget)) {
if ((olddstQ + oldsrcQ) > slotMax) {
mods.add(new ModifyInventory(1, source));
mods.add(new ModifyInventory(1, initialTarget));
} else {
mods.add(new ModifyInventory(3, source));
mods.add(new ModifyInventory(1, initialTarget));
}
} else {
mods.add(new ModifyInventory(2, source, src));
}
c.announce(MaplePacketCreator.modifyInventory(true, mods));
}
public static void equip(MapleClient c, short src, short dst) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
MapleCharacter chr = c.getPlayer();
MapleInventory eqpInv = chr.getInventory(MapleInventoryType.EQUIP);
MapleInventory eqpdInv = chr.getInventory(MapleInventoryType.EQUIPPED);
Equip source = (Equip) eqpInv.getItem(src);
if (source == null || !ii.canWearEquipment(chr, source, dst)) {
c.announce(MaplePacketCreator.enableActions());
return;
} else if ((((source.getItemId() >= 1902000 && source.getItemId() <= 1902002) || source.getItemId() == 1912000) && chr.isCygnus()) || ((source.getItemId() >= 1902005 && source.getItemId() <= 1902007) || source.getItemId() == 1912005) && !chr.isCygnus()) {// Adventurer taming equipment
return;
}
boolean itemChanged = false;
if (ii.isUntradeableOnEquip(source.getItemId())) {
short flag = source.getFlag(); // thanks BHB for noticing flags missing after equipping these
flag |= ItemConstants.UNTRADEABLE;
source.setFlag(flag);
itemChanged = true;
}
if (dst == -6) { // unequip the overall
Item top = eqpdInv.getItem((short) -5);
if (top != null && ItemConstants.isOverall(top.getItemId())) {
if (eqpInv.isFull()) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return;
}
unequip(c, (byte) -5, eqpInv.getNextFreeSlot());
}
} else if (dst == -5) {
final Item bottom = eqpdInv.getItem((short) -6);
if (bottom != null && ItemConstants.isOverall(source.getItemId())) {
if (eqpInv.isFull()) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return;
}
unequip(c, (byte) -6, eqpInv.getNextFreeSlot());
}
} else if (dst == -10) {// check if weapon is two-handed
Item weapon = eqpdInv.getItem((short) -11);
if (weapon != null && ii.isTwoHanded(weapon.getItemId())) {
if (eqpInv.isFull()) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return;
}
unequip(c, (byte) -11, eqpInv.getNextFreeSlot());
}
} else if (dst == -11) {
Item shield = eqpdInv.getItem((short) -10);
if (shield != null && ii.isTwoHanded(source.getItemId())) {
if (eqpInv.isFull()) {
c.announce(MaplePacketCreator.getInventoryFull());
c.announce(MaplePacketCreator.getShowInventoryFull());
return;
}
unequip(c, (byte) -10, eqpInv.getNextFreeSlot());
}
}
if (dst == -18) {
if (chr.getMount() != null) {
chr.getMount().setItemId(source.getItemId());
}
}
//1112413, 1112414, 1112405 (Lilin's Ring)
source = (Equip) eqpInv.getItem(src);
eqpInv.removeSlot(src);
Equip target;
eqpdInv.lockInventory();
try {
target = (Equip) eqpdInv.getItem(dst);
if (target != null) {
chr.unequippedItem(target);
eqpdInv.removeSlot(dst);
}
} finally {
eqpdInv.unlockInventory();
}
final List<ModifyInventory> mods = new ArrayList<>();
if (itemChanged) {
mods.add(new ModifyInventory(3, source));
mods.add(new ModifyInventory(0, source.copy()));//to prevent crashes
}
source.setPosition(dst);
eqpdInv.lockInventory();
try {
if (source.getRingId() > -1) {
chr.getRingById(source.getRingId()).equip();
}
chr.equippedItem(source);
eqpdInv.addItemFromDB(source);
} finally {
eqpdInv.unlockInventory();
}
if (target != null) {
target.setPosition(src);
eqpInv.addItemFromDB(target);
}
if (chr.getBuffedValue(MapleBuffStat.BOOSTER) != null && ItemConstants.isWeapon(source.getItemId())) {
chr.cancelBuffStats(MapleBuffStat.BOOSTER);
}
mods.add(new ModifyInventory(2, source, src));
c.announce(MaplePacketCreator.modifyInventory(true, mods));
chr.equipChanged();
}
public static void unequip(MapleClient c, short src, short dst) {
MapleCharacter chr = c.getPlayer();
MapleInventory eqpInv = chr.getInventory(MapleInventoryType.EQUIP);
MapleInventory eqpdInv = chr.getInventory(MapleInventoryType.EQUIPPED);
Equip source = (Equip) eqpdInv.getItem(src);
Equip target = (Equip) eqpInv.getItem(dst);
if (dst < 0) {
return;
}
if (source == null) {
return;
}
if (target != null && src <= 0) {
c.announce(MaplePacketCreator.getInventoryFull());
return;
}
eqpdInv.lockInventory();
try {
if (source.getRingId() > -1) {
chr.getRingById(source.getRingId()).unequip();
}
chr.unequippedItem(source);
eqpdInv.removeSlot(src);
} finally {
eqpdInv.unlockInventory();
}
if (target != null) {
eqpInv.removeSlot(dst);
}
source.setPosition(dst);
eqpInv.addItemFromDB(source);
if (target != null) {
target.setPosition(src);
eqpdInv.addItemFromDB(target);
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(2, source, src))));
chr.equipChanged();
}
private static boolean isDisappearingItemDrop(Item it) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
if (ii.isDropRestricted(it.getItemId())) {
return true;
} else if (ii.isCash(it.getItemId())) {
if (YamlConfig.config.server.USE_ENFORCE_UNMERCHABLE_CASH) { // thanks Ari for noticing cash drops not available server-side
return true;
} else if (ItemConstants.isPet(it.getItemId()) && YamlConfig.config.server.USE_ENFORCE_UNMERCHABLE_PET) {
return true;
}
} else if (isDroppedItemRestricted(it)) {
return true;
} else if (ItemConstants.isWeddingRing(it.getItemId())) {
return true;
}
return false;
}
public static void drop(MapleClient c, MapleInventoryType type, short src, short quantity) {
if (src < 0) {
type = MapleInventoryType.EQUIPPED;
}
MapleCharacter chr = c.getPlayer();
MapleInventory inv = chr.getInventory(type);
Item source = inv.getItem(src);
if (chr.getTrade() != null || chr.getMiniGame() != null || source == null) { //Only check needed would prob be merchants (to see if the player is in one)
return;
}
int itemId = source.getItemId();
MapleMap map = chr.getMap();
if ((!ItemConstants.isRechargeable(itemId) && source.getQuantity() < quantity) || quantity < 0) {
return;
}
int petid = source.getPetId();
if (petid > -1) {
int petIdx = chr.getPetIndex(petid);
if(petIdx > -1) {
MaplePet pet = chr.getPet(petIdx);
chr.unequipPet(pet, true);
}
}
Point dropPos = new Point(chr.getPosition());
if (quantity < source.getQuantity() && !ItemConstants.isRechargeable(itemId)) {
Item target = source.copy();
target.setQuantity(quantity);
source.setQuantity((short) (source.getQuantity() - quantity));
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(1, source))));
if (ItemConstants.isNewYearCardEtc(itemId)) {
if(itemId == 4300000) {
NewYearCardRecord.removeAllNewYearCard(true, chr);
c.getAbstractPlayerInteraction().removeAll(4300000);
} else {
NewYearCardRecord.removeAllNewYearCard(false, chr);
c.getAbstractPlayerInteraction().removeAll(4301000);
}
}
if (isDisappearingItemDrop(target)) {
map.disappearingItemDrop(chr, chr, target, dropPos);
} else {
map.spawnItemDrop(chr, chr, target, dropPos, true, true);
}
} else {
if (type == MapleInventoryType.EQUIPPED) {
inv.lockInventory();
try {
chr.unequippedItem((Equip) source);
inv.removeSlot(src);
} finally {
inv.unlockInventory();
}
} else {
inv.removeSlot(src);
}
c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(3, source))));
if (src < 0) {
chr.equipChanged();
} else if (ItemConstants.isNewYearCardEtc(itemId)) {
if (itemId == 4300000) {
NewYearCardRecord.removeAllNewYearCard(true, chr);
c.getAbstractPlayerInteraction().removeAll(4300000);
} else {
NewYearCardRecord.removeAllNewYearCard(false, chr);
c.getAbstractPlayerInteraction().removeAll(4301000);
}
}
if (isDisappearingItemDrop(source)) {
map.disappearingItemDrop(chr, chr, source, dropPos);
} else {
map.spawnItemDrop(chr, chr, source, dropPos, true, true);
}
}
int quantityNow = chr.getItemQuantity(itemId, false);
if (itemId == chr.getItemEffect()) {
if (quantityNow <= 0) {
chr.setItemEffect(0);
map.broadcastMessage(MaplePacketCreator.itemEffect(chr.getId(), 0));
}
} else if (itemId == 5370000 || itemId == 5370001) {
if (source.getQuantity() <= 0) {
chr.setChalkboard(null);
}
} else if (itemId == 4031868) {
chr.updateAriantScore(quantityNow);
}
}
private static boolean isDroppedItemRestricted(Item it) {
return YamlConfig.config.server.USE_ERASE_UNTRADEABLE_DROP && it.isUntradeable();
}
public static boolean isSandboxItem(Item it) {
return (it.getFlag() & ItemConstants.SANDBOX) == ItemConstants.SANDBOX;
}
}

View File

@@ -0,0 +1,59 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client.inventory.manipulator;
import constants.inventory.ItemConstants;
import client.inventory.Item;
/**
*
* @author RonanLana
*/
public class MapleKarmaManipulator {
private static short getKarmaFlag(Item item) {
return item.getItemType() == 1 ? ItemConstants.KARMA_EQP : ItemConstants.KARMA_USE;
}
public static boolean hasKarmaFlag(Item item) {
short karmaFlag = getKarmaFlag(item);
return (item.getFlag() & karmaFlag) == karmaFlag;
}
public static void toggleKarmaFlagToUntradeable(Item item) {
short karmaFlag = getKarmaFlag(item);
short flag = item.getFlag();
if ((flag & karmaFlag) == karmaFlag) {
flag ^= karmaFlag;
flag |= ItemConstants.UNTRADEABLE;
item.setFlag((byte) flag);
}
}
public static void setKarmaFlag(Item item) {
short karmaFlag = getKarmaFlag(item);
short flag = item.getFlag();
flag |= karmaFlag;
flag &= (0xFFFFFFFF ^ ItemConstants.UNTRADEABLE);
item.setFlag((byte) flag);
}
}