/*
* 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 gnu.trove.TIntObjectHashMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javolution.util.FastList;
import com.l2jserver.Config;
import com.l2jserver.L2DatabaseFactory;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.datatables.CharSummonTable;
import com.l2jserver.gameserver.datatables.SkillTable;
import com.l2jserver.gameserver.datatables.SummonEffectsTable;
import com.l2jserver.gameserver.datatables.SummonEffectsTable.SummonEffect;
import com.l2jserver.gameserver.model.L2Effect;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2Skill;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.network.serverpackets.SetSummonRemainTime;
import com.l2jserver.gameserver.skills.Env;
import com.l2jserver.gameserver.skills.l2skills.L2SkillSummon;
import com.l2jserver.gameserver.templates.chars.L2NpcTemplate;
import com.l2jserver.gameserver.templates.effects.EffectTemplate;
public class L2SummonInstance extends L2Summon
{
protected static final Logger log = Logger.getLogger(L2SummonInstance.class.getName());
private static final String ADD_SKILL_SAVE = "INSERT INTO character_summon_skills_save (ownerId,ownerClassIndex,summonSkillId,skill_id,skill_level,effect_count,effect_cur_time,buff_index) VALUES (?,?,?,?,?,?,?,?)";
private static final String RESTORE_SKILL_SAVE = "SELECT skill_id,skill_level,effect_count,effect_cur_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 _expPenalty = 0; // exp decrease multiplier (i.e. 0.3 (= 30%) for shadow)
private int _itemConsumeId;
private int _itemConsumeCount;
private int _itemConsumeSteps;
private final int _totalLifeTime;
private final int _timeLostIdle;
private final int _timeLostActive;
private int _timeRemaining;
private int _nextItemConsumeTime;
public int lastShowntimeRemaining; // Following FbiAgent's example to avoid sending useless packets
private Future<?> _summonLifeTask;
private int _referenceSkill;
public L2SummonInstance(int objectId, L2NpcTemplate template, L2PcInstance owner, L2Skill skill)
{
super(objectId, template, owner);
setInstanceType(InstanceType.L2SummonInstance);
setShowSummonAnimation(true);
if (skill != null)
{
final L2SkillSummon summonSkill = (L2SkillSummon)skill;
_itemConsumeId = summonSkill.getItemConsumeIdOT();
_itemConsumeCount = summonSkill.getItemConsumeOT();
_itemConsumeSteps = summonSkill.getItemConsumeSteps();
_totalLifeTime = summonSkill.getTotalLifeTime();
_timeLostIdle = summonSkill.getTimeLostIdle();
_timeLostActive = summonSkill.getTimeLostActive();
_referenceSkill = summonSkill.getId();
}
else
{
// defaults
_itemConsumeId = 0;
_itemConsumeCount = 0;
_itemConsumeSteps = 0;
_totalLifeTime = 1200000; // 20 minutes
_timeLostIdle = 1000;
_timeLostActive = 1000;
}
_timeRemaining = _totalLifeTime;
lastShowntimeRemaining = _totalLifeTime;
if (_itemConsumeId == 0)
_nextItemConsumeTime = -1; // do not consume
else if (_itemConsumeSteps == 0)
_nextItemConsumeTime = -1; // do not consume
else
_nextItemConsumeTime = _totalLifeTime - _totalLifeTime / (_itemConsumeSteps + 1);
// When no item consume is defined task only need to check when summon life time has ended.
// Otherwise have to destroy items from owner's inventory in order to let summon live.
int delay = 1000;
if (Config.DEBUG && (_itemConsumeCount != 0))
_log.warning("L2SummonInstance: Item Consume ID: " + _itemConsumeId + ", Count: " + _itemConsumeCount + ", Rate: " + _itemConsumeSteps + " times.");
if (Config.DEBUG)
_log.warning("L2SummonInstance: Task Delay " + (delay / 1000) + " seconds.");
_summonLifeTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new SummonLifetime(getOwner(), this), delay, delay);
}
@Override
public final int getLevel()
{
return (getTemplate() != null ? getTemplate().level : 0);
}
@Override
public int getSummonType()
{
return 1;
}
public void setExpPenalty(float expPenalty)
{
_expPenalty = expPenalty;
}
public float getExpPenalty()
{
return _expPenalty;
}
public int getItemConsumeCount()
{
return _itemConsumeCount;
}
public int getItemConsumeId()
{
return _itemConsumeId;
}
public int getItemConsumeSteps()
{
return _itemConsumeSteps;
}
public int getNextItemConsumeTime()
{
return _nextItemConsumeTime;
}
public int getTotalLifeTime()
{
return _totalLifeTime;
}
public int getTimeLostIdle()
{
return _timeLostIdle;
}
public int getTimeLostActive()
{
return _timeLostActive;
}
public int getTimeRemaining()
{
return _timeRemaining;
}
public void setNextItemConsumeTime(int value)
{
_nextItemConsumeTime = value;
}
public void decNextItemConsumeTime(int value)
{
_nextItemConsumeTime -= value;
}
public void decTimeRemaining(int value)
{
_timeRemaining -= value;
}
public void addExpAndSp(int addToExp, int addToSp)
{
getOwner().addExpAndSp(addToExp, addToSp);
}
@Override
public boolean doDie(L2Character killer)
{
if (!super.doDie(killer))
return false;
if (Config.DEBUG)
_log.warning("L2SummonInstance: " + getTemplate().name + " (" + getOwner().getName() + ") has been killed.");
if (_summonLifeTask != null)
{
_summonLifeTask.cancel(false);
_summonLifeTask = null;
}
CharSummonTable.getInstance().removeServitor(getOwner());
return true;
}
/**
* Servitors' skills automatically change their level based on the servitor's level.
* Until level 70, the servitor gets 1 lv of skill per 10 levels. After that, it is 1
* skill level per 5 servitor levels. If the resulting skill level doesn't exist use
* the max that does exist!
*
* @see com.l2jserver.gameserver.model.actor.L2Character#doCast(com.l2jserver.gameserver.model.L2Skill)
*/
@Override
public void doCast(L2Skill 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 L2Skill skillToCast = SkillTable.getInstance().getInfo(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(int skillId)
{
super.stopSkillEffects(skillId);
final TIntObjectHashMap<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 L2Skill skill = effect.getSkill();
if ((skill != null) && (skill.getId() == skillId))
{
effects.remove(effect);
}
}
}
}
}
@Override
public void store()
{
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().isInOlympiadMode())
return;
// Clear list for overwrite
if (
SummonEffectsTable.getInstance().getServitorEffectsOwner().contains(getOwner().getObjectId()) &&
SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).contains(getOwner().getClassIndex()) &&
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).contains(getReferenceSkill())
)
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()).clear();
Connection con = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
// Delete all current stored effects for summon to avoid dupe
PreparedStatement statement = con.prepareStatement(DELETE_SKILL_SAVE);
statement.setInt(1, getOwner().getObjectId());
statement.setInt(2, getOwner().getClassIndex());
statement.setInt(3, getReferenceSkill());
statement.execute();
statement.close();
int buff_index = 0;
final List<Integer> storedSkills = new FastList<Integer>();
//Store all effect data along with calculated remaining
statement = con.prepareStatement(ADD_SKILL_SAVE);
if (storeEffects)
{
for (L2Effect effect : getAllEffects())
{
if (effect == null)
continue;
switch (effect.getEffectType())
{
case HEAL_OVER_TIME:
case COMBAT_POINT_HEAL_OVER_TIME:
// TODO: Fix me.
case HIDE:
continue;
}
L2Skill skill = effect.getSkill();
if (storedSkills.contains(skill.getReuseHashCode()))
continue;
storedSkills.add(skill.getReuseHashCode());
if (!effect.isHerbEffect() && effect.getInUse() && !skill.isToggle())
{
statement.setInt(1, getOwner().getObjectId());
statement.setInt(2, getOwner().getClassIndex());
statement.setInt(3, getReferenceSkill());
statement.setInt(4, skill.getId());
statement.setInt(5, skill.getLevel());
statement.setInt(6, effect.getCount());
statement.setInt(7, effect.getTime());
statement.setInt(8, ++buff_index);
statement.execute();
if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().contains(getOwner().getObjectId())) // Check if charId exists in map
SummonEffectsTable.getInstance().getServitorEffectsOwner().put(getOwner().getObjectId(), new TIntObjectHashMap<TIntObjectHashMap<List<SummonEffect>>>());
if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).contains(getOwner().getClassIndex())) // Check if classIndex exists in charId map
SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).put(getOwner().getClassIndex(), new TIntObjectHashMap<List<SummonEffect>>());
if (!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).contains(getReferenceSkill())) // Check is summonSkillId exists in charId+classIndex map
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).put(getReferenceSkill(), new FastList<SummonEffect>());
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, effect.getCount(), effect.getTime()));
}
}
statement.close();
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "Could not store summon effect data: ", e);
}
finally
{
L2DatabaseFactory.close(con);
}
}
@Override
public void restoreEffects()
{
if (getOwner().isInOlympiadMode())
return;
Connection con = null;
PreparedStatement statement = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
if (
!SummonEffectsTable.getInstance().getServitorEffectsOwner().contains(getOwner().getObjectId()) ||
!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).contains(getOwner().getClassIndex()) ||
!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).contains(getReferenceSkill())
)
{
statement = con.prepareStatement(RESTORE_SKILL_SAVE);
statement.setInt(1, getOwner().getObjectId());
statement.setInt(2, getOwner().getClassIndex());
statement.setInt(3, getReferenceSkill());
ResultSet rset = statement.executeQuery();
while (rset.next())
{
int effectCount = rset.getInt("effect_count");
int effectCurTime = rset.getInt("effect_cur_time");
final L2Skill skill = SkillTable.getInstance().getInfo(rset.getInt("skill_id"), rset.getInt("skill_level"));
if (skill == null)
continue;
if (skill.hasEffects())
{
if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().contains(getOwner().getObjectId())) // Check if charId exists in map
SummonEffectsTable.getInstance().getServitorEffectsOwner().put(getOwner().getObjectId(), new TIntObjectHashMap<TIntObjectHashMap<List<SummonEffect>>>());
if (!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).contains(getOwner().getClassIndex())) // Check if classIndex exists in charId map
SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).put(getOwner().getClassIndex(), new TIntObjectHashMap<List<SummonEffect>>());
if (!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).contains(getReferenceSkill())) // Check is summonSkillId exists in charId+classIndex map
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).put(getReferenceSkill(), new FastList<SummonEffect>());
SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()).add(SummonEffectsTable.getInstance().new SummonEffect(skill, effectCount, effectCurTime));
}
}
rset.close();
statement.close();
}
statement = con.prepareStatement(DELETE_SKILL_SAVE);
statement.setInt(1, getOwner().getObjectId());
statement.setInt(2, getOwner().getClassIndex());
statement.setInt(3, getReferenceSkill());
statement.executeUpdate();
statement.close();
}
catch (Exception e)
{
_log.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e);
}
finally
{
L2DatabaseFactory.close(con);
if (
!SummonEffectsTable.getInstance().getServitorEffectsOwner().contains(getOwner().getObjectId()) ||
!SummonEffectsTable.getInstance().getServitorEffectsOwner().get(getOwner().getObjectId()).contains(getOwner().getClassIndex()) ||
!SummonEffectsTable.getInstance().getServitorEffects(getOwner()).contains(getReferenceSkill())
)
return;
for (SummonEffect se : SummonEffectsTable.getInstance().getServitorEffects(getOwner()).get(getReferenceSkill()))
{
Env env = new Env();
env.player = this;
env.target = this;
env.skill = se.getSkill();
L2Effect ef;
for (EffectTemplate et : se.getSkill().getEffectTemplates())
{
ef = et.getEffect(env);
if (ef != null)
{
ef.setCount(se.getEffectCount());
ef.setFirstTime(se.getEffectCurTime());
ef.scheduleEffect();
}
}
}
}
}
static class SummonLifetime implements Runnable
{
private L2PcInstance _activeChar;
private L2SummonInstance _summon;
SummonLifetime(L2PcInstance activeChar, L2SummonInstance newpet)
{
_activeChar = activeChar;
_summon = newpet;
}
public void run()
{
if (Config.DEBUG)
log.warning("L2SummonInstance: " + _summon.getTemplate().name + " (" + _activeChar.getName() + ") run task.");
try
{
double oldTimeRemaining = _summon.getTimeRemaining();
int maxTime = _summon.getTotalLifeTime();
double newTimeRemaining;
// if pet is attacking
if (_summon.isAttackingNow())
{
_summon.decTimeRemaining(_summon.getTimeLostActive());
}
else
{
_summon.decTimeRemaining(_summon.getTimeLostIdle());
}
newTimeRemaining = _summon.getTimeRemaining();
// check if the summon's lifetime has ran out
if (newTimeRemaining < 0)
{
_summon.unSummon(_activeChar);
}
// check if it is time to consume another item
else if ((newTimeRemaining <= _summon.getNextItemConsumeTime()) && (oldTimeRemaining > _summon.getNextItemConsumeTime()))
{
_summon.decNextItemConsumeTime(maxTime / (_summon.getItemConsumeSteps() + 1));
// check if owner has enought itemConsume, if requested
if (_summon.getItemConsumeCount() > 0 && _summon.getItemConsumeId() != 0 && !_summon.isDead() && !_summon.destroyItemByItemId("Consume", _summon.getItemConsumeId(), _summon.getItemConsumeCount(), _activeChar, true))
{
_summon.unSummon(_activeChar);
}
}
// prevent useless packet-sending when the difference isn't visible.
if ((_summon.lastShowntimeRemaining - newTimeRemaining) > maxTime / 352)
{
_summon.getOwner().sendPacket(new SetSummonRemainTime(maxTime, (int) newTimeRemaining));
_summon.lastShowntimeRemaining = (int) newTimeRemaining;
_summon.updateEffectIcons();
}
}
catch (Exception e)
{
log.log(Level.SEVERE, "Error on player [" + _activeChar.getName() + "] summon item consume task.", e);
}
}
}
@Override
public void unSummon(L2PcInstance owner)
{
if (Config.DEBUG)
_log.warning("L2SummonInstance: " + getTemplate().name + " (" + owner.getName() + ") unsummoned.");
if (_summonLifeTask != null)
{
_summonLifeTask.cancel(false);
_summonLifeTask = null;
}
super.unSummon(owner);
if (!_restoreSummon)
CharSummonTable.getInstance().removeServitor(owner);
}
@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)
{
if (Config.DEBUG)
_log.warning("L2SummonInstance: " + getTemplate().name + " (" + getOwner().getName() + ") consume.");
return getOwner().destroyItemByItemId(process, itemId, count, reference, sendMessage);
}
@Override
public byte getAttackElement()
{
if (getOwner() == null || !getOwner().getClassId().isSummoner())
return super.getAttackElement();
return getOwner().getAttackElement();
}
@Override
public int getAttackElementValue(byte attribute)
{
if (getOwner() == null || !getOwner().getClassId().isSummoner() || getOwner().getExpertiseWeaponPenalty() > 0)
return super.getAttackElementValue(attribute);
// 80% of the owner (onwer already has only 20%)
return 4 * getOwner().getAttackElementValue(attribute);
}
@Override
public int getDefenseElementValue(byte attribute)
{
if (getOwner() == null || !getOwner().getClassId().isSummoner())
return super.getDefenseElementValue(attribute);
// bonus from owner
return super.getDefenseElementValue(attribute) + getOwner().getDefenseElementValue(attribute);
}
public void setTimeRemaining(int time)
{
_timeRemaining = time;
}
public int getReferenceSkill()
{
return _referenceSkill;
}
}