Implemented an improved scheduler system for channels, on where Runnables objects are "registered in" to run on a scheduled future time (effective run time will depend on the proc time of the worker acting under-the-hood). Implemented a channel scheduler for detecting "mobs currently on animation state". This allows the server to send info to the client about whether a mob should cast a skill or not at that moment. Improved concurrent protection on MapleMonster listeners registry. Improved resource deallocation when destroying a monster object. Added a server flag to allow clean slates to be used on equipments even on the "only successfully used scroll slots" case. Fixed a critical deadlock case with MapleServerHandler. Added the portal SFX for many scripted portals that still lacked the sound effect.
175 lines
7.9 KiB
Java
175 lines
7.9 KiB
Java
/*
|
|
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 net.server.channel.handlers;
|
|
|
|
import client.MapleCharacter;
|
|
import client.MapleClient;
|
|
import java.awt.Point;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import server.life.MapleMonster;
|
|
import server.life.MapleMonsterInformationProvider;
|
|
//import server.life.MobAttackInfo;
|
|
//import server.life.MobAttackInfoFactory;
|
|
import server.life.MobSkill;
|
|
import server.life.MobSkillFactory;
|
|
import server.maps.MapleMapObject;
|
|
import server.maps.MapleMapObjectType;
|
|
import server.movement.LifeMovementFragment;
|
|
import tools.MaplePacketCreator;
|
|
import tools.Pair;
|
|
import tools.Randomizer;
|
|
import tools.data.input.SeekableLittleEndianAccessor;
|
|
|
|
/**
|
|
* @author Danny (Leifde)
|
|
* @author ExtremeDevilz
|
|
* @author Ronan (HeavenMS)
|
|
*/
|
|
public final class MoveLifeHandler extends AbstractMovementPacketHandler {
|
|
@Override
|
|
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
|
|
int objectid = slea.readInt();
|
|
short moveid = slea.readShort();
|
|
MapleMapObject mmo = c.getPlayer().getMap().getMapObject(objectid);
|
|
if (mmo == null || mmo.getType() != MapleMapObjectType.MONSTER) {
|
|
return;
|
|
}
|
|
MapleMonster monster = (MapleMonster) mmo;
|
|
List<LifeMovementFragment> res = null;
|
|
List<MapleCharacter> banishPlayers = new ArrayList<>();
|
|
byte pNibbles = slea.readByte();
|
|
byte rawActivity = slea.readByte();
|
|
byte useSkillId = slea.readByte();
|
|
byte useSkillLevel = slea.readByte();
|
|
short pOption = slea.readShort();
|
|
slea.skip(8);
|
|
|
|
if (rawActivity >= 0) {
|
|
rawActivity = (byte) (rawActivity & 0xFF >> 1);
|
|
}
|
|
|
|
boolean isAttack = inRangeInclusive(rawActivity, 12, 20);
|
|
boolean isSkill = inRangeInclusive(rawActivity, 21, 25);
|
|
|
|
byte attackId = (byte) (isAttack ? rawActivity - 12 : -1);
|
|
boolean nextMovementCouldBeSkill = (pNibbles & 0x0F) != 0;
|
|
boolean pUnk = (pNibbles & 0xF0) != 0;
|
|
|
|
int nextCastSkill = useSkillId;
|
|
int nextCastSkillLevel = useSkillLevel;
|
|
|
|
MobSkill toUse = null;
|
|
int rndSkill = -1;
|
|
|
|
if(monster.getNoSkills() > 0) {
|
|
if(nextMovementCouldBeSkill) {
|
|
rndSkill = Randomizer.nextInt(monster.getNoSkills());
|
|
}
|
|
} else {
|
|
nextMovementCouldBeSkill = false;
|
|
}
|
|
|
|
if(monster.applyAnimationIfRoaming((attackId - 13) / 2, rndSkill)) {
|
|
if (rndSkill > -1) {
|
|
Pair<Integer, Integer> skillToUse = monster.getSkills().get(rndSkill);
|
|
nextCastSkill = skillToUse.getLeft();
|
|
nextCastSkillLevel = skillToUse.getRight();
|
|
toUse = MobSkillFactory.getMobSkill(nextCastSkill, nextCastSkillLevel);
|
|
|
|
if (isSkill || isAttack) {
|
|
int percHpLeft = (int) (((float) monster.getHp() / monster.getMaxHp()) * 100);
|
|
if (nextCastSkill != toUse.getSkillId() || nextCastSkillLevel != toUse.getSkillLevel()) {
|
|
//toUse.resetAnticipatedSkill();
|
|
return;
|
|
} else if (toUse.getHP() < percHpLeft) {
|
|
toUse = null;
|
|
} else if (monster.canUseSkill(toUse)) {
|
|
int animationTime = MapleMonsterInformationProvider.getInstance().getMobSkillAnimationTime(monster.getId(), rndSkill);
|
|
if(animationTime > 0) {
|
|
toUse.applyDelayedEffect(c.getPlayer(), monster, true, banishPlayers, animationTime);
|
|
} else {
|
|
toUse.applyEffect(c.getPlayer(), monster, true, banishPlayers);
|
|
}
|
|
} else {
|
|
toUse = null;
|
|
}
|
|
} else {
|
|
toUse = null; // paliative measure for suspicious mob movement
|
|
|
|
/*
|
|
long curtime = System.currentTimeMillis();
|
|
if(curtime >= monster.getNextBasicSkillTime()) { // dont use the special attack too often, chase the player f3
|
|
MobAttackInfo mobAttack = MobAttackInfoFactory.getMobAttackInfo(monster, attackId);
|
|
monster.setNextBasicSkillTime(curtime);
|
|
} else {
|
|
toUse = null;
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
} else {
|
|
if(rndSkill > -1) {
|
|
nextMovementCouldBeSkill = false;
|
|
}
|
|
}
|
|
|
|
slea.readByte();
|
|
slea.readInt(); // whatever
|
|
short start_x = slea.readShort(); // hmm.. startpos?
|
|
short start_y = slea.readShort(); // hmm...
|
|
Point startPos = new Point(start_x, start_y);
|
|
res = parseMovement(slea);
|
|
if (monster.getController() != c.getPlayer()) {
|
|
if (monster.isAttackedBy(c.getPlayer())) {
|
|
monster.switchController(c.getPlayer(), true);
|
|
} else {
|
|
return;
|
|
}
|
|
} else if (rawActivity == -1 && monster.isControllerKnowsAboutAggro() && !monster.isMobile() && !monster.isFirstAttack()) {
|
|
monster.setControllerHasAggro(false);
|
|
monster.setControllerKnowsAboutAggro(false);
|
|
}
|
|
boolean aggro = monster.isControllerHasAggro();
|
|
if (toUse != null) {
|
|
c.announce(MaplePacketCreator.moveMonsterResponse(objectid, moveid, monster.getMp(), aggro, toUse.getSkillId(), toUse.getSkillLevel()));
|
|
} else {
|
|
c.announce(MaplePacketCreator.moveMonsterResponse(objectid, moveid, monster.getMp(), aggro));
|
|
}
|
|
if (aggro) {
|
|
monster.setControllerKnowsAboutAggro(true);
|
|
}
|
|
if (res != null) {
|
|
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), MaplePacketCreator.moveMonster(objectid, nextMovementCouldBeSkill, rawActivity, useSkillId, useSkillLevel, pOption, startPos, res), monster.getPosition());
|
|
updatePosition(res, monster, -1);
|
|
c.getPlayer().getMap().moveMonster(monster, monster.getPosition());
|
|
}
|
|
|
|
for (MapleCharacter chr : banishPlayers) {
|
|
chr.changeMapBanish(monster.getBanish().getMap(), monster.getBanish().getPortal(), monster.getBanish().getMsg());
|
|
}
|
|
}
|
|
|
|
public static boolean inRangeInclusive(Byte pVal, Integer pMin, Integer pMax) {
|
|
return !(pVal < pMin) || (pVal > pMax);
|
|
}
|
|
} |