Stat pool & Skills on change field Patch + Elemental Charge broadcast

Patched account storages not getting cached properly at login time.
Reviewed item acquisition at the Cash Shop happening before point transaction.
EXP toggle flag now also works on equipment gains.
Factored several skills (Energy Charge, Wind Walk, Dash) not updating properly other players when changing maps.
Refactored stat pool system, which wasn't working properly on limit scenarios.
Fixed "untradeable at wear"equipments losing flags upon equipping.
Reviewed Inventory Sort, now sorting projectiles at descending order on damage.
Implemented support for visibility of effects on weapons imbued with Charge skill (e.g. Paladin's Holy Charge) for other players.
This commit is contained in:
ronancpl
2019-12-07 03:05:26 -03:00
parent 06b43d9e07
commit 8afbff9db9
23 changed files with 356 additions and 237 deletions

View File

@@ -2305,4 +2305,27 @@ Corrigido buff Final Attack de Cygnus sendo reaplicado a todo acerto de skill.
24 - 25 Novembro 2019,
Corrigido caso não sendo checado devidamente com Maker.
Corrigido contagem de projéteis nos stats de skill usando tipo de dados de tamanho insuficiente.
Refatorado acesso a membros relativos a Dojo em canais de forma a buscar melhorar efetividade dos ingressos e liberações de lobby.
Refatorado acesso a membros relativos a Dojo em canais de forma a buscar melhorar efetividade dos ingressos e liberações de lobby.
Revisado exceção inutilizável na classe geradora de áreas do jogo.
27 Novembro 2019,
Revisado carregamento de storage da DB ocorrendo a cada login realizado.
Revisado aquisição de itens no CS ocorrendo antes de utilizar os pontos disponíveis.
28 Novembro 2019,
Revisado interação de flag de permissão de ganho de EXP em equipamentos.
29 - 30 Novembro 2019,
Fatorado diversas habilidades (Energy Charge, Wind Walk, Dash) não transcorrendo como esperado na visão de outros jogadores ao trocar de mapas.
02 - 03 Dezembro 2019,
Revisado uso de locks compartilhados em MapleClient.
Refatorado criação de conjunto durante checagem de slots, que seria de fato efetivo em cenários muito raros (melhor deixar inserção de itens limitados dar fail-fast nas réplicas).
Refatorado sistema de pool de stats, que estava atuando erroneamente em casos-limite.
Corrigido Item Guard inconsistentemente levando a NPE ao utilizar o mesmo.
Corrigido itens perdendo flags ao equipar aqueles tidos como "untradeable após equipar".
Revisado Inventory Sort, agora ordenando projéteis por bônus de dano.
06 Dezembro 2019,
Implementado pacote para visão de buffs de efeito imbuído em armas para outros jogadores.
Corrigido casos de exceção devido a portais nulos na função que troca jogador de mapas interferindo com próximas trocas de mapa (jogador fica preso até relogar).

View File

@@ -1,6 +1,6 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
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
@@ -61,8 +61,9 @@ function writeFeatureTab_Skills() {
addFeature("Maker skill features developed - pckts thanks Arnah.");
addFeature("Chair Mastery - map chair boosts HP/MP rec.");
addFeature("Mu Lung Dojo skills functional.");
addFeature("Monster Magnet skill no longer crashes players.");
addFeature("Monster Magnet skill on bosses no longer crash.");
addFeature("HP/MP consumption from skills triggers pet autopot.");
addFeature("Elemental weapon imbue visibility for other players.");
}
function writeFeatureTab_Quests() {

View File

@@ -277,17 +277,22 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
this.clientmaxmp = Math.min(30000, mp_);
}
private static long calcStatPoolNode(long v, int displacement) {
if (v > Short.MAX_VALUE) {
v = Short.MAX_VALUE;
} else if (v < Short.MIN_VALUE) {
v = Short.MIN_VALUE;
}
return ((v & 0x0FFFF) << displacement);
private static long clampStat(int v, int min, int max) {
return (v < min) ? min : ((v > max) ? max : v);
}
private static long calcStatPoolLong(int v1, int v2, int v3, int v4) {
private static long calcStatPoolNode(Integer v, int displacement) {
long r;
if (v == null) {
r = -32768;
} else {
r = clampStat(v, -32767, 32767);
}
return ((r & 0x0FFFF) << displacement);
}
private static long calcStatPoolLong(Integer v1, Integer v2, Integer v3, Integer v4) {
long ret = 0;
ret |= calcStatPoolNode(v1, 48);
@@ -419,40 +424,48 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
}
protected void changeHpMp(int newhp, int newmp, boolean silent) {
changeHpMpPool(newhp, newmp, Short.MIN_VALUE, Short.MIN_VALUE, silent);
changeHpMpPool(newhp, newmp, null, null, silent);
}
private void changeHpMpPool(int hp, int mp, int maxhp, int maxmp, boolean silent) {
private void changeHpMpPool(Integer hp, Integer mp, Integer maxhp, Integer maxmp, boolean silent) {
long hpMpPool = calcStatPoolLong(hp, mp, maxhp, maxmp);
changeStatPool(hpMpPool, null, null, -1, silent);
}
public void updateHp(int hp) {
updateHpMaxHp(hp, Short.MIN_VALUE);
updateHpMaxHp(hp, null);
}
public void updateMaxHp(int maxhp) {
updateHpMaxHp(Short.MIN_VALUE, maxhp);
updateHpMaxHp(null, maxhp);
}
public void updateHpMaxHp(int hp, int maxhp) {
changeHpMpPool(hp, Short.MIN_VALUE, maxhp, Short.MIN_VALUE, false);
updateHpMaxHp(Integer.valueOf(hp), Integer.valueOf(maxhp));
}
private void updateHpMaxHp(Integer hp, Integer maxhp) {
changeHpMpPool(hp, null, maxhp, null, false);
}
public void updateMp(int mp) {
updateMpMaxMp(mp, Short.MIN_VALUE);
updateMpMaxMp(mp, null);
}
public void updateMaxMp(int maxmp) {
updateMpMaxMp(Short.MIN_VALUE, maxmp);
updateMpMaxMp(null, maxmp);
}
public void updateMpMaxMp(int mp, int maxmp) {
changeHpMpPool(Short.MIN_VALUE, mp, Short.MIN_VALUE, maxmp, false);
updateMpMaxMp(Integer.valueOf(mp), Integer.valueOf(maxmp));
}
private void updateMpMaxMp(Integer mp, Integer maxmp) {
changeHpMpPool(null, mp, null, maxmp, false);
}
public void updateMaxHpMaxMp(int maxhp, int maxmp) {
changeHpMpPool(Short.MIN_VALUE, Short.MIN_VALUE, maxhp, maxmp, false);
changeHpMpPool(null, null, maxhp, maxmp, false);
}
protected void enforceMaxHpMp() {
@@ -521,7 +534,7 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
effLock.lock();
statWlock.lock();
try {
changeHpMpPool(Short.MIN_VALUE, Short.MIN_VALUE, maxhp + hpdelta, maxmp + mpdelta, silent);
changeHpMpPool(null, null, maxhp + hpdelta, maxmp + mpdelta, silent);
} finally {
statWlock.unlock();
effLock.unlock();
@@ -567,19 +580,19 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
}
public boolean assignStr(int x) {
return assignStrDexIntLuk(x, Short.MIN_VALUE, Short.MIN_VALUE, Short.MIN_VALUE);
return assignStrDexIntLuk(x, null, null, null);
}
public boolean assignDex(int x) {
return assignStrDexIntLuk(Short.MIN_VALUE, x, Short.MIN_VALUE, Short.MIN_VALUE);
return assignStrDexIntLuk(null, x, null, null);
}
public boolean assignInt(int x) {
return assignStrDexIntLuk(Short.MIN_VALUE, Short.MIN_VALUE, x, Short.MIN_VALUE);
return assignStrDexIntLuk(null, null, x, null);
}
public boolean assignLuk(int x) {
return assignStrDexIntLuk(Short.MIN_VALUE, Short.MIN_VALUE, Short.MIN_VALUE, x);
return assignStrDexIntLuk(null, null, null, x);
}
public boolean assignHP(int deltaHP, int deltaAp) {
@@ -590,7 +603,7 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
return false;
}
long hpMpPool = calcStatPoolLong(Short.MIN_VALUE, Short.MIN_VALUE, maxhp + deltaHP, maxmp);
long hpMpPool = calcStatPoolLong(null, null, maxhp + deltaHP, maxmp);
long strDexIntLuk = calcStatPoolLong(str, dex, int_, luk);
changeStatPool(hpMpPool, strDexIntLuk, null, remainingAp - deltaAp, false);
@@ -610,7 +623,7 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
return false;
}
long hpMpPool = calcStatPoolLong(Short.MIN_VALUE, Short.MIN_VALUE, maxhp, maxmp + deltaMP);
long hpMpPool = calcStatPoolLong(null, null, maxhp, maxmp + deltaMP);
long strDexIntLuk = calcStatPoolLong(str, dex, int_, luk);
changeStatPool(hpMpPool, strDexIntLuk, null, remainingAp - deltaAp, false);
@@ -622,11 +635,15 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
}
}
private static int apAssigned(int x) {
return x != Short.MIN_VALUE ? x : 0;
private static int apAssigned(Integer x) {
return x != null ? x : 0;
}
public boolean assignStrDexIntLuk(int deltaStr, int deltaDex, int deltaInt, int deltaLuk) {
return assignStrDexIntLuk(Integer.valueOf(deltaStr), Integer.valueOf(deltaDex), Integer.valueOf(deltaInt), Integer.valueOf(deltaLuk));
}
private boolean assignStrDexIntLuk(Integer deltaStr, Integer deltaDex, Integer deltaInt, Integer deltaLuk) {
effLock.lock();
statWlock.lock();
try {
@@ -636,19 +653,19 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
}
int newStr = str + deltaStr, newDex = dex + deltaDex, newInt = int_ + deltaInt, newLuk = luk + deltaLuk;
if (newStr < 4 && deltaStr != Short.MIN_VALUE || newStr > YamlConfig.config.server.MAX_AP) {
if (newStr < 4 && deltaStr != null || newStr > YamlConfig.config.server.MAX_AP) {
return false;
}
if (newDex < 4 && deltaDex != Short.MIN_VALUE || newDex > YamlConfig.config.server.MAX_AP) {
if (newDex < 4 && deltaDex != null || newDex > YamlConfig.config.server.MAX_AP) {
return false;
}
if (newInt < 4 && deltaInt != Short.MIN_VALUE || newInt > YamlConfig.config.server.MAX_AP) {
if (newInt < 4 && deltaInt != null || newInt > YamlConfig.config.server.MAX_AP) {
return false;
}
if (newLuk < 4 && deltaLuk != Short.MIN_VALUE || newLuk > YamlConfig.config.server.MAX_AP) {
if (newLuk < 4 && deltaLuk != null || newLuk > YamlConfig.config.server.MAX_AP) {
return false;
}
@@ -691,12 +708,12 @@ public abstract class AbstractMapleCharacterObject extends AbstractAnimatedMaple
changeStrDexIntLuk(str, dex, int_, luk, remainingAp, false);
}
private void changeStrDexIntLuk(int str, int dex, int int_, int luk, int remainingAp, boolean silent) {
private void changeStrDexIntLuk(Integer str, Integer dex, Integer int_, Integer luk, int remainingAp, boolean silent) {
long strDexIntLuk = calcStatPoolLong(str, dex, int_, luk);
changeStatPool(null, strDexIntLuk, null, remainingAp, silent);
}
private void changeStrDexIntLukSp(int str, int dex, int int_, int luk, int remainingAp, int remainingSp, int skillbook, boolean silent) {
private void changeStrDexIntLukSp(Integer str, Integer dex, Integer int_, Integer luk, int remainingAp, int remainingSp, int skillbook, boolean silent) {
long strDexIntLuk = calcStatPoolLong(str, dex, int_, luk);
long sp = calcStatPoolLong(0, 0, remainingSp, skillbook);
changeStatPool(null, strDexIntLuk, sp, remainingAp, silent);

View File

@@ -1472,11 +1472,14 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
changeMap(to, to.getPortal(portal));
}
public void changeMap(final MapleMap target, final MaplePortal pto) {
public void changeMap(final MapleMap target, MaplePortal pto) {
canWarpCounter++;
eventChangedMap(target.getId()); // player can be dropped from an event here, hence the new warping target.
MapleMap to = getWarpMap(target.getId());
if (pto == null) {
pto = to.getPortal(0);
}
changeMapInternal(to, pto.getPosition(), MaplePacketCreator.getWarpToMap(to, pto.getId(), this));
canWarpMap = false;
@@ -1504,7 +1507,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
eventAfterChangedMap(this.getMapId());
}
public void forceChangeMap(final MapleMap target, final MaplePortal pto) {
public void forceChangeMap(final MapleMap target, MaplePortal pto) {
// will actually enter the map given as parameter, regardless of being an eventmap or whatnot
canWarpCounter++;
@@ -1525,6 +1528,9 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
MapleMap to = target; // warps directly to the target intead of the target's map id, this allows GMs to patrol players inside instances.
if (pto == null) {
pto = to.getPortal(0);
}
changeMapInternal(to, pto.getPosition(), MaplePacketCreator.getWarpToMap(to, pto.getId(), this));
canWarpMap = false;
@@ -6102,7 +6108,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
List<Pair<MapleBuffStat, Integer>> stat = Collections.singletonList(new Pair<>(MapleBuffStat.ENERGY_CHARGE, energybar));
setBuffedValue(MapleBuffStat.ENERGY_CHARGE, energybar);
client.announce(MaplePacketCreator.giveBuff(energybar, 0, stat));
getMap().broadcastMessage(chr, MaplePacketCreator.giveForeignBuff(energybar, stat));
getMap().broadcastMessage(chr, MaplePacketCreator.cancelForeignFirstDebuff(id, ((long) 1) << 50));
}
}, ceffect.getDuration());
}
@@ -9403,25 +9409,41 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
public byte getSlots(int type) {
return type == MapleInventoryType.CASH.getType() ? 96 : inventory[type].getSlotLimit();
}
public boolean canGainSlots(int type, int slots) {
slots += inventory[type].getSlotLimit();
return slots <= 96;
}
public boolean gainSlots(int type, int slots) {
return gainSlots(type, slots, true);
}
public boolean gainSlots(int type, int slots, boolean update) {
slots += inventory[type].getSlotLimit();
if (slots <= 96) {
inventory[type].setSlotLimit(slots);
boolean ret = gainSlotsInternal(type, slots, update);
if (ret) {
this.saveCharToDB();
if (update) {
client.announce(MaplePacketCreator.updateInventorySlotLimit(type, slots));
}
return true;
}
return false;
return ret;
}
private boolean gainSlotsInternal(int type, int slots, boolean update) {
inventory[type].lockInventory();
try {
if (canGainSlots(type, slots)) {
slots += inventory[type].getSlotLimit();
inventory[type].setSlotLimit(slots);
return true;
} else {
return false;
}
} finally {
inventory[type].unlockInventory();
}
}
public int sellAllItemsFromName(byte invTypeId, String name) {
@@ -10487,18 +10509,20 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
public void increaseEquipExp(int expGain) {
if(expGain < 0) {
expGain = Integer.MAX_VALUE;
}
for (Item item : getUpgradeableEquipList()) {
Equip nEquip = (Equip) item;
String itemName = ii.getName(nEquip.getItemId());
if (itemName == null) {
continue;
if (allowExpGain) { // thanks Vcoc for suggesting equip EXP gain conditionally
if(expGain < 0) {
expGain = Integer.MAX_VALUE;
}
for (Item item : getUpgradeableEquipList()) {
Equip nEquip = (Equip) item;
String itemName = ii.getName(nEquip.getItemId());
if (itemName == null) {
continue;
}
nEquip.gainItemExp(client, expGain);
}
nEquip.gainItemExp(client, expGain);
}
}

View File

@@ -118,8 +118,7 @@ public class MapleClient {
private final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT, true);
private final Lock encoderLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ENCODER, true);
private final Lock announcerLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ANNOUNCER, true);
private static final int lockCount = 200;
private static final Lock loginLocks[] = new Lock[lockCount]; // thanks Masterrulax & try2hack for pointing out a bottleneck issue here
// thanks Masterrulax & try2hack for pointing out a bottleneck issue with shared locks, shavit for noticing an opportunity for improvement
private Calendar tempBanCalendar;
private int votePoints;
private int voteTime = -1;
@@ -129,12 +128,6 @@ public class MapleClient {
private long lastPacket = System.currentTimeMillis();
private int lang = 0;
static {
for (int i = 0; i < lockCount; i++) {
loginLocks[i] = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_LOGIN, true);
}
}
public void updateLastPacket() {
lastPacket = System.currentTimeMillis();
}
@@ -453,8 +446,7 @@ public class MapleClient {
}
public int finishLogin() {
Lock loginLock = loginLocks[this.getAccID() % lockCount];
loginLock.lock();
encoderLock.lock();
try {
if (getLoginState() > LOGIN_NOTLOGGEDIN) { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN
loggedIn = false;
@@ -462,7 +454,7 @@ public class MapleClient {
}
updateLoginState(MapleClient.LOGIN_LOGGEDIN);
} finally {
loginLock.unlock();
encoderLock.unlock();
}
return 0;
@@ -1379,8 +1371,12 @@ public class MapleClient {
characterSlots = slots;
}
public boolean canGainCharacterSlot() {
return characterSlots < 15;
}
public synchronized boolean gainCharacterSlot() {
if (characterSlots < 15) {
if (canGainCharacterSlot()) {
Connection con = null;
try {
con = DatabaseConnection.getConnection();

View File

@@ -41,5 +41,5 @@ public class DisposeCommand extends Command {
c.announce(MaplePacketCreator.enableActions());
c.removeClickedNPC();
c.getPlayer().message("You've been disposed.");
}
}
}

View File

@@ -453,10 +453,10 @@ public class MapleInventory implements Iterable<Item> {
private static boolean checkItemRestricted(List<Pair<Item, MapleInventoryType>> items) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
Set<Integer> itemids = new HashSet<>();
// 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 || !itemids.add(itemid))) {
if (ii.isPickupRestricted(itemid) && p.getLeft().getQuantity() > 1) {
return false;
}
}

View File

@@ -523,7 +523,10 @@ public class MapleInventoryManipulator {
}
boolean itemChanged = false;
if (ii.isUntradeableOnEquip(source.getItemId())) {
source.setFlag((byte) ItemConstants.UNTRADEABLE);
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

View File

@@ -21,7 +21,6 @@
*/
package constants.inventory;
import constants.net.ServerConstants;
import client.inventory.MapleInventoryType;
import config.YamlConfig;

View File

@@ -894,33 +894,8 @@ public class Server {
//MaplePet.clearMissingPetsFromDb(); // thanks Optimist for noticing this taking too long to run
MapleCashidGenerator.loadExistentCashIdsFromDb();
IoBuffer.setUseDirectBuffer(false);
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory()));
ThreadManager.getInstance().start();
TimerManager tMan = TimerManager.getInstance();
tMan.start();
tMan.register(tMan.purge(), YamlConfig.config.server.PURGING_INTERVAL);//Purging ftw...
disconnectIdlesOnLoginTask();
long timeLeft = getTimeLeftForNextHour();
tMan.register(new CharacterDiseaseTask(), YamlConfig.config.server.UPDATE_INTERVAL, YamlConfig.config.server.UPDATE_INTERVAL);
tMan.register(new ReleaseLockTask(), 2 * 60 * 1000, 2 * 60 * 1000);
tMan.register(new CouponTask(), YamlConfig.config.server.COUPON_INTERVAL, timeLeft);
tMan.register(new RankingCommandTask(), 5 * 60 * 1000, 5 * 60 * 1000);
tMan.register(new RankingLoginTask(), YamlConfig.config.server.RANKING_INTERVAL, timeLeft);
tMan.register(new LoginCoordinatorTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new EventRecallCoordinatorTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new LoginStorageTask(), 2 * 60 * 1000, 2 * 60 * 1000);
tMan.register(new DueyFredrickTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new InvitationTask(), 30 * 1000, 30 * 1000);
tMan.register(new RespawnTask(), YamlConfig.config.server.RESPAWN_INTERVAL, YamlConfig.config.server.RESPAWN_INTERVAL);
timeLeft = getTimeLeftForNextDay();
MapleExpeditionBossLog.resetBossLogTable();
tMan.register(new BossLogTask(), 24 * 60 * 60 * 1000, timeLeft);
initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan
long timeToTake = System.currentTimeMillis();
SkillFactory.loadAllSkills();
@@ -965,6 +940,10 @@ public class Server {
System.out.println();
IoBuffer.setUseDirectBuffer(false); // join IO operations performed by lxconan
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory()));
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
acceptor.setHandler(new MapleServerHandler());
try {
@@ -986,6 +965,30 @@ public class Server {
ch.reloadEventScriptManager();
}
}
private void initializeTimelyTasks() {
TimerManager tMan = TimerManager.getInstance();
tMan.start();
tMan.register(tMan.purge(), YamlConfig.config.server.PURGING_INTERVAL);//Purging ftw...
disconnectIdlesOnLoginTask();
long timeLeft = getTimeLeftForNextHour();
tMan.register(new CharacterDiseaseTask(), YamlConfig.config.server.UPDATE_INTERVAL, YamlConfig.config.server.UPDATE_INTERVAL);
tMan.register(new ReleaseLockTask(), 2 * 60 * 1000, 2 * 60 * 1000);
tMan.register(new CouponTask(), YamlConfig.config.server.COUPON_INTERVAL, timeLeft);
tMan.register(new RankingCommandTask(), 5 * 60 * 1000, 5 * 60 * 1000);
tMan.register(new RankingLoginTask(), YamlConfig.config.server.RANKING_INTERVAL, timeLeft);
tMan.register(new LoginCoordinatorTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new EventRecallCoordinatorTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new LoginStorageTask(), 2 * 60 * 1000, 2 * 60 * 1000);
tMan.register(new DueyFredrickTask(), 60 * 60 * 1000, timeLeft);
tMan.register(new InvitationTask(), 30 * 1000, 30 * 1000);
tMan.register(new RespawnTask(), YamlConfig.config.server.RESPAWN_INTERVAL, YamlConfig.config.server.RESPAWN_INTERVAL);
timeLeft = getTimeLeftForNextDay();
MapleExpeditionBossLog.resetBossLogTable();
tMan.register(new BossLogTask(), 24 * 60 * 60 * 1000, timeLeft);
}
public static void main(String args[]) {
System.setProperty("wzpath", "wz");
@@ -1755,7 +1758,7 @@ public class Server {
for (Integer worldid : accWorlds) {
if (worldid < worldList.size()) {
World wserv = worldList.get(worldid);
wserv.registerAccountStorage(accountId);
wserv.loadAccountStorage(accountId);
}
}
}

View File

@@ -0,0 +1,24 @@
package net.server.audit.locks.empty;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public abstract class AbstractEmptyLock {
protected static String printThreadStack(StackTraceElement[] list) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); // DRY-code opportunity performed by jtumidanski
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
String s = "\r\n" + df + "\r\n";
for(int i = 0; i < list.length; i++) {
s += (" " + list[i].toString() + "\r\n");
}
s += "----------------------------\r\n\r\n";
return s;
}
}

View File

@@ -19,11 +19,6 @@
*/
package net.server.audit.locks.empty;
import constants.net.ServerConstants;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock;
import tools.FilePrinter;
@@ -32,27 +27,13 @@ import tools.FilePrinter;
*
* @author RonanLana
*/
public class EmptyReadLock implements MonitoredReadLock {
public class EmptyReadLock extends AbstractEmptyLock implements MonitoredReadLock {
private final MonitoredLockType id;
public EmptyReadLock(MonitoredLockType type) {
this.id = type;
}
private static String printThreadStack(StackTraceElement[] list) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
String s = "\r\n" + df + "\r\n";
for(int i = 0; i < list.length; i++) {
s += (" " + list[i].toString() + "\r\n");
}
s += "----------------------------\r\n\r\n";
return s;
}
@Override
public void lock() {
FilePrinter.printError(FilePrinter.DISPOSED_LOCKS, "Captured locking tentative on disposed lock " + id + ":" + printThreadStack(Thread.currentThread().getStackTrace()));

View File

@@ -19,11 +19,6 @@
*/
package net.server.audit.locks.empty;
import constants.net.ServerConstants;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import tools.FilePrinter;
@@ -32,27 +27,13 @@ import tools.FilePrinter;
*
* @author RonanLana
*/
public class EmptyReentrantLock implements MonitoredReentrantLock {
public class EmptyReentrantLock extends AbstractEmptyLock implements MonitoredReentrantLock {
private final MonitoredLockType id;
public EmptyReentrantLock(MonitoredLockType type) {
this.id = type;
}
private static String printThreadStack(StackTraceElement[] list) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
String s = "\r\n" + df + "\r\n";
for(int i = 0; i < list.length; i++) {
s += (" " + list[i].toString() + "\r\n");
}
s += "----------------------------\r\n\r\n";
return s;
}
@Override
public void lock() {
FilePrinter.printError(FilePrinter.DISPOSED_LOCKS, "Captured locking tentative on disposed lock " + id + ":" + printThreadStack(Thread.currentThread().getStackTrace()));

View File

@@ -19,11 +19,6 @@
*/
package net.server.audit.locks.empty;
import constants.net.ServerConstants;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredWriteLock;
import tools.FilePrinter;
@@ -32,27 +27,13 @@ import tools.FilePrinter;
*
* @author RonanLana
*/
public class EmptyWriteLock implements MonitoredWriteLock {
public class EmptyWriteLock extends AbstractEmptyLock implements MonitoredWriteLock {
private final MonitoredLockType id;
public EmptyWriteLock(MonitoredLockType type) {
this.id = type;
}
private static String printThreadStack(StackTraceElement[] list) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
String s = "\r\n" + df + "\r\n";
for(int i = 0; i < list.length; i++) {
s += (" " + list[i].toString() + "\r\n");
}
s += "----------------------------\r\n\r\n";
return s;
}
@Override
public void lock() {
FilePrinter.printError(FilePrinter.DISPOSED_LOCKS, "Captured locking tentative on disposed lock " + id + ":" + printThreadStack(Thread.currentThread().getStackTrace()));

View File

@@ -86,16 +86,18 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
Item item = cItem.toItem();
cs.gainCash(useNX, cItem, chr.getWorld()); // thanks Rohenn for noticing cash operations after item acquisition
cs.addToInventory(item);
c.announce(MaplePacketCreator.showBoughtCashItem(item, c.getAccID()));
} else { // Package
cs.gainCash(useNX, cItem, chr.getWorld());
List<Item> cashPackage = CashItemFactory.getPackage(cItem.getItemId());
for (Item item : cashPackage) {
cs.addToInventory(item);
}
c.announce(MaplePacketCreator.showBoughtCashPackage(cashPackage, c.getAccID()));
}
cs.gainCash(useNX, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else if (action == 0x04) {//TODO check for gender
int birthday = slea.readInt();
@@ -116,9 +118,9 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA8));
return;
}
cs.gainCash(4, cItem, chr.getWorld());
cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN());
c.announce(MaplePacketCreator.showGiftSucceed(recipient.get("name"), cItem));
cs.gainCash(4, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
try {
chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not
@@ -147,10 +149,17 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.enableCSActions();
return;
}
if (chr.gainSlots(type, 4, false)) {
int qty = 4;
if (!chr.canGainSlots(type, qty)) {
c.enableCSActions();
return;
}
cs.gainCash(cash, -4000);
if (chr.gainSlots(type, qty, false)) {
c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type)));
cs.gainCash(cash, -4000);
c.announce(MaplePacketCreator.showCash(chr));
} else {
FilePrinter.printError(FilePrinter.CASHITEM_BOUGHT, "Could not add " + qty + " slots of type " + type + " for player " + MapleCharacter.makeMapleReadable(chr.getName()));
}
} else {
CashItem cItem = CashItemFactory.getItem(slea.readInt());
@@ -159,10 +168,17 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.enableCSActions();
return;
}
if (chr.gainSlots(type, 8, false)) {
int qty = 8;
if (!chr.canGainSlots(type, qty)) {
c.enableCSActions();
return;
}
cs.gainCash(cash, cItem, chr.getWorld());
if (chr.gainSlots(type, qty, false)) {
c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type)));
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else {
FilePrinter.printError(FilePrinter.CASHITEM_BOUGHT, "Could not add " + qty + " slots of type " + type + " for player " + MapleCharacter.makeMapleReadable(chr.getName()));
}
}
} else if (action == 0x07) { // Increase Storage Slots
@@ -174,13 +190,20 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.enableCSActions();
return;
}
if (chr.getStorage().gainSlots(4)) {
FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 4 slots to their account storage.");
int qty = 4;
if (!chr.getStorage().canGainSlots(qty)) {
c.enableCSActions();
return;
}
cs.gainCash(cash, -4000);
if (chr.getStorage().gainSlots(qty)) {
FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought " + qty + " slots to their account storage.");
chr.setUsedStorage();
c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots()));
cs.gainCash(cash, -4000);
c.announce(MaplePacketCreator.showCash(chr));
} else {
FilePrinter.printError(FilePrinter.CASHITEM_BOUGHT, "Could not add " + qty + " slots to " + MapleCharacter.makeMapleReadable(chr.getName()) + "'s account.");
}
} else {
CashItem cItem = CashItemFactory.getItem(slea.readInt());
@@ -189,13 +212,20 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.enableCSActions();
return;
}
if (chr.getStorage().gainSlots(8)) { // thanks ABaldParrot & Thora for detecting storage issues here
FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 8 slots to their account storage.");
int qty = 8;
if (!chr.getStorage().canGainSlots(qty)) {
c.enableCSActions();
return;
}
cs.gainCash(cash, cItem, chr.getWorld());
if (chr.getStorage().gainSlots(qty)) { // thanks ABaldParrot & Thora for detecting storage issues here
FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought " + qty + " slots to their account storage.");
chr.setUsedStorage();
c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots()));
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else {
FilePrinter.printError(FilePrinter.CASHITEM_BOUGHT, "Could not add " + qty + " slots to " + MapleCharacter.makeMapleReadable(chr.getName()) + "'s account.");
}
}
} else if (action == 0x08) { // Increase Character Slots
@@ -207,13 +237,17 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.enableCSActions();
return;
}
if (!c.canGainCharacterSlot()) {
chr.dropMessage(1, "You have already used up all 12 extra character slots.");
c.enableCSActions();
return;
}
cs.gainCash(cash, cItem, chr.getWorld());
if (c.gainCharacterSlot()) {
c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots()));
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else {
chr.dropMessage(1, "You have already used up all 12 extra character slots.");
FilePrinter.printError(FilePrinter.CASHITEM_BOUGHT, "Could not add a character slot to " + MapleCharacter.makeMapleReadable(chr.getName()) + "'s account.");
c.enableCSActions();
return;
}
@@ -287,8 +321,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
eqp.setRingId(rings.getLeft());
cs.addToInventory(eqp);
c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID()));
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
cs.gainCash(toCharge, itemRing, chr.getWorld());
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
chr.addCrushRing(MapleRing.loadFromDb(rings.getLeft()));
try {
chr.sendNote(partner.getName(), text, (byte) 1);
@@ -353,8 +387,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
eqp.setRingId(rings.getLeft());
cs.addToInventory(eqp);
c.announce(MaplePacketCreator.showBoughtCashRing(eqp, partner.getName(), c.getAccID()));
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
cs.gainCash(payment, -itemRing.getPrice());
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
chr.addFriendshipRing(MapleRing.loadFromDb(rings.getLeft()));
try {
chr.sendNote(partner.getName(), text, (byte) 1);
@@ -391,8 +425,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
if(chr.registerNameChange(newName)) { //success
Item item = cItem.toItem();
c.announce(MaplePacketCreator.showNameChangeSuccess(item, c.getAccID()));
cs.addToInventory(item);
cs.gainCash(4, cItem, chr.getWorld());
cs.addToInventory(item);
} else {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
}
@@ -421,8 +455,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
} else if(chr.registerWorldTransfer(newWorldSelection)) {
Item item = cItem.toItem();
c.announce(MaplePacketCreator.showWorldTransferSuccess(item, c.getAccID()));
cs.addToInventory(item);
cs.gainCash(4, cItem, chr.getWorld());
cs.addToInventory(item);
} else {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
}

View File

@@ -35,7 +35,6 @@ import client.inventory.Equip;
import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import client.inventory.ModifyInventory;
import constants.net.ServerConstants;
import server.MapleItemInformationProvider;
import net.server.Server;
@@ -73,7 +72,11 @@ class PairedQuicksort {
} while (i <= j);
}
private void PartitionByItemIdReverse(int Esq, int Dir, ArrayList<Item> A) {
private int getWatkForProjectile(Item item) {
return ii.getWatkForProjectile(item.getItemId());
}
private void PartitionByProjectileAtk(int Esq, int Dir, ArrayList<Item> A) {
Item x, w;
i = Esq;
@@ -81,8 +84,9 @@ class PairedQuicksort {
x = A.get((i + j) / 2);
do {
while (x.getItemId() < A.get(i).getItemId()) i++;
while (x.getItemId() > A.get(j).getItemId()) j--;
int watk = getWatkForProjectile(x);
while (watk < getWatkForProjectile(A.get(i))) i++;
while (watk > getWatkForProjectile(A.get(j))) j--;
if (i <= j) {
w = A.get(i);
@@ -228,7 +232,7 @@ class PairedQuicksort {
public void reverseSortSublist(ArrayList<Item> A, int[] range) {
if (range != null) {
PartitionByItemIdReverse(range[0], range[1], A);
PartitionByProjectileAtk(range[0], range[1], A);
}
}

View File

@@ -244,7 +244,7 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
eq.setExpiration(currentServerTime() + (period * 60 * 60 * 24 * 1000));
}
remove(c, position, itemId);
// double-remove found thanks to BHB
} else if (itemId == 5060002) { // Incubator
byte inventory2 = (byte) slea.readInt();
short slot2 = (short) slea.readInt();

View File

@@ -467,7 +467,13 @@ public class World {
}
}
public void registerAccountStorage(Integer accountId) {
public void loadAccountStorage(Integer accountId) {
if (getAccountStorage(accountId) == null) {
registerAccountStorage(accountId);
}
}
private void registerAccountStorage(Integer accountId) {
MapleStorage storage = MapleStorage.loadOrCreateFromDB(accountId, this.id);
accountCharsLock.lock();
try {
@@ -572,7 +578,7 @@ public class World {
if(cserv != null) {
if(!cserv.removePlayer(chr)) {
// oy the player is not where it should be, find this mf
// oy the player is not where they should be, find this mf
for(Channel ch : getChannels()) {
if(ch.removePlayer(chr)) {

View File

@@ -27,7 +27,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import config.YamlConfig;
@@ -640,7 +639,7 @@ public class MapleStatEffect {
break;
case WindArcher.WIND_WALK:
statups.add(new Pair<>(MapleBuffStat.WIND_WALK, Integer.valueOf(x)));
break;
//break; thanks Vcoc for noticing WW not showing for other players when changing maps
case Rogue.DARK_SIGHT:
case NightWalker.DARK_SIGHT:
statups.add(new Pair<>(MapleBuffStat.DARKSIGHT, Integer.valueOf(x)));
@@ -1343,6 +1342,8 @@ public class MapleStatEffect {
if (isDash()) {
buff = MaplePacketCreator.givePirateBuff(statups, sourceid, seconds);
mbuff = MaplePacketCreator.giveForeignPirateBuff(applyto.getId(), sourceid, seconds, localstatups);
} else if (isWkCharge()) {
mbuff = MaplePacketCreator.giveForeignWKChargeEffect(applyto.getId(), sourceid, localstatups);
} else if (isInfusion()) {
buff = MaplePacketCreator.givePirateBuff(localstatups, sourceid, seconds);
mbuff = MaplePacketCreator.giveForeignPirateBuff(applyto.getId(), sourceid, seconds, localstatups);
@@ -1746,6 +1747,20 @@ public class MapleStatEffect {
return false;
}
}
private boolean isWkCharge() {
if (!skill) {
return false;
}
for (Pair<MapleBuffStat, Integer> p : statups) {
if (p.getLeft().equals(MapleBuffStat.WK_CHARGE)) {
return true;
}
}
return false;
}
private boolean isDash() {
return skill && (sourceid == Pirate.DASH || sourceid == ThunderBreaker.DASH || sourceid == Beginner.SPACE_DASH || sourceid == Noblesse.SPACE_DASH);

View File

@@ -114,12 +114,16 @@ public class MapleStorage {
return slots;
}
public boolean canGainSlots(int slots) {
slots += this.slots;
return slots <= 48;
}
public boolean gainSlots(int slots) {
lock.lock();
try {
slots += this.slots;
if (slots <= 48) {
if (canGainSlots(slots)) {
slots += this.slots;
this.slots = (byte) slots;
return true;
}

View File

@@ -310,19 +310,9 @@ public class MapleMapFactory {
}
}
try {
map.setMapName(loadPlaceName(mapid));
map.setStreetName(loadStreetName(mapid));
} catch (Exception e) {
if (mapid / 1000 != 1020) { // explorer job introduction scenes
e.printStackTrace();
System.err.println("Not found mapid " + mapid);
}
map.setMapName("");
map.setStreetName("");
}
map.setMapName(loadPlaceName(mapid));
map.setStreetName(loadStreetName(mapid));
map.setClock(mapData.getChildByPath("clock") != null);
map.setEverlast(MapleDataTool.getIntConvert("everlast", infoData, 0) != 0); // thanks davidlafriniere for noticing value 0 accounting as true
map.setTown(MapleDataTool.getIntConvert("town", infoData, 0) != 0);
@@ -435,7 +425,7 @@ public class MapleMapFactory {
return builder.toString();
}
public static String loadPlaceName(int mapid) throws Exception {
public static String loadPlaceName(int mapid) {
try {
return MapleDataTool.getString("mapName", nameData.getChildByPath(getMapStringName(mapid)), "");
} catch (Exception e) {
@@ -443,7 +433,7 @@ public class MapleMapFactory {
}
}
public static String loadStreetName(int mapid) throws Exception {
public static String loadStreetName(int mapid) {
try {
return MapleDataTool.getString("streetName", nameData.getChildByPath(getMapStringName(mapid)), "");
} catch (Exception e) {

View File

@@ -1890,7 +1890,7 @@ public class MaplePacketCreator {
}
long buffmask = 0;
Integer buffvalue = null;
if (chr.getBuffedValue(MapleBuffStat.DARKSIGHT) != null && !chr.isHidden()) {
if ((chr.getBuffedValue(MapleBuffStat.DARKSIGHT) != null || chr.getBuffedValue(MapleBuffStat.WIND_WALK) != null) && !chr.isHidden()) {
buffmask |= MapleBuffStat.DARKSIGHT.getValue();
}
if (chr.getBuffedValue(MapleBuffStat.COMBO) != null) {
@@ -1906,10 +1906,6 @@ public class MaplePacketCreator {
if (chr.getBuffedValue(MapleBuffStat.MORPH) != null) {
buffvalue = Integer.valueOf(chr.getBuffedValue(MapleBuffStat.MORPH).intValue());
}
if (chr.getBuffedValue(MapleBuffStat.ENERGY_CHARGE) != null) {
buffmask |= MapleBuffStat.ENERGY_CHARGE.getValue();
buffvalue = Integer.valueOf(chr.getBuffedValue(MapleBuffStat.ENERGY_CHARGE).intValue());
}//AREN'T THESE
mplew.writeInt((int) ((buffmask >> 32) & 0xffffffffL));
if (buffvalue != null) {
if (chr.getBuffedValue(MapleBuffStat.MORPH) != null) { //TEST
@@ -1919,16 +1915,24 @@ public class MaplePacketCreator {
}
}
mplew.writeInt((int) (buffmask & 0xffffffffL));
int CHAR_MAGIC_SPAWN = Randomizer.nextInt();
mplew.skip(6);
mplew.writeInt(CHAR_MAGIC_SPAWN);
// Energy Charge
mplew.writeInt(chr.getEnergyBar() == 15000 ? 1 : 0);
mplew.writeShort(0);
mplew.skip(4);
boolean dashBuff = chr.getBuffedValue(MapleBuffStat.DASH) != null;
// Dash Speed
mplew.writeInt(dashBuff ? 1 << 24 : 0);
mplew.skip(11);
mplew.writeInt(CHAR_MAGIC_SPAWN);//v74
mplew.skip(11);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.writeShort(0);
// Dash Jump
mplew.skip(9);
mplew.writeInt(dashBuff ? 1 << 24 : 0);
mplew.writeShort(0);
mplew.write(0);
// Monster Riding
Integer bv = chr.getBuffedValue(MapleBuffStat.MONSTER_RIDING);
if (bv != null) {
MapleMount mount = chr.getMount();
@@ -1942,17 +1946,23 @@ public class MaplePacketCreator {
mplew.writeLong(0);
}
int CHAR_MAGIC_SPAWN = Randomizer.nextInt(); // skill references found thanks to Rien dev team
mplew.writeInt(CHAR_MAGIC_SPAWN);
// Speed Infusion
mplew.skip(8);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.write(0);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.writeShort(0);
// Homing Beacon
mplew.skip(9);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.writeInt(0);
// Zombify
mplew.skip(9);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.writeShort(0);
mplew.writeInt(0); // actually not 0, why is it 0 then?
mplew.skip(10);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.skip(13);
mplew.writeInt(CHAR_MAGIC_SPAWN);
mplew.writeShort(0);
mplew.write(0);
}
/**
@@ -3067,6 +3077,15 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] cancelForeignFirstDebuff(int cid, long mask) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CANCEL_FOREIGN_BUFF.getValue());
mplew.writeInt(cid);
mplew.writeLong(mask);
mplew.writeLong(0);
return mplew.getPacket();
}
public static byte[] cancelForeignDebuff(int cid, long mask) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CANCEL_FOREIGN_BUFF.getValue());
@@ -3198,6 +3217,20 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
// packet found thanks to Ronan
public static byte[] giveForeignWKChargeEffect(int cid, int buffid, List<Pair<MapleBuffStat, Integer>> statups) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(19);
mplew.writeShort(SendOpcode.GIVE_FOREIGN_BUFF.getValue());
mplew.writeInt(cid);
writeLongMask(mplew, statups);
mplew.writeInt(buffid);
mplew.writeShort(600);
mplew.writeShort(1000);//Delay
mplew.write(1);
return mplew.getPacket();
}
public static byte[] cancelForeignChairSkillEffect(int cid) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(19);
mplew.writeShort(SendOpcode.CANCEL_FOREIGN_BUFF.getValue());

View File

@@ -230,7 +230,7 @@
</imgdir>
<imgdir name="2010007">
<string name="name" value="Roger&apos;s Apple"/>
<string name="desc" value="A ripe, red apple.\nRecovers HP 30.nn#cTo eat Roger&apos;s Apple, simply double-click on it in your use inventory#."/>
<string name="desc" value="A ripe, red apple.\nRecovers HP 30.\n\n#cTo eat Roger&apos;s Apple, simply double-click on it in your use inventory#."/>
</imgdir>
<imgdir name="2010009">
<string name="name" value="Green Apple"/>
@@ -1445,31 +1445,31 @@
</imgdir>
<imgdir name="2030001">
<string name="name" value="Return Scroll to Lith Harbor"/>
<string name="desc" value="Returns you to Lith Harbor. It disappears once it's used."/>
<string name="desc" value="Returns you to Lith Harbor. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030002">
<string name="name" value="Return Scroll to Ellinia"/>
<string name="desc" value="Returns you to Ellinia. It disappears once it's used."/>
<string name="desc" value="Returns you to Ellinia. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030003">
<string name="name" value="Return Scroll to Perion"/>
<string name="desc" value="Returns you to Perion. It disappears once it's used."/>
<string name="desc" value="Returns you to Perion. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030004">
<string name="name" value="Return Scroll to Henesys"/>
<string name="desc" value="Returns you to Henesys, the peaceful town. It disappears once it's used."/>
<string name="desc" value="Returns you to Henesys, the peaceful town. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030005">
<string name="name" value="Return Scroll to Kerning City"/>
<string name="desc" value="Returns you to the dark Kerning City. It disappears once it's used."/>
<string name="desc" value="Returns you to the dark Kerning City. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030006">
<string name="name" value="Return Scroll to Sleepywood"/>
<string name="desc" value="Returns you to Sleepywood, a quiet and dark forest-town. It disappears once it's used."/>
<string name="desc" value="Returns you to Sleepywood, a quiet and dark forest-town. It disappears once it&apos;s used."/>
</imgdir>
<imgdir name="2030007">
<string name="name" value="Return Scroll to Dead Mine"/>
<string name="desc" value="Returns you to the dead mine at the higher ground of El Nath. It disappears once it's used.\nCan only be used in Orbis and El Nath."/>
<string name="desc" value="Returns you to the dead mine at the higher ground of El Nath. It disappears once it&apos;s used.\nCan only be used in Orbis and El Nath."/>
</imgdir>
<imgdir name="2030008">
<string name="name" value="Coffee Milk"/>
@@ -3689,7 +3689,7 @@
</imgdir>
<imgdir name="2060004">
<string name="name" value="Diamond Arrow for Bow"/>
<string name="desc" value="A case full of arrows. Can only be used with a bow.nAttack+4."/>
<string name="desc" value="A case full of arrows. Can only be used with a bow.\nAttack+4."/>
</imgdir>
<imgdir name="2060005">
<string name="name" value="Snowball"/>
@@ -8305,7 +8305,7 @@
</imgdir>
<imgdir name="2049203">
<string name="name" value="Dark Scroll for Accessory for DEX 30%"/>
<string name="desc" value="Improves DEX on accessories (pendants, belts, rings).\nSuccess rate:30%, DEX+3\.nIf failed, the item will be destroyed at a 50% rate."/>
<string name="desc" value="Improves DEX on accessories (pendants, belts, rings).\nSuccess rate:30%, DEX+3.\nIf failed, the item will be destroyed at a 50% rate."/>
</imgdir>
<imgdir name="2049204">
<string name="name" value="Dark Scroll for Accessory for INT 70%"/>