From 3c9be7d70b1bb972e3c15a2ea16ad74da6ab13a9 Mon Sep 17 00:00:00 2001 From: P0nk Date: Fri, 2 Sep 2022 07:39:26 +0200 Subject: [PATCH 01/16] Lessen magic numbers around mob skills with new "type" enum --- .../channel/handlers/MoveLifeHandler.java | 7 +- src/main/java/server/life/MobSkill.java | 141 ++++++------------ .../java/server/life/MobSkillFactory.java | 5 +- src/main/java/server/life/MobSkillType.java | 61 ++++++++ src/main/java/server/life/Monster.java | 19 ++- 5 files changed, 127 insertions(+), 106 deletions(-) create mode 100644 src/main/java/server/life/MobSkillType.java diff --git a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java index 94b5871596..e29cc79277 100644 --- a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java @@ -27,10 +27,7 @@ import config.YamlConfig; import net.packet.InPacket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import server.life.MobSkill; -import server.life.MobSkillFactory; -import server.life.Monster; -import server.life.MonsterInformationProvider; +import server.life.*; import server.maps.MapObject; import server.maps.MapObjectType; import server.maps.MapleMap; @@ -103,7 +100,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { if (monster.canUseSkill(toUse, true)) { int animationTime = MonsterInformationProvider.getInstance().getMobSkillAnimationTime(toUse); - if (animationTime > 0 && toUse.getSkillId() != 129) { + if (animationTime > 0 && toUse.getType() != MobSkillType.BANISH) { toUse.applyDelayedEffect(player, monster, true, animationTime); } else { banishPlayers = new LinkedList<>(); diff --git a/src/main/java/server/life/MobSkill.java b/src/main/java/server/life/MobSkill.java index 5cb85cc8c5..35d9655f3b 100644 --- a/src/main/java/server/life/MobSkill.java +++ b/src/main/java/server/life/MobSkill.java @@ -47,7 +47,7 @@ import java.util.*; public class MobSkill { private static final Logger log = LoggerFactory.getLogger(MobSkill.class); - private final int skillId; + private final MobSkillType type; private final int skillLevel; private int mpCon; private final List toSummon = new ArrayList<>(); @@ -57,8 +57,8 @@ public class MobSkill { private Point lt, rb; private int limit; - public MobSkill(int skillId, int level) { - this.skillId = skillId; + public MobSkill(MobSkillType type, int level) { + this.type = type; this.skillLevel = level; } @@ -130,28 +130,12 @@ public class MobSkill { Disease disease = null; Map stats = new ArrayMap<>(); List reflection = new LinkedList<>(); - switch (skillId) { - case 100: - case 110: - case 150: - stats.put(MonsterStatus.WEAPON_ATTACK_UP, x); - break; - case 101: - case 111: - case 151: - stats.put(MonsterStatus.MAGIC_ATTACK_UP, x); - break; - case 102: - case 112: - case 152: - stats.put(MonsterStatus.WEAPON_DEFENSE_UP, x); - break; - case 103: - case 113: - case 153: - stats.put(MonsterStatus.MAGIC_DEFENSE_UP, x); - break; - case 114: + switch (type) { + case ATTACK_UP, ATTACK_UP_M, PAD -> stats.put(MonsterStatus.WEAPON_ATTACK_UP, x); + case MAGIC_ATTACK_UP, MAGIC_ATTACK_UP_M, MAD -> stats.put(MonsterStatus.MAGIC_ATTACK_UP, x); + case DEFENSE_UP, DEFENSE_UP_M, PDR -> stats.put(MonsterStatus.WEAPON_DEFENSE_UP, x); + case MAGIC_DEFENSE_UP, MAGIC_DEFENSE_UP_M, MDR -> stats.put(MonsterStatus.MAGIC_DEFENSE_UP, x); + case HEAL_M -> { if (lt != null && rb != null && skill) { List objects = getObjectsInRange(monster, MapObjectType.MONSTER); final int hps = (getX() / 1000) * (int) (950 + 1050 * Math.random()); @@ -161,29 +145,15 @@ public class MobSkill { } else { monster.heal(getX(), getY()); } - break; - case 120: - disease = Disease.SEAL; - break; - case 121: - disease = Disease.DARKNESS; - break; - case 122: - disease = Disease.WEAKEN; - break; - case 123: - disease = Disease.STUN; - break; - case 124: - disease = Disease.CURSE; - break; - case 125: - disease = Disease.POISON; - break; - case 126: // Slow - disease = Disease.SLOW; - break; - case 127: + } + case SEAL -> disease = Disease.SEAL; + case DARKNESS -> disease = Disease.DARKNESS; + case WEAKNESS -> disease = Disease.WEAKEN; + case STUN -> disease = Disease.STUN; + case CURSE -> disease = Disease.CURSE; + case POISON -> disease = Disease.POISON; + case SLOW -> disease = Disease.SLOW; + case DISPEL -> { if (lt != null && rb != null && skill) { for (Character character : getPlayersInRange(monster)) { character.dispel(); @@ -191,66 +161,52 @@ public class MobSkill { } else { player.dispel(); } - break; - case 128: // Seduce - disease = Disease.SEDUCE; - break; - case 129: // Banish + } + case SEDUCE -> disease = Disease.SEDUCE; + case BANISH -> { if (lt != null && rb != null && skill) { banishPlayers.addAll(getPlayersInRange(monster)); } else { banishPlayers.add(player); } - break; - case 131: // Mist + } + case AREA_POISON -> { monster.getMap().spawnMist(new Mist(calculateBoundingBox(monster.getPosition()), monster, this), x * 100, false, false, false); - break; - case 132: - disease = Disease.CONFUSE; - break; - case 133: // zombify - disease = Disease.ZOMBIFY; - break; - case 140: + } + case REVERSE_INPUT -> disease = Disease.CONFUSE; + case UNDEAD -> disease = Disease.ZOMBIFY; + case PHYSICAL_IMMUNE -> { if (makeChanceResult() && !monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) { stats.put(MonsterStatus.WEAPON_IMMUNITY, x); } - break; - case 141: + } + case MAGIC_IMMUNE -> { if (makeChanceResult() && !monster.isBuffed(MonsterStatus.WEAPON_IMMUNITY)) { stats.put(MonsterStatus.MAGIC_IMMUNITY, x); } - break; - case 143: // Weapon Reflect + } + case PHYSICAL_COUNTER -> { stats.put(MonsterStatus.WEAPON_REFLECT, 10); stats.put(MonsterStatus.WEAPON_IMMUNITY, 10); reflection.add(x); - break; - case 144: // Magic Reflect + } + case MAGIC_COUNTER -> { stats.put(MonsterStatus.MAGIC_REFLECT, 10); stats.put(MonsterStatus.MAGIC_IMMUNITY, 10); reflection.add(x); - break; - case 145: // Weapon / Magic reflect + } + case PHYSICAL_AND_MAGIC_COUNTER -> { stats.put(MonsterStatus.WEAPON_REFLECT, 10); stats.put(MonsterStatus.WEAPON_IMMUNITY, 10); stats.put(MonsterStatus.MAGIC_REFLECT, 10); stats.put(MonsterStatus.MAGIC_IMMUNITY, 10); reflection.add(x); - break; - case 154: - stats.put(MonsterStatus.ACC, x); - break; - case 155: - stats.put(MonsterStatus.AVOID, x); - break; - case 156: - stats.put(MonsterStatus.SPEED, x); - break; - case 157: - stats.put(MonsterStatus.SEAL_SKILL, x); - break; - case 200: // summon + } + case ACC -> stats.put(MonsterStatus.ACC, x); + case EVA -> stats.put(MonsterStatus.AVOID, x); + case SPEED -> stats.put(MonsterStatus.SPEED, x); + case SEAL_SKILL -> stats.put(MonsterStatus.SEAL_SKILL, x); + case SUMMON -> { int skillLimit = this.getLimit(); MapleMap map = monster.getMap(); @@ -322,18 +278,15 @@ public class MobSkill { } } } - break; - default: - log.warn("Unhandled Mob skill: {}", skillId); - break; + } } if (stats.size() > 0) { if (lt != null && rb != null && skill) { for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) { - ((Monster) mons).applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection); + ((Monster) mons).applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); } } else { - monster.applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection); + monster.applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); } } if (disease != null) { @@ -361,8 +314,12 @@ public class MobSkill { return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition())); } + public MobSkillType getType() { + return type; + } + public int getSkillId() { - return skillId; + return type.getId(); } public int getSkillLevel() { diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 055cbbc695..0e9cee415b 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -63,8 +63,7 @@ public class MobSkillFactory { } writeLock.lock(); try { - MobSkill ret; - ret = mobSkills.get(key); + MobSkill ret = mobSkills.get(key); if (ret == null) { Data skillData = skillRoot.getChildByPath(skillId + "/level/" + level); if (skillData != null) { @@ -93,7 +92,7 @@ public class MobSkillFactory { lt = (Point) ltd.getData(); rb = (Point) skillData.getChildByPath("rb").getData(); } - ret = new MobSkill(skillId, level); + ret = new MobSkill(MobSkillType.from(skillId), level); ret.addSummons(toSummon); ret.setCoolTime(cooltime); ret.setDuration(duration); diff --git a/src/main/java/server/life/MobSkillType.java b/src/main/java/server/life/MobSkillType.java new file mode 100644 index 0000000000..c16b8ba3cb --- /dev/null +++ b/src/main/java/server/life/MobSkillType.java @@ -0,0 +1,61 @@ +package server.life; + +import java.util.Arrays; + +public enum MobSkillType { + ATTACK_UP(100), + MAGIC_ATTACK_UP(101), + DEFENSE_UP(102), + MAGIC_DEFENSE_UP(103), + ATTACK_UP_M(110), + MAGIC_ATTACK_UP_M(111), + DEFENSE_UP_M(112), + MAGIC_DEFENSE_UP_M(113), + HEAL_M(114), + HASTE_M(115), + SEAL(120), + DARKNESS(121), + WEAKNESS(122), + STUN(123), + CURSE(124), + POISON(125), + SLOW(126), + DISPEL(127), + SEDUCE(128), + BANISH(129), + AREA_POISON(131), + REVERSE_INPUT(132), + UNDEAD(133), + STOP_POTION(134), + STOP_MOTION(135), + FEAR(136), + PHYSICAL_IMMUNE(140), + MAGIC_IMMUNE(141), + HARD_SKIN(142), + PHYSICAL_COUNTER(143), + MAGIC_COUNTER(144), + PHYSICAL_AND_MAGIC_COUNTER(145), + PAD(150), + MAD(151), + PDR(152), + MDR(153), + ACC(154), + EVA(155), + SPEED(156), + SEAL_SKILL(157), + SUMMON(200); + + private final int id; + + MobSkillType(int id) { + this.id = id; + } + + public static MobSkillType from(int id) { + return Arrays.stream(values()).findFirst().orElseThrow(IllegalArgumentException::new); + } + + public int getId() { + return id; + } +} diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index a494c01915..77c1bb9dd9 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -1276,11 +1276,11 @@ public class Monster extends AbstractLoadedLife { return true; } - public final void dispelSkill(final MobSkill skillId) { + public final void dispelSkill(final MobSkill skill) { List toCancel = new ArrayList<>(); for (Entry effects : stati.entrySet()) { MonsterStatusEffect mse = effects.getValue(); - if (mse.getMobSkill() != null && mse.getMobSkill().getSkillId() == skillId.getSkillId()) { //not checking for level. + if (mse.getMobSkill() != null && mse.getMobSkill().getType() == skill.getType()) { //not checking for level. toCancel.add(effects.getKey()); } } @@ -1452,8 +1452,7 @@ public class Monster extends AbstractLoadedLife { return false; } - int useSkillid = toUse.getSkillId(); - if (useSkillid >= 143 && useSkillid <= 145) { + if (isReflectSkill(toUse)) { if (this.isBuffed(MonsterStatus.WEAPON_REFLECT) || this.isBuffed(MonsterStatus.MAGIC_REFLECT)) { return false; } @@ -1462,7 +1461,7 @@ public class Monster extends AbstractLoadedLife { monsterLock.lock(); try { for (Pair skill : usedSkills) { // thanks OishiiKawaiiDesu for noticing an issue with mobskill cooldown - if (skill.getLeft() == useSkillid && skill.getRight() == toUse.getSkillLevel()) { + if (skill.getLeft() == toUse.getSkillId() && skill.getRight() == toUse.getSkillLevel()) { return false; } } @@ -1488,8 +1487,16 @@ public class Monster extends AbstractLoadedLife { return true; } + private boolean isReflectSkill(MobSkill mobSkill) { + return switch (mobSkill.getType()) { + case PHYSICAL_COUNTER, MAGIC_COUNTER, PHYSICAL_AND_MAGIC_COUNTER -> true; + default -> false ; + }; + } + private void usedSkill(MobSkill skill) { - final int skillId = skill.getSkillId(), level = skill.getSkillLevel(); + final int skillId = skill.getSkillId(); + final int level = skill.getSkillLevel(); long cooltime = skill.getCoolTime(); monsterLock.lock(); From 6e62a6a45d662cb2f0aac228a5953d0d1d9475bf Mon Sep 17 00:00:00 2001 From: P0nk Date: Fri, 2 Sep 2022 07:58:52 +0200 Subject: [PATCH 02/16] Break off mob skill loading into separate method --- .../java/server/life/MobSkillFactory.java | 125 ++++++++++-------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 0e9cee415b..599315b8a7 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -28,10 +28,8 @@ import provider.DataTool; import provider.wz.WZFiles; import java.awt.*; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -42,75 +40,90 @@ import static java.util.concurrent.TimeUnit.SECONDS; * @author Danny (Leifde) */ public class MobSkillFactory { - private static final Map mobSkills = new HashMap<>(); - private final static DataProvider dataSource = DataProviderFactory.getDataProvider(WZFiles.SKILL); + private static final DataProvider dataSource = DataProviderFactory.getDataProvider(WZFiles.SKILL); private static final Data skillRoot = dataSource.getData("MobSkill.img"); - private final static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final static Lock readLock = readWriteLock.readLock(); - private final static Lock writeLock = readWriteLock.writeLock(); + private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private static final Lock readLock = readWriteLock.readLock(); + private static final Lock writeLock = readWriteLock.writeLock(); - public static MobSkill getMobSkill(final int skillId, final int level) { - final String key = skillId + "" + level; + public static MobSkill getMobSkill(final int skillId, final int level) { // TODO: return Optional readLock.lock(); try { - MobSkill ret = mobSkills.get(key); + MobSkill ret = mobSkills.get(createKey(skillId, level)); if (ret != null) { return ret; } } finally { readLock.unlock(); } + + return loadMobSkill(skillId, level).orElse(null); + } + + private static Optional loadMobSkill(final int skillId, final int level) { writeLock.lock(); try { - MobSkill ret = mobSkills.get(key); - if (ret == null) { - Data skillData = skillRoot.getChildByPath(skillId + "/level/" + level); - if (skillData != null) { - int mpCon = DataTool.getInt(skillData.getChildByPath("mpCon"), 0); - List toSummon = new ArrayList<>(); - for (int i = 0; i > -1; i++) { - if (skillData.getChildByPath(String.valueOf(i)) == null) { - break; - } - toSummon.add(DataTool.getInt(skillData.getChildByPath(String.valueOf(i)), 0)); - } - int effect = DataTool.getInt("summonEffect", skillData, 0); - int hp = DataTool.getInt("hp", skillData, 100); - int x = DataTool.getInt("x", skillData, 1); - int y = DataTool.getInt("y", skillData, 1); - int count = DataTool.getInt("count", skillData, 1); - long duration = SECONDS.toMillis(DataTool.getInt("time", skillData, 0)); - long cooltime = SECONDS.toMillis(DataTool.getInt("interval", skillData, 0)); - int iprop = DataTool.getInt("prop", skillData, 100); - float prop = iprop / 100; - int limit = DataTool.getInt("limit", skillData, 0); - Data ltd = skillData.getChildByPath("lt"); - Point lt = null; - Point rb = null; - if (ltd != null) { - lt = (Point) ltd.getData(); - rb = (Point) skillData.getChildByPath("rb").getData(); - } - ret = new MobSkill(MobSkillType.from(skillId), level); - ret.addSummons(toSummon); - ret.setCoolTime(cooltime); - ret.setDuration(duration); - ret.setHp(hp); - ret.setMpCon(mpCon); - ret.setSpawnEffect(effect); - ret.setX(x); - ret.setY(y); - ret.setCount(count); - ret.setProp(prop); - ret.setLimit(limit); - ret.setLtRb(lt, rb); - } - mobSkills.put(skillId + "" + level, ret); + MobSkill ms = mobSkills.get(createKey(skillId, level)); + if (ms != null) { + return Optional.of(ms); } - return ret; + + Data skillData = skillRoot.getChildByPath(skillId + "/level/" + level); + if (skillData == null) { + return Optional.empty(); + } + + int mpCon = DataTool.getInt(skillData.getChildByPath("mpCon"), 0); + List toSummon = new ArrayList<>(); + for (int i = 0; i > -1; i++) { + if (skillData.getChildByPath(String.valueOf(i)) == null) { + break; + } + toSummon.add(DataTool.getInt(skillData.getChildByPath(String.valueOf(i)), 0)); + } + int effect = DataTool.getInt("summonEffect", skillData, 0); + int hp = DataTool.getInt("hp", skillData, 100); + int x = DataTool.getInt("x", skillData, 1); + int y = DataTool.getInt("y", skillData, 1); + int count = DataTool.getInt("count", skillData, 1); + long duration = SECONDS.toMillis(DataTool.getInt("time", skillData, 0)); + long cooltime = SECONDS.toMillis(DataTool.getInt("interval", skillData, 0)); + int iprop = DataTool.getInt("prop", skillData, 100); + float prop = iprop / 100; + int limit = DataTool.getInt("limit", skillData, 0); + + Data ltData = skillData.getChildByPath("lt"); + Data rbData = skillData.getChildByPath("rb"); + Point lt = null; + Point rb = null; + if (ltData != null && rbData != null) { + lt = (Point) ltData.getData(); + rb = (Point) rbData.getData(); + } + + ms = new MobSkill(MobSkillType.from(skillId), level); + ms.addSummons(toSummon); + ms.setCoolTime(cooltime); + ms.setDuration(duration); + ms.setHp(hp); + ms.setMpCon(mpCon); + ms.setSpawnEffect(effect); + ms.setX(x); + ms.setY(y); + ms.setCount(count); + ms.setProp(prop); + ms.setLimit(limit); + ms.setLtRb(lt, rb); + + mobSkills.put(createKey(skillId, level), ms); + return Optional.of(ms); } finally { writeLock.unlock(); } } + + private static String createKey(int skillId, int skillLevel) { + return skillId + "" + skillLevel; + } } From 455d4b219585ae2d7f5e78e681ed2bcf95c3074f Mon Sep 17 00:00:00 2001 From: P0nk Date: Fri, 2 Sep 2022 08:44:19 +0200 Subject: [PATCH 03/16] Make MobSkill immutable using builder pattern --- src/main/java/server/life/MobSkill.java | 155 +++++++++++++----- .../java/server/life/MobSkillFactory.java | 43 ++--- 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/src/main/java/server/life/MobSkill.java b/src/main/java/server/life/MobSkill.java index 35d9655f3b..b5c77d4637 100644 --- a/src/main/java/server/life/MobSkill.java +++ b/src/main/java/server/life/MobSkill.java @@ -26,6 +26,7 @@ import client.Disease; import client.status.MonsterStatus; import constants.id.MapId; import constants.id.MobId; +import constants.skills.Bishop; import net.server.services.task.channel.OverallService; import net.server.services.type.ChannelServices; import org.slf4j.Logger; @@ -49,66 +50,130 @@ public class MobSkill { private final MobSkillType type; private final int skillLevel; - private int mpCon; - private final List toSummon = new ArrayList<>(); - private int spawnEffect, hp, x, y, count; - private long duration, cooltime; - private float prop; - private Point lt, rb; - private int limit; + private final int mpCon; + private final int spawnEffect; + private final int hp; + private final int x; + private final int y; + private final int count; + private final long duration; + private final long cooltime; + private final float prop; + private final Point lt; + private final Point rb; + private final int limit; + private final List toSummon; - public MobSkill(MobSkillType type, int level) { + private MobSkill(MobSkillType type, int level, int mpCon, int spawnEffect, int hp, int x, int y, int count, + long duration, long cooltime, float prop, Point lt, Point rb, int limit, List toSummon) { this.type = type; this.skillLevel = level; - } - - public void setMpCon(int mpCon) { this.mpCon = mpCon; - } - - public void addSummons(List toSummon) { - this.toSummon.addAll(toSummon); - } - - public void setSpawnEffect(int spawnEffect) { this.spawnEffect = spawnEffect; - } - - public void setHp(int hp) { this.hp = hp; - } - - public void setX(int x) { this.x = x; - } - - public void setY(int y) { this.y = y; - } - - public void setCount(int count) { this.count = count; - } - - public void setDuration(long duration) { this.duration = duration; - } - - public void setCoolTime(long cooltime) { this.cooltime = cooltime; - } - - public void setProp(float prop) { this.prop = prop; - } - - public void setLtRb(Point lt, Point rb) { this.lt = lt; this.rb = rb; + this.limit = limit; + this.toSummon = toSummon; } - public void setLimit(int limit) { - this.limit = limit; + static class Builder { + private final MobSkillType type; + private final int level; + private int mpCon; + private int spawnEffect; + private int hp; + private int x; + private int y; + private int count; + private long duration; + private long cooltime; + private float prop; + private Point lt; + private Point rb; + private int limit; + private List toSummon; + + public Builder(MobSkillType type, int level) { + this.type = type; + this.level = level; + } + + public Builder mpCon(int mpCon) { + this.mpCon = mpCon; + return this; + } + + public Builder spawnEffect(int spawnEffect) { + this.spawnEffect = spawnEffect; + return this; + } + + public Builder hp(int hp) { + this.hp = hp; + return this; + } + + public Builder x(int x) { + this.x = x; + return this; + } + + public Builder y(int y) { + this.y = y; + return this; + } + + public Builder count(int count) { + this.count = count; + return this; + } + + public Builder duration(long duration) { + this.duration = duration; + return this; + } + + public Builder cooltime(long cooltime) { + this.cooltime = cooltime; + return this; + } + + public Builder prop(float prop) { + this.prop = prop; + return this; + } + + public Builder lt(Point lt) { + this.lt = lt; + return this; + } + + public Builder rb(Point rb) { + this.rb = rb; + return this; + } + + public Builder limit(int limit) { + this.limit = limit; + return this; + } + + public Builder toSummon(List toSummon) { + this.toSummon = Collections.unmodifiableList(toSummon); + return this; + } + + public MobSkill build() { + return new MobSkill(type, level, mpCon, spawnEffect, hp, x, y, count, duration, cooltime, prop, lt, rb, + limit, toSummon); + } } public void applyDelayedEffect(final Character player, final Monster monster, final boolean skill, int animationTime) { @@ -293,7 +358,7 @@ public class MobSkill { if (lt != null && rb != null && skill) { int i = 0; for (Character character : getPlayersInRange(monster)) { - if (!character.hasActiveBuff(2321005)) { // holy shield + if (!character.hasActiveBuff(Bishop.HOLY_SHIELD)) { if (disease.equals(Disease.SEDUCE)) { if (i < count) { character.giveDebuff(Disease.SEDUCE, this); diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 599315b8a7..ea263cd21e 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -47,12 +47,13 @@ public class MobSkillFactory { private static final Lock readLock = readWriteLock.readLock(); private static final Lock writeLock = readWriteLock.writeLock(); + // TODO: take in MobSkillType as argument instead of skillId public static MobSkill getMobSkill(final int skillId, final int level) { // TODO: return Optional readLock.lock(); try { - MobSkill ret = mobSkills.get(createKey(skillId, level)); - if (ret != null) { - return ret; + MobSkill ms = mobSkills.get(createKey(skillId, level)); + if (ms != null) { + return ms; } } finally { readLock.unlock(); @@ -64,9 +65,9 @@ public class MobSkillFactory { private static Optional loadMobSkill(final int skillId, final int level) { writeLock.lock(); try { - MobSkill ms = mobSkills.get(createKey(skillId, level)); - if (ms != null) { - return Optional.of(ms); + MobSkill existingMs = mobSkills.get(createKey(skillId, level)); + if (existingMs != null) { + return Optional.of(existingMs); } Data skillData = skillRoot.getChildByPath(skillId + "/level/" + level); @@ -102,22 +103,22 @@ public class MobSkillFactory { rb = (Point) rbData.getData(); } - ms = new MobSkill(MobSkillType.from(skillId), level); - ms.addSummons(toSummon); - ms.setCoolTime(cooltime); - ms.setDuration(duration); - ms.setHp(hp); - ms.setMpCon(mpCon); - ms.setSpawnEffect(effect); - ms.setX(x); - ms.setY(y); - ms.setCount(count); - ms.setProp(prop); - ms.setLimit(limit); - ms.setLtRb(lt, rb); + MobSkill loadedMobSkill = new MobSkill.Builder(MobSkillType.from(skillId), level) + .toSummon(toSummon) + .cooltime(cooltime) + .duration(duration) + .hp(hp) + .x(x) + .y(y) + .count(count) + .prop(prop) + .limit(limit) + .lt(lt) + .rb(rb) + .build(); - mobSkills.put(createKey(skillId, level), ms); - return Optional.of(ms); + mobSkills.put(createKey(skillId, level), loadedMobSkill); + return Optional.of(loadedMobSkill); } finally { writeLock.unlock(); } From aeaad4194a2eb9e5d73cb3cfcd926be0da16092d Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 04:22:06 +0200 Subject: [PATCH 04/16] Refactor MobSkill#applyEffect, split into multiple smaller methods --- src/main/java/server/life/MobSkill.java | 275 +++++++++++++----------- src/main/java/tools/ArrayMap.java | 1 + 2 files changed, 150 insertions(+), 126 deletions(-) diff --git a/src/main/java/server/life/MobSkill.java b/src/main/java/server/life/MobSkill.java index b5c77d4637..6456611952 100644 --- a/src/main/java/server/life/MobSkill.java +++ b/src/main/java/server/life/MobSkill.java @@ -39,8 +39,10 @@ import tools.ArrayMap; import tools.Randomizer; import java.awt.*; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.*; +import java.util.Map; /** * @author Danny (Leifde) @@ -191,26 +193,17 @@ public class MobSkill { applyEffect(null, monster, false, Collections.emptyList()); } - public void applyEffect(Character player, Monster monster, boolean skill, List banishPlayers) { + // TODO: avoid output argument banishPlayersOutput + public void applyEffect(Character player, Monster monster, boolean skill, List banishPlayersOutput) { Disease disease = null; Map stats = new ArrayMap<>(); - List reflection = new LinkedList<>(); + List reflection = new ArrayList<>(); switch (type) { case ATTACK_UP, ATTACK_UP_M, PAD -> stats.put(MonsterStatus.WEAPON_ATTACK_UP, x); case MAGIC_ATTACK_UP, MAGIC_ATTACK_UP_M, MAD -> stats.put(MonsterStatus.MAGIC_ATTACK_UP, x); case DEFENSE_UP, DEFENSE_UP_M, PDR -> stats.put(MonsterStatus.WEAPON_DEFENSE_UP, x); case MAGIC_DEFENSE_UP, MAGIC_DEFENSE_UP_M, MDR -> stats.put(MonsterStatus.MAGIC_DEFENSE_UP, x); - case HEAL_M -> { - if (lt != null && rb != null && skill) { - List objects = getObjectsInRange(monster, MapObjectType.MONSTER); - final int hps = (getX() / 1000) * (int) (950 + 1050 * Math.random()); - for (MapObject mons : objects) { - ((Monster) mons).heal(hps, getY()); - } - } else { - monster.heal(getX(), getY()); - } - } + case HEAL_M -> applyHealEffect(skill, monster); case SEAL -> disease = Disease.SEAL; case DARKNESS -> disease = Disease.DARKNESS; case WEAKNESS -> disease = Disease.WEAKEN; @@ -218,26 +211,10 @@ public class MobSkill { case CURSE -> disease = Disease.CURSE; case POISON -> disease = Disease.POISON; case SLOW -> disease = Disease.SLOW; - case DISPEL -> { - if (lt != null && rb != null && skill) { - for (Character character : getPlayersInRange(monster)) { - character.dispel(); - } - } else { - player.dispel(); - } - } + case DISPEL -> applyDispelEffect(skill, monster, player); case SEDUCE -> disease = Disease.SEDUCE; - case BANISH -> { - if (lt != null && rb != null && skill) { - banishPlayers.addAll(getPlayersInRange(monster)); - } else { - banishPlayers.add(player); - } - } - case AREA_POISON -> { - monster.getMap().spawnMist(new Mist(calculateBoundingBox(monster.getPosition()), monster, this), x * 100, false, false, false); - } + case BANISH -> applyBanishEffect(skill, monster, player, banishPlayersOutput); + case AREA_POISON -> spawnMonsterMist(monster); case REVERSE_INPUT -> disease = Disease.CONFUSE; case UNDEAD -> disease = Disease.ZOMBIFY; case PHYSICAL_IMMUNE -> { @@ -271,110 +248,156 @@ public class MobSkill { case EVA -> stats.put(MonsterStatus.AVOID, x); case SPEED -> stats.put(MonsterStatus.SPEED, x); case SEAL_SKILL -> stats.put(MonsterStatus.SEAL_SKILL, x); - case SUMMON -> { - int skillLimit = this.getLimit(); - MapleMap map = monster.getMap(); - - if (MapId.isDojo(map.getId())) { // spawns in dojo should be unlimited - skillLimit = Integer.MAX_VALUE; - } - - if (map.getSpawnedMonstersOnMap() < 80) { - List summons = getSummons(); - int summonLimit = monster.countAvailableMobSummons(summons.size(), skillLimit); - if (summonLimit >= 1) { - boolean bossRushMap = MapId.isBossRush(map.getId()); - - Collections.shuffle(summons); - for (Integer mobId : summons.subList(0, summonLimit)) { - Monster toSpawn = LifeFactory.getMonster(mobId); - if (toSpawn != null) { - if (bossRushMap) { - toSpawn.disableDrops(); // no littering on BRPQ pls - } - toSpawn.setPosition(monster.getPosition()); - int ypos, xpos; - xpos = (int) monster.getPosition().getX(); - ypos = (int) monster.getPosition().getY(); - switch (mobId) { - case MobId.HIGH_DARKSTAR: // Pap bomb high - toSpawn.setFh((int) Math.ceil(Math.random() * 19.0)); - ypos = -590; - break; - case MobId.LOW_DARKSTAR: // Pap bomb - xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500); - if (ypos != -590) { - ypos = (int) monster.getPosition().getY(); - } - break; - case MobId.BLOODY_BOOM: //Pianus bomb - if (Math.ceil(Math.random() * 5) == 1) { - ypos = 78; - xpos = Randomizer.nextInt(5) + (Randomizer.nextInt(2) == 1 ? 180 : 0); - } else { - xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500); - } - break; - } - switch (map.getId()) { - case MapId.ORIGIN_OF_CLOCKTOWER: //Pap map - if (xpos < -890) { - xpos = (int) (Math.ceil(Math.random() * 150) - 890); - } else if (xpos > 230) { - xpos = (int) (230 - Math.ceil(Math.random() * 150)); - } - break; - case MapId.CAVE_OF_PIANUS: // Pianus map - if (xpos < -239) { - xpos = (int) (Math.ceil(Math.random() * 150) - 239); - } else if (xpos > 371) { - xpos = (int) (371 - Math.ceil(Math.random() * 150)); - } - break; - } - toSpawn.setPosition(new Point(xpos, ypos)); - if (toSpawn.getId() == MobId.LOW_DARKSTAR) { - map.spawnFakeMonster(toSpawn); - } else { - map.spawnMonsterWithEffect(toSpawn, getSpawnEffect(), toSpawn.getPosition()); - } - monster.addSummonedMob(toSpawn); - } - } - } - } - } + case SUMMON -> summonMonsters(monster); } if (stats.size() > 0) { - if (lt != null && rb != null && skill) { - for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) { - ((Monster) mons).applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); - } - } else { - monster.applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); - } + applyMonsterBuffs(stats, skill, monster, reflection); } if (disease != null) { - if (lt != null && rb != null && skill) { - int i = 0; - for (Character character : getPlayersInRange(monster)) { - if (!character.hasActiveBuff(Bishop.HOLY_SHIELD)) { - if (disease.equals(Disease.SEDUCE)) { - if (i < count) { - character.giveDebuff(Disease.SEDUCE, this); - i++; - } - } else { - character.giveDebuff(disease, this); + applyDisease(disease, skill, monster, player); + } + } + + private void applyHealEffect(boolean skill, Monster monster) { + if (lt != null && rb != null && skill) { + List objects = getObjectsInRange(monster, MapObjectType.MONSTER); + final int hps = (getX() / 1000) * (int) (950 + 1050 * Math.random()); + for (MapObject mons : objects) { + ((Monster) mons).heal(hps, getY()); + } + } else { + monster.heal(getX(), getY()); + } + } + + private void applyDispelEffect(boolean skill, Monster monster, Character player) { + if (lt != null && rb != null && skill) { + getPlayersInRange(monster).forEach(Character::dispel); + } else { + player.dispel(); + } + } + + private void applyBanishEffect(boolean skill, Monster monster, Character player, + List banishPlayersOutput) { + if (lt != null && rb != null && skill) { + banishPlayersOutput.addAll(getPlayersInRange(monster)); + } else { + banishPlayersOutput.add(player); + } + } + + private void spawnMonsterMist(Monster monster) { + Rectangle mistArea = calculateBoundingBox(monster.getPosition()); + var mist = new Mist(mistArea, monster, this); + int mistDuration = x * 100; + monster.getMap().spawnMist(mist, mistDuration, false, false, false); + } + + private void summonMonsters(Monster monster) { + int skillLimit = this.getLimit(); + MapleMap map = monster.getMap(); + + if (MapId.isDojo(map.getId())) { // spawns in dojo should be unlimited + skillLimit = Integer.MAX_VALUE; + } + + if (map.getSpawnedMonstersOnMap() < 80) { + List summons = getSummons(); + int summonLimit = monster.countAvailableMobSummons(summons.size(), skillLimit); + if (summonLimit >= 1) { + boolean bossRushMap = MapId.isBossRush(map.getId()); + + Collections.shuffle(summons); + for (Integer mobId : summons.subList(0, summonLimit)) { + Monster toSpawn = LifeFactory.getMonster(mobId); + if (toSpawn != null) { + if (bossRushMap) { + toSpawn.disableDrops(); // no littering on BRPQ pls } + toSpawn.setPosition(monster.getPosition()); + int ypos, xpos; + xpos = (int) monster.getPosition().getX(); + ypos = (int) monster.getPosition().getY(); + switch (mobId) { + case MobId.HIGH_DARKSTAR: // Pap bomb high + toSpawn.setFh((int) Math.ceil(Math.random() * 19.0)); + ypos = -590; + break; + case MobId.LOW_DARKSTAR: // Pap bomb + xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500); + if (ypos != -590) { + ypos = (int) monster.getPosition().getY(); + } + break; + case MobId.BLOODY_BOOM: //Pianus bomb + if (Math.ceil(Math.random() * 5) == 1) { + ypos = 78; + xpos = Randomizer.nextInt(5) + (Randomizer.nextInt(2) == 1 ? 180 : 0); + } else { + xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500); + } + break; + } + switch (map.getId()) { + case MapId.ORIGIN_OF_CLOCKTOWER: //Pap map + if (xpos < -890) { + xpos = (int) (Math.ceil(Math.random() * 150) - 890); + } else if (xpos > 230) { + xpos = (int) (230 - Math.ceil(Math.random() * 150)); + } + break; + case MapId.CAVE_OF_PIANUS: // Pianus map + if (xpos < -239) { + xpos = (int) (Math.ceil(Math.random() * 150) - 239); + } else if (xpos > 371) { + xpos = (int) (371 - Math.ceil(Math.random() * 150)); + } + break; + } + toSpawn.setPosition(new Point(xpos, ypos)); + if (toSpawn.getId() == MobId.LOW_DARKSTAR) { + map.spawnFakeMonster(toSpawn); + } else { + map.spawnMonsterWithEffect(toSpawn, getSpawnEffect(), toSpawn.getPosition()); + } + monster.addSummonedMob(toSpawn); } } - } else { - player.giveDebuff(disease, this); } } } + private void applyMonsterBuffs(Map stats, boolean skill, Monster monster, List reflection) { + if (lt != null && rb != null && skill) { + for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) { + ((Monster) mons).applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); + } + } else { + monster.applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); + } + } + + private void applyDisease(Disease disease, boolean skill, Monster monster, Character player) { + if (lt != null && rb != null && skill) { + int i = 0; + for (Character character : getPlayersInRange(monster)) { + if (!character.hasActiveBuff(Bishop.HOLY_SHIELD)) { + if (disease.equals(Disease.SEDUCE)) { + if (i < count) { + character.giveDebuff(Disease.SEDUCE, this); + i++; + } + } else { + character.giveDebuff(disease, this); + } + } + } + } else { + player.giveDebuff(disease, this); + } + } + private List getPlayersInRange(Monster monster) { return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition())); } diff --git a/src/main/java/tools/ArrayMap.java b/src/main/java/tools/ArrayMap.java index a224b3059d..4d5682bcc3 100644 --- a/src/main/java/tools/ArrayMap.java +++ b/src/main/java/tools/ArrayMap.java @@ -23,6 +23,7 @@ package tools; import java.util.*; +// TODO: remove public class ArrayMap extends AbstractMap { static class Entry implements Map.Entry { From 66b04969fdde4f04ccd4aed09ab0f32b1707ad0c Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 04:32:09 +0200 Subject: [PATCH 05/16] Delete ArrayMap which filled no function --- src/main/java/server/StatEffect.java | 3 +- src/main/java/server/life/MobSkill.java | 9 +- src/main/java/tools/ArrayMap.java | 145 ------------------------ 3 files changed, 4 insertions(+), 153 deletions(-) delete mode 100644 src/main/java/tools/ArrayMap.java diff --git a/src/main/java/server/StatEffect.java b/src/main/java/server/StatEffect.java index 6c06f47b8d..aa682f2e35 100644 --- a/src/main/java/server/StatEffect.java +++ b/src/main/java/server/StatEffect.java @@ -46,7 +46,6 @@ import server.life.Monster; import server.maps.*; import server.partyquest.CarnivalFactory; import server.partyquest.CarnivalFactory.MCSkill; -import tools.ArrayMap; import tools.PacketCreator; import tools.Pair; @@ -422,7 +421,7 @@ public class StatEffect { ret.itemCon = DataTool.getInt("itemCon", source, 0); ret.itemConNo = DataTool.getInt("itemConNo", source, 0); ret.moveTo = DataTool.getInt("moveTo", source, -1); - Map monsterStatus = new ArrayMap<>(); + Map monsterStatus = new EnumMap<>(MonsterStatus.class); if (skill) { switch (sourceid) { // BEGINNER diff --git a/src/main/java/server/life/MobSkill.java b/src/main/java/server/life/MobSkill.java index 6456611952..0ab6ca91fb 100644 --- a/src/main/java/server/life/MobSkill.java +++ b/src/main/java/server/life/MobSkill.java @@ -35,14 +35,11 @@ import server.maps.MapObject; import server.maps.MapObjectType; import server.maps.MapleMap; import server.maps.Mist; -import tools.ArrayMap; import tools.Randomizer; import java.awt.*; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.*; /** * @author Danny (Leifde) @@ -196,7 +193,7 @@ public class MobSkill { // TODO: avoid output argument banishPlayersOutput public void applyEffect(Character player, Monster monster, boolean skill, List banishPlayersOutput) { Disease disease = null; - Map stats = new ArrayMap<>(); + Map stats = new EnumMap<>(MonsterStatus.class); List reflection = new ArrayList<>(); switch (type) { case ATTACK_UP, ATTACK_UP_M, PAD -> stats.put(MonsterStatus.WEAPON_ATTACK_UP, x); @@ -419,7 +416,7 @@ public class MobSkill { } public List getSummons() { - return new ArrayList<>(toSummon); + return toSummon; } public int getSpawnEffect() { diff --git a/src/main/java/tools/ArrayMap.java b/src/main/java/tools/ArrayMap.java deleted file mode 100644 index 4d5682bcc3..0000000000 --- a/src/main/java/tools/ArrayMap.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation version 3 as published by - the Free Software Foundation. You may not use, modify or distribute - this program under any other version of the GNU Affero General Public - License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ -package tools; - -import java.util.*; - -// TODO: remove -public class ArrayMap extends AbstractMap { - - static class Entry implements Map.Entry { - protected K key; - protected V value; - - public Entry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(V newValue) { - V oldValue = value; - value = newValue; - return oldValue; - } - - @Override - public boolean equals(Object o) { - if (o instanceof Map.Entry e) { - return (key == null ? e.getKey() == null : key.equals(e.getKey())) && (value == null ? e.getValue() == null : value.equals(e.getValue())); - } - return false; - } - - @Override - public int hashCode() { - int keyHash = (key == null ? 0 : key.hashCode()); - int valueHash = (value == null ? 0 : value.hashCode()); - return keyHash ^ valueHash; - } - - @Override - public String toString() { - return key + "=" + value; - } - } - - private Set> entries = null; - private final ArrayList> list; - - public ArrayMap() { - list = new ArrayList<>(); - } - - public ArrayMap(Map map) { - list = new ArrayList<>(); - putAll(map); - } - - public ArrayMap(int initialCapacity) { - list = new ArrayList<>(initialCapacity); - } - - @Override - @SuppressWarnings("unchecked") - public Set> entrySet() { - if (entries == null) { - entries = new AbstractSet>() { - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator> iterator() { - return list.iterator(); - } - - @Override - public int size() { - return list.size(); - } - }; - } - return (Set>) entries; - } - - @Override - public V put(K key, V value) { - int size = list.size(); - Entry entry = null; - int i; - if (key == null) { - for (i = 0; i < size; i++) { - entry = (list.get(i)); - if (entry.getKey() == null) { - break; - } - } - } else { - for (i = 0; i < size; i++) { - entry = (list.get(i)); - if (key.equals(entry.getKey())) { - break; - } - } - } - V oldValue = null; - if (i < size) { - oldValue = entry.getValue(); - entry.setValue(value); - } else { - list.add(new Entry<>(key, value)); - } - return oldValue; - } -} From fba49e7eec13b3f0aeab8ea0a4617759f63ea71e Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 04:52:55 +0200 Subject: [PATCH 06/16] MobSkillFactory takes MobSkillType instead of int as id argument --- src/main/java/client/Character.java | 7 +- .../command/commands/gm2/MobSkillCommand.java | 4 +- .../command/commands/gm3/DebuffCommand.java | 70 ++++++++----------- .../handlers/AbstractDealDamageHandler.java | 8 +-- .../channel/handlers/MoveLifeHandler.java | 4 +- .../channel/handlers/TakeDamageHandler.java | 2 +- .../scripting/AbstractPlayerInteraction.java | 8 +-- src/main/java/server/StatEffect.java | 3 +- src/main/java/server/life/LifeFactory.java | 2 +- .../java/server/life/MobSkillFactory.java | 21 +++--- .../server/partyquest/CarnivalFactory.java | 3 +- 11 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index b9972d4248..0713864928 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -64,10 +64,7 @@ import server.events.Events; import server.events.RescueGaga; import server.events.gm.Fitness; import server.events.gm.Ola; -import server.life.MobSkill; -import server.life.MobSkillFactory; -import server.life.Monster; -import server.life.PlayerNPC; +import server.life.*; import server.maps.*; import server.maps.MiniGame.MiniGameResult; import server.minigame.RockPaperScissor; @@ -7329,7 +7326,7 @@ public class Character extends AbstractCharacterObject { final int skilllv = rs.getInt("mobskilllv"); final long length = rs.getInt("length"); - MobSkill ms = MobSkillFactory.getMobSkill(skillid, skilllv); + MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(skillid), skilllv); if (ms != null) { loadedDiseases.put(disease, new Pair<>(length, ms)); } diff --git a/src/main/java/client/command/commands/gm2/MobSkillCommand.java b/src/main/java/client/command/commands/gm2/MobSkillCommand.java index bba008aef7..63b3cb8994 100644 --- a/src/main/java/client/command/commands/gm2/MobSkillCommand.java +++ b/src/main/java/client/command/commands/gm2/MobSkillCommand.java @@ -5,6 +5,7 @@ import client.Client; import client.command.Command; import server.life.MobSkill; import server.life.MobSkillFactory; +import server.life.MobSkillType; import java.util.Collections; @@ -21,7 +22,8 @@ public class MobSkillCommand extends Command { String skillId = params[0]; String skillLevel = params[1]; - MobSkill mobSkill = MobSkillFactory.getMobSkill(Integer.parseInt(skillId), Integer.parseInt(skillLevel)); + MobSkillType type = MobSkillType.from(Integer.parseInt(skillId)); + MobSkill mobSkill = MobSkillFactory.getMobSkill(type, Integer.parseInt(skillLevel)); if (mobSkill == null) { throw new IllegalArgumentException("Mob skill not found. Id: %s, level: %s".formatted(skillId, skillLevel)); } diff --git a/src/main/java/client/command/commands/gm3/DebuffCommand.java b/src/main/java/client/command/commands/gm3/DebuffCommand.java index aed248286d..bcd18ae9c1 100644 --- a/src/main/java/client/command/commands/gm3/DebuffCommand.java +++ b/src/main/java/client/command/commands/gm3/DebuffCommand.java @@ -29,6 +29,7 @@ import client.Disease; import client.command.Command; import server.life.MobSkill; import server.life.MobSkillFactory; +import server.life.MobSkillType; import server.maps.MapObject; import server.maps.MapObjectType; @@ -51,55 +52,46 @@ public class DebuffCommand extends Command { MobSkill skill = null; switch (params[0].toUpperCase()) { - case "SLOW": + case "SLOW" -> { disease = Disease.SLOW; - skill = MobSkillFactory.getMobSkill(126, 7); - break; - - case "SEDUCE": + skill = MobSkillFactory.getMobSkill(MobSkillType.SLOW, 7); + } + case "SEDUCE" -> { disease = Disease.SEDUCE; - skill = MobSkillFactory.getMobSkill(128, 7); - break; - - case "ZOMBIFY": + skill = MobSkillFactory.getMobSkill(MobSkillType.SEDUCE, 7); + } + case "ZOMBIFY" -> { disease = Disease.ZOMBIFY; - skill = MobSkillFactory.getMobSkill(133, 1); - break; - - case "CONFUSE": + skill = MobSkillFactory.getMobSkill(MobSkillType.UNDEAD, 1); + } + case "CONFUSE" -> { disease = Disease.CONFUSE; - skill = MobSkillFactory.getMobSkill(132, 2); - break; - - case "STUN": + skill = MobSkillFactory.getMobSkill(MobSkillType.REVERSE_INPUT, 2); + } + case "STUN" -> { disease = Disease.STUN; - skill = MobSkillFactory.getMobSkill(123, 7); - break; - - case "POISON": + skill = MobSkillFactory.getMobSkill(MobSkillType.STUN, 7); + } + case "POISON" -> { disease = Disease.POISON; - skill = MobSkillFactory.getMobSkill(125, 5); - break; - - case "SEAL": + skill = MobSkillFactory.getMobSkill(MobSkillType.POISON, 5); + } + case "SEAL" -> { disease = Disease.SEAL; - skill = MobSkillFactory.getMobSkill(120, 1); - break; - - case "DARKNESS": + skill = MobSkillFactory.getMobSkill(MobSkillType.SEAL, 1); + } + case "DARKNESS" -> { disease = Disease.DARKNESS; - skill = MobSkillFactory.getMobSkill(121, 1); - break; - - case "WEAKEN": + skill = MobSkillFactory.getMobSkill(MobSkillType.DARKNESS, 1); + } + case "WEAKEN" -> { disease = Disease.WEAKEN; - skill = MobSkillFactory.getMobSkill(122, 1); - break; - - case "CURSE": + skill = MobSkillFactory.getMobSkill(MobSkillType.WEAKNESS, 1); + } + case "CURSE" -> { disease = Disease.CURSE; - skill = MobSkillFactory.getMobSkill(124, 1); - break; + skill = MobSkillFactory.getMobSkill(MobSkillType.CURSE, 1); + } } if (disease == null) { diff --git a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java index 9343a4f8de..d438238c35 100644 --- a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -494,8 +494,8 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { List> mobSkills = monster.getSkills(); for (Pair ms : mobSkills) { - if (ms.left == 145) { - MobSkill toUse = MobSkillFactory.getMobSkill(ms.left, ms.right); + if (ms.left == 145) { // TODO: de-magic + MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, ms.right); player.addHP(-toUse.getX()); map.broadcastMessage(player, PacketCreator.damagePlayer(0, monster.getId(), player.getId(), toUse.getX(), 0, 0, false, 0, true, monster.getObjectId(), 0, 0), true); } @@ -505,8 +505,8 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { List> mobSkills = monster.getSkills(); for (Pair ms : mobSkills) { - if (ms.left == 145) { - MobSkill toUse = MobSkillFactory.getMobSkill(ms.left, ms.right); + if (ms.left == 145) { // TODO: de-magic + MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, ms.right); player.addHP(-toUse.getY()); map.broadcastMessage(player, PacketCreator.damagePlayer(0, monster.getId(), player.getId(), toUse.getY(), 0, 0, false, 0, true, monster.getObjectId(), 0, 0), true); } diff --git a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java index e29cc79277..3d522a02e5 100644 --- a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java @@ -96,7 +96,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { castPos = monster.getSkillPos(useSkillId, useSkillLevel); if (castPos != -1) { - toUse = MobSkillFactory.getMobSkill(useSkillId, useSkillLevel); + toUse = MobSkillFactory.getMobSkill(MobSkillType.from(useSkillId), useSkillLevel); if (monster.canUseSkill(toUse, true)) { int animationTime = MonsterInformationProvider.getInstance().getMobSkillAnimationTime(toUse); @@ -127,7 +127,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { Pair skillToUse = monster.getSkills().get(rndSkill); nextSkillId = skillToUse.getLeft(); nextSkillLevel = skillToUse.getRight(); - nextUse = MobSkillFactory.getMobSkill(nextSkillId, nextSkillLevel); + nextUse = MobSkillFactory.getMobSkill(MobSkillType.from(nextSkillId), nextSkillLevel); if (!(nextUse != null && monster.canUseSkill(nextUse, false) && nextUse.getHP() >= (int) (((float) monster.getHp() / monster.getMaxHp()) * 100) && mobMp >= nextUse.getMpCon())) { // thanks OishiiKawaiiDesu for noticing mobs trying to cast skills they are not supposed to be able diff --git a/src/main/java/net/server/channel/handlers/TakeDamageHandler.java b/src/main/java/net/server/channel/handlers/TakeDamageHandler.java index 28b6373c2a..4a3f331cf5 100644 --- a/src/main/java/net/server/channel/handlers/TakeDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/TakeDamageHandler.java @@ -151,7 +151,7 @@ public final class TakeDamageHandler extends AbstractPacketHandler { is_deadly = true; } mpattack += attackInfo.getMpBurn(); - MobSkill mobSkill = MobSkillFactory.getMobSkill(attackInfo.getDiseaseSkill(), attackInfo.getDiseaseLevel()); + MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.from(attackInfo.getDiseaseSkill()), attackInfo.getDiseaseLevel()); if (mobSkill != null && damage > 0) { mobSkill.applyEffect(chr, attacker, false, banishPlayers); } diff --git a/src/main/java/scripting/AbstractPlayerInteraction.java b/src/main/java/scripting/AbstractPlayerInteraction.java index 12a0f34f40..91f9ee40f7 100644 --- a/src/main/java/scripting/AbstractPlayerInteraction.java +++ b/src/main/java/scripting/AbstractPlayerInteraction.java @@ -965,10 +965,6 @@ public class AbstractPlayerInteraction { return LifeFactory.getMonster(mid); } - public MobSkill getMobSkill(int skill, int level) { - return MobSkillFactory.getMobSkill(skill, level); - } - public void spawnGuide() { c.sendPacket(PacketCreator.spawnGuide(true)); } @@ -1199,12 +1195,12 @@ public class AbstractPlayerInteraction { } private void applySealSkill(Monster monster) { - MobSkill sealSkill = MobSkillFactory.getMobSkill(157, 1); + MobSkill sealSkill = MobSkillFactory.getMobSkill(MobSkillType.SEAL_SKILL, 1); sealSkill.applyEffect(monster); } private void applyReduceAvoid(Monster monster) { - MobSkill reduceAvoidSkill = MobSkillFactory.getMobSkill(155, 2); + MobSkill reduceAvoidSkill = MobSkillFactory.getMobSkill(MobSkillType.EVA, 2); reduceAvoidSkill.applyEffect(monster); } diff --git a/src/main/java/server/StatEffect.java b/src/main/java/server/StatEffect.java index aa682f2e35..a0791a1281 100644 --- a/src/main/java/server/StatEffect.java +++ b/src/main/java/server/StatEffect.java @@ -42,6 +42,7 @@ import provider.Data; import provider.DataTool; import server.life.MobSkill; import server.life.MobSkillFactory; +import server.life.MobSkillType; import server.life.Monster; import server.maps.*; import server.partyquest.CarnivalFactory; @@ -1067,7 +1068,7 @@ public class StatEffect { applyfrom.dispelDebuff(debuff); } } else if (mobSkill > 0 && mobSkillLevel > 0) { - MobSkill ms = MobSkillFactory.getMobSkill(mobSkill, mobSkillLevel); + MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(mobSkill), mobSkillLevel); Disease dis = Disease.getBySkill(mobSkill); if (target > 0) { diff --git a/src/main/java/server/life/LifeFactory.java b/src/main/java/server/life/LifeFactory.java index 80eb10462c..772a4a5373 100644 --- a/src/main/java/server/life/LifeFactory.java +++ b/src/main/java/server/life/LifeFactory.java @@ -201,7 +201,7 @@ public class LifeFactory { animationTime += DataTool.getIntConvert("delay", effectEntry, 0); } - MobSkill skill = MobSkillFactory.getMobSkill(skillId, skillLv); + MobSkill skill = MobSkillFactory.getMobSkill(MobSkillType.from(skillId), skillLv); mi.setMobSkillAnimationTime(skill, animationTime); } diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index ea263cd21e..4de58ec8ad 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -47,11 +47,10 @@ public class MobSkillFactory { private static final Lock readLock = readWriteLock.readLock(); private static final Lock writeLock = readWriteLock.writeLock(); - // TODO: take in MobSkillType as argument instead of skillId - public static MobSkill getMobSkill(final int skillId, final int level) { // TODO: return Optional + public static MobSkill getMobSkill(final MobSkillType type, final int level) { // TODO: return Optional readLock.lock(); try { - MobSkill ms = mobSkills.get(createKey(skillId, level)); + MobSkill ms = mobSkills.get(createKey(type, level)); if (ms != null) { return ms; } @@ -59,18 +58,18 @@ public class MobSkillFactory { readLock.unlock(); } - return loadMobSkill(skillId, level).orElse(null); + return loadMobSkill(type, level).orElse(null); } - private static Optional loadMobSkill(final int skillId, final int level) { + private static Optional loadMobSkill(final MobSkillType type, final int level) { writeLock.lock(); try { - MobSkill existingMs = mobSkills.get(createKey(skillId, level)); + MobSkill existingMs = mobSkills.get(createKey(type, level)); if (existingMs != null) { return Optional.of(existingMs); } - Data skillData = skillRoot.getChildByPath(skillId + "/level/" + level); + Data skillData = skillRoot.getChildByPath(type + "/level/" + level); if (skillData == null) { return Optional.empty(); } @@ -103,7 +102,7 @@ public class MobSkillFactory { rb = (Point) rbData.getData(); } - MobSkill loadedMobSkill = new MobSkill.Builder(MobSkillType.from(skillId), level) + MobSkill loadedMobSkill = new MobSkill.Builder(type, level) .toSummon(toSummon) .cooltime(cooltime) .duration(duration) @@ -117,14 +116,14 @@ public class MobSkillFactory { .rb(rb) .build(); - mobSkills.put(createKey(skillId, level), loadedMobSkill); + mobSkills.put(createKey(type, level), loadedMobSkill); return Optional.of(loadedMobSkill); } finally { writeLock.unlock(); } } - private static String createKey(int skillId, int skillLevel) { - return skillId + "" + skillLevel; + private static String createKey(MobSkillType type, int skillLevel) { + return type.getId() + "" + skillLevel; } } diff --git a/src/main/java/server/partyquest/CarnivalFactory.java b/src/main/java/server/partyquest/CarnivalFactory.java index 5eb40a6224..19c55bc61e 100644 --- a/src/main/java/server/partyquest/CarnivalFactory.java +++ b/src/main/java/server/partyquest/CarnivalFactory.java @@ -8,6 +8,7 @@ import provider.DataTool; import provider.wz.WZFiles; import server.life.MobSkill; import server.life.MobSkillFactory; +import server.life.MobSkillType; import java.util.ArrayList; import java.util.HashMap; @@ -94,7 +95,7 @@ public class CarnivalFactory { } public static MobSkill getMobSkill(int skillid, int level) { - return MobSkillFactory.getMobSkill(skillid, level); + return MobSkillFactory.getMobSkill(MobSkillType.from(skillid), level); } public Disease getDisease() { From 7a784a7938e983f32d9758874aef44bf1005eb2f Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 05:20:54 +0200 Subject: [PATCH 07/16] Disease and MCSkill hold MobSkillType instead of int --- src/main/java/client/Disease.java | 54 +++++++++---------- .../handlers/MonsterCarnivalHandler.java | 13 +++-- src/main/java/server/StatEffect.java | 9 ++-- .../server/partyquest/CarnivalFactory.java | 34 ++++++++---- 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/main/java/client/Disease.java b/src/main/java/client/Disease.java index 6e8d4ec7be..57aa471e57 100644 --- a/src/main/java/client/Disease.java +++ b/src/main/java/client/Disease.java @@ -22,37 +22,34 @@ package client; import constants.game.GameConstants; +import server.life.MobSkillType; + +import java.util.Arrays; public enum Disease { NULL(0x0), - SLOW(0x1, 126), - SEDUCE(0x80, 128), + SLOW(0x1, MobSkillType.SLOW), + SEDUCE(0x80, MobSkillType.SEDUCE), FISHABLE(0x100), ZOMBIFY(0x4000), - CONFUSE(0x80000, 132), - STUN(0x2000000000000L, 123), - POISON(0x4000000000000L, 125), - SEAL(0x8000000000000L, 120), - DARKNESS(0x10000000000000L, 121), - WEAKEN(0x4000000000000000L, 122), - CURSE(0x8000000000000000L, 124); + CONFUSE(0x80000, MobSkillType.REVERSE_INPUT), + STUN(0x2000000000000L, MobSkillType.STUN), + POISON(0x4000000000000L, MobSkillType.POISON), + SEAL(0x8000000000000L, MobSkillType.SEAL), + DARKNESS(0x10000000000000L, MobSkillType.DARKNESS), + WEAKEN(0x4000000000000000L, MobSkillType.WEAKNESS), + CURSE(0x8000000000000000L, MobSkillType.CURSE); private final long i; - private final boolean first; - private final int mobskill; + private final MobSkillType mobSkillType; Disease(long i) { - this(i, false, 0); + this(i, null); } - Disease(long i, int skill) { - this(i, false, skill); - } - - Disease(long i, boolean first, int skill) { + Disease(long i, MobSkillType skill) { this.i = i; - this.first = first; - this.mobskill = skill; + this.mobSkillType = skill; } public long getValue() { @@ -60,11 +57,11 @@ public enum Disease { } public boolean isFirst() { - return first; + return false; } - public int getDisease() { - return mobskill; + public MobSkillType getMobSkillType() { + return mobSkillType; } public static Disease ordinal(int ord) { @@ -80,13 +77,14 @@ public enum Disease { return diseases[(int) (Math.random() * diseases.length)]; } - public static final Disease getBySkill(final int skill) { - for (Disease d : Disease.values()) { - if (d.getDisease() == skill && d.getDisease() != 0) { - return d; - } + public static final Disease getBySkill(MobSkillType skill) { + if (skill == null) { + return null; } - return null; + return Arrays.stream(Disease.values()) + .filter(d -> d.mobSkillType == skill) + .findAny() + .orElse(null); } } \ No newline at end of file diff --git a/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java b/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java index a477e9edd9..302562e1c7 100644 --- a/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java +++ b/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java @@ -29,6 +29,7 @@ import net.packet.InPacket; import net.server.world.Party; import net.server.world.PartyCharacter; import server.life.LifeFactory; +import server.life.MobSkillType; import server.life.Monster; import server.partyquest.CarnivalFactory; import server.partyquest.CarnivalFactory.MCSkill; @@ -102,10 +103,7 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler { final Disease dis = skill.getDisease(); Party enemies = c.getPlayer().getParty().getEnemy(); if (skill.targetsAll) { - int hitChance = 0; - if (dis.getDisease() == 121 || dis.getDisease() == 122 || dis.getDisease() == 125 || dis.getDisease() == 126) { - hitChance = (int) (Math.random() * 100); - } + int hitChance = rollHitChance(dis.getMobSkillType()); if (hitChance <= 80) { for (PartyCharacter mpc : enemies.getPartyMembers()) { Character mc = mpc.getPlayer(); @@ -179,4 +177,11 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler { } } } + + private int rollHitChance(MobSkillType type) { + return switch (type) { + case DARKNESS, WEAKNESS, POISON, SLOW -> (int) (Math.random() * 100); + default -> 0; + }; + } } diff --git a/src/main/java/server/StatEffect.java b/src/main/java/server/StatEffect.java index a0791a1281..9a42178dca 100644 --- a/src/main/java/server/StatEffect.java +++ b/src/main/java/server/StatEffect.java @@ -1046,7 +1046,7 @@ public class StatEffect { if (dis == null) { chrApp.dispel(); } else { - chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getDisease(), skill.level)); + chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getMobSkillType(), skill.level)); } } } @@ -1058,7 +1058,7 @@ public class StatEffect { if (dis == null) { chrApp.dispel(); } else { - chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getDisease(), skill.level)); + chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getMobSkillType(), skill.level)); } } } @@ -1068,8 +1068,9 @@ public class StatEffect { applyfrom.dispelDebuff(debuff); } } else if (mobSkill > 0 && mobSkillLevel > 0) { - MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(mobSkill), mobSkillLevel); - Disease dis = Disease.getBySkill(mobSkill); + var mobSkillType = MobSkillType.from(mobSkill); + MobSkill ms = MobSkillFactory.getMobSkill(mobSkillType, mobSkillLevel); + Disease dis = Disease.getBySkill(mobSkillType); if (target > 0) { for (Character chr : applyto.getMap().getAllPlayers()) { diff --git a/src/main/java/server/partyquest/CarnivalFactory.java b/src/main/java/server/partyquest/CarnivalFactory.java index 19c55bc61e..3b852504df 100644 --- a/src/main/java/server/partyquest/CarnivalFactory.java +++ b/src/main/java/server/partyquest/CarnivalFactory.java @@ -43,7 +43,15 @@ public class CarnivalFactory { } for (Data z : dataRoot.getData("MCSkill.img")) { Integer id = Integer.parseInt(z.getName()); - MCSkill ms = new MCSkill(DataTool.getInt("spendCP", z, 0), DataTool.getInt("mobSkillID", z, 0), DataTool.getInt("level", z, 0), DataTool.getInt("target", z, 1) > 1); + int spendCp = DataTool.getInt("spendCP", z, 0); + int mobSkillId = DataTool.getInt("mobSkillID", z, 0); + MobSkillType mobSkillType = null; + if (mobSkillId != 0) { + mobSkillType = MobSkillType.from(mobSkillId); + } + int level = DataTool.getInt("level", z, 0); + boolean isMultiTarget = DataTool.getInt("target", z, 1) > 1; + MCSkill ms = new MCSkill(spendCp, mobSkillType, level, isMultiTarget); skills.put(id, ms); if (ms.targetsAll) { @@ -53,7 +61,11 @@ public class CarnivalFactory { } } for (Data z : dataRoot.getData("MCGuardian.img")) { - guardians.put(Integer.parseInt(z.getName()), new MCSkill(DataTool.getInt("spendCP", z, 0), DataTool.getInt("mobSkillID", z, 0), DataTool.getInt("level", z, 0), true)); + int spendCp = DataTool.getInt("spendCP", z, 0); + int mobSkillId = DataTool.getInt("mobSkillID", z, 0); + MobSkillType mobSkillType = MobSkillType.from(mobSkillId); + int level = DataTool.getInt("level", z, 0); + guardians.put(Integer.parseInt(z.getName()), new MCSkill(spendCp, mobSkillType, level, true)); } } @@ -67,7 +79,7 @@ public class CarnivalFactory { public MCSkill getSkill(final int id) { MCSkill skill = skills.get(id); - if (skill != null && skill.skillid <= 0) { + if (skill != null && skill.mobSkillType == null) { return randomizeSkill(skill.targetsAll); } else { return skill; @@ -80,26 +92,28 @@ public class CarnivalFactory { public static class MCSkill { - public int cpLoss, skillid, level; + public int cpLoss; + public MobSkillType mobSkillType; + public int level; public boolean targetsAll; - public MCSkill(int _cpLoss, int _skillid, int _level, boolean _targetsAll) { + public MCSkill(int _cpLoss, MobSkillType mobSkillType, int _level, boolean _targetsAll) { cpLoss = _cpLoss; - skillid = _skillid; + mobSkillType = mobSkillType; level = _level; targetsAll = _targetsAll; } public MobSkill getSkill() { - return getMobSkill(skillid, level); + return getMobSkill(mobSkillType, level); } - public static MobSkill getMobSkill(int skillid, int level) { - return MobSkillFactory.getMobSkill(MobSkillType.from(skillid), level); + public static MobSkill getMobSkill(MobSkillType type, int level) { + return MobSkillFactory.getMobSkill(type, level); } public Disease getDisease() { - return Disease.getBySkill(skillid); + return Disease.getBySkill(mobSkillType); } } } From 930d365752e0e5c289b34448ce81eb54f5c8ce20 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 11:01:51 +0200 Subject: [PATCH 08/16] Add record MobSkillId for mob skill type + level combination Pair is nasty to work with when they are passed around all over --- .../handlers/AbstractDealDamageHandler.java | 17 ++++++----------- .../channel/handlers/MoveLifeHandler.java | 7 +++---- src/main/java/server/life/LifeFactory.java | 7 ++++--- src/main/java/server/life/MobSkillFactory.java | 2 +- src/main/java/server/life/MobSkillId.java | 3 +++ src/main/java/server/life/Monster.java | 8 ++++---- src/main/java/server/life/MonsterStats.java | 11 +++++------ .../tools/mapletools/MonsterStatFetcher.java | 11 ++++++----- 8 files changed, 32 insertions(+), 34 deletions(-) create mode 100644 src/main/java/server/life/MobSkillId.java diff --git a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java index d438238c35..c2f8f09580 100644 --- a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -44,7 +44,6 @@ import server.maps.MapObject; import server.maps.MapObjectType; import server.maps.MapleMap; import tools.PacketCreator; -import tools.Pair; import tools.Randomizer; import java.awt.*; @@ -491,22 +490,18 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { map.damageMonster(player, monster, totDamageToOneMonster); } if (monster.isBuffed(MonsterStatus.WEAPON_REFLECT) && !attack.magic) { - List> mobSkills = monster.getSkills(); - - for (Pair ms : mobSkills) { - if (ms.left == 145) { // TODO: de-magic - MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, ms.right); + for (MobSkillId msId : monster.getSkills()) { + if (msId.type() == MobSkillType.PHYSICAL_AND_MAGIC_COUNTER) { + MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, msId.level()); player.addHP(-toUse.getX()); map.broadcastMessage(player, PacketCreator.damagePlayer(0, monster.getId(), player.getId(), toUse.getX(), 0, 0, false, 0, true, monster.getObjectId(), 0, 0), true); } } } if (monster.isBuffed(MonsterStatus.MAGIC_REFLECT) && attack.magic) { - List> mobSkills = monster.getSkills(); - - for (Pair ms : mobSkills) { - if (ms.left == 145) { // TODO: de-magic - MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, ms.right); + for (MobSkillId msId : monster.getSkills()) { + if (msId.type() == MobSkillType.PHYSICAL_AND_MAGIC_COUNTER) { + MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, msId.level()); player.addHP(-toUse.getY()); map.broadcastMessage(player, PacketCreator.damagePlayer(0, monster.getId(), player.getId(), toUse.getY(), 0, 0, false, 0, true, monster.getObjectId(), 0, 0), true); } diff --git a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java index 3d522a02e5..c00b76b60c 100644 --- a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java @@ -32,7 +32,6 @@ import server.maps.MapObject; import server.maps.MapObjectType; import server.maps.MapleMap; import tools.PacketCreator; -import tools.Pair; import tools.Randomizer; import tools.exceptions.EmptyMovementException; @@ -124,9 +123,9 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { if (noSkills > 0) { int rndSkill = Randomizer.nextInt(noSkills); - Pair skillToUse = monster.getSkills().get(rndSkill); - nextSkillId = skillToUse.getLeft(); - nextSkillLevel = skillToUse.getRight(); + MobSkillId skillToUse = monster.getSkills().get(rndSkill); + nextSkillId = skillToUse.type().getId(); + nextSkillLevel = skillToUse.level(); nextUse = MobSkillFactory.getMobSkill(MobSkillType.from(nextSkillId), nextSkillLevel); if (!(nextUse != null && monster.canUseSkill(nextUse, false) && nextUse.getHP() >= (int) (((float) monster.getHp() / monster.getMaxHp()) * 100) && mobMp >= nextUse.getMpCon())) { diff --git a/src/main/java/server/life/LifeFactory.java b/src/main/java/server/life/LifeFactory.java index 772a4a5373..b465ef8917 100644 --- a/src/main/java/server/life/LifeFactory.java +++ b/src/main/java/server/life/LifeFactory.java @@ -188,11 +188,12 @@ public class LifeFactory { Data monsterSkillInfoData = monsterInfoData.getChildByPath("skill"); if (monsterSkillInfoData != null) { int i = 0; - List> skills = new ArrayList<>(); + List skills = new ArrayList<>(); while (monsterSkillInfoData.getChildByPath(Integer.toString(i)) != null) { int skillId = DataTool.getInt(i + "/skill", monsterSkillInfoData, 0); int skillLv = DataTool.getInt(i + "/level", monsterSkillInfoData, 0); - skills.add(new Pair<>(skillId, skillLv)); + MobSkillType type = MobSkillType.from(skillId); + skills.add(new MobSkillId(type, skillLv)); Data monsterSkillData = monsterData.getChildByPath("skill" + (i + 1)); if (monsterSkillData != null) { @@ -201,7 +202,7 @@ public class LifeFactory { animationTime += DataTool.getIntConvert("delay", effectEntry, 0); } - MobSkill skill = MobSkillFactory.getMobSkill(MobSkillType.from(skillId), skillLv); + MobSkill skill = MobSkillFactory.getMobSkill(type, skillLv); mi.setMobSkillAnimationTime(skill, animationTime); } diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 4de58ec8ad..e2d85daebb 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -69,7 +69,7 @@ public class MobSkillFactory { return Optional.of(existingMs); } - Data skillData = skillRoot.getChildByPath(type + "/level/" + level); + Data skillData = skillRoot.getChildByPath("%d/level/%d".formatted(type.getId(), level)); if (skillData == null) { return Optional.empty(); } diff --git a/src/main/java/server/life/MobSkillId.java b/src/main/java/server/life/MobSkillId.java new file mode 100644 index 0000000000..0e80304ca0 --- /dev/null +++ b/src/main/java/server/life/MobSkillId.java @@ -0,0 +1,3 @@ +package server.life; + +public record MobSkillId(MobSkillType type, int level) {} diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index 77c1bb9dd9..9e836e403a 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -1426,7 +1426,7 @@ public class Monster extends AbstractLoadedLife { return map.getAggroCoordinator(); } - public List> getSkills() { + public List getSkills() { return stats.getSkills(); } @@ -1436,8 +1436,8 @@ public class Monster extends AbstractLoadedLife { public int getSkillPos(int skillId, int level) { int pos = 0; - for (Pair ms : this.getSkills()) { - if (ms.getLeft() == skillId && ms.getRight() == level) { + for (MobSkillId ms : this.getSkills()) { + if (ms.type().getId() == skillId && ms.level() == level) { return pos; } @@ -1490,7 +1490,7 @@ public class Monster extends AbstractLoadedLife { private boolean isReflectSkill(MobSkill mobSkill) { return switch (mobSkill.getType()) { case PHYSICAL_COUNTER, MAGIC_COUNTER, PHYSICAL_AND_MAGIC_COUNTER -> true; - default -> false ; + default -> false; }; } diff --git a/src/main/java/server/life/MonsterStats.java b/src/main/java/server/life/MonsterStats.java index 8a7cecddbd..05fa81aa4a 100644 --- a/src/main/java/server/life/MonsterStats.java +++ b/src/main/java/server/life/MonsterStats.java @@ -41,7 +41,7 @@ public class MonsterStats { public Map resistance = new HashMap<>(); public List revives = Collections.emptyList(); public byte tagColor, tagBgColor; - public List> skills = new ArrayList<>(); + public List skills = new ArrayList<>(); public Pair cool = null; public BanishInfo banish = null; public List loseItem = null; @@ -190,17 +190,16 @@ public class MonsterStats { this.tagBgColor = (byte) tagBgColor; } - public void setSkills(List> skills) { + public void setSkills(List skills) { for (int i = this.skills.size(); i < skills.size(); i++) { this.skills.add(null); } - for (int i = 0; i < skills.size(); i++) { this.skills.set(i, skills.get(i)); } } - public List> getSkills() { + public List getSkills() { return Collections.unmodifiableList(this.skills); } @@ -209,8 +208,8 @@ public class MonsterStats { } public boolean hasSkill(int skillId, int level) { - for (Pair skill : skills) { - if (skill.getLeft() == skillId && skill.getRight() == level) { + for (MobSkillId skill : skills) { + if (skill.type().getId() == skillId && skill.level() == level) { return true; } } diff --git a/src/main/java/tools/mapletools/MonsterStatFetcher.java b/src/main/java/tools/mapletools/MonsterStatFetcher.java index 508e6d0815..641727856a 100644 --- a/src/main/java/tools/mapletools/MonsterStatFetcher.java +++ b/src/main/java/tools/mapletools/MonsterStatFetcher.java @@ -3,12 +3,10 @@ package tools.mapletools; import provider.*; import provider.wz.DataType; import provider.wz.WZFiles; -import server.life.Element; -import server.life.ElementalEffectiveness; +import server.life.*; import server.life.LifeFactory.BanishInfo; import server.life.LifeFactory.loseItem; import server.life.LifeFactory.selfDestruction; -import server.life.MonsterStats; import tools.Pair; import java.time.Duration; @@ -110,9 +108,12 @@ public class MonsterStatFetcher { Data monsterSkillData = monsterInfoData.getChildByPath("skill"); if (monsterSkillData != null) { int i = 0; - List> skills = new ArrayList<>(); + List skills = new ArrayList<>(); while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) { - skills.add(new Pair<>(DataTool.getInt(i + "/skill", monsterSkillData, 0), DataTool.getInt(i + "/level", monsterSkillData, 0))); + int skillId = DataTool.getInt(i + "/skill", monsterSkillData, 0); + MobSkillType type = MobSkillType.from(skillId); + int skillLevel = DataTool.getInt(i + "/level", monsterSkillData, 0); + skills.add(new MobSkillId(type, skillLevel)); i++; } stats.setSkills(skills); From c47ca4d6a4283cd397ad7af7481d752dc9c4cd53 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 13:11:45 +0200 Subject: [PATCH 09/16] Fix MobSkillType always being ATTACK_UP --- src/main/java/server/life/MobSkillType.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/server/life/MobSkillType.java b/src/main/java/server/life/MobSkillType.java index c16b8ba3cb..cf38c5e057 100644 --- a/src/main/java/server/life/MobSkillType.java +++ b/src/main/java/server/life/MobSkillType.java @@ -52,7 +52,10 @@ public enum MobSkillType { } public static MobSkillType from(int id) { - return Arrays.stream(values()).findFirst().orElseThrow(IllegalArgumentException::new); + return Arrays.stream(values()) + .filter(type -> type.getId() == id) + .findFirst() + .orElseThrow(IllegalArgumentException::new); } public int getId() { From 7cdaabf6f8cd46ac977356492eca03b4104fc823 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 13:30:44 +0200 Subject: [PATCH 10/16] Simplify Monster skills with Set instead of List Doable now with MobSkillId records --- .../channel/handlers/MoveLifeHandler.java | 40 +++++++------------ src/main/java/server/life/LifeFactory.java | 2 +- src/main/java/server/life/Monster.java | 31 +++++++------- src/main/java/server/life/MonsterStats.java | 15 +++---- .../tools/mapletools/MonsterStatFetcher.java | 2 +- 5 files changed, 37 insertions(+), 53 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java index c00b76b60c..4b7c7d6493 100644 --- a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java @@ -32,7 +32,6 @@ import server.maps.MapObject; import server.maps.MapObjectType; import server.maps.MapleMap; import tools.PacketCreator; -import tools.Randomizer; import tools.exceptions.EmptyMovementException; import java.awt.*; @@ -80,7 +79,6 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { boolean isAttack = inRangeInclusive(rawActivity, 24, 41); boolean isSkill = inRangeInclusive(rawActivity, 42, 59); - MobSkill toUse = null; int useSkillId = 0, useSkillLevel = 0; MobSkill nextUse = null; @@ -88,14 +86,12 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { boolean nextMovementCouldBeSkill = !(isSkill || (pNibbles != 0)); - int castPos; if (isSkill) { useSkillId = skillId; useSkillLevel = skillLv; - castPos = monster.getSkillPos(useSkillId, useSkillLevel); - if (castPos != -1) { - toUse = MobSkillFactory.getMobSkill(MobSkillType.from(useSkillId), useSkillLevel); + if (monster.hasSkill(useSkillId, useSkillLevel)) { + MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.from(useSkillId), useSkillLevel); if (monster.canUseSkill(toUse, true)) { int animationTime = MonsterInformationProvider.getInstance().getMobSkillAnimationTime(toUse); @@ -108,8 +104,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { } } } else { - castPos = (rawActivity - 24) / 2; - + int castPos = (rawActivity - 24) / 2; int atkStatus = monster.canUseAttack(castPos, isSkill); if (atkStatus < 1) { rawActivity = -1; @@ -118,23 +113,18 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { } int mobMp = monster.getMp(); - if (nextMovementCouldBeSkill) { - int noSkills = monster.getNoSkills(); - if (noSkills > 0) { - int rndSkill = Randomizer.nextInt(noSkills); + if (nextMovementCouldBeSkill && monster.hasAnySkill()) { + MobSkillId skillToUse = monster.getRandomSkill(); + nextSkillId = skillToUse.type().getId(); + nextSkillLevel = skillToUse.level(); + nextUse = MobSkillFactory.getMobSkill(skillToUse.type(), skillToUse.level()); - MobSkillId skillToUse = monster.getSkills().get(rndSkill); - nextSkillId = skillToUse.type().getId(); - nextSkillLevel = skillToUse.level(); - nextUse = MobSkillFactory.getMobSkill(MobSkillType.from(nextSkillId), nextSkillLevel); + if (!(nextUse != null && monster.canUseSkill(nextUse, false) && nextUse.getHP() >= (int) (((float) monster.getHp() / monster.getMaxHp()) * 100) && mobMp >= nextUse.getMpCon())) { + // thanks OishiiKawaiiDesu for noticing mobs trying to cast skills they are not supposed to be able - if (!(nextUse != null && monster.canUseSkill(nextUse, false) && nextUse.getHP() >= (int) (((float) monster.getHp() / monster.getMaxHp()) * 100) && mobMp >= nextUse.getMpCon())) { - // thanks OishiiKawaiiDesu for noticing mobs trying to cast skills they are not supposed to be able - - nextSkillId = 0; - nextSkillLevel = 0; - nextUse = null; - } + nextSkillId = 0; + nextSkillLevel = 0; + nextUse = null; } } @@ -164,8 +154,8 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { p.seek(movementDataStart); if (YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_MVLIFE) { - log.debug("{} castPos: {}, rawAct: {}, opt: {}, skillId: {}, skillLv: {}, allowSkill: {}, mobMp: {}", - isSkill ? "SKILL" : (isAttack ? "ATTCK" : ""), castPos, rawActivity, pOption, useSkillId, + log.debug("{} rawAct: {}, opt: {}, skillId: {}, skillLv: {}, allowSkill: {}, mobMp: {}", + isSkill ? "SKILL" : (isAttack ? "ATTCK" : ""), rawActivity, pOption, useSkillId, useSkillLevel, nextMovementCouldBeSkill, mobMp); } diff --git a/src/main/java/server/life/LifeFactory.java b/src/main/java/server/life/LifeFactory.java index b465ef8917..000ec75174 100644 --- a/src/main/java/server/life/LifeFactory.java +++ b/src/main/java/server/life/LifeFactory.java @@ -188,7 +188,7 @@ public class LifeFactory { Data monsterSkillInfoData = monsterInfoData.getChildByPath("skill"); if (monsterSkillInfoData != null) { int i = 0; - List skills = new ArrayList<>(); + Set skills = new HashSet<>(); while (monsterSkillInfoData.getChildByPath(Integer.toString(i)) != null) { int skillId = DataTool.getInt(i + "/skill", monsterSkillInfoData, 0); int skillLv = DataTool.getInt(i + "/level", monsterSkillInfoData, 0); diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index 9e836e403a..7a254d0fb1 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -1426,7 +1426,7 @@ public class Monster extends AbstractLoadedLife { return map.getAggroCoordinator(); } - public List getSkills() { + public Set getSkills() { return stats.getSkills(); } @@ -1434,19 +1434,6 @@ public class Monster extends AbstractLoadedLife { return stats.hasSkill(skillId, level); } - public int getSkillPos(int skillId, int level) { - int pos = 0; - for (MobSkillId ms : this.getSkills()) { - if (ms.type().getId() == skillId && ms.level() == level) { - return pos; - } - - pos++; - } - - return -1; - } - public boolean canUseSkill(MobSkill toUse, boolean apply) { if (toUse == null || isBuffed(MonsterStatus.SEAL_SKILL)) { return false; @@ -1600,8 +1587,20 @@ public class Monster extends AbstractLoadedLife { } } - public int getNoSkills() { - return this.stats.getNoSkills(); + public boolean hasAnySkill() { + return this.stats.getNoSkills() > 0; + } + + public MobSkillId getRandomSkill() { + Set skills = stats.getSkills(); + if (skills.size() == 0) { + return null; + } + // There is no simple way of getting a random element from a Set. Have to make do with this. + return skills.stream() + .skip(Randomizer.nextInt(skills.size())) + .findAny() + .orElse(null); } public boolean isFirstAttack() { diff --git a/src/main/java/server/life/MonsterStats.java b/src/main/java/server/life/MonsterStats.java index 05fa81aa4a..093adc9334 100644 --- a/src/main/java/server/life/MonsterStats.java +++ b/src/main/java/server/life/MonsterStats.java @@ -41,7 +41,7 @@ public class MonsterStats { public Map resistance = new HashMap<>(); public List revives = Collections.emptyList(); public byte tagColor, tagBgColor; - public List skills = new ArrayList<>(); + public Set skills = new HashSet<>(); public Pair cool = null; public BanishInfo banish = null; public List loseItem = null; @@ -190,17 +190,12 @@ public class MonsterStats { this.tagBgColor = (byte) tagBgColor; } - public void setSkills(List skills) { - for (int i = this.skills.size(); i < skills.size(); i++) { - this.skills.add(null); - } - for (int i = 0; i < skills.size(); i++) { - this.skills.set(i, skills.get(i)); - } + public void setSkills(Set skills) { + this.skills = skills; } - public List getSkills() { - return Collections.unmodifiableList(this.skills); + public Set getSkills() { + return Collections.unmodifiableSet(this.skills); } public int getNoSkills() { diff --git a/src/main/java/tools/mapletools/MonsterStatFetcher.java b/src/main/java/tools/mapletools/MonsterStatFetcher.java index 641727856a..8b0dc826a6 100644 --- a/src/main/java/tools/mapletools/MonsterStatFetcher.java +++ b/src/main/java/tools/mapletools/MonsterStatFetcher.java @@ -108,7 +108,7 @@ public class MonsterStatFetcher { Data monsterSkillData = monsterInfoData.getChildByPath("skill"); if (monsterSkillData != null) { int i = 0; - List skills = new ArrayList<>(); + Set skills = new HashSet<>(); while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) { int skillId = DataTool.getInt(i + "/skill", monsterSkillData, 0); MobSkillType type = MobSkillType.from(skillId); From a6ec6adbb5ce3ee08e0426c2b83a5c42dc62fef1 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 14:07:24 +0200 Subject: [PATCH 11/16] Combine type and skillLevel in MobSkill --- src/main/java/client/Character.java | 5 ++-- src/main/java/server/life/MobSkill.java | 26 ++++++++----------- src/main/java/server/life/Monster.java | 11 +++++---- src/main/java/server/maps/Mist.java | 4 +-- src/main/java/tools/PacketCreator.java | 33 +++++++++++++------------ 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 0713864928..1a2f148638 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8108,8 +8108,9 @@ public class Character extends AbstractCharacterObject { ps.setInt(2, e.getKey().ordinal()); MobSkill ms = e.getValue().getRight(); - ps.setInt(3, ms.getSkillId()); - ps.setInt(4, ms.getSkillLevel()); + MobSkillId msId = ms.getId(); + ps.setInt(3, msId.type().getId()); + ps.setInt(4, msId.level()); ps.setInt(5, e.getValue().getLeft().intValue()); ps.addBatch(); } diff --git a/src/main/java/server/life/MobSkill.java b/src/main/java/server/life/MobSkill.java index 0ab6ca91fb..560a2262e2 100644 --- a/src/main/java/server/life/MobSkill.java +++ b/src/main/java/server/life/MobSkill.java @@ -47,8 +47,7 @@ import java.util.*; public class MobSkill { private static final Logger log = LoggerFactory.getLogger(MobSkill.class); - private final MobSkillType type; - private final int skillLevel; + private final MobSkillId id; private final int mpCon; private final int spawnEffect; private final int hp; @@ -65,8 +64,7 @@ public class MobSkill { private MobSkill(MobSkillType type, int level, int mpCon, int spawnEffect, int hp, int x, int y, int count, long duration, long cooltime, float prop, Point lt, Point rb, int limit, List toSummon) { - this.type = type; - this.skillLevel = level; + this.id = new MobSkillId(type, level); this.mpCon = mpCon; this.spawnEffect = spawnEffect; this.hp = hp; @@ -195,7 +193,7 @@ public class MobSkill { Disease disease = null; Map stats = new EnumMap<>(MonsterStatus.class); List reflection = new ArrayList<>(); - switch (type) { + switch (id.type()) { case ATTACK_UP, ATTACK_UP_M, PAD -> stats.put(MonsterStatus.WEAPON_ATTACK_UP, x); case MAGIC_ATTACK_UP, MAGIC_ATTACK_UP_M, MAD -> stats.put(MonsterStatus.MAGIC_ATTACK_UP, x); case DEFENSE_UP, DEFENSE_UP_M, PDR -> stats.put(MonsterStatus.WEAPON_DEFENSE_UP, x); @@ -368,10 +366,10 @@ public class MobSkill { private void applyMonsterBuffs(Map stats, boolean skill, Monster monster, List reflection) { if (lt != null && rb != null && skill) { for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) { - ((Monster) mons).applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); + ((Monster) mons).applyMonsterBuff(stats, getX(), getDuration(), this, reflection); } } else { - monster.applyMonsterBuff(stats, getX(), type.getId(), getDuration(), this, reflection); + monster.applyMonsterBuff(stats, getX(), getDuration(), this, reflection); } } @@ -399,16 +397,12 @@ public class MobSkill { return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition())); } + public MobSkillId getId() { + return id; + } + public MobSkillType getType() { - return type; - } - - public int getSkillId() { - return type.getId(); - } - - public int getSkillLevel() { - return skillLevel; + return id.type(); } public int getMpCon() { diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index 7a254d0fb1..ee000abf84 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -82,7 +82,7 @@ public class Monster extends AbstractLoadedLife { private int VenomMultiplier = 0; private boolean fake = false; private boolean dropsDisabled = false; - private final List> usedSkills = new ArrayList<>(); + private final List> usedSkills = new ArrayList<>(); // TODO: change to Set private final Map, Integer> skillsUsed = new HashMap<>(); private final Set usedAttacks = new HashSet<>(); private Set calledMobOids = null; @@ -1289,7 +1289,7 @@ public class Monster extends AbstractLoadedLife { } } - public void applyMonsterBuff(final Map stats, final int x, int skillId, long duration, MobSkill skill, final List reflection) { + public void applyMonsterBuff(final Map stats, final int x, long duration, MobSkill skill, final List reflection) { final Runnable cancelTask = () -> { if (isAlive()) { Packet packet = PacketCreator.cancelMonsterStatus(getObjectId(), stats); @@ -1448,7 +1448,8 @@ public class Monster extends AbstractLoadedLife { monsterLock.lock(); try { for (Pair skill : usedSkills) { // thanks OishiiKawaiiDesu for noticing an issue with mobskill cooldown - if (skill.getLeft() == toUse.getSkillId() && skill.getRight() == toUse.getSkillLevel()) { + MobSkillId msId = toUse.getId(); + if (skill.getLeft() == msId.type().getId() && skill.getRight() == msId.level()) { return false; } } @@ -1482,8 +1483,8 @@ public class Monster extends AbstractLoadedLife { } private void usedSkill(MobSkill skill) { - final int skillId = skill.getSkillId(); - final int level = skill.getSkillLevel(); + final int skillId = skill.getId().type().getId(); + final int level = skill.getId().level(); long cooltime = skill.getCoolTime(); monsterLock.lock(); diff --git a/src/main/java/server/maps/Mist.java b/src/main/java/server/maps/Mist.java index 0b9960824e..6aa41391d8 100644 --- a/src/main/java/server/maps/Mist.java +++ b/src/main/java/server/maps/Mist.java @@ -138,14 +138,14 @@ public class Mist extends AbstractMapObject { if (owner != null) { return PacketCreator.spawnMist(getObjectId(), owner.getId(), getSourceSkill().getId(), owner.getSkillLevel(SkillFactory.getSkill(source.getSourceId())), this); } - return PacketCreator.spawnMist(getObjectId(), mob.getId(), skill.getSkillId(), skill.getSkillLevel(), this); + return PacketCreator.spawnMobMist(getObjectId(), mob.getId(), skill.getId(), this); } public final Packet makeFakeSpawnData(int level) { if (owner != null) { return PacketCreator.spawnMist(getObjectId(), owner.getId(), getSourceSkill().getId(), level, this); } - return PacketCreator.spawnMist(getObjectId(), mob.getId(), skill.getSkillId(), skill.getSkillLevel(), this); + return PacketCreator.spawnMobMist(getObjectId(), mob.getId(), skill.getId(), this); } @Override diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 6df026de3d..54629626b3 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -64,10 +64,7 @@ import server.CashShop.CashItemFactory; import server.CashShop.SpecialCashItem; import server.*; import server.events.gm.Snowball; -import server.life.MobSkill; -import server.life.Monster; -import server.life.NPC; -import server.life.PlayerNPC; +import server.life.*; import server.maps.*; import server.maps.MiniGame.MiniGameResult; import server.movement.LifeMovementFragment; @@ -106,6 +103,11 @@ public class PacketCreator { return utcTimestamp * 10000 + FT_UT_OFFSET; } + private static void writeMobSkillId(OutPacket packet, MobSkillId msId) { + packet.writeShort(msId.type().getId()); + packet.writeShort(msId.level()); + } + public static Packet showHpHealed(int cid, int amount) { OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT); p.writeInt(cid); @@ -1410,8 +1412,7 @@ public class PacketCreator { MobSkill mobSkill = mse.getMobSkill(); if (mobSkill != null) { - p.writeShort(mobSkill.getSkillId()); - p.writeShort(mobSkill.getSkillLevel()); + writeMobSkillId(p, mobSkill.getId()); switch (s.getKey()) { case WEAPON_REFLECT -> pCounter = mobSkill.getX(); @@ -2917,8 +2918,7 @@ public class PacketCreator { writeLongMaskD(p, statups); for (Pair statup : statups) { p.writeShort(statup.getRight().shortValue()); - p.writeShort(skill.getSkillId()); - p.writeShort(skill.getSkillLevel()); + writeMobSkillId(p, skill.getId()); p.writeInt((int) skill.getDuration()); } p.writeShort(0); // ??? wk charges have 600 here o.o @@ -2936,8 +2936,7 @@ public class PacketCreator { if (statup.getLeft() == Disease.POISON) { p.writeShort(statup.getRight().shortValue()); } - p.writeShort(skill.getSkillId()); - p.writeShort(skill.getSkillLevel()); + writeMobSkillId(p, skill.getId()); } p.writeShort(0); // same as give_buff p.writeShort(900);//Delay @@ -3051,8 +3050,7 @@ public class PacketCreator { if (statup.getLeft() == Disease.POISON) { p.writeShort(statup.getRight().shortValue()); } - p.writeShort(skill.getSkillId()); - p.writeShort(skill.getSkillLevel()); + writeMobSkillId(p, skill.getId()); } p.writeShort(0); // same as give_buff p.writeShort(900);//Delay @@ -3932,8 +3930,7 @@ public class PacketCreator { for (Map.Entry stat : stati.entrySet()) { p.writeShort(stat.getValue()); if (mse.isMonsterSkill()) { - p.writeShort(mse.getMobSkill().getSkillId()); - p.writeShort(mse.getMobSkill().getSkillLevel()); + writeMobSkillId(p, mse.getMobSkill().getId()); } else { p.writeInt(mse.getSkill().getId()); } @@ -3984,11 +3981,15 @@ public class PacketCreator { return p; } - public static Packet spawnMist(int objId, int ownerChrId, int skill, int level, Mist mist) { + public static Packet spawnMobMist(int objId, int ownerMobId, MobSkillId msId, Mist mist) { + return spawnMist(objId, ownerMobId, msId.type().getId(), msId.level(), mist); + } + + public static Packet spawnMist(int objId, int ownerId, int skill, int level, Mist mist) { OutPacket p = OutPacket.create(SendOpcode.SPAWN_MIST); p.writeInt(objId); p.writeInt(mist.isMobMist() ? 0 : mist.isPoisonMist() ? 1 : mist.isRecoveryMist() ? 4 : 2); // mob mist = 0, player poison = 1, smokescreen = 2, unknown = 3, recovery = 4 - p.writeInt(ownerChrId); + p.writeInt(ownerId); p.writeInt(skill); p.writeByte(level); p.writeShort(mist.getSkillDelay()); // Skill delay From 9e0479fe9f535bd8864209d4d979c97914e87e5a Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 14:31:43 +0200 Subject: [PATCH 12/16] Clean up Monster usedSkills --- src/main/java/server/life/Monster.java | 42 ++++++-------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/main/java/server/life/Monster.java b/src/main/java/server/life/Monster.java index ee000abf84..efd42f8b8d 100644 --- a/src/main/java/server/life/Monster.java +++ b/src/main/java/server/life/Monster.java @@ -82,8 +82,7 @@ public class Monster extends AbstractLoadedLife { private int VenomMultiplier = 0; private boolean fake = false; private boolean dropsDisabled = false; - private final List> usedSkills = new ArrayList<>(); // TODO: change to Set - private final Map, Integer> skillsUsed = new HashMap<>(); + private final Set usedSkills = new HashSet<>(); private final Set usedAttacks = new HashSet<>(); private Set calledMobOids = null; private WeakReference callerMob = new WeakReference<>(null); @@ -1447,11 +1446,8 @@ public class Monster extends AbstractLoadedLife { monsterLock.lock(); try { - for (Pair skill : usedSkills) { // thanks OishiiKawaiiDesu for noticing an issue with mobskill cooldown - MobSkillId msId = toUse.getId(); - if (skill.getLeft() == msId.type().getId() && skill.getRight() == msId.level()) { - return false; - } + if (usedSkills.contains(toUse.getId())) { + return false; } int mpCon = toUse.getMpCon(); @@ -1483,48 +1479,28 @@ public class Monster extends AbstractLoadedLife { } private void usedSkill(MobSkill skill) { - final int skillId = skill.getId().type().getId(); - final int level = skill.getId().level(); - long cooltime = skill.getCoolTime(); - + final MobSkillId msId = skill.getId(); monsterLock.lock(); try { mp -= skill.getMpCon(); - Pair skillKey = new Pair<>(skillId, level); - this.usedSkills.add(skillKey); - - Integer useCount = this.skillsUsed.remove(skillKey); - if (useCount != null) { - this.skillsUsed.put(skillKey, useCount + 1); - } else { - this.skillsUsed.put(skillKey, 1); - } + this.usedSkills.add(msId); } finally { monsterLock.unlock(); } final Monster mons = this; MapleMap mmap = mons.getMap(); - Runnable r = () -> mons.clearSkill(skillId, level); + Runnable r = () -> mons.clearSkill(skill.getId()); MobClearSkillService service = (MobClearSkillService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_CLEAR_SKILL); - service.registerMobClearSkillAction(mmap.getId(), r, cooltime); + service.registerMobClearSkillAction(mmap.getId(), r, skill.getCoolTime()); } - private void clearSkill(int skillId, int level) { + private void clearSkill(MobSkillId msId) { monsterLock.lock(); try { - int index = -1; - for (Pair skill : usedSkills) { - if (skill.getLeft() == skillId && skill.getRight() == level) { - index = usedSkills.indexOf(skill); - break; - } - } - if (index != -1) { - usedSkills.remove(index); - } + usedSkills.remove(msId); } finally { monsterLock.unlock(); } From 15c4188d48046ccea22c2e28c3be2171a6442c3b Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 3 Sep 2022 14:44:14 +0200 Subject: [PATCH 13/16] Make MCSkill immutable with record --- .../handlers/MonsterCarnivalHandler.java | 10 ++++----- src/main/java/server/StatEffect.java | 8 ++++--- .../java/server/life/MobSkillFactory.java | 2 +- .../server/partyquest/CarnivalFactory.java | 21 ++----------------- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java b/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java index 302562e1c7..b0b6a9f1f5 100644 --- a/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java +++ b/src/main/java/net/server/channel/handlers/MonsterCarnivalHandler.java @@ -95,14 +95,14 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler { return; } final MCSkill skill = CarnivalFactory.getInstance().getSkill(skillid.get(num)); //ugh wtf - if (skill == null || c.getPlayer().getCP() < skill.cpLoss) { + if (skill == null || c.getPlayer().getCP() < skill.cpLoss()) { c.sendPacket(PacketCreator.CPQMessage((byte) 1)); c.sendPacket(PacketCreator.enableActions()); return; } final Disease dis = skill.getDisease(); Party enemies = c.getPlayer().getParty().getEnemy(); - if (skill.targetsAll) { + if (skill.targetsAll()) { int hitChance = rollHitChance(dis.getMobSkillType()); if (hitChance <= 80) { for (PartyCharacter mpc : enemies.getPartyMembers()) { @@ -128,11 +128,11 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler { } } } - neededCP = skill.cpLoss; + neededCP = skill.cpLoss(); c.sendPacket(PacketCreator.enableActions()); } else if (tab == 2) { //protectors final MCSkill skill = CarnivalFactory.getInstance().getGuardian(num); - if (skill == null || c.getPlayer().getCP() < skill.cpLoss) { + if (skill == null || c.getPlayer().getCP() < skill.cpLoss()) { c.sendPacket(PacketCreator.CPQMessage((byte) 1)); c.sendPacket(PacketCreator.enableActions()); return; @@ -163,7 +163,7 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.enableActions()); return; } else { - neededCP = skill.cpLoss; + neededCP = skill.cpLoss(); } } } diff --git a/src/main/java/server/StatEffect.java b/src/main/java/server/StatEffect.java index 9a42178dca..5f308e6219 100644 --- a/src/main/java/server/StatEffect.java +++ b/src/main/java/server/StatEffect.java @@ -1039,14 +1039,15 @@ public class StatEffect { if (skill != null) { final Disease dis = skill.getDisease(); Party opposition = applyfrom.getParty().getEnemy(); - if (skill.targetsAll) { + if (skill.targetsAll()) { for (PartyCharacter enemyChrs : opposition.getPartyMembers()) { Character chrApp = enemyChrs.getPlayer(); if (chrApp != null && chrApp.getMap().isCPQMap()) { if (dis == null) { chrApp.dispel(); } else { - chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getMobSkillType(), skill.level)); + MobSkill mobSkill = MobSkillFactory.getMobSkill(dis.getMobSkillType(), skill.level()); + chrApp.giveDebuff(dis, mobSkill); } } } @@ -1058,7 +1059,8 @@ public class StatEffect { if (dis == null) { chrApp.dispel(); } else { - chrApp.giveDebuff(dis, MCSkill.getMobSkill(dis.getMobSkillType(), skill.level)); + MobSkill mobSkill = MobSkillFactory.getMobSkill(dis.getMobSkillType(), skill.level()); + chrApp.giveDebuff(dis, mobSkill); } } } diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index e2d85daebb..0c090d4289 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -47,7 +47,7 @@ public class MobSkillFactory { private static final Lock readLock = readWriteLock.readLock(); private static final Lock writeLock = readWriteLock.writeLock(); - public static MobSkill getMobSkill(final MobSkillType type, final int level) { // TODO: return Optional + public static MobSkill getMobSkill(final MobSkillType type, final int level) { readLock.lock(); try { MobSkill ms = mobSkills.get(createKey(type, level)); diff --git a/src/main/java/server/partyquest/CarnivalFactory.java b/src/main/java/server/partyquest/CarnivalFactory.java index 3b852504df..450a08c080 100644 --- a/src/main/java/server/partyquest/CarnivalFactory.java +++ b/src/main/java/server/partyquest/CarnivalFactory.java @@ -90,26 +90,9 @@ public class CarnivalFactory { return guardians.get(id); } - public static class MCSkill { - - public int cpLoss; - public MobSkillType mobSkillType; - public int level; - public boolean targetsAll; - - public MCSkill(int _cpLoss, MobSkillType mobSkillType, int _level, boolean _targetsAll) { - cpLoss = _cpLoss; - mobSkillType = mobSkillType; - level = _level; - targetsAll = _targetsAll; - } - + public record MCSkill(int cpLoss, MobSkillType mobSkillType, int level, boolean targetsAll) { public MobSkill getSkill() { - return getMobSkill(mobSkillType, level); - } - - public static MobSkill getMobSkill(MobSkillType type, int level) { - return MobSkillFactory.getMobSkill(type, level); + return MobSkillFactory.getMobSkill(mobSkillType, level); } public Disease getDisease() { From fd793d6b04195eb508117aa50399af118c188349 Mon Sep 17 00:00:00 2001 From: P0nk Date: Wed, 7 Sep 2022 18:58:02 +0200 Subject: [PATCH 14/16] Fix not reading mpCon from MobSkill --- src/main/java/server/life/MobSkillFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 0c090d4289..2d8ce8e7a4 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -74,7 +74,7 @@ public class MobSkillFactory { return Optional.empty(); } - int mpCon = DataTool.getInt(skillData.getChildByPath("mpCon"), 0); + int mpCon = DataTool.getInt("mpCon", skillData, 0); List toSummon = new ArrayList<>(); for (int i = 0; i > -1; i++) { if (skillData.getChildByPath(String.valueOf(i)) == null) { @@ -103,6 +103,7 @@ public class MobSkillFactory { } MobSkill loadedMobSkill = new MobSkill.Builder(type, level) + .mpCon(mpCon) .toSummon(toSummon) .cooltime(cooltime) .duration(duration) From 319d65a0c334f055122e6d55b8d7cefd5ee415f0 Mon Sep 17 00:00:00 2001 From: P0nk Date: Wed, 7 Sep 2022 18:58:19 +0200 Subject: [PATCH 15/16] Add tests for MobSkillFactory --- pom.xml | 12 +- .../java/server/life/MobSkillFactoryTest.java | 70 +++ src/test/resources/MobSkill-test.img.xml | 522 ++++++++++++++++++ 3 files changed, 603 insertions(+), 1 deletion(-) create mode 100644 src/test/java/server/life/MobSkillFactoryTest.java create mode 100644 src/test/resources/MobSkill-test.img.xml diff --git a/pom.xml b/pom.xml index 82536389b4..1a333333a6 100644 --- a/pom.xml +++ b/pom.xml @@ -28,11 +28,12 @@ 2.18.0 22.2.0 4.1.79.Final - 5.9.0 1.15 1.0 5.0.1 8.0.30 + 5.9.0 + 4.7.0 @@ -120,16 +121,25 @@ org.junit.jupiter junit-jupiter-api ${junit.version} + test org.junit.jupiter junit-jupiter-engine ${junit.version} + test org.junit.jupiter junit-jupiter-params ${junit.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test diff --git a/src/test/java/server/life/MobSkillFactoryTest.java b/src/test/java/server/life/MobSkillFactoryTest.java new file mode 100644 index 0000000000..2917ab6a5c --- /dev/null +++ b/src/test/java/server/life/MobSkillFactoryTest.java @@ -0,0 +1,70 @@ +package server.life; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class MobSkillFactoryTest { + + @TempDir + private Path wzPath; + + @BeforeEach + void setWzPath() { + MockitoAnnotations.openMocks(this); + writeTestFileToTempDir(); + System.setProperty("wz-path", "%s/wz".formatted(wzPath.toString())); + } + + private void writeTestFileToTempDir() { + try { + String testFileContents = readTestFileContents(); + writeTempDirFile(testFileContents); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String readTestFileContents() throws IOException { + return new String(getClass() + .getClassLoader() + .getResourceAsStream("MobSkill-test.img.xml") + .readAllBytes() + ); + } + + private void writeTempDirFile(String fileContents) throws IOException { + Path tempDirDirectory = wzPath.resolve("wz/Skill.wz"); + Files.createDirectories(tempDirDirectory); + Path tempDirFile = Files.createFile(tempDirDirectory.resolve("MobSkill.img.xml")); + Files.writeString(tempDirFile, fileContents); + } + + @Test + void shouldLoadExistingMobSkill() { + MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.ATTACK_UP, 1); + + assertNotNull(mobSkill); + assertAll("MobSkill", + () -> assertEquals(115, mobSkill.getX()), + () -> assertEquals(5, mobSkill.getMpCon()), + () -> assertEquals(40_000, mobSkill.getCoolTime()), + () -> assertEquals(30_000, mobSkill.getDuration()) + ); + } + + @Test + void shouldReturnNullForNonExistingMobSkill() { + MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.DEFENSE_UP, 1); + + assertNull(mobSkill); + } + +} \ No newline at end of file diff --git a/src/test/resources/MobSkill-test.img.xml b/src/test/resources/MobSkill-test.img.xml new file mode 100644 index 0000000000..3a2658d0be --- /dev/null +++ b/src/test/resources/MobSkill-test.img.xml @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From df5159e34c320d2dd5c6d4682671cd7a9a43d9bf Mon Sep 17 00:00:00 2001 From: P0nk Date: Wed, 7 Sep 2022 19:25:30 +0200 Subject: [PATCH 16/16] MobSkillFactory throws exception instead of returning null --- src/main/java/client/Character.java | 4 +--- src/main/java/server/life/MobSkillFactory.java | 4 +++- src/test/java/server/life/MobSkillFactoryTest.java | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 1a2f148638..5404d41530 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -7327,9 +7327,7 @@ public class Character extends AbstractCharacterObject { final long length = rs.getInt("length"); MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(skillid), skilllv); - if (ms != null) { - loadedDiseases.put(disease, new Pair<>(length, ms)); - } + loadedDiseases.put(disease, new Pair<>(length, ms)); } } } diff --git a/src/main/java/server/life/MobSkillFactory.java b/src/main/java/server/life/MobSkillFactory.java index 2d8ce8e7a4..ac02cfa2d2 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -58,7 +58,9 @@ public class MobSkillFactory { readLock.unlock(); } - return loadMobSkill(type, level).orElse(null); + return loadMobSkill(type, level).orElseThrow( + () -> new IllegalArgumentException("No MobSkill exists for type %s, level %d".formatted(type, level)) + ); } private static Optional loadMobSkill(final MobSkillType type, final int level) { diff --git a/src/test/java/server/life/MobSkillFactoryTest.java b/src/test/java/server/life/MobSkillFactoryTest.java index 2917ab6a5c..09b4f7fbbe 100644 --- a/src/test/java/server/life/MobSkillFactoryTest.java +++ b/src/test/java/server/life/MobSkillFactoryTest.java @@ -61,10 +61,8 @@ class MobSkillFactoryTest { } @Test - void shouldReturnNullForNonExistingMobSkill() { - MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.DEFENSE_UP, 1); - - assertNull(mobSkill); + void shouldThrowExceptionOnNonExisting() { + assertThrows(IllegalArgumentException.class, () -> MobSkillFactory.getMobSkill(MobSkillType.DEFENSE_UP, 1)); } } \ No newline at end of file