Added Puppets on Aggro + Guild/Alliance packet info patch + Pet flags
Added pet flags recognition, such as the "pet speed same as owner" mechanic. Fixed repeatable quest "Remnants of HT..." not rewarding Dragon Stones. Refactored character object's check slots method, now using the same algorithm from the API class. Fixed a vunerability from before reviving/respawning players to towns, on where players would get their HP restored in area map before sending them back to safety. Fixed some NPC gates in Mushking Empire, allowing players to access bosses free of quest requirements. Improved dialog of NPCs for "permission to attempt Zakum expeds". Implemented (or rather improved) item scripts, now using NPC conversation methods as it was intended originally. Rehauled mob-skill "summon mobs" limit, now using the placeholders found in the wz to determine limit of concurrent underlings spawned, and "limit" now refers to total of underlings spawned from a mob. Revised IoSession closing mechanics in several login classes. Implemented automated loggedin account shutdown if another player has gained a pass from the DB to login. Improved the server-side check for teleport rocks. Fixed removeAfter from mobs being procced after they were disposed. Reviewed EIM start references on several scripts, minding the latest EIM setup implementations. Fixed equipments that were reused Scissors of Karma not not being able to sell on pshops/merchants. Fixed a bug where entering alliances would lead a player to see information from other guilds of past alliances until an update takes place. Padronized a few skillbook names. Reviewed player puppets not gaining priority properly on the recent aggro system. Improved "item sold" message of merchants, now displaying also quantity left of an item on store. Fixed a bug with itemid limits of weapons on server.
This commit is contained in:
@@ -112,7 +112,7 @@ public class MapleItemInformationProvider {
|
||||
protected Map<Integer, Integer> monsterBookID = new HashMap<>();
|
||||
protected Map<Integer, Boolean> untradeableCache = new HashMap<>();
|
||||
protected Map<Integer, Boolean> onEquipUntradeableCache = new HashMap<>();
|
||||
protected Map<Integer, scriptedItem> scriptedItemCache = new HashMap<>();
|
||||
protected Map<Integer, ScriptedItem> scriptedItemCache = new HashMap<>();
|
||||
protected Map<Integer, Boolean> karmaCache = new HashMap<>();
|
||||
protected Map<Integer, Integer> triggerItemCache = new HashMap<>();
|
||||
protected Map<Integer, Integer> expCache = new HashMap<>();
|
||||
@@ -1447,14 +1447,14 @@ public class MapleItemInformationProvider {
|
||||
return untradeableOnEquip;
|
||||
}
|
||||
|
||||
public scriptedItem getScriptedItemInfo(int itemId) {
|
||||
public ScriptedItem getScriptedItemInfo(int itemId) {
|
||||
if (scriptedItemCache.containsKey(itemId)) {
|
||||
return scriptedItemCache.get(itemId);
|
||||
}
|
||||
if ((itemId / 10000) != 243) {
|
||||
return null;
|
||||
}
|
||||
scriptedItem script = new scriptedItem(MapleDataTool.getInt("spec/npc", getItemData(itemId), 0),
|
||||
ScriptedItem script = new ScriptedItem(MapleDataTool.getInt("spec/npc", getItemData(itemId), 0),
|
||||
MapleDataTool.getString("spec/script", getItemData(itemId), ""),
|
||||
MapleDataTool.getInt("spec/runOnPickup", getItemData(itemId), 0) == 1);
|
||||
scriptedItemCache.put(itemId, script);
|
||||
@@ -2100,13 +2100,13 @@ public class MapleItemInformationProvider {
|
||||
return skillbook;
|
||||
}
|
||||
|
||||
public class scriptedItem {
|
||||
public class ScriptedItem {
|
||||
|
||||
private boolean runOnPickup;
|
||||
private int npc;
|
||||
private String script;
|
||||
|
||||
public scriptedItem(int npc, String script, boolean rop) {
|
||||
public ScriptedItem(int npc, String script, boolean rop) {
|
||||
this.npc = npc;
|
||||
this.script = script;
|
||||
this.runOnPickup = rop;
|
||||
|
||||
+88
-23
@@ -82,16 +82,16 @@ public class MapleTrade {
|
||||
partner.getChr().getClient().announce(MaplePacketCreator.getTradeConfirmation());
|
||||
}
|
||||
|
||||
private void complete1() {
|
||||
private void fetchExchangedItems() {
|
||||
exchangeItems = partner.getItems();
|
||||
exchangeMeso = partner.getMeso();
|
||||
}
|
||||
|
||||
private void complete2() {
|
||||
private void completeTrade() {
|
||||
boolean show = ServerConstants.USE_DEBUG;
|
||||
|
||||
items.clear();
|
||||
meso = 0;
|
||||
|
||||
for (Item item : exchangeItems) {
|
||||
MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item);
|
||||
MapleInventoryManipulator.addFromDrop(chr.getClient(), item, show);
|
||||
@@ -164,12 +164,21 @@ public class MapleTrade {
|
||||
}
|
||||
}
|
||||
|
||||
public void addItem(Item item) {
|
||||
items.add(item);
|
||||
chr.getClient().announce(MaplePacketCreator.getTradeItemAdd((byte) 0, item));
|
||||
if (partner != null) {
|
||||
partner.getChr().getClient().announce(MaplePacketCreator.getTradeItemAdd((byte) 1, item));
|
||||
public boolean addItem(Item item) {
|
||||
synchronized (items) {
|
||||
if (items.size() > 9) {
|
||||
return false;
|
||||
}
|
||||
for (Item it : items) {
|
||||
if (it.getPosition() == item.getPosition()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void chat(String message) {
|
||||
@@ -214,38 +223,65 @@ public class MapleTrade {
|
||||
|
||||
return MapleInventory.checkSpotsAndOwnership(chr, tradeItems);
|
||||
}
|
||||
|
||||
|
||||
private synchronized boolean checkTradeCompleteHandshake(boolean updateSelf) {
|
||||
MapleTrade self, other;
|
||||
|
||||
if (updateSelf) {
|
||||
self = this;
|
||||
other = this.getPartner();
|
||||
} else {
|
||||
self = this.getPartner();
|
||||
other = this;
|
||||
}
|
||||
|
||||
if (self.isLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.lockTrade();
|
||||
return other.isLocked();
|
||||
}
|
||||
|
||||
private boolean checkCompleteHandshake() { // handshake checkout thanks to Ronan
|
||||
if (this.getChr().getId() < this.getPartner().getChr().getId()) {
|
||||
return this.checkTradeCompleteHandshake(true);
|
||||
} else {
|
||||
return this.getPartner().checkTradeCompleteHandshake(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void completeTrade(MapleCharacter c) {
|
||||
c.getTrade().lockTrade();
|
||||
MapleTrade local = c.getTrade();
|
||||
MapleTrade partner = local.getPartner();
|
||||
if (partner.isLocked()) {
|
||||
local.complete1();
|
||||
partner.complete1();
|
||||
if (local.checkCompleteHandshake()) {
|
||||
local.fetchExchangedItems();
|
||||
partner.fetchExchangedItems();
|
||||
|
||||
if (!local.fitsMeso()) {
|
||||
cancelTrade(c);
|
||||
c.message("There is not enough meso inventory space to complete the trade.");
|
||||
partner.getChr().message("Partner does not have enough meso inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
else if (!partner.fitsMeso()) {
|
||||
} else if (!partner.fitsMeso()) {
|
||||
cancelTrade(c);
|
||||
c.message("Partner does not have enough meso inventory space to complete the trade.");
|
||||
partner.getChr().message("There is not enough meso inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!local.fitsInInventory()) {
|
||||
cancelTrade(c);
|
||||
c.message("There is not enough inventory space to complete the trade.");
|
||||
partner.getChr().message("Partner does not have enough inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
else if (!partner.fitsInInventory()) {
|
||||
} else if (!partner.fitsInInventory()) {
|
||||
cancelTrade(c);
|
||||
c.message("Partner does not have enough inventory space to complete the trade.");
|
||||
partner.getChr().message("There is not enough inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (local.getChr().getLevel() < 15) {
|
||||
if (local.getChr().getMesosTraded() + local.exchangeMeso > 1000000) {
|
||||
cancelTrade(c);
|
||||
@@ -263,16 +299,18 @@ public class MapleTrade {
|
||||
partner.getChr().addMesosTraded(partner.exchangeMeso);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.logTrade(local, partner);
|
||||
local.complete2();
|
||||
partner.complete2();
|
||||
local.completeTrade();
|
||||
partner.completeTrade();
|
||||
|
||||
partner.getChr().setTrade(null);
|
||||
c.setTrade(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void cancelTrade(MapleCharacter c) {
|
||||
MapleTrade trade = c.getTrade();
|
||||
|
||||
private static void cancelTradeInternal(MapleCharacter chr) {
|
||||
MapleTrade trade = chr.getTrade();
|
||||
if(trade == null) return;
|
||||
|
||||
trade.cancel();
|
||||
@@ -280,9 +318,36 @@ public class MapleTrade {
|
||||
trade.getPartner().cancel();
|
||||
trade.getPartner().getChr().setTrade(null);
|
||||
}
|
||||
c.setTrade(null);
|
||||
chr.setTrade(null);
|
||||
}
|
||||
|
||||
private synchronized void tradeCancelHandshake(boolean updateSelf) {
|
||||
MapleTrade self;
|
||||
|
||||
if (updateSelf) {
|
||||
self = this;
|
||||
} else {
|
||||
self = this.getPartner();
|
||||
}
|
||||
|
||||
cancelTradeInternal(self.getChr());
|
||||
}
|
||||
|
||||
private void cancelHandshake() { // handshake checkout thanks to Ronan
|
||||
if (this.getChr().getId() < this.getPartner().getChr().getId()) {
|
||||
this.tradeCancelHandshake(true);
|
||||
} else {
|
||||
this.getPartner().tradeCancelHandshake(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void cancelTrade(MapleCharacter chr) {
|
||||
MapleTrade trade = chr.getTrade();
|
||||
if(trade == null) return;
|
||||
|
||||
trade.cancelHandshake();
|
||||
}
|
||||
|
||||
public static void startTrade(MapleCharacter c) {
|
||||
if (c.getTrade() == null) {
|
||||
c.setTrade(new MapleTrade((byte) 0, c));
|
||||
|
||||
@@ -63,7 +63,6 @@ import scripting.event.EventInstanceManager;
|
||||
import server.TimerManager;
|
||||
import server.life.MapleLifeFactory.BanishInfo;
|
||||
import server.maps.MapleMap;
|
||||
import server.maps.MapleMapObject;
|
||||
import server.maps.MapleMapObjectType;
|
||||
import tools.MaplePacketCreator;
|
||||
import tools.Pair;
|
||||
@@ -72,6 +71,8 @@ import net.server.audit.LockCollector;
|
||||
import net.server.audit.locks.MonitoredLockType;
|
||||
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
|
||||
import net.server.coordinator.MapleMonsterAggroCoordinator;
|
||||
import server.MapleStatEffect;
|
||||
import server.maps.MapleSummon;
|
||||
|
||||
public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats.
|
||||
@@ -80,7 +81,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private AtomicLong maxHpPlusHeal = new AtomicLong(1);
|
||||
private int mp;
|
||||
private WeakReference<MapleCharacter> controller = new WeakReference<>(null);
|
||||
private boolean controllerHasAggro, controllerKnowsAboutAggro;
|
||||
private boolean controllerHasAggro, controllerKnowsAboutAggro, controllerHasPuppet;
|
||||
private Collection<MonsterListener> listeners = new LinkedList<>();
|
||||
private EnumMap<MonsterStatus, MonsterStatusEffect> stati = new EnumMap<>(MonsterStatus.class);
|
||||
private ArrayList<MonsterStatus> alreadyBuffed = new ArrayList<>();
|
||||
@@ -91,10 +92,15 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private List<Pair<Integer, Integer>> usedSkills = new ArrayList<>();
|
||||
private Map<Pair<Integer, Integer>, Integer> skillsUsed = new HashMap<>();
|
||||
private Set<Integer> usedAttacks = new HashSet<>();
|
||||
private Set<Integer> calledMobOids = null;
|
||||
private int calledMobCount = 0;
|
||||
private WeakReference<MapleMonster> callerMob = new WeakReference<>(null);
|
||||
private List<Integer> stolenItems = new ArrayList<>();
|
||||
private int team;
|
||||
private int parentMobOid = 0;
|
||||
private final HashMap<Integer, AtomicInteger> takenDamage = new HashMap<>();
|
||||
private Runnable removeAfterAction = null;
|
||||
private boolean availablePuppetUpdate = true;
|
||||
|
||||
private MonitoredReentrantLock externalLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_EXT);
|
||||
private MonitoredReentrantLock monsterLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB, true);
|
||||
@@ -152,7 +158,59 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
public void setParentMobOid(int parentMobId) {
|
||||
this.parentMobOid = parentMobId;
|
||||
}
|
||||
|
||||
|
||||
public int countAvailableMobSummons(int limit, int skillLimit) { // limit prop for summons has another conotation, found thanks to MedicOP
|
||||
Set<Integer> calledOids = this.calledMobOids;
|
||||
if(calledOids != null) {
|
||||
limit -= calledOids.size();
|
||||
}
|
||||
|
||||
return Math.min(limit, skillLimit - this.calledMobCount);
|
||||
}
|
||||
|
||||
public void addSummonedMob(MapleMonster mob) {
|
||||
Set<Integer> calledOids = this.calledMobOids;
|
||||
if (calledOids == null) {
|
||||
calledOids = Collections.synchronizedSet(new HashSet<Integer>());
|
||||
this.calledMobOids = calledOids;
|
||||
}
|
||||
|
||||
calledOids.add(mob.getObjectId());
|
||||
mob.setSummonerMob(this);
|
||||
this.calledMobCount += 1;
|
||||
}
|
||||
|
||||
private void removeSummonedMob(int mobOid) {
|
||||
Set<Integer> calledOids = this.calledMobOids;
|
||||
if (calledOids != null) {
|
||||
calledOids.remove(mobOid);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSummonerMob(MapleMonster mob) {
|
||||
this.callerMob = new WeakReference<>(mob);
|
||||
}
|
||||
|
||||
private void dispatchClearSummons() {
|
||||
MapleMonster caller = this.callerMob.get();
|
||||
if (caller != null) {
|
||||
caller.removeSummonedMob(this.getObjectId());
|
||||
}
|
||||
|
||||
this.calledMobOids = null;
|
||||
}
|
||||
|
||||
public void pushRemoveAfterAction(Runnable run) {
|
||||
this.removeAfterAction = run;
|
||||
}
|
||||
|
||||
public Runnable popRemoveAfterAction() {
|
||||
Runnable r = this.removeAfterAction;
|
||||
this.removeAfterAction = null;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public int getHp() {
|
||||
return hp.get();
|
||||
}
|
||||
@@ -421,7 +479,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
int expSharersLevel = 0;
|
||||
for (MapleCharacter mc : members) {
|
||||
if (mc.getLevel() >= minThresholdLevel) { //NO EXP WILL BE GIVEN for those who are underleveled!
|
||||
if (Math.abs(killerLevel - mc.getLevel()) < ServerConstants.MIN_UNDERLEVEL_TO_EXP_LEECH) {
|
||||
if (Math.abs(killerLevel - mc.getLevel()) < ServerConstants.MIN_RANGELEVEL_TO_EXP_LEECH) {
|
||||
// thanks Thora for pointing out leech level limitation
|
||||
|
||||
expSharersLevel += mc.getLevel();
|
||||
@@ -710,6 +768,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
|
||||
this.aggroClearDamages();
|
||||
this.dispatchClearSummons();
|
||||
|
||||
MonsterListener[] listenersList;
|
||||
statiLock.lock();
|
||||
@@ -809,6 +868,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
|
||||
if (!fake) this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
|
||||
}
|
||||
|
||||
private void setControllerHasPuppet(boolean controllerHasPuppet) {
|
||||
this.controllerHasPuppet = controllerHasPuppet;
|
||||
}
|
||||
|
||||
public byte[] makeBossHPBarPacket() {
|
||||
return MaplePacketCreator.showBossHP(getId(), getHp(), getMaxHp(), getTagColor(), getTagBgColor());
|
||||
@@ -1233,39 +1296,13 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
return false;
|
||||
}
|
||||
|
||||
int useLimit = toUse.getLimit();
|
||||
int useSkillid = toUse.getSkillId();
|
||||
if (useSkillid == 200) {
|
||||
int i = 0;
|
||||
for (MapleMapObject mo : getMap().getMapObjects()) {
|
||||
if (mo.getType() == MapleMapObjectType.MONSTER) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i > 100) {
|
||||
return false;
|
||||
}
|
||||
if (map.isDojoMap()) { // spawns in dojo should be unlimited
|
||||
useLimit = 0;
|
||||
}
|
||||
} else if (useSkillid >= 143 && useSkillid <= 145) {
|
||||
if (useSkillid >= 143 && useSkillid <= 145) {
|
||||
if (this.isBuffed(MonsterStatus.WEAPON_REFLECT) || this.isBuffed(MonsterStatus.MAGIC_REFLECT)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (useLimit > 0) {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
Integer times = this.skillsUsed.get(new Pair<>(useSkillid, toUse.getSkillLevel()));
|
||||
if (times != null && times >= useLimit) {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
monsterLock.lock();
|
||||
try {
|
||||
/*
|
||||
@@ -1303,13 +1340,14 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
try {
|
||||
mp -= skill.getMpCon();
|
||||
|
||||
this.usedSkills.add(new Pair<>(skillId, level));
|
||||
if (this.skillsUsed.containsKey(new Pair<>(skillId, level))) {
|
||||
int times = this.skillsUsed.get(new Pair<>(skillId, level)) + 1;
|
||||
this.skillsUsed.remove(new Pair<>(skillId, level));
|
||||
this.skillsUsed.put(new Pair<>(skillId, level), times);
|
||||
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(new Pair<>(skillId, level), 1);
|
||||
this.skillsUsed.put(skillKey, 1);
|
||||
}
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
@@ -1605,6 +1643,40 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
changeLevelByDifficulty(difficulty, pqMob);
|
||||
}
|
||||
|
||||
private boolean isPuppetInVicinity(MapleSummon summon) {
|
||||
return summon.getPosition().distanceSq(this.getPosition()) < 177777;
|
||||
}
|
||||
|
||||
public boolean isCharacterPuppetInVicinity(MapleCharacter chr) {
|
||||
MapleStatEffect mse = chr.getBuffEffect(MapleBuffStat.PUPPET);
|
||||
if (mse != null) {
|
||||
MapleSummon summon = chr.getSummonByKey(mse.getSourceId());
|
||||
|
||||
// check whether mob is currently under a puppet's field of action or not
|
||||
if (summon != null) {
|
||||
if (isPuppetInVicinity(summon)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
map.getAggroCoordinator().removePuppetAggro(chr.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isLeadingPuppetInVicinity() {
|
||||
MapleCharacter chrController = this.getActiveController();
|
||||
|
||||
if (chrController != null) {
|
||||
if (this.isCharacterPuppetInVicinity(chrController)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private MapleCharacter getNextControllerCandidate() {
|
||||
int mincontrolled = Integer.MAX_VALUE;
|
||||
MapleCharacter newController = null;
|
||||
@@ -1612,11 +1684,16 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
int mincontrolleddead = Integer.MAX_VALUE;
|
||||
MapleCharacter newControllerDead = null;
|
||||
|
||||
MapleCharacter newControllerWithPuppet = null;
|
||||
|
||||
for (MapleCharacter chr : getMap().getAllPlayers()) {
|
||||
if (!chr.isHidden()) {
|
||||
int ctrlMonsSize = chr.getNumControlledMonsters();
|
||||
|
||||
if (chr.isAlive()) {
|
||||
if (isCharacterPuppetInVicinity(chr)) {
|
||||
newControllerWithPuppet = chr;
|
||||
break;
|
||||
} else if (chr.isAlive()) {
|
||||
if (ctrlMonsSize < mincontrolled) {
|
||||
mincontrolled = ctrlMonsSize;
|
||||
newController = chr;
|
||||
@@ -1630,7 +1707,13 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
return newController != null ? newController : newControllerDead;
|
||||
if (newControllerWithPuppet != null) {
|
||||
return newControllerWithPuppet;
|
||||
} else if (newController != null) {
|
||||
return newController;
|
||||
} else {
|
||||
return newControllerDead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1682,15 +1765,39 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
this.setController(newController);
|
||||
this.setControllerHasAggro(immediateAggro);
|
||||
this.setControllerKnowsAboutAggro(false);
|
||||
this.setControllerHasPuppet(false);
|
||||
} finally {
|
||||
aggroUpdateLock.unlock();
|
||||
}
|
||||
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
newController.announce(MaplePacketCreator.controlMonster(this, false, immediateAggro));
|
||||
newController.controlMonster(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void aggroAddPuppet(MapleCharacter player) {
|
||||
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();
|
||||
mmac.addPuppetAggro(player);
|
||||
|
||||
aggroUpdatePuppetController(player);
|
||||
|
||||
if (this.isControllerHasAggro()) {
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public void aggroRemovePuppet(MapleCharacter player) {
|
||||
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();
|
||||
mmac.removePuppetAggro(player.getId());
|
||||
|
||||
aggroUpdatePuppetController(null);
|
||||
|
||||
if (this.isControllerHasAggro()) {
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automagically finds a new controller for the given monster from the chars
|
||||
* on the map it is from...
|
||||
@@ -1710,6 +1817,60 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
this.aggroSwitchController(newController, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a new controller for the given monster from the chars with deployed puppet
|
||||
* nearby on the map it is from...
|
||||
*
|
||||
*/
|
||||
private void aggroUpdatePuppetController(MapleCharacter newController) {
|
||||
MapleCharacter chrController = this.getActiveController();
|
||||
boolean updateController = false;
|
||||
|
||||
if (chrController != null && chrController.isAlive()) {
|
||||
if (isCharacterPuppetInVicinity(chrController)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
updateController = true;
|
||||
}
|
||||
|
||||
if (newController == null || !isCharacterPuppetInVicinity(newController)) {
|
||||
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();
|
||||
|
||||
List<Integer> puppetOwners = mmac.getPuppetAggroList();
|
||||
List<Integer> toRemovePuppets = new LinkedList<>();
|
||||
|
||||
for (Integer cid : puppetOwners) {
|
||||
MapleCharacter chr = map.getCharacterById(cid);
|
||||
|
||||
if (chr != null) {
|
||||
if (isCharacterPuppetInVicinity(chr)) {
|
||||
newController = chr;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
toRemovePuppets.add(cid);
|
||||
}
|
||||
}
|
||||
|
||||
for (Integer cid : toRemovePuppets) {
|
||||
mmac.removePuppetAggro(cid);
|
||||
}
|
||||
|
||||
if (newController == null) { // was a new controller found? (if not there's no puppet nearby)
|
||||
if (updateController) {
|
||||
aggroUpdateController();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} else if (chrController == newController) {
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
}
|
||||
|
||||
this.aggroSwitchController(newController, this.isControllerHasAggro());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures controllability removal of the current player controller, and
|
||||
* fetches for any player on the map to start controlling in place.
|
||||
@@ -1767,6 +1928,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
if (chrController != attacker) {
|
||||
if (this.getMapAggroCoordinator().isLeadingCharacterAggro(this, attacker)) {
|
||||
this.aggroSwitchController(attacker, true);
|
||||
} else {
|
||||
this.setControllerHasAggro(true);
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1780,9 +1944,69 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
*/
|
||||
} else {
|
||||
this.setControllerHasAggro(true);
|
||||
this.aggroUpdatePuppetVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void aggroRefreshPuppetVisibility(MapleCharacter chrController, MapleSummon puppet) {
|
||||
// lame patch for client to redirect all aggro to the puppet
|
||||
|
||||
List<MapleMonster> puppetControlled = new LinkedList<>();
|
||||
for (MapleMonster mob : chrController.getControlledMonsters()) {
|
||||
if (mob.isPuppetInVicinity(puppet)) {
|
||||
puppetControlled.add(mob);
|
||||
}
|
||||
}
|
||||
|
||||
for (MapleMonster mob : puppetControlled) {
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(mob.getObjectId()));
|
||||
}
|
||||
chrController.announce(MaplePacketCreator.removeSummon(puppet, false));
|
||||
|
||||
for (MapleMonster mob : puppetControlled) {
|
||||
chrController.announce(MaplePacketCreator.controlMonster(mob, false, mob.isControllerHasAggro()));
|
||||
}
|
||||
chrController.announce(MaplePacketCreator.spawnSummon(puppet, false));
|
||||
}
|
||||
|
||||
public void aggroUpdatePuppetVisibility() {
|
||||
if (!availablePuppetUpdate) return;
|
||||
|
||||
availablePuppetUpdate = false;
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MapleCharacter chrController = MapleMonster.this.getActiveController();
|
||||
if (chrController == null) return;
|
||||
|
||||
MapleStatEffect puppetEffect = chrController.getBuffEffect(MapleBuffStat.PUPPET);
|
||||
if (puppetEffect != null) {
|
||||
MapleSummon puppet = chrController.getSummonByKey(puppetEffect.getSourceId());
|
||||
|
||||
if (puppet != null && isPuppetInVicinity(puppet)) {
|
||||
controllerHasPuppet = true;
|
||||
aggroRefreshPuppetVisibility(chrController, puppet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controllerHasPuppet) {
|
||||
controllerHasPuppet = false;
|
||||
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(MapleMonster.this.getObjectId()));
|
||||
chrController.announce(MaplePacketCreator.controlMonster(MapleMonster.this, false, MapleMonster.this.isControllerHasAggro()));
|
||||
}
|
||||
} finally {
|
||||
availablePuppetUpdate = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// had to schedule this since mob wouldn't stick to puppet aggro who knows why
|
||||
this.getMap().getChannelServer().registerOverallAction(this.getMap().getId(), r, ServerConstants.UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all applied damage input for this mob, doesn't refresh target aggro.
|
||||
*
|
||||
@@ -1805,7 +2029,12 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
public final void disposeLocks() {
|
||||
public void dispose() {
|
||||
this.getMap().dismissRemoveAfter(this);
|
||||
disposeLocks();
|
||||
}
|
||||
|
||||
private void disposeLocks() {
|
||||
LockCollector.getInstance().registerDisposeAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -33,6 +33,7 @@ import constants.GameConstants;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import tools.Randomizer;
|
||||
import server.maps.MapleMap;
|
||||
import server.maps.MapleMapObject;
|
||||
import server.maps.MapleMapObjectType;
|
||||
import server.maps.MapleMist;
|
||||
@@ -235,57 +236,72 @@ public class MobSkill {
|
||||
case 156: // speed up
|
||||
break;
|
||||
case 200: // summon
|
||||
if (monster.getMap().getSpawnedMonstersOnMap() < 80) {
|
||||
for (Integer mobId : getSummons()) {
|
||||
MapleMonster toSpawn = MapleLifeFactory.getMonster(mobId);
|
||||
if(toSpawn != null) {
|
||||
if(GameConstants.isBossRush(monster.getMap().getId())) 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 8500003: // Pap bomb high
|
||||
toSpawn.setFh((int) Math.ceil(Math.random() * 19.0));
|
||||
ypos = -590;
|
||||
break;
|
||||
case 8500004: // Pap bomb
|
||||
xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500);
|
||||
if (ypos != -590) {
|
||||
ypos = (int) monster.getPosition().getY();
|
||||
}
|
||||
break;
|
||||
case 8510100: //Pianus bomb
|
||||
if (Math.ceil(Math.random() * 5) == 1) {
|
||||
ypos = 78;
|
||||
xpos = (int) Randomizer.nextInt(5) + (Randomizer.nextInt(2) == 1 ? 180 : 0);
|
||||
} else {
|
||||
int skillLimit = this.getLimit();
|
||||
MapleMap map = monster.getMap();
|
||||
|
||||
if (map.isDojoMap()) { // 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) {
|
||||
Collections.shuffle(summons);
|
||||
boolean bossRushMap = GameConstants.isBossRush(map.getId());
|
||||
|
||||
for (Integer mobId : summons.subList(0, summonLimit)) {
|
||||
MapleMonster toSpawn = MapleLifeFactory.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 8500003: // Pap bomb high
|
||||
toSpawn.setFh((int) Math.ceil(Math.random() * 19.0));
|
||||
ypos = -590;
|
||||
break;
|
||||
case 8500004: // Pap bomb
|
||||
xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (monster.getMap().getId()) {
|
||||
case 220080001: //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 230040420: // 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() == 8500004) {
|
||||
monster.getMap().spawnFakeMonster(toSpawn);
|
||||
} else {
|
||||
monster.getMap().spawnMonsterWithEffect(toSpawn, getSpawnEffect(), toSpawn.getPosition());
|
||||
if (ypos != -590) {
|
||||
ypos = (int) monster.getPosition().getY();
|
||||
}
|
||||
break;
|
||||
case 8510100: //Pianus bomb
|
||||
if (Math.ceil(Math.random() * 5) == 1) {
|
||||
ypos = 78;
|
||||
xpos = (int) Randomizer.nextInt(5) + (Randomizer.nextInt(2) == 1 ? 180 : 0);
|
||||
} else {
|
||||
xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (map.getId()) {
|
||||
case 220080001: //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 230040420: // 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() == 8500004) {
|
||||
map.spawnFakeMonster(toSpawn);
|
||||
} else {
|
||||
map.spawnMonsterWithEffect(toSpawn, getSpawnEffect(), toSpawn.getPosition());
|
||||
}
|
||||
monster.addSummonedMob(toSpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +358,7 @@ public class MobSkill {
|
||||
}
|
||||
|
||||
public List<Integer> getSummons() {
|
||||
return Collections.unmodifiableList(toSummon);
|
||||
return new ArrayList<>(toSummon);
|
||||
}
|
||||
|
||||
public int getSpawnEffect() {
|
||||
|
||||
@@ -44,12 +44,19 @@ public class MapMonitor {
|
||||
}
|
||||
|
||||
private void cancelAction() {
|
||||
monitorSchedule.cancel(false);
|
||||
if (monitorSchedule != null) { // thanks Thora for pointing a NPE occurring here
|
||||
monitorSchedule.cancel(false);
|
||||
monitorSchedule = null;
|
||||
}
|
||||
|
||||
map.killAllMonsters();
|
||||
map.clearDrops();
|
||||
if (portal != null) {
|
||||
portal.setPortalStatus(MaplePortal.OPEN);
|
||||
}
|
||||
map.resetReactors();
|
||||
|
||||
map = null;
|
||||
portal = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,18 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
return MapleInventoryManipulator.checkSpace(c, newItem.getItemId(), newItem.getQuantity(), newItem.getOwner()) && MapleInventoryManipulator.addFromDrop(c, newItem, false);
|
||||
}
|
||||
|
||||
private int getQuantityLeft(int itemid) {
|
||||
int count = 0;
|
||||
|
||||
for (MaplePlayerShopItem mpsi : items) {
|
||||
if (mpsi.getItem().getItemId() == itemid) {
|
||||
count += (mpsi.getBundles() * mpsi.getItem().getQuantity());
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void buy(MapleClient c, int item, short quantity) {
|
||||
synchronized (items) {
|
||||
MaplePlayerShopItem pItem = items.get(item);
|
||||
@@ -247,7 +259,6 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
if (c.getPlayer().getMeso() >= price) {
|
||||
if (canBuy(c, newItem)) {
|
||||
c.getPlayer().gainMeso(-price, false);
|
||||
if(ServerConstants.USE_ANNOUNCE_SHOPITEMSOLD) announceItemSold(newItem, price); // idea thanks to Vcoc
|
||||
|
||||
synchronized (sold) {
|
||||
sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), newItem.getQuantity(), price));
|
||||
@@ -257,6 +268,11 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
if (pItem.getBundles() < 1) {
|
||||
pItem.setDoesExist(false);
|
||||
}
|
||||
|
||||
if(ServerConstants.USE_ANNOUNCE_SHOPITEMSOLD) { // idea thanks to Vcoc
|
||||
announceItemSold(newItem, price, getQuantityLeft(pItem.getItem().getItemId()));
|
||||
}
|
||||
|
||||
MapleCharacter owner = Server.getInstance().getWorld(world).getPlayerStorage().getCharacterByName(ownerName);
|
||||
if (owner != null) {
|
||||
owner.addMerchantMesos(price);
|
||||
@@ -288,12 +304,12 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
}
|
||||
}
|
||||
|
||||
private void announceItemSold(Item item, int mesos) {
|
||||
String qtyStr = (item.getQuantity() > 1) ? " (qty. " + item.getQuantity() + ")" : "";
|
||||
private void announceItemSold(Item item, int mesos, int inStore) {
|
||||
String qtyStr = (item.getQuantity() > 1) ? " x " + item.getQuantity() : "";
|
||||
|
||||
MapleCharacter player = Server.getInstance().getWorld(world).getPlayerStorage().getCharacterById(ownerId);
|
||||
if(player != null && player.isLoggedinWorld()) {
|
||||
player.dropMessage(6, "[HIRED MERCHANT] Item '" + MapleItemInformationProvider.getInstance().getName(item.getItemId()) + "'" + qtyStr + " has been sold for " + mesos + " mesos.");
|
||||
player.dropMessage(6, "[Hired Merchant] Item '" + MapleItemInformationProvider.getInstance().getName(item.getItemId()) + "'" + qtyStr + " has been sold for " + mesos + " mesos. (" + inStore + " left)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1796,21 +1796,36 @@ public class MapleMap {
|
||||
private void applyRemoveAfter(final MapleMonster monster) {
|
||||
final selfDestruction selfDestruction = monster.getStats().selfDestruction();
|
||||
if (monster.getStats().removeAfter() > 0 || selfDestruction != null && selfDestruction.getHp() < 0) {
|
||||
Runnable removeAfterAction;
|
||||
|
||||
if (selfDestruction == null) {
|
||||
registerMapSchedule(new Runnable() {
|
||||
removeAfterAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
killMonster(monster, null, false);
|
||||
}
|
||||
}, monster.getStats().removeAfter() * 1000);
|
||||
};
|
||||
|
||||
registerMapSchedule(removeAfterAction, monster.getStats().removeAfter() * 1000);
|
||||
} else {
|
||||
registerMapSchedule(new Runnable() {
|
||||
removeAfterAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
killMonster(monster, null, false, selfDestruction.getAction());
|
||||
}
|
||||
}, selfDestruction.removeAfter() * 1000);
|
||||
};
|
||||
|
||||
registerMapSchedule(removeAfterAction, selfDestruction.removeAfter() * 1000);
|
||||
}
|
||||
|
||||
monster.pushRemoveAfterAction(removeAfterAction);
|
||||
}
|
||||
}
|
||||
|
||||
public void dismissRemoveAfter(final MapleMonster monster) {
|
||||
Runnable removeAfterAction = monster.popRemoveAfterAction();
|
||||
if (removeAfterAction != null) {
|
||||
this.getChannelServer().forceRunOverallAction(mapid, removeAfterAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2583,6 +2598,18 @@ public class MapleMap {
|
||||
return Collections.unmodifiableCollection(portals.values());
|
||||
}
|
||||
*/
|
||||
|
||||
public void addPlayerPuppet(MapleCharacter player) {
|
||||
for (MapleMonster mm : this.getMonsters()) {
|
||||
mm.aggroAddPuppet(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlayerPuppet(MapleCharacter player) {
|
||||
for (MapleMonster mm : this.getMonsters()) {
|
||||
mm.aggroRemovePuppet(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlayer(MapleCharacter chr) {
|
||||
Channel cserv = chr.getClient().getChannelServer();
|
||||
@@ -3699,8 +3726,11 @@ public class MapleMap {
|
||||
if (mapid >= 922240100 && mapid <= 922240119) {
|
||||
toggleHiddenNPC(9001108);
|
||||
}
|
||||
mapMonitor.cancel(true);
|
||||
mapMonitor = null;
|
||||
|
||||
if (mapMonitor != null) {
|
||||
mapMonitor.cancel(true);
|
||||
mapMonitor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
@@ -3993,7 +4023,7 @@ public class MapleMap {
|
||||
|
||||
public void dispose() {
|
||||
for(MapleMonster mm : this.getMonsters()) {
|
||||
mm.disposeLocks();
|
||||
mm.dispose();
|
||||
}
|
||||
|
||||
clearMapObjects();
|
||||
|
||||
@@ -536,6 +536,9 @@ public class MapleQuest {
|
||||
case PETTAMENESS:
|
||||
ret = new PetTamenessAction(this, data);
|
||||
break;
|
||||
case PETSPEED:
|
||||
ret = new PetSpeedAction(this, data);
|
||||
break;
|
||||
default:
|
||||
//FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Unhandled Action Type: " + type.toString() + " QuestID: " + this.getId());
|
||||
break;
|
||||
|
||||
@@ -26,7 +26,7 @@ package server.quest;
|
||||
* @author Matze
|
||||
*/
|
||||
public enum MapleQuestActionType {
|
||||
UNDEFINED(-1), EXP(0), ITEM(1), NEXTQUEST(2), MESO(3), QUEST(4), SKILL(5), FAME(6), BUFF(7), PETSKILL(8), YES(9), NO(10), NPC(11), MIN_LEVEL(12), NORMAL_AUTO_START(13), PETTAMENESS(14), ZERO(15);
|
||||
UNDEFINED(-1), EXP(0), ITEM(1), NEXTQUEST(2), MESO(3), QUEST(4), SKILL(5), FAME(6), BUFF(7), PETSKILL(8), YES(9), NO(10), NPC(11), MIN_LEVEL(12), NORMAL_AUTO_START(13), PETTAMENESS(14), PETSPEED(15), ZERO(16);
|
||||
final byte type;
|
||||
|
||||
private MapleQuestActionType(int type) {
|
||||
@@ -62,6 +62,8 @@ public enum MapleQuestActionType {
|
||||
return NORMAL_AUTO_START;
|
||||
} else if (name.equals("pettameness")) {
|
||||
return PETTAMENESS;
|
||||
} else if (name.equals("petspeed")) {
|
||||
return PETSPEED;
|
||||
} else if (name.equals("0")) {
|
||||
return ZERO;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
This file is part of the HeavenMS MapleStory Server
|
||||
Copyleft (L) 2016 - 2018 RonanLana
|
||||
|
||||
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 server.quest.actions;
|
||||
|
||||
import client.MapleClient;
|
||||
import client.MapleCharacter;
|
||||
import client.inventory.MaplePet;
|
||||
import provider.MapleData;
|
||||
import provider.MapleDataTool;
|
||||
import server.quest.MapleQuest;
|
||||
import server.quest.MapleQuestActionType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ronan
|
||||
*/
|
||||
public class PetSpeedAction extends MapleQuestAction {
|
||||
|
||||
public PetSpeedAction(MapleQuest quest, MapleData data) {
|
||||
super(MapleQuestActionType.PETTAMENESS, quest);
|
||||
questID = quest.getId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void processData(MapleData data) {}
|
||||
|
||||
@Override
|
||||
public void run(MapleCharacter chr, Integer extSelection) {
|
||||
MapleClient c = chr.getClient();
|
||||
|
||||
MaplePet pet = chr.getPet(0); // assuming here only the pet leader will gain owner speed
|
||||
if(pet == null) return;
|
||||
|
||||
c.lockClient();
|
||||
try {
|
||||
pet.addPetFlag(c.getPlayer(), MaplePet.PetFlag.OWNER_SPEED);
|
||||
} finally {
|
||||
c.unlockClient();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user