diff --git a/config.yaml b/config.yaml index a05bd6b5796..879d52280aa 100644 --- a/config.yaml +++ b/config.yaml @@ -241,7 +241,6 @@ server: USE_ENFORCE_MERCHANT_SAVE: true #Forces automatic DB save on merchant owners, at every item movement on shop. USE_ENFORCE_MDOOR_POSITION: false #Forces mystic door to be spawned near spawnpoints. USE_SPAWN_CLEAN_MDOOR: false #Makes mystic doors to be spawned without deploy animation. This clears disconnecting issues that may happen when trying to cancel doors a couple seconds after deployment. - USE_SPAWN_LOOT_ON_ANIMATION: false #Makes loot appear some time after the mob has been killed (following the mob death animation, instead of instantly). USE_SPAWN_RELEVANT_LOOT: true #Forces to only spawn loots that are collectable by the player or any of their party members. USE_ERASE_PERMIT_ON_OPENSHOP: true #Forces "shop permit" item to be consumed when player deploy his/her player shop. USE_ERASE_UNTRADEABLE_DROP: true #Forces flagged untradeable items to disappear when dropped. diff --git a/src/main/java/config/ServerConfig.java b/src/main/java/config/ServerConfig.java index 0a16361a999..c0d0c7e0c25 100644 --- a/src/main/java/config/ServerConfig.java +++ b/src/main/java/config/ServerConfig.java @@ -90,7 +90,6 @@ public class ServerConfig { public boolean USE_ENFORCE_MERCHANT_SAVE; public boolean USE_ENFORCE_MDOOR_POSITION; public boolean USE_SPAWN_CLEAN_MDOOR; - public boolean USE_SPAWN_LOOT_ON_ANIMATION; public boolean USE_SPAWN_RELEVANT_LOOT; public boolean USE_ERASE_PERMIT_ON_OPENSHOP; public boolean USE_ERASE_UNTRADEABLE_DROP; diff --git a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java index c661d03efcd..793a6199b24 100644 --- a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -96,7 +96,6 @@ import server.life.MonsterInformationProvider; import server.maps.MapItem; import server.maps.MapObject; -import server.maps.MapObjectType; import server.maps.MapleMap; import tools.PacketCreator; import tools.Randomizer; @@ -117,10 +116,12 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { public static class AttackInfo { public int numAttacked, numDamage, numAttackedAndDamage, skill, skilllevel, stance, direction, rangedirection, charge, display; - public Map> allDamage; + public Map targets; public boolean ranged, magic; public int speed = 4; public Point position = new Point(); + public List explodedMesos; + public Short attackDelay; public StatEffect getAttackEffect(Character chr, Skill theSkill) { Skill mySkill = theSkill; @@ -146,6 +147,9 @@ public StatEffect getAttackEffect(Character chr, Skill theSkill) { } } + // TODO: add position + public record AttackTarget(short delay, List damageLines) {} + protected void applyAttack(AttackInfo attack, final Character player, int attackCount) { final MapleMap map = player.getMap(); if (map.isOwnershipRestricted(player)) { @@ -207,50 +211,14 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack return; } - //WTF IS THIS F3,1 - /*if (attackCount != attack.numDamage && attack.skill != ChiefBandit.MESO_EXPLOSION && attack.skill != NightWalker.VAMPIRE && attack.skill != WindArcher.WIND_SHOT && attack.skill != Aran.COMBO_SMASH && attack.skill != Aran.COMBO_FENRIR && attack.skill != Aran.COMBO_TEMPEST && attack.skill != NightLord.NINJA_AMBUSH && attack.skill != Shadower.NINJA_AMBUSH) { - return; - }*/ - int totDamage = 0; if (attack.skill == ChiefBandit.MESO_EXPLOSION) { - int delay = 0; - for (Integer oned : attack.allDamage.keySet()) { - MapObject mapobject = map.getMapObject(oned); - if (mapobject != null && mapobject.getType() == MapObjectType.ITEM) { - final MapItem mapitem = (MapItem) mapobject; - if (mapitem.getMeso() == 0) { //Maybe it is possible some how? - return; - } - - mapitem.lockItem(); - try { - if (mapitem.isPickedUp()) { - return; - } - TimerManager.getInstance().schedule(() -> { - mapitem.lockItem(); - try { - if (mapitem.isPickedUp()) { - return; - } - map.pickItemDrop(PacketCreator.removeItemFromMap(mapitem.getObjectId(), 4, 0), mapitem); - } finally { - mapitem.unlockItem(); - } - }, delay); - delay += 100; - } finally { - mapitem.unlockItem(); - } - } else if (mapobject != null && mapobject.getType() != MapObjectType.MONSTER) { - return; - } - } + removeExplodedMesos(map, attack); } - for (Integer oned : attack.allDamage.keySet()) { - final Monster monster = map.getMonsterByOid(oned); + + for (Map.Entry target : attack.targets.entrySet()) { + final Monster monster = map.getMonsterByOid(target.getKey()); if (monster != null) { double distance = player.getPosition().distanceSq(monster.getPosition()); double distanceToDetect = 200000.0; @@ -285,7 +253,7 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack } int totDamageToOneMonster = 0; - List onedList = attack.allDamage.get(oned); + List onedList = target.getValue().damageLines(); if (attack.magic) { // thanks BHB, Alex (CanIGetaPR) for noticing no immunity status check here if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) { @@ -321,7 +289,7 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET); int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket); if (picklv > 0) { - int delay = 0; + short delay = 0; final int maxmeso = player.getBuffedValue(BuffStat.PICKPOCKET); for (Integer eachd : onedList) { eachd += Integer.MAX_VALUE; @@ -334,7 +302,9 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack eachdf = eachd; } - TimerManager.getInstance().schedule(() -> map.spawnMesoDrop(Math.min((int) Math.max(((double) eachdf / (double) 20000) * (double) maxmeso, 1), maxmeso), new Point((int) (monster.getPosition().getX() + Randomizer.nextInt(100) - 50), (int) (monster.getPosition().getY())), monster, player, true, (byte) 2), delay); + int meso = Math.min((int) Math.max(((double) eachdf / (double) 20000) * (double) maxmeso, 1), maxmeso); + Point position = new Point((int) (monster.getPosition().getX() + Randomizer.nextInt(100) - 50), (int) (monster.getPosition().getY())); + map.spawnMesoDrop(meso, position, monster, player, true, (byte) 2, delay); delay += 100; } } @@ -360,7 +330,7 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack List toSteal = new ArrayList<>(); toSteal.add(mi.retrieveDrop(monster.getId()).get(i)); - map.dropItemsFromMonster(toSteal, player, monster); + map.dropItemsFromMonster(toSteal, player, monster, target.getValue().delay()); monster.addStolen(toSteal.get(0).itemId); } } @@ -480,7 +450,7 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack StatEffect mortal = mortalBlow.getEffect(skillLevel); if (monster.getHp() <= (monster.getStats().getHp() * mortal.getX()) / 100) { if (Randomizer.rand(1, 100) <= mortal.getY()) { - map.damageMonster(player, monster, Integer.MAX_VALUE); // thanks Conrad for noticing reduced EXP gain from skill kill + map.damageMonster(player, monster, Integer.MAX_VALUE, target.getValue().delay()); // thanks Conrad for noticing reduced EXP gain from skill kill } } } @@ -546,7 +516,7 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack map.broadcastMessage(PacketCreator.damageMonster(monster.getObjectId(), totDamageToOneMonster)); } - map.damageMonster(player, monster, totDamageToOneMonster); + map.damageMonster(player, monster, totDamageToOneMonster, target.getValue().delay()); } if (monster.isBuffed(MonsterStatus.WEAPON_REFLECT) && !attack.magic) { for (MobSkillId msId : monster.getSkills()) { @@ -573,7 +543,8 @@ protected void applyAttack(AttackInfo attack, final Character player, int attack } } - private static void damageMonsterWithSkill(final Character attacker, final MapleMap map, final Monster monster, final int damage, int skillid, int fixedTime) { + private static void damageMonsterWithSkill(final Character attacker, final MapleMap map, final Monster monster, + final int damage, int skillid, int fixedTime) { int animationTime; if (fixedTime == 0) { @@ -600,7 +571,7 @@ protected AttackInfo parseDamage(InPacket p, Character chr, boolean ranged, bool ret.numAttackedAndDamage = p.readByte(); ret.numAttacked = (ret.numAttackedAndDamage >>> 4) & 0xF; ret.numDamage = ret.numAttackedAndDamage & 0xF; - ret.allDamage = new HashMap<>(); + ret.targets = new HashMap<>(); ret.skill = p.readInt(); ret.ranged = ranged; ret.magic = magic; @@ -623,41 +594,9 @@ protected AttackInfo parseDamage(InPacket p, Character chr, boolean ranged, bool ret.direction = p.readByte(); ret.stance = p.readByte(); if (ret.skill == ChiefBandit.MESO_EXPLOSION) { - if (ret.numAttackedAndDamage == 0) { - p.skip(10); - int bullets = p.readByte(); - for (int j = 0; j < bullets; j++) { - int mesoid = p.readInt(); - p.skip(1); - ret.allDamage.put(mesoid, null); - } - return ret; - } else { - p.skip(6); - } - for (int i = 0; i < ret.numAttacked + 1; i++) { - int oid = p.readInt(); - if (i < ret.numAttacked) { - p.skip(12); - int bullets = p.readByte(); - List allDamageNumbers = new ArrayList<>(); - for (int j = 0; j < bullets; j++) { - int damage = p.readInt(); - allDamageNumbers.add(damage); - } - ret.allDamage.put(oid, allDamageNumbers); - p.skip(4); - } else { - int bullets = p.readByte(); - for (int j = 0; j < bullets; j++) { - int mesoid = p.readInt(); - p.skip(1); - ret.allDamage.put(mesoid, null); - } - } - } - return ret; + return parseMesoExplosion(p, ret); } + if (ranged) { p.readByte(); ret.speed = p.readByte(); @@ -814,10 +753,12 @@ protected AttackInfo parseDamage(InPacket p, Character chr, boolean ranged, bool } for (int i = 0; i < ret.numAttacked; i++) { int oid = p.readInt(); - p.skip(14); - List allDamageNumbers = new ArrayList<>(); - Monster monster = chr.getMap().getMonsterByOid(oid); - + p.skip(4); + Point curPos = p.readPos(); + Point nextPos = p.readPos(); + short delay = p.readShort(); + List damageLines = new ArrayList<>(); + final Monster monster = chr.getMap().getMonsterByOid(oid); if (chr.getBuffEffect(BuffStat.WK_CHARGE) != null) { // Charge, so now we need to check elemental effectiveness int sourceID = chr.getBuffSource(BuffStat.WK_CHARGE); @@ -941,12 +882,12 @@ protected AttackInfo parseDamage(InPacket p, Character chr, boolean ranged, bool } } - allDamageNumbers.add(damage); + damageLines.add(damage); } if (ret.skill != Corsair.RAPID_FIRE || ret.skill != Aran.HIDDEN_FULL_DOUBLE || ret.skill != Aran.HIDDEN_FULL_TRIPLE || ret.skill != Aran.HIDDEN_OVER_DOUBLE || ret.skill != Aran.HIDDEN_OVER_TRIPLE) { p.skip(4); } - ret.allDamage.put(oid, allDamageNumbers); + ret.targets.put(oid, new AttackTarget(delay, damageLines)); } if (ret.skill == NightWalker.POISON_BOMB) { // Poison Bomb p.skip(4); @@ -955,7 +896,66 @@ protected AttackInfo parseDamage(InPacket p, Character chr, boolean ranged, bool return ret; } - private static int rand(int l, int u) { - return (int) ((Math.random() * (u - l + 1)) + l); + private AttackInfo parseMesoExplosion(InPacket p, AttackInfo attackInfo) { + p.skip(6); + + Map> targetDamage = new HashMap<>(); + for (int i = 0; i < attackInfo.numAttacked; i++) { + int mobOid = p.readInt(); + p.skip(4); + Point curPos = p.readPos(); + Point nextPos = p.readPos(); + int damageLines = p.readByte(); + List allDamageNumbers = new ArrayList<>(); + for (int j = 0; j < damageLines; j++) { + int damage = p.readInt(); + allDamageNumbers.add(damage); + } + p.skip(4); + targetDamage.put(mobOid, allDamageNumbers); + } + + p.skip(4); + + List explodedMesos = new ArrayList<>(); + int explodedMesoCount = p.readByte(); + for (int j = 0; j < explodedMesoCount; j++) { + int mesoOid = p.readInt(); + p.skip(1); + explodedMesos.add(mesoOid); + } + attackInfo.explodedMesos = explodedMesos; + + final short attackDelay = p.readShort(); + attackInfo.attackDelay = attackDelay; + + Map targets = new HashMap<>(); + targetDamage.forEach((id, damage) -> targets.put(id, new AttackTarget(attackDelay, damage))); + attackInfo.targets = targets; + return attackInfo; + } + + private void removeExplodedMesos(MapleMap map, AttackInfo attack) { + short delay = attack.attackDelay; + for (Integer mesoId : attack.explodedMesos) { + MapObject mapobject = map.getMapObject(mesoId); + if (!(mapobject instanceof MapItem mapItem)) { + return; + } + if (mapItem.getMeso() == 0) { + return; + } + + mapItem.lockItem(); + try { + if (mapItem.isPickedUp()) { + return; + } + map.pickItemDrop(PacketCreator.removeExplodedMesoFromMap(mapItem.getObjectId(), delay), mapItem); + } finally { + mapItem.unlockItem(); + } + delay += 100; + } } } diff --git a/src/main/java/net/server/channel/handlers/AdminCommandHandler.java b/src/main/java/net/server/channel/handlers/AdminCommandHandler.java index 3cd9dd67044..182087606a9 100644 --- a/src/main/java/net/server/channel/handlers/AdminCommandHandler.java +++ b/src/main/java/net/server/channel/handlers/AdminCommandHandler.java @@ -133,7 +133,7 @@ public void handlePacket(InPacket p, Client c) { for (int x = 0; x < amount; x++) { Monster monster = (Monster) monsterx.get(x); if (monster.getId() == mobToKill) { - c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true); + c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true, (short) 0); } } break; diff --git a/src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java b/src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java index 5a8a9bef616..b25712d912f 100644 --- a/src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java @@ -78,7 +78,9 @@ public final void handlePacket(InPacket p, Client c) { c.sendPacket(PacketCreator.getEnergy("energy", chr.getDojoEnergy())); } - chr.getMap().broadcastMessage(chr, PacketCreator.closeRangeAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, attack.allDamage, attack.speed, attack.direction, attack.display), false, true); + chr.getMap().broadcastMessage(chr, PacketCreator.closeRangeAttack(chr, attack.skill, attack.skilllevel, + attack.stance, attack.numAttackedAndDamage, attack.targets, attack.speed, attack.direction, + attack.display), false, true); int numFinisherOrbs = 0; Integer comboBuff = chr.getBuffedValue(BuffStat.COMBO); if (GameConstants.isFinisherSkill(attack.skill)) { @@ -139,9 +141,9 @@ public final void handlePacket(InPacket p, Client c) { } if (attack.numAttacked > 0 && attack.skill == DragonKnight.SACRIFICE) { int totDamageToOneMonster = 0; // sacrifice attacks only 1 mob with 1 attack - final Iterator> dmgIt = attack.allDamage.values().iterator(); + final Iterator dmgIt = attack.targets.values().iterator(); if (dmgIt.hasNext()) { - totDamageToOneMonster = dmgIt.next().get(0); + totDamageToOneMonster = dmgIt.next().damageLines().getFirst(); } chr.safeAddHP(-1 * totDamageToOneMonster * attack.getAttackEffect(chr, null).getX() / 100); diff --git a/src/main/java/net/server/channel/handlers/MagicDamageHandler.java b/src/main/java/net/server/channel/handlers/MagicDamageHandler.java index 7350e55c553..3324ebb0230 100644 --- a/src/main/java/net/server/channel/handlers/MagicDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/MagicDamageHandler.java @@ -66,7 +66,8 @@ public final void handlePacket(InPacket p, Client c) { } int charge = (attack.skill == Evan.FIRE_BREATH || attack.skill == Evan.ICE_BREATH || attack.skill == FPArchMage.BIG_BANG || attack.skill == ILArchMage.BIG_BANG || attack.skill == Bishop.BIG_BANG) ? attack.charge : -1; - Packet packet = PacketCreator.magicAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, attack.allDamage, charge, attack.speed, attack.direction, attack.display); + Packet packet = PacketCreator.magicAttack(chr, attack.skill, attack.skilllevel, attack.stance, + attack.numAttackedAndDamage, attack.targets, charge, attack.speed, attack.direction, attack.display); chr.getMap().broadcastMessage(chr, packet, false, true); StatEffect effect = attack.getAttackEffect(chr, null); @@ -84,8 +85,8 @@ public final void handlePacket(InPacket p, Client c) { Skill eaterSkill = SkillFactory.getSkill((chr.getJob().getId() - (chr.getJob().getId() % 10)) * 10000);// MP Eater, works with right job int eaterLevel = chr.getSkillLevel(eaterSkill); if (eaterLevel > 0) { - for (Integer singleDamage : attack.allDamage.keySet()) { - eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(singleDamage), 0); + for (Integer oid : attack.targets.keySet()) { + eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(oid), 0); } } } diff --git a/src/main/java/net/server/channel/handlers/MesoDropHandler.java b/src/main/java/net/server/channel/handlers/MesoDropHandler.java index f99c7edbee6..94389b96b70 100644 --- a/src/main/java/net/server/channel/handlers/MesoDropHandler.java +++ b/src/main/java/net/server/channel/handlers/MesoDropHandler.java @@ -67,7 +67,8 @@ public final void handlePacket(InPacket p, Client c) { if (player.attemptCatchFish(meso)) { player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition()); } else { - player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); + player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2, + (short) 0); } } -} \ No newline at end of file +} diff --git a/src/main/java/net/server/channel/handlers/MobDamageMobHandler.java b/src/main/java/net/server/channel/handlers/MobDamageMobHandler.java index ad50bd5afbd..c11063d539f 100644 --- a/src/main/java/net/server/channel/handlers/MobDamageMobHandler.java +++ b/src/main/java/net/server/channel/handlers/MobDamageMobHandler.java @@ -57,21 +57,24 @@ public void handlePacket(InPacket p, Client c) { Monster attacker = map.getMonsterByOid(from); Monster damaged = map.getMonsterByOid(to); - if (attacker != null && damaged != null) { - int maxDmg = calcMaxDamage(attacker, damaged, magic); // thanks Darter (YungMoozi) for reporting unchecked dmg - - if (dmg > maxDmg) { - AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing hypnotize damage exploit."); // thanks Rien dev team - String attackerName = MonsterInformationProvider.getInstance().getMobNameFromId(attacker.getId()); - String damagedName = MonsterInformationProvider.getInstance().getMobNameFromId(damaged.getId()); - log.warn("Chr {} had hypnotized {} to attack {} with damage {} (max: {})", c.getPlayer().getName(), - attackerName, damagedName, dmg, maxDmg); - dmg = maxDmg; - } - - map.damageMonster(chr, damaged, dmg); - map.broadcastMessage(chr, PacketCreator.damageMonster(to, dmg), false); + if (attacker == null || damaged == null) { + return; } + + int maxDmg = calcMaxDamage(attacker, damaged, magic); // thanks Darter (YungMoozi) for reporting unchecked dmg + + if (dmg > maxDmg) { + AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing hypnotize damage exploit."); // thanks Rien dev team + String attackerName = MonsterInformationProvider.getInstance().getMobNameFromId(attacker.getId()); + String damagedName = MonsterInformationProvider.getInstance().getMobNameFromId(damaged.getId()); + log.warn("Chr {} had hypnotized {} to attack {} with damage {} (max: {})", c.getPlayer().getName(), + attackerName, damagedName, dmg, maxDmg); + dmg = maxDmg; + } + + map.damageMonster(chr, damaged, dmg); + map.broadcastMessage(chr, PacketCreator.damageMonster(to, dmg), false); + } private static int calcMaxDamage(Monster attacker, Monster damaged, boolean magic) { diff --git a/src/main/java/net/server/channel/handlers/RangedAttackHandler.java b/src/main/java/net/server/channel/handlers/RangedAttackHandler.java index 85d599b9fc8..dff4e77d7ad 100644 --- a/src/main/java/net/server/channel/handlers/RangedAttackHandler.java +++ b/src/main/java/net/server/channel/handlers/RangedAttackHandler.java @@ -83,17 +83,23 @@ public void handlePacket(InPacket p, Client c) { } if (attack.skill == Buccaneer.ENERGY_ORB || attack.skill == ThunderBreaker.SPARK || attack.skill == Shadower.TAUNT || attack.skill == NightLord.TAUNT) { - chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false); + chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, + attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed, + attack.direction, attack.display), false); applyAttack(attack, chr, 1); } else if (attack.skill == ThunderBreaker.SHARK_WAVE && chr.getSkillLevel(ThunderBreaker.SHARK_WAVE) > 0) { - chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false); + chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, + attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed, + attack.direction, attack.display), false); applyAttack(attack, chr, 1); for (int i = 0; i < attack.numAttacked; i++) { chr.handleEnergyChargeGain(); } } else if (attack.skill == Aran.COMBO_SMASH || attack.skill == Aran.COMBO_FENRIR || attack.skill == Aran.COMBO_TEMPEST) { - chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false); + chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, + attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed, + attack.direction, attack.display), false); if (attack.skill == Aran.COMBO_SMASH && chr.getCombo() >= 30) { chr.setCombo((short) 0); applyAttack(attack, chr, 1); @@ -213,10 +219,14 @@ public void handlePacket(InPacket p, Client c) { case 3221001: // Pierce case 5221004: // Rapid Fire case 13111002: // KoC Hurricane - packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.rangedirection, attack.numAttackedAndDamage, visProjectile, attack.allDamage, attack.speed, attack.direction, attack.display); + packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.rangedirection, + attack.numAttackedAndDamage, visProjectile, attack.targets, attack.speed, + attack.direction, attack.display); break; default: - packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, visProjectile, attack.allDamage, attack.speed, attack.direction, attack.display); + packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, + attack.numAttackedAndDamage, visProjectile, attack.targets, attack.speed, + attack.direction, attack.display); break; } chr.getMap().broadcastMessage(chr, packet, false, true); diff --git a/src/main/java/net/server/channel/handlers/SummonDamageHandler.java b/src/main/java/net/server/channel/handlers/SummonDamageHandler.java index 5506818992e..b22e473b75f 100644 --- a/src/main/java/net/server/channel/handlers/SummonDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/SummonDamageHandler.java @@ -41,31 +41,14 @@ import server.maps.Summon; import tools.PacketCreator; +import java.awt.*; import java.util.ArrayList; import java.util.List; public final class SummonDamageHandler extends AbstractDealDamageHandler { private static final Logger log = LoggerFactory.getLogger(SummonDamageHandler.class); - public final class SummonAttackEntry { - - private final int monsterOid; - private final int damage; - - public SummonAttackEntry(int monsterOid, int damage) { - this.monsterOid = monsterOid; - this.damage = damage; - } - - public int getMonsterOid() { - return monsterOid; - } - - public int getDamage() { - return damage; - } - - } + public record SummonAttackTarget(int monsterOid, int damage, short delay) {} @Override public void handlePacket(InPacket p, Client c) { @@ -86,17 +69,21 @@ public void handlePacket(InPacket p, Client c) { Skill summonSkill = SkillFactory.getSkill(summon.getSkill()); StatEffect summonEffect = summonSkill.getEffect(summon.getSkillLevel()); p.skip(4); - List allDamage = new ArrayList<>(); + List targets = new ArrayList<>(); byte direction = p.readByte(); int numAttacked = p.readByte(); p.skip(8); // I failed lol (mob x,y and summon x,y), Thanks Gerald for (int x = 0; x < numAttacked; x++) { int monsterOid = p.readInt(); // attacked oid - p.skip(18); + p.skip(8); + Point curPos = p.readPos(); + Point nextPos = p.readPos(); + short delay = p.readShort(); int damage = p.readInt(); - allDamage.add(new SummonAttackEntry(monsterOid, damage)); + targets.add(new SummonAttackTarget(monsterOid, damage, delay)); } - player.getMap().broadcastMessage(player, PacketCreator.summonAttack(player.getId(), summon.getObjectId(), direction, allDamage), summon.getPosition()); + player.getMap().broadcastMessage(player, PacketCreator.summonAttack(player.getId(), summon.getObjectId(), + direction, targets), summon.getPosition()); if (player.getMap().isOwnershipRestricted(player)) { return; @@ -104,25 +91,28 @@ public void handlePacket(InPacket p, Client c) { boolean magic = summonEffect.getWatk() == 0; int maxDmg = calcMaxDamage(summonEffect, player, magic); // thanks Darter (YungMoozi) for reporting unchecked max dmg - for (SummonAttackEntry attackEntry : allDamage) { - int damage = attackEntry.getDamage(); - Monster target = player.getMap().getMonsterByOid(attackEntry.getMonsterOid()); - if (target != null) { - if (damage > maxDmg) { - AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing summon damage exploit."); - final String mobName = MonsterInformationProvider.getInstance().getMobNameFromId(target.getId()); - log.info("Possible exploit - chr {} used a summon of skillId {} to attack {} with damage {} (max: {})", - c.getPlayer().getName(), summon.getSkill(), mobName, damage, maxDmg); - damage = maxDmg; - } + for (SummonAttackTarget target : targets) { + int damage = target.damage(); + Monster mob = player.getMap().getMonsterByOid(target.monsterOid()); + if (mob == null) { + continue; + } - if (damage > 0 && summonEffect.getMonsterStati().size() > 0) { - if (summonEffect.makeChanceResult()) { - target.applyStatus(player, new MonsterStatusEffect(summonEffect.getMonsterStati(), summonSkill, null, false), summonEffect.isPoison(), 4000); - } + if (damage > maxDmg) { + AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing summon damage exploit."); + final String mobName = MonsterInformationProvider.getInstance().getMobNameFromId(mob.getId()); + log.info("Possible exploit - chr {} used a summon of skillId {} to attack {} with damage {} (max: {})", + c.getPlayer().getName(), summon.getSkill(), mobName, damage, maxDmg); + damage = maxDmg; + } + + if (damage > 0 && summonEffect.getMonsterStati().size() > 0) { + if (summonEffect.makeChanceResult()) { + mob.applyStatus(player, new MonsterStatusEffect(summonEffect.getMonsterStati(), summonSkill, null, false), summonEffect.isPoison(), 4000); } - player.getMap().damageMonster(player, target, damage); } + player.getMap().damageMonster(player, mob, damage, target.delay()); + } if (summon.getSkill() == Outlaw.GAVIOTA) { // thanks Periwinks for noticing Gaviota not cancelling after grenade toss diff --git a/src/main/java/net/server/channel/handlers/UseCatchItemHandler.java b/src/main/java/net/server/channel/handlers/UseCatchItemHandler.java index ead6813e663..781bdcd0322 100644 --- a/src/main/java/net/server/channel/handlers/UseCatchItemHandler.java +++ b/src/main/java/net/server/channel/handlers/UseCatchItemHandler.java @@ -61,7 +61,7 @@ public final void handlePacket(InPacket p, Client c) { case ItemId.PHEROMONE_PERFUME: if (mob.getId() == MobId.TAMABLE_HOG) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.HOG, (short) 1, "", -1); } @@ -72,7 +72,7 @@ public final void handlePacket(InPacket p, Client c) { if ((abm.getLastSpam(10) + 1000) < currentServerTime()) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.GHOST_SACK, (short) 1, "", -1); } else { @@ -90,7 +90,7 @@ public final void handlePacket(InPacket p, Client c) { if (chr.canHold(ItemId.ARPQ_SPIRIT_JEWEL, 1)) { if (Math.random() < 0.5) { // 50% chance chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.ARPQ_SPIRIT_JEWEL, (short) 1, "", -1); chr.updateAriantScore(); @@ -113,7 +113,7 @@ public final void handlePacket(InPacket p, Client c) { if (mob.getId() == MobId.LOST_RUDOLPH) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.TAMED_RUDOLPH, (short) 1, "", -1); } else { @@ -126,7 +126,7 @@ public final void handlePacket(InPacket p, Client c) { if (mob.getId() == MobId.KING_SLIME_DOJO) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_1, (short) 1, "", -1); } else { @@ -139,7 +139,7 @@ public final void handlePacket(InPacket p, Client c) { if (mob.getId() == MobId.FAUST_DOJO) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_2, (short) 1, "", -1); } else { @@ -152,7 +152,7 @@ public final void handlePacket(InPacket p, Client c) { if (mob.getId() == MobId.MUSHMOM_DOJO) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_3, (short) 1, "", -1); } else { @@ -165,7 +165,7 @@ public final void handlePacket(InPacket p, Client c) { if (mob.getId() == MobId.POISON_FLOWER) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.EPQ_MONSTER_MARBLE, (short) 1, "", -1); } else { @@ -179,7 +179,7 @@ public final void handlePacket(InPacket p, Client c) { if ((abm.getLastSpam(10) + 3000) < currentServerTime()) { abm.spam(10); chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, ItemId.FISH_NET_WITH_A_CATCH, (short) 1, "", -1); } else { @@ -202,7 +202,7 @@ public final void handlePacket(InPacket p, Client c) { if (timeCatch != 0 && (abm.getLastSpam(10) + timeCatch) < currentServerTime()) { if (mobHp != 0 && mob.getHp() < ((mob.getMaxHp() / 100) * mobHp)) { chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); - mob.getMap().killMonster(mob, null, false); + killMonster(mob); InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true); InventoryManipulator.addById(c, itemGanho, (short) 1, "", -1); } else if (mob.getId() != MobId.P_JUNIOR) { @@ -220,4 +220,8 @@ public final void handlePacket(InPacket p, Client c) { // System.out.println("UseCatchItemHandler: \r\n" + slea.toString()); } } + + private static void killMonster(Monster mob) { + mob.getMap().killMonster(mob, null, false, (short) 0); + } } diff --git a/src/main/java/scripting/reactor/ReactorActionManager.java b/src/main/java/scripting/reactor/ReactorActionManager.java index b7f7c4e6a78..dd82320832d 100644 --- a/src/main/java/scripting/reactor/ReactorActionManager.java +++ b/src/main/java/scripting/reactor/ReactorActionManager.java @@ -33,6 +33,7 @@ import server.life.LifeFactory; import server.life.Monster; import server.maps.MapMonitor; +import server.maps.MapleMap; import server.maps.Reactor; import server.maps.ReactorDropEntry; import server.partyquest.CarnivalFactory; @@ -43,7 +44,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.ScheduledFuture; /** * @author Lerk @@ -52,7 +52,6 @@ public class ReactorActionManager extends AbstractPlayerInteraction { private final Reactor reactor; private final Invocable iv; - private ScheduledFuture sprayTask = null; public ReactorActionManager(Client c, Reactor reactor, Invocable iv) { super(c); @@ -172,7 +171,8 @@ public void dropItems(boolean delayed, int posX, int posY, boolean meso, int mes int range = maxMeso - minMeso; int displayDrop = (int) (Math.random() * range) + minMeso; int mesoDrop = (displayDrop * c.getWorldServer().getMesoRate()); - reactor.getMap().spawnMesoDrop(mesoDrop, reactor.getMap().calcDropPos(dropPos, reactor.getPosition()), reactor, c.getPlayer(), false, (byte) 2); + reactor.getMap().spawnMesoDrop(mesoDrop, reactor.getMap().calcDropPos(dropPos, + reactor.getPosition()), reactor, c.getPlayer(), false, (byte) 2, (short) 0); } else { Item drop; @@ -182,31 +182,24 @@ public void dropItems(boolean delayed, int posX, int posY, boolean meso, int mes drop = ii.randomizeStats((Equip) ii.getEquipById(d.itemId)); } - reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid); + reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid, (short) 0); } } } else { - final Reactor r = reactor; - final List dropItems = items; final int worldMesoRate = c.getWorldServer().getMesoRate(); dropPos.x -= (12 * items.size()); - - sprayTask = TimerManager.getInstance().register(() -> { - if (dropItems.isEmpty()) { - sprayTask.cancel(false); - return; - } - - ReactorDropEntry d = dropItems.remove(0); + short delay = 0; + for (ReactorDropEntry d : items) { if (d.itemId == 0) { int range = maxMeso - minMeso; int displayDrop = (int) (Math.random() * range) + minMeso; - int mesoDrop = (displayDrop * worldMesoRate); - r.getMap().spawnMesoDrop(mesoDrop, r.getMap().calcDropPos(dropPos, r.getPosition()), r, chr, false, (byte) 2); + int mesoDrop = displayDrop * worldMesoRate; + MapleMap map = reactor.getMap(); + map.spawnMesoDrop(mesoDrop, map.calcDropPos(dropPos, reactor.getPosition()), reactor, chr, + false, (byte) 2, delay); } else { - Item drop; - + final Item drop; if (ItemConstants.getInventoryType(d.itemId) != InventoryType.EQUIP) { drop = new Item(d.itemId, (short) 0, (short) 1); } else { @@ -214,11 +207,12 @@ public void dropItems(boolean delayed, int posX, int posY, boolean meso, int mes drop = ii.randomizeStats((Equip) ii.getEquipById(d.itemId)); } - r.getMap().dropFromReactor(getPlayer(), r, drop, dropPos, (short) d.questid); + reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid, delay); } dropPos.x += 25; - }, 200); + delay += 200; + } } } @@ -333,4 +327,4 @@ public void dispelAllMonsters(int num, int team) { //dispels all mobs, cpq getPlayer().getMap().getBlueTeamBuffs().remove(skil); } } -} \ No newline at end of file +} diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index ea3beb85dd1..e3ad4bf5b84 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -825,12 +825,12 @@ public Character killBy(final Character killer) { } if (htKilled) { - reviveMap.killMonster(ht, killer, true); + reviveMap.killMonster(ht, killer, true, (short) 0); } } for (int i = MobId.DEAD_HORNTAIL_MAX; i >= MobId.DEAD_HORNTAIL_MIN; i--) { - reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true); + reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true, (short) 0); } } else if (controller != null) { mob.aggroSwitchController(controller, aggro); diff --git a/src/main/java/server/maps/MapItem.java b/src/main/java/server/maps/MapItem.java index 9f1086d842b..b87306990ad 100644 --- a/src/main/java/server/maps/MapItem.java +++ b/src/main/java/server/maps/MapItem.java @@ -208,7 +208,8 @@ public void sendSpawnData(final Client client) { if (chr.needQuestItem(questid, getItemId())) { this.lockItem(); try { - client.sendPacket(PacketCreator.dropItemFromMapObject(chr, this, null, getPosition(), (byte) 2)); + client.sendPacket(PacketCreator.dropItemFromMapObject(chr, this, null, getPosition(), + (byte) 2, (short) 0)); } finally { this.unlockItem(); } @@ -219,4 +220,4 @@ public void sendSpawnData(final Client client) { public void sendDestroyData(final Client client) { client.sendPacket(PacketCreator.removeItemFromMap(getObjectId(), 1, 0)); } -} \ No newline at end of file +} diff --git a/src/main/java/server/maps/MapleMap.java b/src/main/java/server/maps/MapleMap.java index ceadc01aff1..d07fbddd662 100644 --- a/src/main/java/server/maps/MapleMap.java +++ b/src/main/java/server/maps/MapleMap.java @@ -82,7 +82,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -121,7 +120,6 @@ public class MapleMap { private final Map environment = new LinkedHashMap<>(); private final Map droppedItems = new LinkedHashMap<>(); private final LinkedList> registeredDrops = new LinkedList<>(); - private final Map mobLootEntries = new HashMap(20); private final List statUpdateRunnables = new ArrayList(50); private final List areas = new ArrayList<>(); private FootholdTree footholds = null; @@ -160,7 +158,6 @@ public class MapleMap { private MonsterAggroCoordinator aggroMonitor = null; // aggroMonitor activity in sync with itemMonitor private ScheduledFuture itemMonitor = null; private ScheduledFuture expireItemsTask = null; - private ScheduledFuture mobSpawnLootTask = null; private ScheduledFuture characterStatUpdateTask = null; private short itemMonitorTimeout; private Pair timeMob = null; @@ -654,9 +651,10 @@ private static void sortDropEntries(List from, List dropEntry, Point pos, byte d, int chRate, byte droptype, int mobpos, Character chr, Monster mob) { + private byte dropItemsFromMonsterOnMap(List dropEntry, Point pos, byte index, int chRate, + byte droptype, int mobpos, Character chr, Monster mob, short delay) { if (dropEntry.isEmpty()) { - return d; + return index; } Collections.shuffle(dropEntry); @@ -670,9 +668,9 @@ private byte dropItemsFromMonsterOnMap(List dropEntry, Point p if (Randomizer.nextInt(999999) < dropChance) { if (droptype == 3) { - pos.x = mobpos + ((d % 2 == 0) ? (40 * ((d + 1) / 2)) : -(40 * (d / 2))); + pos.x = mobpos + ((index % 2 == 0) ? (40 * ((index + 1) / 2)) : -(40 * (index / 2))); } else { - pos.x = mobpos + ((d % 2 == 0) ? (25 * ((d + 1) / 2)) : -(25 * (d / 2))); + pos.x = mobpos + ((index % 2 == 0) ? (25 * ((index + 1) / 2)) : -(25 * (index / 2))); } if (de.itemId == 0) { // meso int mesos = Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum; @@ -686,7 +684,8 @@ private byte dropItemsFromMonsterOnMap(List dropEntry, Point p mesos = Integer.MAX_VALUE; } - spawnMesoDrop(mesos, calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype); + spawnMesoDrop(mesos, calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype, + delay); } } else { if (ItemConstants.getInventoryType(de.itemId) == InventoryType.EQUIP) { @@ -694,16 +693,17 @@ private byte dropItemsFromMonsterOnMap(List dropEntry, Point p } else { idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1)); } - spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid); + spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid, delay); } - d++; + index++; } } - return d; + return index; } - private byte dropGlobalItemsFromMonsterOnMap(List globalEntry, Point pos, byte d, byte droptype, int mobpos, Character chr, Monster mob) { + private byte dropGlobalItemsFromMonsterOnMap(List globalEntry, Point pos, byte d, + byte droptype, int mobpos, Character chr, Monster mob, short delay) { Collections.shuffle(globalEntry); Item idrop; @@ -722,7 +722,7 @@ private byte dropGlobalItemsFromMonsterOnMap(List global } else { idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1)); } - spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid); + spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid, delay); d++; } } @@ -731,7 +731,7 @@ private byte dropGlobalItemsFromMonsterOnMap(List global return d; } - private void dropFromMonster(final Character chr, final Monster mob, final boolean useBaseRate) { + private void dropFromMonster(final Character chr, final Monster mob, final boolean useBaseRate, short delay) { if (mob.dropsDisabled() || !dropsOn) { return; } @@ -739,7 +739,6 @@ private void dropFromMonster(final Character chr, final Monster mob, final boole final byte droptype = (byte) (mob.getStats().isExplosiveReward() ? 3 : mob.getStats().isFfaLoot() ? 2 : chr.getParty() != null ? 1 : 0); final int mobpos = mob.getPosition().x; int chRate = !mob.isBoss() ? chr.getDropRate() : chr.getBossDropRate(); - byte d = 1; Point pos = new Point(0, mob.getPosition().y); MonsterStatusEffect stati = mob.getStati(MonsterStatus.SHOWDOWN); @@ -765,10 +764,20 @@ private void dropFromMonster(final Character chr, final Monster mob, final boole return; } - registerMobItemDrops(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob); + + byte index = 1; + // Normal Drops + index = dropItemsFromMonsterOnMap(dropEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay); + + // Global Drops + index = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, index, droptype, mobpos, chr, mob, delay); + + // Quest Drops + index = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay); + dropItemsFromMonsterOnMap(otherQuestEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay); } - public void dropItemsFromMonster(List list, final Character chr, final Monster mob) { + public void dropItemsFromMonster(List list, final Character chr, final Monster mob, short delay) { if (mob.dropsDisabled() || !dropsOn) { return; } @@ -779,15 +788,17 @@ public void dropItemsFromMonster(List list, final Character ch byte d = 1; Point pos = new Point(0, mob.getPosition().y); - dropItemsFromMonsterOnMap(list, pos, d, chRate, droptype, mobpos, chr, mob); + dropItemsFromMonsterOnMap(list, pos, d, chRate, droptype, mobpos, chr, mob, delay); } public void dropFromFriendlyMonster(final Character chr, final Monster mob) { - dropFromMonster(chr, mob, true); + dropFromMonster(chr, mob, true, (short) 0); } - public void dropFromReactor(final Character chr, final Reactor reactor, Item drop, Point dropPos, short questid) { - spawnDrop(drop, this.calcDropPos(dropPos, reactor.getPosition()), reactor, chr, (byte) (chr.getParty() != null ? 1 : 0), questid); + public void dropFromReactor(final Character chr, final Reactor reactor, Item drop, Point dropPos, short questid, + short delay) { + spawnDrop(drop, this.calcDropPos(dropPos, reactor.getPosition()), reactor, chr, + (byte) (chr.getParty() != null ? 1 : 0), questid, delay); } private void stopItemMonitor() { @@ -797,11 +808,6 @@ private void stopItemMonitor() { expireItemsTask.cancel(false); expireItemsTask = null; - if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) { - mobSpawnLootTask.cancel(false); - mobSpawnLootTask = null; - } - characterStatUpdateTask.cancel(false); characterStatUpdateTask = null; } @@ -858,17 +864,6 @@ private void startItemMonitor() { expireItemsTask = TimerManager.getInstance().register(() -> makeDisappearExpiredItemDrops(), YamlConfig.config.server.ITEM_EXPIRE_CHECK, YamlConfig.config.server.ITEM_EXPIRE_CHECK); - if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) { - lootLock.lock(); - try { - mobLootEntries.clear(); - } finally { - lootLock.unlock(); - } - - mobSpawnLootTask = TimerManager.getInstance().register(() -> spawnMobItemDrops(), 200, 200); - } - characterStatUpdateTask = TimerManager.getInstance().register(() -> runCharacterStatUpdate(), 200, 200); itemMonitorTimeout = 1; @@ -965,63 +960,6 @@ private void makeDisappearExpiredItemDrops() { } } - private void registerMobItemDrops(byte droptype, int mobpos, int chRate, Point pos, List dropEntry, List visibleQuestEntry, List otherQuestEntry, List globalEntry, Character chr, Monster mob) { - MobLootEntry mle = new MobLootEntry(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob); - - if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) { - int animationTime = mob.getAnimationTime("die1"); - - lootLock.lock(); - try { - long timeNow = Server.getInstance().getCurrentTime(); - mobLootEntries.put(mle, timeNow + ((long) (0.42 * animationTime))); - } finally { - lootLock.unlock(); - } - } else { - mle.run(); - } - } - - private void spawnMobItemDrops() { - Set> mleList; - - lootLock.lock(); - try { - mleList = new HashSet<>(mobLootEntries.entrySet()); - } finally { - lootLock.unlock(); - } - - long timeNow = Server.getInstance().getCurrentTime(); - List toRemove = new LinkedList<>(); - for (Entry mlee : mleList) { - if (mlee.getValue() < timeNow) { - toRemove.add(mlee.getKey()); - } - } - - if (!toRemove.isEmpty()) { - List toSpawnLoot = new LinkedList<>(); - - lootLock.lock(); - try { - for (MobLootEntry mle : toRemove) { - Long mler = mobLootEntries.remove(mle); - if (mler != null) { - toSpawnLoot.add(mle); - } - } - } finally { - lootLock.unlock(); - } - - for (MobLootEntry mle : toSpawnLoot) { - mle.run(); - } - } - } - private List getDroppedItems() { objectRLock.lock(); try { @@ -1123,7 +1061,8 @@ public void updatePartyItemDropsToNewcomer(Character newcomer, List par } } - private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dropper, final Character chr, final byte droptype, final short questid) { + private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dropper, final Character chr, + final byte droptype, final short questid, short delay) { final MapItem mdrop = new MapItem(idrop, dropPos, dropper, chr, chr.getClient(), droptype, false, questid); mdrop.setDropTime(Server.getInstance().getCurrentTime()); spawnAndAddRangedMapObject(mdrop, c -> { @@ -1132,7 +1071,8 @@ private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dr if (chr1.needQuestItem(questid, idrop.getItemId())) { mdrop.lockItem(); try { - c.sendPacket(PacketCreator.dropItemFromMapObject(chr1, mdrop, dropper.getPosition(), dropPos, (byte) 1)); + c.sendPacket(PacketCreator.dropItemFromMapObject(chr1, mdrop, dropper.getPosition(), dropPos, + (byte) 1, delay)); } finally { mdrop.unlockItem(); } @@ -1143,7 +1083,8 @@ private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dr activateItemReactors(mdrop, chr.getClient()); } - public final void spawnMesoDrop(final int meso, final Point position, final MapObject dropper, final Character owner, final boolean playerDrop, final byte droptype) { + public final void spawnMesoDrop(final int meso, final Point position, final MapObject dropper, + final Character owner, final boolean playerDrop, final byte droptype, short delay) { final Point droppos = calcDropPos(position, position); final MapItem mdrop = new MapItem(meso, droppos, dropper, owner, owner.getClient(), droptype, playerDrop); mdrop.setDropTime(Server.getInstance().getCurrentTime()); @@ -1151,7 +1092,8 @@ public final void spawnMesoDrop(final int meso, final Point position, final MapO spawnAndAddRangedMapObject(mdrop, c -> { mdrop.lockItem(); try { - c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1)); + c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, + (byte) 1, delay)); } finally { mdrop.unlockItem(); } @@ -1166,7 +1108,7 @@ public final void disappearingItemDrop(final MapObject dropper, final Character mdrop.lockItem(); try { - broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition()); + broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, (short) 0, mdrop.getPosition()); } finally { mdrop.unlockItem(); } @@ -1178,7 +1120,7 @@ public final void disappearingMesoDrop(final int meso, final MapObject dropper, mdrop.lockItem(); try { - broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition()); + broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, (short) 0, mdrop.getPosition()); } finally { mdrop.unlockItem(); } @@ -1326,7 +1268,11 @@ public int countBosses() { return count; } - public boolean damageMonster(final Character chr, final Monster monster, final int damage) { + public boolean damageMonster(Character chr, Monster monster, int damage) { + return damageMonster(chr, monster, damage, (short) 0); + } + + public boolean damageMonster(final Character chr, final Monster monster, final int damage, short delay) { if (monster.getId() == MobId.ZAKUM_1) { for (MapObject object : chr.getMap().getMapObjects()) { Monster mons = chr.getMap().getMonsterByOid(object.getObjectId()); @@ -1337,22 +1283,23 @@ public boolean damageMonster(final Character chr, final Monster monster, final i } } } - if (monster.isAlive()) { - boolean killed = monster.damage(chr, damage, false); + if (!monster.isAlive()) { + return false; + } - selfDestruction selfDestr = monster.getStats().selfDestruction(); - if (selfDestr != null && selfDestr.getHp() > -1) {// should work ;p - if (monster.getHp() <= selfDestr.getHp()) { - killMonster(monster, chr, true, selfDestr.getAction()); - return true; - } - } - if (killed) { - killMonster(monster, chr, true); + boolean killed = monster.damage(chr, damage, false); + + selfDestruction selfDestr = monster.getStats().selfDestruction(); + if (selfDestr != null && selfDestr.getHp() > -1) {// should work ;p + if (monster.getHp() <= selfDestr.getHp()) { + killMonster(monster, chr, true, selfDestr.getAction()); + return true; } - return true; } - return false; + if (killed) { + killMonster(monster, chr, true, delay); + } + return true; } public void broadcastBalrogVictory(String leaderName) { @@ -1391,11 +1338,12 @@ private boolean removeKilledMonsterObject(Monster monster) { } } - public void killMonster(final Monster monster, final Character chr, final boolean withDrops) { - killMonster(monster, chr, withDrops, 1); + public void killMonster(final Monster monster, final Character chr, final boolean withDrops, short dropDelay) { + killMonster(monster, chr, withDrops, 1, dropDelay); } - public void killMonster(final Monster monster, final Character chr, final boolean withDrops, int animation) { + public void killMonster(final Monster monster, final Character chr, final boolean withDrops, int animation, + short dropDelay) { if (monster == null) { return; } @@ -1406,12 +1354,17 @@ public void killMonster(final Monster monster, final Character chr, final boolea broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); monster.aggroSwitchController(null, false); } - } else { - if (removeKilledMonsterObject(monster)) { - try { - if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) { - AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher."); - } + return; + } + + if (!removeKilledMonsterObject(monster)) { + return; + } + + try { + if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) { + AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher."); + } /*if (chr.getQuest(Quest.getInstance(29400)).getStatus().equals(QuestStatus.Status.STARTED)) { if (chr.getLevel() >= 120 && monster.getStats().getLevel() >= 120) { @@ -1420,78 +1373,78 @@ public void killMonster(final Monster monster, final Character chr, final boolea } }*/ - if (monster.getCP() > 0 && chr.getMap().isCPQMap()) { - chr.gainCP(monster.getCP()); - } + if (monster.getCP() > 0 && chr.getMap().isCPQMap()) { + chr.gainCP(monster.getCP()); + } - int buff = monster.getBuffToGive(); - if (buff > -1) { - ItemInformationProvider mii = ItemInformationProvider.getInstance(); - for (MapObject mmo : this.getPlayers()) { - Character character = (Character) mmo; - if (character.isAlive()) { - StatEffect statEffect = mii.getItemEffect(buff); - character.sendPacket(PacketCreator.showOwnBuffEffect(buff, 1)); - broadcastMessage(character, PacketCreator.showBuffEffect(character.getId(), buff, 1), false); - statEffect.applyTo(character); - } - } + int buff = monster.getBuffToGive(); + if (buff > -1) { + ItemInformationProvider mii = ItemInformationProvider.getInstance(); + for (MapObject mmo : this.getPlayers()) { + Character character = (Character) mmo; + if (character.isAlive()) { + StatEffect statEffect = mii.getItemEffect(buff); + character.sendPacket(PacketCreator.showOwnBuffEffect(buff, 1)); + broadcastMessage(character, PacketCreator.showBuffEffect(character.getId(), buff, 1), false); + statEffect.applyTo(character); } + } + } - if (MobId.isZakumArm(monster.getId())) { - boolean makeZakReal = true; - Collection objects = getMapObjects(); - for (MapObject object : objects) { - Monster mons = getMonsterByOid(object.getObjectId()); - if (mons != null) { - if (MobId.isZakumArm(mons.getId())) { - makeZakReal = false; - break; - } - } + if (MobId.isZakumArm(monster.getId())) { + boolean makeZakReal = true; + Collection objects = getMapObjects(); + for (MapObject object : objects) { + Monster mons = getMonsterByOid(object.getObjectId()); + if (mons != null) { + if (MobId.isZakumArm(mons.getId())) { + makeZakReal = false; + break; } - if (makeZakReal) { - MapleMap map = chr.getMap(); - - for (MapObject object : objects) { - Monster mons = map.getMonsterByOid(object.getObjectId()); - if (mons != null) { - if (mons.getId() == MobId.ZAKUM_1) { - makeMonsterReal(mons); - break; - } - } + } + } + if (makeZakReal) { + MapleMap map = chr.getMap(); + + for (MapObject object : objects) { + Monster mons = map.getMonsterByOid(object.getObjectId()); + if (mons != null) { + if (mons.getId() == MobId.ZAKUM_1) { + makeMonsterReal(mons); + break; } } } + } + } - Character dropOwner = monster.killBy(chr); - if (withDrops && !monster.dropsDisabled()) { - if (dropOwner == null) { - dropOwner = chr; - } - dropFromMonster(dropOwner, monster, false); - } + Character dropOwner = monster.killBy(chr); + if (withDrops && !monster.dropsDisabled()) { + if (dropOwner == null) { + dropOwner = chr; + } + dropFromMonster(dropOwner, monster, false, dropDelay); + } - if (monster.hasBossHPBar()) { - for (Character mc : this.getAllPlayers()) { - if (mc.getTargetHpBarHash() == monster.hashCode()) { - mc.resetPlayerAggro(); - } - } + if (monster.hasBossHPBar()) { + for (Character mc : this.getAllPlayers()) { + if (mc.getTargetHpBarHash() == monster.hashCode()) { + mc.resetPlayerAggro(); } - } catch (Exception e) { - e.printStackTrace(); - } finally { // thanks resinate for pointing out a memory leak possibly from an exception thrown - monster.dispatchMonsterKilled(true); - broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); } } + } catch (Exception e) { + e.printStackTrace(); + } finally { // thanks resinate for pointing out a memory leak possibly from an exception thrown + monster.dispatchMonsterKilled(true); + broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); } + + } public void killFriendlies(Monster mob) { - this.killMonster(mob, (Character) getPlayers().get(0), false); + this.killMonster(mob, (Character) getPlayers().get(0), false, (short) 0); } public void killMonster(int mobId) { @@ -1500,7 +1453,7 @@ public void killMonster(int mobId) { for (Monster mob : mobList) { if (mob.getId() == mobId) { - this.killMonster(mob, chr, false); + this.killMonster(mob, chr, false, (short) 0); } } } @@ -1519,7 +1472,7 @@ public void killMonsterWithDrops(int mobId) { chr = defaultChr; } - this.killMonster(mob, chr, true); + this.killMonster(mob, chr, true, (short) 0); } } } @@ -1549,7 +1502,7 @@ public void killAllMonstersNotFriendly() { continue; } - killMonster(monster, null, false, 1); + killMonster(monster, null, false, 1, (short) 0); } } @@ -1559,7 +1512,7 @@ public void killAllMonsters() { for (MapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapObjectType.MONSTER))) { Monster monster = (Monster) monstermo; - killMonster(monster, null, false, 1); + killMonster(monster, null, false, 1, (short) 0); } } @@ -1906,7 +1859,7 @@ private void applyRemoveAfter(final Monster monster) { Runnable removeAfterAction; if (selfDestruction == null) { - removeAfterAction = () -> killMonster(monster, null, false); + removeAfterAction = () -> killMonster(monster, null, false, (short) 0); registerMapSchedule(removeAfterAction, SECONDS.toMillis(monster.getStats().removeAfter())); } else { @@ -2154,11 +2107,13 @@ public void spawnKite(final Kite kite) { getWorldServer().registerTimedMapObject(expireKite, YamlConfig.config.server.KITE_EXPIRE_TIME); } - public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, final boolean ffaDrop, final boolean playerDrop) { + public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, + final boolean ffaDrop, final boolean playerDrop) { spawnItemDrop(dropper, owner, item, pos, (byte) (ffaDrop ? 2 : 0), playerDrop); } - public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, final byte dropType, final boolean playerDrop) { + public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, + final byte dropType, final boolean playerDrop) { if (FieldLimit.DROP_LIMIT.check(this.getFieldLimit())) { // thanks Conrad for noticing some maps shouldn't have loots available this.disappearingItemDrop(dropper, owner, item, pos); return; @@ -2171,7 +2126,8 @@ public final void spawnItemDrop(final MapObject dropper, final Character owner, spawnAndAddRangedMapObject(mdrop, c -> { mdrop.lockItem(); try { - c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1)); + c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, + (byte) 1, (short) 0)); } finally { mdrop.unlockItem(); } @@ -2179,7 +2135,7 @@ public final void spawnItemDrop(final MapObject dropper, final Character owner, mdrop.lockItem(); try { - broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 0); + broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 0, (short) 0); } finally { mdrop.unlockItem(); } @@ -2188,49 +2144,6 @@ public final void spawnItemDrop(final MapObject dropper, final Character owner, activateItemReactors(mdrop, owner.getClient()); } - public final void spawnItemDropList(List list, final MapObject dropper, final Character owner, Point pos) { - spawnItemDropList(list, 1, 1, dropper, owner, pos, true, false); - } - - public final void spawnItemDropList(List list, int minCopies, int maxCopies, final MapObject dropper, final Character owner, Point pos) { - spawnItemDropList(list, minCopies, maxCopies, dropper, owner, pos, true, false); - } - - // spawns item instances of all defined item ids on a list - public final void spawnItemDropList(List list, int minCopies, int maxCopies, final MapObject dropper, final Character owner, Point pos, final boolean ffaDrop, final boolean playerDrop) { - int copies = (maxCopies - minCopies) + 1; - if (copies < 1) { - return; - } - - Collections.shuffle(list); - - ItemInformationProvider ii = ItemInformationProvider.getInstance(); - Random rnd = new Random(); - - final Point dropPos = new Point(pos); - dropPos.x -= (12 * list.size()); - - for (Integer integer : list) { - if (integer == 0) { - spawnMesoDrop(owner != null ? 10 * owner.getMesoRate() : 10, calcDropPos(dropPos, pos), dropper, owner, playerDrop, (byte) (ffaDrop ? 2 : 0)); - } else { - final Item drop; - int randomedId = integer; - - if (ItemConstants.getInventoryType(randomedId) != InventoryType.EQUIP) { - drop = new Item(randomedId, (short) 0, (short) (rnd.nextInt(copies) + minCopies)); - } else { - drop = ii.randomizeStats((Equip) ii.getEquipById(randomedId)); - } - - spawnItemDrop(dropper, owner, drop, calcDropPos(dropPos, pos), ffaDrop, playerDrop); - } - - dropPos.x += 25; - } - } - private void registerMapSchedule(Runnable r, long delay) { OverallService service = (OverallService) this.getChannelServer().getServiceAccess(ChannelServices.OVERALL); service.registerOverallAction(mapid, r, delay); @@ -2861,20 +2774,23 @@ private void broadcastBossHpMessage(Monster mm, int bossHash, Character source, } } - private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, Point rangedFrom) { - broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, getRangedDistance(), rangedFrom); + private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay, + Point rangedFrom) { + broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, delay, getRangedDistance(), rangedFrom); } - private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod) { - broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, Double.POSITIVE_INFINITY, null); + private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay) { + broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, delay, Double.POSITIVE_INFINITY, null); } - private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, double rangeSq, Point rangedFrom) { + private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay, + double rangeSq, Point rangedFrom) { chrRLock.lock(); try { for (Character chr : characters) { - Packet packet = PacketCreator.dropItemFromMapObject(chr, mdrop, dropperPos, dropPos, mod); + Packet packet = PacketCreator.dropItemFromMapObject(chr, mdrop, dropperPos, dropPos, mod, delay); + // TODO: remove along with USE_MAXRANGE config if (rangeSq < Double.POSITIVE_INFINITY) { if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) { chr.sendPacket(packet); @@ -3399,12 +3315,14 @@ public boolean makeDisappearItemFromMap(MapItem mapitem) { return false; } + // TODO: no reason to implement runnable - this is not intended to be submitted to another thread private class MobLootEntry implements Runnable { private final byte droptype; private final int mobpos; private final int chRate; private final Point pos; + private final short delay; private final List dropEntry; private final List visibleQuestEntry; private final List otherQuestEntry; @@ -3412,11 +3330,15 @@ private class MobLootEntry implements Runnable { private final Character chr; private final Monster mob; - protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, List dropEntry, List visibleQuestEntry, List otherQuestEntry, List globalEntry, Character chr, Monster mob) { + protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, short delay, + List dropEntry, List visibleQuestEntry, + List otherQuestEntry, List globalEntry, + Character chr, Monster mob) { this.droptype = droptype; this.mobpos = mobpos; this.chRate = chRate; this.pos = pos; + this.delay = delay; this.dropEntry = dropEntry; this.visibleQuestEntry = visibleQuestEntry; this.otherQuestEntry = otherQuestEntry; @@ -3430,14 +3352,14 @@ public void run() { byte d = 1; // Normal Drops - d = dropItemsFromMonsterOnMap(dropEntry, pos, d, chRate, droptype, mobpos, chr, mob); + d = dropItemsFromMonsterOnMap(dropEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay); // Global Drops - d = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, d, droptype, mobpos, chr, mob); + d = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, d, droptype, mobpos, chr, mob, delay); // Quest Drops - d = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob); - dropItemsFromMonsterOnMap(otherQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob); + d = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay); + dropItemsFromMonsterOnMap(otherQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay); } } @@ -4457,11 +4379,6 @@ public void dispose() { expireItemsTask = null; } - if (mobSpawnLootTask != null) { - mobSpawnLootTask.cancel(false); - mobSpawnLootTask = null; - } - if (characterStatUpdateTask != null) { characterStatUpdateTask.cancel(false); characterStatUpdateTask = null; diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 2c93703dd8f..6fa832de5b8 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -56,6 +56,7 @@ import constants.id.NpcId; import constants.inventory.ItemConstants; import constants.skills.Buccaneer; +import constants.skills.ChiefBandit; import constants.skills.Corsair; import constants.skills.ThunderBreaker; import net.encryption.InitializationVector; @@ -67,8 +68,9 @@ import net.server.PlayerCoolDownValueHolder; import net.server.Server; import net.server.channel.Channel; +import net.server.channel.handlers.AbstractDealDamageHandler.AttackTarget; import net.server.channel.handlers.PlayerInteractionHandler; -import net.server.channel.handlers.SummonDamageHandler.SummonAttackEntry; +import net.server.channel.handlers.SummonDamageHandler.SummonAttackTarget; import net.server.channel.handlers.WhisperHandler; import net.server.guild.Alliance; import net.server.guild.Guild; @@ -1813,7 +1815,8 @@ public static Packet updateMapItemObject(MapItem drop, boolean giveOwnership) { return p; } - public static Packet dropItemFromMapObject(Character player, MapItem drop, Point dropfrom, Point dropto, byte mod) { + public static Packet dropItemFromMapObject(Character player, MapItem drop, Point dropfrom, Point dropto, byte mod, + short delay) { int dropType = drop.getDropType(); if (drop.hasClientsideOwnership(player) && dropType < 3) { dropType = 2; @@ -1831,7 +1834,7 @@ public static Packet dropItemFromMapObject(Character player, MapItem drop, Point if (mod != 2) { p.writePos(dropfrom); - p.writeShort(0);//Fh? + p.writeShort(delay); } if (drop.getMeso() == 0) { addExpirationTime(p, drop.getItem().getExpiration()); @@ -2301,18 +2304,18 @@ public static Packet moveMonster(int oid, boolean skillPossible, int skill, int return p; } - public static Packet summonAttack(int cid, int summonOid, byte direction, List allDamage) { + public static Packet summonAttack(int cid, int summonOid, byte direction, List targets) { OutPacket p = OutPacket.create(SendOpcode.SUMMON_ATTACK); //b2 00 29 f7 00 00 9a a3 04 00 c8 04 01 94 a3 04 00 06 ff 2b 00 p.writeInt(cid); p.writeInt(summonOid); p.writeByte(0); // char level p.writeByte(direction); - p.writeByte(allDamage.size()); - for (SummonAttackEntry attackEntry : allDamage) { - p.writeInt(attackEntry.getMonsterOid()); // oid + p.writeByte(targets.size()); + for (SummonAttackTarget target : targets) { + p.writeInt(target.monsterOid()); // oid p.writeByte(6); // who knows - p.writeInt(attackEntry.getDamage()); // damage + p.writeInt(target.damage()); // damage } return p; @@ -2337,29 +2340,40 @@ public static Packet summonAttack(int cid, int summonSkillId, byte direction, Li } */ - public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map> damage, int speed, int direction, int display) { + public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance, + int numAttackedAndDamage, Map targets, int speed, + int direction, int display) { final OutPacket p = OutPacket.create(SendOpcode.CLOSE_RANGE_ATTACK); - addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display); + addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, targets, speed, direction, + display); return p; } - public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map> damage, int speed, int direction, int display) { + public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, + int projectile, Map targets, int speed, int direction, + int display) { final OutPacket p = OutPacket.create(SendOpcode.RANGED_ATTACK); - addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, projectile, damage, speed, direction, display); + addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, projectile, targets, speed, direction, + display); p.writeInt(0); return p; } - public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map> damage, int charge, int speed, int direction, int display) { + public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, + Map targets, int charge, int speed, int direction, + int display) { final OutPacket p = OutPacket.create(SendOpcode.MAGIC_ATTACK); - addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display); + addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, targets, speed, direction, + display); if (charge != -1) { p.writeInt(charge); } return p; } - private static void addAttackBody(OutPacket p, Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map> damage, int speed, int direction, int display) { + private static void addAttackBody(OutPacket p, Character chr, int skill, int skilllevel, int stance, + int numAttackedAndDamage, int projectile, Map targets, + int speed, int direction, int display) { p.writeInt(chr.getId()); p.writeByte(numAttackedAndDamage); p.writeByte(0x5B);//? @@ -2373,16 +2387,16 @@ private static void addAttackBody(OutPacket p, Character chr, int skill, int ski p.writeByte(speed); p.writeByte(0x0A); p.writeInt(projectile); - for (Integer oned : damage.keySet()) { - List onedList = damage.get(oned); - if (onedList != null) { - p.writeInt(oned); + for (Map.Entry target : targets.entrySet()) { + AttackTarget value = target.getValue(); + if (value != null) { + p.writeInt(target.getKey()); p.writeByte(0x0); - if (skill == 4211006) { - p.writeByte(onedList.size()); + if (skill == ChiefBandit.MESO_EXPLOSION) { + p.writeByte(value.damageLines().size()); } - for (Integer eachd : onedList) { - p.writeInt(eachd); + for (Integer damageLine : value.damageLines()) { + p.writeInt(damageLine); } } } @@ -2593,6 +2607,14 @@ public static Packet removeItemFromMap(int objId, int animation, int chrId, bool return p; } + public static Packet removeExplodedMesoFromMap(int mapObjectId, short delay) { + OutPacket p = OutPacket.create(SendOpcode.REMOVE_ITEM_FROM_MAP); + p.writeByte(4); + p.writeInt(mapObjectId); + p.writeShort(delay); + return p; + } + public static Packet updateCharLook(Client target, Character chr) { OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_LOOK); p.writeInt(chr.getId());