/*
* 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 static silentium.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import silentium.commons.utils.Rnd;
import silentium.gameserver.ThreadPoolManager;
import silentium.gameserver.ai.CtrlIntention;
import silentium.gameserver.model.L2Skill;
import silentium.gameserver.model.actor.L2Character;
import silentium.gameserver.network.serverpackets.AbstractNpcInfo.NpcInfo;
import silentium.gameserver.network.serverpackets.NpcSay;
import silentium.gameserver.network.serverpackets.SocialAction;
import silentium.gameserver.network.serverpackets.StopMove;
import silentium.gameserver.templates.chars.L2NpcTemplate;
import silentium.gameserver.templates.skills.L2SkillType;
/**
* A tamed beast behaves a lot like a pet and has an owner. Some points :
* <ul>
* <li>feeding another beast to level 4 will vanish your actual tamed beast.</li>
* <li>running out of spices will vanish your actual tamed beast. There's a 1min food check timer.</li>
* <li>running out of the Beast Farm perimeter will vanish your tamed beast.</li>
* <li>no need to force attack it, it's a normal monster.</li>
* </ul>
* This class handles the running tasks (such as skills use and feed) of the mob.
*/
public final class L2TamedBeastInstance extends L2FeedableBeastInstance
{
private static final int MAX_DISTANCE_FROM_HOME = 13000;
private static final int MAX_DISTANCE_FROM_OWNER = 2000;
private static final int DURATION_CHECK_INTERVAL = 60000;
private static final int BUFF_INTERVAL = 5000;
private int _foodSkillId;
private L2PcInstance _owner;
private Future<?> _buffTask = null;
private Future<?> _foodTask = null;
// Messages used every minute by the tamed beast when he automatically eats food.
protected static final String[] TAMED_TEXT = { "Refills! Yeah!", "I am such a gluttonous beast, it is embarrassing! Ha ha.", "Your cooperative feeling has been getting better and better.", "I will help you!", "The weather is really good. Wanna go for a picnic?", "I really like you! This is tasty...", "If you do not have to leave this place, then I can help you.", "What can I help you with?", "I am not here only for food!", "Yam, yam, yam, yam, yam!" };
public L2TamedBeastInstance(int objectId, L2NpcTemplate template, L2PcInstance owner, int foodSkillId, int x, int y, int z)
{
super(objectId, template);
disableCoreAI(true); // Make it brainless.
setCurrentHp(getMaxHp());
setCurrentMp(getMaxMp());
setOwner(owner);
setFoodType(foodSkillId);
spawnMe(x, y, z);
}
public int getFoodType()
{
return _foodSkillId;
}
public void setFoodType(int foodItemId)
{
if (foodItemId > 0)
{
_foodSkillId = foodItemId;
// Cancel the food check, if existing.
if (_foodTask != null)
_foodTask.cancel(true);
// Start the food check.
_foodTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new FoodCheck(this), DURATION_CHECK_INTERVAL, DURATION_CHECK_INTERVAL);
}
}
@Override
public boolean doDie(L2Character killer)
{
if (!super.doDie(killer))
return false;
getAI().stopFollow();
// Clean up AIs.
if (_buffTask != null)
_buffTask.cancel(true);
if (_foodTask != null)
_foodTask.cancel(true);
// Clean up actual trained beast.
if (_owner != null)
_owner.setTrainedBeast(null);
// Clean up variables.
_buffTask = null;
_foodTask = null;
_owner = null;
_foodSkillId = 0;
return true;
}
public L2PcInstance getOwner()
{
return _owner;
}
public void setOwner(L2PcInstance owner)
{
if (owner != null)
{
_owner = owner;
setTitle(owner.getName());
setShowSummonAnimation(true);
broadcastPacket(new NpcInfo(this, owner));
owner.setTrainedBeast(this);
// always and automatically follow the owner.
getAI().startFollow(_owner, 200);
// instead of calculating this value each time, let's get this now and pass it on
int totalBuffsAvailable = 0;
for (L2Skill skill : getTemplate().getSkillsArray())
{
if (skill.getSkillType() == L2SkillType.BUFF)
totalBuffsAvailable++;
}
// Cancel the buff task, if existing.
if (_buffTask != null)
_buffTask.cancel(true);
// Start the buff task.
_buffTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new CheckOwnerBuffs(this, totalBuffsAvailable), BUFF_INTERVAL, BUFF_INTERVAL);
}
// Despawn if no owner
else
deleteMe();
}
/**
* The "Home" is considered as the central tower in middle of Wild Beast Reserve.
*
* @return true or false, depending of the location.
*/
protected boolean isTooFarFromHome()
{
return !(isInsideRadius(52335, -83086, MAX_DISTANCE_FROM_HOME, true));
}
@Override
public void deleteMe()
{
// Clean up AI.
if (_buffTask != null)
_buffTask.cancel(true);
_foodTask.cancel(true);
stopHpMpRegeneration();
// Clean up actual trained beast.
if (_owner != null)
_owner.setTrainedBeast(null);
// Clean up variables.
setTarget(null);
_buffTask = null;
_foodTask = null;
_owner = null;
_foodSkillId = 0;
// Remove the spawn.
super.deleteMe();
}
/**
* Notification triggered by the owner when the owner is attacked.<br>
* Tamed mobs will heal/recharge or debuff the enemy according to their skills.
*
* @param attacker
*/
public void onOwnerGotAttacked(L2Character attacker)
{
// Check if the owner is no longer around. If so, despawn.
if (_owner == null || !_owner.isOnline())
{
deleteMe();
return;
}
// If the owner is too far away, stop anything else and immediately run towards the owner.
if (!_owner.isInsideRadius(this, MAX_DISTANCE_FROM_OWNER, true, true))
{
getAI().startFollow(_owner, 200);
return;
}
// If the owner is dead or if the tamed beast is currently casting a spell,do nothing.
if (_owner.isDead() || isCastingNow())
return;
int proba = Rnd.get(3);
// Heal, 33% luck.
if (proba == 0)
{
// Happen only when owner's HPs < 50%
float HPRatio = ((float) _owner.getCurrentHp()) / _owner.getMaxHp();
if (HPRatio < 0.5)
{
for (L2Skill skill : getTemplate().getSkillsArray())
{
switch (skill.getSkillType())
{
case HEAL:
case HOT:
case BALANCE_LIFE:
case HEAL_PERCENT:
case HEAL_STATIC:
sitCastAndFollow(skill, _owner);
return;
}
}
}
}
// Debuff, 33% luck.
else if (proba == 1)
{
for (L2Skill skill : getTemplate().getSkillsArray())
{
// if the skill is a debuff, check if the attacker has it already
if ((skill.getSkillType() == L2SkillType.DEBUFF) && (attacker.getFirstEffect(skill) == null))
sitCastAndFollow(skill, attacker);
}
}
// Recharge, 33% luck.
else if (proba == 2)
{
// Happen only when owner's MPs < 50%
float MPRatio = ((float) _owner.getCurrentMp()) / _owner.getMaxMp();
if (MPRatio < 0.5)
{
for (L2Skill skill : getTemplate().getSkillsArray())
{
switch (skill.getSkillType())
{
case MANAHEAL:
case MANAHEAL_PERCENT:
case MANARECHARGE:
case MPHOT:
sitCastAndFollow(skill, _owner);
return;
}
}
}
}
}
/**
* Prepare and cast a skill:
* <ul>
* <li>First, prepare the beast for casting, by abandoning other actions.</li>
* <li>Next, call doCast in order to cast the spell.</li>
* <li>Finally, return to auto-following the owner.</li>
* </ul>
*
* @param skill
* The skill to cast.
* @param target
* The benefactor of the skill.
*/
protected void sitCastAndFollow(L2Skill skill, L2Character target)
{
stopMove(null);
broadcastPacket(new StopMove(this));
getAI().setIntention(AI_INTENTION_IDLE);
setTarget(target);
doCast(skill);
getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, _owner);
}
private static class FoodCheck implements Runnable
{
private final L2TamedBeastInstance _tamedBeast;
FoodCheck(L2TamedBeastInstance tamedBeast)
{
_tamedBeast = tamedBeast;
}
@Override
public void run()
{
// Verify first if the tamed beast is still in the good range. If not, delete it.
if (_tamedBeast.isTooFarFromHome())
{
// After deletion, don't go further.
_tamedBeast.deleteMe();
return;
}
// Destroy the food from owner's inventory ; if none is found, delete the pet.
if (_tamedBeast.getOwner().destroyItemByItemId("BeastMob", _tamedBeast.getFoodType(), 1, _tamedBeast, true))
{
_tamedBeast.broadcastPacket(new SocialAction(_tamedBeast, 2));
_tamedBeast.broadcastPacket(new NpcSay(_tamedBeast.getObjectId(), 0, _tamedBeast.getNpcId(), TAMED_TEXT[Rnd.get(TAMED_TEXT.length)]));
}
else
_tamedBeast.deleteMe();
}
}
private class CheckOwnerBuffs implements Runnable
{
private final L2TamedBeastInstance _tamedBeast;
private final int _numBuffs;
CheckOwnerBuffs(L2TamedBeastInstance tamedBeast, int numBuffs)
{
_tamedBeast = tamedBeast;
_numBuffs = numBuffs;
}
@Override
public void run()
{
L2PcInstance owner = _tamedBeast.getOwner();
// Check if the owner is no longer around. If so, despawn.
if (owner == null || !owner.isOnline())
{
deleteMe();
return;
}
// If the owner is too far away, stop anything else and immediately run towards the owner.
if (!isInsideRadius(owner, MAX_DISTANCE_FROM_OWNER, true, true))
{
getAI().startFollow(owner, 200);
return;
}
// If the owner is dead or if the tamed beast is currently casting a spell,do nothing.
if (owner.isDead() || isCastingNow())
return;
int totalBuffsOnOwner = 0;
int i = 0;
int rand = Rnd.get(_numBuffs);
L2Skill buffToGive = null;
// Get this npc's skills
for (L2Skill skill : getTemplate().getSkillsArray())
{
if (skill.getSkillType() == L2SkillType.BUFF)
{
if (i == rand)
buffToGive = skill;
i++;
if (owner.getFirstEffect(skill) != null)
totalBuffsOnOwner++;
}
}
/*
* If the owner has less than 60% of available buff, cast a random buff. That buff is casted only if the player hasn't it.
*/
if ((_numBuffs * 2 / 3) > totalBuffsOnOwner)
if (owner.getFirstEffect(buffToGive) == null)
_tamedBeast.sitCastAndFollow(buffToGive, owner);
getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, _tamedBeast.getOwner());
}
}
}