/* * Copyright (C) 2004-2014 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 <http://www.gnu.org/licenses/>. */ package com.l2jserver.gameserver.model; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.logging.Logger; import javolution.util.FastMap; import com.l2jserver.Config; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Summon; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.effects.AbstractEffect; import com.l2jserver.gameserver.model.effects.EffectFlag; import com.l2jserver.gameserver.model.effects.L2EffectType; import com.l2jserver.gameserver.model.olympiad.OlympiadGameManager; import com.l2jserver.gameserver.model.olympiad.OlympiadGameTask; import com.l2jserver.gameserver.model.skills.AbnormalType; import com.l2jserver.gameserver.model.skills.BuffInfo; import com.l2jserver.gameserver.model.skills.EffectScope; import com.l2jserver.gameserver.model.skills.Skill; import com.l2jserver.gameserver.network.serverpackets.AbnormalStatusUpdate; import com.l2jserver.gameserver.network.serverpackets.ExAbnormalStatusUpdateFromTargetPacket; // l2jtw add import com.l2jserver.gameserver.network.serverpackets.ExOlympiadSpelledInfo; import com.l2jserver.gameserver.network.serverpackets.PartySpelled; import com.l2jserver.gameserver.network.serverpackets.ShortBuffStatusUpdate; /** * Effect lists.<br> * Holds all the buff infos that are affecting a character.<br> * Manages the logic that controls whether a buff is added, remove, replaced or set inactive.<br> * Uses maps with skill ID as key and buff info DTO as value to avoid iterations.<br> * Uses Double-Checked Locking to avoid useless initialization and synchronization issues and overhead.<br> * Methods may resemble List interface, although it doesn't implement such interface. * @author Zoey76 */ public final class CharEffectList { private static final Logger _log = Logger.getLogger(CharEffectList.class.getName()); /** Map containing all effects from buffs for this effect list. */ private volatile FastMap<Integer, BuffInfo> _buffs; /** Map containing all triggered skills for this effect list. */ private volatile FastMap<Integer, BuffInfo> _triggered; /** Map containing all dances/songs for this effect list. */ private volatile FastMap<Integer, BuffInfo> _dances; /** Map containing all toggle for this effect list. */ private volatile FastMap<Integer, BuffInfo> _toggles; /** Map containing all debuffs for this effect list. */ private volatile FastMap<Integer, BuffInfo> _debuffs; /** They bypass most of the actions, they are not included in most operations. */ private volatile FastMap<Integer, BuffInfo> _passives; /** Map containing the all stacked effect in progress for each abnormal type. */ private volatile Map<AbnormalType, BuffInfo> _stackedEffects; /** Set containing all abnormal types that shouldn't be added to this character effect list. */ private volatile Set<AbnormalType> _blockedBuffSlots = null; /** Short buff skill ID. */ private BuffInfo _shortBuff = null; /** If {@code true} this effect list has buffs removed on any action. */ private volatile boolean _hasBuffsRemovedOnAnyAction = false; /** If {@code true} this effect list has buffs removed on damage. */ private volatile boolean _hasBuffsRemovedOnDamage = false; /** If {@code true} this effect list has debuffs removed on damage. */ private volatile boolean _hasDebuffsRemovedOnDamage = false; /** Effect flags. */ private int _effectFlags; /** If {@code true} only party icons need to be updated. */ private boolean _partyOnly = false; /** The owner of this effect list. */ private final L2Character _owner; /** Hidden buffs count, prevents iterations. */ private final AtomicInteger _hiddenBuffs = new AtomicInteger(); /** * Constructor for effect list. * @param owner the character that owns this effect list */ public CharEffectList(L2Character owner) { _owner = owner; } /** * Gets buff skills. * @return the buff skills */ public Map<Integer, BuffInfo> getBuffs() { if (_buffs == null) { synchronized (this) { if (_buffs == null) { _buffs = new FastMap<>(); _buffs.shared(); } } } return _buffs; } /** * Gets triggered skill skills. * @return the triggered skill skills */ public Map<Integer, BuffInfo> getTriggered() { if (_triggered == null) { synchronized (this) { if (_triggered == null) { _triggered = new FastMap<>(); _triggered.shared(); } } } return _triggered; } /** * Gets dance/song skills. * @return the dance/song skills */ public Map<Integer, BuffInfo> getDances() { if (_dances == null) { synchronized (this) { if (_dances == null) { _dances = new FastMap<>(); _dances.shared(); } } } return _dances; } /** * Gets toggle skills. * @return the toggle skills */ public Map<Integer, BuffInfo> getToggles() { if (_toggles == null) { synchronized (this) { if (_toggles == null) { _toggles = new FastMap<>(); _toggles.shared(); } } } return _toggles; } /** * Gets debuff skills. * @return the debuff skills */ public Map<Integer, BuffInfo> getDebuffs() { if (_debuffs == null) { synchronized (this) { if (_debuffs == null) { _debuffs = new FastMap<>(); _debuffs.shared(); } } } return _debuffs; } /** * Gets passive skills. * @return the passive skills */ public Map<Integer, BuffInfo> getPassives() { if (_passives == null) { synchronized (this) { if (_passives == null) { _passives = new FastMap<>(); _passives.shared(); } } } return _passives; } /** * Gets all the effects on this effect list. * @return all the effects on this effect list */ public List<BuffInfo> getEffects() { if (isEmpty()) { return Collections.<BuffInfo> emptyList(); } final List<BuffInfo> buffs = new ArrayList<>(); if (hasBuffs()) { buffs.addAll(getBuffs().values()); } if (hasTriggered()) { buffs.addAll(getTriggered().values()); } if (hasDances()) { buffs.addAll(getDances().values()); } if (hasToggles()) { buffs.addAll(getToggles().values()); } if (hasDebuffs()) { buffs.addAll(getDebuffs().values()); } return buffs; } /** * Gets the effect list where the skill effects should be. * @param skill the skill * @return the effect list */ private Map<Integer, BuffInfo> getEffectList(Skill skill) { if (skill == null) { return null; } final Map<Integer, BuffInfo> effects; if (skill.isPassive()) { effects = getPassives(); } else if (skill.isDebuff()) { effects = getDebuffs(); } else if (skill.isTriggeredSkill()) { effects = getTriggered(); } else if (skill.isDance()) { effects = getDances(); } else if (skill.isToggle()) { effects = getToggles(); } else { effects = getBuffs(); } return effects; } /** * Gets the first effect for the given effect type.<br> * Prevents initialization.<br> * TODO: Remove this method after all the effect types gets replaced by abnormal skill types. * @param type the effect type * @return the first effect matching the given effect type */ public BuffInfo getFirstEffect(L2EffectType type) { if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { return info; } } } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { return info; } } } } } if (hasDances()) { for (BuffInfo info : getDances().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { return info; } } } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { /* Update by rocknow if (info != null) */ if (info != null && info.getSkill().getId() != 7029) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { return info; } } } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { return info; } } } } } return null; } /** * Verifies if this effect list contains the given skill ID.<br> * Prevents initialization. * @param skillId the skill ID to verify * @return {@code true} if the skill ID is present in the effect list, {@code false} otherwise */ public boolean isAffectedBySkill(int skillId) { return (hasBuffs() && getBuffs().containsKey(skillId)) || (hasDebuffs() && getDebuffs().containsKey(skillId)) || (hasTriggered() && getTriggered().containsKey(skillId)) || (hasDances() && getDances().containsKey(skillId)) || (hasToggles() && getToggles().containsKey(skillId)) || (hasPassives() && getPassives().containsKey(skillId)); } /** * Gets the buff info by skill ID.<br> * Prevents initialization. * @param skillId the skill ID * @return the buff info */ public BuffInfo getBuffInfoBySkillId(int skillId) { BuffInfo info = null; if (isAffectedBySkill(skillId)) { if (hasBuffs() && getBuffs().containsKey(skillId)) { info = getBuffs().get(skillId); } else if (hasTriggered() && getTriggered().containsKey(skillId)) { info = getTriggered().get(skillId); } else if (hasDances() && getDances().containsKey(skillId)) { info = getDances().get(skillId); } else if (hasToggles() && getToggles().containsKey(skillId)) { info = getToggles().get(skillId); } else if (hasDebuffs() && getDebuffs().containsKey(skillId)) { info = getDebuffs().get(skillId); } else if (hasPassives() && getPassives().containsKey(skillId)) { info = getPassives().get(skillId); } } return info; } /** * Gets a buff info by abnormal type.<br> * It's O(1) for every buff in this effect list. * @param type the abnormal skill type * @return the buff info if it's present, {@code null} otherwise */ public BuffInfo getBuffInfoByAbnormalType(AbnormalType type) { return (_stackedEffects != null) ? _stackedEffects.get(type) : null; } /** * Adds abnormal types to the blocked buff slot set. * @param blockedBuffSlots the blocked buff slot set to add */ public void addBlockedBuffSlots(Set<AbnormalType> blockedBuffSlots) { if (_blockedBuffSlots == null) { synchronized (this) { if (_blockedBuffSlots == null) { _blockedBuffSlots = new CopyOnWriteArraySet<>(); } } } _blockedBuffSlots.addAll(blockedBuffSlots); } /** * Removes abnormal types from the blocked buff slot set. * @param blockedBuffSlots the blocked buff slot set to remove * @return {@code true} if the blocked buff slots set has been modified, {@code false} otherwise */ public boolean removeBlockedBuffSlots(Set<AbnormalType> blockedBuffSlots) { if (_blockedBuffSlots != null) { return _blockedBuffSlots.removeAll(blockedBuffSlots); } return false; } /** * Gets all the blocked abnormal types for this character effect list. * @return the current blocked buff slots set */ public Set<AbnormalType> getAllBlockedBuffSlots() { return _blockedBuffSlots; } /** * Gets the Short Buff info. * @return the short buff info */ public BuffInfo getShortBuff() { return _shortBuff; } /** * Sets the Short Buff data and sends an update if the effected is a player. * @param info the buff info */ public void shortBuffStatusUpdate(BuffInfo info) { if (_owner.isPlayer()) { _shortBuff = info; if (info == null) { _owner.sendPacket(ShortBuffStatusUpdate.RESET_SHORT_BUFF); } else { _owner.sendPacket(new ShortBuffStatusUpdate(info.getSkill().getId(), info.getSkill().getLevel(), info.getTime())); } } } /** * Checks if the given skill stacks with an existing one. * @param skill the skill to verify * @return {@code true} if this effect stacks with the given skill, {@code false} otherwise */ private boolean doesStack(Skill skill) { final AbnormalType type = skill.getAbnormalType(); if (type.isNone() || isEmpty()) { return false; } final Map<Integer, BuffInfo> effects = getEffectList(skill); if ((effects == null) || effects.isEmpty()) { return false; } for (BuffInfo info : effects.values()) { if ((info != null) && (info.getSkill().getAbnormalType() == type)) { return true; } } return false; } /** * Gets the buffs count without including the hidden buffs (after getting an Herb buff).<br> * Prevents initialization. * @return the number of buffs in this character effect list */ public int getBuffCount() { return hasBuffs() ? getBuffs().size() - _hiddenBuffs.get() - (getShortBuff() != null ? 1 : 0) : 0; } /** * Gets the Songs/Dances count.<br> * Prevents initialization. * @return the number of Songs/Dances in this character effect list */ public int getDanceCount() { return hasDances() ? getDances().size() : 0; } /** * Gets the triggered buffs count.<br> * Prevents initialization. * @return the number of triggered buffs in this character effect list */ public int getTriggeredBuffCount() { return hasTriggered() ? getTriggered().size() : 0; } /** * Gets the hidden buff count. * @return the number of hidden buffs */ public int getHiddenBuffsCount() { return _hiddenBuffs.get(); } /** * Auxiliary method to stop all effects from a buff info and remove it from an effect list and stacked effects. * @param removed {@code true} if the effect is removed, {@code false} otherwise * @param info the buff info * @param effects the effect list */ private void stopAndRemove(boolean removed, BuffInfo info, Map<Integer, BuffInfo> effects) { // Stop the buff effects. info.stopAllEffects(removed); // If it's a hidden buff that ends, then decrease hidden buff count. if (!info.isInUse()) { _hiddenBuffs.decrementAndGet(); } // Removes the buff from the stack. else if (_stackedEffects != null) { _stackedEffects.remove(info.getSkill().getAbnormalType()); } // Removes the buff from the given effect list. effects.remove(info.getSkill().getId()); // If it's an herb that ends, check if there are hidden buffs. if (info.getSkill().isAbnormalInstant() && hasBuffs()) { for (BuffInfo buff : getBuffs().values()) { if ((buff != null) && (buff.getSkill().getAbnormalType() == info.getSkill().getAbnormalType()) && !buff.isInUse()) { // Sets the buff in use again. buff.setInUse(true); // Adds the stats. buff.addStats(); // Adds the buff to the stack. if (_stackedEffects != null) { _stackedEffects.put(buff.getSkill().getAbnormalType(), buff); } // If it's a hidden buff that gets activated, then decrease hidden buff count. _hiddenBuffs.decrementAndGet(); break; } } } if (!removed) { info.getSkill().applyEffectScope(EffectScope.END, info, true, false); } } /** * Auxiliary method to stop all effects from a buff info and remove it from an effect list and stacked effects. * @param info the buff info * @param effects the effect list */ private void stopAndRemove(BuffInfo info, Map<Integer, BuffInfo> effects) { stopAndRemove(true, info, effects); } /** * Exits all effects in this effect list.<br> * Stops all the effects, clear the effect lists and updates the effect flags and icons. */ public void stopAllEffects() { boolean update = false; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info != null) { info.stopAllEffects(false); } } getBuffs().clear(); _hiddenBuffs.set(0); update = true; } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (info != null) { info.stopAllEffects(false); } } getTriggered().clear(); update = true; } if (hasDances()) { for (BuffInfo info : getDances().values()) { if (info != null) { info.stopAllEffects(false); } } getDances().clear(); update = true; } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (info != null) { info.stopAllEffects(false); } } getToggles().clear(); update = true; } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if (info != null) { info.stopAllEffects(false); } } getDebuffs().clear(); update = true; } if (_stackedEffects != null) { _stackedEffects.clear(); } // Update effect flags and icons. updateEffectList(update); } /** * Stops all effects in this effect list except debuffs and those that last through death. */ public void stopAllEffectsExceptThoseThatLastThroughDeath() { boolean update = false; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if ((info != null) && !info.getSkill().isStayAfterDeath()) { stopAndRemove(info, getBuffs()); update = true; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if ((info != null) && !info.getSkill().isStayAfterDeath()) { stopAndRemove(info, getTriggered()); update = true; } } } if (hasDances()) { for (BuffInfo info : getDances().values()) { if ((info != null) && !info.getSkill().isStayAfterDeath()) { stopAndRemove(info, getDances()); update = true; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if ((info != null) && !info.getSkill().isStayAfterDeath()) { stopAndRemove(info, getToggles()); update = true; } } } // Update effect flags and icons. updateEffectList(update); } /** * Stop all effects that doesn't stay on sub-class change. */ public void stopAllEffectsNotStayOnSubclassChange() { boolean update = false; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if ((info != null) && !info.getSkill().isStayOnSubclassChange()) { stopAndRemove(info, getBuffs()); update = true; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if ((info != null) && !info.getSkill().isStayOnSubclassChange()) { stopAndRemove(info, getTriggered()); update = true; } } } if (hasDances()) { for (BuffInfo info : getDances().values()) { if ((info != null) && !info.getSkill().isStayOnSubclassChange()) { stopAndRemove(info, getDances()); update = true; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if ((info != null) && !info.getSkill().isStayOnSubclassChange()) { stopAndRemove(info, getToggles()); update = true; } } } // Update effect flags and icons. updateEffectList(update); } /** * Stops all active toggle skills. */ public void stopAllToggles() { if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (info != null) { stopAndRemove(info, getToggles()); } } // Update effect flags and icons. updateEffectList(true); } } /** * Stops all active dances/songs skills. */ public void stopAllDances() { if (hasDances()) { for (BuffInfo info : getDances().values()) { if (info != null) { stopAndRemove(info, getDances()); } } // Update effect flags and icons. updateEffectList(true); } } /** * Exit all effects having a specified type.<br> * TODO: Remove after all effect types are replaced by abnormal skill types. * @param type the type of the effect to stop */ public void stopEffects(L2EffectType type) { boolean update = false; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { stopAndRemove(info, getBuffs()); update = true; break; } } } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { stopAndRemove(info, getTriggered()); update = true; break; } } } } } if (hasDances()) { for (BuffInfo info : getDances().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { stopAndRemove(info, getDances()); update = true; break; } } } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { stopAndRemove(info, getToggles()); update = true; break; } } } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if (info != null) { for (AbstractEffect effect : info.getEffects()) { if ((effect != null) && (effect.getEffectType() == type)) { stopAndRemove(info, getDebuffs()); update = true; break; } } } } } // Update effect flags and icons. updateEffectList(update); } /** * Exits all effects created by a specific skill ID.<br> * Removes the effects from the effect list.<br> * Removes the stats from the character.<br> * Updates the effect flags and icons.<br> * Presents two overloads:<br> * {@link #stopSkillEffects(boolean, Skill)}<br> * {@link #stopSkillEffects(boolean, AbnormalType)} * @param removed {@code true} if the effect is removed, {@code false} otherwise * @param skillId the skill ID */ public void stopSkillEffects(boolean removed, int skillId) { final BuffInfo info = getBuffInfoBySkillId(skillId); if (info != null) { stopSkillEffects(removed, info.getSkill()); } } /** * Exits all effects created by a specific skill.<br> * Removes the effects from the effect list.<br> * Removes the stats from the character.<br> * Updates the effect flags and icons.<br> * Presents two overloads:<br> * {@link #stopSkillEffects(boolean, int)}<br> * {@link #stopSkillEffects(boolean, AbnormalType)} * @param removed {@code true} if the effect is removed, {@code false} otherwise * @param skill the skill */ public void stopSkillEffects(boolean removed, Skill skill) { if ((skill == null) || !isAffectedBySkill(skill.getId())) { return; } final Map<Integer, BuffInfo> effects = getEffectList(skill); if (effects != null) { remove(removed, effects.get(skill.getId())); } } /** * Exits all effects created by a specific skill abnormal type.<br> * It's O(1) for every effect in this effect list except passive effects.<br> * Presents two overloads:<br> * {@link #stopSkillEffects(boolean, int)}<br> * {@link #stopSkillEffects(boolean, Skill)} * @param removed {@code true} if the effect is removed, {@code false} otherwise * @param type the skill abnormal type * @return {@code true} if there was a buff info with the given abnormal type */ public boolean stopSkillEffects(boolean removed, AbnormalType type) { if (_stackedEffects != null) { final BuffInfo old = _stackedEffects.remove(type); if (old != null) { stopSkillEffects(removed, old.getSkill()); return true; } } return false; } /** * Exits all buffs effects of the skills with "removedOnAnyAction" set.<br> * Called on any action except movement (attack, cast). */ public void stopEffectsOnAction() { if (_hasBuffsRemovedOnAnyAction) { boolean update = false; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if ((info != null) && info.getSkill().isRemovedOnAnyActionExceptMove()) { stopAndRemove(info, getBuffs()); update = true; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if ((info != null) && info.getSkill().isRemovedOnAnyActionExceptMove()) { stopAndRemove(info, getTriggered()); update = true; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if ((info != null) && info.getSkill().isRemovedOnAnyActionExceptMove()) { stopAndRemove(info, getToggles()); update = true; } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if ((info != null) && info.getSkill().isRemovedOnAnyActionExceptMove()) { stopAndRemove(info, getDebuffs()); update = true; } } } // Update effect flags and icons. updateEffectList(update); } } public void stopEffectsOnDamage(boolean awake) { if (awake) { boolean update = false; if (_hasBuffsRemovedOnDamage) { if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if ((info != null) && info.getSkill().isRemovedOnDamage()) { stopAndRemove(info, getBuffs()); update = true; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if ((info != null) && info.getSkill().isRemovedOnDamage()) { stopAndRemove(info, getTriggered()); update = true; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if ((info != null) && info.getSkill().isRemovedOnDamage()) { stopAndRemove(info, getToggles()); update = true; } } } } if (_hasDebuffsRemovedOnDamage && hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if ((info != null) && info.getSkill().isRemovedOnDamage()) { stopAndRemove(info, getDebuffs()); update = true; } } } // Update effect flags and icons. updateEffectList(update); } } /** * @param partyOnly */ public void updateEffectIcons(boolean partyOnly) { if (partyOnly) { _partyOnly = true; } // Update effect flags and icons. updateEffectList(true); } /** * Verify if this effect list is empty.<br> * Prevents initialization. * @return {@code true} if this effect list contains any skills */ public boolean isEmpty() { return !hasBuffs() && !hasTriggered() && !hasDances() && !hasDebuffs(); } /** * Verify if this effect list has buffs skills.<br> * Prevents initialization. * @return {@code true} if {@link #_buffs} is not {@code null} and is not empty */ public boolean hasBuffs() { return (_buffs != null) && !_buffs.isEmpty(); } /** * Verify if this effect list has triggered skills.<br> * Prevents initialization. * @return {@code true} if {@link #_triggered} is not {@code null} and is not empty */ public boolean hasTriggered() { return (_triggered != null) && !_triggered.isEmpty(); } /** * Verify if this effect list has dance/song skills.<br> * Prevents initialization. * @return {@code true} if {@link #_dances} is not {@code null} and is not empty */ public boolean hasDances() { return (_dances != null) && !_dances.isEmpty(); } /** * Verify if this effect list has toggle skills.<br> * Prevents initialization. * @return {@code true} if {@link #_toggles} is not {@code null} and is not empty */ public boolean hasToggles() { return (_toggles != null) && !_toggles.isEmpty(); } /** * Verify if this effect list has debuffs skills.<br> * Prevents initialization. * @return {@code true} if {@link #_debuffs} is not {@code null} and is not empty */ public boolean hasDebuffs() { return (_debuffs != null) && !_debuffs.isEmpty(); } /** * Verify if this effect list has passive skills.<br> * Prevents initialization. * @return {@code true} if {@link #_passives} is not {@code null} and is not empty */ public boolean hasPassives() { return (_passives != null) && !_passives.isEmpty(); } /** * Executes a procedure for all effects.<br> * Prevents initialization. * @param function the function to execute * @param dances if {@code true} dances/songs will be included * @return {@code true} if the procedure is executed successfully for every element, {@code false} otherwise */ public boolean forEach(Function<BuffInfo, Boolean> function, boolean dances) { if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (!function.apply(info)) { return false; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (!function.apply(info)) { return false; } } } if (dances && hasDances()) { for (BuffInfo info : getDances().values()) { if (!function.apply(info)) { return false; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (!function.apply(info)) { return false; } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if (!function.apply(info)) { return false; } } } // Update effect flags and icons. updateEffectList(true); return true; } /** * Removes a set of effects from this effect list. * @param removed {@code true} if the effect is removed, {@code false} otherwise * @param info the effects to remove */ public void remove(boolean removed, BuffInfo info) { if ((info == null) || !isAffectedBySkill(info.getSkill().getId())) { return; } // Remove the effect from character effects. stopAndRemove(removed, info, getEffectList(info.getSkill())); // Update effect flags and icons. updateEffectList(true); } /** * Adds a set of effects to this effect list. * @param info the buff info */ public void add(BuffInfo info) { if (info == null) { return; } // Support for blocked buff slots. final Skill skill = info.getSkill(); if ((_blockedBuffSlots != null) && _blockedBuffSlots.contains(skill.getAbnormalType())) { return; } // Passive effects are treated specially if (skill.isPassive()) { // Passive effects don't need stack type! if (!skill.getAbnormalType().isNone()) { _log.warning("Passive " + skill + " with abnormal type: " + skill.getAbnormalType() + "!"); } // Check for passive skill conditions. if (!skill.checkCondition(info.getEffector(), info.getEffected(), false)) { return; } // Puts the effects in the list. final BuffInfo infoToRemove = getPassives().put(skill.getId(), info); if (infoToRemove != null) { // Removes the old stats from the character if the skill was present. infoToRemove.setInUse(false); infoToRemove.removeStats(); } // Initialize effects. info.initializeEffects(); return; } // The old effect is removed using Map#remove(key) instead of Map#put(key, value) (that would be the wisest choice), // Because order matters and put method would insert in the same place it was before, instead of, at the end of the effect list // Where new buff should be placed if (skill.getAbnormalType().isNone()) { stopSkillEffects(false, skill); } // Verify stacked skills. else { if (_stackedEffects == null) { synchronized (this) { if (_stackedEffects == null) { _stackedEffects = new ConcurrentHashMap<>(); } } } if (_stackedEffects.containsKey(skill.getAbnormalType())) { BuffInfo stackedInfo = _stackedEffects.get(skill.getAbnormalType()); // Skills are only replaced if the incoming buff has greater or equal abnormal level. /* l2jtw add : GS-comment-044 if ((stackedInfo != null) && (skill.getAbnormalLvl() >= stackedInfo.getSkill().getAbnormalLvl())) */ if ((stackedInfo != null) && (skill.getAbnormalLvl() >= stackedInfo.getSkill().getAbnormalLvl()) && (skill.getAbnormalTime() >= stackedInfo.getSkill().getAbnormalTime())) { // If it is an herb, set as not in use the lesser buff. // Effect will be present in the effect list. // Effects stats are removed and onActionTime() is not called. // But finish task continues to run, and ticks as well. if (skill.isAbnormalInstant()) { if (stackedInfo.getSkill().isAbnormalInstant()) { stopSkillEffects(false, skill.getAbnormalType()); stackedInfo = _stackedEffects.get(skill.getAbnormalType()); } if (stackedInfo != null) { stackedInfo.setInUse(false); // Remove stats stackedInfo.removeStats(); _hiddenBuffs.incrementAndGet(); } } // Remove buff that will stack with the abnormal type. else { if (stackedInfo.getSkill().isAbnormalInstant()) { stopSkillEffects(false, skill.getAbnormalType()); } stopSkillEffects(false, skill.getAbnormalType()); } } // If the new buff is a lesser buff, then don't add it. else { return; } } _stackedEffects.put(skill.getAbnormalType(), info); } // Select the map that holds the effects related to this skill. final Map<Integer, BuffInfo> effects = getEffectList(skill); // Remove first buff when buff list is full. if (!skill.isDebuff() && !skill.isToggle() && !skill.is7Signs() && !doesStack(skill)) { int buffsToRemove = -1; if (skill.isDance()) { buffsToRemove = getDanceCount() - Config.DANCES_MAX_AMOUNT; } else if (skill.isTriggeredSkill()) { buffsToRemove = getTriggeredBuffCount() - Config.TRIGGERED_BUFFS_MAX_AMOUNT; } else if (!skill.isHealingPotionSkill()) { buffsToRemove = getBuffCount() - _owner.getStat().getMaxBuffCount(); } for (Entry<Integer, BuffInfo> entry : effects.entrySet()) { if (buffsToRemove < 0) { break; } if (!entry.getValue().isInUse()) { continue; } stopAndRemove(entry.getValue(), effects); buffsToRemove--; } } // After removing old buff (same ID) or stacked buff (same abnormal type), // Add the buff to the end of the effect list. effects.put(skill.getId(), info); // Initialize effects. info.initializeEffects(); // Update effect flags and icons. updateEffectList(true); } /** * Update effect icons.<br> * Prevents initialization. */ private void updateEffectIcons() { if (_owner == null) { return; } // 603 : GS-comment-049 start for (L2Character temp : _owner.getStatus().getStatusListener()) { if ((temp != null) && (temp.getTargetId() == _owner.getObjectId())) { temp.sendPacket(new ExAbnormalStatusUpdateFromTargetPacket(_owner.getObjectId())); } } // 603 : GS-comment-049 end updateEffectFlags(); if (!_owner.isPlayable()) { return; } AbnormalStatusUpdate asu = null; PartySpelled ps = null; PartySpelled psSummon = null; ExOlympiadSpelledInfo os = null; boolean isSummon = false; if (_owner.isPlayer()) { if (_partyOnly) { _partyOnly = false; } else { asu = new AbnormalStatusUpdate(); } if (_owner.isInParty()) { ps = new PartySpelled(_owner); } if (_owner.getActingPlayer().isInOlympiadMode() && _owner.getActingPlayer().isOlympiadStart()) { os = new ExOlympiadSpelledInfo(_owner.getActingPlayer()); } } else if (_owner.isSummon()) { isSummon = true; ps = new PartySpelled(_owner); psSummon = new PartySpelled(_owner); } // Buffs. if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info.getSkill().isHealingPotionSkill()) { shortBuffStatusUpdate(info); } else { addIcon(info, asu, ps, psSummon, os, isSummon); } } } // Triggered buffs. if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { addIcon(info, asu, ps, psSummon, os, isSummon); } } // Songs and dances. if (hasDances()) { for (BuffInfo info : getDances().values()) { addIcon(info, asu, ps, psSummon, os, isSummon); } } // Songs and dances. if (hasToggles()) { for (BuffInfo info : getToggles().values()) { addIcon(info, asu, ps, psSummon, os, isSummon); } } // Debuffs. if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { addIcon(info, asu, ps, psSummon, os, isSummon); } } if (asu != null) { _owner.sendPacket(asu); // 603 : GS-comment-049 start if (_owner.getTargetId() == _owner.getObjectId()) { _owner.sendPacket(new ExAbnormalStatusUpdateFromTargetPacket(_owner.getObjectId())); } // 603 : GS-comment-049 end } if (ps != null) { if (_owner.isSummon()) { final L2PcInstance summonOwner = ((L2Summon) _owner).getOwner(); if (summonOwner != null) { if (summonOwner.isInParty()) { summonOwner.getParty().broadcastToPartyMembers(summonOwner, psSummon); // send to all member except summonOwner summonOwner.sendPacket(ps); // now send to summonOwner } else { summonOwner.sendPacket(ps); } } } else if (_owner.isPlayer() && _owner.isInParty()) { _owner.getParty().broadcastPacket(ps); } } if (os != null) { final OlympiadGameTask game = OlympiadGameManager.getInstance().getOlympiadTask(_owner.getActingPlayer().getOlympiadGameId()); if ((game != null) && game.isBattleStarted()) { game.getZone().broadcastPacketToObservers(os); } } } private void addIcon(BuffInfo info, AbnormalStatusUpdate asu, PartySpelled ps, PartySpelled psSummon, ExOlympiadSpelledInfo os, boolean isSummon) { // Avoid null and not in use buffs. if ((info == null) || !info.isInUse()) { return; } final Skill skill = info.getSkill(); if (asu != null) { asu.addSkill(info); } if ((ps != null) && (isSummon || !skill.isToggle())) { ps.addSkill(info); } if ((psSummon != null) && !skill.isToggle()) { psSummon.addSkill(info); } if (os != null) { os.addSkill(info); } } /** * Wrapper to update abnormal icons and effect flags. * @param update if {@code true} performs an update */ private void updateEffectList(boolean update) { if (update) { updateEffectIcons(); computeEffectFlags(); } } /** * Updates effect flags.<br> * TODO: Rework it to update in real time (add/remove/stop/activate/deactivate operations) and avoid iterations. */ private void updateEffectFlags() { if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info == null) { continue; } if (info.getSkill().isRemovedOnAnyActionExceptMove()) { _hasBuffsRemovedOnAnyAction = true; } if (info.getSkill().isRemovedOnDamage()) { _hasBuffsRemovedOnDamage = true; } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (info == null) { continue; } if (info.getSkill().isRemovedOnAnyActionExceptMove()) { _hasBuffsRemovedOnAnyAction = true; } if (info.getSkill().isRemovedOnDamage()) { _hasBuffsRemovedOnDamage = true; } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (info == null) { continue; } if (info.getSkill().isRemovedOnAnyActionExceptMove()) { _hasBuffsRemovedOnAnyAction = true; } if (info.getSkill().isRemovedOnDamage()) { _hasBuffsRemovedOnDamage = true; } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if ((info != null) && info.getSkill().isRemovedOnDamage()) { _hasDebuffsRemovedOnDamage = true; } } } } /** * Recalculate effect bits flag.<br> * TODO: Rework to update in real time and avoid iterations. */ private void computeEffectFlags() { int flags = 0; if (hasBuffs()) { for (BuffInfo info : getBuffs().values()) { if (info != null) { for (AbstractEffect e : info.getEffects()) { flags |= e.getEffectFlags(); } } } } if (hasTriggered()) { for (BuffInfo info : getTriggered().values()) { if (info != null) { for (AbstractEffect e : info.getEffects()) { flags |= e.getEffectFlags(); } } } } if (hasDances()) { for (BuffInfo info : getDances().values()) { if (info != null) { for (AbstractEffect e : info.getEffects()) { flags |= e.getEffectFlags(); } } } } if (hasToggles()) { for (BuffInfo info : getToggles().values()) { if (info != null) { for (AbstractEffect e : info.getEffects()) { flags |= e.getEffectFlags(); } } } } if (hasDebuffs()) { for (BuffInfo info : getDebuffs().values()) { if (info != null) { for (AbstractEffect e : info.getEffects()) { flags |= e.getEffectFlags(); } } } } _effectFlags = flags; } /** * Check if target is affected with special buff * @param flag of special buff * @return boolean true if affected */ public boolean isAffected(EffectFlag flag) { return (_effectFlags & flag.getMask()) != 0; } }