/* * Copyright (C) 2004-2013 L2J Server * * This file is part of L2J Server. * * L2J Server 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 3 of the License, or * (at your option) any later version. * * L2J Server 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, see . */ package com.l2jserver.gameserver.model.skills; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastMap; import com.l2jserver.Config; import com.l2jserver.gameserver.GeoData; import com.l2jserver.gameserver.datatables.ItemTable; import com.l2jserver.gameserver.datatables.SkillTable; import com.l2jserver.gameserver.datatables.SkillTreesData; import com.l2jserver.gameserver.handler.ITargetTypeHandler; import com.l2jserver.gameserver.handler.TargetHandler; import com.l2jserver.gameserver.model.ChanceCondition; import com.l2jserver.gameserver.model.L2ExtractableProductItem; import com.l2jserver.gameserver.model.L2ExtractableSkill; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.PcCondOverride; import com.l2jserver.gameserver.model.StatsSet; import com.l2jserver.gameserver.model.actor.L2Attackable; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Playable; import com.l2jserver.gameserver.model.actor.L2Summon; import com.l2jserver.gameserver.model.actor.instance.L2CubicInstance; import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jserver.gameserver.model.conditions.Condition; import com.l2jserver.gameserver.model.effects.EffectTemplate; import com.l2jserver.gameserver.model.effects.L2Effect; import com.l2jserver.gameserver.model.effects.L2EffectType; import com.l2jserver.gameserver.model.entity.TvTEvent; import com.l2jserver.gameserver.model.holders.ItemHolder; import com.l2jserver.gameserver.model.interfaces.IChanceSkillTrigger; import com.l2jserver.gameserver.model.items.L2Armor; import com.l2jserver.gameserver.model.items.type.L2ArmorType; import com.l2jserver.gameserver.model.skills.funcs.Func; import com.l2jserver.gameserver.model.skills.funcs.FuncTemplate; import com.l2jserver.gameserver.model.skills.targets.L2TargetType; import com.l2jserver.gameserver.model.stats.BaseStats; import com.l2jserver.gameserver.model.stats.Env; import com.l2jserver.gameserver.model.stats.Formulas; import com.l2jserver.gameserver.model.stats.Stats; import com.l2jserver.gameserver.model.zone.ZoneId; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; import com.l2jserver.gameserver.util.Util; public abstract class L2Skill implements IChanceSkillTrigger { protected static final Logger _log = Logger.getLogger(L2Skill.class.getName()); private static final L2Object[] _emptyTargetList = new L2Object[0]; public static final int SKILL_CREATE_DWARVEN = 172; public static final int SKILL_EXPERTISE = 239; public static final int SKILL_CRYSTALLIZE = 248; public static final int SKILL_CLAN_LUCK = 390; public static final int SKILL_ONYX_BEAST_TRANSFORMATION = 617; public static final int SKILL_CREATE_COMMON = 1320; public static final int SKILL_DIVINE_INSPIRATION = 1405; public static final int SKILL_NPC_RACE = 4416; public static final boolean geoEnabled = Config.GEODATA > 0; // conditional values public static final int COND_RUNNING = 0x0001; public static final int COND_WALKING = 0x0002; public static final int COND_SIT = 0x0004; public static final int COND_BEHIND = 0x0008; public static final int COND_CRIT = 0x0010; public static final int COND_LOWHP = 0x0020; public static final int COND_ROBES = 0x0040; public static final int COND_CHARGES = 0x0080; public static final int COND_SHIELD = 0x0100; private static final Func[] _emptyFunctionSet = new Func[0]; private static final L2Effect[] _emptyEffectSet = new L2Effect[0]; // these two build the primary key private final int _id; private final int _level; /** Custom skill Id displayed by the client. */ private final int _displayId; /** Custom skill level displayed by the client. */ private final int _displayLevel; // not needed, just for easier debug private final String _name; private final L2SkillOpType _operateType; private final int _magic; private final L2TraitType _traitType; private final boolean _staticReuse; private final boolean _staticDamage; // Damage dealing skills do static damage based on the power value. private final int _mpConsume; private final int _mpInitialConsume; private final int _hpConsume; private final int _cpConsume; private final int _targetConsume; private final int _targetConsumeId; private final int _itemConsume; private final int _itemConsumeId; private final int _castRange; private final int _effectRange; // Abnormal levels for skills and their canceling, e.g. poison vs negate private final int _abnormalLvl; // e.g. poison or bleed lvl 2 // Note: see also _effectAbnormalLvl private final Map _negateAbnormals; // lists the effect abnormal types with order below the presented that are canceled private final int _maxNegatedEffects; // maximum number of effects to negate private final boolean _stayAfterDeath; // skill should stay after death private final boolean _stayOnSubclassChange; // skill should stay on subclass change // kill by damage over time private final boolean _killByDOT; private final int _refId; // all times in milliseconds private final int _hitTime; private final int[] _hitTimings; // private final int _skillInterruptTime; private final int _coolTime; private final int _reuseHashCode; private final int _reuseDelay; /** Target type of the skill : SELF, PARTY, CLAN, PET... */ private final L2TargetType _targetType; private final int _feed; // base success chance private final double _power; private final double _pvpPower; private final double _pvePower; private final int _magicLevel; private final int _lvlBonusRate; private final int _minChance; private final int _maxChance; private final int _blowChance; // Effecting area of the skill, in radius. // The radius center varies according to the _targetType: // "caster" if targetType = AURA/PARTY/CLAN or "target" if targetType = AREA private final int _affectRange; private final int[] _affectLimit = new int[2]; private final L2SkillType _skillType; private final L2SkillType _effectType; // additional effect has a type private final int _effectAbnormalLvl; // abnormal level for the additional effect type, e.g. poison lvl 1 private final int _effectId; private final int _effectLvl; // normal effect level private final boolean _nextActionIsAttack; private final boolean _removedOnAnyActionExceptMove; private final boolean _removedOnDamage; private final byte _element; private final int _elementPower; private final Stats _stat; private final BaseStats _saveVs; private final int _condition; private final int _conditionValue; private final boolean _overhit; private final int _weaponsAllowed; private final int _armorsAllowed; private final int _minPledgeClass; private final boolean _isOffensive; private final boolean _isPVP; private final int _chargeConsume; private final int _triggeredId; private final int _triggeredLevel; private final String _chanceType; private final int _soulMaxConsume; private final int _numSouls; private final int _expNeeded; private final int _critChance; private final boolean _dependOnTargetBuff; private final int _transformId; private final int _transformDuration; private final int _afterEffectId; private final int _afterEffectLvl; private final boolean _isHeroSkill; // If true the skill is a Hero Skill private final boolean _isGMSkill; // True if skill is GM skill private final boolean _isSevenSigns; private final int _baseCritRate; // percent of success for skill critical hit (especially for PDAM & BLOW - they're not affected by rCrit values or buffs). Default loads -1 for all other skills but 0 to PDAM & BLOW private final int _halfKillRate; private final int _lethalStrikeRate; private final boolean _directHpDmg; // If true then dmg is being make directly private final boolean _isTriggeredSkill; // If true the skill will take activation buff slot instead of a normal buff slot private final float _sSBoost; // If true skill will have SoulShot boost (power*2) private final int _aggroPoints; protected List _preCondition; protected List _itemPreCondition; protected FuncTemplate[] _funcTemplates; public EffectTemplate[] _effectTemplates; protected EffectTemplate[] _effectTemplatesSelf; protected EffectTemplate[] _effectTemplatesPassive; protected ChanceCondition _chanceCondition = null; // Flying support private final String _flyType; private final int _flyRadius; private final float _flyCourse; private final boolean _isDebuff; private final String _attribute; private final boolean _ignoreShield; private final boolean _isSuicideAttack; private final boolean _canBeReflected; private final boolean _canBeDispeled; private final boolean _isClanSkill; private final boolean _excludedFromCheck; private final boolean _simultaneousCast; private L2ExtractableSkill _extractableItems = null; private final int _maxTargets; private int _npcId = 0; private byte[] _effectTypes; protected L2Skill(StatsSet set) { _id = set.getInteger("skill_id"); _level = set.getInteger("level"); _refId = set.getInteger("referenceId", 0); _displayId = set.getInteger("displayId", _id); _displayLevel = set.getInteger("displayLevel", _level); _name = set.getString("name", ""); _operateType = set.getEnum("operateType", L2SkillOpType.class); _magic = set.getInteger("isMagic", 0); _traitType = set.getEnum("trait", L2TraitType.class, L2TraitType.NONE); _staticReuse = set.getBool("staticReuse", false); _staticDamage = set.getBool("staticDamage", false); _mpConsume = set.getInteger("mpConsume", 0); _mpInitialConsume = set.getInteger("mpInitialConsume", 0); _hpConsume = set.getInteger("hpConsume", 0); _cpConsume = set.getInteger("cpConsume", 0); _targetConsume = set.getInteger("targetConsumeCount", 0); _targetConsumeId = set.getInteger("targetConsumeId", 0); _itemConsume = set.getInteger("itemConsumeCount", 0); _itemConsumeId = set.getInteger("itemConsumeId", 0); _afterEffectId = set.getInteger("afterEffectId", 0); _afterEffectLvl = set.getInteger("afterEffectLvl", 1); _castRange = set.getInteger("castRange", -1); _effectRange = set.getInteger("effectRange", -1); _abnormalLvl = set.getInteger("abnormalLvl", -1); _effectAbnormalLvl = set.getInteger("effectAbnormalLvl", -1); // support for a separate effect abnormal lvl, e.g. poison inside a different skill _attribute = set.getString("attribute", ""); String negateAbnormals = set.getString("negateAbnormals", null); if ((negateAbnormals != null) && !negateAbnormals.isEmpty()) { _negateAbnormals = new FastMap<>(); for (String ngtStack : negateAbnormals.split(";")) { String[] ngt = ngtStack.split(","); if (ngt.length == 1) // Only abnormalType is present, without abnormalLvl { _negateAbnormals.put(ngt[0], Byte.MAX_VALUE); } else if (ngt.length == 2) // Both abnormalType and abnormalLvl are present { try { _negateAbnormals.put(ngt[0], Byte.parseByte(ngt[1])); } catch (Exception e) { throw new IllegalArgumentException("SkillId: " + _id + " Byte value required, but found: " + ngt[1]); } } else { throw new IllegalArgumentException("SkillId: " + _id + ": Incorrect negate Abnormals for " + ngtStack + ". Lvl: abnormalType1,abnormalLvl1;abnormalType2,abnormalLvl2;abnormalType3,abnormalLvl3... or abnormalType1;abnormalType2;abnormalType3..."); } } } else { _negateAbnormals = null; } _maxNegatedEffects = set.getInteger("maxNegated", 0); _stayAfterDeath = set.getBool("stayAfterDeath", false); _stayOnSubclassChange = set.getBool("stayOnSubclassChange", true); _killByDOT = set.getBool("killByDOT", false); _hitTime = set.getInteger("hitTime", 0); String hitTimings = set.getString("hitTimings", null); if (hitTimings != null) { try { String[] valuesSplit = hitTimings.split(","); _hitTimings = new int[valuesSplit.length]; for (int i = 0; i < valuesSplit.length; i++) { _hitTimings[i] = Integer.parseInt(valuesSplit[i]); } } catch (Exception e) { throw new IllegalArgumentException("SkillId: " + _id + " invalid hitTimings value: " + hitTimings + ", \"percent,percent,...percent\" required"); } } else { _hitTimings = new int[0]; } _coolTime = set.getInteger("coolTime", 0); _isDebuff = set.getBool("isDebuff", false); _feed = set.getInteger("feed", 0); _reuseHashCode = SkillTable.getSkillHashCode(_id, _level); if (Config.ENABLE_MODIFY_SKILL_REUSE && Config.SKILL_REUSE_LIST.containsKey(_id)) { if (Config.DEBUG) { _log.info("*** Skill " + _name + " (" + _level + ") changed reuse from " + set.getInteger("reuseDelay", 0) + " to " + Config.SKILL_REUSE_LIST.get(_id) + " seconds."); } _reuseDelay = Config.SKILL_REUSE_LIST.get(_id); } else { _reuseDelay = set.getInteger("reuseDelay", 0); } _affectRange = set.getInteger("affectRange", 0); final String affectLimit = set.getString("affectLimit", null); if (affectLimit != null) { try { String[] valuesSplit = affectLimit.split("-"); _affectLimit[0] = Integer.parseInt(valuesSplit[0]); _affectLimit[1] = Integer.parseInt(valuesSplit[1]); } catch (Exception e) { throw new IllegalArgumentException("SkillId: " + _id + " invalid affectLimit value: " + affectLimit + ", \"percent-percent\" required"); } } _targetType = set.getEnum("targetType", L2TargetType.class); _power = set.getFloat("power", 0.f); _pvpPower = set.getFloat("pvpPower", (float) getPower()); _pvePower = set.getFloat("pvePower", (float) getPower()); _magicLevel = set.getInteger("magicLvl", 0); _lvlBonusRate = set.getInteger("lvlBonusRate", 0); _minChance = set.getInteger("minChance", Config.MIN_ABNORMAL_STATE_SUCCESS_RATE); _maxChance = set.getInteger("maxChance", Config.MAX_ABNORMAL_STATE_SUCCESS_RATE); _stat = set.getEnum("stat", Stats.class, null); _ignoreShield = set.getBool("ignoreShld", false); _skillType = set.getEnum("skillType", L2SkillType.class, L2SkillType.DUMMY); _effectType = set.getEnum("effectType", L2SkillType.class, null); _effectId = set.getInteger("effectId", 0); _effectLvl = set.getInteger("effectLevel", 0); _nextActionIsAttack = set.getBool("nextActionAttack", false); _removedOnAnyActionExceptMove = set.getBool("removedOnAnyActionExceptMove", false); _removedOnDamage = set.getBool("removedOnDamage", _skillType == L2SkillType.SLEEP); _element = set.getByte("element", (byte) -1); _elementPower = set.getInteger("elementPower", 0); _saveVs = set.getEnum("saveVs", BaseStats.class, BaseStats.NULL); _condition = set.getInteger("condition", 0); _conditionValue = set.getInteger("conditionValue", 0); _overhit = set.getBool("overHit", false); _isSuicideAttack = set.getBool("isSuicideAttack", false); String weaponsAllowedString = set.getString("weaponsAllowed", null); if ((weaponsAllowedString != null) && !weaponsAllowedString.trim().isEmpty()) { int mask = 0; StringTokenizer st = new StringTokenizer(weaponsAllowedString, ","); while (st.hasMoreTokens()) { int old = mask; String item = st.nextToken().trim(); if (ItemTable._weaponTypes.containsKey(item)) { mask |= ItemTable._weaponTypes.get(item).mask(); } if (ItemTable._armorTypes.containsKey(item)) { mask |= ItemTable._armorTypes.get(item).mask(); } if (old == mask) { _log.info("[weaponsAllowed] Unknown item type name: " + item); } } _weaponsAllowed = mask; } else { _weaponsAllowed = 0; } _armorsAllowed = set.getInteger("armorsAllowed", 0); _minPledgeClass = set.getInteger("minPledgeClass", 0); _isOffensive = set.getBool("offensive", false); _isPVP = set.getBool("pvp", false); _chargeConsume = set.getInteger("chargeConsume", 0); _triggeredId = set.getInteger("triggeredId", 0); _triggeredLevel = set.getInteger("triggeredLevel", 1); _chanceType = set.getString("chanceType", ""); if (!_chanceType.isEmpty()) { _chanceCondition = ChanceCondition.parse(set); } _numSouls = set.getInteger("num_souls", 0); _soulMaxConsume = set.getInteger("soulMaxConsumeCount", 0); _blowChance = set.getInteger("blowChance", 0); _expNeeded = set.getInteger("expNeeded", 0); _critChance = set.getInteger("critChance", 0); _transformId = set.getInteger("transformId", 0); _transformDuration = set.getInteger("transformDuration", 0); _isHeroSkill = SkillTreesData.getInstance().isHeroSkill(_id, _level); _isGMSkill = SkillTreesData.getInstance().isGMSkill(_id, _level); _isSevenSigns = (_id > 4360) && (_id < 4367); _isClanSkill = SkillTreesData.getInstance().isClanSkill(_id, _level); _baseCritRate = set.getInteger("baseCritRate", ((_skillType == L2SkillType.PDAM) || (_skillType == L2SkillType.BLOW)) ? 0 : -1); _halfKillRate = set.getInteger("halfKillRate", 0); _lethalStrikeRate = set.getInteger("lethalStrikeRate", 0); _directHpDmg = set.getBool("dmgDirectlyToHp", false); _isTriggeredSkill = set.getBool("isTriggeredSkill", false); _sSBoost = set.getFloat("SSBoost", 0.f); _aggroPoints = set.getInteger("aggroPoints", 0); _flyType = set.getString("flyType", null); _flyRadius = set.getInteger("flyRadius", 0); _flyCourse = set.getFloat("flyCourse", 0); _canBeReflected = set.getBool("canBeReflected", true); _canBeDispeled = set.getBool("canBeDispeled", true); _excludedFromCheck = set.getBool("excludedFromCheck", false); _dependOnTargetBuff = set.getBool("dependOnTargetBuff", false); _simultaneousCast = set.getBool("simultaneousCast", false); String capsuled_items = set.getString("capsuled_items_skill", null); if (capsuled_items != null) { if (capsuled_items.isEmpty()) { _log.warning("Empty Extractable Item Skill data in Skill Id: " + _id); } _extractableItems = parseExtractableSkill(_id, _level, capsuled_items); } _maxTargets = set.getInteger("maxTargets", -1); _npcId = set.getInteger("npcId", 0); } public abstract void useSkill(L2Character caster, L2Object[] targets); public final int getArmorsAllowed() { return _armorsAllowed; } public final int getConditionValue() { return _conditionValue; } public final L2SkillType getSkillType() { return _skillType; } public final L2TraitType getTraitType() { return _traitType; } public final byte getElement() { return _element; } public final int getElementPower() { return _elementPower; } /** * Return the target type of the skill : SELF, PARTY, CLAN, PET... * @return */ public final L2TargetType getTargetType() { return _targetType; } public final int getCondition() { return _condition; } public final boolean isOverhit() { return _overhit; } public final boolean killByDOT() { return _killByDOT; } public final boolean isSuicideAttack() { return _isSuicideAttack; } public final boolean allowOnTransform() { return isPassive(); } /** * Return the power of the skill. * @param activeChar * @param target * @param isPvP * @param isPvE * @return */ public final double getPower(L2Character activeChar, L2Character target, boolean isPvP, boolean isPvE) { if (activeChar == null) { return getPower(isPvP, isPvE); } switch (_skillType) { case DEATHLINK: { return getPower(isPvP, isPvE) * (-((activeChar.getCurrentHp() * 2) / activeChar.getMaxHp()) + 2); } case FATAL: { return getPower(isPvP, isPvE) * (-((target.getCurrentHp() * 2) / target.getMaxHp()) + 2); } default: { return getPower(isPvP, isPvE); } } } public final double getPower() { return _power; } public final double getPower(boolean isPvP, boolean isPvE) { return isPvE ? _pvePower : isPvP ? _pvpPower : _power; } public final Map getNegateAbnormals() { return _negateAbnormals; } public final int getAbnormalLvl() { return _abnormalLvl; } public final int getMagicLevel() { return _magicLevel; } public final int getMaxNegatedEffects() { return _maxNegatedEffects; } public final int getLvlBonusRate() { return _lvlBonusRate; } /** * Return custom minimum skill/effect chance. * @return */ public final int getMinChance() { return _minChance; } /** * Return custom maximum skill/effect chance. * @return */ public final int getMaxChance() { return _maxChance; } /** * Return true if skill effects should be removed on any action except movement * @return */ public final boolean isRemovedOnAnyActionExceptMove() { return _removedOnAnyActionExceptMove; } /** * Return true if skill effects should be removed on damage * @return */ public final boolean isRemovedOnDamage() { return _removedOnDamage; } /** * Return the additional effect Id. * @return */ public final int getEffectId() { return _effectId; } /** * Return the additional effect level. * @return */ public final int getEffectLvl() { return _effectLvl; } public final int getEffectAbnormalLvl() { return _effectAbnormalLvl; } /** * Return the additional effect skill type (ex : STUN, PARALYZE,...). * @return */ public final L2SkillType getEffectType() { return _effectType; } /** * Return true if character should attack target after skill * @return */ public final boolean nextActionIsAttack() { return _nextActionIsAttack; } /** * TODO: Zoey76, temp fix until skill reworks is done. * @return the calculated buff duration used to display buff icons. */ public final int getBuffDuration() { int duration = 0; final EffectTemplate firstEffect = hasEffects() ? getEffectTemplates()[0] : null; if (firstEffect != null) { duration = firstEffect.abnormalTime * 1000; if (firstEffect.counter > 1) { duration *= firstEffect.counter; } } return duration; } /** * @return Returns the castRange. */ public final int getCastRange() { return _castRange; } /** * @return Returns the cpConsume; */ public final int getCpConsume() { return _cpConsume; } /** * @return Returns the effectRange. */ public final int getEffectRange() { return _effectRange; } /** * @return Returns the hpConsume. */ public final int getHpConsume() { return _hpConsume; } /** * @return Returns the id. */ public final int getId() { return _id; } /** * @return Returns the boolean _isDebuff. */ public final boolean isDebuff() { return _isDebuff; } public int getDisplayId() { return _displayId; } public int getDisplayLevel() { return _displayLevel; } public int getTriggeredId() { return _triggeredId; } public int getTriggeredLevel() { return _triggeredLevel; } public boolean triggerAnotherSkill() { return _triggeredId > 1; } /** * Return the skill type (ex : BLEED, SLEEP, WATER...). * @return */ public final Stats getStat() { return _stat; } /** * Return skill saveVs base stat (STR, INT ...). * @return */ public final BaseStats getSaveVs() { return _saveVs; } /** * @return Returns the _targetConsumeId. */ public final int getTargetConsumeId() { return _targetConsumeId; } /** * @return Returns the targetConsume. */ public final int getTargetConsume() { return _targetConsume; } /** * @return Returns the itemConsume. */ public final int getItemConsume() { return _itemConsume; } /** * @return Returns the itemConsumeId. */ public final int getItemConsumeId() { return _itemConsumeId; } /** * @return Returns the level. */ public final int getLevel() { return _level; } /** * @return Returns true to set physical skills. */ public final boolean isPhysical() { return _magic == 0; } /** * @return Returns true to set magic skills. */ public final boolean isMagic() { return _magic == 1; } /** * @return Returns true to set static skills. */ public final boolean isStatic() { return _magic == 2; } public final boolean isDance() { return _magic == 3; } /** * @return Returns true to set static reuse. */ public final boolean isStaticReuse() { return _staticReuse; } public final boolean isStaticDamage() { return _staticDamage; } /** * @return Returns the mpConsume. */ public final int getMpConsume() { return _mpConsume; } /** * @return Returns the mpInitialConsume. */ public final int getMpInitialConsume() { return _mpInitialConsume; } /** * @return Returns the name. */ public final String getName() { return _name; } /** * @return Returns the reuseDelay. */ public final int getReuseDelay() { return _reuseDelay; } public final int getReuseHashCode() { return _reuseHashCode; } public final int getHitTime() { return _hitTime; } public final int getHitCounts() { return _hitTimings.length; } public final int[] getHitTimings() { return _hitTimings; } /** * @return Returns the coolTime. */ public final int getCoolTime() { return _coolTime; } public final int getAffectRange() { return _affectRange; } public final int[] getAffectLimit() { return _affectLimit; } public final boolean isActive() { return (_operateType != null) && _operateType.isActive(); } public final boolean isPassive() { return (_operateType != null) && _operateType.isPassive(); } public final boolean isToggle() { return (_operateType != null) && _operateType.isToggle(); } public final boolean isChance() { return (_chanceCondition != null) && isPassive(); } public final boolean isTriggeredSkill() { return _isTriggeredSkill; } public final float getSSBoost() { return _sSBoost; } public final int getAggroPoints() { return _aggroPoints; } public final boolean useSoulShot() { switch (getSkillType()) { case PDAM: case CHARGEDAM: case BLOW: return true; default: return false; } } public final boolean useSpiritShot() { return _magic == 1; } public final boolean useFishShot() { return ((getSkillType() == L2SkillType.PUMPING) || (getSkillType() == L2SkillType.REELING)); } public final int getWeaponsAllowed() { return _weaponsAllowed; } public int getMinPledgeClass() { return _minPledgeClass; } public final boolean isOffensive() { return _isOffensive || isPVP(); } public final boolean isPVP() { return _isPVP; } public final boolean isHeroSkill() { return _isHeroSkill; } public final boolean isGMSkill() { return _isGMSkill; } public final boolean is7Signs() { return _isSevenSigns; } public final int getChargeConsume() { return _chargeConsume; } public final int getNumSouls() { return _numSouls; } public final int getMaxSoulConsumeCount() { return _soulMaxConsume; } public final int getExpNeeded() { return _expNeeded; } public final int getCritChance() { return _critChance; } public final int getTransformId() { return _transformId; } public final int getTransformDuration() { return _transformDuration; } public final int getBaseCritRate() { return _baseCritRate; } public final int getHalfKillRate() { return _halfKillRate; } public final int getLethalStrikeRate() { return _lethalStrikeRate; } public final boolean getDmgDirectlyToHP() { return _directHpDmg; } public final String getFlyType() { return _flyType; } public final int getFlyRadius() { return _flyRadius; } public final float getFlyCourse() { return _flyCourse; } public final boolean isStayAfterDeath() { return _stayAfterDeath; } public final boolean isStayOnSubclassChange() { return _stayOnSubclassChange; } public final boolean getWeaponDependancy(L2Character activeChar) { if (getWeaponDependancy(activeChar, false)) { return true; } final SystemMessage message = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED); message.addSkillName(this); activeChar.sendPacket(message); return false; } public final boolean getWeaponDependancy(L2Character activeChar, boolean chance) { int weaponsAllowed = getWeaponsAllowed(); // check to see if skill has a weapon dependency. if (weaponsAllowed == 0) { return true; } int mask = 0; if (activeChar.getActiveWeaponItem() != null) { mask |= activeChar.getActiveWeaponItem().getItemType().mask(); } if ((activeChar.getSecondaryWeaponItem() != null) && (activeChar.getSecondaryWeaponItem() instanceof L2Armor)) { mask |= ((L2ArmorType) activeChar.getSecondaryWeaponItem().getItemType()).mask(); } if ((mask & weaponsAllowed) != 0) { return true; } return false; } public boolean checkCondition(L2Character activeChar, L2Object target, boolean itemOrWeapon) { if (activeChar.canOverrideCond(PcCondOverride.SKILL_CONDITIONS) && !Config.GM_SKILL_RESTRICTION) { return true; } if ((getCondition() & L2Skill.COND_SHIELD) != 0) { // L2ItemInstance dummy = activeChar.getInventory().getPaperdollItem(Inventory.PAPERDOLL_RHAND); // L2Armor armorPiece = (L2Armor) dummy.getItem(); // TODO add checks for shield here. } List preCondition = _preCondition; if (itemOrWeapon) { preCondition = _itemPreCondition; } if ((preCondition == null) || preCondition.isEmpty()) { return true; } for (Condition cond : preCondition) { Env env = new Env(); env.setCharacter(activeChar); if (target instanceof L2Character) { env.setTarget((L2Character) target); } env.setSkill(this); if (!cond.test(env)) { String msg = cond.getMessage(); int msgId = cond.getMessageId(); if (msgId != 0) { SystemMessage sm = SystemMessage.getSystemMessage(msgId); if (cond.isAddName()) { sm.addSkillName(_id); } activeChar.sendPacket(sm); } else if (msg != null) { activeChar.sendMessage(msg); } return false; } } return true; } public final L2Object[] getTargetList(L2Character activeChar, boolean onlyFirst) { // Init to null the target of the skill L2Character target = null; // Get the L2Objcet targeted by the user of the skill at this moment L2Object objTarget = activeChar.getTarget(); // If the L2Object targeted is a L2Character, it becomes the L2Character target if (objTarget instanceof L2Character) { target = (L2Character) objTarget; } return getTargetList(activeChar, onlyFirst, target); } /** * Return all targets of the skill in a table in function a the skill type.
* Values of skill type: *
    *
  • ONE : The skill can only be used on the L2PcInstance targeted, or on the caster if it's a L2PcInstance and no L2PcInstance targeted
  • *
  • SELF
  • *
  • HOLY, UNDEAD
  • *
  • PET
  • *
  • AURA, AURA_CLOSE
  • *
  • AREA
  • *
  • MULTIFACE
  • *
  • PARTY, CLAN
  • *
  • CORPSE_PLAYER, CORPSE_MOB, CORPSE_CLAN
  • *
  • UNLOCKABLE
  • *
  • ITEM
  • *
      * @param activeChar The L2Character who use the skill * @param onlyFirst * @param target * @return */ public final L2Object[] getTargetList(L2Character activeChar, boolean onlyFirst, L2Character target) { final ITargetTypeHandler handler = TargetHandler.getInstance().getHandler(getTargetType()); if (handler != null) { try { return handler.getTargetList(this, activeChar, onlyFirst, target); } catch (Exception e) { _log.log(Level.WARNING, "Exception in L2Skill.getTargetList(): " + e.getMessage(), e); } } activeChar.sendMessage("Target type of skill is not currently handled."); return _emptyTargetList; } public final L2Object[] getTargetList(L2Character activeChar) { return getTargetList(activeChar, false); } public final L2Object getFirstOfTargetList(L2Character activeChar) { L2Object[] targets = getTargetList(activeChar, true); if (targets.length == 0) { return null; } return targets[0]; } /** * Check if should be target added to the target list false if target is dead, target same as caster,
      * target inside peace zone, target in the same party with caster, caster can see target Additional checks if not in PvP zones (arena, siege):
      * target in not the same clan and alliance with caster, and usual skill PvP check. If TvT event is active - performing additional checks. Caution: distance is not checked. * @param caster * @param target * @param skill * @param sourceInArena * @return */ public static final boolean checkForAreaOffensiveSkills(L2Character caster, L2Character target, L2Skill skill, boolean sourceInArena) { if ((target == null) || target.isDead() || (target == caster)) { return false; } final L2PcInstance player = caster.getActingPlayer(); final L2PcInstance targetPlayer = target.getActingPlayer(); if (player != null) { if (targetPlayer != null) { if ((targetPlayer == caster) || (targetPlayer == player)) { return false; } if (targetPlayer.inObserverMode()) { return false; } if (skill.isOffensive() && (player.getSiegeState() > 0) && player.isInsideZone(ZoneId.SIEGE) && (player.getSiegeState() == targetPlayer.getSiegeState()) && (player.getSiegeSide() == targetPlayer.getSiegeSide())) { return false; } if (skill.isOffensive() && target.isInsideZone(ZoneId.PEACE)) { return false; } if (player.isInParty() && targetPlayer.isInParty()) { // Same party if (player.getParty().getLeaderObjectId() == targetPlayer.getParty().getLeaderObjectId()) { return false; } // Same command channel if (player.getParty().isInCommandChannel() && (player.getParty().getCommandChannel() == targetPlayer.getParty().getCommandChannel())) { return false; } } if (!TvTEvent.checkForTvTSkill(player, targetPlayer, skill)) { return false; } if (!sourceInArena && !(targetPlayer.isInsideZone(ZoneId.PVP) && !targetPlayer.isInsideZone(ZoneId.SIEGE))) { if ((player.getAllyId() != 0) && (player.getAllyId() == targetPlayer.getAllyId())) { return false; } if ((player.getClanId() != 0) && (player.getClanId() == targetPlayer.getClanId())) { return false; } if (!player.checkPvpSkill(targetPlayer, skill, (caster instanceof L2Summon))) { return false; } } } } else { // target is mob if ((targetPlayer == null) && (target instanceof L2Attackable) && (caster instanceof L2Attackable)) { String casterEnemyClan = ((L2Attackable) caster).getEnemyClan(); if ((casterEnemyClan == null) || casterEnemyClan.isEmpty()) { return false; } String targetClan = ((L2Attackable) target).getClan(); if ((targetClan == null) || targetClan.isEmpty()) { return false; } if (!casterEnemyClan.equals(targetClan)) { return false; } } } if (geoEnabled && !GeoData.getInstance().canSeeTarget(caster, target)) { return false; } return true; } public static final boolean addSummon(L2Character caster, L2PcInstance owner, int radius, boolean isDead) { if (!owner.hasSummon()) { return false; } return addCharacter(caster, owner.getSummon(), radius, isDead); } public static final boolean addCharacter(L2Character caster, L2Character target, int radius, boolean isDead) { if (isDead != target.isDead()) { return false; } if ((radius > 0) && !Util.checkIfInRange(radius, caster, target, true)) { return false; } return true; } public final Func[] getStatFuncs(L2Effect effect, L2Character player) { if (_funcTemplates == null) { return _emptyFunctionSet; } if (!(player instanceof L2Playable) && !(player instanceof L2Attackable)) { return _emptyFunctionSet; } List funcs = new ArrayList<>(_funcTemplates.length); Env env = new Env(); env.setCharacter(player); env.setSkill(this); Func f; for (FuncTemplate t : _funcTemplates) { f = t.getFunc(env, this); // skill is owner if (f != null) { funcs.add(f); } } if (funcs.isEmpty()) { return _emptyFunctionSet; } return funcs.toArray(new Func[funcs.size()]); } public boolean hasEffects() { return ((_effectTemplates != null) && (_effectTemplates.length > 0)); } public EffectTemplate[] getEffectTemplates() { return _effectTemplates; } public EffectTemplate[] getEffectTemplatesPassive() { return _effectTemplatesPassive; } public boolean hasSelfEffects() { return ((_effectTemplatesSelf != null) && (_effectTemplatesSelf.length > 0)); } public boolean hasPassiveEffects() { return ((_effectTemplatesPassive != null) && (_effectTemplatesPassive.length > 0)); } /** * Env is used to pass parameters for secondary effects (shield and ss/bss/bsss) * @param effector * @param effected * @param env * @return an array with the effects that have been added to effector */ public final L2Effect[] getEffects(L2Character effector, L2Character effected, Env env) { if (!hasEffects() || isPassive()) { return _emptyEffectSet; } // doors and siege flags cannot receive any effects if ((effected instanceof L2DoorInstance) || (effected instanceof L2SiegeFlagInstance)) { return _emptyEffectSet; } if (effector != effected) { if (isOffensive() || isDebuff()) { if (effected.isInvul()) { return _emptyEffectSet; } if ((effector instanceof L2PcInstance) && ((L2PcInstance) effector).isGM()) { if (!((L2PcInstance) effector).getAccessLevel().canGiveDamage()) { return _emptyEffectSet; } } } } List effects = new ArrayList<>(_effectTemplates.length); if (env == null) { env = new Env(); } env.setSkillMastery(Formulas.calcSkillMastery(effector, this)); env.setCharacter(effector); env.setTarget(effected); env.setSkill(this); for (EffectTemplate et : _effectTemplates) { if (Formulas.calcEffectSuccess(effector, effected, et, this, env.getShield(), env.isSoulShot(), env.isSpiritShot(), env.isBlessedSpiritShot())) { L2Effect e = et.getEffect(env); if (e != null) { e.scheduleEffect(); effects.add(e); } } // display fail message only for effects with icons else if (et.icon && (effector instanceof L2PcInstance)) { SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_RESISTED_YOUR_S2); sm.addCharName(effected); sm.addSkillName(this); ((L2PcInstance) effector).sendPacket(sm); } } if (effects.isEmpty()) { return _emptyEffectSet; } return effects.toArray(new L2Effect[effects.size()]); } /** * Warning: this method doesn't consider modifier (shield, ss, sps, bss) for secondary effects * @param effector * @param effected * @return */ public final L2Effect[] getEffects(L2Character effector, L2Character effected) { return getEffects(effector, effected, null); } /** * This method has suffered some changes in CT2.2 ->CT2.3
      * Effect engine is now supporting secondary effects with independent success/fail calculus from effect skill.
      * Env parameter has been added to pass parameters like soulshot, spiritshots, blessed spiritshots or shield defense.
      * Some other optimizations have been done
      * This new feature works following next rules: *
        *
      • To enable feature, effectPower must be over -1 (check DocumentSkill#attachEffect for further information)
      • *
      • If main skill fails, secondary effect always fail
      • *
      * @param effector * @param effected * @param env * @return */ public final L2Effect[] getEffects(L2CubicInstance effector, L2Character effected, Env env) { if (!hasEffects() || isPassive()) { return _emptyEffectSet; } if (effector.getOwner() != effected) { if (isDebuff() || isOffensive()) { if (effected.isInvul()) { return _emptyEffectSet; } if (effector.getOwner().isGM() && !effector.getOwner().getAccessLevel().canGiveDamage()) { return _emptyEffectSet; } } } List effects = new ArrayList<>(_effectTemplates.length); if (env == null) { env = new Env(); } env.setCharacter(effector.getOwner()); env.setCubic(effector); env.setTarget(effected); env.setSkill(this); for (EffectTemplate et : _effectTemplates) { if (Formulas.calcEffectSuccess(effector.getOwner(), effected, et, this, env.getShield(), env.isSoulShot(), env.isSpiritShot(), env.isBlessedSpiritShot())) { L2Effect e = et.getEffect(env); if (e != null) { e.scheduleEffect(); effects.add(e); } } } if (effects.isEmpty()) { return _emptyEffectSet; } return effects.toArray(new L2Effect[effects.size()]); } public final L2Effect[] getEffectsSelf(L2Character effector) { if (!hasSelfEffects() || isPassive()) { return _emptyEffectSet; } List effects = new ArrayList<>(_effectTemplatesSelf.length); for (EffectTemplate et : _effectTemplatesSelf) { Env env = new Env(); env.setCharacter(effector); env.setTarget(effector); env.setSkill(this); L2Effect e = et.getEffect(env); if (e != null) { e.setSelfEffect(); e.scheduleEffect(); effects.add(e); } } if (effects.isEmpty()) { return _emptyEffectSet; } return effects.toArray(new L2Effect[effects.size()]); } public final L2Effect[] getEffectsPassive(L2Character effector) { if (!hasPassiveEffects()) { return _emptyEffectSet; } List effects = new ArrayList<>(_effectTemplatesPassive.length); for (EffectTemplate et : _effectTemplatesPassive) { Env env = new Env(); env.setCharacter(effector); env.setTarget(effector); env.setSkill(this); L2Effect e = et.getEffect(env); if (e != null) { e.setPassiveEffect(); e.scheduleEffect(); effects.add(e); } } if (effects.isEmpty()) { return _emptyEffectSet; } return effects.toArray(new L2Effect[effects.size()]); } public final void attach(FuncTemplate f) { if (_funcTemplates == null) { _funcTemplates = new FuncTemplate[] { f }; } else { int len = _funcTemplates.length; FuncTemplate[] tmp = new FuncTemplate[len + 1]; System.arraycopy(_funcTemplates, 0, tmp, 0, len); tmp[len] = f; _funcTemplates = tmp; } } public final void attach(EffectTemplate effect) { if (_effectTemplates == null) { _effectTemplates = new EffectTemplate[] { effect }; } else { int len = _effectTemplates.length; EffectTemplate[] tmp = new EffectTemplate[len + 1]; System.arraycopy(_effectTemplates, 0, tmp, 0, len); tmp[len] = effect; _effectTemplates = tmp; } } public final void attachSelf(EffectTemplate effect) { if (_effectTemplatesSelf == null) { _effectTemplatesSelf = new EffectTemplate[] { effect }; } else { int len = _effectTemplatesSelf.length; EffectTemplate[] tmp = new EffectTemplate[len + 1]; System.arraycopy(_effectTemplatesSelf, 0, tmp, 0, len); tmp[len] = effect; _effectTemplatesSelf = tmp; } } public final void attachPassive(EffectTemplate effect) { if (_effectTemplatesPassive == null) { _effectTemplatesPassive = new EffectTemplate[] { effect }; } else { int len = _effectTemplatesPassive.length; EffectTemplate[] tmp = new EffectTemplate[len + 1]; System.arraycopy(_effectTemplatesPassive, 0, tmp, 0, len); tmp[len] = effect; _effectTemplatesPassive = tmp; } } public final void attach(Condition c, boolean itemOrWeapon) { if (itemOrWeapon) { if (_itemPreCondition == null) { _itemPreCondition = new ArrayList<>(); } _itemPreCondition.add(c); } else { if (_preCondition == null) { _preCondition = new ArrayList<>(); } _preCondition.add(c); } } @Override public String toString() { return _name + "[id=" + _id + ",lvl=" + _level + "]"; } /** * @return pet food */ public int getFeed() { return _feed; } /** * used for tracking item id in case that item consume cannot be used * @return reference item id */ public int getReferenceItemId() { return _refId; } public int getAfterEffectId() { return _afterEffectId; } public int getAfterEffectLvl() { return _afterEffectLvl; } @Override public boolean triggersChanceSkill() { return (_triggeredId > 0) && isChance(); } @Override public int getTriggeredChanceId() { return _triggeredId; } @Override public int getTriggeredChanceLevel() { return _triggeredLevel; } @Override public ChanceCondition getTriggeredChanceCondition() { return _chanceCondition; } public String getAttributeName() { return _attribute; } /** * @return the _blowChance */ public int getBlowChance() { return _blowChance; } public boolean ignoreShield() { return _ignoreShield; } public boolean canBeReflected() { return _canBeReflected; } public boolean canBeDispeled() { return _canBeDispeled; } public boolean isClanSkill() { return _isClanSkill; } public boolean isExcludedFromCheck() { return _excludedFromCheck; } public boolean getDependOnTargetBuff() { return _dependOnTargetBuff; } public boolean isSimultaneousCast() { return _simultaneousCast; } /** * @param skillId * @param skillLvl * @param values * @return L2ExtractableSkill * @author Zoey76 */ private L2ExtractableSkill parseExtractableSkill(int skillId, int skillLvl, String values) { final String[] prodLists = values.split(";"); final List products = new ArrayList<>(); String[] prodData; for (String prodList : prodLists) { prodData = prodList.split(","); if (prodData.length < 3) { _log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> wrong seperator!"); } List items = null; double chance = 0; int prodId = 0; int quantity = 0; final int lenght = prodData.length - 1; try { items = new ArrayList<>(lenght / 2); for (int j = 0; j < lenght; j++) { prodId = Integer.parseInt(prodData[j]); quantity = Integer.parseInt(prodData[j += 1]); if ((prodId <= 0) || (quantity <= 0)) { _log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " wrong production Id: " + prodId + " or wrond quantity: " + quantity + "!"); } items.add(new ItemHolder(prodId, quantity)); } chance = Double.parseDouble(prodData[lenght]); } catch (Exception e) { _log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> incomplete/invalid production data or wrong seperator!"); } products.add(new L2ExtractableProductItem(items, chance)); } if (products.isEmpty()) { _log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> There are no production items!"); } return new L2ExtractableSkill(SkillTable.getSkillHashCode(skillId, skillLvl), products); } public L2ExtractableSkill getExtractableSkill() { return _extractableItems; } public int getMaxTargets() { return _maxTargets; } /** * @return the _npcId */ public int getNpcId() { return _npcId; } /** * @param types * @return {@code true} if at least one of specified {@link L2EffectType} types present on the current skill's effects, {@code false} otherwise. */ public boolean hasEffectType(L2EffectType... types) { if (hasEffects() && (types != null) && (types.length > 0)) { if (_effectTypes == null) { _effectTypes = new byte[_effectTemplates.length]; final Env env = new Env(); env.setSkill(this); int i = 0; for (EffectTemplate et : _effectTemplates) { final L2Effect e = et.getEffect(env, true); if (e == null) { continue; } _effectTypes[i++] = (byte) e.getEffectType().ordinal(); } Arrays.sort(_effectTypes); } for (L2EffectType type : types) { if (Arrays.binarySearch(_effectTypes, (byte) type.ordinal()) >= 0) { return true; } } } return false; } }