diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 5404d41530..7b4fc8c92d 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -7326,7 +7326,8 @@ public class Character extends AbstractCharacterObject { final int skilllv = rs.getInt("mobskilllv"); final long length = rs.getInt("length"); - MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(skillid), skilllv); + MobSkillType type = MobSkillType.from(skillid).orElseThrow(); + MobSkill ms = MobSkillFactory.getMobSkillOrThrow(type, skilllv); 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 63b3cb8994..d5adbdf0b3 100644 --- a/src/main/java/client/command/commands/gm2/MobSkillCommand.java +++ b/src/main/java/client/command/commands/gm2/MobSkillCommand.java @@ -8,6 +8,7 @@ import server.life.MobSkillFactory; import server.life.MobSkillType; import java.util.Collections; +import java.util.Optional; public class MobSkillCommand extends Command { { @@ -22,13 +23,18 @@ public class MobSkillCommand extends Command { String skillId = params[0]; String skillLevel = params[1]; - 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)); + Optional possibleType = MobSkillType.from(Integer.parseInt(skillId)); + Optional possibleSkill = possibleType.map( + type -> MobSkillFactory.getMobSkillOrThrow(type, Integer.parseInt(skillLevel)) + ); + if (possibleSkill.isEmpty()) { + return; } Character chr = client.getPlayer(); - chr.getMap().getAllMonsters().forEach(monster -> mobSkill.applyEffect(chr, monster, false, Collections.emptyList())); + MobSkill mobSkill = possibleSkill.get(); + chr.getMap().getAllMonsters().forEach( + monster -> mobSkill.applyEffect(chr, monster, false, Collections.emptyList()) + ); } } diff --git a/src/main/java/client/command/commands/gm3/DebuffCommand.java b/src/main/java/client/command/commands/gm3/DebuffCommand.java index bcd18ae9c1..dedc6d853c 100644 --- a/src/main/java/client/command/commands/gm3/DebuffCommand.java +++ b/src/main/java/client/command/commands/gm3/DebuffCommand.java @@ -34,6 +34,7 @@ import server.maps.MapObject; import server.maps.MapObjectType; import java.util.Arrays; +import java.util.Optional; public class DebuffCommand extends Command { { @@ -49,7 +50,7 @@ public class DebuffCommand extends Command { } Disease disease = null; - MobSkill skill = null; + Optional skill = Optional.empty(); switch (params[0].toUpperCase()) { case "SLOW" -> { @@ -94,7 +95,7 @@ public class DebuffCommand extends Command { } } - if (disease == null) { + if (disease == null || skill.isEmpty()) { player.yellowMessage("Syntax: !debuff SLOW|SEDUCE|ZOMBIFY|CONFUSE|STUN|POISON|SEAL|DARKNESS|WEAKEN|CURSE"); return; } @@ -103,7 +104,7 @@ public class DebuffCommand extends Command { Character chr = (Character) mmo; if (chr.getId() != player.getId()) { - chr.giveDebuff(disease, skill); + chr.giveDebuff(disease, skill.get()); } } } diff --git a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java index c2f8f09580..62dac683ce 100644 --- a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -492,7 +492,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { if (monster.isBuffed(MonsterStatus.WEAPON_REFLECT) && !attack.magic) { for (MobSkillId msId : monster.getSkills()) { if (msId.type() == MobSkillType.PHYSICAL_AND_MAGIC_COUNTER) { - MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, msId.level()); + MobSkill toUse = MobSkillFactory.getMobSkillOrThrow(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); } @@ -501,7 +501,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { if (monster.isBuffed(MonsterStatus.MAGIC_REFLECT) && attack.magic) { for (MobSkillId msId : monster.getSkills()) { if (msId.type() == MobSkillType.PHYSICAL_AND_MAGIC_COUNTER) { - MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.PHYSICAL_AND_MAGIC_COUNTER, msId.level()); + MobSkill toUse = MobSkillFactory.getMobSkillOrThrow(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 4b7c7d6493..0f6ea855a5 100644 --- a/src/main/java/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/main/java/net/server/channel/handlers/MoveLifeHandler.java @@ -79,19 +79,16 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { boolean isAttack = inRangeInclusive(rawActivity, 24, 41); boolean isSkill = inRangeInclusive(rawActivity, 42, 59); - int useSkillId = 0, useSkillLevel = 0; - - MobSkill nextUse = null; - int nextSkillId = 0, nextSkillLevel = 0; - - boolean nextMovementCouldBeSkill = !(isSkill || (pNibbles != 0)); + int useSkillId = 0; + int useSkillLevel = 0; if (isSkill) { useSkillId = skillId; useSkillLevel = skillLv; if (monster.hasSkill(useSkillId, useSkillLevel)) { - MobSkill toUse = MobSkillFactory.getMobSkill(MobSkillType.from(useSkillId), useSkillLevel); + MobSkillType mobSkillType = MobSkillType.from(useSkillId).orElseThrow(); + MobSkill toUse = MobSkillFactory.getMobSkillOrThrow(mobSkillType, useSkillLevel); if (monster.canUseSkill(toUse, true)) { int animationTime = MonsterInformationProvider.getInstance().getMobSkillAnimationTime(toUse); @@ -112,12 +109,16 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { } } + boolean nextMovementCouldBeSkill = !(isSkill || (pNibbles != 0)); + MobSkill nextUse = null; + int nextSkillId = 0; + int nextSkillLevel = 0; int mobMp = monster.getMp(); if (nextMovementCouldBeSkill && monster.hasAnySkill()) { MobSkillId skillToUse = monster.getRandomSkill(); nextSkillId = skillToUse.type().getId(); nextSkillLevel = skillToUse.level(); - nextUse = MobSkillFactory.getMobSkill(skillToUse.type(), skillToUse.level()); + nextUse = MobSkillFactory.getMobSkillOrThrow(skillToUse.type(), skillToUse.level()); 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 4a3f331cf5..ca21a3f257 100644 --- a/src/main/java/net/server/channel/handlers/TakeDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/TakeDamageHandler.java @@ -49,6 +49,7 @@ import java.awt.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; public final class TakeDamageHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(TakeDamageHandler.class); @@ -151,9 +152,11 @@ public final class TakeDamageHandler extends AbstractPacketHandler { is_deadly = true; } mpattack += attackInfo.getMpBurn(); - MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.from(attackInfo.getDiseaseSkill()), attackInfo.getDiseaseLevel()); - if (mobSkill != null && damage > 0) { - mobSkill.applyEffect(chr, attacker, false, banishPlayers); + + Optional possibleType = MobSkillType.from(attackInfo.getDiseaseSkill()); + Optional possibleMobSkill = possibleType.map(type -> MobSkillFactory.getMobSkillOrThrow(type, attackInfo.getDiseaseLevel())); + if (possibleMobSkill.isPresent() && damage > 0) { + possibleMobSkill.get().applyEffect(chr, attacker, false, banishPlayers); } attacker.setMp(attacker.getMp() - attackInfo.getMpCon()); diff --git a/src/main/java/scripting/AbstractPlayerInteraction.java b/src/main/java/scripting/AbstractPlayerInteraction.java index 91f9ee40f7..3f8cd0626b 100644 --- a/src/main/java/scripting/AbstractPlayerInteraction.java +++ b/src/main/java/scripting/AbstractPlayerInteraction.java @@ -1195,12 +1195,12 @@ public class AbstractPlayerInteraction { } private void applySealSkill(Monster monster) { - MobSkill sealSkill = MobSkillFactory.getMobSkill(MobSkillType.SEAL_SKILL, 1); + MobSkill sealSkill = MobSkillFactory.getMobSkillOrThrow(MobSkillType.SEAL_SKILL, 1); sealSkill.applyEffect(monster); } private void applyReduceAvoid(Monster monster) { - MobSkill reduceAvoidSkill = MobSkillFactory.getMobSkill(MobSkillType.EVA, 2); + MobSkill reduceAvoidSkill = MobSkillFactory.getMobSkillOrThrow(MobSkillType.EVA, 2); reduceAvoidSkill.applyEffect(monster); } diff --git a/src/main/java/server/StatEffect.java b/src/main/java/server/StatEffect.java index 5f308e6219..820d5df9b5 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 { - MobSkill mobSkill = MobSkillFactory.getMobSkill(dis.getMobSkillType(), skill.level()); + MobSkill mobSkill = MobSkillFactory.getMobSkillOrThrow(dis.getMobSkillType(), skill.level()); chrApp.giveDebuff(dis, mobSkill); } } @@ -1059,7 +1059,7 @@ public class StatEffect { if (dis == null) { chrApp.dispel(); } else { - MobSkill mobSkill = MobSkillFactory.getMobSkill(dis.getMobSkillType(), skill.level()); + MobSkill mobSkill = MobSkillFactory.getMobSkillOrThrow(dis.getMobSkillType(), skill.level()); chrApp.giveDebuff(dis, mobSkill); } } @@ -1070,8 +1070,8 @@ public class StatEffect { applyfrom.dispelDebuff(debuff); } } else if (mobSkill > 0 && mobSkillLevel > 0) { - var mobSkillType = MobSkillType.from(mobSkill); - MobSkill ms = MobSkillFactory.getMobSkill(mobSkillType, mobSkillLevel); + MobSkillType mobSkillType = MobSkillType.from(mobSkill).orElseThrow(); + MobSkill ms = MobSkillFactory.getMobSkillOrThrow(mobSkillType, mobSkillLevel); Disease dis = Disease.getBySkill(mobSkillType); if (target > 0) { diff --git a/src/main/java/server/life/LifeFactory.java b/src/main/java/server/life/LifeFactory.java index 000ec75174..6f2e9f8aed 100644 --- a/src/main/java/server/life/LifeFactory.java +++ b/src/main/java/server/life/LifeFactory.java @@ -192,7 +192,7 @@ public class LifeFactory { while (monsterSkillInfoData.getChildByPath(Integer.toString(i)) != null) { int skillId = DataTool.getInt(i + "/skill", monsterSkillInfoData, 0); int skillLv = DataTool.getInt(i + "/level", monsterSkillInfoData, 0); - MobSkillType type = MobSkillType.from(skillId); + MobSkillType type = MobSkillType.from(skillId).orElseThrow(); skills.add(new MobSkillId(type, skillLv)); Data monsterSkillData = monsterData.getChildByPath("skill" + (i + 1)); @@ -202,7 +202,7 @@ public class LifeFactory { animationTime += DataTool.getIntConvert("delay", effectEntry, 0); } - MobSkill skill = MobSkillFactory.getMobSkill(type, skillLv); + MobSkill skill = MobSkillFactory.getMobSkillOrThrow(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 ac02cfa2d2..7b5ea96b9e 100644 --- a/src/main/java/server/life/MobSkillFactory.java +++ b/src/main/java/server/life/MobSkillFactory.java @@ -47,20 +47,24 @@ 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) { + public static MobSkill getMobSkillOrThrow(MobSkillType type, int level) { + return getMobSkill(type, level).orElseThrow( + () -> new IllegalArgumentException("No MobSkill exists for type %s, level %d".formatted(type, level)) + ); + } + + public static Optional getMobSkill(final MobSkillType type, final int level) { readLock.lock(); try { MobSkill ms = mobSkills.get(createKey(type, level)); if (ms != null) { - return ms; + return Optional.of(ms); } } finally { readLock.unlock(); } - return loadMobSkill(type, level).orElseThrow( - () -> new IllegalArgumentException("No MobSkill exists for type %s, level %d".formatted(type, level)) - ); + return loadMobSkill(type, level); } private static Optional loadMobSkill(final MobSkillType type, final int level) { diff --git a/src/main/java/server/life/MobSkillType.java b/src/main/java/server/life/MobSkillType.java index cf38c5e057..4fd1bfc1b7 100644 --- a/src/main/java/server/life/MobSkillType.java +++ b/src/main/java/server/life/MobSkillType.java @@ -1,6 +1,7 @@ package server.life; import java.util.Arrays; +import java.util.Optional; public enum MobSkillType { ATTACK_UP(100), @@ -51,11 +52,18 @@ public enum MobSkillType { this.id = id; } - public static MobSkillType from(int id) { + public static Optional from(int id) { + if (isOutOfIdRange(id)) { + return Optional.empty(); + } + return Arrays.stream(values()) .filter(type -> type.getId() == id) - .findFirst() - .orElseThrow(IllegalArgumentException::new); + .findAny(); + } + + private static boolean isOutOfIdRange(int id) { + return id < 100 || id > 200; } public int getId() { diff --git a/src/main/java/server/partyquest/CarnivalFactory.java b/src/main/java/server/partyquest/CarnivalFactory.java index 450a08c080..c73e7df6eb 100644 --- a/src/main/java/server/partyquest/CarnivalFactory.java +++ b/src/main/java/server/partyquest/CarnivalFactory.java @@ -47,7 +47,7 @@ public class CarnivalFactory { int mobSkillId = DataTool.getInt("mobSkillID", z, 0); MobSkillType mobSkillType = null; if (mobSkillId != 0) { - mobSkillType = MobSkillType.from(mobSkillId); + mobSkillType = MobSkillType.from(mobSkillId).orElseThrow(); } int level = DataTool.getInt("level", z, 0); boolean isMultiTarget = DataTool.getInt("target", z, 1) > 1; @@ -63,7 +63,7 @@ public class CarnivalFactory { for (Data z : dataRoot.getData("MCGuardian.img")) { int spendCp = DataTool.getInt("spendCP", z, 0); int mobSkillId = DataTool.getInt("mobSkillID", z, 0); - MobSkillType mobSkillType = MobSkillType.from(mobSkillId); + MobSkillType mobSkillType = MobSkillType.from(mobSkillId).orElseThrow(); int level = DataTool.getInt("level", z, 0); guardians.put(Integer.parseInt(z.getName()), new MCSkill(spendCp, mobSkillType, level, true)); } @@ -92,7 +92,7 @@ public class CarnivalFactory { public record MCSkill(int cpLoss, MobSkillType mobSkillType, int level, boolean targetsAll) { public MobSkill getSkill() { - return MobSkillFactory.getMobSkill(mobSkillType, level); + return MobSkillFactory.getMobSkillOrThrow(mobSkillType, level); } public Disease getDisease() { diff --git a/src/main/java/tools/mapletools/MonsterStatFetcher.java b/src/main/java/tools/mapletools/MonsterStatFetcher.java index 8b0dc826a6..6cbb136ca8 100644 --- a/src/main/java/tools/mapletools/MonsterStatFetcher.java +++ b/src/main/java/tools/mapletools/MonsterStatFetcher.java @@ -111,7 +111,7 @@ public class MonsterStatFetcher { Set skills = new HashSet<>(); while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) { int skillId = DataTool.getInt(i + "/skill", monsterSkillData, 0); - MobSkillType type = MobSkillType.from(skillId); + MobSkillType type = MobSkillType.from(skillId).orElseThrow(); int skillLevel = DataTool.getInt(i + "/level", monsterSkillData, 0); skills.add(new MobSkillId(type, skillLevel)); i++; diff --git a/src/test/java/server/life/MobSkillFactoryTest.java b/src/test/java/server/life/MobSkillFactoryTest.java index 09b4f7fbbe..c4aebebec6 100644 --- a/src/test/java/server/life/MobSkillFactoryTest.java +++ b/src/test/java/server/life/MobSkillFactoryTest.java @@ -8,6 +8,7 @@ import org.mockito.MockitoAnnotations; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -49,9 +50,10 @@ class MobSkillFactoryTest { @Test void shouldLoadExistingMobSkill() { - MobSkill mobSkill = MobSkillFactory.getMobSkill(MobSkillType.ATTACK_UP, 1); + Optional possibleSkill = MobSkillFactory.getMobSkill(MobSkillType.ATTACK_UP, 1); - assertNotNull(mobSkill); + assertTrue(possibleSkill.isPresent()); + MobSkill mobSkill = possibleSkill.get(); assertAll("MobSkill", () -> assertEquals(115, mobSkill.getX()), () -> assertEquals(5, mobSkill.getMpCon()), @@ -62,7 +64,7 @@ class MobSkillFactoryTest { @Test void shouldThrowExceptionOnNonExisting() { - assertThrows(IllegalArgumentException.class, () -> MobSkillFactory.getMobSkill(MobSkillType.DEFENSE_UP, 1)); + assertThrows(IllegalArgumentException.class, () -> MobSkillFactory.getMobSkillOrThrow(MobSkillType.DEFENSE_UP, 1)); } } \ No newline at end of file