Merge pull request #116 from P0nk/mob-skill-refactoring
Clean up and refactor mob skills
This commit is contained in:
@@ -28,11 +28,12 @@
|
||||
<log4j.version>2.18.0</log4j.version> <!-- Slf4j implementation -->
|
||||
<graalvm.version>22.2.0</graalvm.version> <!-- ScriptEngine implementation -->
|
||||
<netty.version>4.1.79.Final</netty.version> <!-- Networking -->
|
||||
<junit.version>5.9.0</junit.version> <!-- Unit test -->
|
||||
<yamlbeans.version>1.15</yamlbeans.version> <!-- Config file -->
|
||||
<jcip-annotations.version>1.0</jcip-annotations.version> <!-- Annotations for concurrency documentation -->
|
||||
<HikariCP.version>5.0.1</HikariCP.version> <!-- Database connection pool -->
|
||||
<mysql-connector-java.version>8.0.30</mysql-connector-java.version> <!-- MySQL JDBC driver -->
|
||||
<junit.version>5.9.0</junit.version> <!-- Unit test -->
|
||||
<mockito.version>4.7.0</mockito.version> <!-- Unit test -->
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -120,16 +121,25 @@
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -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,10 +7326,8 @@ public class Character extends AbstractCharacterObject {
|
||||
final int skilllv = rs.getInt("mobskilllv");
|
||||
final long length = rs.getInt("length");
|
||||
|
||||
MobSkill ms = MobSkillFactory.getMobSkill(skillid, skilllv);
|
||||
if (ms != null) {
|
||||
loadedDiseases.put(disease, new Pair<>(length, ms));
|
||||
}
|
||||
MobSkill ms = MobSkillFactory.getMobSkill(MobSkillType.from(skillid), skilllv);
|
||||
loadedDiseases.put(disease, new Pair<>(length, ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8111,8 +8106,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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Pair<Integer, Integer>> mobSkills = monster.getSkills();
|
||||
|
||||
for (Pair<Integer, Integer> ms : mobSkills) {
|
||||
if (ms.left == 145) {
|
||||
MobSkill toUse = MobSkillFactory.getMobSkill(ms.left, 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<Pair<Integer, Integer>> mobSkills = monster.getSkills();
|
||||
|
||||
for (Pair<Integer, Integer> ms : mobSkills) {
|
||||
if (ms.left == 145) {
|
||||
MobSkill toUse = MobSkillFactory.getMobSkill(ms.left, 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -94,18 +95,15 @@ 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) {
|
||||
int hitChance = 0;
|
||||
if (dis.getDisease() == 121 || dis.getDisease() == 122 || dis.getDisease() == 125 || dis.getDisease() == 126) {
|
||||
hitChance = (int) (Math.random() * 100);
|
||||
}
|
||||
if (skill.targetsAll()) {
|
||||
int hitChance = rollHitChance(dis.getMobSkillType());
|
||||
if (hitChance <= 80) {
|
||||
for (PartyCharacter mpc : enemies.getPartyMembers()) {
|
||||
Character mc = mpc.getPlayer();
|
||||
@@ -130,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;
|
||||
@@ -165,7 +163,7 @@ public final class MonsterCarnivalHandler extends AbstractPacketHandler {
|
||||
c.sendPacket(PacketCreator.enableActions());
|
||||
return;
|
||||
} else {
|
||||
neededCP = skill.cpLoss;
|
||||
neededCP = skill.cpLoss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,16 +27,11 @@ 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;
|
||||
import tools.PacketCreator;
|
||||
import tools.Pair;
|
||||
import tools.Randomizer;
|
||||
import tools.exceptions.EmptyMovementException;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -84,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;
|
||||
@@ -92,18 +86,16 @@ 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(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);
|
||||
if (animationTime > 0 && toUse.getSkillId() != 129) {
|
||||
if (animationTime > 0 && toUse.getType() != MobSkillType.BANISH) {
|
||||
toUse.applyDelayedEffect(player, monster, true, animationTime);
|
||||
} else {
|
||||
banishPlayers = new LinkedList<>();
|
||||
@@ -112,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;
|
||||
@@ -122,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());
|
||||
|
||||
Pair<Integer, Integer> skillToUse = monster.getSkills().get(rndSkill);
|
||||
nextSkillId = skillToUse.getLeft();
|
||||
nextSkillLevel = skillToUse.getRight();
|
||||
nextUse = MobSkillFactory.getMobSkill(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,11 +42,11 @@ 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;
|
||||
import server.partyquest.CarnivalFactory.MCSkill;
|
||||
import tools.ArrayMap;
|
||||
import tools.PacketCreator;
|
||||
import tools.Pair;
|
||||
|
||||
@@ -422,7 +422,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, Integer> monsterStatus = new ArrayMap<>();
|
||||
Map<MonsterStatus, Integer> monsterStatus = new EnumMap<>(MonsterStatus.class);
|
||||
if (skill) {
|
||||
switch (sourceid) {
|
||||
// BEGINNER
|
||||
@@ -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.getDisease(), 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.getDisease(), skill.level));
|
||||
MobSkill mobSkill = MobSkillFactory.getMobSkill(dis.getMobSkillType(), skill.level());
|
||||
chrApp.giveDebuff(dis, mobSkill);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1068,8 +1070,9 @@ public class StatEffect {
|
||||
applyfrom.dispelDebuff(debuff);
|
||||
}
|
||||
} else if (mobSkill > 0 && mobSkillLevel > 0) {
|
||||
MobSkill ms = MobSkillFactory.getMobSkill(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()) {
|
||||
|
||||
@@ -188,11 +188,12 @@ public class LifeFactory {
|
||||
Data monsterSkillInfoData = monsterInfoData.getChildByPath("skill");
|
||||
if (monsterSkillInfoData != null) {
|
||||
int i = 0;
|
||||
List<Pair<Integer, Integer>> skills = new ArrayList<>();
|
||||
Set<MobSkillId> 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);
|
||||
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(skillId, skillLv);
|
||||
MobSkill skill = MobSkillFactory.getMobSkill(type, skillLv);
|
||||
mi.setMobSkillAnimationTime(skill, animationTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -34,7 +35,6 @@ 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.*;
|
||||
@@ -47,68 +47,130 @@ import java.util.*;
|
||||
public class MobSkill {
|
||||
private static final Logger log = LoggerFactory.getLogger(MobSkill.class);
|
||||
|
||||
private final int skillId;
|
||||
private final int skillLevel;
|
||||
private int mpCon;
|
||||
private final List<Integer> 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 MobSkillId id;
|
||||
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<Integer> toSummon;
|
||||
|
||||
public MobSkill(int skillId, int level) {
|
||||
this.skillId = skillId;
|
||||
this.skillLevel = level;
|
||||
}
|
||||
|
||||
public void setMpCon(int mpCon) {
|
||||
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<Integer> toSummon) {
|
||||
this.id = new MobSkillId(type, level);
|
||||
this.mpCon = mpCon;
|
||||
}
|
||||
|
||||
public void addSummons(List<Integer> 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<Integer> 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<Integer> 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) {
|
||||
@@ -126,247 +188,221 @@ public class MobSkill {
|
||||
applyEffect(null, monster, false, Collections.emptyList());
|
||||
}
|
||||
|
||||
public void applyEffect(Character player, Monster monster, boolean skill, List<Character> banishPlayers) {
|
||||
// TODO: avoid output argument banishPlayersOutput
|
||||
public void applyEffect(Character player, Monster monster, boolean skill, List<Character> banishPlayersOutput) {
|
||||
Disease disease = null;
|
||||
Map<MonsterStatus, Integer> stats = new ArrayMap<>();
|
||||
List<Integer> 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:
|
||||
if (lt != null && rb != null && skill) {
|
||||
List<MapObject> 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());
|
||||
}
|
||||
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:
|
||||
if (lt != null && rb != null && skill) {
|
||||
for (Character character : getPlayersInRange(monster)) {
|
||||
character.dispel();
|
||||
}
|
||||
} else {
|
||||
player.dispel();
|
||||
}
|
||||
break;
|
||||
case 128: // Seduce
|
||||
disease = Disease.SEDUCE;
|
||||
break;
|
||||
case 129: // Banish
|
||||
if (lt != null && rb != null && skill) {
|
||||
banishPlayers.addAll(getPlayersInRange(monster));
|
||||
} else {
|
||||
banishPlayers.add(player);
|
||||
}
|
||||
break;
|
||||
case 131: // Mist
|
||||
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:
|
||||
Map<MonsterStatus, Integer> stats = new EnumMap<>(MonsterStatus.class);
|
||||
List<Integer> reflection = new ArrayList<>();
|
||||
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);
|
||||
case MAGIC_DEFENSE_UP, MAGIC_DEFENSE_UP_M, MDR -> stats.put(MonsterStatus.MAGIC_DEFENSE_UP, x);
|
||||
case HEAL_M -> applyHealEffect(skill, monster);
|
||||
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 -> applyDispelEffect(skill, monster, player);
|
||||
case SEDUCE -> disease = Disease.SEDUCE;
|
||||
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 -> {
|
||||
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
|
||||
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<Integer> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.warn("Unhandled Mob skill: {}", skillId);
|
||||
break;
|
||||
}
|
||||
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 -> summonMonsters(monster);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
monster.applyMonsterBuff(stats, getX(), getSkillId(), 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(2321005)) { // 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<MapObject> 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<Character> 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<Integer> 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<MonsterStatus, Integer> stats, boolean skill, Monster monster, List<Integer> reflection) {
|
||||
if (lt != null && rb != null && skill) {
|
||||
for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) {
|
||||
((Monster) mons).applyMonsterBuff(stats, getX(), getDuration(), this, reflection);
|
||||
}
|
||||
} else {
|
||||
monster.applyMonsterBuff(stats, getX(), 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<Character> getPlayersInRange(Monster monster) {
|
||||
return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition()));
|
||||
}
|
||||
|
||||
public int getSkillId() {
|
||||
return skillId;
|
||||
public MobSkillId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getSkillLevel() {
|
||||
return skillLevel;
|
||||
public MobSkillType getType() {
|
||||
return id.type();
|
||||
}
|
||||
|
||||
public int getMpCon() {
|
||||
@@ -374,7 +410,7 @@ public class MobSkill {
|
||||
}
|
||||
|
||||
public List<Integer> getSummons() {
|
||||
return new ArrayList<>(toSummon);
|
||||
return toSummon;
|
||||
}
|
||||
|
||||
public int getSpawnEffect() {
|
||||
|
||||
@@ -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,76 +40,93 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
* @author Danny (Leifde)
|
||||
*/
|
||||
public class MobSkillFactory {
|
||||
|
||||
private static final Map<String, MobSkill> 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 MobSkillType type, final int level) {
|
||||
readLock.lock();
|
||||
try {
|
||||
MobSkill ret = mobSkills.get(key);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
MobSkill ms = mobSkills.get(createKey(type, level));
|
||||
if (ms != null) {
|
||||
return ms;
|
||||
}
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
return loadMobSkill(type, level).orElseThrow(
|
||||
() -> new IllegalArgumentException("No MobSkill exists for type %s, level %d".formatted(type, level))
|
||||
);
|
||||
}
|
||||
|
||||
private static Optional<MobSkill> loadMobSkill(final MobSkillType type, final int level) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
MobSkill ret;
|
||||
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<Integer> 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(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 existingMs = mobSkills.get(createKey(type, level));
|
||||
if (existingMs != null) {
|
||||
return Optional.of(existingMs);
|
||||
}
|
||||
return ret;
|
||||
|
||||
Data skillData = skillRoot.getChildByPath("%d/level/%d".formatted(type.getId(), level));
|
||||
if (skillData == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
int mpCon = DataTool.getInt("mpCon", skillData, 0);
|
||||
List<Integer> 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();
|
||||
}
|
||||
|
||||
MobSkill loadedMobSkill = new MobSkill.Builder(type, level)
|
||||
.mpCon(mpCon)
|
||||
.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(type, level), loadedMobSkill);
|
||||
return Optional.of(loadedMobSkill);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static String createKey(MobSkillType type, int skillLevel) {
|
||||
return type.getId() + "" + skillLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package server.life;
|
||||
|
||||
public record MobSkillId(MobSkillType type, int level) {}
|
||||
@@ -0,0 +1,64 @@
|
||||
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())
|
||||
.filter(type -> type.getId() == id)
|
||||
.findFirst()
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -82,8 +82,7 @@ public class Monster extends AbstractLoadedLife {
|
||||
private int VenomMultiplier = 0;
|
||||
private boolean fake = false;
|
||||
private boolean dropsDisabled = false;
|
||||
private final List<Pair<Integer, Integer>> usedSkills = new ArrayList<>();
|
||||
private final Map<Pair<Integer, Integer>, Integer> skillsUsed = new HashMap<>();
|
||||
private final Set<MobSkillId> usedSkills = new HashSet<>();
|
||||
private final Set<Integer> usedAttacks = new HashSet<>();
|
||||
private Set<Integer> calledMobOids = null;
|
||||
private WeakReference<Monster> callerMob = new WeakReference<>(null);
|
||||
@@ -1276,11 +1275,11 @@ public class Monster extends AbstractLoadedLife {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final void dispelSkill(final MobSkill skillId) {
|
||||
public final void dispelSkill(final MobSkill skill) {
|
||||
List<MonsterStatus> toCancel = new ArrayList<>();
|
||||
for (Entry<MonsterStatus, MonsterStatusEffect> 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());
|
||||
}
|
||||
}
|
||||
@@ -1289,7 +1288,7 @@ public class Monster extends AbstractLoadedLife {
|
||||
}
|
||||
}
|
||||
|
||||
public void applyMonsterBuff(final Map<MonsterStatus, Integer> stats, final int x, int skillId, long duration, MobSkill skill, final List<Integer> reflection) {
|
||||
public void applyMonsterBuff(final Map<MonsterStatus, Integer> stats, final int x, long duration, MobSkill skill, final List<Integer> reflection) {
|
||||
final Runnable cancelTask = () -> {
|
||||
if (isAlive()) {
|
||||
Packet packet = PacketCreator.cancelMonsterStatus(getObjectId(), stats);
|
||||
@@ -1426,7 +1425,7 @@ public class Monster extends AbstractLoadedLife {
|
||||
return map.getAggroCoordinator();
|
||||
}
|
||||
|
||||
public List<Pair<Integer, Integer>> getSkills() {
|
||||
public Set<MobSkillId> getSkills() {
|
||||
return stats.getSkills();
|
||||
}
|
||||
|
||||
@@ -1434,26 +1433,12 @@ public class Monster extends AbstractLoadedLife {
|
||||
return stats.hasSkill(skillId, level);
|
||||
}
|
||||
|
||||
public int getSkillPos(int skillId, int level) {
|
||||
int pos = 0;
|
||||
for (Pair<Integer, Integer> ms : this.getSkills()) {
|
||||
if (ms.getLeft() == skillId && ms.getRight() == level) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean canUseSkill(MobSkill toUse, boolean apply) {
|
||||
if (toUse == null || isBuffed(MonsterStatus.SEAL_SKILL)) {
|
||||
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;
|
||||
}
|
||||
@@ -1461,10 +1446,8 @@ public class Monster extends AbstractLoadedLife {
|
||||
|
||||
monsterLock.lock();
|
||||
try {
|
||||
for (Pair<Integer, Integer> skill : usedSkills) { // thanks OishiiKawaiiDesu for noticing an issue with mobskill cooldown
|
||||
if (skill.getLeft() == useSkillid && skill.getRight() == toUse.getSkillLevel()) {
|
||||
return false;
|
||||
}
|
||||
if (usedSkills.contains(toUse.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int mpCon = toUse.getMpCon();
|
||||
@@ -1488,48 +1471,36 @@ public class Monster extends AbstractLoadedLife {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void usedSkill(MobSkill skill) {
|
||||
final int skillId = skill.getSkillId(), level = skill.getSkillLevel();
|
||||
long cooltime = skill.getCoolTime();
|
||||
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 MobSkillId msId = skill.getId();
|
||||
monsterLock.lock();
|
||||
try {
|
||||
mp -= skill.getMpCon();
|
||||
|
||||
Pair<Integer, Integer> 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<Integer, Integer> 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();
|
||||
}
|
||||
@@ -1593,8 +1564,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<MobSkillId> 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() {
|
||||
|
||||
@@ -41,7 +41,7 @@ public class MonsterStats {
|
||||
public Map<Element, ElementalEffectiveness> resistance = new HashMap<>();
|
||||
public List<Integer> revives = Collections.emptyList();
|
||||
public byte tagColor, tagBgColor;
|
||||
public List<Pair<Integer, Integer>> skills = new ArrayList<>();
|
||||
public Set<MobSkillId> skills = new HashSet<>();
|
||||
public Pair<Integer, Integer> cool = null;
|
||||
public BanishInfo banish = null;
|
||||
public List<loseItem> loseItem = null;
|
||||
@@ -190,18 +190,12 @@ public class MonsterStats {
|
||||
this.tagBgColor = (byte) tagBgColor;
|
||||
}
|
||||
|
||||
public void setSkills(List<Pair<Integer, Integer>> 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<MobSkillId> skills) {
|
||||
this.skills = skills;
|
||||
}
|
||||
|
||||
public List<Pair<Integer, Integer>> getSkills() {
|
||||
return Collections.unmodifiableList(this.skills);
|
||||
public Set<MobSkillId> getSkills() {
|
||||
return Collections.unmodifiableSet(this.skills);
|
||||
}
|
||||
|
||||
public int getNoSkills() {
|
||||
@@ -209,8 +203,8 @@ public class MonsterStats {
|
||||
}
|
||||
|
||||
public boolean hasSkill(int skillId, int level) {
|
||||
for (Pair<Integer, Integer> skill : skills) {
|
||||
if (skill.getLeft() == skillId && skill.getRight() == level) {
|
||||
for (MobSkillId skill : skills) {
|
||||
if (skill.type().getId() == skillId && skill.level() == level) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -42,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) {
|
||||
@@ -52,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,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;
|
||||
@@ -77,28 +90,13 @@ public class CarnivalFactory {
|
||||
return guardians.get(id);
|
||||
}
|
||||
|
||||
public static class MCSkill {
|
||||
|
||||
public int cpLoss, skillid, level;
|
||||
public boolean targetsAll;
|
||||
|
||||
public MCSkill(int _cpLoss, int _skillid, int _level, boolean _targetsAll) {
|
||||
cpLoss = _cpLoss;
|
||||
skillid = _skillid;
|
||||
level = _level;
|
||||
targetsAll = _targetsAll;
|
||||
}
|
||||
|
||||
public record MCSkill(int cpLoss, MobSkillType mobSkillType, int level, boolean targetsAll) {
|
||||
public MobSkill getSkill() {
|
||||
return getMobSkill(skillid, level);
|
||||
}
|
||||
|
||||
public static MobSkill getMobSkill(int skillid, int level) {
|
||||
return MobSkillFactory.getMobSkill(skillid, level);
|
||||
return MobSkillFactory.getMobSkill(mobSkillType, level);
|
||||
}
|
||||
|
||||
public Disease getDisease() {
|
||||
return Disease.getBySkill(skillid);
|
||||
return Disease.getBySkill(mobSkillType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
This file is part of the OdinMS Maple Story Server
|
||||
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
|
||||
Matthias Butz <matze@odinms.de>
|
||||
Jan Christian Meyer <vimes@odinms.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package tools;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ArrayMap<K, V> extends AbstractMap<K, V> {
|
||||
|
||||
static class Entry<K, V> implements Map.Entry<K, V> {
|
||||
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<? extends java.util.Map.Entry<K, V>> entries = null;
|
||||
private final ArrayList<Entry<K, V>> list;
|
||||
|
||||
public ArrayMap() {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ArrayMap(Map<K, V> map) {
|
||||
list = new ArrayList<>();
|
||||
putAll(map);
|
||||
}
|
||||
|
||||
public ArrayMap(int initialCapacity) {
|
||||
list = new ArrayList<>(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<java.util.Map.Entry<K, V>> entrySet() {
|
||||
if (entries == null) {
|
||||
entries = new AbstractSet<Entry<K, V>>() {
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
return (Set<java.util.Map.Entry<K, V>>) entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
int size = list.size();
|
||||
Entry<K, V> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Disease, Integer> 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<MonsterStatus, Integer> 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
|
||||
|
||||
@@ -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<Pair<Integer, Integer>> skills = new ArrayList<>();
|
||||
Set<MobSkillId> skills = new HashSet<>();
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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 shouldThrowExceptionOnNonExisting() {
|
||||
assertThrows(IllegalArgumentException.class, () -> MobSkillFactory.getMobSkill(MobSkillType.DEFENSE_UP, 1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<imgdir name="MobSkill.img">
|
||||
<imgdir name="100">
|
||||
<imgdir name="level">
|
||||
<imgdir name="1">
|
||||
<int name="x" value="115"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="40"/>
|
||||
<int name="time" value="30"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="128" height="128">
|
||||
<vector name="origin" x="62" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="128" height="127">
|
||||
<vector name="origin" x="61" y="64"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="131" height="130">
|
||||
<vector name="origin" x="65" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="141" height="140">
|
||||
<vector name="origin" x="68" y="70"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="123" height="123">
|
||||
<vector name="origin" x="58" y="62"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="126" height="122">
|
||||
<vector name="origin" x="63" y="63"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="6" width="111" height="112">
|
||||
<vector name="origin" x="54" y="57"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="7" width="24" height="23">
|
||||
<vector name="origin" x="9" y="15"/>
|
||||
<int name="delay" value="100"/>
|
||||
<int name="a0" value="255"/>
|
||||
<int name="a1" value="0"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="x" value="130"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="40"/>
|
||||
<int name="time" value="30"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="128" height="128">
|
||||
<vector name="origin" x="62" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="128" height="127">
|
||||
<vector name="origin" x="61" y="64"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="131" height="130">
|
||||
<vector name="origin" x="65" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="141" height="140">
|
||||
<vector name="origin" x="68" y="70"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="123" height="123">
|
||||
<vector name="origin" x="58" y="62"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="126" height="122">
|
||||
<vector name="origin" x="63" y="63"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="6" width="111" height="112">
|
||||
<vector name="origin" x="54" y="57"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="7" width="24" height="23">
|
||||
<vector name="origin" x="9" y="15"/>
|
||||
<int name="delay" value="100"/>
|
||||
<int name="a0" value="255"/>
|
||||
<int name="a1" value="0"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="3">
|
||||
<int name="x" value="130"/>
|
||||
<int name="hp" value="50"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="40"/>
|
||||
<int name="time" value="30"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="128" height="128">
|
||||
<vector name="origin" x="62" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="128" height="127">
|
||||
<vector name="origin" x="61" y="64"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="131" height="130">
|
||||
<vector name="origin" x="65" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="141" height="140">
|
||||
<vector name="origin" x="68" y="70"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="123" height="123">
|
||||
<vector name="origin" x="58" y="62"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="126" height="122">
|
||||
<vector name="origin" x="63" y="63"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="6" width="111" height="112">
|
||||
<vector name="origin" x="54" y="57"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="7" width="24" height="23">
|
||||
<vector name="origin" x="9" y="15"/>
|
||||
<int name="delay" value="100"/>
|
||||
<int name="a0" value="255"/>
|
||||
<int name="a1" value="0"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="4">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="30"/>
|
||||
<int name="time" value="60"/>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="84" height="74">
|
||||
<vector name="origin" x="42" y="37"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="84" height="75">
|
||||
<vector name="origin" x="43" y="36"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="84" height="75">
|
||||
<vector name="origin" x="43" y="36"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="85" height="75">
|
||||
<vector name="origin" x="44" y="35"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="84" height="74">
|
||||
<vector name="origin" x="42" y="37"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="85" height="73">
|
||||
<vector name="origin" x="43" y="38"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="repeat" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="5">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="30"/>
|
||||
<int name="time" value="60"/>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="84" height="74">
|
||||
<vector name="origin" x="42" y="37"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="84" height="75">
|
||||
<vector name="origin" x="43" y="36"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="84" height="75">
|
||||
<vector name="origin" x="43" y="36"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="85" height="75">
|
||||
<vector name="origin" x="44" y="35"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="84" height="74">
|
||||
<vector name="origin" x="42" y="37"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="85" height="73">
|
||||
<vector name="origin" x="43" y="38"/>
|
||||
<int name="delay" value="120"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="repeat" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="6">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="1"/>
|
||||
<int name="interval" value="5"/>
|
||||
<int name="time" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="7">
|
||||
<int name="x" value="1"/>
|
||||
<int name="mpCon" value="1"/>
|
||||
<int name="interval" value="8"/>
|
||||
<int name="time" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="8">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="1"/>
|
||||
<int name="interval" value="90"/>
|
||||
<int name="time" value="10"/>
|
||||
</imgdir>
|
||||
<imgdir name="9">
|
||||
<int name="x" value="110"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="10">
|
||||
<int name="x" value="120"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="11">
|
||||
<int name="x" value="130"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="12">
|
||||
<int name="x" value="140"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="13">
|
||||
<int name="x" value="150"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="14">
|
||||
<int name="x" value="160"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="15">
|
||||
<int name="x" value="170"/>
|
||||
<int name="time" value="6000"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="16">
|
||||
<int name="x" value="170"/>
|
||||
<int name="time" value="15"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="17">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="1"/>
|
||||
<int name="interval" value="15"/>
|
||||
<int name="time" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="18">
|
||||
<int name="x" value="100"/>
|
||||
<int name="mpCon" value="1"/>
|
||||
<int name="interval" value="5"/>
|
||||
<int name="time" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="19">
|
||||
<int name="x" value="170"/>
|
||||
<int name="time" value="15"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="20">
|
||||
<int name="x" value="115"/>
|
||||
<int name="mpCon" value="5"/>
|
||||
<int name="interval" value="40"/>
|
||||
<int name="time" value="30"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="128" height="128">
|
||||
<vector name="origin" x="62" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="128" height="127">
|
||||
<vector name="origin" x="61" y="64"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="131" height="130">
|
||||
<vector name="origin" x="65" y="65"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="141" height="140">
|
||||
<vector name="origin" x="68" y="70"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="123" height="123">
|
||||
<vector name="origin" x="58" y="62"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="5" width="126" height="122">
|
||||
<vector name="origin" x="63" y="63"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="6" width="111" height="112">
|
||||
<vector name="origin" x="54" y="57"/>
|
||||
<int name="delay" value="100"/>
|
||||
</canvas>
|
||||
<canvas name="7" width="24" height="23">
|
||||
<vector name="origin" x="9" y="15"/>
|
||||
<int name="delay" value="100"/>
|
||||
<int name="a0" value="255"/>
|
||||
<int name="a1" value="0"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="49" height="54">
|
||||
<vector name="origin" x="22" y="72"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="21">
|
||||
<string name="info" value="넥스1/넥스2"/>
|
||||
<int name="x" value="120"/>
|
||||
<int name="time" value="15"/>
|
||||
<int name="mpCon" value="20"/>
|
||||
<int name="interval" value="30"/>
|
||||
<int name="hp" value="70"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="22">
|
||||
<string name="info" value="넥스3/넥스4"/>
|
||||
<int name="x" value="140"/>
|
||||
<int name="time" value="15"/>
|
||||
<int name="mpCon" value="20"/>
|
||||
<int name="interval" value="30"/>
|
||||
<int name="hp" value="70"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="23">
|
||||
<string name="info" value="넥스5/넥스6"/>
|
||||
<int name="x" value="160"/>
|
||||
<int name="time" value="15"/>
|
||||
<int name="mpCon" value="20"/>
|
||||
<int name="interval" value="30"/>
|
||||
<int name="hp" value="70"/>
|
||||
<imgdir name="effect">
|
||||
<canvas name="0" width="10" height="10">
|
||||
<vector name="origin" x="-2" y="32"/>
|
||||
</canvas>
|
||||
<int name="pos" value="3"/>
|
||||
</imgdir>
|
||||
<imgdir name="mob">
|
||||
<canvas name="0" width="19" height="23">
|
||||
<vector name="origin" x="9" y="44"/>
|
||||
</canvas>
|
||||
<int name="pos" value="2"/>
|
||||
<int name="icon" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
Reference in New Issue
Block a user