/*
* 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
*/
package silentium.gameserver.model;
import java.util.ArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import silentium.gameserver.GameTimeController;
import silentium.gameserver.ThreadPoolManager;
import silentium.gameserver.model.actor.L2Character;
import silentium.gameserver.model.actor.instance.L2PcInstance;
import silentium.gameserver.model.actor.instance.L2SummonInstance;
import silentium.gameserver.network.SystemMessageId;
import silentium.gameserver.network.serverpackets.AbnormalStatusUpdate;
import silentium.gameserver.network.serverpackets.ExOlympiadSpelledInfo;
import silentium.gameserver.network.serverpackets.PartySpelled;
import silentium.gameserver.network.serverpackets.SystemMessage;
import silentium.gameserver.skills.AbnormalEffect;
import silentium.gameserver.skills.Env;
import silentium.gameserver.skills.basefuncs.Func;
import silentium.gameserver.skills.basefuncs.FuncTemplate;
import silentium.gameserver.skills.basefuncs.Lambda;
import silentium.gameserver.skills.effects.EffectTemplate;
import silentium.gameserver.templates.skills.L2EffectType;
import silentium.gameserver.templates.skills.L2SkillType;
public abstract class L2Effect
{
protected static final Logger _log = LoggerFactory.getLogger(L2Effect.class.getName());
public static enum EffectState
{
CREATED, ACTING, FINISHING
}
private static final Func[] _emptyFunctionSet = new Func[0];
private final L2Character _effector;
private final L2Character _effected;
private final L2Skill _skill; // the skill that was used.
private final boolean _isHerbEffect;
private final Lambda _lambda; // the value of an update
private EffectState _state; // the current state
private final int _period; // period, seconds
protected int _periodStartTicks;
protected int _periodFirstTime;
private final EffectTemplate _template;
private final FuncTemplate[] _funcTemplates; // function templates
private final int _totalCount; // initial count
private int _count; // counter
private final AbnormalEffect _abnormalEffect; // abnormal effect mask
private final boolean _icon; // show icon
private boolean _isSelfEffect = false; // is selfeffect ?
public boolean preventExitUpdate;
protected final class EffectTask implements Runnable
{
@Override
public void run()
{
try
{
_periodFirstTime = 0;
_periodStartTicks = GameTimeController.getGameTicks();
scheduleEffect();
}
catch (Exception e)
{
_log.error("", e);
}
}
}
private ScheduledFuture<?> _currentFuture;
/** The Identifier of the stack group */
private final String _stackType;
/** The position of the effect in the stack group */
private final float _stackOrder;
private boolean _inUse = false;
private boolean _startConditionsCorrect = true;
private final double _effectPower;
private final L2SkillType _effectSkillType;
/**
* <font color="FF0000"><b>WARNING: scheduleEffect no longer inside constructor ; you must call it explicitly.</b></font>
*
* @param env
* @param template
*/
protected L2Effect(Env env, EffectTemplate template)
{
_state = EffectState.CREATED;
_skill = env.skill;
_template = template;
_effected = env.target;
_effector = env.player;
_lambda = template.lambda;
_funcTemplates = template.funcTemplates;
_count = template.counter;
_totalCount = _count;
// Support for retail herbs duration when _effected has a Summon
int temp = template.period;
if ((_skill.getId() > 2277 && _skill.getId() < 2286) || (_skill.getId() >= 2512 && _skill.getId() <= 2514))
{
if (_effected instanceof L2SummonInstance || (_effected instanceof L2PcInstance && ((L2PcInstance) _effected).getPet() != null))
temp /= 2;
}
if (env.skillMastery)
temp *= 2;
_period = temp;
_abnormalEffect = template.abnormalEffect;
_stackType = template.stackType;
_stackOrder = template.stackOrder;
_periodStartTicks = GameTimeController.getGameTicks();
_periodFirstTime = 0;
_icon = template.icon;
_effectPower = template.effectPower;
_effectSkillType = template.effectType;
_isHerbEffect = _skill.getName().contains("Herb");
}
public int getCount()
{
return _count;
}
public int getTotalCount()
{
return _totalCount;
}
public void setCount(int newcount)
{
_count = Math.min(newcount, _totalCount); // sanity check
}
public void setFirstTime(int newFirstTime)
{
_periodFirstTime = Math.min(newFirstTime, _period);
_periodStartTicks -= _periodFirstTime * GameTimeController.TICKS_PER_SECOND;
}
public boolean getShowIcon()
{
return _icon;
}
public int getPeriod()
{
return _period;
}
public int getTime()
{
return (GameTimeController.getGameTicks() - _periodStartTicks) / GameTimeController.TICKS_PER_SECOND;
}
/**
* Returns the elapsed time of the task.
*
* @return Time in seconds.
*/
public int getTaskTime()
{
if (_count == _totalCount)
return 0;
return (Math.abs(_count - _totalCount + 1) * _period) + getTime() + 1;
}
public boolean getInUse()
{
return _inUse;
}
public boolean setInUse(boolean inUse)
{
_inUse = inUse;
if (_inUse)
_startConditionsCorrect = onStart();
else
onExit();
return _startConditionsCorrect;
}
public String getStackType()
{
return _stackType;
}
public float getStackOrder()
{
return _stackOrder;
}
public final L2Skill getSkill()
{
return _skill;
}
public final L2Character getEffector()
{
return _effector;
}
public final L2Character getEffected()
{
return _effected;
}
public boolean isSelfEffect()
{
return _isSelfEffect;
}
public void setSelfEffect()
{
_isSelfEffect = true;
}
public boolean isHerbEffect()
{
return _isHerbEffect;
}
public final double calc()
{
Env env = new Env();
env.player = _effector;
env.target = _effected;
env.skill = _skill;
return _lambda.calc(env);
}
private final synchronized void startEffectTask()
{
if (_period > 0)
{
stopEffectTask();
final int initialDelay = Math.max((_period - _periodFirstTime) * 1000, 5);
if (_count > 1)
_currentFuture = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new EffectTask(), initialDelay, _period * 1000);
else
_currentFuture = ThreadPoolManager.getInstance().scheduleEffect(new EffectTask(), initialDelay);
}
if (_state == EffectState.ACTING)
{
if (isSelfEffectType())
_effector.addEffect(this);
else
_effected.addEffect(this);
}
}
/**
* Stop the L2Effect task and send Server->Client update packet.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Cancel the effect in the the abnormal effect map of the L2Character</li> <li>Stop the task of the L2Effect, remove it and update
* client magic icon</li><BR>
* <BR>
*/
public final void exit()
{
this.exit(false);
}
public final void exit(boolean preventUpdate)
{
preventExitUpdate = preventUpdate;
_state = EffectState.FINISHING;
scheduleEffect();
}
/**
* Stop the task of the L2Effect, remove it and update client magic icon.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Cancel the task</li> <li>Stop and remove L2Effect from L2Character and update client magic icon</li><BR>
* <BR>
*/
public final synchronized void stopEffectTask()
{
if (_currentFuture != null)
{
// Cancel the task
_currentFuture.cancel(false);
_currentFuture = null;
if (isSelfEffectType() && getEffector() != null)
getEffector().removeEffect(this);
else if (getEffected() != null)
getEffected().removeEffect(this);
}
}
/**
* @return effect type
*/
public abstract L2EffectType getEffectType();
/**
* Notify started
*
* @return always true, overidden in each effect.
*/
public boolean onStart()
{
if (_abnormalEffect != AbnormalEffect.NULL)
getEffected().startAbnormalEffect(_abnormalEffect);
return true;
}
/**
* Cancel the effect in the the abnormal effect map of the effected L2Character.
*/
public void onExit()
{
if (_abnormalEffect != AbnormalEffect.NULL)
getEffected().stopAbnormalEffect(_abnormalEffect);
}
/**
* @return true for continuation of this effect
*/
public abstract boolean onActionTime();
public final void rescheduleEffect()
{
if (_state != EffectState.ACTING)
scheduleEffect();
else
{
if (_period != 0)
{
startEffectTask();
return;
}
}
}
public final void scheduleEffect()
{
switch (_state)
{
case CREATED:
{
_state = EffectState.ACTING;
if (_skill.isPvpSkill() && _icon && getEffected() instanceof L2PcInstance)
{
SystemMessage smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT);
smsg.addSkillName(_skill);
getEffected().sendPacket(smsg);
}
if (_period != 0)
{
startEffectTask();
return;
}
// effects not having count or period should start
_startConditionsCorrect = onStart();
}
case ACTING:
{
if (_count > 0)
{
_count--;
if (getInUse())
{ // effect has to be in use
if (onActionTime() && _startConditionsCorrect && _count > 0)
return; // false causes effect to finish right away
}
else if (_count > 0)
{ // do not finish it yet, in case reactivated
return;
}
}
_state = EffectState.FINISHING;
}
case FINISHING:
{
// If the time left is equal to zero, send the message
if (_count == 0 && _icon && getEffected() instanceof L2PcInstance)
getEffected().sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_HAS_WORN_OFF).addSkillName(_skill));
// if task is null - stopEffectTask does not remove effect
if (_currentFuture == null && getEffected() != null)
getEffected().removeEffect(this);
// Stop the task of the L2Effect, remove it and update client magic icon
stopEffectTask();
// Cancel the effect in the the abnormal effect map of the L2Character
if (getInUse() || !(_count > 1 || _period > 0))
if (_startConditionsCorrect)
onExit();
}
}
}
public Func[] getStatFuncs()
{
if (_funcTemplates == null)
return _emptyFunctionSet;
ArrayList<Func> funcs = new ArrayList<>(_funcTemplates.length);
Env env = new Env();
env.player = getEffector();
env.target = getEffected();
env.skill = getSkill();
Func f;
for (FuncTemplate t : _funcTemplates)
{
f = t.getFunc(env, this); // effect is owner
if (f != null)
funcs.add(f);
}
if (funcs.isEmpty())
return _emptyFunctionSet;
return funcs.toArray(new Func[funcs.size()]);
}
public final void addIcon(AbnormalStatusUpdate mi)
{
if (_state != EffectState.ACTING)
return;
final ScheduledFuture<?> future = _currentFuture;
final L2Skill sk = getSkill();
if (_totalCount > 1)
{
if (sk.isPotion())
mi.addEffect(sk.getId(), getLevel(), sk.getBuffDuration() - (getTaskTime() * 1000));
else
mi.addEffect(sk.getId(), getLevel(), -1);
}
else if (future != null)
mi.addEffect(sk.getId(), getLevel(), (int) future.getDelay(TimeUnit.MILLISECONDS));
else if (_period == -1)
mi.addEffect(sk.getId(), getLevel(), _period);
}
public final void addPartySpelledIcon(PartySpelled ps)
{
if (_state != EffectState.ACTING)
return;
final ScheduledFuture<?> future = _currentFuture;
final L2Skill sk = getSkill();
if (future != null)
ps.addPartySpelledEffect(sk.getId(), getLevel(), (int) future.getDelay(TimeUnit.MILLISECONDS));
else if (_period == -1)
ps.addPartySpelledEffect(sk.getId(), getLevel(), _period);
}
public final void addOlympiadSpelledIcon(ExOlympiadSpelledInfo os)
{
if (_state != EffectState.ACTING)
return;
final ScheduledFuture<?> future = _currentFuture;
final L2Skill sk = getSkill();
if (future != null)
os.addEffect(sk.getId(), getLevel(), (int) future.getDelay(TimeUnit.MILLISECONDS));
else if (_period == -1)
os.addEffect(sk.getId(), getLevel(), _period);
}
public int getLevel()
{
return getSkill().getLevel();
}
public int getPeriodStartTicks()
{
return _periodStartTicks;
}
public EffectTemplate getEffectTemplate()
{
return _template;
}
public double getEffectPower()
{
return _effectPower;
}
public L2SkillType getSkillType()
{
return _effectSkillType;
}
/**
* Return bit flag for current effect
*
* @return int flag
*/
public int getEffectFlags()
{
return 0;
}
@Override
public String toString()
{
return "L2Effect [_skill=" + _skill + ", _state=" + _state + ", _period=" + _period + "]";
}
public boolean isSelfEffectType()
{
return false;
}
}