/*
* 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 .
*/
package com.l2jserver.gameserver.model.skills;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import com.l2jserver.Config;
import com.l2jserver.gameserver.GameTimeController;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.CharEffectList;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.effects.AbstractEffect;
import com.l2jserver.gameserver.model.effects.EffectTaskInfo;
import com.l2jserver.gameserver.model.effects.EffectTickTask;
import com.l2jserver.gameserver.model.stats.Env;
import com.l2jserver.gameserver.model.stats.Formulas;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
/**
* Buff Info.
* Complex DTO that holds all the information for a given buff (or debuff or dance/song) set of effects issued by an skill.
* @author Zoey76
*/
public final class BuffInfo
{
// Data
/** Data. */
private final Env _env;
/** The effects. */
private final List _effects = new ArrayList<>(1);
// Tasks
/** Effect tasks for ticks. */
private volatile Map _tasks;
/** Task for effect ending. */
private BuffTimeTask _effectTimeTask;
/** Scheduled future. */
private ScheduledFuture> _scheduledFutureTimeTask;
// Time and ticks
/** Abnormal time. */
private int _abnormalTime;
/** The game ticks at the start of this effect. */
private final int _periodStartTicks;
// Misc
/** If {@code true} then this effect has been cancelled. */
private boolean _isRemoved = false;
/** If {@code true} then this effect is in use (or has been stop because an Herb took place). */
private boolean _isInUse = true;
/**
* Buff Info constructor.
* @param env the cast data
*/
public BuffInfo(Env env)
{
_env = env;
_abnormalTime = Formulas.calcEffectAbnormalTime(env);
_periodStartTicks = GameTimeController.getInstance().getGameTicks();
}
/**
* Gets the effects on this buff info.
* @return the effects
*/
public List getEffects()
{
return _effects;
}
/**
* Adds an effect to this buff info.
* @param effect the effect to add
*/
public void addEffect(AbstractEffect effect)
{
_effects.add(effect);
}
/**
* Adds an effect task to this buff info.
* Uses double-checked locking to initialize the map if it's necessary.
* @param effect the effect that owns the task
* @param effectTaskInfo the task info
*/
private void addTask(AbstractEffect effect, EffectTaskInfo effectTaskInfo)
{
if (_tasks == null)
{
synchronized (this)
{
if (_tasks == null)
{
_tasks = new ConcurrentHashMap<>();
}
}
}
_tasks.put(effect, effectTaskInfo);
}
/**
* Gets the task for the given effect.
* @param effect the effect
* @return the task
*/
private EffectTaskInfo getEffectTask(AbstractEffect effect)
{
return (_tasks == null) ? null : _tasks.get(effect);
}
/**
* Gets the skill that created this buff info.
* @return the skill
*/
public Skill getSkill()
{
return _env.getSkill();
}
public Env getEnv()
{
return _env;
}
/**
* Gets the calculated abnormal time.
* @return the abnormal time
*/
public int getAbnormalTime()
{
return _abnormalTime;
}
/**
* Sets the abnormal time.
* @param abnormalTime the abnormal time to set
*/
public void setAbnormalTime(int abnormalTime)
{
_abnormalTime = abnormalTime;
}
/**
* Gets the period start ticks.
* @return the period start
*/
public int getPeriodStartTicks()
{
return _periodStartTicks;
}
/**
* Get the remaining time in seconds for this buff info.
* @return the elapsed time
*/
public int getTime()
{
return _abnormalTime - ((GameTimeController.getInstance().getGameTicks() - _periodStartTicks) / GameTimeController.TICKS_PER_SECOND);
}
/**
* Verify if this buff info has been cancelled.
* @return {@code true} if this buff info has been cancelled, {@code false} otherwise
*/
public boolean isRemoved()
{
return _isRemoved;
}
/**
* Set the buff info to removed.
* @param val the value to set
*/
public void setRemoved(boolean val)
{
_isRemoved = val;
}
/**
* Verify if this buff info is in use.
* @return {@code true} if this buff info is in use, {@code false} otherwise
*/
public boolean isInUse()
{
return _isInUse;
}
/**
* Set the buff info to in use.
* @param val the value to set
*/
public void setInUse(boolean val)
{
_isInUse = val;
}
/**
* Gets the character that launched the buff.
* @return the effector
*/
public L2Character getEffector()
{
return _env.getCharacter();
}
/**
* Gets the target of the skill.
* @return the effected
*/
public L2Character getEffected()
{
return _env.getTarget();
}
/**
* Stops all the effects for this buff info.
* Removes effects stats.
* It will not remove the buff info from the effect list.
* Instead call {@link CharEffectList#stopSkillEffects(boolean, Skill)}
* @param removed if {@code true} the skill will be handled as removed
*/
public void stopAllEffects(boolean removed)
{
setRemoved(removed);
// Cancels the task that will end this buff info
if ((_scheduledFutureTimeTask != null) && !_scheduledFutureTimeTask.isCancelled())
{
_scheduledFutureTimeTask.cancel(false);
}
// Remove stats
removeStats();
finishEffects();
}
public void initializeEffects()
{
// When effects are initialized, the successfully landed.
if (_env.getTarget().isPlayer() && !_env.getSkill().isPassive())
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT);
sm.addSkillName(_env.getSkill());
_env.getTarget().sendPacket(sm);
}
// Creates a task that will stop all the effects.
if (_abnormalTime > 0)
{
_effectTimeTask = new BuffTimeTask(this);
_scheduledFutureTimeTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(_effectTimeTask, 0, 1000L);
}
applyAbnormalVisualEffects();
for (AbstractEffect effect : _effects)
{
if (effect.isInstant())
{
continue;
}
// Call on start.
effect.onStart(this);
// If it's a continuous effect, if has ticks schedule a task with period, otherwise schedule a simple task to end it.
if (effect.getTicks() > 0)
{
// The task for the effect ticks
final EffectTickTask effectTask = new EffectTickTask(this, effect);
final ScheduledFuture> scheduledFuture = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(effectTask, effect.getTicks() * Config.EFFECT_TICK_RATIO, effect.getTicks() * Config.EFFECT_TICK_RATIO);
// Adds the task for ticking
addTask(effect, new EffectTaskInfo(effectTask, scheduledFuture));
}
// Add stats.
_env.getTarget().addStatFuncs(effect.getStatFuncs(_env));
}
}
/**
* Called on each tick.
* Verify if the effect should end and the effect task should be cancelled.
* @param effect the effect that is ticking
* @param tickCount the tick count
*/
public void onTick(AbstractEffect effect, int tickCount)
{
boolean continueForever = false;
// If the effect is in use, allow it to affect the effected.
if (_isInUse)
{
// Callback for on action time event.
continueForever = effect.onActionTime(this);
}
if (!continueForever && _env.getSkill().isToggle())
{
final EffectTaskInfo task = getEffectTask(effect);
if (task != null)
{
task.getScheduledFuture().cancel(true); // Allow to finish current run.
_env.getTarget().getEffectList().remove(true, this); // Remove the buff from the effect list.
}
}
}
public void finishEffects()
{
removeAbnormalVisualEffects();
for (AbstractEffect effect : _effects)
{
// Instant effects shouldn't call onExit(..).
if ((effect != null) && !effect.isInstant())
{
effect.onExit(this);
}
}
// Cancels the ticking task.
if (_tasks != null)
{
for (Entry entry : _tasks.entrySet())
{
entry.getValue().getScheduledFuture().cancel(true);
}
}
// Sends the proper system message.
SystemMessageId smId = null;
if (!(_env.getTarget().isSummon() && !((L2Summon) _env.getTarget()).getOwner().hasSummon()))
{
if (_env.getSkill().isToggle())
{
smId = SystemMessageId.S1_HAS_BEEN_ABORTED;
}
else if (isRemoved())
{
smId = SystemMessageId.EFFECT_S1_HAS_BEEN_REMOVED;
}
else if (!_env.getSkill().isPassive())
{
smId = SystemMessageId.S1_HAS_WORN_OFF;
}
}
if (smId != null)
{
final SystemMessage sm = SystemMessage.getSystemMessage(smId);
sm.addSkillName(_env.getSkill());
_env.getTarget().sendPacket(sm);
}
if (this == _env.getTarget().getEffectList().getShortBuff())
{
_env.getTarget().getEffectList().shortBuffStatusUpdate(null);
}
}
/**
* Applies all the abnormal visual effects to the effected.
* Prevents multiple updates.
*/
private void applyAbnormalVisualEffects()
{
if ((_env.getTarget() == null) || (_env.getSkill() == null))
{
return;
}
if (_env.getSkill().hasAbnormalVisualEffects())
{
_env.getTarget().startAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffects());
}
if (_env.getTarget().isPlayer() && _env.getSkill().hasAbnormalVisualEffectsEvent())
{
_env.getTarget().startAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffectsEvent());
}
if (_env.getSkill().hasAbnormalVisualEffectsSpecial())
{
_env.getTarget().startAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffectsSpecial());
}
_env.getTarget().updateAbnormalEffect();
}
/**
* Removes all the abnormal visual effects from the effected.
* Prevents multiple updates.
*/
private void removeAbnormalVisualEffects()
{
if ((_env.getTarget() == null) || (_env.getSkill() == null))
{
return;
}
if (_env.getSkill().hasAbnormalVisualEffects())
{
_env.getTarget().stopAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffects());
}
if (_env.getTarget().isPlayer() && _env.getSkill().hasAbnormalVisualEffectsEvent())
{
_env.getTarget().stopAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffectsEvent());
}
if (_env.getSkill().hasAbnormalVisualEffectsSpecial())
{
_env.getTarget().stopAbnormalVisualEffect(false, _env.getSkill().getAbnormalVisualEffectsSpecial());
}
_env.getTarget().updateAbnormalEffect();
}
/**
* Adds the buff stats.
*/
public void addStats()
{
for (AbstractEffect effect : _effects)
{
_env.getTarget().addStatFuncs(effect.getStatFuncs(_env));
}
}
/**
* Removes the buff stats.
*/
public void removeStats()
{
for (AbstractEffect effect : _effects)
{
_env.getTarget().removeStatsOwner(effect);
}
_env.getTarget().removeStatsOwner(_env.getSkill());
}
/**
* Gets the effect tick count.
* @param effect the effect
* @return the current tick count
*/
public int getTickCount(AbstractEffect effect)
{
if (_tasks != null)
{
final EffectTaskInfo effectTaskInfo = _tasks.get(effect);
if (effectTaskInfo != null)
{
return effectTaskInfo.getEffectTask().getTickCount();
}
}
return 0;
}
}