/*
* 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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
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.L2DatabaseFactory;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.ai.CtrlIntention;
import com.l2jserver.gameserver.data.sql.impl.CharSummonTable;
import com.l2jserver.gameserver.data.sql.impl.SummonEffectsTable;
import com.l2jserver.gameserver.data.sql.impl.SummonEffectsTable.SummonEffect;
import com.l2jserver.gameserver.data.xml.impl.PetDataTable;
import com.l2jserver.gameserver.datatables.ItemTable;
import com.l2jserver.gameserver.datatables.SkillData;
import com.l2jserver.gameserver.enums.InstanceType;
import com.l2jserver.gameserver.enums.ItemLocation;
import com.l2jserver.gameserver.enums.PartyDistributionType;
import com.l2jserver.gameserver.handler.IItemHandler;
import com.l2jserver.gameserver.handler.ItemHandler;
import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
import com.l2jserver.gameserver.instancemanager.FortSiegeManager;
import com.l2jserver.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2PetData;
import com.l2jserver.gameserver.model.L2PetLevelData;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.actor.stat.PetStat;
import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jserver.gameserver.model.itemcontainer.Inventory;
import com.l2jserver.gameserver.model.itemcontainer.PetInventory;
import com.l2jserver.gameserver.model.items.L2Item;
import com.l2jserver.gameserver.model.items.L2Weapon;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
import com.l2jserver.gameserver.model.skills.AbnormalType;
import com.l2jserver.gameserver.model.skills.BuffInfo;
import com.l2jserver.gameserver.model.skills.EffectScope;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.model.zone.ZoneId;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
import com.l2jserver.gameserver.network.serverpackets.ExChangeNpcState;
import com.l2jserver.gameserver.network.serverpackets.ExUserInfoInvenWeight;
import com.l2jserver.gameserver.network.serverpackets.InventoryUpdate;
import com.l2jserver.gameserver.network.serverpackets.PetInventoryUpdate;
import com.l2jserver.gameserver.network.serverpackets.StopMove;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.taskmanager.DecayTaskManager;
import com.l2jserver.util.Rnd;
public class L2PetInstance extends L2Summon
{
protected static final Logger _logPet = Logger.getLogger(L2PetInstance.class.getName());
private static final String ADD_SKILL_SAVE = "INSERT INTO character_pet_skills_save (petObjItemId,skill_id,skill_level,remaining_time,buff_index) VALUES (?,?,?,?,?)";
private static final String RESTORE_SKILL_SAVE = "SELECT petObjItemId,skill_id,skill_level,remaining_time,buff_index FROM character_pet_skills_save WHERE petObjItemId=? ORDER BY buff_index ASC";
private static final String DELETE_SKILL_SAVE = "DELETE FROM character_pet_skills_save WHERE petObjItemId=?";
private int _curFed;
private final PetInventory _inventory;
private final int _controlObjectId;
private boolean _respawned;
private final boolean _mountable;
private Future<?> _feedTask;
private L2PetData _data;
private L2PetLevelData _leveldata;
/** The Experience before the last Death Penalty */
private long _expBeforeDeath = 0;
private int _curWeightPenalty = 0;
/**
* Creates a pet.
* @param template the pet NPC template
* @param owner the owner
* @param control the summoning item
*/
public L2PetInstance(L2NpcTemplate template, L2PcInstance owner, L2ItemInstance control)
{
this(template, owner, control, (byte) (template.getDisplayId() == 12564 ? owner.getLevel() : template.getLevel()));
}
/**
* Creates a pet.
* @param template the pet NPC template
* @param owner the pet NPC template
* @param control the summoning item
* @param level the level
*/
public L2PetInstance(L2NpcTemplate template, L2PcInstance owner, L2ItemInstance control, byte level)
{
super(template, owner);
setInstanceType(InstanceType.L2PetInstance);
_controlObjectId = control.getObjectId();
getStat().setLevel((byte) Math.max(level, PetDataTable.getInstance().getPetMinLevel(template.getId())));
_inventory = new PetInventory(this);
_inventory.restore();
int npcId = template.getId();
_mountable = PetDataTable.isMountable(npcId);
getPetData();
getPetLevelData();
}
public final L2PetLevelData getPetLevelData()
{
if (_leveldata == null)
{
_leveldata = PetDataTable.getInstance().getPetLevelData(getTemplate().getId(), getStat().getLevel());
}
return _leveldata;
}
public final L2PetData getPetData()
{
if (_data == null)
{
_data = PetDataTable.getInstance().getPetData(getTemplate().getId());
}
return _data;
}
public final void setPetData(L2PetLevelData value)
{
_leveldata = value;
}
/**
* Manage Feeding Task.<BR>
* Feed or kill the pet depending on hunger level.<br>
* If pet has food in inventory and feed level drops below 55% then consume food from inventory.<br>
* Send a broadcastStatusUpdate packet for this L2PetInstance
*/
class FeedTask implements Runnable
{
@Override
public void run()
{
try
{
final L2Summon pet = getOwner().getPet();
if ((getOwner() == null) || (pet == null) || (pet.getObjectId() != getObjectId()))
{
stopFeed();
return;
}
else if (getCurrentFed() > getFeedConsume())
{
setCurrentFed(getCurrentFed() - getFeedConsume());
}
else
{
setCurrentFed(0);
}
broadcastStatusUpdate();
List<Integer> foodIds = getPetData().getFood();
if (foodIds.isEmpty())
{
if (isUncontrollable())
{
// Owl Monk remove PK
if ((getTemplate().getId() == 16050) && (getOwner() != null))
{
getOwner().setPkKills(Math.max(0, getOwner().getPkKills() - Rnd.get(1, 6)));
}
sendPacket(SystemMessageId.THE_PET_IS_NOW_LEAVING);
deleteMe(getOwner());
}
else if (isHungry())
{
sendPacket(SystemMessageId.THERE_IS_NOT_MUCH_TIME_REMAINING_UNTIL_THE_PET_LEAVES);
}
return;
}
L2ItemInstance food = null;
for (int id : foodIds)
{
food = getInventory().getItemByItemId(id);
if (food != null)
{
break;
}
}
if ((food != null) && isHungry())
{
final IItemHandler handler = ItemHandler.getInstance().getHandler(food.getEtcItem());
if (handler != null)
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOUR_PET_WAS_HUNGRY_SO_IT_ATE_S1);
sm.addItemName(food.getId());
sendPacket(sm);
handler.useItem(L2PetInstance.this, food, false);
}
}
if (isUncontrollable())
{
sendPacket(SystemMessageId.YOUR_PET_IS_STARVING_AND_WILL_NOT_OBEY_UNTIL_IT_GETS_IT_S_FOOD_FEED_YOUR_PET);
}
}
catch (Exception e)
{
_logPet.log(Level.SEVERE, "Pet [ObjectId: " + getObjectId() + "] a feed task error has occurred", e);
}
}
private int getFeedConsume()
{
// if pet is attacking
if (isAttackingNow())
{
return getPetLevelData().getPetFeedBattle();
}
return getPetLevelData().getPetFeedNormal();
}
}
public synchronized static L2PetInstance spawnPet(L2NpcTemplate template, L2PcInstance owner, L2ItemInstance control)
{
if (L2World.getInstance().getPet(owner.getObjectId()) != null)
{
return null; // owner has a pet listed in world
}
final L2PetData data = PetDataTable.getInstance().getPetData(template.getId());
L2PetInstance pet = restore(control, template, owner);
// add the pet instance to world
if (pet != null)
{
pet.setTitle(owner.getName());
if (data.isSynchLevel() && (pet.getLevel() != owner.getLevel()))
{
pet.getStat().setLevel((byte) owner.getLevel());
pet.getStat().setExp(pet.getStat().getExpForLevel(owner.getLevel()));
}
L2World.getInstance().addPet(owner.getObjectId(), pet);
}
return pet;
}
@Override
public PetStat getStat()
{
return (PetStat) super.getStat();
}
@Override
public void initCharStat()
{
setStat(new PetStat(this));
}
public boolean isRespawned()
{
return _respawned;
}
@Override
public int getSummonType()
{
return 2;
}
@Override
public int getControlObjectId()
{
return _controlObjectId;
}
public L2ItemInstance getControlItem()
{
return getOwner().getInventory().getItemByObjectId(_controlObjectId);
}
public int getCurrentFed()
{
return _curFed;
}
public void setCurrentFed(int num)
{
if (num <= 0)
{
sendPacket(new ExChangeNpcState(getObjectId(), 0x64));
}
else if ((_curFed <= 0) && (num > 0))
{
sendPacket(new ExChangeNpcState(getObjectId(), 0x65));
}
_curFed = num > getMaxFed() ? getMaxFed() : num;
}
/**
* Returns the pet's currently equipped weapon instance (if any).
*/
@Override
public L2ItemInstance getActiveWeaponInstance()
{
for (L2ItemInstance item : getInventory().getItems())
{
if ((item.getItemLocation() == ItemLocation.PET_EQUIP) && (item.getItem().getBodyPart() == L2Item.SLOT_R_HAND))
{
return item;
}
}
return null;
}
/**
* Returns the pet's currently equipped weapon (if any).
*/
@Override
public L2Weapon getActiveWeaponItem()
{
L2ItemInstance weapon = getActiveWeaponInstance();
if (weapon == null)
{
return null;
}
return (L2Weapon) weapon.getItem();
}
@Override
public L2ItemInstance getSecondaryWeaponInstance()
{
// temporary? unavailable
return null;
}
@Override
public L2Weapon getSecondaryWeaponItem()
{
// temporary? unavailable
return null;
}
@Override
public PetInventory getInventory()
{
return _inventory;
}
/**
* Destroys item from inventory and send a Server->Client InventoryUpdate packet to the L2PcInstance.
* @param process : String Identifier of process triggering this action
* @param objectId : int Item Instance identifier of the item to be destroyed
* @param count : int Quantity of items to be destroyed
* @param reference : L2Object Object referencing current action like NPC selling item or previous item in transformation
* @param sendMessage : boolean Specifies whether to send message to Client about this action
* @return boolean informing if the action was successfull
*/
@Override
public boolean destroyItem(String process, int objectId, long count, L2Object reference, boolean sendMessage)
{
L2ItemInstance item = _inventory.destroyItem(process, objectId, count, getOwner(), reference);
if (item == null)
{
if (sendMessage)
{
sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT2);
}
return false;
}
// Send Pet inventory update packet
PetInventoryUpdate petIU = new PetInventoryUpdate();
petIU.addItem(item);
sendPacket(petIU);
if (sendMessage)
{
if (count > 1)
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1_S_DISAPPEARED);
sm.addItemName(item.getId());
sm.addLong(count);
sendPacket(sm);
}
else
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
sm.addItemName(item.getId());
sendPacket(sm);
}
}
return true;
}
/**
* Destroy item from inventory by using its <B>itemId</B> and send a Server->Client InventoryUpdate packet to the L2PcInstance.
* @param process : String Identifier of process triggering this action
* @param itemId : int Item identifier of the item to be destroyed
* @param count : int Quantity of items to be destroyed
* @param reference : L2Object Object referencing current action like NPC selling item or previous item in transformation
* @param sendMessage : boolean Specifies whether to send message to Client about this action
* @return boolean informing if the action was successfull
*/
@Override
public boolean destroyItemByItemId(String process, int itemId, long count, L2Object reference, boolean sendMessage)
{
L2ItemInstance item = _inventory.destroyItemByItemId(process, itemId, count, getOwner(), reference);
if (item == null)
{
if (sendMessage)
{
sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT2);
}
return false;
}
// Send Pet inventory update packet
PetInventoryUpdate petIU = new PetInventoryUpdate();
petIU.addItem(item);
sendPacket(petIU);
if (sendMessage)
{
if (count > 1)
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1_S_DISAPPEARED);
sm.addItemName(item.getId());
sm.addLong(count);
sendPacket(sm);
}
else
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
sm.addItemName(item.getId());
sendPacket(sm);
}
}
return true;
}
@Override
public void doPickupItem(L2Object object)
{
if (isDead())
{
return;
}
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
broadcastPacket(new StopMove(this));
if (!(object instanceof L2ItemInstance))
{
// dont try to pickup anything that is not an item :)
_logPet.warning(this + " trying to pickup wrong target." + object);
sendPacket(ActionFailed.STATIC_PACKET);
return;
}
boolean follow = getFollowStatus();
final L2ItemInstance target = (L2ItemInstance) object;
// Cursed weapons
if (CursedWeaponsManager.getInstance().isCursed(target.getId()))
{
SystemMessage smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1);
smsg.addItemName(target.getId());
sendPacket(smsg);
return;
}
else if (FortSiegeManager.getInstance().isCombat(target.getId()))
{
return;
}
SystemMessage smsg = null;
synchronized (target)
{
// Check if the target to pick up is visible
if (!target.isVisible())
{
// Send a Server->Client packet ActionFailed to this L2PcInstance
sendPacket(ActionFailed.STATIC_PACKET);
return;
}
if (!target.getDropProtection().tryPickUp(this))
{
sendPacket(ActionFailed.STATIC_PACKET);
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1);
smsg.addItemName(target);
sendPacket(smsg);
return;
}
if (((isInParty() && (getParty().getDistributionType() == PartyDistributionType.FINDERS_KEEPERS)) || !isInParty()) && !_inventory.validateCapacity(target))
{
sendPacket(ActionFailed.STATIC_PACKET);
sendPacket(SystemMessageId.YOUR_PET_CANNOT_CARRY_ANY_MORE_ITEMS);
return;
}
if ((target.getOwnerId() != 0) && (target.getOwnerId() != getOwner().getObjectId()) && !getOwner().isInLooterParty(target.getOwnerId()))
{
if (target.getId() == Inventory.ADENA_ID)
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1_ADENA);
smsg.addLong(target.getCount());
}
else if (target.getCount() > 1)
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S2_S1_S);
smsg.addItemName(target);
smsg.addLong(target.getCount());
}
else
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1);
smsg.addItemName(target);
}
sendPacket(ActionFailed.STATIC_PACKET);
sendPacket(smsg);
return;
}
if ((target.getItemLootShedule() != null) && ((target.getOwnerId() == getOwner().getObjectId()) || getOwner().isInLooterParty(target.getOwnerId())))
{
target.resetOwnerTimer();
}
// Remove from the ground!
target.pickupMe(this);
if (Config.SAVE_DROPPED_ITEM)
{
ItemsOnGroundManager.getInstance().removeObject(target);
}
}
// Herbs
if (target.getItem().hasExImmediateEffect())
{
IItemHandler handler = ItemHandler.getInstance().getHandler(target.getEtcItem());
if (handler == null)
{
_log.warning("No item handler registered for item ID: " + target.getId() + ".");
}
else
{
handler.useItem(this, target, false);
}
ItemTable.getInstance().destroyItem("Consume", target, getOwner(), null);
broadcastStatusUpdate();
}
else
{
if (target.getId() == Inventory.ADENA_ID)
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1_ADENA);
smsg.addLong(target.getCount());
sendPacket(smsg);
}
else if (target.getEnchantLevel() > 0)
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1_S2);
smsg.addInt(target.getEnchantLevel());
smsg.addItemName(target);
sendPacket(smsg);
}
else if (target.getCount() > 1)
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S2_S1_S);
smsg.addLong(target.getCount());
smsg.addItemName(target);
sendPacket(smsg);
}
else
{
smsg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1);
smsg.addItemName(target);
sendPacket(smsg);
}
// If owner is in party and it isnt finders keepers, distribute the item instead of stealing it -.-
if (getOwner().isInParty() && (getOwner().getParty().getDistributionType() != PartyDistributionType.FINDERS_KEEPERS))
{
getOwner().getParty().distributeItem(getOwner(), target);
}
else
{
final L2ItemInstance item = getInventory().addItem("Pickup", target, getOwner(), this);
// sendPacket(new PetItemList(getInventory().getItems()));
sendPacket(new PetInventoryUpdate(item));
}
}
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
if (follow)
{
followOwner();
}
}
@Override
public void deleteMe(L2PcInstance owner)
{
getInventory().transferItemsToOwner();
super.deleteMe(owner);
destroyControlItem(owner, false); // this should also delete the pet from the db
CharSummonTable.getInstance().getPets().remove(getOwner().getObjectId());
}
@Override
public boolean doDie(L2Character killer)
{
L2PcInstance owner = getOwner();
if ((owner != null) && !owner.isInDuel() && (!isInsideZone(ZoneId.PVP) || isInsideZone(ZoneId.SIEGE)))
{
deathPenalty();
}
if (!super.doDie(killer, true))
{
return false;
}
stopFeed();
sendPacket(SystemMessageId.THE_PET_HAS_BEEN_KILLED_IF_YOU_DON_T_RESURRECT_IT_WITHIN_24_HOURS_THE_PET_S_BODY_WILL_DISAPPEAR_ALONG_WITH_ALL_THE_PET_S_ITEMS);
DecayTaskManager.getInstance().add(this);
// do not decrease exp if is in duel, arena
return true;
}
@Override
public void doRevive()
{
getOwner().removeReviving();
super.doRevive();
// stopDecay
DecayTaskManager.getInstance().cancel(this);
startFeed();
if (!isHungry())
{
setRunning();
}
getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE, null);
}
@Override
public void doRevive(double revivePower)
{
// Restore the pet's lost experience,
// depending on the % return of the skill used (based on its power).
restoreExp(revivePower);
doRevive();
}
/**
* Transfers item to another inventory
* @param process string identifier of process triggering this action
* @param objectId Item Identifier of the item to be transfered
* @param count Quantity of items to be transfered
* @param target
* @param actor the player requesting the item transfer
* @param reference Object referencing current action like NPC selling item or previous item in transformation
* @return L2ItemInstance corresponding to the new item or the updated item in inventory
*/
public L2ItemInstance transferItem(String process, int objectId, long count, Inventory target, L2PcInstance actor, L2Object reference)
{
L2ItemInstance oldItem = getInventory().getItemByObjectId(objectId);
L2ItemInstance playerOldItem = target.getItemByItemId(oldItem.getId());
L2ItemInstance newItem = getInventory().transferItem(process, objectId, count, target, actor, reference);
if (newItem == null)
{
return null;
}
// Send inventory update packet
PetInventoryUpdate petIU = new PetInventoryUpdate();
if ((oldItem.getCount() > 0) && (oldItem != newItem))
{
petIU.addModifiedItem(oldItem);
}
else
{
petIU.addRemovedItem(oldItem);
}
sendPacket(petIU);
// Send target update packet
if (!newItem.isStackable())
{
InventoryUpdate iu = new InventoryUpdate();
iu.addNewItem(newItem);
sendPacket(iu);
}
else if ((playerOldItem != null) && newItem.isStackable())
{
InventoryUpdate iu = new InventoryUpdate();
iu.addModifiedItem(newItem);
sendPacket(iu);
}
return newItem;
}
/**
* Remove the Pet from DB and its associated item from the player inventory
* @param owner The owner from whose inventory we should delete the item
* @param evolve
*/
public void destroyControlItem(L2PcInstance owner, boolean evolve)
{
// remove the pet instance from world
L2World.getInstance().removePet(owner.getObjectId());
// delete from inventory
try
{
L2ItemInstance removedItem;
if (evolve)
{
removedItem = owner.getInventory().destroyItem("Evolve", getControlObjectId(), 1, getOwner(), this);
}
else
{
removedItem = owner.getInventory().destroyItem("PetDestroy", getControlObjectId(), 1, getOwner(), this);
if (removedItem != null)
{
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
sm.addItemName(removedItem);
owner.sendPacket(sm);
}
}
if (removedItem == null)
{
_log.warning("Couldn't destroy pet control item for " + owner + " pet: " + this + " evolve: " + evolve);
}
else
{
InventoryUpdate iu = new InventoryUpdate();
iu.addRemovedItem(removedItem);
owner.sendPacket(iu);
owner.sendPacket(new ExUserInfoInvenWeight(owner));
owner.broadcastUserInfo();
L2World.getInstance().removeObject(removedItem);
}
}
catch (Exception e)
{
_logPet.log(Level.WARNING, "Error while destroying control item: " + e.getMessage(), e);
}
// pet control item no longer exists, delete the pet from the db
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("DELETE FROM pets WHERE item_obj_id = ?"))
{
statement.setInt(1, getControlObjectId());
statement.execute();
}
catch (Exception e)
{
_logPet.log(Level.SEVERE, "Failed to delete Pet [ObjectId: " + getObjectId() + "]", e);
}
}
public void dropAllItems()
{
try
{
for (L2ItemInstance item : getInventory().getItems())
{
dropItemHere(item);
}
}
catch (Exception e)
{
_logPet.log(Level.WARNING, "Pet Drop Error: " + e.getMessage(), e);
}
}
public void dropItemHere(L2ItemInstance dropit, boolean protect)
{
dropit = getInventory().dropItem("Drop", dropit.getObjectId(), dropit.getCount(), getOwner(), this);
if (dropit != null)
{
if (protect)
{
dropit.getDropProtection().protect(getOwner());
}
_logPet.finer("Item id to drop: " + dropit.getId() + " amount: " + dropit.getCount());
dropit.dropMe(this, getX(), getY(), getZ() + 100);
}
}
public void dropItemHere(L2ItemInstance dropit)
{
dropItemHere(dropit, false);
}
/** @return Returns the mount able. */
@Override
public boolean isMountable()
{
return _mountable;
}
private static L2PetInstance restore(L2ItemInstance control, L2NpcTemplate template, L2PcInstance owner)
{
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("SELECT item_obj_id, name, level, curHp, curMp, exp, sp, fed FROM pets WHERE item_obj_id=?"))
{
L2PetInstance pet;
statement.setInt(1, control.getObjectId());
try (ResultSet rset = statement.executeQuery())
{
if (!rset.next())
{
if (template.isType("L2BabyPet"))
{
pet = new L2BabyPetInstance(template, owner, control);
}
else
{
pet = new L2PetInstance(template, owner, control);
}
return pet;
}
if (template.isType("L2BabyPet"))
{
pet = new L2BabyPetInstance(template, owner, control, rset.getByte("level"));
}
else
{
pet = new L2PetInstance(template, owner, control, rset.getByte("level"));
}
pet._respawned = true;
pet.setName(rset.getString("name"));
long exp = rset.getLong("exp");
L2PetLevelData info = PetDataTable.getInstance().getPetLevelData(pet.getId(), pet.getLevel());
// DS: update experience based by level
// Avoiding pet delevels due to exp per level values changed.
if ((info != null) && (exp < info.getPetMaxExp()))
{
exp = info.getPetMaxExp();
}
pet.getStat().setExp(exp);
pet.getStat().setSp(rset.getInt("sp"));
pet.getStatus().setCurrentHp(rset.getInt("curHp"));
pet.getStatus().setCurrentMp(rset.getInt("curMp"));
pet.getStatus().setCurrentCp(pet.getMaxCp());
if (rset.getDouble("curHp") < 1)
{
pet.setIsDead(true);
pet.stopHpMpRegeneration();
}
pet.setCurrentFed(rset.getInt("fed"));
}
return pet;
}
catch (Exception e)
{
_logPet.log(Level.WARNING, "Could not restore pet data for owner: " + owner + " - " + e.getMessage(), e);
}
return null;
}
@Override
public void setRestoreSummon(boolean val)
{
_restoreSummon = val;
}
@Override
public final void stopSkillEffects(boolean removed, int skillId)
{
super.stopSkillEffects(removed, skillId);
List<SummonEffect> effects = SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId());
if ((effects != null) && !effects.isEmpty())
{
for (SummonEffect effect : effects)
{
if (effect.getSkill().getId() == skillId)
{
SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId()).remove(effect);
}
}
}
}
@Override
public void storeMe()
{
if (getControlObjectId() == 0)
{
// this is a summon, not a pet, don't store anything
return;
}
if (!Config.RESTORE_PET_ON_RECONNECT)
{
_restoreSummon = false;
}
String req;
if (!isRespawned())
{
req = "INSERT INTO pets (name,level,curHp,curMp,exp,sp,fed,ownerId,restore,item_obj_id) " + "VALUES (?,?,?,?,?,?,?,?,?,?)";
}
else
{
req = "UPDATE pets SET name=?,level=?,curHp=?,curMp=?,exp=?,sp=?,fed=?,ownerId=?,restore=? " + "WHERE item_obj_id = ?";
}
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement(req))
{
statement.setString(1, getName());
statement.setInt(2, getStat().getLevel());
statement.setDouble(3, getStatus().getCurrentHp());
statement.setDouble(4, getStatus().getCurrentMp());
statement.setLong(5, getStat().getExp());
statement.setLong(6, getStat().getSp());
statement.setInt(7, getCurrentFed());
statement.setInt(8, getOwner().getObjectId());
statement.setString(9, String.valueOf(_restoreSummon)); // True restores pet on login
statement.setInt(10, getControlObjectId());
statement.executeUpdate();
_respawned = true;
if (_restoreSummon)
{
CharSummonTable.getInstance().getPets().put(getOwner().getObjectId(), getControlObjectId());
}
else
{
CharSummonTable.getInstance().getPets().remove(getOwner().getObjectId());
}
}
catch (Exception e)
{
_logPet.log(Level.SEVERE, "Failed to store Pet [ObjectId: " + getObjectId() + "] data", e);
}
L2ItemInstance itemInst = getControlItem();
if ((itemInst != null) && (itemInst.getEnchantLevel() != getStat().getLevel()))
{
itemInst.setEnchantLevel(getStat().getLevel());
itemInst.updateDatabase();
}
}
@Override
public void storeEffect(boolean storeEffects)
{
if (!Config.SUMMON_STORE_SKILL_COOLTIME)
{
return;
}
// Clear list for overwrite
SummonEffectsTable.getInstance().getPetEffects().getOrDefault(getControlObjectId(), Collections.emptyList()).clear();
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement ps1 = con.prepareStatement(DELETE_SKILL_SAVE);
PreparedStatement ps2 = con.prepareStatement(ADD_SKILL_SAVE))
{
// Delete all current stored effects for summon to avoid dupe
ps1.setInt(1, getControlObjectId());
ps1.execute();
int buff_index = 0;
final List<Integer> storedSkills = new ArrayList<>();
// Store all effect data along with calculated remaining
if (storeEffects)
{
for (BuffInfo info : getEffectList().getEffects())
{
if (info == null)
{
continue;
}
final Skill skill = info.getSkill();
// Do not save heals.
if (skill.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS)
{
continue;
}
if (skill.isToggle())
{
continue;
}
// Dances and songs are not kept in retail.
if (skill.isDance() && !Config.ALT_STORE_DANCES)
{
continue;
}
if (storedSkills.contains(skill.getReuseHashCode()))
{
continue;
}
storedSkills.add(skill.getReuseHashCode());
ps2.setInt(1, getControlObjectId());
ps2.setInt(2, skill.getId());
ps2.setInt(3, skill.getLevel());
ps2.setInt(4, info.getTime());
ps2.setInt(5, ++buff_index);
ps2.execute();
if (!SummonEffectsTable.getInstance().getPetEffects().containsKey(getControlObjectId()))
{
SummonEffectsTable.getInstance().getPetEffects().put(getControlObjectId(), new ArrayList<SummonEffect>());
}
SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, info.getTime()));
}
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Could not store pet effect data: ", e);
}
}
@Override
public void restoreEffects()
{
try (Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement ps1 = con.prepareStatement(RESTORE_SKILL_SAVE);
PreparedStatement ps2 = con.prepareStatement(DELETE_SKILL_SAVE))
{
if (!SummonEffectsTable.getInstance().getPetEffects().containsKey(getControlObjectId()))
{
ps1.setInt(1, getControlObjectId());
try (ResultSet rset = ps1.executeQuery())
{
while (rset.next())
{
int effectCurTime = rset.getInt("remaining_time");
final Skill skill = SkillData.getInstance().getSkill(rset.getInt("skill_id"), rset.getInt("skill_level"));
if (skill == null)
{
continue;
}
if (skill.hasEffects(EffectScope.GENERAL))
{
if (!SummonEffectsTable.getInstance().getPetEffects().containsKey(getControlObjectId()))
{
SummonEffectsTable.getInstance().getPetEffects().put(getControlObjectId(), new ArrayList<SummonEffect>());
}
SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, effectCurTime));
}
}
}
}
ps2.setInt(1, getControlObjectId());
ps2.executeUpdate();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e);
}
finally
{
if (SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId()) == null)
{
return;
}
for (SummonEffect se : SummonEffectsTable.getInstance().getPetEffects().get(getControlObjectId()))
{
if (se != null)
{
se.getSkill().applyEffects(this, this, false, se.getEffectCurTime());
}
}
}
}
public synchronized void stopFeed()
{
if (_feedTask != null)
{
_feedTask.cancel(false);
_feedTask = null;
}
}
public synchronized void startFeed()
{
// stop feeding task if its active
stopFeed();
if (!isDead() && (getOwner().getPet() == this))
{
_feedTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new FeedTask(), 10000, 10000);
}
}
@Override
public synchronized void unSummon(L2PcInstance owner)
{
stopFeed();
stopHpMpRegeneration();
super.unSummon(owner);
if (!isDead())
{
if (getInventory() != null)
{
getInventory().deleteMe();
}
L2World.getInstance().removePet(owner.getObjectId());
}
}
/**
* Restore the specified % of experience this L2PetInstance has lost.<BR>
* <BR>
* @param restorePercent
*/
public void restoreExp(double restorePercent)
{
if (_expBeforeDeath > 0)
{
// Restore the specified % of lost experience.
getStat().addExp(Math.round(((_expBeforeDeath - getStat().getExp()) * restorePercent) / 100));
_expBeforeDeath = 0;
}
}
private void deathPenalty()
{
// TODO: Need Correct Penalty
int lvl = getStat().getLevel();
double percentLost = (-0.07 * lvl) + 6.5;
// Calculate the Experience loss
long lostExp = Math.round(((getStat().getExpForLevel(lvl + 1) - getStat().getExpForLevel(lvl)) * percentLost) / 100);
// Get the Experience before applying penalty
_expBeforeDeath = getStat().getExp();
// Set the new Experience value of the L2PetInstance
getStat().addExp(-lostExp);
}
@Override
public void addExpAndSp(long addToExp, long addToSp)
{
if (getId() == 12564)
{
getStat().addExpAndSp(Math.round(addToExp * Config.SINEATER_XP_RATE), addToSp);
}
else
{
getStat().addExpAndSp(Math.round(addToExp * Config.PET_XP_RATE), addToSp);
}
}
@Override
public long getExpForThisLevel()
{
return getStat().getExpForLevel(getLevel());
}
@Override
public long getExpForNextLevel()
{
return getStat().getExpForLevel(getLevel() + 1);
}
@Override
public final int getLevel()
{
return getStat().getLevel();
}
public int getMaxFed()
{
return getStat().getMaxFeed();
}
@Override
public int getCriticalHit(L2Character target, Skill skill)
{
return getStat().getCriticalHit(target, skill);
}
@Override
public int getMAtk(L2Character target, Skill skill)
{
return getStat().getMAtk(target, skill);
}
@Override
public int getMDef(L2Character target, Skill skill)
{
return getStat().getMDef(target, skill);
}
@Override
public final int getSkillLevel(int skillId)
{
if (getKnownSkill(skillId) == null)
{
return -1;
}
final int lvl = getLevel();
return lvl > 70 ? 7 + ((lvl - 70) / 5) : lvl / 10;
}
public void updateRefOwner(L2PcInstance owner)
{
int oldOwnerId = getOwner().getObjectId();
setOwner(owner);
L2World.getInstance().removePet(oldOwnerId);
L2World.getInstance().addPet(oldOwnerId, this);
}
public int getInventoryLimit()
{
return Config.INVENTORY_MAXIMUM_PET;
}
public void refreshOverloaded()
{
int maxLoad = getMaxLoad();
if (maxLoad > 0)
{
long weightproc = (((getCurrentLoad() - getBonusWeightPenalty()) * 1000) / maxLoad);
int newWeightPenalty;
if ((weightproc < 500) || getOwner().getDietMode())
{
newWeightPenalty = 0;
}
else if (weightproc < 666)
{
newWeightPenalty = 1;
}
else if (weightproc < 800)
{
newWeightPenalty = 2;
}
else if (weightproc < 1000)
{
newWeightPenalty = 3;
}
else
{
newWeightPenalty = 4;
}
if (_curWeightPenalty != newWeightPenalty)
{
_curWeightPenalty = newWeightPenalty;
if (newWeightPenalty > 0)
{
addSkill(SkillData.getInstance().getSkill(4270, newWeightPenalty));
setIsOverloaded(getCurrentLoad() >= maxLoad);
}
else
{
removeSkill(getKnownSkill(4270), true);
setIsOverloaded(false);
}
}
}
}
@Override
public void updateAndBroadcastStatus(int val)
{
refreshOverloaded();
super.updateAndBroadcastStatus(val);
}
@Override
public final boolean isHungry()
{
return getCurrentFed() < ((getPetData().getHungryLimit() / 100f) * getPetLevelData().getPetMaxFeed());
}
/**
* Verifies if a pet can be controlled by it's owner.<br>
* Starving pets cannot be controlled.
* @return {@code true} if the per cannot be controlled
*/
public boolean isUncontrollable()
{
return getCurrentFed() <= 0;
}
@Override
public final int getWeapon()
{
L2ItemInstance weapon = getInventory().getPaperdollItem(Inventory.PAPERDOLL_RHAND);
if (weapon != null)
{
return weapon.getId();
}
return 0;
}
@Override
public final int getArmor()
{
L2ItemInstance weapon = getInventory().getPaperdollItem(Inventory.PAPERDOLL_CHEST);
if (weapon != null)
{
return weapon.getId();
}
return 0;
}
public final int getJewel()
{
L2ItemInstance weapon = getInventory().getPaperdollItem(Inventory.PAPERDOLL_NECK);
if (weapon != null)
{
return weapon.getId();
}
return 0;
}
@Override
public short getSoulShotsPerHit()
{
return getPetLevelData().getPetSoulShot();
}
@Override
public short getSpiritShotsPerHit()
{
return getPetLevelData().getPetSpiritShot();
}
@Override
public void setName(String name)
{
final L2ItemInstance controlItem = getControlItem();
if (controlItem != null)
{
if (controlItem.getCustomType2() == (name == null ? 1 : 0))
{
// name not set yet
controlItem.setCustomType2(name != null ? 1 : 0);
controlItem.updateDatabase();
InventoryUpdate iu = new InventoryUpdate();
iu.addModifiedItem(controlItem);
sendPacket(iu);
}
}
else
{
_log.log(Level.WARNING, "Pet control item null, for pet: " + toString());
}
super.setName(name);
}
public boolean canEatFoodId(int itemId)
{
return _data.getFood().contains(itemId);
}
@Override
public boolean isPet()
{
return true;
}
@Override
public final double getRunSpeed()
{
return super.getRunSpeed() * (isUncontrollable() ? 0.5d : 1.0d);
}
@Override
public final double getWalkSpeed()
{
return super.getWalkSpeed() * (isUncontrollable() ? 0.5d : 1.0d);
}
@Override
public final double getMovementSpeedMultiplier()
{
return super.getMovementSpeedMultiplier() * (isUncontrollable() ? 0.5d : 1.0d);
}
@Override
public final double getMoveSpeed()
{
if (isInsideZone(ZoneId.WATER))
{
return isRunning() ? getSwimRunSpeed() : getSwimWalkSpeed();
}
return isRunning() ? getRunSpeed() : getWalkSpeed();
}
}