Merge pull request #262 from P0nk/fix/drop-delay #patch

Proper delay for item drops
This commit is contained in:
Ponk
2024-08-20 20:38:27 +02:00
committed by GitHub
16 changed files with 421 additions and 478 deletions

View File

@@ -249,7 +249,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.

View File

@@ -97,7 +97,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;

View File

@@ -96,7 +96,6 @@ import server.life.MonsterDropEntry;
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<Integer, List<Integer>> allDamage;
public Map<Integer, AttackTarget> targets;
public boolean ranged, magic;
public int speed = 4;
public Point position = new Point();
public List<Integer> explodedMesos;
public Short attackDelay;
public StatEffect getAttackEffect(Character chr, Skill theSkill) {
Skill mySkill = theSkill;
@@ -146,6 +147,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
}
}
// TODO: add position
public record AttackTarget(short delay, List<Integer> damageLines) {}
protected void applyAttack(AttackInfo attack, final Character player, int attackCount) {
final MapleMap map = player.getMap();
if (map.isOwnershipRestricted(player)) {
@@ -207,50 +211,14 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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<Integer, AttackTarget> 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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
}
int totDamageToOneMonster = 0;
List<Integer> onedList = attack.allDamage.get(oned);
List<Integer> 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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
List<MonsterDropEntry> 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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
}
}
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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<Integer> 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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
}
for (int i = 0; i < ret.numAttacked; i++) {
int oid = p.readInt();
p.skip(14);
List<Integer> 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<Integer> 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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
}
}
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 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
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<Integer, List<Integer>> 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<Integer> 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<Integer> 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<Integer, AttackTarget> 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;
}
}
}

View File

@@ -133,7 +133,7 @@ public final class AdminCommandHandler extends AbstractPacketHandler {
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;

View File

@@ -78,7 +78,9 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
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 class CloseRangeDamageHandler extends AbstractDealDamageHandler {
}
if (attack.numAttacked > 0 && attack.skill == DragonKnight.SACRIFICE) {
int totDamageToOneMonster = 0; // sacrifice attacks only 1 mob with 1 attack
final Iterator<List<Integer>> dmgIt = attack.allDamage.values().iterator();
final Iterator<AttackTarget> 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);

View File

@@ -66,7 +66,8 @@ public final class MagicDamageHandler extends AbstractDealDamageHandler {
}
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 class MagicDamageHandler extends AbstractDealDamageHandler {
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);
}
}
}

View File

@@ -67,7 +67,8 @@ public final class MesoDropHandler extends AbstractPacketHandler {
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);
}
}
}
}

View File

@@ -57,21 +57,24 @@ public final class MobDamageMobHandler extends AbstractPacketHandler {
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) {

View File

@@ -83,17 +83,23 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
}
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 final class RangedAttackHandler extends AbstractDealDamageHandler {
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);

View File

@@ -41,31 +41,14 @@ import server.life.MonsterInformationProvider;
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 final class SummonDamageHandler extends AbstractDealDamageHandler {
Skill summonSkill = SkillFactory.getSkill(summon.getSkill());
StatEffect summonEffect = summonSkill.getEffect(summon.getSkillLevel());
p.skip(4);
List<SummonAttackEntry> allDamage = new ArrayList<>();
List<SummonAttackTarget> 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 final class SummonDamageHandler extends AbstractDealDamageHandler {
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;
}
if (damage > 0 && summonEffect.getMonsterStati().size() > 0) {
if (summonEffect.makeChanceResult()) {
target.applyStatus(player, new MonsterStatusEffect(summonEffect.getMonsterStati(), summonSkill, null, false), summonEffect.isPoison(), 4000);
}
}
player.getMap().damageMonster(player, target, damage);
for (SummonAttackTarget target : targets) {
int damage = target.damage();
Monster mob = player.getMap().getMonsterByOid(target.monsterOid());
if (mob == null) {
continue;
}
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, mob, damage, target.delay());
}
if (summon.getSkill() == Outlaw.GAVIOTA) { // thanks Periwinks for noticing Gaviota not cancelling after grenade toss

View File

@@ -61,7 +61,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
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 class UseCatchItemHandler extends AbstractPacketHandler {
// System.out.println("UseCatchItemHandler: \r\n" + slea.toString());
}
}
private static void killMonster(Monster mob) {
mob.getMap().killMonster(mob, null, false, (short) 0);
}
}

