/*
* 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.actor.instance;
import java.util.List;
import java.util.concurrent.Future;
import javolution.util.FastList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import silentium.commons.utils.Rnd;
import silentium.gameserver.ThreadPoolManager;
import silentium.gameserver.ai.CtrlEvent;
import silentium.gameserver.handler.ISkillHandler;
import silentium.gameserver.handler.SkillHandler;
import silentium.gameserver.instancemanager.DuelManager;
import silentium.gameserver.model.L2Effect;
import silentium.gameserver.model.L2Object;
import silentium.gameserver.model.L2Party;
import silentium.gameserver.model.L2Skill;
import silentium.gameserver.model.actor.L2Attackable;
import silentium.gameserver.model.actor.L2Character;
import silentium.gameserver.model.actor.L2Playable;
import silentium.gameserver.model.entity.TvTEvent;
import silentium.gameserver.model.entity.TvTEventTeam;
import silentium.gameserver.network.SystemMessageId;
import silentium.gameserver.network.serverpackets.MagicSkillUse;
import silentium.gameserver.skills.Formulas;
import silentium.gameserver.skills.l2skills.L2SkillDrain;
import silentium.gameserver.tables.SkillTable;
import silentium.gameserver.taskmanager.AttackStanceTaskManager;
import silentium.gameserver.templates.skills.L2SkillType;
public class L2CubicInstance
{
protected static final Logger _log = LoggerFactory.getLogger(L2CubicInstance.class.getName());
// Type of cubics
public static final int STORM_CUBIC = 1;
public static final int VAMPIRIC_CUBIC = 2;
public static final int LIFE_CUBIC = 3;
public static final int VIPER_CUBIC = 4;
public static final int POLTERGEIST_CUBIC = 5;
public static final int BINDING_CUBIC = 6;
public static final int AQUA_CUBIC = 7;
public static final int SPARK_CUBIC = 8;
public static final int ATTRACT_CUBIC = 9;
// Max range of cubic skills
public static final int MAX_MAGIC_RANGE = 900;
// Cubic skills
public static final int SKILL_CUBIC_HEAL = 4051;
public static final int SKILL_CUBIC_CURE = 5579;
protected L2PcInstance _owner;
protected L2Character _target;
protected int _id;
protected int _matk;
protected int _activationtime;
protected int _activationchance;
protected boolean _active;
private final boolean _givenByOther;
protected List<L2Skill> _skills = new FastList<>();
private Future<?> _disappearTask;
private Future<?> _actionTask;
public L2CubicInstance(L2PcInstance owner, int id, int level, int mAtk, int activationtime, int activationchance, int totallifetime, boolean givenByOther)
{
_owner = owner;
_id = id;
_matk = mAtk;
_activationtime = activationtime * 1000;
_activationchance = activationchance;
_active = false;
_givenByOther = givenByOther;
switch (_id)
{
case STORM_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4049, level));
break;
case VAMPIRIC_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4050, level));
break;
case LIFE_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4051, level));
doAction();
break;
case VIPER_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4052, level));
break;
case POLTERGEIST_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4053, level));
_skills.add(SkillTable.getInstance().getInfo(4054, level));
_skills.add(SkillTable.getInstance().getInfo(4055, level));
break;
case BINDING_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4164, level));
break;
case AQUA_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4165, level));
break;
case SPARK_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(4166, level));
break;
case ATTRACT_CUBIC:
_skills.add(SkillTable.getInstance().getInfo(5115, level));
_skills.add(SkillTable.getInstance().getInfo(5116, level));
break;
}
_disappearTask = ThreadPoolManager.getInstance().scheduleGeneral(new Disappear(), totallifetime); // disappear
}
public synchronized void doAction()
{
if (_active)
return;
_active = true;
switch (_id)
{
case AQUA_CUBIC:
case BINDING_CUBIC:
case SPARK_CUBIC:
case STORM_CUBIC:
case POLTERGEIST_CUBIC:
case VAMPIRIC_CUBIC:
case VIPER_CUBIC:
case ATTRACT_CUBIC:
_actionTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new Action(_activationchance), 0, _activationtime);
break;
case LIFE_CUBIC:
_actionTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new Heal(), 0, _activationtime);
break;
}
}
public int getId()
{
return _id;
}
public L2PcInstance getOwner()
{
return _owner;
}
public final int getMCriticalHit(L2Character target, L2Skill skill)
{
return _owner.getMCriticalHit(target, skill);
}
public int getMAtk()
{
return _matk;
}
public void stopAction()
{
_target = null;
if (_actionTask != null)
{
_actionTask.cancel(true);
_actionTask = null;
}
_active = false;
}
public void cancelDisappear()
{
if (_disappearTask != null)
{
_disappearTask.cancel(true);
_disappearTask = null;
}
}
/** this sets the enemy target for a cubic */
public void getCubicTarget()
{
try
{
_target = null;
L2Object ownerTarget = _owner.getTarget();
if (ownerTarget == null)
return;
// TvT event targeting
if (TvTEvent.isStarted() && TvTEvent.isPlayerParticipant(_owner.getObjectId()))
{
TvTEventTeam enemyTeam = TvTEvent.getParticipantEnemyTeam(_owner.getObjectId());
if (ownerTarget.getActingPlayer() != null)
{
L2PcInstance target = ownerTarget.getActingPlayer();
if (enemyTeam.containsPlayer(target.getObjectId()) && !(target.isDead()))
{
_target = (L2Character) ownerTarget;
}
}
return;
}
// Duel targeting
if (_owner.isInDuel())
{
L2PcInstance PlayerA = DuelManager.getInstance().getDuel(_owner.getDuelId()).getPlayerA();
L2PcInstance PlayerB = DuelManager.getInstance().getDuel(_owner.getDuelId()).getPlayerB();
if (DuelManager.getInstance().getDuel(_owner.getDuelId()).isPartyDuel())
{
L2Party partyA = PlayerA.getParty();
L2Party partyB = PlayerB.getParty();
L2Party partyEnemy = null;
if (partyA != null)
{
if (partyA.getPartyMembers().contains(_owner))
if (partyB != null)
partyEnemy = partyB;
else
_target = PlayerB;
else
partyEnemy = partyA;
}
else
{
if (PlayerA == _owner)
if (partyB != null)
partyEnemy = partyB;
else
_target = PlayerB;
else
_target = PlayerA;
}
if (_target == PlayerA || _target == PlayerB)
if (_target == ownerTarget)
return;
if (partyEnemy != null)
{
if (partyEnemy.getPartyMembers().contains(ownerTarget))
_target = (L2Character) ownerTarget;
return;
}
}
if (PlayerA != _owner && ownerTarget == PlayerA)
{
_target = PlayerA;
return;
}
if (PlayerB != _owner && ownerTarget == PlayerB)
{
_target = PlayerB;
return;
}
_target = null;
return;
}
// Olympiad targeting
if (_owner.isInOlympiadMode())
{
if (_owner.isOlympiadStart())
{
if (ownerTarget instanceof L2Playable)
{
final L2PcInstance targetPlayer = ownerTarget.getActingPlayer();
if (targetPlayer != null && targetPlayer.getOlympiadGameId() == _owner.getOlympiadGameId() && targetPlayer.getOlympiadSide() != _owner.getOlympiadSide())
_target = (L2Character) ownerTarget;
}
}
return;
}
// test owners target if it is valid then use it
if (ownerTarget instanceof L2Character && ownerTarget != _owner.getPet() && ownerTarget != _owner)
{
// target mob which has aggro on you or your summon
if (ownerTarget instanceof L2Attackable)
{
if (((L2Attackable) ownerTarget).getAggroList().get(_owner) != null && !((L2Attackable) ownerTarget).isDead())
{
_target = (L2Character) ownerTarget;
return;
}
if (_owner.getPet() != null)
{
if (((L2Attackable) ownerTarget).getAggroList().get(_owner.getPet()) != null && !((L2Attackable) ownerTarget).isDead())
{
_target = (L2Character) ownerTarget;
return;
}
}
}
// get target in pvp or in siege
L2PcInstance enemy = null;
if ((_owner.getPvpFlag() > 0 && !_owner.isInsideZone(L2Character.ZONE_PEACE)) || _owner.isInsideZone(L2Character.ZONE_PVP))
{
if (!((L2Character) ownerTarget).isDead())
enemy = ownerTarget.getActingPlayer();
if (enemy != null)
{
boolean targetIt = true;
if (_owner.getParty() != null)
{
if (_owner.getParty().getPartyMembers().contains(enemy))
targetIt = false;
else if (_owner.getParty().getCommandChannel() != null)
{
if (_owner.getParty().getCommandChannel().getMembers().contains(enemy))
targetIt = false;
}
}
if (_owner.getClan() != null && !_owner.isInsideZone(L2Character.ZONE_PVP))
{
if (_owner.getClan().isMember(enemy.getObjectId()))
targetIt = false;
if (_owner.getAllyId() > 0 && enemy.getAllyId() > 0)
{
if (_owner.getAllyId() == enemy.getAllyId())
targetIt = false;
}
}
if (enemy.getPvpFlag() == 0 && !enemy.isInsideZone(L2Character.ZONE_PVP))
targetIt = false;
if (enemy.isInsideZone(L2Character.ZONE_PEACE))
targetIt = false;
if (_owner.getSiegeState() > 0 && _owner.getSiegeState() == enemy.getSiegeState())
targetIt = false;
if (!enemy.isVisible())
targetIt = false;
if (targetIt)
{
_target = enemy;
return;
}
}
}
}
}
catch (Exception e)
{
_log.error("", e);
}
}
private class Action implements Runnable
{
private final int _chance;
Action(int chance)
{
_chance = chance;
// run task
}
@Override
public void run()
{
try
{
if (_owner.isDead() || !_owner.isOnline())
{
stopAction();
_owner.delCubic(_id);
_owner.broadcastUserInfo();
cancelDisappear();
return;
}
if (!AttackStanceTaskManager.getInstance().getAttackStanceTask(_owner))
{
if (_owner.getPet() != null)
{
if (!AttackStanceTaskManager.getInstance().getAttackStanceTask(_owner.getPet()))
{
stopAction();
return;
}
}
else
{
stopAction();
return;
}
}
L2Skill skill = null;
if (Rnd.get(1, 100) < _chance)
{
skill = _skills.get(Rnd.get(_skills.size()));
if (skill != null)
{
if (skill.getId() == SKILL_CUBIC_HEAL)
{
// friendly skill, so we look a target in owner's party
cubicTargetForHeal();
}
else
{
// offensive skill, we look for an enemy target
getCubicTarget();
if (!isInCubicRange(_owner, _target))
_target = null;
}
L2Character target = _target; // copy to avoid npe
if ((target != null) && (!target.isDead()))
{
if (_log.isTraceEnabled())
{
_log.trace("L2CubicInstance: Action.run();");
_log.trace("Cubic Id: " + _id + " Target: " + target.getName() + " distance: " + Math.sqrt(target.getDistanceSq(_owner.getX(), _owner.getY(), _owner.getZ())));
}
_owner.broadcastPacket(new MagicSkillUse(_owner, target, skill.getId(), skill.getLevel(), 0, 0));
L2SkillType type = skill.getSkillType();
ISkillHandler handler = SkillHandler.getInstance().getSkillHandler(skill.getSkillType());
L2Character[] targets = { target };
if ((type == L2SkillType.PARALYZE) || (type == L2SkillType.STUN) || (type == L2SkillType.ROOT) || (type == L2SkillType.AGGDAMAGE))
{
_log.trace("L2CubicInstance: Action.run() handler " + type);
useCubicDisabler(type, L2CubicInstance.this, skill, targets);
}
else if (type == L2SkillType.MDAM)
{
_log.trace("L2CubicInstance: Action.run() handler " + type);
useCubicMdam(L2CubicInstance.this, skill, targets);
}
else if ((type == L2SkillType.POISON) || (type == L2SkillType.DEBUFF) || (type == L2SkillType.DOT))
{
_log.trace("L2CubicInstance: Action.run() handler " + type);
useCubicContinuous(L2CubicInstance.this, skill, targets);
}
else if (type == L2SkillType.DRAIN)
{
_log.trace("L2CubicInstance: Action.run() skill " + type);
((L2SkillDrain) skill).useCubicSkill(L2CubicInstance.this, targets);
}
else
{
handler.useSkill(_owner, skill, targets);
_log.trace("L2CubicInstance: Action.run(); other handler");
}
}
}
}
}
catch (Exception e)
{
_log.error("", e);
}
}
}
public void useCubicContinuous(L2CubicInstance activeCubic, L2Skill skill, L2Object[] targets)
{
for (L2Character target : (L2Character[]) targets)
{
if (target == null || target.isDead())
continue;
if (skill.isOffensive())
{
byte shld = Formulas.calcShldUse(activeCubic.getOwner(), target, skill);
boolean acted = Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld);
if (!acted)
{
activeCubic.getOwner().sendPacket(SystemMessageId.ATTACK_FAILED);
continue;
}
}
// if this is a debuff let the duel manager know about it
// so the debuff can be removed after the duel
// (player & target must be in the same duel)
if (target instanceof L2PcInstance && ((L2PcInstance) target).isInDuel() && skill.getSkillType() == L2SkillType.DEBUFF && activeCubic.getOwner().getDuelId() == ((L2PcInstance) target).getDuelId())
{
DuelManager dm = DuelManager.getInstance();
for (L2Effect debuff : skill.getEffects(activeCubic.getOwner(), target))
if (debuff != null)
dm.onBuff(((L2PcInstance) target), debuff);
}
else
skill.getEffects(activeCubic, target, null);
}
}
public void useCubicMdam(L2CubicInstance activeCubic, L2Skill skill, L2Object[] targets)
{
for (L2Character target : (L2Character[]) targets)
{
if (target == null)
continue;
if (target.isAlikeDead())
{
if (target instanceof L2PcInstance)
target.stopFakeDeath(true);
else
continue;
}
boolean mcrit = Formulas.calcMCrit(activeCubic.getMCriticalHit(target, skill));
byte shld = Formulas.calcShldUse(activeCubic.getOwner(), target, skill);
int damage = (int) Formulas.calcMagicDam(activeCubic, target, skill, mcrit, shld);
/*
* If target is reflecting the skill then no damage is done Ignoring vengance-like reflections
*/
if ((Formulas.calcSkillReflect(target, skill) & Formulas.SKILL_REFLECT_SUCCEED) > 0)
damage = 0;
_log.trace("L2SkillMdam: useCubicSkill() -> damage = " + damage);
if (damage > 0)
{
// Manage cast break of the target (calculating rate, sending message...)
Formulas.calcCastBreak(target, damage);
activeCubic.getOwner().sendDamageMessage(target, damage, mcrit, false, false);
if (skill.hasEffects())
{
// activate attacked effects, if any
target.stopSkillEffects(skill.getId());
if (target.getFirstEffect(skill) != null)
target.removeEffect(target.getFirstEffect(skill));
if (Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld))
skill.getEffects(activeCubic, target, null);
}
target.reduceCurrentHp(damage, activeCubic.getOwner(), skill);
}
}
}
public void useCubicDisabler(L2SkillType type, L2CubicInstance activeCubic, L2Skill skill, L2Object[] targets)
{
_log.trace("Disablers: useCubicSkill()");
for (L2Character target : (L2Character[]) targets)
{
if (target == null || target.isDead()) // bypass if target is null or dead
continue;
byte shld = Formulas.calcShldUse(activeCubic.getOwner(), target, skill);
switch (type)
{
case STUN:
{
if (Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld))
{
// if this is a debuff let the duel manager know about it
// so the debuff can be removed after the duel
// (player & target must be in the same duel)
if (target instanceof L2PcInstance && ((L2PcInstance) target).isInDuel() && skill.getSkillType() == L2SkillType.DEBUFF && activeCubic.getOwner().getDuelId() == ((L2PcInstance) target).getDuelId())
{
DuelManager dm = DuelManager.getInstance();
for (L2Effect debuff : skill.getEffects(activeCubic.getOwner(), target))
if (debuff != null)
dm.onBuff(((L2PcInstance) target), debuff);
}
else
skill.getEffects(activeCubic, target, null);
_log.trace("Disablers: useCubicSkill() -> success");
}
else
{
_log.trace("Disablers: useCubicSkill() -> failed");
}
break;
}
case PARALYZE: // use same as root for now
{
if (Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld))
{
// if this is a debuff let the duel manager know about it
// so the debuff can be removed after the duel
// (player & target must be in the same duel)
if (target instanceof L2PcInstance && ((L2PcInstance) target).isInDuel() && skill.getSkillType() == L2SkillType.DEBUFF && activeCubic.getOwner().getDuelId() == ((L2PcInstance) target).getDuelId())
{
DuelManager dm = DuelManager.getInstance();
for (L2Effect debuff : skill.getEffects(activeCubic.getOwner(), target))
if (debuff != null)
dm.onBuff(((L2PcInstance) target), debuff);
}
else
skill.getEffects(activeCubic, target, null);
_log.trace("Disablers: useCubicSkill() -> success");
}
else
{
_log.trace("Disablers: useCubicSkill() -> failed");
}
break;
}
case CANCEL_DEBUFF:
{
L2Effect[] effects = target.getAllEffects();
if (effects == null || effects.length == 0)
break;
int count = (skill.getMaxNegatedEffects() > 0) ? 0 : -2;
for (L2Effect e : effects)
{
if (e.getSkill().isDebuff() && count < skill.getMaxNegatedEffects())
{
// Do not remove raid curse skills
if (e.getSkill().getId() != 4215 && e.getSkill().getId() != 4515 && e.getSkill().getId() != 4082)
{
e.exit();
if (count > -1)
count++;
}
}
}
break;
}
case ROOT:
{
if (Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld))
{
// if this is a debuff let the duel manager know about it
// so the debuff can be removed after the duel
// (player & target must be in the same duel)
if (target instanceof L2PcInstance && ((L2PcInstance) target).isInDuel() && skill.getSkillType() == L2SkillType.DEBUFF && activeCubic.getOwner().getDuelId() == ((L2PcInstance) target).getDuelId())
{
DuelManager dm = DuelManager.getInstance();
for (L2Effect debuff : skill.getEffects(activeCubic.getOwner(), target))
if (debuff != null)
dm.onBuff(((L2PcInstance) target), debuff);
}
else
skill.getEffects(activeCubic, target, null);
_log.trace("Disablers: useCubicSkill() -> success");
}
else
{
_log.trace("Disablers: useCubicSkill() -> failed");
}
break;
}
case AGGDAMAGE:
{
if (Formulas.calcCubicSkillSuccess(activeCubic, target, skill, shld))
{
if (target instanceof L2Attackable)
target.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, activeCubic.getOwner(), (int) ((150 * skill.getPower()) / (target.getLevel() + 7)));
skill.getEffects(activeCubic, target, null);
_log.trace("Disablers: useCubicSkill() -> success");
}
else
{
_log.trace("Disablers: useCubicSkill() -> failed");
}
break;
}
}
}
}
/**
* @param owner
* @param target
* @return true if the target is inside of the owner's max Cubic range
*/
public boolean isInCubicRange(L2Character owner, L2Character target)
{
if (owner == null || target == null)
return false;
int x, y, z;
int range = MAX_MAGIC_RANGE;
x = (owner.getX() - target.getX());
y = (owner.getY() - target.getY());
z = (owner.getZ() - target.getZ());
return ((x * x) + (y * y) + (z * z) <= (range * range));
}
/** this sets the friendly target for a cubic */
public void cubicTargetForHeal()
{
L2Character target = null;
double percentleft = 100.0;
L2Party party = _owner.getParty();
// if owner is in a duel but not in a party duel, then it is the same as he does not have a
// party
if (_owner.isInDuel())
if (!DuelManager.getInstance().getDuel(_owner.getDuelId()).isPartyDuel())
party = null;
if (party != null && !_owner.isInOlympiadMode())
{
// Get all visible objects in a spheric area near the L2Character
// Get a list of Party Members
List<L2PcInstance> partyList = party.getPartyMembers();
for (L2Character partyMember : partyList)
{
if (!partyMember.isDead())
{
// if party member not dead, check if he is in castrange of heal cubic
if (isInCubicRange(_owner, partyMember))
{
// member is in cubic casting range, check if he need heal and if he have
// the lowest HP
if (partyMember.getCurrentHp() < partyMember.getMaxHp())
{
if (percentleft > (partyMember.getCurrentHp() / partyMember.getMaxHp()))
{
percentleft = (partyMember.getCurrentHp() / partyMember.getMaxHp());
target = partyMember;
}
}
}
}
if (partyMember.getPet() != null)
{
if (partyMember.getPet().isDead())
continue;
// if party member's pet not dead, check if it is in castrange of heal cubic
if (!isInCubicRange(_owner, partyMember.getPet()))
continue;
// member's pet is in cubic casting range, check if he need heal and if he have
// the lowest HP
if (partyMember.getPet().getCurrentHp() < partyMember.getPet().getMaxHp())
{
if (percentleft > (partyMember.getPet().getCurrentHp() / partyMember.getPet().getMaxHp()))
{
percentleft = (partyMember.getPet().getCurrentHp() / partyMember.getPet().getMaxHp());
target = partyMember.getPet();
}
}
}
}
}
else
{
if (_owner.getCurrentHp() < _owner.getMaxHp())
{
percentleft = (_owner.getCurrentHp() / _owner.getMaxHp());
target = _owner;
}
if (_owner.getPet() != null)
if (!_owner.getPet().isDead() && _owner.getPet().getCurrentHp() < _owner.getPet().getMaxHp() && percentleft > (_owner.getPet().getCurrentHp() / _owner.getPet().getMaxHp()) && isInCubicRange(_owner, _owner.getPet()))
{
target = _owner.getPet();
}
}
_target = target;
}
public boolean givenByOther()
{
return _givenByOther;
}
private class Heal implements Runnable
{
Heal()
{
// run task
}
@Override
public void run()
{
if (_owner.isDead() || !_owner.isOnline())
{
stopAction();
_owner.delCubic(_id);
_owner.broadcastUserInfo();
cancelDisappear();
return;
}
try
{
L2Skill skill = null;
for (L2Skill sk : _skills)
{
if (sk.getId() == SKILL_CUBIC_HEAL)
{
skill = sk;
break;
}
}
if (skill != null)
{
cubicTargetForHeal();
L2Character target = _target;
if (target != null && !target.isDead())
{
if (target.getMaxHp() - target.getCurrentHp() > skill.getPower())
{
L2Character[] targets = { target };
ISkillHandler handler = SkillHandler.getInstance().getSkillHandler(skill.getSkillType());
if (handler != null)
{
handler.useSkill(_owner, skill, targets);
}
else
{
skill.useSkill(_owner, targets);
}
MagicSkillUse msu = new MagicSkillUse(_owner, target, skill.getId(), skill.getLevel(), 0, 0);
_owner.broadcastPacket(msu);
}
}
}
}
catch (Exception e)
{
_log.error("", e);
}
}
}
private class Disappear implements Runnable
{
Disappear()
{
// run task
}
@Override
public void run()
{
stopAction();
_owner.delCubic(_id);
_owner.broadcastUserInfo();
}
}
}