/*
* 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 com.l2jserver.gameserver.model.actor.instance;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Future;
import javolution.util.FastList;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.ai.CtrlIntention;
import com.l2jserver.gameserver.datatables.PetDataTable;
import com.l2jserver.gameserver.datatables.SkillTable;
import com.l2jserver.gameserver.model.L2Effect;
import com.l2jserver.gameserver.model.L2ItemInstance;
import com.l2jserver.gameserver.model.L2PetData.L2PetSkillLearn;
import com.l2jserver.gameserver.model.L2Skill;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.skills.SkillHolder;
import com.l2jserver.gameserver.templates.chars.L2NpcTemplate;
import com.l2jserver.util.Rnd;
/**
*
* This class ...
*
* @version $Revision: 1.15.2.10.2.16 $ $Date: 2005/04/06 16:13:40 $
*/
public final class L2BabyPetInstance extends L2PetInstance
{
private static final int BUFF_CONTROL = 5771;
private static final int AWAKENING = 5753;
private FastList<SkillHolder> _buffs = null;
private SkillHolder _majorHeal = null;
private SkillHolder _minorHeal = null;
private SkillHolder _recharge = null;
private Future<?> _castTask;
private boolean _bufferMode = true;
public L2BabyPetInstance(int objectId, L2NpcTemplate template, L2PcInstance owner, L2ItemInstance control)
{
super(objectId, template, owner, control);
setInstanceType(InstanceType.L2BabyPetInstance);
}
public L2BabyPetInstance(int objectId, L2NpcTemplate template, L2PcInstance owner, L2ItemInstance control, byte level)
{
super(objectId, template, owner, control, level);
setInstanceType(InstanceType.L2BabyPetInstance);
}
@Override
public void onSpawn()
{
super.onSpawn();
L2Skill skill;
double healPower = 0;
for (L2PetSkillLearn psl : PetDataTable.getInstance().getPetData(getNpcId()).getAvailableSkills())
{
int id = psl.getId();
int lvl = PetDataTable.getInstance().getPetData(getNpcId()).getAvailableLevel(id, getLevel());
if (lvl == 0) // not enough pet lvl
continue;
skill = SkillTable.getInstance().getInfo(id, lvl);
if (skill != null)
{
if (skill.getId() == BUFF_CONTROL || skill.getId() == AWAKENING)
continue;
switch (skill.getSkillType())
{
case HEAL:
if (healPower == 0)
{
// set both heal types to the same skill
_majorHeal = new SkillHolder(skill);
_minorHeal = _majorHeal;
healPower = skill.getPower();
}
else
{
// another heal skill found - search for most powerful
if (skill.getPower() > healPower)
_majorHeal = new SkillHolder(skill);
else
_minorHeal = new SkillHolder(skill);
}
break;
case BUFF:
if (_buffs == null)
_buffs = new FastList<SkillHolder>();
_buffs.add(new SkillHolder(skill));
break;
case MANAHEAL:
case MANARECHARGE:
case MANA_BY_LEVEL:
_recharge = new SkillHolder(skill);
break;
}
}
}
startCastTask();
}
@Override
public boolean doDie(L2Character killer)
{
if (!super.doDie(killer))
return false;
stopCastTask();
abortCast();
return true;
}
@Override
public synchronized void unSummon (L2PcInstance owner)
{
stopCastTask();
abortCast();
super.unSummon(owner);
}
@Override
public void doRevive()
{
super.doRevive();
startCastTask();
}
@Override
public void onDecay()
{
super.onDecay();
if (_buffs != null)
_buffs.clear();
}
private final void startCastTask()
{
if ((_majorHeal != null || _buffs != null || _recharge != null)
&& _castTask == null && !isDead()) // cast task is not yet started and not dead (will start on revive)
_castTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new CastTask(this), 3000, 1000);
}
public void switchMode()
{
_bufferMode = !_bufferMode;
}
private final void stopCastTask()
{
if (_castTask != null)
{
_castTask.cancel(false);
_castTask = null;
}
}
protected void castSkill(L2Skill skill)
{
// casting automatically stops any other action (such as autofollow or a move-to).
// We need to gather the necessary info to restore the previous state.
final boolean previousFollowStatus = getFollowStatus();
// pet not following and owner outside cast range
if (!previousFollowStatus && !isInsideRadius(getOwner(), skill.getCastRange(), true, true))
return;
setTarget(getOwner());
useMagic(skill, false, false);
SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.PET_USES_S1);
msg.addSkillName(skill);
getOwner().sendPacket(msg);
// calling useMagic changes the follow status, if the babypet actually casts
// (as opposed to failing due some factors, such as too low MP, etc).
// if the status has actually been changed, revert it. Else, allow the pet to
// continue whatever it was trying to do.
// NOTE: This is important since the pet may have been told to attack a target.
// reverting the follow status will abort this attack! While aborting the attack
// in order to heal is natural, it is not acceptable to abort the attack on its own,
// merely because the timer stroke and without taking any other action...
if(previousFollowStatus != getFollowStatus())
setFollowStatus(previousFollowStatus);
}
private class CastTask implements Runnable
{
private final L2BabyPetInstance _baby;
private List<L2Skill> _currentBuffs = new FastList<L2Skill>();
public CastTask(L2BabyPetInstance baby)
{
_baby = baby;
}
public void run()
{
L2PcInstance owner = _baby.getOwner();
// if the owner is dead, merely wait for the owner to be resurrected
// if the pet is still casting from the previous iteration, allow the cast to complete...
if (owner != null
&& !owner.isDead()
&& !owner.isInvul()
&& !_baby.isCastingNow()
&& !_baby.isBetrayed()
&& !_baby.isMuted()
&& !_baby.isOutOfControl()
&& _bufferMode
&& _baby.getAI().getIntention() != CtrlIntention.AI_INTENTION_CAST)
{
L2Skill skill = null;
if (_majorHeal != null)
{
/**
* If the owner's HP is more than 80% for Baby Pets and 70% for Improved Baby pets, do nothing.
* If the owner's HP is very low, under 15% for Baby pets and under 30% for Improved Baby Pets, have 75% chances of using a strong heal.
* Otherwise, have 25% chances for weak heal.
*/
final double hpPercent = owner.getCurrentHp() / owner.getMaxHp();
final boolean isImprovedBaby = PetDataTable.isImprovedBaby(getNpcId());
if ((isImprovedBaby && hpPercent < 0.3) || (!isImprovedBaby && hpPercent < 0.15))
{
skill = _majorHeal.getSkill();
if (!_baby.isSkillDisabled(skill) && Rnd.get(100) <= 75)
{
if (_baby.getCurrentMp() >= skill.getMpConsume())
{
castSkill(skill);
return;
}
}
}
else if ((_majorHeal.getSkill() != _minorHeal.getSkill()) && ((isImprovedBaby && hpPercent < 0.7) || (!isImprovedBaby && hpPercent < 0.8)))
{
//Cast _minorHeal only if it's different than _majorHeal, then pet has two heals available.
skill = _minorHeal.getSkill();
if (!_baby.isSkillDisabled(skill) && Rnd.get(100) <= 25)
{
if (_baby.getCurrentMp() >= skill.getMpConsume())
{
castSkill(skill);
return;
}
}
}
}
if (_baby.getFirstEffect(BUFF_CONTROL) == null) // Buff Control is not active
{
// searching for usable buffs
if (_buffs != null && !_buffs.isEmpty())
{
for (SkillHolder i : _buffs)
{
skill = i.getSkill();
if (_baby.isSkillDisabled(skill))
continue;
if (_baby.getCurrentMp() >= skill.getMpConsume())
_currentBuffs.add(skill);
}
}
// buffs found, checking owner buffs
if (!_currentBuffs.isEmpty())
{
L2Effect[] effects = owner.getAllEffects();
Iterator<L2Skill> iter;
L2Skill currentSkill;
for (L2Effect e : effects)
{
if (e == null)
continue;
currentSkill = e.getSkill();
// skipping debuffs, passives, toggles
if (currentSkill.isDebuff()
|| currentSkill.isPassive()
|| currentSkill.isToggle())
continue;
// if buff does not need to be casted - remove it from list
iter = _currentBuffs.iterator();
while (iter.hasNext())
{
skill = iter.next();
if (currentSkill.getId() == skill.getId()
&& currentSkill.getLevel() >= skill.getLevel())
{
iter.remove();
}
else
{
// effect with same stacktype and greater or equal stackorder
if (skill.hasEffects()
&& !"none".equals(skill.getEffectTemplates()[0].abnormalType)
&& e.getAbnormalType().equals(skill.getEffectTemplates()[0].abnormalType)
&& e.getAbnormalLvl() >= skill.getEffectTemplates()[0].abnormalLvl)
{
iter.remove();
}
}
}
// no more buffs in list
if (_currentBuffs.isEmpty())
break;
}
// buffs list ready, casting random
if (!_currentBuffs.isEmpty())
{
castSkill(_currentBuffs.get(Rnd.get(_currentBuffs.size())));
_currentBuffs.clear();
return;
}
}
}
// buffs/heal not casted, trying recharge, if exist
if (_recharge != null
&& owner.isInCombat() // recharge casted only if owner in combat stance
&& owner.getCurrentMp()/owner.getMaxMp() < 0.6
&& Rnd.get(100) <= 60)
{
skill = _recharge.getSkill();
if (!_baby.isSkillDisabled(skill)
&& _baby.getCurrentMp() >= skill.getMpConsume())
{
castSkill(skill);
return;
}
}
}
}
}
}