View File

@@ -33,6 +33,7 @@ import server.TimerManager;
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.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
/**
* @author Lerk
@@ -52,7 +52,6 @@ import java.util.concurrent.ScheduledFuture;
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 class ReactorActionManager extends AbstractPlayerInteraction {
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 class ReactorActionManager extends AbstractPlayerInteraction {
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<ReactorDropEntry> 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 class ReactorActionManager extends AbstractPlayerInteraction {
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 class ReactorActionManager extends AbstractPlayerInteraction {
getPlayer().getMap().getBlueTeamBuffs().remove(skil);
}
}
}
}

View File

@@ -825,12 +825,12 @@ public class Monster extends AbstractLoadedLife {
}
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);

View File

@@ -208,7 +208,8 @@ public class MapItem extends AbstractMapObject {
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 class MapItem extends AbstractMapObject {
public void sendDestroyData(final Client client) {
client.sendPacket(PacketCreator.removeItemFromMap(getObjectId(), 1, 0));
}
}
}

View File

@@ -82,7 +82,6 @@ import java.util.Calendar;
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<String, Integer> environment = new LinkedHashMap<>();
private final Map<MapItem, Long> droppedItems = new LinkedHashMap<>();
private final LinkedList<WeakReference<MapObject>> registeredDrops = new LinkedList<>();
private final Map<MobLootEntry, Long> mobLootEntries = new HashMap(20);
private final List<Runnable> statUpdateRunnables = new ArrayList(50);
private final List<Rectangle> 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<Integer, String> timeMob = null;
@@ -654,9 +651,10 @@ public class MapleMap {
}
}
private byte dropItemsFromMonsterOnMap(List<MonsterDropEntry> dropEntry, Point pos, byte d, int chRate, byte droptype, int mobpos, Character chr, Monster mob) {
private byte dropItemsFromMonsterOnMap(List<MonsterDropEntry> 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 @@ public class MapleMap {
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 @@ public class MapleMap {
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 @@ public class MapleMap {
} 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<MonsterGlobalDropEntry> globalEntry, Point pos, byte d, byte droptype, int mobpos, Character chr, Monster mob) {
private byte dropGlobalItemsFromMonsterOnMap(List<MonsterGlobalDropEntry> globalEntry, Point pos, byte d,
byte droptype, int mobpos, Character chr, Monster mob, short delay) {
Collections.shuffle(globalEntry);
Item idrop;
@@ -722,7 +722,7 @@ public class MapleMap {
} 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 @@ public class MapleMap {
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 @@ public class MapleMap {
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 @@ public class MapleMap {
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<MonsterDropEntry> list, final Character chr, final Monster mob) {
public void dropItemsFromMonster(List<MonsterDropEntry> list, final Character chr, final Monster mob, short delay) {
if (mob.dropsDisabled() || !dropsOn) {
return;
}
@@ -779,15 +788,17 @@ public class MapleMap {
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 @@ public class MapleMap {
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 @@ public class MapleMap {
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 @@ public class MapleMap {
}
}
private void registerMobItemDrops(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> 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<Entry<MobLootEntry, Long>> mleList;
lootLock.lock();
try {
mleList = new HashSet<>(mobLootEntries.entrySet());
} finally {
lootLock.unlock();
}
long timeNow = Server.getInstance().getCurrentTime();
List<MobLootEntry> toRemove = new LinkedList<>();
for (Entry<MobLootEntry, Long> mlee : mleList) {
if (mlee.getValue() < timeNow) {
toRemove.add(mlee.getKey());
}
}
if (!toRemove.isEmpty()) {
List<MobLootEntry> 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<MapItem> getDroppedItems() {
objectRLock.lock();
try {
@@ -1123,7 +1061,8 @@ public class MapleMap {
}
}
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 @@ public class MapleMap {
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 @@ public class MapleMap {
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 class MapleMap {
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 class MapleMap {
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 class MapleMap {
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 class MapleMap {
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 class MapleMap {
}
}
}
if (monster.isAlive()) {
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;
}
}
if (killed) {
killMonster(monster, chr, true);
}
return true;
if (!monster.isAlive()) {
return false;
}
return false;
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;
}
}
if (killed) {
killMonster(monster, chr, true, delay);
}
return true;
}
public void broadcastBalrogVictory(String leaderName) {
@@ -1391,11 +1338,12 @@ public class MapleMap {
}
}
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 class MapleMap {
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 class MapleMap {
}
}*/
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<MapObject> 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;
}
}
}
}
}
Character dropOwner = monster.killBy(chr);
if (withDrops && !monster.dropsDisabled()) {
if (dropOwner == null) {
dropOwner = chr;
}
dropFromMonster(dropOwner, monster, false);
}
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());
}
}
if (MobId.isZakumArm(monster.getId())) {
boolean makeZakReal = true;
Collection<MapObject> 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;
}
}
}
}
}
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();
}
}
}
} 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 class MapleMap {
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 class MapleMap {
chr = defaultChr;
}
this.killMonster(mob, chr, true);
this.killMonster(mob, chr, true, (short) 0);
}
}
}
@@ -1549,7 +1502,7 @@ public class MapleMap {
continue;
}
killMonster(monster, null, false, 1);
killMonster(monster, null, false, 1, (short) 0);
}
}
@@ -1559,7 +1512,7 @@ public class MapleMap {
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 @@ public class MapleMap {
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 class MapleMap {
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 class MapleMap {
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 class MapleMap {
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 class MapleMap {
activateItemReactors(mdrop, owner.getClient());
}
public final void spawnItemDropList(List<Integer> list, final MapObject dropper, final Character owner, Point pos) {
spawnItemDropList(list, 1, 1, dropper, owner, pos, true, false);
}
public final void spawnItemDropList(List<Integer> 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<Integer> 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 @@ public class MapleMap {
}
}
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 class MapleMap {
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<MonsterDropEntry> dropEntry;
private final List<MonsterDropEntry> visibleQuestEntry;
private final List<MonsterDropEntry> otherQuestEntry;
@@ -3412,11 +3330,15 @@ public class MapleMap {
private final Character chr;
private final Monster mob;
protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry, Character chr, Monster mob) {
protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, short delay,
List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry,
List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> 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 class MapleMap {
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 class MapleMap {
expireItemsTask = null;
}
if (mobSpawnLootTask != null) {
mobSpawnLootTask.cancel(false);
mobSpawnLootTask = null;
}
if (characterStatUpdateTask != null) {
characterStatUpdateTask.cancel(false);
characterStatUpdateTask = null;

View File

@@ -56,6 +56,7 @@ import constants.id.MapId;
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.packet.Packet;
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;
@@ -1814,7 +1816,8 @@ public class PacketCreator {
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;
@@ -1832,7 +1835,7 @@ public class PacketCreator {
if (mod != 2) {
p.writePos(dropfrom);
p.writeShort(0);//Fh?
p.writeShort(delay);
}
if (drop.getMeso() == 0) {
addExpirationTime(p, drop.getItem().getExpiration());
@@ -2302,18 +2305,18 @@ public class PacketCreator {
return p;
}
public static Packet summonAttack(int cid, int summonOid, byte direction, List<SummonAttackEntry> allDamage) {
public static Packet summonAttack(int cid, int summonOid, byte direction, List<SummonAttackTarget> 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;
@@ -2338,29 +2341,40 @@ public class PacketCreator {
}
*/
public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance,
int numAttackedAndDamage, Map<Integer, AttackTarget> 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<Integer, List<Integer>> damage, int speed, int direction, int display) {
public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage,
int projectile, Map<Integer, AttackTarget> 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<Integer, List<Integer>> damage, int charge, int speed, int direction, int display) {
public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage,
Map<Integer, AttackTarget> 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<Integer, List<Integer>> 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<Integer, AttackTarget> targets,
int speed, int direction, int display) {
p.writeInt(chr.getId());
p.writeByte(numAttackedAndDamage);
p.writeByte(0x5B);//?
@@ -2374,16 +2388,16 @@ public class PacketCreator {
p.writeByte(speed);
p.writeByte(0x0A);
p.writeInt(projectile);
for (Integer oned : damage.keySet()) {
List<Integer> onedList = damage.get(oned);
if (onedList != null) {
p.writeInt(oned);
for (Map.Entry<Integer, AttackTarget> 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);
}
}
}
@@ -2594,6 +2608,14 @@ public class PacketCreator {
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());