/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * http://www.gnu.org/copyleft/gpl.html */package net.sf.l2j.gameserver.model;import java.util.Collection;import java.util.Collections;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.Future;import java.util.logging.Level;import java.util.logging.Logger;import javolution.util.FastList;import javolution.util.FastTable;import javolution.util.FastMap;import net.sf.l2j.Config;import net.sf.l2j.gameserver.GeoData;import net.sf.l2j.gameserver.Olympiad;import net.sf.l2j.gameserver.GameTimeController;import net.sf.l2j.gameserver.ThreadPoolManager;import net.sf.l2j.gameserver.ai.CtrlEvent;import net.sf.l2j.gameserver.ai.CtrlIntention;import net.sf.l2j.gameserver.ai.L2AttackableAI;import net.sf.l2j.gameserver.ai.L2CharacterAI;import net.sf.l2j.gameserver.datatables.MapRegionTable;import net.sf.l2j.gameserver.datatables.SkillTable;import net.sf.l2j.gameserver.datatables.MapRegionTable.TeleportWhereType;import net.sf.l2j.gameserver.handler.ISkillHandler;import net.sf.l2j.gameserver.handler.SkillHandler;import net.sf.l2j.gameserver.instancemanager.ZoneManager;import net.sf.l2j.gameserver.lib.Rnd;import net.sf.l2j.gameserver.model.L2Skill.SkillTargetType;import net.sf.l2j.gameserver.model.L2Skill.SkillType;import net.sf.l2j.gameserver.model.actor.instance.L2ArtefactInstance;import net.sf.l2j.gameserver.model.actor.instance.L2BoatInstance;import net.sf.l2j.gameserver.model.actor.instance.L2DoorInstance;import net.sf.l2j.gameserver.model.actor.instance.L2MinionInstance;import net.sf.l2j.gameserver.model.actor.instance.L2MonsterInstance;import net.sf.l2j.gameserver.model.actor.instance.L2NpcInstance;import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;import net.sf.l2j.gameserver.model.actor.instance.L2PetInstance;import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance.SkillDat;import net.sf.l2j.gameserver.model.actor.knownlist.CharKnownList;import net.sf.l2j.gameserver.model.actor.knownlist.ObjectKnownList.KnownListAsynchronousUpdateTask;import net.sf.l2j.gameserver.model.actor.stat.CharStat;import net.sf.l2j.gameserver.model.actor.status.CharStatus;import net.sf.l2j.gameserver.model.entity.Zone;import net.sf.l2j.gameserver.model.entity.ZoneType;import net.sf.l2j.gameserver.model.quest.QuestState;import net.sf.l2j.gameserver.serverpackets.ActionFailed;import net.sf.l2j.gameserver.serverpackets.Attack;import net.sf.l2j.gameserver.serverpackets.ChangeMoveType;import net.sf.l2j.gameserver.serverpackets.ChangeWaitType;import net.sf.l2j.gameserver.serverpackets.ExOlympiadSpelledInfo;import net.sf.l2j.gameserver.serverpackets.MagicEffectIcons;import net.sf.l2j.gameserver.serverpackets.MagicSkillCanceld;import net.sf.l2j.gameserver.serverpackets.MagicSkillLaunched;import net.sf.l2j.gameserver.serverpackets.MagicSkillUser;import net.sf.l2j.gameserver.serverpackets.MyTargetSelected;import net.sf.l2j.gameserver.serverpackets.NpcInfo;import net.sf.l2j.gameserver.serverpackets.PartySpelled;import net.sf.l2j.gameserver.serverpackets.PetInfo;import net.sf.l2j.gameserver.serverpackets.Revive;import net.sf.l2j.gameserver.serverpackets.ServerBasePacket;import net.sf.l2j.gameserver.serverpackets.SetupGauge;import net.sf.l2j.gameserver.serverpackets.SocialAction;import net.sf.l2j.gameserver.serverpackets.StatusUpdate;import net.sf.l2j.gameserver.serverpackets.StopMove;import net.sf.l2j.gameserver.serverpackets.SystemMessage;import net.sf.l2j.gameserver.serverpackets.TargetUnselected;import net.sf.l2j.gameserver.serverpackets.TeleportToLocation;import net.sf.l2j.gameserver.skills.Calculator;import net.sf.l2j.gameserver.skills.Formulas;import net.sf.l2j.gameserver.skills.Stats;import net.sf.l2j.gameserver.skills.funcs.Func;import net.sf.l2j.gameserver.templates.L2CharTemplate;import net.sf.l2j.gameserver.templates.L2NpcTemplate;import net.sf.l2j.gameserver.templates.L2Weapon;import net.sf.l2j.gameserver.templates.L2WeaponType;import net.sf.l2j.gameserver.util.Util;/** * Mother class of all character objects of the world (PC, NPC...)<BR><BR> * * L2Character :<BR><BR> * <li>L2CastleGuardInstance</li> * <li>L2DoorInstance</li> * <li>L2NpcInstance</li> * <li>L2PlayableInstance </li><BR><BR> * * * <B><U> Concept of L2CharTemplate</U> :</B><BR><BR> * Each L2Character owns generic and static properties (ex : all Keltir have the same number of HP...). * All of those properties are stored in a different template for each type of L2Character. * Each template is loaded once in the server cache memory (reduce memory use). * When a new instance of L2Character is spawned, server just create a link between the instance and the template. * This link is stored in <B>_template</B><BR><BR> * * * @version $Revision: 1.53.2.45.2.34 $ $Date: 2005/04/11 10:06:08 $ */public abstract class L2Character extends L2Object{ protected static final Logger _log = Logger.getLogger(L2Character.class.getName()); // ========================================================= // Data Field private List<L2Character> _AttackByList; private L2Character _AttackingChar; private L2Skill _AttackingCharSkill; private boolean _IsAffraid = false; // Flee in a random direction private boolean _IsConfused = false; // Attack anyone randomly private boolean _IsFakeDeath = false; // Fake death private boolean _IsFlying = false; //Is flying Wyvern? private boolean _IsMuted = false; // Cannot use magic private boolean _IsPsychicalMuted = false; // Cannot use psychical skills private boolean _IsKilledAlready = false; private boolean _IsImobilised = false; private boolean _IsOverloaded = false; // the char is carrying too much private boolean _IsParalyzed = false; private boolean _IsRiding = false; //Is Riding strider? private boolean _IsPendingRevive = false; private boolean _IsRooted = false; // Cannot move until root timed out private boolean _IsRunning = false; private boolean _IsSleeping = false; // Cannot move/attack until sleep timed out or monster is attacked private boolean _IsStunned = false; // Cannot move/attack until stun timed out private boolean _IsBetrayed = false; // Betrayed by own summon protected boolean _IsTeleporting = false; private boolean _IsNoblesseBlessed = false; private L2Character _LastBuffer = null; protected boolean _isInvul = false; private int _LastHealAmount = 0; private CharStat _Stat; private CharStatus _Status; private L2CharTemplate _Template; // The link on the L2CharTemplate object containing generic and static properties of this L2Character type (ex : Max HP, Speed...) private String _Title; private String _aiClass = "default"; private double _hpUpdateIncCheck; private double _hpUpdateDecCheck; private double _hpUpdateInterval; /** Table of Calculators containing all used calculator */ private Calculator[] _Calculators; /** FastMap(Integer, L2Skill) containing all skills of the L2Character */ protected final Map<Integer, L2Skill> _Skills; // ========================================================= // Constructor /** * Constructor of L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * Each L2Character owns generic and static properties (ex : all Keltir have the same number of HP...). * All of those properties are stored in a different template for each type of L2Character. * Each template is loaded once in the server cache memory (reduce memory use). * When a new instance of L2Character is spawned, server just create a link between the instance and the template * This link is stored in <B>_template</B><BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Set the _template of the L2Character </li> * <li>Set _overloaded to false (the charcater can take more items)</li><BR><BR> * * <li>If L2Character is a L2NPCInstance, copy skills from template to object</li> * <li>If L2Character is a L2NPCInstance, link _calculators to NPC_STD_CALCULATOR</li><BR><BR> * * <li>If L2Character is NOT a L2NPCInstance, create an empty _skills slot</li> * <li>If L2Character is a L2PcInstance or L2Summon, copy basic Calculator set to object</li><BR><BR> * * @param objectId Identifier of the object to initialized * @param template The L2CharTemplate to apply to the object */ public L2Character(int objectId, L2CharTemplate template) { super(objectId); this.getKnownList(); // Set its template to the new L2Character _Template = template; if (template != null && this instanceof L2NpcInstance) { // Copy the Standard Calcultors of the L2NPCInstance in _calculators _Calculators = NPC_STD_CALCULATOR; // Copy the skills of the L2NPCInstance from its template to the L2Character Instance // The skills list can be affected by spell effects so it's necessary to make a copy // to avoid that a spell affecting a L2NPCInstance, affects others L2NPCInstance of the same type too. _Skills = ((L2NpcTemplate)template).getSkills(); if (_Skills != null) { for(Map.Entry<Integer, L2Skill> skill : _Skills.entrySet()) addStatFuncs(skill.getValue().getStatFuncs(null, this)); } } else { // Initialize the FastMap _skills to null _Skills = new FastMap<Integer,L2Skill>(); // If L2Character is a L2PcInstance or a L2Summon, create the basic calculator set _Calculators = new Calculator[Stats.NUM_STATS]; Formulas.getInstance().addFuncsToNewCharacter(this); } _hpUpdateInterval = getMaxHp()/352.0; // MAX_HP div MAX_HP_BAR_PX _hpUpdateIncCheck = getMaxHp(); _hpUpdateDecCheck = getMaxHp()-_hpUpdateInterval; } // ========================================================= // Event - Public /** * Remove the L2Character from the world when the decay task is launched.<BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T REMOVE the object from _allObjects of L2World </B></FONT><BR> * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packets to players</B></FONT><BR><BR> * */ public void onDecay() { decayMe(); } public void onTeleported() { setIsTeleporting(false); spawnMe(getPosition().getX(), getPosition().getY(), getPosition().getZ()); if (_IsPendingRevive) doRevive(); // Modify the position of the pet if necessary if(getPet() != null) getPet().teleToLocation(getPosition().getX() + Rnd.get(-100,100), getPosition().getY() + Rnd.get(-100,100), getPosition().getZ(), false); } // ========================================================= // Method - Public /** * Add L2Character instance that is attacking to the attacker list.<BR><BR> * @param player The L2Character that attcks this one */ public void addAttackerToAttackByList (L2Character player) { if (player == null || player == this || getAttackByList() == null || getAttackByList().contains(player)) return; getAttackByList().add(player); } /** * Send a packet to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * L2PcInstance in the detection area of the L2Character are identified in <B>_knownPlayers</B>. * In order to inform other players of state modification on the L2Character, server just need to go through _knownPlayers to send Server->Client Packet<BR><BR> * */ public final void broadcastPacket(ServerBasePacket mov) { sendPacket(mov); if (getKnownList().getKnownPlayers() == null) return; Collection<L2PcInstance> knownPlayers = getKnownList().getKnownPlayers().values(); if (knownPlayers == null) return; if (Config.DEBUG) _log.fine("players to notify:" + knownPlayers.size() + " packet:"+mov.getType()); try { for (L2PcInstance player : knownPlayers) { if (player == null || player == this) continue; player.sendPacket(mov); //if(Config.DEVELOPER && !isInsideRadius(player, 3500, false, false)) _log.warning("broadcastPacket: Too far player see event!"); } } catch (Exception e) { } // this npe catch might not be necessary any more } /** * Returns true if status update should be done, false if not * @return boolean */ private boolean needStatusUpdate() { if (!(this instanceof L2MonsterInstance)) return true; double currentHp = getCurrentHp(); if (currentHp <= 0.00 || getMaxHp() < 352) return true; boolean needUpdate = false;/* if (currentHp > getMaxHp()) currentHp = getMaxHp();*/ if (currentHp < _hpUpdateDecCheck) { needUpdate = true; _hpUpdateDecCheck -= _hpUpdateInterval; _hpUpdateIncCheck -= _hpUpdateInterval; } else if (currentHp > _hpUpdateIncCheck) { needUpdate = true; _hpUpdateDecCheck += _hpUpdateInterval; _hpUpdateIncCheck += _hpUpdateInterval; } return needUpdate; } /** * Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Create the Server->Client packet StatusUpdate with current HP and MP </li> * <li>Send the Server->Client packet StatusUpdate with current HP and MP to all * L2Character called _statusListener that must be informed of HP/MP updates of this L2Character </li><BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND CP information</B></FONT><BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance : Send current HP,MP and CP to the L2PcInstance and only current HP, MP and Level to all other L2PcInstance of the Party</li><BR><BR> * */ public void broadcastStatusUpdate() { if (getStatus().getStatusListener() == null || getStatus().getStatusListener().isEmpty()) return; if (!needStatusUpdate()) return; // Create the Server->Client packet StatusUpdate with current HP and MP StatusUpdate su = new StatusUpdate(getObjectId()); su.addAttribute(StatusUpdate.CUR_HP, (int)getCurrentHp()); su.addAttribute(StatusUpdate.CUR_MP, (int)getCurrentMp()); // Go through the StatusListener // Send the Server->Client packet StatusUpdate with current HP and MP Set<L2Character> listeners = getStatus().getStatusListener(); if (listeners != null) for (L2Character temp : listeners) temp.sendPacket(su); } /** * Not Implemented.<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> */ public void sendPacket(@SuppressWarnings("unused") ServerBasePacket mov) { // default implementation } /** * Teleport a L2Character and its pet if necessary.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Stop the movement of the L2Character</li> * <li>Set the x,y,z position of the L2Object and if necessary modify its _worldRegion</li> * <li>Send a Server->Client packet TeleportToLocationt to the L2Character AND to all L2PcInstance in its _KnownPlayers</li> * <li>Modify the position of the pet if necessary</li><BR><BR> * */ public void teleToLocation(int x, int y, int z, boolean allowRandomOffset) { // Stop movement stopMove(null, false); this.abortAttack(); this.abortCast(); setIsTeleporting(true); setTarget(null); if (Config.RESPAWN_RANDOM_ENABLED && allowRandomOffset) { x += Rnd.get(-Config.RESPAWN_RANDOM_MAX_OFFSET, Config.RESPAWN_RANDOM_MAX_OFFSET); y += Rnd.get(-Config.RESPAWN_RANDOM_MAX_OFFSET, Config.RESPAWN_RANDOM_MAX_OFFSET); } z += 5; if (Config.DEBUG) _log.fine("Teleporting to: " + x + ", " + y + ", " + z); // Send a Server->Client packet TeleportToLocationt to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character broadcastPacket(new TeleportToLocation(this, x, y, z)); // Set the x,y,z position of the L2Object and if necessary modify its _worldRegion getPosition().setXYZ(x, y, z); decayMe(); if (!(this instanceof L2PcInstance)) onTeleported(); } public void teleToLocation(int x, int y, int z) { teleToLocation(x, y, z, false); } public void teleToLocation(Location loc, boolean allowRandomOffset) { teleToLocation(loc.getX(), loc.getY(), loc.getZ(), allowRandomOffset); } public void teleToLocation(TeleportWhereType teleportWhere) { teleToLocation(MapRegionTable.getInstance().getTeleToLocation(this, teleportWhere), true); } // ========================================================= // Method - Private /** * Launch a physical attack against a target (Simple, Bow, Pole or Dual).<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Get the active weapon (always equiped in the right hand) </li><BR><BR> * <li>If weapon is a bow, check for arrows, MP and bow re-use delay (if necessary, equip the L2PcInstance with arrows in left hand)</li> * <li>If weapon is a bow, consumme MP and set the new period of bow non re-use </li><BR><BR> * <li>Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) </li> * <li>Select the type of attack to start (Simple, Bow, Pole or Dual) and verify if SoulShot are charged then start calculation</li> * <li>If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character</li> * <li>Notify AI with EVT_READY_TO_ACT</li><BR><BR> * * @param target The L2Character targeted * */ protected void doAttack(L2Character target) { if (Config.DEBUG) _log.fine(this.getName()+" doAttack: target="+target); if (isAlikeDead() || target == null || (this instanceof L2NpcInstance && target.isAlikeDead()) || (this instanceof L2PcInstance && target.isDead() && !target.isFakeDeath()) || !getKnownList().knowsObject(target) || (this instanceof L2PcInstance && isDead())) { // If L2PcInstance is dead or the target is dead, the action is stoped getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); sendPacket(new ActionFailed()); return; } if (isAttackingDisabled()) return; if (this instanceof L2PcInstance) { if (((L2PcInstance)this).inObserverMode()) { ((L2PcInstance)this).sendMessage("Cant attack in observer mode"); sendPacket(new ActionFailed()); return; } if (target instanceof L2PcInstance) { if (((L2PcInstance)target).isCursedWeaponEquiped() && ((L2PcInstance)this).getLevel()<=20){ ((L2PcInstance)this).sendMessage("Cant attack a cursed Player under twenty"); sendPacket(new ActionFailed()); return; } if (((L2PcInstance)this).isCursedWeaponEquiped() && ((L2PcInstance)target).getLevel()<=20){ ((L2PcInstance)this).sendMessage("Cant attack a newbie player with Zarike"); sendPacket(new ActionFailed()); return; } } } // Get the active weapon instance (always equiped in the right hand) L2ItemInstance weaponInst = getActiveWeaponInstance(); // Get the active weapon item corresponding to the active weapon instance (always equiped in the right hand) L2Weapon weaponItem = getActiveWeaponItem(); if (weaponItem != null && weaponItem.getItemType() == L2WeaponType.ROD) { // You can't make an attack with a fishing pole. ((L2PcInstance)this).sendPacket(new SystemMessage(SystemMessage.CANNOT_ATTACK_WITH_FISHING_POLE)); getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE); ActionFailed af = new ActionFailed(); sendPacket(af); return; } // GeoData Los Check here if (!GeoData.getInstance().canSeeTarget(this, target)) { sendPacket(new SystemMessage(SystemMessage.CANT_SEE_TARGET)); sendPacket(new ActionFailed()); return; } // Check for a bow if ((weaponItem != null && weaponItem.getItemType() == L2WeaponType.BOW)) { //Check for arrows and MP if (this instanceof L2PcInstance) { // Verify if the bow can be use if (_disableBowAttackEndTime <= GameTimeController.getGameTicks()) { // Verify if L2PcInstance owns enough MP int saMpConsume = (int)getStat().calcStat(Stats.MP_CONSUME, 0, null, null); int mpConsume = saMpConsume == 0 ? weaponItem.getMpConsume() : saMpConsume; if (getCurrentMp() < mpConsume) { // If L2PcInstance doesn't have enough MP, stop the attack ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), 1000); sendPacket(new SystemMessage(SystemMessage.NOT_ENOUGH_MP)); sendPacket(new ActionFailed()); return; } // If L2PcInstance have enough MP, the bow consummes it getStatus().reduceMp(mpConsume); // Set the period of bow non re-use _disableBowAttackEndTime = 5 * GameTimeController.TICKS_PER_SECOND + GameTimeController.getGameTicks(); } else { // Cancel the action because the bow can't be re-use at this moment ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), 1000); sendPacket(new ActionFailed()); return; } // Equip arrows needed in left hand and send a Server->Client packet ItemList to the L2PcINstance then return True if (!checkAndEquipArrows()) { // Cancel the action because the L2PcInstance have no arrow getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE); sendPacket(new ActionFailed()); sendPacket(new SystemMessage(SystemMessage.NOT_ENOUGH_ARROWS)); return; } } else if (this instanceof L2NpcInstance) { if (_disableBowAttackEndTime > GameTimeController.getGameTicks()) return; } } // Add the L2PcInstance to _knownObjects and _knownPlayer of the target target.getKnownList().addKnownObject(this); // Reduce the current CP if TIREDNESS configuration is activated if (Config.ALT_GAME_TIREDNESS) setCurrentCp(getCurrentCp() - 10); // Recharge any active auto soulshot tasks for player (or player's summon if one exists). if (this instanceof L2PcInstance) ((L2PcInstance)this).rechargeAutoSoulShot(true, false, false); else if (this instanceof L2Summon) ((L2Summon)this).getOwner().rechargeAutoSoulShot(true, false, true); // Verify if soulshots are charged. boolean wasSSCharged; if (this instanceof L2Summon && !(this instanceof L2PetInstance)) wasSSCharged = (((L2Summon)this).getChargedSoulShot() != L2ItemInstance.CHARGED_NONE); else wasSSCharged = (weaponInst != null && weaponInst.getChargedSoulshot() != L2ItemInstance.CHARGED_NONE); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) int sAtk = calculateAttackSpeed(target, weaponInst); _attackEndTime = GameTimeController.getGameTicks(); _attackEndTime += (sAtk / GameTimeController.MILLIS_IN_TICK); _attackEndTime -= 1; int ssGrade = 0; if (weaponItem != null) ssGrade = weaponItem.getCrystalType(); // Create a Server->Client packet Attack Attack attack = new Attack(this, wasSSCharged, ssGrade); boolean hitted; // Set the Attacking Body part to CHEST setAttackingBodypart(); // Select the type of attack to start if (weaponItem == null) hitted = doAttackHitSimple(attack, target); else if (weaponItem.getItemType() == L2WeaponType.BOW) hitted = doAttackHitByBow(attack, target, sAtk); else if (weaponItem.getItemType() == L2WeaponType.POLE) hitted = doAttackHitByPole(attack); else if (isUsingDualWeapon()) hitted = doAttackHitByDual(attack, target); else hitted = doAttackHitSimple(attack, target); // Flag the attacker if it's a L2PcInstance outside a PvP area L2PcInstance player = null; if (this instanceof L2PcInstance) player = (L2PcInstance)this; else if (this instanceof L2Summon) player = ((L2Summon)this).getOwner(); if (player != null) player.updatePvPStatus(target); // Check if hit isn't missed if (!hitted) // Abort the attack of the L2Character and send Server->Client ActionFailed packet abortAttack(); else { /* ADDED BY nexus - 2006-08-17 * * As soon as we know that our hit landed, we must discharge any active soulshots. * This must be done so to avoid unwanted soulshot consumption. */ // If we didn't miss the hit, discharge the shoulshots, if any if (this instanceof L2Summon && !(this instanceof L2PetInstance)) ((L2Summon)this).setChargedSoulShot(L2ItemInstance.CHARGED_NONE); else if (weaponInst != null) weaponInst.setChargedSoulshot(L2ItemInstance.CHARGED_NONE); if (player != null) { if (player.isCursedWeaponEquiped()) { // If hitted by a cursed weapon, Cp is reduced to 0 if (!target.isInvul()) target.setCurrentCp(0); } else if (player.isHero()) { if (target instanceof L2PcInstance && ((L2PcInstance)target).isCursedWeaponEquiped()) // If a cursed weapon is hitted by a Hero, Cp is reduced to 0 target.setCurrentCp(0); } } } // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character if (attack.hasHits()) broadcastPacket(attack); // Get the Attack Reuse Delay of the L2Weapon int reuse = (weaponItem == null) ? 0 : weaponItem.getAttackReuseDelay(); // Notify AI with EVT_READY_TO_ACT ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), sAtk+reuse); } /** * Launch a Bow attack.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Calculate if hit is missed or not </li> * <li>Consumme arrows </li> * <li>If hit isn't missed, calculate if shield defense is efficient </li> * <li>If hit isn't missed, calculate if hit is critical </li> * <li>If hit isn't missed, calculate physical damages </li> * <li>If the L2Character is a L2PcInstance, Send a Server->Client packet SetupGauge </li> * <li>Create a new hit task with Medium priority</li> * <li>Calculate and set the disable delay of the bow in function of the Attack Speed</li> * <li>Add this hit to the Server-Client packet Attack </li><BR><BR> * * @param attack Server->Client packet Attack in which the hit will be added * @param target The L2Character targeted * @param sAtk The Attack Speed of the attacker * * @return True if the hit isn't missed * */ private boolean doAttackHitByBow(Attack attack, L2Character target, int sAtk) { int damage1 = 0; boolean shld1 = false; boolean crit1 = false; // Calculate if hit is missed or not boolean miss1 = Formulas.getInstance().calcHitMiss(this, target); // Consumme arrows reduceArrowCount(); _move = null; // Check if hit isn't missed if (!miss1) { // Calculate if shield defense is efficient shld1 = Formulas.getInstance().calcShldUse(this, target); // Calculate if hit is critical crit1 = Formulas.getInstance().calcCrit(getStat().getCriticalHit(target, null)); // Calculate physical damages damage1 = (int)Formulas.getInstance().calcPhysDam(this, target, null, shld1, crit1, false, attack._soulshot); } // Calculate the bow re-use delay int reuse; L2Weapon bow = getActiveWeaponItem(); if (bow == null) reuse = 1500; else reuse = bow.getAttackReuseDelay(); reuse *= getStat().getReuseModifier(target); // Check if the L2Character is a L2PcInstance if (this instanceof L2PcInstance) { // Send a system message sendPacket(new SystemMessage(SystemMessage.GETTING_READY_TO_SHOOT_AN_ARROW)); // Send a Server->Client packet SetupGauge SetupGauge sg = new SetupGauge(SetupGauge.RED, sAtk+reuse); sendPacket(sg); } // Create a new hit task with Medium priority ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, attack._soulshot, shld1), sAtk); // Calculate and set the disable delay of the bow in function of the Attack Speed _disableBowAttackEndTime = (sAtk+reuse)/GameTimeController.MILLIS_IN_TICK + GameTimeController.getGameTicks(); // Add this hit to the Server-Client packet Attack attack.addHit(target, damage1, miss1, crit1, shld1); // Return true if hit isn't missed return !miss1; } /** * Launch a Dual attack.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Calculate if hits are missed or not </li> * <li>If hits aren't missed, calculate if shield defense is efficient </li> * <li>If hits aren't missed, calculate if hit is critical </li> * <li>If hits aren't missed, calculate physical damages </li> * <li>Create 2 new hit tasks with Medium priority</li> * <li>Add those hits to the Server-Client packet Attack </li><BR><BR> * * @param attack Server->Client packet Attack in which the hit will be added * @param target The L2Character targeted * * @return True if hit 1 or hit 2 isn't missed * */ private boolean doAttackHitByDual(Attack attack, L2Character target) { int damage1 = 0; int damage2 = 0; boolean shld1 = false; boolean shld2 = false; boolean crit1 = false; boolean crit2 = false; // Calculate if hits are missed or not boolean miss1 = Formulas.getInstance().calcHitMiss(this, target); boolean miss2 = Formulas.getInstance().calcHitMiss(this, target); // Check if hit 1 isn't missed if (!miss1) { // Calculate if shield defense is efficient against hit 1 shld1 = Formulas.getInstance().calcShldUse(this, target); // Calculate if hit 1 is critical crit1 = Formulas.getInstance().calcCrit(getStat().getCriticalHit(target, null)); // Calculate physical damages of hit 1 damage1 = (int)Formulas.getInstance().calcPhysDam(this, target, null, shld1, crit1, true, attack._soulshot); damage1 /= 2; } // Check if hit 2 isn't missed if (!miss2) { // Calculate if shield defense is efficient against hit 2 shld2 = Formulas.getInstance().calcShldUse(this, target); // Calculate if hit 2 is critical crit2 = Formulas.getInstance().calcCrit(getStat().getCriticalHit(target, null)); // Calculate physical damages of hit 2 damage2 = (int)Formulas.getInstance().calcPhysDam(this, target, null, shld2, crit2, true, attack._soulshot); damage2 /= 2; } // Create a new hit task with Medium priority for hit 1 ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, attack._soulshot, shld1), 650); // Create a new hit task with Medium priority for hit 2 with a higher delay ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage2, crit2, miss2, attack._soulshot, shld2), 1250); // Add those hits to the Server-Client packet Attack attack.addHit(target, damage1, miss1, crit1, shld1); attack.addHit(target, damage2, miss2, crit2, shld2); // Return true if hit 1 or hit 2 isn't missed return (!miss1 || !miss2); } /** * Launch a Pole attack.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Get all visible objects in a spheric area near the L2Character to obtain possible targets </li> * <li>If possible target is the L2Character targeted, launch a simple attack against it </li> * <li>If possible target isn't the L2Character targeted but is attakable, launch a simple attack against it </li><BR><BR> * * @param attack Server->Client packet Attack in which the hit will be added * * @return True if one hit isn't missed * */ private boolean doAttackHitByPole(Attack attack) { boolean hitted = false; double angleChar, angleTarget; int maxRadius = (int)getStat().calcStat(Stats.POWER_ATTACK_RANGE, 66, null, null); int maxAngleDiff = (int)getStat().calcStat(Stats.POWER_ATTACK_ANGLE, 120, null, null); if(getTarget() == null) return false; if (Config.DEBUG) { _log.info("doAttackHitByPole: Max radius = " + maxRadius); _log.info("doAttackHitByPole: Max angle = " + maxAngleDiff); } // o1 x: 83420 y: 148158 (Giran) // o2 x: 83379 y: 148081 (Giran) // dx = -41 // dy = -77 // distance between o1 and o2 = 87.24 // arctan2 = -120 (240) degree (excel arctan2(dx, dy); java arctan2(dy, dx)) // // o2 // // o1 ----- (heading) // In the diagram above: // o1 has a heading of 0/360 degree from horizontal (facing East) // Degree of o2 in respect to o1 = -120 (240) degree // // o2 / (heading) // / // o1 // In the diagram above // o1 has a heading of -80 (280) degree from horizontal (facing north east) // Degree of o2 in respect to 01 = -40 (320) degree // =========================================================== // Make sure that char is facing selected target angleTarget = Util.calculateAngleFrom(this, getTarget()); this.setHeading((int)((angleTarget / 9.0) * 1610.0)); // = this.setHeading((int)((angleTarget / 360.0) * 64400.0)); // Update char's heading degree angleChar = Util.convertHeadingToDegree(this.getHeading()); double attackpercent = 85; int attackcountmax = (int)getStat().calcStat(Stats.ATTACK_COUNT_MAX, 3, null, null); int attackcount = 0; if (angleChar <= 0) angleChar += 360; // =========================================================== L2Character target; for (L2Object obj : getKnownList().getKnownObjects().values()) { //Check if the L2Object is a L2Character if(obj instanceof L2Character) { if (obj instanceof L2PetInstance && this instanceof L2PcInstance && ((L2PetInstance)obj).getOwner() == ((L2PcInstance)this)) continue; if (!Util.checkIfInRange(maxRadius, this, obj, false)) continue; //otherwise hit too high/low. 650 because mob z coord sometimes wrong on hills if(Math.abs(obj.getZ() - this.getZ()) > 650) continue; angleTarget = Util.calculateAngleFrom(this, obj); if ( Math.abs(angleChar - angleTarget) > maxAngleDiff && Math.abs((angleChar + 360) - angleTarget) > maxAngleDiff && // Example: char is at 1 degree and target is at 359 degree Math.abs(angleChar - (angleTarget + 360)) > maxAngleDiff // Example: target is at 1 degree and char is at 359 degree ) continue; target = (L2Character) obj; // Launch a simple attack against the L2Character targeted if(!target.isAlikeDead()) { attackcount += 1; if (attackcount <= attackcountmax) { if (target == getAI().getAttackTarget() || target.isAutoAttackable(this)) { hitted |= doAttackHitSimple(attack, target, attackpercent); attackpercent /= 1.15; } } } } } // Return true if one hit isn't missed return hitted; } /** * Launch a simple attack.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Calculate if hit is missed or not </li> * <li>If hit isn't missed, calculate if shield defense is efficient </li> * <li>If hit isn't missed, calculate if hit is critical </li> * <li>If hit isn't missed, calculate physical damages </li> * <li>Create a new hit task with Medium priority</li> * <li>Add this hit to the Server-Client packet Attack </li><BR><BR> * * @param attack Server->Client packet Attack in which the hit will be added * @param target The L2Character targeted * * @return True if the hit isn't missed * */ private boolean doAttackHitSimple(Attack attack, L2Character target) { return doAttackHitSimple(attack, target, 100); } private boolean doAttackHitSimple(Attack attack, L2Character target, double attackpercent) { int damage1 = 0; boolean shld1 = false; boolean crit1 = false; // Calculate if hit is missed or not boolean miss1 = Formulas.getInstance().calcHitMiss(this, target); // Check if hit isn't missed if (!miss1) { // Calculate if shield defense is efficient shld1 = Formulas.getInstance().calcShldUse(this, target); // Calculate if hit is critical crit1 = Formulas.getInstance().calcCrit(getStat().getCriticalHit(target, null)); // Calculate physical damages damage1 = (int)Formulas.getInstance().calcPhysDam(this, target, null, shld1, crit1, false, attack._soulshot); if (attackpercent != 100) damage1 = (int)(damage1*attackpercent/100); } // Create a new hit task with Medium priority ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, attack._soulshot, shld1), 980); // Add this hit to the Server-Client packet Attack attack.addHit(target, damage1, miss1, crit1, shld1); // Return true if hit isn't missed return !miss1; } /** * Manage the casting task (casting and interrupt time, re-use delay...) and display the casting bar and animation on client.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Verify the possibilty of the the cast : skill is a spell, caster isn't muted... </li> * <li>Get the list of all targets (ex : area effects) and define the L2Charcater targeted (its stats will be used in calculation)</li> * <li>Calculate the casting time (base + modifier of MAtkSpd), interrupt time and re-use delay</li> * <li>Send a Server->Client packet MagicSkillUser (to diplay casting animation), a packet SetupGauge (to display casting bar) and a system message </li> * <li>Disable all skills during the casting time (create a task EnableAllSkills)</li> * <li>Disable the skill during the re-use delay (create a task EnableSkill)</li> * <li>Create a task MagicUseTask (that will call method onMagicUseTimer) to launch the Magic Skill at the end of the casting time</li><BR><BR> * * @param skill The L2Skill to use * */ public void doCast(L2Skill skill) { if (skill == null) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } if (isSkillDisabled(skill.getId())) { if (this instanceof L2PcInstance) { SystemMessage sm = new SystemMessage(SystemMessage.S1_PREPARED_FOR_REUSE); sm.addSkillName(skill.getId()); sendPacket(sm); } return; } // Check if the skill is a magic spell and if the L2Character is not muted if (skill.isMagic() && isMuted() && !skill.isPotion()) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } // Check if the skill is psychical and if the L2Character is not psychical_muted if (!skill.isMagic() && isPsychicalMuted() && !skill.isPotion()) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } //Recharge AutoSoulShot if (skill.useSoulShot()) { if (this instanceof L2PcInstance) ((L2PcInstance)this).rechargeAutoSoulShot(true, false, false); else if (this instanceof L2Summon) ((L2Summon)this).getOwner().rechargeAutoSoulShot(true, false, true); } else if (skill.useSpiritShot()) { if (this instanceof L2PcInstance) ((L2PcInstance)this).rechargeAutoSoulShot(false, true, false); else if (this instanceof L2Summon) ((L2Summon)this).getOwner().rechargeAutoSoulShot(false, true, true); } //else if (skill.useFishShot()) //{ // if (this instanceof L2PcInstance) // ((L2PcInstance)this).rechargeAutoSoulShot(true, false, false); //} // Get all possible targets of the skill in a table in function of the skill target type L2Object[] targets = skill.getTargetList(this); if (targets == null || targets.length == 0) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } // Set the target of the skill in function of Skill Type and Target Type L2Character target = null; if( skill.getSkillType() == SkillType.BUFF || skill.getSkillType() == SkillType.HEAL || skill.getSkillType() == SkillType.COMBATPOINTHEAL || skill.getSkillType() == SkillType.MANAHEAL || skill.getSkillType() == SkillType.REFLECT || skill.getSkillType() == SkillType.SEED || skill.getTargetType() == L2Skill.SkillTargetType.TARGET_SELF || skill.getTargetType() == L2Skill.SkillTargetType.TARGET_PET || skill.getTargetType() == L2Skill.SkillTargetType.TARGET_PARTY || skill.getTargetType() == L2Skill.SkillTargetType.TARGET_CLAN || skill.getTargetType() == L2Skill.SkillTargetType.TARGET_ALLY) { target = (L2Character) targets[0]; if (this instanceof L2PcInstance && target instanceof L2PcInstance && target.getAI().getIntention() == CtrlIntention.AI_INTENTION_ATTACK) { if(skill.getSkillType() == SkillType.BUFF || skill.getSkillType() == SkillType.HOT || skill.getSkillType() == SkillType.HEAL || skill.getSkillType() == SkillType.HEAL_PERCENT || skill.getSkillType() == SkillType.MANAHEAL || skill.getSkillType() == SkillType.MANAHEAL_PERCENT || skill.getSkillType() == SkillType.BALANCE_LIFE) target.setLastBuffer(this); if (((L2PcInstance)this).isInParty() && skill.getTargetType() == L2Skill.SkillTargetType.TARGET_PARTY) { for (L2PcInstance member : ((L2PcInstance)this).getParty().getPartyMembers()) member.setLastBuffer(this); } } } else target = (L2Character) getTarget(); // AURA skills should always be using caster as target if (skill.getTargetType() == SkillTargetType.TARGET_AURA) target = this; if (target == null) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } setAttackingChar(this); setAttackingCharSkill(skill); // Get the Identifier of the skill int magicId = skill.getId(); // Get the Display Identifier for a skill that client can't display int displayId = skill.getDisplayId(); // Get the level of the skill int level = getSkillLevel(magicId); if (level < 1) level = 1; // Get the casting time of the skill (base) int skillTime = skill.getSkillTime(); // Get the delay under wich the cast can be aborted (base) int skillInterruptTime = skill.getSkillInterruptTime(); // Calculate the casting time of the skill (base + modifier of MAtkSpd) skillTime = Formulas.getInstance().calcMAtkSpd(this, skill, skillTime); // Calculate the Interrupt Time of the skill (base + modifier) if the skill is a spell else 0 if (skill.isMagic()) skillInterruptTime = Formulas.getInstance().calcMAtkSpd(this, skill, skillInterruptTime); else skillInterruptTime = 0; // Calculate altered Cast Speed due to BSpS/SpS L2ItemInstance weaponInst = this.getActiveWeaponInstance(); if ((weaponInst != null)&&(skill.isMagic())&&((skill.getTargetType())!=(SkillTargetType.TARGET_SELF))) { if ((weaponInst.getChargedSpiritshot() == L2ItemInstance.CHARGED_BLESSED_SPIRITSHOT) || (weaponInst.getChargedSpiritshot() == L2ItemInstance.CHARGED_SPIRITSHOT)) { //Only takes 70% of the time to cast a BSpS/SpS cast skillTime = (int)(0.70 * skillTime); skillInterruptTime = (int)(0.70 * skillInterruptTime); //Because the following are magic skills that do not actively 'eat' BSpS/SpS, //I must 'eat' them here so players don't take advantage of infinite speed increase if ((skill.getSkillType() == SkillType.BUFF)|| (skill.getSkillType() == SkillType.MANAHEAL)|| (skill.getSkillType() == SkillType.RESURRECT)|| (skill.getSkillType() == SkillType.RECALL)|| (skill.getSkillType() == SkillType.POISON)|| (skill.getSkillType() == SkillType.CANCEL)|| (skill.getSkillType() == SkillType.DEBUFF)|| (skill.getSkillType() == SkillType.PARALYZE)|| (skill.getSkillType() == SkillType.ROOT)|| (skill.getSkillType() == SkillType.SLEEP)|| (skill.getSkillType() == SkillType.DOT)) { weaponInst.setChargedSpiritshot(L2ItemInstance.CHARGED_NONE); } } } // Set the _castEndTime and _castInterruptTim _castEndTime = 10 + GameTimeController.getGameTicks() + skillTime / GameTimeController.MILLIS_IN_TICK; _castInterruptTime = GameTimeController.getGameTicks() + skillInterruptTime / GameTimeController.MILLIS_IN_TICK; // Init the reuse time of the skill int reuseDelay = (int)(skill.getReuseDelay() * getStat().getMReuseRate(skill)); if (skill.isMagic()) reuseDelay *= 333.0 / getMAtkSpd(); else reuseDelay *= 333.0 / getPAtkSpd(); // Send a Server->Client packet MagicSkillUser with target, displayId, level, skillTime, reuseDelay // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character broadcastPacket(new MagicSkillUser(this, target, displayId, level, skillTime, reuseDelay)); // Send a system message USE_S1 to the L2Character if (this instanceof L2PcInstance && magicId != 1312) { SystemMessage sm = new SystemMessage(SystemMessage.USE_S1); sm.addSkillName(magicId); sendPacket(sm); } // Skill reuse check if (reuseDelay > 30000) addTimeStamp(skill.getId(),reuseDelay); // Check if this skill consume mp on start casting int initmpcons = getStat().getMpInitialConsume(skill); if (initmpcons > 0) { StatusUpdate su = new StatusUpdate(getObjectId()); getStatus().reduceMp(calcStat(Stats.MP_CONSUME_RATE,initmpcons,null,null)); su.addAttribute(StatusUpdate.CUR_MP, (int) getCurrentMp()); sendPacket(su); } // Disable the skill during the re-use delay and create a task EnableSkill with Medium priority to enable it at the end of the re-use delay if (reuseDelay > 10) { disableSkill(skill.getId(), reuseDelay); } // launch the magic in skillTime milliseconds if (skillTime > 50) { // Send a Server->Client packet SetupGauge with the color of the gauge and the casting time if (this instanceof L2PcInstance) { SetupGauge sg = new SetupGauge(SetupGauge.BLUE, skillTime); sendPacket(sg); } // Disable all skills during the casting disableAllSkills(); if (_skillCast != null) { _skillCast.cancel(true); _skillCast = null; } // Create a task MagicUseTask with Medium priority to launch the MagicSkill at the end of the casting time _skillCast = ThreadPoolManager.getInstance().scheduleEffect(new MagicUseTask(targets, skill), skillTime); } else { onMagicUseTimer(targets, skill); } } /** * Index according to skill id the current timestamp of use.<br><br> * * @param skill id * @param reuse delay * <BR><B>Overriden in :</B> (L2PcInstance) */ public void addTimeStamp(int s, int r) {/***/} /** * Index according to skill id the current timestamp of use.<br><br> * * @param skill id * <BR><B>Overriden in :</B> (L2PcInstance) */ public void removeTimeStamp(int s) {/***/} /** * Kill the L2Character.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Set target to null and cancel Attack or Cast </li> * <li>Stop movement </li> * <li>Stop HP/MP/CP Regeneration task </li> * <li>Stop all active skills effects in progress on the L2Character </li> * <li>Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform </li> * <li>Notify L2Character AI </li><BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2NpcInstance : Create a DecayTask to remove the corpse of the L2NpcInstance after 7 seconds </li> * <li> L2Attackable : Distribute rewards (EXP, SP, Drops...) and notify Quest Engine </li> * <li> L2PcInstance : Apply Death Penalty, Manage gain/loss Karma and Item Drop </li><BR><BR> * * @param killer The L2Character who killed it * */ public void doDie(L2Character killer) { // Set target to null and cancel Attack or Cast setTarget(null); // Stop movement stopMove(null); // Stop HP/MP/CP Regeneration task getStatus().stopHpMpRegeneration(); //Stop all active skills effects in progress on the L2Character, if the Character isn't Noblesse Blessed if(isNoblesseBlessed() && (killer.isRaid() || (killer instanceof L2MinionInstance && killer.isRaid()))) stopNoblesseBlessing(null); else stopAllEffects(); // Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform broadcastStatusUpdate(); // Notify L2Character AI getAI().notifyEvent(CtrlEvent.EVT_DEAD, null); if (killer != null) { // Notify Quest of character's death L2NpcInstance npc = null; if (killer instanceof L2NpcInstance) npc = (L2NpcInstance)killer; for (QuestState qs: getNotifyQuestOfDeath()) { qs.getQuest().notifyDeath(npc, this, qs); } getNotifyQuestOfDeath().clear(); } getAttackByList().clear(); } /** Sets HP, MP and CP and revives the L2Character. */ public void doRevive() { if (!isTeleporting()) { setIsPendingRevive(false); _Status.setCurrentCp(getMaxCp() * Config.RESPAWN_RESTORE_CP); _Status.setCurrentHp(getMaxHp() * Config.RESPAWN_RESTORE_HP); //_Status.setCurrentMp(getMaxMp() * Config.RESPAWN_RESTORE_MP); // Start broadcast status broadcastPacket(new SocialAction(getObjectId(), 15)); broadcastPacket(new Revive(this)); } else setIsPendingRevive(true); } /** Revives the L2Character using skill. */ public void doRevive(double revivePower) { doRevive(); } /** * Check if the active L2Skill can be casted.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Check if the L2Character can cast (ex : not sleeping...) </li> * <li>Check if the target is correct </li> * <li>Notify the AI with AI_INTENTION_CAST and target</li><BR><BR> * * @param skill The L2Skill to use * */ protected void useMagic(L2Skill skill) { if (skill == null || isDead()) return; // Check if the L2Character can cast if (isAllSkillsDisabled()) { // must be checked by caller return; } // Ignore the passive skill request. why does the client send it anyway ?? if (skill.isPassive()) return; // Get the target for the skill L2Object target = null; switch (skill.getTargetType()) { case TARGET_AURA: // AURA, SELF should be cast even if no target has been found case TARGET_SELF: target = this; break; default: // Get the first target of the list target = skill.getFirstOfTargetList(this); break; } // Notify the AI with AI_INTENTION_CAST and target getAI().setIntention(CtrlIntention.AI_INTENTION_CAST, skill, target); } // ========================================================= // Property - Public /** * Return the L2CharacterAI of the L2Character and if its null create a new one. */ public L2CharacterAI getAI() { if (_ai == null) { synchronized(this) { if (_ai == null) _ai = new L2CharacterAI(new AIAccessor()); } } return _ai; } public void setAI(L2CharacterAI newAI) { L2CharacterAI oldAI = getAI(); if(oldAI != null && oldAI != newAI && oldAI instanceof L2AttackableAI) ((L2AttackableAI)oldAI).stopAITask(); _ai = newAI; } /** Return True if the L2Character has a L2CharacterAI. */ public boolean hasAI() { return _ai != null; } /** Return True if the L2Character is RaidBoss or his minion. */ public boolean isRaid() { return false; } /** Return a list of L2Character that attacked. */ public final List<L2Character> getAttackByList () { if (_AttackByList == null) _AttackByList = new FastList<L2Character>(); return _AttackByList; } public final L2Character getAttackingChar() { return _AttackingChar; } /** * Set _attackingChar to the L2Character that attacks this one.<BR><BR> * @param player The L2Character that attcks this one */ public final void setAttackingChar (L2Character player) { if (player == null || player == this) return; _AttackingChar = player; addAttackerToAttackByList(player); } public final L2Skill getAttackingCharSkill() { return _AttackingCharSkill; } /** * Set _attackingCharSkill to the L2Skill used against this L2Character.<BR><BR> * @param skill The L2Skill used against this L2Character */ public void setAttackingCharSkill (L2Skill skill) { _AttackingCharSkill = skill; } public final boolean isAffraid() { return _IsAffraid; } public final void setIsAffraid(boolean value) { _IsAffraid = value; } /** Return True if the L2Character is dead or use fake death. */ public final boolean isAlikeDead() { return isFakeDeath() || !(getCurrentHp() > 0.5); } /** Return True if the L2Character can't use its skills (ex : stun, sleep...). */ public final boolean isAllSkillsDisabled() { return _allSkillsDisabled || isStunned() || isSleeping() || isParalyzed(); } /** Return True if the L2Character can't attack (stun, sleep, attackEndTime, fakeDeath, paralyse). */ public final boolean isAttackingDisabled() { return isStunned() || isSleeping() || _attackEndTime > GameTimeController.getGameTicks() || isFakeDeath() || isParalyzed(); } public final Calculator[] getCalculators() { return _Calculators; } public final boolean isConfused() { return _IsConfused; } public final void setIsConfused(boolean value) { _IsConfused = value; } /** Return True if the L2Character is dead. */ public final boolean isDead() { return !(isFakeDeath()) && !(getCurrentHp() > 0.5); } public final boolean isFakeDeath() { return _IsFakeDeath; } public final void setIsFakeDeath(boolean value) { _IsFakeDeath = value; } /** Return True if the L2Character is flying. */ public final boolean isFlying() { return _IsFlying; } /** Set the L2Character flying mode to True. */ public final void setIsFlying(boolean mode) { _IsFlying = mode; } public boolean isImobilised() { return _IsImobilised; } public void setIsImobilised(boolean value){ _IsImobilised = value; } public final boolean isKilledAlready() { return _IsKilledAlready; } public final void setIsKilledAlready(boolean value) { _IsKilledAlready = value; } public final boolean isMuted() { return _IsMuted; } public final void setIsMuted(boolean value) { _IsMuted = value; } public final boolean isPsychicalMuted() { return _IsPsychicalMuted; } public final void setIsPsychicalMuted(boolean value) { _IsPsychicalMuted = value; } /** Return True if the L2Character can't move (stun, root, sleep, overload, paralyzed). */ public final boolean isMovementDisabled() { return isStunned() || isRooted() || isSleeping() || isOverloaded() || isParalyzed() || isImobilised() || isFakeDeath(); } /** Return True if the L2Character can be controlled by the player (confused, affraid). */ public final boolean isOutOfControl() { return isConfused() || isAffraid(); } public final boolean isOverloaded() { return _IsOverloaded; } /** Set the overloaded status of the L2Character is overloaded (if True, the L2PcInstance can't take more item). */ public final void setIsOverloaded(boolean value) { _IsOverloaded = value; } public final boolean isParalyzed() { return _IsParalyzed; } public final void setIsParalyzed(boolean value) { _IsParalyzed = value; } public final boolean isPendingRevive() { return isDead() && _IsPendingRevive; } public final void setIsPendingRevive(boolean value) { _IsPendingRevive = value; } /** * Return the L2Summon of the L2Character.<BR><BR> * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> */ public L2Summon getPet() { return null; } /** Return True if the L2Character is ridding. */ public final boolean isRiding() { return _IsRiding; } /** Set the L2Character riding mode to True. */ public final void setIsRiding(boolean mode) { _IsRiding = mode; } public final boolean isRooted() { return _IsRooted; } public final void setIsRooted(boolean value) { _IsRooted = value; } /** Return True if the L2Character is running. */ public final boolean isRunning() { return _IsRunning; } public final void setIsRunning(boolean value) { _IsRunning = value; broadcastPacket(new ChangeMoveType(this)); } /** Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance. */ public final void setRunning() { if (!isRunning()) setIsRunning(true); } public final boolean isSleeping() { return _IsSleeping; } public final void setIsSleeping(boolean value) { _IsSleeping = value; } public final boolean isStunned() { return _IsStunned; } public final void setIsStunned(boolean value) { _IsStunned = value; } public final boolean isBetrayed() { return _IsBetrayed; } public final void setIsBetrayed(boolean value) { _IsBetrayed = value; } public final boolean isTeleporting() { return _IsTeleporting; } public final void setIsTeleporting(boolean value) { _IsTeleporting = value; } public final boolean isNoblesseBlessed() { return _IsNoblesseBlessed; } public final void setIsNoblesseBlessed(boolean value) { _IsNoblesseBlessed = value; } public void setIsInvul(boolean b){_isInvul = b;} public boolean isInvul(){return _isInvul || _IsTeleporting;} public boolean isUndead() { return false; } public CharKnownList getKnownList() { if(super.getKnownList() == null || !(super.getKnownList() instanceof CharKnownList)) this.setKnownList(new CharKnownList(this)); return ((CharKnownList)super.getKnownList()); } public CharStat getStat() { if (_Stat == null) _Stat = new CharStat(this); return _Stat; } public final void setStat(CharStat value) { _Stat = value; } public CharStatus getStatus() { if (_Status == null) _Status = new CharStatus(this); return _Status; } public final void setStatus(CharStatus value) { _Status = value; } public L2CharTemplate getTemplate() { return _Template; } /** * Set the template of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * Each L2Character owns generic and static properties (ex : all Keltir have the same number of HP...). * All of those properties are stored in a different template for each type of L2Character. * Each template is loaded once in the server cache memory (reduce memory use). * When a new instance of L2Character is spawned, server just create a link between the instance and the template * This link is stored in <B>_template</B><BR><BR> * * <B><U> Assert </U> :</B><BR><BR> * <li> this instanceof L2Character</li><BR><BR */ protected final void setTemplate(L2CharTemplate template) { _Template = template; } /** Return the Title of the L2Character. */ public final String getTitle() { return _Title; } /** Set the Title of the L2Character. */ public final void setTitle(String value) { _Title = value; } /** Set the L2Character movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance. */ public final void setWalking() { if (isRunning()) setIsRunning(false); } /** Task lauching the function enableSkill() */ class EnableSkill implements Runnable { int _skillId; public EnableSkill(int skillId) { _skillId = skillId; } public void run() { try { enableSkill(_skillId); } catch (Throwable e) { _log.log(Level.SEVERE, "", e); } } } /** * Task lauching the function onHitTimer().<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance)</li> * <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance </li> * <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary </li> * <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...) </li><BR><BR> * */ class HitTask implements Runnable { L2Character _hitTarget; int _damage; boolean _crit; boolean _miss; boolean _shld; boolean _soulshot; public HitTask(L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, boolean shld) { _hitTarget = target; _damage = damage; _crit = crit; _shld = shld; _miss = miss; _soulshot = soulshot; } public void run() { try { onHitTimer(_hitTarget, _damage, _crit, _miss, _soulshot, _shld); } catch (Throwable e) { _log.severe(e.toString()); } } } /** Task lauching the function onMagicUseTimer() */ class MagicUseTask implements Runnable { L2Object[] _targets; L2Skill _skill; public MagicUseTask(L2Object[] targets, L2Skill skill) { _targets = targets; _skill = skill; } public void run() { try { enableAllSkills(); onMagicUseTimer(_targets, _skill); _skillCast = null; } catch (Throwable e) { _log.log(Level.SEVERE, "", e); } } } /** Task lauching the function useMagic() */ class QueuedMagicUseTask implements Runnable { L2PcInstance _currPlayer; L2Skill _queuedSkill; boolean _isCtrlPressed; boolean _isShiftPressed; public QueuedMagicUseTask(L2PcInstance currPlayer, L2Skill queuedSkill, boolean isCtrlPressed, boolean isShiftPressed) { _currPlayer = currPlayer; _queuedSkill = queuedSkill; _isCtrlPressed = isCtrlPressed; _isShiftPressed = isShiftPressed; } public void run() { try { _currPlayer.useMagic(_queuedSkill, _isCtrlPressed, _isShiftPressed); } catch (Throwable e) { _log.log(Level.SEVERE, "", e); } } } /** Task of AI notification */ public class NotifyAITask implements Runnable { private final CtrlEvent _evt; NotifyAITask(CtrlEvent evt) { this._evt = evt; } public void run() { try { getAI().notifyEvent(_evt, null); } catch (Throwable t) { _log.log(Level.WARNING, "", t); } } } /** Task lauching the function stopPvPFlag() */ class PvPFlag implements Runnable { public void run() { try { // _log.fine("Checking pvp time: " + getlastPvpAttack()); // "lastattack: " _lastAttackTime "currenttime: " // System.currentTimeMillis()); if (Math.abs(System.currentTimeMillis()-getlastPvpAttack()) > Config.PVP_TIME) { // _log.fine("Stopping PvP"); stopPvPFlag(); } else if (Math.abs(System.currentTimeMillis()-getlastPvpAttack()) > Config.PVP_TIME - 5000) { updatePvPFlag(2); } else { updatePvPFlag(1); // Start a new PvP timer check //checkPvPFlag(); } } catch (Exception e) { _log.log(Level.WARNING, "error in pvp flag task:", e); } } } // ========================================================= // ========================================================= // Abnormal Effect - NEED TO REMOVE ONCE L2CHARABNORMALEFFECT IS COMPLETE // Data Field /** Map 16 bits (0x0000) containing all abnormal effect in progress */ private short _AbnormalEffects; /** * FastTable containing all active skills effects in progress of a L2Character. */ private FastTable<L2Effect> _effects; /** The table containing the List of all stacked effect in progress for each Stack group Identifier */ protected Map<String, List<L2Effect>> _stackedEffects; /** Table EMPTY_EFFECTS shared by all L2Character without effects in progress */ private static final L2Effect[] EMPTY_EFFECTS = new L2Effect[0]; public static final int ABNORMAL_EFFECT_BLEEDING = 0x0001; // not sure public static final int ABNORMAL_EFFECT_POISON = 0x0002; public static final int ABNORMAL_EFFECT_UNKNOWN_3 = 0x0004; public static final int ABNORMAL_EFFECT_UNKNOWN_4 = 0x0008; public static final int ABNORMAL_EFFECT_UNKNOWN_5 = 0x0010; public static final int ABNORMAL_EFFECT_UNKNOWN_6 = 0x0020; public static final int ABNORMAL_EFFECT_STUN = 0x0040; public static final int ABNORMAL_EFFECT_SLEEP = 0x0080; public static final int ABNORMAL_EFFECT_MUTED = 0x0100; // not sure public static final int ABNORMAL_EFFECT_ROOT = 0x0200; public static final int ABNORMAL_EFFECT_HOLD_1 = 0x0400; public static final int ABNORMAL_EFFECT_HOLD_2 = 0x0800; public static final int ABNORMAL_EFFECT_UNKNOWN_13 = 0x1000; public static final int ABNORMAL_EFFECT_BIG_HEAD = 0x2000; public static final int ABNORMAL_EFFECT_FLAME = 0x4000; public static final int ABNORMAL_EFFECT_UNKNOWN_16 = 0x8000; // FIXME : correct the value of the abnormal effect confused public static final int ABNORMAL_EFFECT_CONFUSED = 0x0020; // FIXME : correct the value of the abnormal effect affraid public static final int ABNORMAL_EFFECT_AFFRAID = 0x0010; // Method - Public /** * Launch and add L2Effect (including Stack Group management) to L2Character and update client magic icone.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR><BR> * * Several same effect can't be used on a L2Character at the same time. * Indeed, effects are not stackable and the last cast will replace the previous in progress. * More, some effects belong to the same Stack Group (ex WindWald and Haste Potion). * If 2 effects of a same group are used at the same time on a L2Character, only the more efficient (identified by its priority order) will be preserve.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Add the L2Effect to the L2Character _effects</li> * <li>If this effect doesn't belong to a Stack Group, add its Funcs to the Calculator set of the L2Character (remove the old one if necessary)</li> * <li>If this effect has higher priority in its Stack Group, add its Funcs to the Calculator set of the L2Character (remove previous stacked effect Funcs if necessary)</li> * <li>If this effect has NOT higher priority in its Stack Group, set the effect to Not In Use</li> * <li>Update active skills in progress icones on player client</li><BR> * */ public final void addEffect(L2Effect newEffect) { if(newEffect == null) return; synchronized (this) { if (_effects == null) _effects = new FastTable<L2Effect>(); if (_stackedEffects == null) _stackedEffects = new FastMap<String, List<L2Effect>>(); } synchronized(_effects) { L2Effect tempEffect = null; // Make sure there's no same effect previously for (int i=0; i<_effects.size(); i++) { if (_effects.get(i).getSkill().getId() == newEffect.getSkill().getId()) return; } // Remove first Buff if number of buffs > 19 L2Skill tempskill = newEffect.getSkill(); if (getBuffCount() > Config.BUFFS_MAX_AMOUNT && !doesStack(tempskill) && (( tempskill.getSkillType() == L2Skill.SkillType.BUFF || tempskill.getSkillType() == L2Skill.SkillType.DEBUFF || tempskill.getSkillType() == L2Skill.SkillType.REFLECT || tempskill.getSkillType() == L2Skill.SkillType.HEAL_PERCENT || tempskill.getSkillType() == L2Skill.SkillType.MANAHEAL_PERCENT)&& tempskill.getId() != 4267 && tempskill.getId() != 4270 && !(tempskill.getId() > 4360 && tempskill.getId() < 4367)) ) removeFirstBuff(tempskill.getId()); // Add the L2Effect to all effect in progress on the L2Character if (!newEffect.getSkill().isToggle()) { int pos=0; for (int i=0; i<_effects.size(); i++) { if (_effects.get(i) != null) { int skillid = _effects.get(i).getSkill().getId(); if (!_effects.get(i).getSkill().isToggle() && (skillid != 4267 && skillid != 4270 && !(skillid > 4360 && skillid < 4367)) ) pos++; } else break; } _effects.add(pos, newEffect); } else _effects.addLast(newEffect); // Check if a stack group is defined for this effect if (newEffect.getStackType().equals("none")) { // Set this L2Effect to In Use newEffect.setInUse(true); // Add Funcs of this effect to the Calculator set of the L2Character addStatFuncs(newEffect.getStatFuncs()); // Update active skills in progress icones on player client updateEffectIcons(); return; } // Get the list of all stacked effects corresponding to the stack type of the L2Effect to add List<L2Effect> stackQueue = _stackedEffects.get(newEffect.getStackType()); if (stackQueue == null) stackQueue = new FastList<L2Effect>(); if (stackQueue.size() > 0) { // Get the first stacked effect of the Stack group selected tempEffect = null; for (int i=0; i<_effects.size(); i++) { if (_effects.get(i) == stackQueue.get(0)) { tempEffect = _effects.get(i); break; } } if (tempEffect != null) { // Remove all Func objects corresponding to this stacked effect from the Calculator set of the L2Character removeStatsOwner(tempEffect); // Set the L2Effect to Not In Use tempEffect.setInUse(false); } } // Add the new effect to the stack group selected at its position stackQueue = effectQueueInsert(newEffect, stackQueue); if (stackQueue == null) return; // Update the Stack Group table _stackedEffects of the L2Character _stackedEffects.put(newEffect.getStackType(), stackQueue); // Get the first stacked effect of the Stack group selected tempEffect = null; for (int i=0; i<_effects.size(); i++) { if (_effects.get(i) == stackQueue.get(0)) { tempEffect = _effects.get(i); break; } } // Set this L2Effect to In Use tempEffect.setInUse(true); // Add all Func objects corresponding to this stacked effect to the Calculator set of the L2Character addStatFuncs(tempEffect.getStatFuncs()); } // Update active skills in progress (In Use and Not In Use because stacked) icones on client updateEffectIcons(); } /** * Insert an effect at the specified position in a Stack Group.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * Several same effect can't be used on a L2Character at the same time. * Indeed, effects are not stackable and the last cast will replace the previous in progress. * More, some effects belong to the same Stack Group (ex WindWald and Haste Potion). * If 2 effects of a same group are used at the same time on a L2Character, only the more efficient (identified by its priority order) will be preserve.<BR><BR> * * @param id The identifier of the stacked effect to add to the Stack Group * @param stackOrder The position of the effect in the Stack Group * @param stackQueue The Stack Group in wich the effect must be added * */ private List<L2Effect> effectQueueInsert(L2Effect newStackedEffect, List<L2Effect> stackQueue) { // Get the L2Effect corresponding to the Effect Identifier from the L2Character _effects if(_effects == null) return null; // Create an Iterator to go through the list of stacked effects in progress on the L2Character Iterator<L2Effect> queueIterator = stackQueue.iterator(); int i = 0; while (queueIterator.hasNext()) { L2Effect cur = queueIterator.next(); if (newStackedEffect.getStackOrder() < cur.getStackOrder()) i++; else break; } // Add the new effect to the Stack list in function of its position in the Stack group stackQueue.add(i, newStackedEffect); // skill.exit() could be used, if the users don't wish to see "effect // removed" always when a timer goes off, even if the buff isn't active // any more (has been replaced). but then check e.g. npc hold and raid petrify. if (Config.EFFECT_CANCELING && !newStackedEffect.isHerbEffect() && stackQueue.size() > 1) { // only keep the current effect, cancel other effects for (int n=0; n<_effects.size(); n++) { if (_effects.get(n) == stackQueue.get(1)) { _effects.remove(n); break; } } stackQueue.remove(1); } return stackQueue; } /** * Stop and remove L2Effect (including Stack Group management) from L2Character and update client magic icone.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR><BR> * * Several same effect can't be used on a L2Character at the same time. * Indeed, effects are not stackable and the last cast will replace the previous in progress. * More, some effects belong to the same Stack Group (ex WindWald and Haste Potion). * If 2 effects of a same group are used at the same time on a L2Character, only the more efficient (identified by its priority order) will be preserve.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Remove Func added by this effect from the L2Character Calculator (Stop L2Effect)</li> * <li>If the L2Effect belongs to a not empty Stack Group, replace theses Funcs by next stacked effect Funcs</li> * <li>Remove the L2Effect from _effects of the L2Character</li> * <li>Update active skills in progress icones on player client</li><BR> * */ public final void removeEffect(L2Effect effect) { if(effect == null || _effects == null) return; synchronized(_effects) { if (effect.getStackType() == "none") { // Remove Func added by this effect from the L2Character Calculator removeStatsOwner(effect); } else { if(_stackedEffects == null) return; // Get the list of all stacked effects corresponding to the stack type of the L2Effect to add List<L2Effect> stackQueue = _stackedEffects.get(effect.getStackType()); if (stackQueue == null || stackQueue.size() < 1) return; // Get the Identifier of the first stacked effect of the Stack group selected L2Effect frontEffect = stackQueue.get(0); // Remove the effect from the Stack Group boolean removed = stackQueue.remove(effect); if (removed) { // Check if the first stacked effect was the effect to remove if (frontEffect == effect) { // Remove all its Func objects from the L2Character calculator set removeStatsOwner(effect); // Check if there's another effect in the Stack Group if (stackQueue.size() > 0) { // Add its list of Funcs to the Calculator set of the L2Character for (int i=0; i<_effects.size(); i++) { if (_effects.get(i) == stackQueue.get(0)) { // Add its list of Funcs to the Calculator set of the L2Character addStatFuncs(_effects.get(i).getStatFuncs()); // Set the effect to In Use _effects.get(i).setInUse(true); break; } } } } if (stackQueue.isEmpty()) _stackedEffects.remove(effect.getStackType()); else // Update the Stack Group table _stackedEffects of the L2Character _stackedEffects.put(effect.getStackType(), stackQueue); } } // Remove the active skill L2effect from _effects of the L2Character // The Integer key of _effects is the L2Skill Identifier that has created the effect for (int i=0; i<_effects.size(); i++) { if (_effects.get(i) == effect) { _effects.remove(i); break; } } } // Update active skills in progress (In Use and Not In Use because stacked) icones on client updateEffectIcons(); } /** * Active abnormal effects flags in the binary mask and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startAbnormalEffect(short mask) { _AbnormalEffects |= mask; updateAbnormalEffect(); } /** * Active the abnormal effect Confused flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startConfused() { setIsConfused(true); getAI().notifyEvent(CtrlEvent.EVT_CONFUSED); updateAbnormalEffect(); } /** * Active the abnormal effect Fake Death flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startFakeDeath() { setIsFakeDeath(true); /* Aborts any attacks/casts if fake dead */ abortAttack(); abortCast(); getAI().notifyEvent(CtrlEvent.EVT_FAKE_DEATH, null); broadcastPacket(new ChangeWaitType(this,ChangeWaitType.WT_START_FAKEDEATH)); } /** * Active the abnormal effect Fear flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startFear() { setIsAffraid(true); getAI().notifyEvent(CtrlEvent.EVT_AFFRAID); updateAbnormalEffect(); } /** * Active the abnormal effect Muted flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startMuted() { setIsMuted(true); /* Aborts any casts if muted */ abortCast(); getAI().notifyEvent(CtrlEvent.EVT_MUTED); updateAbnormalEffect(); } /** * Active the abnormal effect Psychical_Muted flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startPsychicalMuted() { setIsPsychicalMuted(true); getAI().notifyEvent(CtrlEvent.EVT_MUTED); updateAbnormalEffect(); } /** * Active the abnormal effect Root flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startRooted() { setIsRooted(true); getAI().notifyEvent(CtrlEvent.EVT_ROOTED, null); updateAbnormalEffect(); } /** * Active the abnormal effect Sleep flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet.<BR><BR> */ public final void startSleeping() { setIsSleeping(true); /* Aborts any attacks/casts if sleeped */ abortAttack(); abortCast(); getAI().notifyEvent(CtrlEvent.EVT_SLEEPING, null); updateAbnormalEffect(); } /** * Launch a Stun Abnormal Effect on the L2Character.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Calculate the success rate of the Stun Abnormal Effect on this L2Character</li> * <li>If Stun succeed, active the abnormal effect Stun flag, notify the L2Character AI and send Server->Client UserInfo/CharInfo packet</li> * <li>If Stun NOT succeed, send a system message Failed to the L2PcInstance attacker</li><BR><BR> * */ public final void startStunning() { setIsStunned(true); /* Aborts any attacks/casts if stunned */ abortAttack(); abortCast(); getAI().notifyEvent(CtrlEvent.EVT_STUNNED, null); updateAbnormalEffect(); } public final void startBetray() { setIsBetrayed(true); getAI().notifyEvent(CtrlEvent.EVT_BETRAYED, null); updateAbnormalEffect(); } public final void stopBetray() { stopEffects(L2Effect.EffectType.BETRAY); setIsBetrayed(false); updateAbnormalEffect(); } public final void startNoblesseBlessing() { setIsNoblesseBlessed(true); updateAbnormalEffect(); } } /** * Modify the abnormal effect map according to the mask.<BR><BR> */ public final void stopAbnormalEffect(short mask) { _AbnormalEffects &= ~mask; updateAbnormalEffect(); } public final void stopNoblesseBlessing(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.NOBLESSE_BLESSING); else removeEffect(effect); setIsNoblesseBlessed(false); updateAbnormalEffect(); } /** * Stop all active skills effects in progress on the L2Character.<BR><BR> */ public final void stopAllEffects() { // Get all active skills effects in progress on the L2Character L2Effect[] effects = getAllEffects(); if (effects == null) return; // Go through all active skills effects for(L2Effect e : effects) if(e != null) e.exit(); } /** * Stop a specified/all Confused abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Confused abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _confused to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopConfused(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.CONFUSION); else removeEffect(effect); setIsConfused(false); getAI().notifyEvent(CtrlEvent.EVT_THINK, null); updateAbnormalEffect(); } /** * Stop and remove the L2Effect corresponding to the L2Skill Identifier and update client magic icone.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR><BR> * * @param effectId The L2Skill Identifier of the L2Effect to remove from _effects * */ public final void stopEffect(int effectId) { L2Effect effect = getEffect(effectId); if(effect != null) effect.exit(); } /** * Stop and remove all L2Effect of the selected type (ex : BUFF, DMG_OVER_TIME...) from the L2Character and update client magic icone.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Remove Func added by this effect from the L2Character Calculator (Stop L2Effect)</li> * <li>Remove the L2Effect from _effects of the L2Character</li> * <li>Update active skills in progress icones on player client</li><BR><BR> * * @param type The type of effect to stop ((ex : BUFF, DMG_OVER_TIME...) * */ public final void stopEffects(L2Effect.EffectType type) { // Get all active skills effects in progress on the L2Character L2Effect[] effects = getAllEffects(); if (effects == null) return; // Go through all active skills effects for(L2Effect e : effects) { // Stop active skills effects of the selected type if (e.getEffectType() == type) e.exit(); } } /** * Stop a specified/all Fake Death abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Fake Death abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _fake_death to False </li> * <li>Notify the L2Character AI</li><BR><BR> * */ public final void stopFakeDeath(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.FAKE_DEATH); else removeEffect(effect); setIsFakeDeath(false); ChangeWaitType revive = new ChangeWaitType(this,ChangeWaitType.WT_STOP_FAKEDEATH); broadcastPacket(revive); getAI().notifyEvent(CtrlEvent.EVT_THINK, null); } /** * Stop a specified/all Fear abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Fear abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _affraid to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopFear(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.FEAR); else removeEffect(effect); setIsAffraid(false); updateAbnormalEffect(); } /** * Stop a specified/all Muted abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Muted abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _muted to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopMuted(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.MUTE); else removeEffect(effect); setIsMuted(false); updateAbnormalEffect(); } public final void stopPsychicalMuted(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.PSYCHICAL_MUTE); else removeEffect(effect); setIsPsychicalMuted(false); updateAbnormalEffect(); } /** * Stop a specified/all Root abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Root abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _rooted to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopRooting(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.ROOT); else removeEffect(effect); setIsRooted(false); getAI().notifyEvent(CtrlEvent.EVT_THINK, null); updateAbnormalEffect(); } /** * Stop a specified/all Sleep abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Sleep abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _sleeping to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopSleeping(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.SLEEP); else removeEffect(effect); setIsSleeping(false); getAI().notifyEvent(CtrlEvent.EVT_THINK, null); updateAbnormalEffect(); } /** * Stop a specified/all Stun abnormal L2Effect.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete a specified/all (if effect=null) Stun abnormal L2Effect from L2Character and update client magic icone </li> * <li>Set the abnormal effect flag _stuned to False </li> * <li>Notify the L2Character AI</li> * <li>Send Server->Client UserInfo/CharInfo packet</li><BR><BR> * */ public final void stopStunning(L2Effect effect) { if (effect == null) stopEffects(L2Effect.EffectType.STUN); else removeEffect(effect); setIsStunned(false); getAI().notifyEvent(CtrlEvent.EVT_THINK, null); updateAbnormalEffect(); } /** * Not Implemented.<BR><BR> * * <B><U> Overridden in</U> :</B><BR><BR> * <li>L2NPCInstance</li> * <li>L2PcInstance</li> * <li>L2Summon</li> * <li>L2DoorInstance</li><BR><BR> * */ public abstract void updateAbnormalEffect(); /** * Update active skills in progress (In Use and Not In Use because stacked) icones on client.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress (In Use and Not In Use because stacked) are represented by an icone on the client.<BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method ONLY UPDATE the client of the player and not clients of all players in the party.</B></FONT><BR><BR> * */ public final void updateEffectIcons() { updateEffectIcons(false); } public final void updateEffectIcons(boolean partyOnly) { // Create a L2PcInstance of this if needed L2PcInstance player = null; if (this instanceof L2PcInstance) player = (L2PcInstance)this; // Create a L2Summon of this if needed L2Summon summon = null; if (this instanceof L2Summon) { summon = (L2Summon)this; player = summon.getOwner(); } // Create the main packet if needed MagicEffectIcons mi = null; if (!partyOnly) mi = new MagicEffectIcons(); // Create the party packet if needed PartySpelled ps = null; if (summon != null) ps = new PartySpelled(summon); else if (player != null && player.isInParty()) ps = new PartySpelled(player); // Create the olympiad spectator packet if needed ExOlympiadSpelledInfo os = null; if (player != null && player.isInOlympiadMode()) os = new ExOlympiadSpelledInfo(player); if (mi == null && ps ==null && os == null) return; // nothing to do (should not happen) // Add special effects if (player != null && mi != null) { if (player.getWeightPenalty() > 0) mi.addEffect(4270, player.getWeightPenalty(), -1); if (player.getexpertisePenalty() > 0) mi.addEffect(4267, 1, -1); if (player.getMessageRefusal()) mi.addEffect(4269, 1, -1); } // Go through all effects if any L2Effect[] effects = getAllEffects(); if (effects != null && effects.length > 0) for (int i = 0; i < effects.length; i++) { L2Effect effect = effects[i]; if (effect == null) continue; if (effect.getInUse()) { if (mi != null) effect.addIcon(mi); if (ps != null) effect.addPartySpelledIcon(ps); if (os != null) effect.addOlympiadSpelledIcon(os); } } // Send the packets if needed if (mi != null) sendPacket(mi); if (ps != null && player != null) { if (player.isInParty()) player.getParty().broadcastToPartyMembers(player, ps); else player.sendPacket(ps); } if (os != null) { if (Olympiad.getInstance().getSpectators(player.getOlympiadGameId()) != null) { for (L2PcInstance spectator : Olympiad.getInstance().getSpectators(player.getOlympiadGameId())) { if (spectator == null) continue; spectator.sendPacket(os); } } } } // Property - Public /** * Return a map of 16 bits (0x0000) containing all abnormal effect in progress for this L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * In Server->Client packet, each effect is represented by 1 bit of the map (ex : BLEEDING = 0x0001 (bit 1), SLEEP = 0x0080 (bit 8)...). * The map is calculated by applying a BINARY OR operation on each effect.<BR><BR> * * <B><U> Example of use </U> :</B><BR><BR> * <li> Server Packet : CharInfo, NpcInfo, NpcInfoPoly, UserInfo...</li><BR><BR> */ public short getAbnormalEffect() { short ae = _AbnormalEffects; if (isStunned()) ae |= ABNORMAL_EFFECT_STUN; if (isRooted()) ae |= ABNORMAL_EFFECT_ROOT; if (isSleeping()) ae |= ABNORMAL_EFFECT_SLEEP; if (isConfused()) ae |= ABNORMAL_EFFECT_CONFUSED; if (isMuted()) ae |= ABNORMAL_EFFECT_MUTED; if (isAffraid()) ae |= ABNORMAL_EFFECT_AFFRAID; return ae; } /** * Return all active skills effects in progress on the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the effect.<BR><BR> * * @return A table containing all active skills effect in progress on the L2Character * */ public final L2Effect[] getAllEffects() { // Create a copy of the effects set FastTable<L2Effect> effects = _effects; // If no effect found, return EMPTY_EFFECTS if (effects == null || effects.isEmpty()) return EMPTY_EFFECTS; // Return all effects in progress in a table int ArraySize = effects.size(); L2Effect[] effectArray = new L2Effect[ArraySize]; for (int i=0; i<ArraySize; i++) { if (i >= effects.size() || effects.get(i) == null) break; effectArray[i] = effects.get(i); } return effectArray; } /** * Return L2Effect in progress on the L2Character corresponding to the L2Skill Identifier.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in <B>_effects</B>. * * @param index The L2Skill Identifier of the L2Effect to return from the _effects * * @return The L2Effect corresponding to the L2Skill Identifier * */ public final L2Effect getEffect(int index) { FastTable<L2Effect> effects = _effects; if (effects == null) return null; L2Effect e; L2Effect eventNotInUse = null; for (int i=0; i<effects.size(); i++) { e = effects.get(i); if (e.getSkill().getId() == index) { if (e.getInUse()) return e; else eventNotInUse = e; } } return eventNotInUse; } /** * Return the first L2Effect in progress on the L2Character created by the L2Skill.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in <B>_effects</B>. * * @param skill The L2Skill whose effect must be returned * * @return The first L2Effect created by the L2Skill * */ public final L2Effect getEffect(L2Skill skill) { FastTable<L2Effect> effects = _effects; if (effects == null) return null; L2Effect e; L2Effect eventNotInUse = null; for (int i=0; i<effects.size(); i++) { e = effects.get(i); if (e.getSkill() == skill) { if (e.getInUse()) return e; else eventNotInUse = e; } } return eventNotInUse; } /** * Return the first L2Effect in progress on the L2Character corresponding to the Effect Type (ex : BUFF, STUN, ROOT...).<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. * The Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR><BR> * * @param tp The Effect Type of skills whose effect must be returned * * @return The first L2Effect corresponding to the Effect Type * */ public final L2Effect getEffect(L2Effect.EffectType tp) { FastTable<L2Effect> effects = _effects; if (effects == null) return null; L2Effect e; L2Effect eventNotInUse = null; for (int i=0; i<effects.size(); i++) { e = effects.get(i); if (e.getEffectType() == tp) { if (e.getInUse()) return e; else eventNotInUse = e; } } return eventNotInUse; } // ========================================================= // ========================================================= // NEED TO ORGANIZE AND MOVE TO PROPER PLACE /** This class permit to the L2Character AI to obtain informations and uses L2Character method */ public class AIAccessor { public AIAccessor() {} /** * Return the L2Character managed by this Accessor AI.<BR><BR> */ public L2Character getActor() { return L2Character.this; } /** * Accessor to L2Character moveToLocation() method with an interaction area.<BR><BR> */ public void moveTo(int x, int y, int z, int offset) { L2Character.this.moveToLocation(x, y, z, offset); } /** * Accessor to L2Character moveToLocation() method without interaction area.<BR><BR> */ public void moveTo(int x, int y, int z) { L2Character.this.moveToLocation(x, y, z, 0); } /** * Accessor to L2Character stopMove() method.<BR><BR> */ public void stopMove(L2CharPosition pos) { L2Character.this.stopMove(pos); } /** * Accessor to L2Character doAttack() method.<BR><BR> */ public void doAttack(L2Character target) { L2Character.this.doAttack(target); } /** * Accessor to L2Character doCast() method.<BR><BR> */ public void doCast(L2Skill skill) { L2Character.this.doCast(skill); } /** * Create a NotifyAITask.<BR><BR> */ public NotifyAITask newNotifyTask(CtrlEvent evt) { return new NotifyAITask(evt); } /** * Cancel the AI.<BR><BR> */ public void detachAI() { L2Character.this._ai = null; } } /** * This class group all mouvement data.<BR><BR> * * <B><U> Data</U> :</B><BR><BR> * <li>_moveTimestamp : Last time position update</li> * <li>_xDestination, _yDestination, _zDestination : Position of the destination</li> * <li>_xMoveFrom, _yMoveFrom, _zMoveFrom : Position of the origin</li> * <li>_moveStartTime : Start time of the movement</li> * <li>_ticksToMove : Nb of ticks between the start and the destination</li> * <li>_xSpeedTicks, _ySpeedTicks : Speed in unit/ticks</li><BR><BR> * * */ public static class MoveData { // when we retrieve x/y/z we use GameTimeControl.getGameTicks() // if we are moving, but move timestamp==gameticks, we don't need // to recalculate position public int _moveTimestamp; public int _xDestination; public int _yDestination; public int _zDestination; public int _xMoveFrom; public int _yMoveFrom; public int _zMoveFrom; public int _heading; public int _moveStartTime; public int _ticksToMove; public float _xSpeedTicks; public float _ySpeedTicks; } /** Table containing all skillId that are disabled */ protected List<Integer> _disabledSkills; private boolean _allSkillsDisabled;// private int _flyingRunSpeed;// private int _floatingWalkSpeed;// private int _flyingWalkSpeed;// private int _floatingRunSpeed; /** Movement data of this L2Character */ protected MoveData _move; /** Orientation of the L2Character */ private int _heading; /** L2Charcater targeted by the L2Character */ private L2Object _target; // setted by the start of casting, in game ticks private int _castEndTime; private int _castInterruptTime; private boolean _inCombat; // setted by the start of attack, in game ticks private int _attackEndTime; private int _attacking; private int _disableBowAttackEndTime; /** Table of calculators containing all standard NPC calculator (ex : ACCURACY_COMBAT, EVASION_RATE */ private static final Calculator[] NPC_STD_CALCULATOR; static {NPC_STD_CALCULATOR = Formulas.getInstance().getStdNPCCalculators();} protected L2CharacterAI _ai; /** Future Skill Cast */ protected Future _skillCast; /** Char Coords from Client */ private int _clientX; private int _clientY; private int _clientZ; private int _clientHeading; /** List of all QuestState instance that needs to be notified of this character's death */ private List<QuestState> _NotifyQuestOfDeathList = new FastList<QuestState>(); /** * Add QuestState instance that is to be notified of character's death.<BR><BR> * * @param qs The QuestState that subscribe to this event * */ public void addNotifyQuestOfDeath (QuestState qs) { if (qs == null || _NotifyQuestOfDeathList.contains(qs)) return; _NotifyQuestOfDeathList.add(qs); } /** * Return a list of L2Character that attacked.<BR><BR> */ public final List<QuestState> getNotifyQuestOfDeath () { if (_NotifyQuestOfDeathList == null) _NotifyQuestOfDeathList = new FastList<QuestState>(); return _NotifyQuestOfDeathList; } /** * Add a Func to the Calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). * To reduce cache memory use, L2NPCInstances who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR><BR> * * That's why, if a L2NPCInstance is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR * must be create in its _calculators before addind new Func object.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>If _calculators is linked to NPC_STD_CALCULATOR, create a copy of NPC_STD_CALCULATOR in _calculators</li> * <li>Add the Func object to _calculators</li><BR><BR> * * @param f The Func object to add to the Calculator corresponding to the state affected */ public final synchronized void addStatFunc(Func f) { if (f == null) return; // Check if Calculator set is linked to the standard Calculator set of NPC if (_Calculators == NPC_STD_CALCULATOR) { // Create a copy of the standard NPC Calculator set _Calculators = new Calculator[Stats.NUM_STATS]; for (int i=0; i < Stats.NUM_STATS; i++) { if (NPC_STD_CALCULATOR[i] != null) _Calculators[i] = new Calculator(NPC_STD_CALCULATOR[i]); } } // Select the Calculator of the affected state in the Calculator set int stat = f._stat.ordinal(); if (_Calculators[stat] == null) _Calculators[stat] = new Calculator(); // Add the Func to the calculator corresponding to the state _Calculators[stat].addFunc(f); } /** * Add a list of Funcs to the Calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). <BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is ONLY for L2PcInstance</B></FONT><BR><BR> * * <B><U> Example of use </U> :</B><BR><BR> * <li> Equip an item from inventory</li> * <li> Learn a new passive skill</li> * <li> Use an active skill</li><BR><BR> * * @param funcs The list of Func objects to add to the Calculator corresponding to the state affected */ public final synchronized void addStatFuncs(Func[] funcs) { for (Func f : funcs) addStatFunc(f); if (funcs.length > 0) updateStats(); } /** * Remove a Func from the Calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). * To reduce cache memory use, L2NPCInstances who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR><BR> * * That's why, if a L2NPCInstance is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR * must be create in its _calculators before addind new Func object.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Remove the Func object from _calculators</li><BR><BR> * <li>If L2Character is a L2NPCInstance and _calculators is equal to NPC_STD_CALCULATOR, * free cache memory and just create a link on NPC_STD_CALCULATOR in _calculators</li><BR><BR> * * @param f The Func object to remove from the Calculator corresponding to the state affected */ public final synchronized void removeStatFunc(Func f) { if (f == null) return; // Select the Calculator of the affected state in the Calculator set int stat = f._stat.ordinal(); if (_Calculators[stat] == null) return; // Remove the Func object from the Calculator _Calculators[stat].removeFunc(f); if (_Calculators[stat].size() == 0) _Calculators[stat] = null; // If possible, free the memory and just create a link on NPC_STD_CALCULATOR if (this instanceof L2NpcInstance) { int i = 0; for (; i < Stats.NUM_STATS; i++) { if (!Calculator.equalsCals(_Calculators[i], NPC_STD_CALCULATOR[i])) break; } if (i >= Stats.NUM_STATS) _Calculators = NPC_STD_CALCULATOR; } } /** * Remove a list of Funcs from the Calculator set of the L2PcInstance.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). <BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is ONLY for L2PcInstance</B></FONT><BR><BR> * * <B><U> Example of use </U> :</B><BR><BR> * <li> Unequip an item from inventory</li> * <li> Stop an active skill</li><BR><BR> * * @param funcs The list of Func objects to add to the Calculator corresponding to the state affected */ public final synchronized void removeStatFuncs(Func[] funcs) { for (Func f : funcs) removeStatFunc(f); if (funcs.length > 0) updateStats(); } /** * Remove all Func objects with the selected owner from the Calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). * To reduce cache memory use, L2NPCInstances who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR><BR> * * That's why, if a L2NPCInstance is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR * must be create in its _calculators before addind new Func object.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Remove all Func objects of the selected owner from _calculators</li><BR><BR> * <li>If L2Character is a L2NPCInstance and _calculators is equal to NPC_STD_CALCULATOR, * free cache memory and just create a link on NPC_STD_CALCULATOR in _calculators</li><BR><BR> * * <B><U> Example of use </U> :</B><BR><BR> * <li> Unequip an item from inventory</li> * <li> Stop an active skill</li><BR><BR> * * @param owner The Object(Skill, Item...) that has created the effect */ public final synchronized void removeStatsOwner(Object owner) { // Go through the Calculator set for (int i=0; i < _Calculators.length; i++) { if (_Calculators[i] != null) { // Delete all Func objects of the selected owner _Calculators[i].removeOwner(owner); if (_Calculators[i].size() == 0) _Calculators[i] = null; } } // If possible, free the memory and just create a link on NPC_STD_CALCULATOR if (this instanceof L2NpcInstance) { int i = 0; for (; i < Stats.NUM_STATS; i++) { if (!Calculator.equalsCals(_Calculators[i], NPC_STD_CALCULATOR[i])) break; } if (i >= Stats.NUM_STATS) _Calculators = NPC_STD_CALCULATOR; } } /** * Return the orientation of the L2Character.<BR><BR> */ public final int getHeading() { return _heading; } /** * Set the orientation of the L2Character.<BR><BR> */ public final void setHeading(int heading) { _heading = heading; } /** * Get zone L2Character is currently located in */ public Zone getZone() { for (Zone zone: ZoneManager.getInstance().getZones(ZoneType.getZoneTypeName(ZoneType.ZoneTypeEnum.Town))) if (zone.checkIfInZone(this))return zone; return null; } /** * Return the X destination of the L2Character or the X position if not in movement.<BR><BR> */ public final int getClientX() { return _clientX; } public final int getClientY() { return _clientY; } public final int getClientZ() { return _clientZ; } public final int getClientHeading() { return _clientHeading; } public final void setClientX(int val) { _clientX=val; } public final void setClientY(int val) { _clientY=val; } public final void setClientZ(int val) { _clientZ=val; } public final void setClientHeading(int val) { _clientHeading=val; } public final int getXdestination() { MoveData m = _move; if (m != null) return m._xDestination; return getX(); } /** * Return the Y destination of the L2Character or the Y position if not in movement.<BR><BR> */ public final int getYdestination() { MoveData m = _move; if (m != null) return m._yDestination; return getY(); } /** * Return the Z destination of the L2Character or the Z position if not in movement.<BR><BR> */ public final int getZdestination() { MoveData m = _move; if (m != null) return m._zDestination; return getZ(); } /** * Return True if the L2Character is in combat.<BR><BR> */ public final boolean isInCombat() { return _inCombat; } /** * Return True if the L2Character is moving.<BR><BR> */ public final boolean isMoving() { return _move != null; } /** * Return True if the L2Character is casting.<BR><BR> */ public final boolean isCastingNow() { return _castEndTime > GameTimeController.getGameTicks(); } /** * Return True if the cast of the L2Character can be aborted.<BR><BR> */ public final boolean canAbortCast() { return _castInterruptTime > GameTimeController.getGameTicks(); } /** * Return True if the L2Character is attacking.<BR><BR> */ public final boolean isAttackingNow() { return _attackEndTime > GameTimeController.getGameTicks(); } /** * Return True if the L2Character has aborted its attack.<BR><BR> */ public final boolean isAttackAborted() { return _attacking <= 0; } /** * Abort the attack of the L2Character and send Server->Client ActionFailed packet.<BR><BR> */ public final void abortAttack() { if (isAttackingNow()) { _attacking = 0; sendPacket(new ActionFailed()); } } /** * Returns body part (paperdoll slot) we are targeting right now */ public final int getAttackingBodyPart() { return _attacking; } /** * Abort the cast of the L2Character and send Server->Client MagicSkillCanceld/ActionFailed packet.<BR><BR> */ public final void abortCast() { if (isCastingNow()) { _castEndTime = 0; _castInterruptTime = 0; if (_skillCast != null) { _skillCast.cancel(true); _skillCast = null; } // cancels the skill hit scheduled task enableAllSkills(); // re-enables the skills if (this instanceof L2PcInstance) getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING); // setting back previous intention broadcastPacket(new MagicSkillCanceld(getObjectId())); // broadcast packet to stop animations client-side sendPacket(new ActionFailed()); // send an "action failed" packet to the caster } } /** * Update the position of the L2Character during a movement and return True if the movement is finished.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * At the beginning of the move action, all properties of the movement are stored in the MoveData object called <B>_move</B> of the L2Character. * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.<BR><BR> * * When the movement is started (ex : by MovetoLocation), this method will be called each 0.1 sec to estimate and update the L2Character position on the server. * Note, that the current server position can differe from the current client position even if each movement is straight foward. * That's why, client send regularly a Client->Server ValidatePosition packet to eventually correct the gap on the server. * But, it's always the server position that is used in range calculation.<BR><BR> * * At the end of the estimated movement time, the L2Character position is automatically set to the destination position even if the movement is not finished.<BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : The current Z position is obtained FROM THE CLIENT by the Client->Server ValidatePosition Packet. * But x and y positions must be calculated to avoid that players try to modify their movement speed.</B></FONT><BR><BR> * * @param gameTicks Nb of ticks since the server start * @return True if the movement is finished */ public boolean updatePosition(int gameTicks) { // Get movement data MoveData m = _move; if (m == null) return true; if (!isVisible()) { _move = null; return true; } // Check if the position has alreday be calculated if (m._moveTimestamp == gameTicks) return false; // Calculate the time between the beginning of the deplacement and now int elapsed = gameTicks - m._moveStartTime; // If estimated time needed to achieve the destination is passed, // the L2Character is positionned to the destination position if (elapsed >= m._ticksToMove) { // Set the timer of last position update to now m._moveTimestamp = gameTicks; // Set the position of the L2Character to the destination if(this instanceof L2BoatInstance ) { super.getPosition().setXYZ(m._xDestination, m._yDestination, m._zDestination); ((L2BoatInstance)this).updatePeopleInTheBoat(m._xDestination, m._yDestination, m._zDestination); } else { super.getPosition().setXYZ(m._xDestination, m._yDestination, m._zDestination); } // Cancel the move action _move = null; return true; } // Estimate the position of the L2Character dureing the movement according to its _xSpeedTicks and _ySpeedTicks // The Z position is obtained from the client if(this instanceof L2BoatInstance ) { super.getPosition().setXYZ(m._xMoveFrom + (int)(elapsed * m._xSpeedTicks),m._yMoveFrom + (int)(elapsed * m._ySpeedTicks),super.getZ()); ((L2BoatInstance)this).updatePeopleInTheBoat(m._xMoveFrom + (int)(elapsed * m._xSpeedTicks),m._yMoveFrom + (int)(elapsed * m._ySpeedTicks),super.getZ()); } else { super.getPosition().setXYZ(m._xMoveFrom + (int)(elapsed * m._xSpeedTicks),m._yMoveFrom + (int)(elapsed * m._ySpeedTicks),super.getZ()); } // Set the timer of last position update to now m._moveTimestamp = gameTicks; return false; } /** * Stop movement of the L2Character (Called by AI Accessor only).<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Delete movement data of the L2Character </li> * <li>Set the current position (x,y,z), its current L2WorldRegion if necessary and its heading </li> * <li>Remove the L2Object object from _gmList** of GmListTable </li> * <li>Remove object from _knownObjects and _knownPlayer* of all surrounding L2WorldRegion L2Characters </li><BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T send Server->Client packet StopMove/StopRotation </B></FONT><BR><BR> * */ public void stopMove(L2CharPosition pos) { stopMove(pos, true); } public void stopMove(L2CharPosition pos, boolean updateKnownObjects) { // Delete movement data of the L2Character _move = null; //if (getAI() != null) // getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE); // Set the current position (x,y,z), its current L2WorldRegion if necessary and its heading // All data are contained in a L2CharPosition object if (pos != null) { getPosition().setXYZ(pos.x, pos.y, pos.z); setHeading(pos.heading); } sendPacket(new StopMove(this)); if (updateKnownObjects) ThreadPoolManager.getInstance().executeTask(new KnownListAsynchronousUpdateTask(this)); } /** * Target a L2Object (add the target to the L2Character _target, _knownObject and L2Character to _KnownObject of the L2Object).<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * The L2Object (including L2Character) targeted is identified in <B>_target</B> of the L2Character<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Set the _target of L2Character to L2Object </li> * <li>If necessary, add L2Object to _knownObject of the L2Character </li> * <li>If necessary, add L2Character to _KnownObject of the L2Object </li> * <li>If object==null, cancel Attak or Cast </li><BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance : Remove the L2PcInstance from the old target _statusListener and add it to the new target if it was a L2Character</li><BR><BR> * * @param object L2object to target * */ public void setTarget(L2Object object) { if (object != null && !object.isVisible()) object = null; if (object != null && object != _target) { this.getKnownList().addKnownObject(object); object.getKnownList().addKnownObject(this); } // If object==null, Cancel Attak or Cast if (object == null) { if(_target != null) { broadcastPacket(new TargetUnselected(this)); } /*if (isAttackingNow() && getAI().getAttackTarget() == _target) { abortAttack(); getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); if (this instanceof L2PcInstance) { sendPacket(new ActionFailed()); SystemMessage sm = new SystemMessage(SystemMessage.S1_S2); sm.addString("Attack is aborted"); sendPacket(sm); } } if (isCastingNow() && canAbortCast() && getAI().getCastTarget() == _target) { abortCast(); getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); if (this instanceof L2PcInstance) { sendPacket(new ActionFailed()); SystemMessage sm = new SystemMessage(SystemMessage.S1_S2); sm.addString("Casting is aborted"); sendPacket(sm); } }*/ } _target = object; } /** * Return the identifier of the L2Object targeted or -1.<BR><BR> */ public final int getTargetId() { if (_target != null) { return _target.getObjectId(); } return -1; } /** * Return the L2Object targeted or null.<BR><BR> */ public final L2Object getTarget() { return _target; } // called from AIAccessor only /** * Calculate movement data for a move to location action and add the L2Character to movingObjects of GameTimeController (only called by AI Accessor).<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * At the beginning of the move action, all properties of the movement are stored in the MoveData object called <B>_move</B> of the L2Character. * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.<BR><BR> * All L2Character in movement are identified in <B>movingObjects</B> of GameTimeController that will call the updatePosition method of those L2Character each 0.1s.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Get current position of the L2Character </li> * <li>Calculate distance (dx,dy) between current position and destination including offset </li> * <li>Create and Init a MoveData object </li> * <li>Set the L2Character _move object to MoveData object </li> * <li>Add the L2Character to movingObjects of the GameTimeController </li> * <li>Create a task to notify the AI that L2Character arrives at a check point of the movement </li><BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T send Server->Client packet MoveToPawn/CharMoveToLocation </B></FONT><BR><BR> * * <B><U> Example of use </U> :</B><BR><BR> * <li> AI : onIntentionMoveTo(L2CharPosition), onIntentionPickUp(L2Object), onIntentionInteract(L2Object) </li> * <li> FollowTask </li><BR><BR> * * @param x The X position of the destination * @param y The Y position of the destination * @param z The Y position of the destination * @param offset The size of the interaction area of the L2Character targeted * */ protected void moveToLocation(int x, int y, int z, int offset) { // Get current position of the L2Character final int curX = super.getX(); final int curY = super.getY(); final int curZ = super.getZ(); // Calculate distance (dx,dy) between current position and destination final double dx = (x - curX); final double dy = (y - curY); double distance = Math.sqrt(dx*dx + dy*dy); if (Config.DEBUG) _log.fine("distance to target:" + distance); // Define movement angles needed // ^ // | X (x,y) // | / // | /distance // | / // |/ angle // X ----------> // (curx,cury) double cos; double sin; // Check if a movement offset is defined or no distance to go through if (offset > 0 || distance < 1) { // If no distance to go through, the movement is canceled if (distance < 1 || distance - offset <= 0) { sin = 0; cos = 1; distance = 0; x = curX; y = curY; if (Config.DEBUG) _log.fine("already in range, no movement needed."); // Notify the AI that the L2Character is arrived at destination getAI().notifyEvent(CtrlEvent.EVT_ARRIVED, null); return; } // Calculate movement angles needed sin = dy/distance; cos = dx/distance; distance -= (offset-5); // due to rounding error, we have to move a bit closer to be in range // Calculate the new destination with offset included x = curX + (int)(distance * cos); y = curY + (int)(distance * sin); } else { // Calculate movement angles needed sin = dy/distance; cos = dx/distance; } // Get the Move Speed of the L2Charcater float speed = getStat().getMoveSpeed(); // Create and Init a MoveData object MoveData m = new MoveData(); // Caclulate the Nb of ticks between the current position and the destination m._ticksToMove = (int)(GameTimeController.TICKS_PER_SECOND * (distance + 25) / speed); // Calculate the xspeed and yspeed in unit/ticks in function of the movement speed m._xSpeedTicks = (float)(cos * speed / GameTimeController.TICKS_PER_SECOND); m._ySpeedTicks = (float)(sin * speed / GameTimeController.TICKS_PER_SECOND); // Calculate and set the heading of the L2Character int heading = (int) (Math.atan2(-sin, -cos) * 10430.378350470452724949566316381); heading += 32768; setHeading(heading); if (Config.DEBUG) _log.fine("dist:"+ distance +"speed:" + speed + " ttt:" +m._ticksToMove + " dx:"+(int)m._xSpeedTicks + " dy:"+(int)m._ySpeedTicks + " heading:" + heading); m._xDestination = x; m._yDestination = y; m._zDestination = z; // this is what was requested from client m._heading = 0; m._moveStartTime = GameTimeController.getGameTicks(); m._xMoveFrom = curX; m._yMoveFrom = curY; m._zMoveFrom = curZ; // If necessary set Nb ticks needed to a min value to ensure small distancies movements if (m._ticksToMove < 1 ) m._ticksToMove = 1; if (Config.DEBUG) _log.fine("time to target:" + m._ticksToMove); // Set the L2Character _move object to MoveData object _move = m; // Add the L2Character to movingObjects of the GameTimeController // The GameTimeController manage objects movement GameTimeController.getInstance().registerMovingObject(this); int tm = m._ticksToMove*GameTimeController.MILLIS_IN_TICK; // Create a task to notify the AI that L2Character arrives at a check point of the movement if (tm > 3000) ThreadPoolManager.getInstance().scheduleAi( new NotifyAITask(CtrlEvent.EVT_ARRIVED_REVALIDATE), 2000); // the CtrlEvent.EVT_ARRIVED will be sent when the character will actually arrive // to destination by GameTimeController } public boolean validateMovementHeading(int heading) { if (_move == null) return true; boolean result = true;// if (_move._heading < heading - 5 || _move._heading > heading 5) if (_move._heading != heading) { result = (_move._heading == 0); _move._heading = heading; } return result; } /** * Return the distance between the current position of the L2Character and the target (x,y).<BR><BR> * * @param x X position of the target * @param y Y position of the target * @return the plan distance * * @deprecated use getPlanDistanceSq(int x, int y, int z) */ @Deprecated public final double getDistance(int x, int y) { double dx = x - getX(); double dy = y - getY(); return Math.sqrt(dx*dx + dy*dy); } /** * Return the distance between the current position of the L2Character and the target (x,y).<BR><BR> * * @param x X position of the target * @param y Y position of the target * @return the plan distance * * @deprecated use getPlanDistanceSq(int x, int y, int z) */ @Deprecated public final double getDistance(int x, int y, int z) { double dx = x - getX(); double dy = y - getY(); double dz = z - getZ(); return Math.sqrt(dx*dx + dy*dy + dz*dz); } /** * Return the squared distance between the current position of the L2Character and the given object.<BR><BR> * * @param object L2Object * @return the squared distance */ public final double getDistanceSq(L2Object object) { return getDistanceSq(object.getX(), object.getY(), object.getZ()); } /** * Return the squared distance between the current position of the L2Character and the given x, y, z.<BR><BR> * * @param x X position of the target * @param y Y position of the target * @param z Z position of the target * @return the squared distance */ public final double getDistanceSq(int x, int y, int z) { double dx = x - getX(); double dy = y - getY(); double dz = z - getZ(); return (dx*dx + dy*dy + dz*dz); } /** * Return the squared plan distance between the current position of the L2Character and the given object.<BR> * (check only x and y, not z)<BR><BR> * * @param object L2Object * @return the squared plan distance */ public final double getPlanDistanceSq(L2Object object) { return getPlanDistanceSq(object.getX(), object.getY()); } /** * Return the squared plan distance between the current position of the L2Character and the given x, y, z.<BR> * (check only x and y, not z)<BR><BR> * * @param x X position of the target * @param y Y position of the target * @return the squared plan distance */ public final double getPlanDistanceSq(int x, int y) { double dx = x - getX(); double dy = y - getY(); return (dx*dx + dy*dy); } /** * Check if this object is inside the given radius around the given object.<BR><BR> * * @param object the target * @param radius the radius around the target * @param checkZ should we check Z axis also * @param strictCheck true if (distance < radius), false if (distance <= radius) * @return true is the L2Character is inside the radius. * * @see net.sf.l2j.gameserver.model.L2Character.isInsideRadius(int x, int y, int z, int radius, boolean checkZ, boolean strictCheck) */ public final boolean isInsideRadius(L2Object object, int radius, boolean checkZ, boolean strictCheck) { return isInsideRadius(object.getX(), object.getY(), object.getZ(), radius, checkZ, strictCheck); } /** * Check if this object is inside the given plan radius around the given point.<BR><BR> * * @param x X position of the target * @param y Y position of the target * @param radius the radius around the target * @param strictCheck true if (distance < radius), false if (distance <= radius) * @return true is the L2Character is inside the radius. */ public final boolean isInsideRadius(int x, int y, int radius, boolean strictCheck) { return isInsideRadius(x, y, 0, radius, false, strictCheck); } /** * Check if this object is inside the given radius around the given point.<BR><BR> * * @param x X position of the target * @param y Y position of the target * @param z Z position of the target * @param radius the radius around the target * @param checkZ should we check Z axis also * @param strictCheck true if (distance < radius), false if (distance <= radius) * @return true is the L2Character is inside the radius. */ public final boolean isInsideRadius(int x, int y, int z, int radius, boolean checkZ, boolean strictCheck) { double dx = x - getX(); double dy = y - getY(); double dz = z - getZ(); if (strictCheck) { if (checkZ) return (dx*dx + dy*dy + dz*dz) < radius * radius; else return (dx*dx + dy*dy) < radius * radius; } else { if (checkZ) return (dx*dx + dy*dy + dz*dz) <= radius * radius; else return (dx*dx + dy*dy) <= radius * radius; } }// /**// * event that is called when the destination coordinates are reached// */// public void onTargetReached()// {// L2Character pawn = getPawnTarget();//// if (pawn != null)// {// int x = pawn.getX(), y=pawn.getY(),z = pawn.getZ();//// double distance = getDistance(x,y);// if (getCurrentState() == STATE_FOLLOW)// {// calculateMovement(x,y,z,distance);// return;// }//// // takes care of moving away but distance is 0 so i won't follow problem////// if (((distance > getAttackRange()) && (getCurrentState() == STATE_ATTACKING)) || (pawn.isMoving() && getCurrentState() != STATE_ATTACKING))// {// calculateMovement(x,y,z,distance);// return;// }//// }// // update x,y,z with the current calculated position// stopMove();//// if (Config.DEBUG)// _log.fine(this.getName() +":: target reached at: x "+getX()+" y "+getY()+ " z:" + getZ());//// if (getPawnTarget() != null)// {//// setPawnTarget(null);// setMovingToPawn(false);// }// }//// public void setTo(int x, int y, int z, int heading)// {// setX(x);// setY(y);// setZ(z);// setHeading(heading);// updateCurrentWorldRegion(); //TODO: maybe not needed here// if (isMoving())// {// setCurrentState(STATE_IDLE);// StopMove setto = new StopMove(this);// broadcastPacket(setto);// }// else// {// ValidateLocation setto = new ValidateLocation(this);// broadcastPacket(setto);// }//// FinishRotation fr = new FinishRotation(this);// broadcastPacket(fr);// }// protected void startCombat()// {// if (_currentAttackTask == null )//&& !isInCombat())// {// _currentAttackTask = ThreadPoolManager.getInstance().scheduleMed(new AttackTask(), 0);// }// else// {// _log.info("multiple attacks want to start in parallel. prevented.");// }// }// /** * Return the Weapon Expertise Penalty of the L2Character.<BR><BR> */ public float getWeaponExpertisePenalty() { return 1.f; } /** * Return the Armour Expertise Penalty of the L2Character.<BR><BR> */ public float getArmourExpertisePenalty() { return 1.f; } /** * Set _attacking corresponding to Attacking Body part to CHEST.<BR><BR> */ public void setAttackingBodypart() { _attacking = Inventory.PAPERDOLL_CHEST; } /** * Retun True if arrows are available.<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ protected boolean checkAndEquipArrows() { return true; } /** * Add Exp and Sp to the L2Character.<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li> * <li> L2PetInstance</li><BR><BR> * */ public void addExpAndSp(@SuppressWarnings("unused") long addToExp, @SuppressWarnings("unused") int addToSp) { // Dummy method (overridden by players and pets) } /** * Return the active weapon instance (always equiped in the right hand).<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ public abstract L2ItemInstance getActiveWeaponInstance(); /** * Return the active weapon item (always equiped in the right hand).<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ public abstract L2Weapon getActiveWeaponItem(); /** * Return the secondary weapon instance (always equiped in the left hand).<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ public abstract L2ItemInstance getSecondaryWeaponInstance(); /** * Return the secondary weapon item (always equiped in the left hand).<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ public abstract L2Weapon getSecondaryWeaponItem(); /** * Manage hit process (called by Hit Task).<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance)</li> * <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance </li> * <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary </li> * <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...) </li><BR><BR> * * @param target The L2Character targeted * @param damage Nb of HP to reduce * @param crit True if hit is critical * @param miss True if hit is missed * @param soulshot True if SoulShot are charged * @param shld True if shield is efficient * */ protected void onHitTimer(L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, boolean shld) { // If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL // and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance) if (target == null || isAlikeDead() ||(this instanceof L2NpcInstance && ((L2NpcInstance) this).isEventMob)) { getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } if ((this instanceof L2NpcInstance && target.isAlikeDead()) || target.isDead() || (!getKnownList().knowsObject(target) && !(this instanceof L2DoorInstance))) { //getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE, null); getAI().notifyEvent(CtrlEvent.EVT_CANCEL); sendPacket(new ActionFailed()); return; } if (miss) { if (target instanceof L2PcInstance) { SystemMessage sm = new SystemMessage(SystemMessage.AVOIDED_S1s_ATTACK); if (this instanceof L2Summon) { int mobId = ((L2Summon)this).getTemplate().npcId; sm.addNpcName(mobId); } else { sm.addString(getName()); } ((L2PcInstance)target).sendPacket(sm); } } // If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance if (!isAttackAborted()) { // If L2Character attacker is a L2PcInstance, send a system message if (this instanceof L2PcInstance) { // Check if hit is critical if (crit) sendPacket(new SystemMessage(SystemMessage.CRITICAL_HIT)); // Check if hit is missed if (miss) { sendPacket(new SystemMessage(SystemMessage.MISSED_TARGET)); } else if (damage < 1) { //((L2PcInstance)this).sendMessage("You hit the target's armor."); } else { SystemMessage sm = new SystemMessage(SystemMessage.YOU_DID_S1_DMG); sm.addNumber(damage); sendPacket(sm); } } else if (this instanceof L2Summon) { L2Summon activeSummon = (L2Summon)this; // Prevents the double spam of system messages, if the target is the owning player. if (target.getObjectId() != activeSummon.getOwner().getObjectId()) { if (crit) activeSummon.getOwner().sendPacket(new SystemMessage(SystemMessage.PET_CRITICAL_HIT)); if (miss) { activeSummon.getOwner().sendMessage("The pet missed the target."); } else { SystemMessage sm = new SystemMessage(SystemMessage.PET_DID_S1_DMG); sm.addNumber(damage); activeSummon.getOwner().sendPacket(sm); } } } // If L2Character target is a L2PcInstance, send a system message if (target instanceof L2PcInstance) { L2PcInstance enemy = (L2PcInstance)target; // Check if shield is efficient if (shld) enemy.sendPacket(new SystemMessage(SystemMessage.SHIELD_DEFENCE_SUCCESSFULL)); //else if (!miss && damage < 1) //enemy.sendMessage("You hit the target's armor."); } else if (target instanceof L2Summon) { L2Summon activeSummon = (L2Summon)target; SystemMessage sm = new SystemMessage(SystemMessage.PET_RECEIVED_DAMAGE_OF_S2_BY_S1); sm.addString(getName()); sm.addNumber(damage); activeSummon.getOwner().sendPacket(sm); } if (!miss && damage > 0) { L2Weapon weapon = getActiveWeaponItem(); boolean isBow = (weapon != null && weapon.getItemType().toString().equalsIgnoreCase("Bow")); if (!isBow) // Do not reflect or absorb if weapon is of type bow { // Reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary double reflectPercent = target.getStat().calcStat(Stats.REFLECT_DAMAGE_PERCENT,0,null,null); if (reflectPercent > 0) { int reflectedDamage = (int)(reflectPercent / 100. * damage); damage -= reflectedDamage; if(reflectedDamage > target.getMaxHp()) // to prevent extreme damage when hitting a low lvl char... reflectedDamage = target.getMaxHp(); getStatus().reduceHp(reflectedDamage, null, true); // Custom messages - nice but also more network load /* if (target instanceof L2PcInstance) ((L2PcInstance)target).sendMessage("You reflected " + reflectedDamage + " damage."); else if (target instanceof L2Summon) ((L2Summon)target).getOwner().sendMessage("Summon reflected " + reflectedDamage + " damage."); if (this instanceof L2PcInstance) ((L2PcInstance)this).sendMessage("Target reflected to you " + reflectedDamage + " damage."); else if (this instanceof L2Summon) ((L2Summon)this).getOwner().sendMessage("Target reflected to your summon " + reflectedDamage + " damage."); */ } // Absorb HP from the damage inflicted double absorbPercent = this.getStat().calcStat(Stats.ABSORB_DAMAGE_PERCENT,0, null,null); if (absorbPercent > 0) { int maxCanAbsorb = (int)(this.getMaxHp() - this.getCurrentHp()); int absorbDamage = (int)(absorbPercent / 100. * damage); if (absorbDamage > maxCanAbsorb) absorbDamage = maxCanAbsorb; // Can't absord more than max hp if (absorbDamage > 0) { setCurrentHp(getCurrentHp() + absorbDamage); // Custom messages - nice but also more network load /* if (this instanceof L2PcInstance) ((L2PcInstance)this).sendMessage("You absorbed " + absorbDamage + " damage."); else if (this instanceof L2Summon) ((L2Summon)this).getOwner().sendMessage("Summon absorbed " + absorbDamage + " damage."); else if (Config.DEBUG) _log.info(getName() + " absorbed " + absorbDamage + " damage."); */ } } } target.reduceCurrentHp(damage, this); // Notify AI with EVT_ATTACKED target.getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, this); getAI().clientStartAutoAttack(); // Manage attack or cast break of the target (calculating rate, sending message...) if (!target.isRaid() && Formulas.getInstance().calcAtkBreak(target, damage)) { target.breakAttack(); target.breakCast(); } } // Launch weapon Special ability effect if available L2Weapon activeWeapon = getActiveWeaponItem(); if (activeWeapon != null) activeWeapon.getSkillEffects(this, target, crit); // Check Raidboss attack if (target.isRaid()) { int level = 0; if (this instanceof L2PcInstance) level = getLevel(); else if (this instanceof L2Summon) level = ((L2Summon)this).getOwner().getLevel(); if (level > target.getLevel() + 8) { L2Skill skill = SkillTable.getInstance().getInfo(4515, 99); if (skill != null) skill.getEffects(target, this); else _log.warning("Skill 4515 at level 99 is missing in DP."); } } /* COMMENTED OUT BY nexus - 2006-08-17 * * We must not discharge the soulshouts at the onHitTimer method, * as this can cause unwanted soulshout consumption if the attacker * recharges the soulshot right after an attack request but before * his hit actually lands on the target. * * The soulshot discharging has been moved to the doAttack method: * As soon as we know that we didn't missed the hit there, then we * must discharge any charged soulshots. */ /* L2ItemInstance weapon = getActiveWeaponInstance(); if (!miss) { if (this instanceof L2Summon && !(this instanceof L2PetInstance)) { if (((L2Summon)this).getChargedSoulShot() != L2ItemInstance.CHARGED_NONE) ((L2Summon)this).setChargedSoulShot(L2ItemInstance.CHARGED_NONE); } else { if (weapon != null && weapon.getChargedSoulshot() != L2ItemInstance.CHARGED_NONE) weapon.setChargedSoulshot(L2ItemInstance.CHARGED_NONE); } } */ return; } getAI().notifyEvent(CtrlEvent.EVT_CANCEL); } /** * Break an attack and send Server->Client ActionFailed packet and a System Message to the L2Character.<BR><BR> */ public void breakAttack() { if (isAttackingNow()) { // Abort the attack of the L2Character and send Server->Client ActionFailed packet abortAttack(); if (this instanceof L2PcInstance) { //TODO Remove sendPacket because it's always done in abortAttack sendPacket(new ActionFailed()); // Send a system message sendPacket(new SystemMessage(SystemMessage.ATTACK_FAILED)); } } } /** * Break a cast and send Server->Client ActionFailed packet and a System Message to the L2Character.<BR><BR> */ public void breakCast() { if (isCastingNow() && canAbortCast()) { // Abort the cast of the L2Character and send Server->Client MagicSkillCanceld/ActionFailed packet. abortCast(); if (this instanceof L2PcInstance) { // Send a system message sendPacket(new SystemMessage(SystemMessage.CASTING_INTERRUPTED)); } } } /** * Reduce the arrow number of the L2Character.<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ protected void reduceArrowCount() { // default is to do nothin } /** * Manage Forced attack (shift + select target).<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>If L2Character or target is in a town area, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed </li> * <li>If target is confused, send a Server->Client packet ActionFailed </li> * <li>If L2Character is a L2ArtefactInstance, send a Server->Client packet ActionFailed </li> * <li>Send a Server->Client packet MyTargetSelected to start attack and Notify AI with AI_INTENTION_ATTACK </li><BR><BR> * * @param player The L2PcInstance to attack * */ public void onForcedAttack(L2PcInstance player) { if (isInsidePeaceZone(player)) { // If L2Character or target is in a peace zone, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed player.sendPacket(new SystemMessage(SystemMessage.TARGET_IN_PEACEZONE)); player.sendPacket(new ActionFailed()); } else if (player.getTarget() != null && !player.getTarget().isAttackable() && (player.getAccessLevel() < Config.GM_PEACEATTACK)) { // If target is not attackable, send a Server->Client packet ActionFailed player.sendPacket(new ActionFailed()); return; } else if (player.isConfused()) { // If target is confused, send a Server->Client packet ActionFailed player.sendPacket(new ActionFailed()); } else if (this instanceof L2ArtefactInstance) { // If L2Character is a L2ArtefactInstance, send a Server->Client packet ActionFailed player.sendPacket(new ActionFailed()); } else { //_log.config("Not within a zone"); //player.startAttack(this); // Send a Server->Client packet MyTargetSelected to start attack player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel() - getLevel())); // Notify AI with AI_INTENTION_ATTACK player.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, this); } } /** * Return True if inside peace zone.<BR><BR> */ public boolean isInsidePeaceZone(L2PcInstance attacker) { return isInsidePeaceZone(attacker, this); } public boolean isInsidePeaceZone(L2PcInstance attacker, L2Object target) { return ( (attacker.getAccessLevel() < Config.GM_PEACEATTACK) && isInsidePeaceZone((L2Object)attacker, target) ); } public boolean isInsidePeaceZone(L2Object attacker, L2Object target) { return ( target != null && !(target instanceof L2MonsterInstance) && // Target is not a monster and ( // ( ZoneManager.getInstance().checkIfInZonePeace(attacker) || // Player is inside peace zone or ZoneManager.getInstance().checkIfInZonePeace(target) // Target is inside peace zone ) && // ) and ( // ( !(target instanceof L2PcInstance) || // Target is not a player or (!Config.ALT_GAME_KARMA_PLAYER_CAN_BE_KILLED_IN_PEACEZONE) || // Target is a player and killable in peace zone is false or (((L2PcInstance)target).getKarma() <= 0) // Target is a player, killable in peace zone is true, and has no karma ) // ) ); } /** * return true if this character is inside an active grid. */ public Boolean isInActiveRegion() { L2WorldRegion region = L2World.getInstance().getRegion(getX(),getY()); return ((region !=null) && (region.isActive())); } /** * Return True if the L2Character has a Party in progress.<BR><BR> */ public boolean isInParty() { return false; } /** * Return the L2Party object of the L2Character.<BR><BR> */ public L2Party getParty() { return null; } /** * Return the Attack Speed of the L2Character (delay (in milliseconds) before next attack).<BR><BR> */ public int calculateAttackSpeed(L2Character target, @SuppressWarnings("unused") L2ItemInstance weaponInst) { double atkSpd = 0; if (this instanceof L2Summon) { atkSpd = getPAtkSpd(); } else { L2Weapon weapon = this.getActiveWeaponItem(); if (weapon !=null) { switch (weapon.getItemType()) { case DAGGER: atkSpd = getStat().getPAtkSpd(); atkSpd /= 1.15; break; default: atkSpd = getStat().getPAtkSpd(); } } } return Formulas.getInstance().calcPAtkSpd(this, target, atkSpd); } /** * Return True if the L2Character use a dual weapon.<BR><BR> */ public boolean isUsingDualWeapon() { return false; } /** * Add a skill to the L2Character _skills and its Func objects to the calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills own by a L2Character are identified in <B>_skills</B><BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Replace oldSkill by newSkill or Add the newSkill </li> * <li>If an old skill has been replaced, remove all its Func objects of L2Character calculator set</li> * <li>Add Func objects of newSkill to the calculator set of the L2Character </li><BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance : Save update in the character_skills table of the database</li><BR><BR> * * @param newSkill The L2Skill to add to the L2Character * * @return The L2Skill replaced or null if just added a new L2Skill * */ public L2Skill addSkill(L2Skill newSkill) { L2Skill oldSkill = null; if (newSkill != null) { // Replace oldSkill by newSkill or Add the newSkill oldSkill = _Skills.put(newSkill.getId(), newSkill); // If an old skill has been replaced, remove all its Func objects if (oldSkill != null) removeStatsOwner(oldSkill); // Add Func objects of newSkill to the calculator set of the L2Character addStatFuncs(newSkill.getStatFuncs(null, this)); } return oldSkill; } /** * Remove a skill from the L2Character and its Func objects from calculator set of the L2Character.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills own by a L2Character are identified in <B>_skills</B><BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Remove the skill from the L2Character _skills </li> * <li>Remove all its Func objects from the L2Character calculator set</li><BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance : Save update in the character_skills table of the database</li><BR><BR> * * @param skill The L2Skill to remove from the L2Character * * @return The L2Skill removed * */ public L2Skill removeSkill(L2Skill skill) { if (skill == null) return null; // Remove the skill from the L2Character _skills L2Skill oldSkill = _Skills.remove(skill.getId()); // Remove all its Func objects from the L2Character calculator set if (oldSkill != null) removeStatsOwner(oldSkill); return oldSkill; } /** * Return all skills own by the L2Character in a table of L2Skill.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills own by a L2Character are identified in <B>_skills</B> the L2Character <BR><BR> * */ public final L2Skill[] getAllSkills() { if (_Skills == null) return new L2Skill[0]; return _Skills.values().toArray(new L2Skill[_Skills.values().size()]); } /** * Return the level of a skill owned by the L2Character.<BR><BR> * * @param skillId The identifier of the L2Skill whose level must be returned * * @return The level of the L2Skill identified by skillId * */ public int getSkillLevel(int skillId) { if (_Skills == null) return -1; L2Skill skill = _Skills.get(skillId); if (skill == null) return -1; return skill.getLevel(); } /** * Return True if the skill is known by the L2Character.<BR><BR> * * @param skillId The identifier of the L2Skill to check the knowledge * */ public final L2Skill getKnownSkill(int skillId) { if (_Skills == null) return null; return _Skills.get(skillId); } /** * Return the number of skills of type(Buff, Debuff, HEAL_PERCENT, MANAHEAL_PERCENT) affecting this L2Character.<BR><BR> * * @return The number of Buffs affecting this L2Character */ public int getBuffCount() { L2Effect[] effects = this.getAllEffects(); int numBuffs=0; if (effects != null) { for (L2Effect e : effects) { if (e != null) { if ((e.getSkill().getSkillType() == L2Skill.SkillType.BUFF || e.getSkill().getSkillType() == L2Skill.SkillType.DEBUFF || e.getSkill().getSkillType() == L2Skill.SkillType.REFLECT || e.getSkill().getSkillType() == L2Skill.SkillType.HEAL_PERCENT || e.getSkill().getSkillType() == L2Skill.SkillType.MANAHEAL_PERCENT) && e.getSkill().getId() != 4267 && e.getSkill().getId() != 4270 && !(e.getSkill().getId() > 4360 && e.getSkill().getId() < 4367)) { // 7s buffs numBuffs++; } } } } return numBuffs; } /** * Removes the first Buff of this L2Character.<BR><BR> * * @param preferSkill If != 0 the given skill Id will be removed instead of first */ public void removeFirstBuff(int preferSkill) { L2Effect[] effects = this.getAllEffects(); L2Effect removeMe=null; if (effects != null) { for (L2Effect e : effects) { if (e != null) { if ((e.getSkill().getSkillType() == L2Skill.SkillType.BUFF || e.getSkill().getSkillType() == L2Skill.SkillType.DEBUFF || e.getSkill().getSkillType() == L2Skill.SkillType.REFLECT || e.getSkill().getSkillType() == L2Skill.SkillType.HEAL_PERCENT || e.getSkill().getSkillType() == L2Skill.SkillType.MANAHEAL_PERCENT) && e.getSkill().getId() != 4267 && e.getSkill().getId() != 4270 && !(e.getSkill().getId() > 4360 && e.getSkill().getId() < 4367)) { if (preferSkill == 0) { removeMe=e; break; } else if (e.getSkill().getId() == preferSkill) { removeMe=e; break; } else if (removeMe==null) removeMe=e; } } } } if (removeMe != null) removeMe.exit(); } public int getDanceCount() { int danceCount = 0; L2Effect[] effects = getAllEffects(); for (L2Effect effect : effects) { if (effect == null) continue; if (effect.getSkill().isDance() && effect.getInUse()) danceCount++; } return danceCount; } /** * Checks if the given skill stacks with an existing one.<BR><BR> * * @param checkSkill the skill to be checked * * @return Returns whether or not this skill will stack */ public boolean doesStack(L2Skill checkSkill) { if (_effects == null || _effects.size() < 1 || checkSkill._effectTemplates == null || checkSkill._effectTemplates.length < 1 || checkSkill._effectTemplates[0]._stackType == null) return false; String stackType=checkSkill._effectTemplates[0]._stackType; if (stackType.equals("none")) return false; for (int i=0; i<_effects.size(); i++) { if (_effects.get(i).getStackType() != null && _effects.get(i).getStackType().equals(stackType)) return true; } return false; } /** * Manage the magic skill launching task (MP, HP, Item consummation...) and display the magic skill animation on client.<BR><BR> * * <B><U> Actions</U> :</B><BR><BR> * <li>Send a Server->Client packet MagicSkillLaunched (to display magic skill animation) to all L2PcInstance of L2Charcater _knownPlayers</li> * <li>Consumme MP, HP and Item if necessary</li> * <li>Send a Server->Client packet StatusUpdate with MP modification to the L2PcInstance</li> * <li>Launch the magic skill in order to calculate its effects</li> * <li>If the skill type is PDAM, notify the AI of the target with AI_INTENTION_ATTACK</li> * <li>Notify the AI of the L2Character with EVT_FINISH_CASTING</li><BR><BR> * * <FONT COLOR=#FF0000><B> <U>Caution</U> : A magic skill casting MUST BE in progress</B></FONT><BR><BR> * * @param skill The L2Skill to use * */ public void onMagicUseTimer(L2Object[] targets, L2Skill skill) { if (skill == null || targets == null || targets.length <= 0) { setAttackingChar(null); setAttackingCharSkill(null); getAI().notifyEvent(CtrlEvent.EVT_CANCEL); return; } // Escaping from under skill's radius. First version, not perfect in AoE skills. int escapeRange = 0; if(skill.getEffectRange() > escapeRange) escapeRange = skill.getEffectRange(); else if(skill.getCastRange() < 0 && skill.getSkillRadius() > 80) escapeRange = skill.getSkillRadius(); if(escapeRange > 0) { List<L2Character> targetList = new FastList<L2Character>(); for (int i = 0; i < targets.length; i++) { if (targets[i] instanceof L2Character) { if(!this.isInsideRadius(targets[i],escapeRange,true,false)) continue; else targetList.add((L2Character)targets[i]); } //else //{ // if (Config.DEBUG) // _log.warning("Class cast bad: "+targets[i].getClass().toString()); //} } if(targetList.isEmpty()) { abortCast(); return; } else targets = targetList.toArray(new L2Character[targetList.size()]); } // Check if player is using fake death. if (isAlikeDead()) { setAttackingChar(null); setAttackingCharSkill(null); getAI().notifyEvent(CtrlEvent.EVT_CANCEL); _castEndTime = 0; _castInterruptTime = 0; return; } // Check if a cast is in progress if (isCastingNow()) { // Get the display identifier of the skill int magicId = skill.getDisplayId(); // Get the level of the skill int level = getSkillLevel(skill.getId()); if (level < 1) level = 1; // Go through targets table for (int i = 0;i < targets.length;i++) { if (targets[i] instanceof L2Character) { L2Character target = (L2Character) targets[i]; if (skill.getSkillType() == L2Skill.SkillType.BUFF || skill.getSkillType() == L2Skill.SkillType.SEED) { SystemMessage smsg = new SystemMessage(SystemMessage.YOU_FEEL_S1_EFFECT); smsg.addString(skill.getName()); target.sendPacket(smsg); } if (Config.DEBUG) _log.fine("msl: "+getName()+" "+magicId+" "+level+" "+target.getTitle()); // Send a Server->Client packet MagicSkillLaunched to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character broadcastPacket(new MagicSkillLaunched(this, magicId, level, target)); if (this instanceof L2PcInstance && target instanceof L2Summon) { if (this.equals(((L2Summon)target).getOwner())) this.sendPacket(new PetInfo((L2Summon)target)); else this.sendPacket(new NpcInfo((L2Summon)target, this)); } } } StatusUpdate su = new StatusUpdate(getObjectId()); boolean isSendStatus = false; // Consume MP of the L2Character and Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform if (getStat().getMpConsume(skill) > 0) { getStatus().reduceMp(calcStat(Stats.MP_CONSUME_RATE,getStat().getMpConsume(skill),null,null)); su.addAttribute(StatusUpdate.CUR_MP, (int) getCurrentMp()); isSendStatus = true; } // Consume HP if necessary and Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform if (skill.getHpConsume() > 0) { double consumeHp; consumeHp = calcStat(Stats.HP_CONSUME_RATE,skill.getHpConsume(),null,null); if(consumeHp+1 >= getCurrentHp()) consumeHp = getCurrentHp()-1.0; getStatus().reduceHp(consumeHp, this); su.addAttribute(StatusUpdate.CUR_HP, (int) getCurrentHp()); isSendStatus = true; } // Send a Server->Client packet StatusUpdate with MP modification to the L2PcInstance if (isSendStatus) sendPacket(su); // Consume Items if necessary and Send the Server->Client packet InventoryUpdate with Item modification to all the L2Character if (skill.getItemConsume() > 0) consumeItem(skill.getItemConsumeId(), skill.getItemConsume()); _castEndTime = 0; _castInterruptTime = 0; _skillCast = null; // Launch the magic skill in order to calculate its effects callSkill(skill, targets); //if the skill has changed the character's state to something other than STATE_CASTING //then just leave it that way, otherwise switch back to STATE_IDLE. //if(isCastingNow()) // getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE, null); // If the skill type is PDAM or DRAIN_SOUL, notify the AI of the target with AI_INTENTION_ATTACK if (skill.getSkillType() == SkillType.PDAM || skill.getSkillType() == SkillType.DRAIN_SOUL) { if ((getTarget() != null) && (getTarget() instanceof L2Character)) getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, getTarget()); } if (skill.isOffensive()) getAI().clientStartAutoAttack(); // Notify the AI of the L2Character with EVT_FINISH_CASTING getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING); /* * If character is a player, then wipe their current cast state and * check if a skill is queued. * * If there is a queued skill, launch it and wipe the queue. */ if (this instanceof L2PcInstance) { L2PcInstance currPlayer = (L2PcInstance)this; SkillDat queuedSkill = currPlayer.getQueuedSkill(); currPlayer.setCurrentSkill(null, false, false); if (queuedSkill != null) { currPlayer.setQueuedSkill(null, false, false); // DON'T USE : Recursive call to useMagic() method // currPlayer.useMagic(queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed()); ThreadPoolManager.getInstance().executeTask(new QueuedMagicUseTask(currPlayer, queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed()) ); } } } } /** * Reduce the item number of the L2Character.<BR><BR> * * <B><U> Overriden in </U> :</B><BR><BR> * <li> L2PcInstance</li><BR><BR> * */ public void consumeItem(@SuppressWarnings("unused") int itemConsumeId, @SuppressWarnings("unused") int itemCount) { } /** * Enable a skill (remove it from _disabledSkills of the L2Character).<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills disabled are identified by their skillId in <B>_disabledSkills</B> of the L2Character <BR><BR> * * @param skillId The identifier of the L2Skill to enable * */ public void enableSkill(int skillId) { if (_disabledSkills == null) return; synchronized (_disabledSkills) { _disabledSkills.remove(new Integer(skillId)); if (this instanceof L2PcInstance) removeTimeStamp(skillId); } } /** * Disable a skill (add it to _disabledSkills of the L2Character).<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills disabled are identified by their skillId in <B>_disabledSkills</B> of the L2Character <BR><BR> * * @param skillId The identifier of the L2Skill to disable * */ public void disableSkill(int skillId) { if (_disabledSkills == null) _disabledSkills = Collections.synchronizedList(new FastList<Integer>()); _disabledSkills.add(skillId); } /** * Disable this skill id for the duration of the delay in milliseconds. * @param skillId * @param delay (seconds * 1000) */ public void disableSkill(int skillId, long delay) { disableSkill(skillId); if (delay > 10) ThreadPoolManager.getInstance().scheduleAi(new EnableSkill(skillId), delay); } /** * Check if a skill is disabled.<BR><BR> * * <B><U> Concept</U> :</B><BR><BR> * All skills disabled are identified by their skillId in <B>_disabledSkills</B> of the L2Character <BR><BR> * * @param skillId The identifier of the L2Skill to disable * */ public boolean isSkillDisabled(int skillId) { if (isAllSkillsDisabled()) return true; if (_disabledSkills == null) return false; return _disabledSkills.contains(skillId); } /** * Disable all skills (set _allSkillsDisabled to True).<BR><BR> */ public void disableAllSkills() { if (Config.DEBUG) _log.fine("all skills disabled"); _allSkillsDisabled = true; } /** * Enable all skills (set _allSkillsDisabled to False).<BR><BR> */ public void enableAllSkills() { if (Config.DEBUG) _log.fine("all skills enabled"); _allSkillsDisabled = false; } /** * Launch the magic skill and calculate its effects on each target contained in the targets table.<BR><BR> * * @param skill The L2Skill to use * @param targets The table of L2Object targets * */ public void callSkill(L2Skill skill, L2Object[] targets) { try { // Do initial checkings for skills and set pvp flag/draw aggro when needed for (L2Object target : targets) { if (target instanceof L2Character) { // Set some values inside target's instance for later use L2Character player = (L2Character) target; L2Weapon activeWeapon = getActiveWeaponItem(); // Launch weapon Special ability skill effect if available if (activeWeapon != null && !((L2Character)target).isDead()) { if (activeWeapon.getSkillEffects(this, player, skill).length > 0 && this instanceof L2PcInstance) this.sendPacket(SystemMessage.sendString("Target affected by weapon special ability!")); } // Check Raidboss attack if (player.isRaid() && getLevel() > player.getLevel() + 8) { L2Skill tempSkill = SkillTable.getInstance().getInfo(4515, 99); if(tempSkill != null) { tempSkill.getEffects(player, this); } else { _log.warning("Skill 4515 at level 99 is missing in DP."); } } L2PcInstance activeChar = null; if (this instanceof L2PcInstance) activeChar = (L2PcInstance)this; else if (this instanceof L2Summon) activeChar = ((L2Summon)this).getOwner(); if (activeChar != null) { if (skill.isOffensive()) { if ((player instanceof L2PcInstance || player instanceof L2Summon) && activeChar.checkIfPvP(player)) { player.getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, activeChar); activeChar.updatePvPStatus(player); }else if (player instanceof L2Attackable) { // notify the AI that she is attacked player.getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, activeChar); } } else { if (player instanceof L2PcInstance) { // Casting non offensive skill on player with pvp flag set or with karma if (!player.equals(this) && (((L2PcInstance)player).getPvpFlag() > 0 || ((L2PcInstance)player).getKarma() > 0)) activeChar.updatePvPStatus(); } else if (player instanceof L2Attackable && !(skill.getSkillType() == L2Skill.SkillType.SUMMON)) activeChar.updatePvPStatus(); } } } } ISkillHandler handler = null; // TODO Remove this useless section if(skill.isToggle()) { // Check if the skill effects are already in progress on the L2Character if(getEffect(skill.getId()) != null) { handler = SkillHandler.getInstance().getSkillHandler(skill.getSkillType()); if (handler != null) handler.useSkill(this, skill, targets); else skill.useSkill(this, targets); return; } } // Check if over-hit is possible if(skill.isOverhit()) { // Set the "over-hit enabled" flag on each of the possible targets for (L2Object target : targets) { L2Character player = (L2Character) target; if(player instanceof L2Attackable) { ((L2Attackable)player).overhitEnabled(true); } } } // Get the skill handler corresponding to the skill type (PDAM, MDAM, SWEEP...) started in gameserver handler = SkillHandler.getInstance().getSkillHandler(skill.getSkillType()); // Launch the magic skill and calculate its effects if (handler != null) handler.useSkill(this, skill, targets); else skill.useSkill(this, targets); } catch (Exception e) { _log.log(Level.WARNING, "", e); } } /** * Return True if the L2Character is behind the target and can't be seen.<BR><BR> */ public boolean isBehindTarget() { double angleChar, angleTarget, angleDiff, maxAngleDiff = 45; if(getTarget() == null) return false; if (getTarget() instanceof L2Character) { L2Character target = (L2Character) getTarget(); angleChar = Util.calculateAngleFrom(target, this); angleTarget = Util.convertHeadingToDegree(target.getHeading()); angleDiff = angleChar - angleTarget; if (angleDiff <= -360 + maxAngleDiff) angleDiff += 360; if (angleDiff >= 360 - maxAngleDiff) angleDiff -= 360; if (Math.abs(angleDiff) <= maxAngleDiff) { if (Config.DEBUG) _log.info("Char " + this.getName() + " is behind " + target.getName()); return true; } } else { _log.fine("isBehindTarget's target not an L2 Character."); } return false; } /** * Return 1.<BR><BR> */ public double getLevelMod() { return 1; } public final void setSkillCast(Future newSkillCast) { _skillCast = newSkillCast; } public final void setSkillCastEndTime(int newSkillCastEndTime) { _castEndTime = newSkillCastEndTime; } /** * Not Implemented.<BR><BR> */ public void updateStats() { } private Future _PvPRegTask; private long _lastPvpAttack; public void setlastPvpAttack(long time) { _lastPvpAttack = time; } public long getlastPvpAttack() { return _lastPvpAttack; } public void startPvPFlag() { updatePvPFlag(1); _PvPRegTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate( new PvPFlag(), 1000, 1000); } public void stopPvPFlag() { if (_PvPRegTask != null) _PvPRegTask.cancel(false); updatePvPFlag(0); _PvPRegTask = null; } public void updatePvPFlag(int value) { if (!(this instanceof L2PcInstance)) return; L2PcInstance player = (L2PcInstance)this; if (player.getPvpFlag() == value) return; player.setPvpFlag(value); if (player.getKarma() < 1) { StatusUpdate su = new StatusUpdate(getObjectId()); su.addAttribute(StatusUpdate.PVP_FLAG, value); broadcastPacket(su); } else return; }// public void checkPvPFlag()// {// if (Config.DEBUG) _log.fine("Checking PvpFlag");// _PvPRegTask = ThreadPoolManager.getInstance().scheduleLowAtFixedRate(// new PvPFlag(), 1000, 5000);// _PvPRegActive = true;// // _log.fine("PvP recheck");// }// /** * Return a Random Damage in function of the weapon.<BR><BR> */ public final int getRandomDamage(@SuppressWarnings("unused") L2Character target) { L2Weapon weaponItem = getActiveWeaponItem(); if (weaponItem == null) return 5+(int)Math.sqrt(getLevel()); return weaponItem.getRandomDamage(); } public String toString() { return "mob "+this.getObjectId(); } public int getAttackEndTime() { return _attackEndTime; } /** * Not Implemented.<BR><BR> */ public abstract int getLevel(); // ========================================================= // ========================================================= // Stat - NEED TO REMOVE ONCE L2CHARSTAT IS COMPLETE // Property - Public public final double calcStat(Stats stat, double init, L2Character target, L2Skill skill) { return getStat().calcStat(stat, init, target, skill); } // Property - Public public int getAccuracy() { return getStat().getAccuracy(); } public final float getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } public int getCON() { return getStat().getCON(); } public int getDEX() { return getStat().getDEX(); } public final double getCriticalDmg(L2Character target, double init) { return getStat().getCriticalDmg(target, init); } public int getCriticalHit(L2Character target, L2Skill skill) { return getStat().getCriticalHit(target, skill); } public int getEvasionRate(L2Character target) { return getStat().getEvasionRate(target); } public int getINT() { return getStat().getINT(); } public final int getMagicalAttackRange(L2Skill skill) { return getStat().getMagicalAttackRange(skill); } public final int getMaxCp() { return getStat().getMaxCp(); } public int getMAtk(L2Character target, L2Skill skill) { return getStat().getMAtk(target, skill); } public int getMAtkSpd() { return getStat().getMAtkSpd(); } public int getMaxMp() { return getStat().getMaxMp(); } public int getMaxHp() { return getStat().getMaxHp(); } public final int getMCriticalHit(L2Character target, L2Skill skill) { return getStat().getMCriticalHit(target, skill); } public int getMDef(L2Character target, L2Skill skill) { return getStat().getMDef(target, skill); } public int getMEN() { return getStat().getMEN(); } public double getMReuseRate(L2Skill skill) { return getStat().getMReuseRate(skill); } public float getMovementSpeedMultiplier() { return getStat().getMovementSpeedMultiplier(); } public int getPAtk(L2Character target) { return getStat().getPAtk(target); } public double getPAtkAnimals(L2Character target) { return getStat().getPAtkAnimals(target); } public double getPAtkDragons(L2Character target) { return getStat().getPAtkDragons(target); } public double getPAtkInsects(L2Character target) { return getStat().getPAtkInsects(target); } public double getPAtkMonsters(L2Character target) { return getStat().getPAtkMonsters(target); } public double getPAtkPlants(L2Character target) { return getStat().getPAtkPlants(target); } public int getPAtkSpd() { return getStat().getPAtkSpd(); } public double getPAtkUndead(L2Character target) { return getStat().getPAtkUndead(target); } public double getPDefUndead(L2Character target) { return getStat().getPDefUndead(target); } public int getPDef(L2Character target) { return getStat().getPDef(target); } public final int getPhysicalAttackRange() { return getStat().getPhysicalAttackRange(); } public int getRunSpeed() { return getStat().getRunSpeed(); } public final int getShldDef() { return getStat().getShldDef(); } public int getSTR() { return getStat().getSTR(); } public final int getWalkSpeed() { return getStat().getWalkSpeed(); } public int getWIT() { return getStat().getWIT(); } // ========================================================= // ========================================================= // Status - NEED TO REMOVE ONCE L2CHARTATUS IS COMPLETE // Method - Public public void addStatusListener(L2Character object) { getStatus().addStatusListener(object); } public void reduceCurrentHp(double i, L2Character attacker) { reduceCurrentHp(i, attacker, true); } public void reduceCurrentHp(double i, L2Character attacker, boolean awake) { getStatus().reduceHp(i, attacker, awake); } public void reduceCurrentMp(double i) { getStatus().reduceMp(i); } public void removeStatusListener(L2Character object) { getStatus().removeStatusListener(object); } protected synchronized void stopHpMpRegeneration() { getStatus().stopHpMpRegeneration(); } // Property - Public public final double getCurrentCp() { return getStatus().getCurrentCp(); } public final void setCurrentCp(Double newCp) { setCurrentCp((double) newCp); } public final void setCurrentCp(double newCp) { getStatus().setCurrentCp(newCp); } public final double getCurrentHp() { return getStatus().getCurrentHp(); } public final void setCurrentHp(double newHp) { getStatus().setCurrentHp(newHp); } public final void setCurrentHpMp(double newHp, double newMp){ getStatus().setCurrentHpMp(newHp, newMp); } public final double getCurrentMp() { return getStatus().getCurrentMp(); } public final void setCurrentMp(Double newMp) { setCurrentMp((double)newMp); } public final void setCurrentMp(double newMp) { getStatus().setCurrentMp(newMp); } // ========================================================= public void setAiClass(String aiClass) { _aiClass = aiClass; } public String getAiClass() { return _aiClass; } public L2Character getLastBuffer() { return _LastBuffer; } public int getLastHealAmount() { return _LastHealAmount; } public void setLastBuffer(L2Character buffer) { _LastBuffer = buffer; } public void setLastHealAmount(int hp) { _LastHealAmount = hp; } /** * Check if character reflected skill * @param skill * @return */ public boolean reflectSkill(L2Skill skill) { double reflect = calcStat(skill.isMagic() ? Stats.REFLECT_SKILL_MAGIC : Stats.REFLECT_SKILL_PHYSIC, 0, null, null); if( Rnd.get(100) < reflect) return true; return false; }}