/* * 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()); } } }