/* * 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.HashMap; import java.util.List; import java.util.Map; 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.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.datatables.SkillData; import com.l2jserver.gameserver.enums.InstanceType; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Summon; import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate; import com.l2jserver.gameserver.model.holders.ItemHolder; 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.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.SetSummonRemainTime; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; /** * @author UnAfraid */ public class L2ServitorInstance extends L2Summon implements Runnable { protected static final Logger log = Logger.getLogger(L2ServitorInstance.class.getName()); private static final String ADD_SKILL_SAVE = "INSERT INTO character_summon_skills_save (ownerId,ownerClassIndex,summonSkillId,skill_id,skill_level,remaining_time,buff_index) VALUES (?,?,?,?,?,?,?)"; private static final String RESTORE_SKILL_SAVE = "SELECT skill_id,skill_level,remaining_time,buff_index FROM character_summon_skills_save WHERE ownerId=? AND ownerClassIndex=? AND summonSkillId=? ORDER BY buff_index ASC"; private static final String DELETE_SKILL_SAVE = "DELETE FROM character_summon_skills_save WHERE ownerId=? AND ownerClassIndex=? AND summonSkillId=?"; private float _expMultiplier = 0; private ItemHolder _itemConsume; private int _lifeTime; private int _lifeTimeRemaining; private int _consumeItemInterval; private int _consumeItemIntervalRemaining; protected Future<?> _summonLifeTask; private int _referenceSkill; public L2ServitorInstance(L2NpcTemplate template, L2PcInstance owner) { super(template, owner); setInstanceType(InstanceType.L2ServitorInstance); setShowSummonAnimation(true); } @Override public void onSpawn() { super.onSpawn(); if (_summonLifeTask == null) { _summonLifeTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(this, 0, 5000); } } @Override public final int getLevel() { return (getTemplate() != null ? getTemplate().getLevel() : 0); } @Override public int getSummonType() { return 1; } public void setExpMultiplier(float expMultiplier) { _expMultiplier = expMultiplier; } public float getExpMultiplier() { return _expMultiplier; } public void setItemConsume(ItemHolder item) { _itemConsume = item; } public ItemHolder getItemConsume() { return _itemConsume; } public void setItemConsumeInterval(int interval) { _consumeItemInterval = interval; _consumeItemIntervalRemaining = interval; } public int getItemConsumeInterval() { return _consumeItemInterval; } public void setLifeTime(int lifeTime) { _lifeTime = lifeTime; _lifeTimeRemaining = lifeTime; } public int getLifeTime() { return _lifeTime; } public void setLifeTimeRemaining(int time) { _lifeTimeRemaining = time; } public int getLifeTimeRemaining() { return _lifeTimeRemaining; } public void setReferenceSkill(int skillId) { _referenceSkill = skillId; } public int getReferenceSkill() { return _referenceSkill; } @Override public boolean doDie(L2Character killer) { if (!super.doDie(killer)) { return false; } if (_summonLifeTask != null) { _summonLifeTask.cancel(false); } CharSummonTable.getInstance().removeServitor(getOwner(), getObjectId()); return true; } @Override public void doPickupItem(L2Object object) { } /** * Servitors' skills automatically change their level based on the servitor's level.<br> * Until level 70, the servitor gets 1 lv of skill per 10 levels.<br> * After that, it is 1 skill level per 5 servitor levels.<br> * If the resulting skill level doesn't exist use the max that does exist! */ @Override public void doCast(Skill skill) { final int petLevel = getLevel(); int skillLevel = petLevel / 10; if (petLevel >= 70) { skillLevel += (petLevel - 65) / 10; } // Adjust the level for servitors less than level 1. if (skillLevel < 1) { skillLevel = 1; } final Skill skillToCast = SkillData.getInstance().getSkill(skill.getId(), skillLevel); if (skillToCast != null) { super.doCast(skillToCast); } else { super.doCast(skill); } } @Override public void setRestoreSummon(boolean val) { _restoreSummon = val; } @Override public final void stopSkillEffects(boolean removed, int skillId) { super.stopSkillEffects(removed, skillId); final Map<Integer, List<SummonEffect>> servitorEffects = SummonEffectsTable.getInstance().getServitorEffects(getOwner()); if (servitorEffects != null) { final List<SummonEffect> effects = servitorEffects.get(getReferenceSkill()); if ((effects != null) && !effects.isEmpty()) { for (SummonEffect effect : effects) { final Skill skill = effect.getSkill(); if ((skill != null) && (skill.getId() == skillId)) { effects.remove(effect); } } } } } @Override public void storeMe() { if ((_referenceSkill == 0) || isDead()) { return; } if (Config.RESTORE_SERVITOR_ON_RECONNECT) { CharSummonTable.getInstance().saveSummon(this); } } @Override public void storeEffect(boolean storeEffects) { if (!Config.SUMMON_STORE_SKILL_COOLTIME) { return; } if ((getOwner() == null) || getOwner().isInOlympiadMode()) { return; } // Clear list for overwrite if (SummonEffectsTable.getInstance().getServitorEffectsOwner().getOrDefault(getOwner().getObjectId(), Collections.emptyMap()).containsKey(getOwner().getClassIndex())) { SummonEffectsTable.getInstance().getServitorEffects(getOwner()).getOrDefault(getReferenceSkill(), Collections.emptyList()).clear(); } try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement(DELETE_SKILL_SAVE)) { // Delete all current stored effects for summon to avoid dupe statement.setInt(1, getOwner().getObjectId()); statement.setInt(2, getOwner().getClassIndex()); statement.setInt(3, getReferenceSkill()); statement.execute(); int buff_index = 0; final List<Integer> storedSkills = new ArrayList<>(); // Store all effect data along with calculated remaining if (storeEffects) { try (PreparedStatement ps2 = con.prepareStatement(ADD_SKILL_SAVE)) { 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, getOwner().getObjectId()); ps2.setInt(2, getOwner().getClassIndex()); ps2.setInt(3, getReferenceSkill()); ps2.setInt(4, skill.getId()); ps2.setInt(5, skill.getLevel()); ps2.setInt(6, info.getTime()); ps2.setInt(7, ++buff_index); ps2.execute(); // XXX: Rework me! if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().containsKey(getOwner().getObjectId())) { SummonEffectsTable.getInstance().getServitorEffectsOwner().put(getOwner().getObjectId(), new HashMap<Integer, Map<Integer, List<SummonEffect>>>()); } if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).containsKey(getOwner().getClassIndex())) { SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).put(getOwner().getClassIndex(), new HashMap<Integer, List<SummonEffect>>()); } if (!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).containsKey(getReferenceSkill())) { SummonEffectsTable.getInstance().getServitorEffects(getOwner()).put(getReferenceSkill(), new ArrayList<SummonEffect>()); } SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, info.getTime())); } } } } catch (Exception e) { _log.log(Level.WARNING, "Could not store summon effect data: ", e); } } @Override public void restoreEffects() { if (getOwner().isInOlympiadMode()) { return; } try (Connection con = L2DatabaseFactory.getInstance().getConnection()) { if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().containsKey(getOwner().getObjectId()) || !SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).containsKey(getOwner().getClassIndex()) || !SummonEffectsTable.getInstance().getServitorEffects(getOwner()).containsKey(getReferenceSkill())) { try (PreparedStatement statement = con.prepareStatement(RESTORE_SKILL_SAVE)) { statement.setInt(1, getOwner().getObjectId()); statement.setInt(2, getOwner().getClassIndex()); statement.setInt(3, getReferenceSkill()); try (ResultSet rset = statement.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; } // XXX: Rework me! if (skill.hasEffects(EffectScope.GENERAL)) { if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().containsKey(getOwner().getObjectId())) { SummonEffectsTable.getInstance().getServitorEffectsOwner().put(getOwner().getObjectId(), new HashMap<Integer, Map<Integer, List<SummonEffect>>>()); } if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).containsKey(getOwner().getClassIndex())) { SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).put(getOwner().getClassIndex(), new HashMap<Integer, List<SummonEffect>>()); } if (!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).containsKey(getReferenceSkill())) { SummonEffectsTable.getInstance().getServitorEffects(getOwner()).put(getReferenceSkill(), new ArrayList<SummonEffect>()); } SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, effectCurTime)); } } } } } try (PreparedStatement statement = con.prepareStatement(DELETE_SKILL_SAVE)) { statement.setInt(1, getOwner().getObjectId()); statement.setInt(2, getOwner().getClassIndex()); statement.setInt(3, getReferenceSkill()); statement.executeUpdate(); } } catch (Exception e) { _log.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e); } finally { if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().containsKey(getOwner().getObjectId()) || !SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).containsKey(getOwner().getClassIndex()) || !SummonEffectsTable.getInstance().getServitorEffects(getOwner()).containsKey(getReferenceSkill())) { return; } for (SummonEffect se : SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill())) { if (se != null) { se.getSkill().applyEffects(this, this, false, se.getEffectCurTime()); } } } } @Override public void unSummon(L2PcInstance owner) { if (_summonLifeTask != null) { _summonLifeTask.cancel(false); } super.unSummon(owner); if (!_restoreSummon) { CharSummonTable.getInstance().removeServitor(owner, getObjectId()); } } @Override public boolean destroyItem(String process, int objectId, long count, L2Object reference, boolean sendMessage) { return getOwner().destroyItem(process, objectId, count, reference, sendMessage); } @Override public boolean destroyItemByItemId(String process, int itemId, long count, L2Object reference, boolean sendMessage) { return getOwner().destroyItemByItemId(process, itemId, count, reference, sendMessage); } @Override public byte getAttackElement() { if (getOwner() != null) { return getOwner().getAttackElement(); } return super.getAttackElement(); } @Override public int getAttackElementValue(byte attackAttribute) { if (getOwner() != null) { return (getOwner().getAttackElementValue(attackAttribute)); } return super.getAttackElementValue(attackAttribute); } @Override public int getDefenseElementValue(byte defenseAttribute) { if (getOwner() != null) { return (getOwner().getDefenseElementValue(defenseAttribute)); } return super.getDefenseElementValue(defenseAttribute); } @Override public boolean isServitor() { return true; } @Override public void run() { int usedtime = 5000; _lifeTimeRemaining -= usedtime; if (isDead() || !isVisible()) { if (_summonLifeTask != null) { _summonLifeTask.cancel(false); } return; } // check if the summon's lifetime has ran out if (_lifeTimeRemaining < 0) { sendPacket(SystemMessageId.YOUR_SERVITOR_PASSED_AWAY); unSummon(getOwner()); return; } if (_consumeItemInterval > 0) { _consumeItemIntervalRemaining -= usedtime; // check if it is time to consume another item if ((_consumeItemIntervalRemaining <= 0) && (getItemConsume().getCount() > 0) && (getItemConsume().getId() > 0) && !isDead()) { if (destroyItemByItemId("Consume", getItemConsume().getId(), getItemConsume().getCount(), this, false)) { final SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.A_SUMMONED_MONSTER_USES_S1); msg.addItemName(getItemConsume().getId()); sendPacket(msg); // Reset _consumeItemIntervalRemaining = _consumeItemInterval; } else { sendPacket(SystemMessageId.SINCE_YOU_DO_NOT_HAVE_ENOUGH_ITEMS_TO_MAINTAIN_THE_SERVITOR_S_STAY_THE_SERVITOR_HAS_DISAPPEARED); unSummon(getOwner()); } } } sendPacket(new SetSummonRemainTime(getLifeTime(), _lifeTimeRemaining)); updateEffectIcons(); } }