/*
* Copyright (C) 2004-2015 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.actor.instance;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jserver.Config;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.ai.CtrlEvent;
import com.l2jserver.gameserver.datatables.SkillData;
import com.l2jserver.gameserver.instancemanager.DuelManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2Party;
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.tasks.cubics.CubicAction;
import com.l2jserver.gameserver.model.actor.tasks.cubics.CubicDisappear;
import com.l2jserver.gameserver.model.actor.tasks.cubics.CubicHeal;
import com.l2jserver.gameserver.model.effects.L2EffectType;
import com.l2jserver.gameserver.model.entity.TvTEvent;
import com.l2jserver.gameserver.model.entity.TvTEventTeam;
import com.l2jserver.gameserver.model.interfaces.IIdentifiable;
import com.l2jserver.gameserver.model.skills.Skill;
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.util.Rnd;
public final class L2CubicInstance implements IIdentifiable
{
private static final Logger _log = Logger.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;
public static final int SMART_CUBIC_EVATEMPLAR = 10;
public static final int SMART_CUBIC_SHILLIENTEMPLAR = 11;
public static final int SMART_CUBIC_ARCANALORD = 12;
public static final int SMART_CUBIC_ELEMENTALMASTER = 13;
public static final int SMART_CUBIC_SPECTRALMASTER = 14;
// Max range of cubic skills
// TODO: Check/fix the max range
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;
private final L2PcInstance _owner;
private L2Character _target;
private final int _cubicId;
private final int _cubicPower;
private final int _cubicDelay;
private final int _cubicSkillChance;
private final int _cubicMaxCount;
private boolean _active;
private final boolean _givenByOther;
private final List<Skill> _skills = new ArrayList<>();
private Future<?> _disappearTask;
private Future<?> _actionTask;
public L2CubicInstance(L2PcInstance owner, int cubicId, int level, int cubicPower, int cubicDelay, int cubicSkillChance, int cubicMaxCount, int cubicDuration, boolean givenByOther)
{
_owner = owner;
_cubicId = cubicId;
_cubicPower = cubicPower;
_cubicDelay = cubicDelay * 1000;
_cubicSkillChance = cubicSkillChance;
_cubicMaxCount = cubicMaxCount;
_active = false;
_givenByOther = givenByOther;
switch (_cubicId)
{
case STORM_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4049, level));
break;
case VAMPIRIC_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4050, level));
break;
case LIFE_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4051, level));
doAction();
break;
case VIPER_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4052, level));
break;
case POLTERGEIST_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4053, level));
_skills.add(SkillData.getInstance().getSkill(4054, level));
_skills.add(SkillData.getInstance().getSkill(4055, level));
break;
case BINDING_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4164, level));
break;
case AQUA_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4165, level));
break;
case SPARK_CUBIC:
_skills.add(SkillData.getInstance().getSkill(4166, level));
break;
case ATTRACT_CUBIC:
_skills.add(SkillData.getInstance().getSkill(5115, level));
_skills.add(SkillData.getInstance().getSkill(5116, level));
break;
case SMART_CUBIC_ARCANALORD:
_skills.add(SkillData.getInstance().getSkill(4051, 7));
_skills.add(SkillData.getInstance().getSkill(4165, 9));
break;
case SMART_CUBIC_ELEMENTALMASTER:
_skills.add(SkillData.getInstance().getSkill(4049, 8));
_skills.add(SkillData.getInstance().getSkill(4166, 9));
break;
case SMART_CUBIC_SPECTRALMASTER:
_skills.add(SkillData.getInstance().getSkill(4049, 8));
_skills.add(SkillData.getInstance().getSkill(4052, 6));
break;
case SMART_CUBIC_EVATEMPLAR:
_skills.add(SkillData.getInstance().getSkill(4053, 8));
_skills.add(SkillData.getInstance().getSkill(4165, 9));
break;
case SMART_CUBIC_SHILLIENTEMPLAR:
_skills.add(SkillData.getInstance().getSkill(4049, 8));
_skills.add(SkillData.getInstance().getSkill(5115, 4));
break;
}
_disappearTask = ThreadPoolManager.getInstance().scheduleGeneral(new CubicDisappear(this), cubicDuration * 1000); // disappear
}
public synchronized void doAction()
{
if (_active)
{
return;
}
_active = true;
switch (_cubicId)
{
case AQUA_CUBIC:
case BINDING_CUBIC:
case SPARK_CUBIC:
case STORM_CUBIC:
case POLTERGEIST_CUBIC:
case VAMPIRIC_CUBIC:
case VIPER_CUBIC:
case ATTRACT_CUBIC:
case SMART_CUBIC_ARCANALORD:
case SMART_CUBIC_ELEMENTALMASTER:
case SMART_CUBIC_SPECTRALMASTER:
case SMART_CUBIC_EVATEMPLAR:
case SMART_CUBIC_SHILLIENTEMPLAR:
_actionTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new CubicAction(this, _cubicSkillChance), 0, _cubicDelay);
break;
case LIFE_CUBIC:
_actionTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new CubicHeal(this), 0, _cubicDelay);
break;
}
}
@Override
public int getId()
{
return _cubicId;
}
public L2PcInstance getOwner()
{
return _owner;
}
public int getCubicPower()
{
return _cubicPower;
}
public L2Character getTarget()
{
return _target;
}
public void setTarget(L2Character target)
{
_target = target;
}
public List<Skill> getSkills()
{
return _skills;
}
public int getCubicMaxCount()
{
return _cubicMaxCount;
}
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.getMembers().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.getMembers().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
final L2Summon pet = _owner.getPet();
if (ownerTarget.isCharacter() && (ownerTarget != pet) && !_owner.hasServitor(ownerTarget.getObjectId()) && (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.hasSummon())
{
if ((((L2Attackable) ownerTarget).getAggroList().get(pet) != null) && !((L2Attackable) ownerTarget).isDead())
{
_target = (L2Character) ownerTarget;
return;
}
for (L2Summon servitor : _owner.getServitors().values())
{
if ((((L2Attackable) ownerTarget).getAggroList().get(servitor) != null) && !((L2Attackable) ownerTarget).isDead())
{
_target = (L2Character) ownerTarget;
return;
}
}
}
}
// get target in pvp or in siege
L2PcInstance enemy = null;
if (((_owner.getPvpFlag() > 0) && !_owner.isInsideZone(ZoneId.PEACE)) || _owner.isInsideZone(ZoneId.PVP))
{
if (!((L2Character) ownerTarget).isDead())
{
enemy = ownerTarget.getActingPlayer();
}
if (enemy != null)
{
boolean targetIt = true;
if (_owner.getParty() != null)
{
if (_owner.getParty().getMembers().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(ZoneId.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(ZoneId.PVP))
{
targetIt = false;
}
if (enemy.isInsideZone(ZoneId.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.log(Level.SEVERE, "", e);
}
}
public void useCubicContinuous(Skill skill, L2Object[] targets)
{
for (L2Character target : (L2Character[]) targets)
{
if ((target == null) || target.isDead())
{
continue;
}
if (skill.isBad())
{
byte shld = Formulas.calcShldUse(getOwner(), target, skill);
boolean acted = Formulas.calcCubicSkillSuccess(this, target, skill, shld);
if (!acted)
{
getOwner().sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED);
continue;
}
}
// Apply effects
skill.applyEffects(getOwner(), this, target, false, false, true, 0);
// If this is a bad skill notify the duel manager, so it can be removed after the duel (player & target must be in the same duel).
if (target.isPlayer() && target.getActingPlayer().isInDuel() && skill.isBad() && (getOwner().getDuelId() == target.getActingPlayer().getDuelId()))
{
DuelManager.getInstance().onBuff(target.getActingPlayer(), skill);
}
}
}
/**
* @param activeCubic
* @param skill
* @param targets
*/
public void useCubicMdam(L2CubicInstance activeCubic, Skill skill, L2Object[] targets)
{
for (L2Character target : (L2Character[]) targets)
{
if (target == null)
{
continue;
}
if (target.isAlikeDead())
{
if (target.isPlayer())
{
target.stopFakeDeath(true);
}
else
{
continue;
}
}
boolean mcrit = Formulas.calcMCrit(activeCubic.getOwner().getMCriticalHit(target, skill));
byte shld = Formulas.calcShldUse(activeCubic.getOwner(), target, skill);
int damage = (int) Formulas.calcMagicDam(activeCubic, target, skill, mcrit, shld);
if (Config.DEBUG)
{
_log.info("L2SkillMdam: useCubicSkill() -> damage = " + damage);
}
if (damage > 0)
{
// Manage attack or cast break of the target (calculating rate, sending message...)
if (!target.isRaid() && Formulas.calcAtkBreak(target, damage))
{
target.breakAttack();
target.breakCast();
}
// Shield Deflect Magic: If target is reflecting the skill then no damage is done.
if (target.getStat().calcStat(Stats.VENGEANCE_SKILL_MAGIC_DAMAGE, 0, target, skill) > Rnd.get(100))
{
damage = 0;
}
else
{
activeCubic.getOwner().sendDamageMessage(target, damage, mcrit, false, false);
target.reduceCurrentHp(damage, activeCubic.getOwner(), skill);
}
}
}
}
public void useCubicDrain(L2CubicInstance activeCubic, Skill skill, L2Object[] targets)
{
if (Config.DEBUG)
{
_log.info("L2SkillDrain: useCubicSkill()");
}
for (L2Character target : (L2Character[]) targets)
{
if (target.isAlikeDead())
{
continue;
}
boolean mcrit = Formulas.calcMCrit(activeCubic.getOwner().getMCriticalHit(target, skill));
byte shld = Formulas.calcShldUse(activeCubic.getOwner(), target, skill);
int damage = (int) Formulas.calcMagicDam(activeCubic, target, skill, mcrit, shld);
if (Config.DEBUG)
{
_log.info("L2SkillDrain: useCubicSkill() -> damage = " + damage);
}
// TODO: Unhardcode fixed value
double hpAdd = (0.4 * damage);
L2PcInstance owner = activeCubic.getOwner();
double hp = ((owner.getCurrentHp() + hpAdd) > owner.getMaxHp() ? owner.getMaxHp() : (owner.getCurrentHp() + hpAdd));
owner.setCurrentHp(hp);
// Check to see if we should damage the target
if ((damage > 0) && !target.isDead())
{
target.reduceCurrentHp(damage, activeCubic.getOwner(), skill);
// Manage attack or cast break of the target (calculating rate, sending message...)
if (!target.isRaid() && Formulas.calcAtkBreak(target, damage))
{
target.breakAttack();
target.breakCast();
}
owner.sendDamageMessage(target, damage, mcrit, false, false);
}
}
}
public void useCubicDisabler(Skill skill, L2Object[] targets)
{
if (Config.DEBUG)
{
_log.info("Disablers: useCubicSkill()");
}
for (L2Character target : (L2Character[]) targets)
{
if ((target == null) || target.isDead())
{
continue;
}
byte shld = Formulas.calcShldUse(getOwner(), target, skill);
if (skill.hasEffectType(L2EffectType.STUN, L2EffectType.PARALYZE, L2EffectType.ROOT))
{
if (Formulas.calcCubicSkillSuccess(this, target, skill, shld))
{
// Apply effects
skill.applyEffects(getOwner(), this, target, false, false, true, 0);
// If this is a bad skill notify the duel manager, so it can be removed after the duel (player & target must be in the same duel).
if (target.isPlayer() && target.getActingPlayer().isInDuel() && skill.isBad() && (getOwner().getDuelId() == target.getActingPlayer().getDuelId()))
{
DuelManager.getInstance().onBuff(target.getActingPlayer(), skill);
}
if (Config.DEBUG)
{
_log.info("Disablers: useCubicSkill() -> success");
}
}
else
{
if (Config.DEBUG)
{
_log.info("Disablers: useCubicSkill() -> failed");
}
}
}
if (skill.hasEffectType(L2EffectType.AGGRESSION))
{
if (Formulas.calcCubicSkillSuccess(this, target, skill, shld))
{
if (target.isAttackable())
{
target.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getOwner(), (int) ((150 * skill.getPower()) / (target.getLevel() + 7)));
}
// Apply effects
skill.applyEffects(getOwner(), this, target, false, false, true, 0);
if (Config.DEBUG)
{
_log.info("Disablers: useCubicSkill() -> success");
}
}
else
{
if (Config.DEBUG)
{
_log.info("Disablers: useCubicSkill() -> failed");
}
}
}
}
}
/**
* @param owner
* @param target
* @return true if the target is inside of the owner's max Cubic range
*/
public static boolean isInCubicRange(L2Character owner, L2Character target)
{
if ((owner == null) || (target == null))
{
return false;
}
int x, y, z;
// temporary range check until real behavior of cubics is known/coded
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
for (L2Character partyMember : party.getMembers())
{
if (!partyMember.isDead())
{
// if party member not dead, check if he is in cast range 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;
}
}
}
}
final L2Summon pet = partyMember.getPet();
if (pet != null)
{
if (pet.isDead() || !isInCubicRange(_owner, pet))
{
continue;
}
// member's pet is in cubic casting range, check if he need heal and if he have
// the lowest HP
if (pet.getCurrentHp() < pet.getMaxHp())
{
if (percentleft > (pet.getCurrentHp() / pet.getMaxHp()))
{
percentleft = (pet.getCurrentHp() / pet.getMaxHp());
target = pet;
}
}
}
for (L2Summon s : partyMember.getServitors().values())
{
if (s.isDead() || !isInCubicRange(_owner, s))
{
continue;
}
// member's pet is in cubic casting range, check if he need heal and if he have
// the lowest HP
if (s.getCurrentHp() < s.getMaxHp())
{
if (percentleft > (s.getCurrentHp() / s.getMaxHp()))
{
percentleft = (s.getCurrentHp() / s.getMaxHp());
target = s;
}
}
}
}
}
else
{
if (_owner.getCurrentHp() < _owner.getMaxHp())
{
percentleft = (_owner.getCurrentHp() / _owner.getMaxHp());
target = _owner;
}
for (L2Summon summon : _owner.getServitors().values())
{
if (!summon.isDead() && (summon.getCurrentHp() < summon.getMaxHp()) && (percentleft > (summon.getCurrentHp() / summon.getMaxHp())) && isInCubicRange(_owner, summon))
{
target = summon;
}
}
final L2Summon pet = _owner.getPet();
if (pet != null)
{
if (!pet.isDead() && (pet.getCurrentHp() < pet.getMaxHp()) && (percentleft > (pet.getCurrentHp() / pet.getMaxHp())) && isInCubicRange(_owner, pet))
{
target = _owner.getPet();
}
}
}
_target = target;
}
public boolean givenByOther()
{
return _givenByOther;
}
}