/*
 * 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 2, 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.l2jfrozen.gameserver.model;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import javolution.util.FastList;
import javolution.util.FastMap;

import com.l2jfrozen.Config;
import com.l2jfrozen.gameserver.ai.CtrlEvent;
import com.l2jfrozen.gameserver.ai.CtrlIntention;
import com.l2jfrozen.gameserver.ai.L2AttackableAI;
import com.l2jfrozen.gameserver.ai.L2CharacterAI;
import com.l2jfrozen.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jfrozen.gameserver.ai.L2SiegeGuardAI;
import com.l2jfrozen.gameserver.datatables.sql.ItemTable;
import com.l2jfrozen.gameserver.managers.CursedWeaponsManager;
import com.l2jfrozen.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2FolkInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2FortSiegeGuardInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2ItemInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2MinionInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2MonsterInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PcInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PetInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PlayableInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2RaidBossInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2SiegeGuardInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2SummonInstance;
import com.l2jfrozen.gameserver.model.actor.knownlist.AttackableKnownList;
import com.l2jfrozen.gameserver.model.base.SoulCrystal;
import com.l2jfrozen.gameserver.model.quest.Quest;
import com.l2jfrozen.gameserver.network.SystemMessageId;
import com.l2jfrozen.gameserver.network.clientpackets.Say2;
import com.l2jfrozen.gameserver.network.serverpackets.CreatureSay;
import com.l2jfrozen.gameserver.network.serverpackets.InventoryUpdate;
import com.l2jfrozen.gameserver.network.serverpackets.SystemMessage;
import com.l2jfrozen.gameserver.script.EventDroplist;
import com.l2jfrozen.gameserver.script.EventDroplist.DateDrop;
import com.l2jfrozen.gameserver.skills.Stats;
import com.l2jfrozen.gameserver.templates.L2EtcItemType;
import com.l2jfrozen.gameserver.templates.L2Item;
import com.l2jfrozen.gameserver.templates.L2NpcTemplate;
import com.l2jfrozen.gameserver.thread.ThreadPoolManager;
import com.l2jfrozen.gameserver.thread.daemons.ItemsAutoDestroy;
import com.l2jfrozen.gameserver.util.Util;
import com.l2jfrozen.util.random.Rnd;

/**
 * This class manages all NPC that can be attacked.<BR>
 * <BR>
 * L2Attackable :<BR>
 * <BR>
 * <li>L2ArtefactInstance</li> <li>L2FriendlyMobInstance</li> <li>L2MonsterInstance</li> <li>L2SiegeGuardInstance</li>
 * 
 * @version $Revision: 1.24.2.3.2.16 $ $Date: 2009/04/13 02:11:03 $
 * @author scoria dev
 */
public class L2Attackable extends L2NpcInstance
{
	//protected static Logger _log = Logger.getLogger(L2Attackable.class.getName());

	/**
	 * This class contains all AggroInfo of the L2Attackable against the attacker L2Character.<BR>
	 * <BR>
	 * <B><U> Data</U> :</B><BR>
	 * <BR>
	 * <li>attacker : The attaker L2Character concerned by this AggroInfo of this L2Attackable</li> <li>hate : Hate
	 * level of this L2Attackable against the attaker L2Character (hate = damage)</li> <li>damage : Number of damages
	 * that the attaker L2Character gave to this L2Attackable</li><BR>
	 * <BR>
	 */
	public final class AggroInfo
	{
		/** The attaker L2Character concerned by this AggroInfo of this L2Attackable */
		protected L2Character _attacker;

		/** Hate level of this L2Attackable against the attaker L2Character (hate = damage) */
		protected int _hate;

		/** Number of damages that the attaker L2Character gave to this L2Attackable */
		protected int _damage;

		/**
		 * Constructor of AggroInfo.<BR>
		 * <BR>
		 */
		AggroInfo(L2Character pAttacker)
		{
			_attacker = pAttacker;
		}

		/**
		 * Verify is object is equal to this AggroInfo.<BR>
		 * <BR>
		 */
		@Override
		public boolean equals(Object obj)
		{
			if(this == obj)
				return true;

			if(obj instanceof AggroInfo)
				return ((AggroInfo) obj)._attacker == _attacker;

			return false;
		}

		/**
		 * Return the Identifier of the attaker L2Character.<BR>
		 * <BR>
		 */
		@Override
		public int hashCode()
		{
			return _attacker.getObjectId();
		}

	}

	/**
	 * This class contains all RewardInfo of the L2Attackable against the any attacker L2Character, based on amount of
	 * damage done.<BR>
	 * <BR>
	 * <B><U> Data</U> :</B><BR>
	 * <BR>
	 * <li>attacker : The attaker L2Character concerned by this RewardInfo of this L2Attackable</li> <li>dmg : Total
	 * amount of damage done by the attacker to this L2Attackable (summon + own)</li>
	 */
	protected final class RewardInfo
	{
		protected L2Character _attacker;
		protected int _dmg = 0;

		public RewardInfo(L2Character pAttacker, int pDmg)
		{
			_attacker = pAttacker;
			_dmg = pDmg;
		}

		public void addDamage(int pDmg)
		{
			_dmg += pDmg;
		}

		@Override
		public boolean equals(Object obj)
		{
			if(this == obj)
				return true;

			if(obj instanceof RewardInfo)
				return ((RewardInfo) obj)._attacker == _attacker;

			return false;
		}

		@Override
		public int hashCode()
		{
			return _attacker.getObjectId();
		}
	}

	/**
	 * This class contains all AbsorberInfo of the L2Attackable against the absorber L2Character.<BR>
	 * <BR>
	 * <B><U> Data</U> :</B><BR>
	 * <BR>
	 * <li>absorber : The attaker L2Character concerned by this AbsorberInfo of this L2Attackable</li>
	 */
	public final class AbsorberInfo
	{
		/** The attaker L2Character concerned by this AbsorberInfo of this L2Attackable */
		protected L2PcInstance _absorber;
		protected int _crystalId;
		protected double _absorbedHP;

		/**
		 * Constructor of AbsorberInfo.<BR>
		 * <BR>
		 */
		AbsorberInfo(L2PcInstance attacker, int pCrystalId, double pAbsorbedHP)
		{
			_absorber = attacker;
			_crystalId = pCrystalId;
			_absorbedHP = pAbsorbedHP;
		}

		/**
		 * Verify is object is equal to this AbsorberInfo.<BR>
		 * <BR>
		 */
		@Override
		public boolean equals(Object obj)
		{
			if(this == obj)
				return true;

			if(obj instanceof AbsorberInfo)
				return ((AbsorberInfo) obj)._absorber == _absorber;

			return false;
		}

		/**
		 * Return the Identifier of the absorber L2Character.<BR>
		 * <BR>
		 */
		@Override
		public int hashCode()
		{
			return _absorber.getObjectId();
		}
	}

	/**
	 * This class is used to create item reward lists instead of creating item instances.<BR>
	 * <BR>
	 */
	public final class RewardItem
	{
		protected int _itemId;
		protected int _count;

		public RewardItem(int itemId, int count)
		{
			_itemId = itemId;
			_count = count;
		}

		public int getItemId()
		{
			return _itemId;
		}

		public int getCount()
		{
			return _count;
		}
	}

	/**
	 * The table containing all autoAttackable L2Character in its Aggro Range and L2Character that attacked the
	 * L2Attackable This Map is Thread Safe, but Removing Object While Interating Over It Will Result NPE
	 */
	private FastMap<L2Character, AggroInfo> _aggroList = new FastMap<L2Character, AggroInfo>().shared();

	/** Use this to Read or Put Object to this Map */
	public final FastMap<L2Character, AggroInfo> getAggroListRP()
	{
		return _aggroList;
	}

	/**
	 * Use this to Remove Object from this Map This Should be Synchronized While Interating over This Map - ie u cant
	 * interating and removing object at once
	 */
	public final FastMap<L2Character, AggroInfo> getAggroList()
	{
		return _aggroList;
	}

	private boolean _isReturningToSpawnPoint = false;

	public final boolean isReturningToSpawnPoint()
	{
		return _isReturningToSpawnPoint;
	}

	public final void setisReturningToSpawnPoint(boolean value)
	{
		_isReturningToSpawnPoint = value;
	}

	private boolean _canReturnToSpawnPoint = true;

	public final boolean canReturnToSpawnPoint()
	{
		return _canReturnToSpawnPoint;
	}

	public final void setCanReturnToSpawnPoint(boolean value)
	{
		_canReturnToSpawnPoint = value;
	}

	/** Table containing all Items that a Dwarf can Sweep on this L2Attackable */
	private RewardItem[] _sweepItems;

	/** crops */
	private RewardItem[] _harvestItems;
	private boolean _seeded;
	private int _seedType = 0;
	private L2PcInstance _seeder = null;

	/** True if an over-hit enabled skill has successfully landed on the L2Attackable */
	private boolean _overhit;

	/** Stores the extra (over-hit) damage done to the L2Attackable when the attacker uses an over-hit enabled skill */
	private double _overhitDamage;

	/** Stores the attacker who used the over-hit enabled skill on the L2Attackable */
	private L2Character _overhitAttacker;

	/** First CommandChannel who attacked the L2Attackable and meet the requirements **/
	private L2CommandChannel _firstCommandChannelAttacked = null;
	private CommandChannelTimer _commandChannelTimer = null;
	private long _commandChannelLastAttack = 0;
	
	/** True if a Soul Crystal was successfuly used on the L2Attackable */
	private boolean _absorbed;

	/** The table containing all L2PcInstance that successfuly absorbed the soul of this L2Attackable */
	private FastMap<L2PcInstance, AbsorberInfo> _absorbersList = new FastMap<L2PcInstance, AbsorberInfo>().shared();

	/** Have this L2Attackable to reward Exp and SP on Die? **/
	private boolean _mustGiveExpSp;

	public L2Character _mostHated;
	
	/**
	 * Constructor of L2Attackable (use L2Character and L2NpcInstance constructor).<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Call the L2Character constructor to set the _template of the L2Attackable (copy skills from template to
	 * object and link _calculators to NPC_STD_CALCULATOR)</li> <li>Set the name of the L2Attackable</li> <li>Create a
	 * RandomAnimation Task that will be launched after the calculated delay if the server allow it</li><BR>
	 * <BR>
	 * 
	 * @param objectId Identifier of the object to initialized
	 * @param L2NpcTemplate Template to apply to the NPC
	 */
	public L2Attackable(int objectId, L2NpcTemplate template)
	{
		super(objectId, template);
		getKnownList(); // init knownlist
		_mustGiveExpSp = true;
	}

	@Override
	public AttackableKnownList getKnownList()
	{
		if(super.getKnownList() == null || !(super.getKnownList() instanceof AttackableKnownList))
		{
			setKnownList(new AttackableKnownList(this));
		}

		return (AttackableKnownList) super.getKnownList();
	}

	/**
	 * Return the L2Character AI of the L2Attackable and if its null create a new one.<BR>
	 * <BR>
	 */
	@Override
	public L2CharacterAI getAI()
	{
		if(_ai == null)
		{
			synchronized (this)
			{
				if(_ai == null)
				{
					_ai = new L2AttackableAI(new AIAccessor());
				}
			}
		}
		return _ai;
	}

	// get condition to hate, actually isAggressive() is checked
	// by monster and karma by guards in motheds that overwrite this one.
	/**
	 * Not used.<BR>
	 * <BR>
	 * 
	 * @deprecated
	 */
	@Deprecated
	public boolean getCondition2(L2Character target)
	{
		if(target instanceof L2FolkInstance || target instanceof L2DoorInstance)
			return false;

		if(target.isAlikeDead() || !isInsideRadius(target, getAggroRange(), false, false) || Math.abs(getZ() - target.getZ()) > 100)
			return false;

		return !target.isInvul();
	}

	/**
	 * Reduce the current HP of the L2Attackable.<BR>
	 * <BR>
	 * 
	 * @param damage The HP decrease value
	 * @param attacker The L2Character who attacks
	 */
	@Override
	public void reduceCurrentHp(double damage, L2Character attacker)
	{
		reduceCurrentHp(damage, attacker, true);
	}

	/**
	 * Reduce the current HP of the L2Attackable, update its _aggroList and launch the doDie Task if necessary.<BR>
	 * <BR>
	 * 
	 * @param i The HP decrease value
	 * @param attacker The L2Character who attacks
	 * @param awake The awake state (If True : stop sleeping)
	 */
	@Override
	public void reduceCurrentHp(double damage, L2Character attacker, boolean awake)
	{
		/*
		if ((this instanceof L2SiegeGuardInstance) && (attacker instanceof L2SiegeGuardInstance))
		    //if((this.getEffect(L2Effect.EffectType.CONFUSION)!=null) && (attacker.getEffect(L2Effect.EffectType.CONFUSION)!=null))
		        return;

		if ((this instanceof L2MonsterInstance)&&(attacker instanceof L2MonsterInstance))
		    if((this.getEffect(L2Effect.EffectType.CONFUSION)!=null) && (attacker.getEffect(L2Effect.EffectType.CONFUSION)!=null))
		        return;
		*/

		if (isRaid() && attacker != null && attacker.getParty() != null
				&& attacker.getParty().isInCommandChannel() && attacker.getParty().getCommandChannel().meetRaidWarCondition(this))
		{
			if (_firstCommandChannelAttacked == null) //looting right isn't set
			{
				synchronized (this)
				{
					if (_firstCommandChannelAttacked == null)
					{
						_firstCommandChannelAttacked = attacker.getParty().getCommandChannel();
						if (_firstCommandChannelAttacked != null)
						{
							_commandChannelTimer = new CommandChannelTimer(this);
							_commandChannelLastAttack = System.currentTimeMillis();
							ThreadPoolManager.getInstance().scheduleGeneral(_commandChannelTimer, 10000); // check for last attack
							_firstCommandChannelAttacked.broadcastToChannelMembers(new CreatureSay(0, Say2.PARTYROOM_ALL, "", "You have looting rights!")); //TODO: retail msg
						}
					}
				}
			}
			else if (attacker.getParty().getCommandChannel().equals(_firstCommandChannelAttacked)) //is in same channel
			{
				_commandChannelLastAttack = System.currentTimeMillis(); // update last attack time
			}
		}
		/*
		// CommandChannel
		if(_commandChannelTimer == null && isRaid() && attacker != null)
		{
			if(attacker.isInParty() && attacker.getParty().isInCommandChannel() && attacker.getParty().getCommandChannel().meetRaidWarCondition(this))
			{
				_firstCommandChannelAttacked = attacker.getParty().getCommandChannel();
				_commandChannelTimer = new CommandChannelTimer(this, attacker.getParty().getCommandChannel());
				ThreadPoolManager.getInstance().scheduleGeneral(_commandChannelTimer, 300000); // 5 min
				_firstCommandChannelAttacked.broadcastToChannelMembers(new CreatureSay(0, Say2.PARTYROOM_ALL, "", "You have looting rights!"));
			}
		}
		 */
		if(isEventMob)
			return;

		// Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList
		if(attacker != null)
		{
			addDamage(attacker, (int) damage);
		}

		// If this L2Attackable is a L2MonsterInstance and it has spawned minions, call its minions to battle
		if(this instanceof L2MonsterInstance)
		{
			L2MonsterInstance master = (L2MonsterInstance) this;

			if(this instanceof L2MinionInstance)
			{
				master = ((L2MinionInstance) this).getLeader();

				if(!master.isInCombat() && !master.isDead())
				{
					master.addDamage(attacker, 1);
				}
			}

			if(master.hasMinions())
			{
				master.callMinionsToAssist(attacker);
			}

			master = null;
		}

		// Reduce the current HP of the L2Attackable and launch the doDie Task if necessary
		super.reduceCurrentHp(damage, attacker, awake);
	}

	public synchronized void setMustRewardExpSp(boolean value)
	{
		_mustGiveExpSp = value;
	}

	public synchronized boolean getMustRewardExpSP()
	{
		return _mustGiveExpSp;
	}

	/**
	 * Kill the L2Attackable (the corpse disappeared after 7 seconds), distribute rewards (EXP, SP, Drops...) and notify
	 * Quest Engine.<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their
	 * Party members</li> <li>Notify the Quest Engine of the L2Attackable death if necessary</li> <li>Kill the
	 * L2NpcInstance (the corpse disappeared after 7 seconds)</li><BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T GIVE rewards to L2PetInstance</B></FONT><BR>
	 * <BR>
	 * 
	 * @param killer The L2Character that has killed the L2Attackable
	 */
	@Override
	public boolean doDie(L2Character killer)
	{
		// Kill the L2NpcInstance (the corpse disappeared after 7 seconds)
		if(!super.doDie(killer))
			return false;

		// Enhance soul crystals of the attacker if this L2Attackable had its soul absorbed
		try
		{
			if(killer instanceof L2PcInstance)
			{
				levelSoulCrystals(killer);
			}
		}
		catch(Exception e)
		{
			_log.log(Level.SEVERE, "", e);
		}

		// Notify the Quest Engine of the L2Attackable death if necessary
		try
		{
			if(killer instanceof L2PcInstance || killer instanceof L2Summon)
			{
				L2PcInstance player = killer instanceof L2PcInstance ? (L2PcInstance) killer : ((L2Summon) killer).getOwner();

				//only 1 randomly choosen quest of all quests registered to this character can be applied
				Quest[] allOnKillQuests = getTemplate().getEventQuests(Quest.QuestEventType.ON_KILL);

				if(allOnKillQuests != null)
				{
					for(Quest quest : allOnKillQuests)
					{
						quest.notifyKill(this, player, killer instanceof L2Summon);
					}
				}

				player = null;
				allOnKillQuests = null;
			}
		}
		catch(Exception e)
		{
			_log.log(Level.SEVERE, "", e);
		}

		setChampion(false);
		//TODO
		if(Config.L2JMOD_CHAMPION_ENABLE)
		{
			//Set champion on next spawn
			if(!(this instanceof L2GrandBossInstance) && !(this instanceof L2RaidBossInstance) && this instanceof L2MonsterInstance
			/*&& !getTemplate().isQuestMonster*/&& Config.L2JMOD_CHAMPION_FREQUENCY > 0 && getLevel() >= Config.L2JMOD_CHAMP_MIN_LVL && getLevel() <= Config.L2JMOD_CHAMP_MAX_LVL)
			{
				int random = Rnd.get(100);
				if(random < Config.L2JMOD_CHAMPION_FREQUENCY)
				{
					setChampion(true);
				}
			}
		}

		return true;

	}

	class OnKillNotifyTask implements Runnable
	{
		private L2Attackable _attackable;
		private Quest _quest;
		private L2PcInstance _killer;
		private boolean _isPet;

		public OnKillNotifyTask(L2Attackable attackable, Quest quest, L2PcInstance killer, boolean isPet)
		{
			_attackable = attackable;
			_quest = quest;
			_killer = killer;
			_isPet = isPet;
		}

		@Override
		public void run()
		{
			_quest.notifyKill(_attackable, _killer, _isPet);
		}
	}

	/**
	 * Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their
	 * Party members.<BR>
	 * <BR>
	 * <B><U> Actions</U> :</B><BR>
	 * <BR>
	 * <li>Get the L2PcInstance owner of the L2SummonInstance (if necessary) and L2Party in progress</li> <li>Calculate
	 * the Experience and SP rewards in function of the level difference</li> <li>Add Exp and SP rewards to L2PcInstance
	 * (including Summon penalty) and to Party members in the known area of the last attacker</li><BR>
	 * <BR>
	 * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T GIVE rewards to L2PetInstance</B></FONT><BR>
	 * <BR>
	 * 
	 * @param lastAttacker The L2Character that has killed the L2Attackable
	 */
	@Override
	protected void calculateRewards(L2Character lastAttacker)
	{
		// Creates an empty list of rewards
		FastMap<L2Character, RewardInfo> rewards = new FastMap<L2Character, RewardInfo>().shared();

		try
		{
			if(getAggroListRP().isEmpty())
				return;

			L2PcInstance maxDealer = null; 
			int maxDamage = 0;

			int damage;

			L2Character attacker, ddealer;
			RewardInfo reward;

			// While Interacting over This Map Removing Object is Not Allowed
			synchronized (getAggroList())
			{
				// Go through the _aggroList of the L2Attackable
				for(AggroInfo info : getAggroListRP().values())
				{
					if(info == null)
					{
						continue;
					}

					// Get the L2Character corresponding to this attacker
					attacker = info._attacker;

					// Get damages done by this attacker
					damage = info._damage;

					// Prevent unwanted behavior
					if(damage > 1)
					{
						if(attacker instanceof L2SummonInstance || attacker instanceof L2PetInstance && ((L2PetInstance) attacker).getPetData().getOwnerExpTaken() > 0)
						{
							ddealer = ((L2Summon) attacker).getOwner();
						}
						else
						{
							ddealer = info._attacker;
						}

						// Check if ddealer isn't too far from this (killed monster)
						if(!Util.checkIfInRange(Config.ALT_PARTY_RANGE, this, ddealer, true))
						{
							continue;
						}

						// Calculate real damages (Summoners should get own damage plus summon's damage)
						reward = rewards.get(ddealer);

						if(reward == null)
						{
							reward = new RewardInfo(ddealer, damage);
						}
						else
						{
							reward.addDamage(damage);
						}

						rewards.put(ddealer, reward);
						
						if (ddealer instanceof L2PlayableInstance && ((L2PlayableInstance)ddealer).getActingPlayer() != null && reward._dmg > maxDamage) 
						{ 
							maxDealer = ((L2PlayableInstance)ddealer).getActingPlayer(); 
							maxDamage = reward._dmg; 
						} 
						
					}
				}
			}

			// Manage Base, Quests and Sweep drops of the L2Attackable
			doItemDrop(maxDealer != null && maxDealer.isOnline() == 1 ? maxDealer : lastAttacker);
			
			// Manage drop of Special Events created by GM for a defined period
			doEventDrop(maxDealer != null && maxDealer.isOnline() == 1 ? maxDealer : lastAttacker);

			if(!getMustRewardExpSP())
				return;
			
			if(!rewards.isEmpty())
			{
				L2Party attackerParty;
				long exp;
				int levelDiff, partyDmg, partyLvl, sp;
				float partyMul, penalty;
				RewardInfo reward2;
				int[] tmp;

				for(FastMap.Entry<L2Character, RewardInfo> entry = rewards.head(), end = rewards.tail(); (entry = entry.getNext()) != end;)
				{
					if(entry == null)
					{
						continue;
					}

					reward = entry.getValue();

					if(reward == null)
					{
						continue;
					}

					// Penalty applied to the attacker's XP
					penalty = 0;

					// Attacker to be rewarded
					attacker = reward._attacker;

					// Total amount of damage done
					damage = reward._dmg;

					// If the attacker is a Pet, get the party of the owner
					if(attacker instanceof L2PetInstance)
					{
						attackerParty = ((L2PetInstance) attacker).getParty();
					}
					else if(attacker instanceof L2PcInstance)
					{
						attackerParty = ((L2PcInstance) attacker).getParty();
					}
					else
						return;

					// If this attacker is a L2PcInstance with a summoned L2SummonInstance, get Exp Penalty applied for the current summoned L2SummonInstance
					if(attacker instanceof L2PcInstance && ((L2PcInstance) attacker).getPet() instanceof L2SummonInstance)
					{
						penalty = ((L2SummonInstance) ((L2PcInstance) attacker).getPet()).getExpPenalty();
					}

					// We must avoid "over damage", if any
					if(damage > getMaxHp())
					{
						damage = getMaxHp();
					}

					// If there's NO party in progress
					if(attackerParty == null)
					{
						// Calculate Exp and SP rewards
						if(attacker.getKnownList().knowsObject(this))
						{
							// Calculate the difference of level between this attacker (L2PcInstance or L2SummonInstance owner) and the L2Attackable
							// mob = 24, atk = 10, diff = -14 (full xp)
							// mob = 24, atk = 28, diff = 4 (some xp)
							// mob = 24, atk = 50, diff = 26 (no xp)
							levelDiff = attacker.getLevel() - getLevel();

							tmp = calculateExpAndSp(levelDiff, damage);
							exp = tmp[0];
							exp *= 1 - penalty;
							sp = tmp[1];

							if(Config.L2JMOD_CHAMPION_ENABLE && isChampion())
							{
								exp *= Config.L2JMOD_CHAMPION_REWARDS;
								sp *= Config.L2JMOD_CHAMPION_REWARDS;
							}

							// Check for an over-hit enabled strike and Donator options
							if(attacker instanceof L2PcInstance)
							{
								L2PcInstance player = (L2PcInstance) attacker;
								if(isOverhit() && attacker == getOverhitAttacker())
								{
									player.sendPacket(new SystemMessage(SystemMessageId.OVER_HIT));
									exp += calculateOverhitExp(exp);
								}
								if(player.getPremiumService() == 1)
								{
									exp = (long) (exp * Config.PREMIUM_XP_RATE);
									sp = (int) (sp * Config.PREMIUM_SP_RATE);
								}

								player = null;
							}

							// Distribute the Exp and SP between the L2PcInstance and its L2Summon
							if(!attacker.isDead())
							{
								attacker.addExpAndSp(Math.round(attacker.calcStat(Stats.EXPSP_RATE, exp, null, null)), (int) attacker.calcStat(Stats.EXPSP_RATE, sp, null, null));
							}
						}
					}
					else
					{
						//share with party members
						partyDmg = 0;
						partyMul = 1.f;
						partyLvl = 0;

						// Get all L2Character that can be rewarded in the party
						List<L2PlayableInstance> rewardedMembers = new FastList<L2PlayableInstance>();

						// Go through all L2PcInstance in the party
						List<L2PcInstance> groupMembers;

						if(attackerParty.isInCommandChannel())
						{
							groupMembers = attackerParty.getCommandChannel().getMembers();
						}
						else
						{
							groupMembers = attackerParty.getPartyMembers();
						}

						for(L2PcInstance pl : groupMembers)
						{
							if(pl == null || pl.isDead())
							{
								continue;
							}

							// Get the RewardInfo of this L2PcInstance from L2Attackable rewards
							reward2 = rewards.get(pl);

							// If the L2PcInstance is in the L2Attackable rewards add its damages to party damages
							if(reward2 != null)
							{
								if(Util.checkIfInRange(Config.ALT_PARTY_RANGE, this, pl, true))
								{
									partyDmg += reward2._dmg; // Add L2PcInstance damages to party damages
									rewardedMembers.add(pl);

									if(pl.getLevel() > partyLvl)
									{
										if(attackerParty.isInCommandChannel())
										{
											partyLvl = attackerParty.getCommandChannel().getLevel();
										}
										else
										{
											partyLvl = pl.getLevel();
										}
									}
								}

								rewards.remove(pl); // Remove the L2PcInstance from the L2Attackable rewards
							}
							else
							{
								// Add L2PcInstance of the party (that have attacked or not) to members that can be rewarded
								// and in range of the monster.
								if(Util.checkIfInRange(Config.ALT_PARTY_RANGE, this, pl, true))
								{
									rewardedMembers.add(pl);

									if(pl.getLevel() > partyLvl)
									{
										if(attackerParty.isInCommandChannel())
										{
											partyLvl = attackerParty.getCommandChannel().getLevel();
										}
										else
										{
											partyLvl = pl.getLevel();
										}
									}
								}
							}

							L2PlayableInstance summon = pl.getPet();

							if(summon != null && summon instanceof L2PetInstance)
							{
								reward2 = rewards.get(summon);

								if(reward2 != null) // Pets are only added if they have done damage
								{
									if(Util.checkIfInRange(Config.ALT_PARTY_RANGE, this, summon, true))
									{
										partyDmg += reward2._dmg; // Add summon damages to party damages
										rewardedMembers.add(summon);

										if(summon.getLevel() > partyLvl)
										{
											partyLvl = summon.getLevel();
										}
									}

									rewards.remove(summon); // Remove the summon from the L2Attackable rewards
								}
							}

							summon = null;
						}

						// If the party didn't killed this L2Attackable alone
						if(partyDmg < getMaxHp())
						{
							partyMul = (float) partyDmg / (float) getMaxHp();
						}

						// Avoid "over damage"
						if(partyDmg > getMaxHp())
						{
							partyDmg = getMaxHp();
						}

						// Calculate the level difference between Party and L2Attackable
						levelDiff = partyLvl - getLevel();

						// Calculate Exp and SP rewards
						tmp = calculateExpAndSp(levelDiff, partyDmg);
						exp = tmp[0];
						sp = tmp[1];

						if(Config.L2JMOD_CHAMPION_ENABLE && isChampion())
						{
							exp *= Config.L2JMOD_CHAMPION_REWARDS;
							sp *= Config.L2JMOD_CHAMPION_REWARDS;
						}

						exp *= partyMul;
						sp *= partyMul;

						// Check for an over-hit enabled strike
						// (When in party, the over-hit exp bonus is given to the whole party and splitted proportionally through the party members)
						if(attacker instanceof L2PcInstance)
						{
							L2PcInstance player = (L2PcInstance) attacker;

							if(isOverhit() && attacker == getOverhitAttacker())
							{
								player.sendPacket(new SystemMessage(SystemMessageId.OVER_HIT));
								exp += calculateOverhitExp(exp);
							}

							player = null;
						}

						// Distribute Experience and SP rewards to L2PcInstance Party members in the known area of the last attacker
						if(partyDmg > 0)
						{
							attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl);
						}

						groupMembers = null;
						rewardedMembers = null;
					}
				}

				attackerParty = null;
				reward2 = null;
			}

			rewards = null;
			attacker = null;
			ddealer = null;

		}
		catch(Exception e)
		{
			_log.log(Level.SEVERE, "", e);
		}
	}

	/**
	 * Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList.<BR>
	 * <BR>
	 * 
	 * @param attacker The L2Character that gave damages to this L2Attackable
	 * @param damage The number of damages given by the attacker L2Character
	 */
	public void addDamage(L2Character attacker, int damage)
	{
		addDamageHate(attacker, damage, damage);
	}

	/**
	 * Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList.<BR>
	 * <BR>
	 * 
	 * @param attacker The L2Character that gave damages to this L2Attackable
	 * @param damage The number of damages given by the attacker L2Character
	 * @param aggro The hate (=damage) given by the attacker L2Character
	 */
	public void addDamageHate(L2Character attacker, int damage, int aggro)
	{
		if(attacker == null /*|| _aggroList == null*/)
			return;

		// Get the AggroInfo of the attacker L2Character from the _aggroList of the L2Attackable
		AggroInfo ai = getAggroListRP().get(attacker);

		if(ai == null)
		{
			ai = new AggroInfo(attacker);
			ai._damage = 0;
			ai._hate = 0;
			getAggroListRP().put(attacker, ai);
		}

		// If aggro is negative, its comming from SEE_SPELL, buffs use constant 150
		if(aggro < 0)
		{
			ai._hate -= aggro * 150 / (getLevel() + 7);
			aggro = -aggro;
		}
		// if damage == 0 -> this is case of adding only to aggro list, dont apply formula on it
		else if(damage == 0)
		{
			ai._hate += aggro;
			// else its damage that must be added using constant 100
		}
		else
		{
			ai._hate += aggro * 100 / (getLevel() + 7);
		}

		// Add new damage and aggro (=damage) to the AggroInfo object
		ai._damage += damage;

		// Set the intention to the L2Attackable to AI_INTENTION_ACTIVE
		if(getAI() !=null && aggro > 0 && getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE)
		{
			getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
		}

		ai = null;
		// Notify the L2Attackable AI with EVT_ATTACKED
		if(damage > 0)
		{
			if(getAI() !=null)
				getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, attacker);

			try
			{
				if(attacker instanceof L2PcInstance || attacker instanceof L2Summon)
				{
					L2PcInstance player = attacker instanceof L2PcInstance ? (L2PcInstance) attacker : ((L2Summon) attacker).getOwner();

					if(getTemplate().getEventQuests(Quest.QuestEventType.ON_ATTACK) != null)
					{
						for(Quest quest : getTemplate().getEventQuests(Quest.QuestEventType.ON_ATTACK))
						{
							quest.notifyAttack(this, player, damage, attacker instanceof L2Summon);
						}
					}

					player = null;
				}
			}
			catch(Exception e)
			{
				_log.log(Level.SEVERE, "", e);
			}
		}
	}

	public void reduceHate(L2Character target, int amount)
	{
		if(getAI() instanceof L2SiegeGuardAI)
		{
			// TODO: this just prevents error until siege guards are handled properly
			stopHating(target);
			setTarget(null);
			getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null, null);

			return;
		}
		if(target == null) // whole aggrolist
		{
			L2Character mostHated = getMostHated();

			if(mostHated == null) // makes target passive for a moment more
			{
				((L2AttackableAI) getAI()).setGlobalAggro(-25);
				return;
			}
			else
			{
				for(L2Character aggroed : getAggroListRP().keySet())
				{
					AggroInfo ai = getAggroListRP().get(aggroed);
					if(ai == null)
						return;

					ai._hate -= amount;

					ai = null;
				}
			}

			amount = getHating(mostHated);
			if(amount <= 0)
			{
				((L2AttackableAI) getAI()).setGlobalAggro(-25);
				clearAggroList();
				getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
				setWalking();
			}

			mostHated = null;

			return;
		}

		AggroInfo ai = getAggroListRP().get(target);

		if(ai == null)
			return;

		ai._hate -= amount;

		if(ai._hate <= 0)
		{
			if(getMostHated() == null)
			{
				((L2AttackableAI) getAI()).setGlobalAggro(-25);
				clearAggroList();
				getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
				setWalking();
			}
		}

		ai = null;
	}

	/**
	 * Clears _aggroList hate of the L2Character without removing from the list.<BR>
	 * <BR>
	 */
	public void stopHating(L2Character target)
	{
		if(target == null)
			return;

		AggroInfo ai = getAggroListRP().get(target);

		if(ai == null)
			return;

		ai._hate = 0;
		ai = null;
	}

	/**
	 * Return the most hated L2Character of the L2Attackable _aggroList.<BR>
	 * <BR>
	 */
	public L2Character getMostHated()
	{
		if(getAggroListRP().isEmpty() || isAlikeDead())
			return null;

		L2Character mostHated = null;

		int maxHate = 0;

		// While Interating over This Map Removing Object is Not Allowed
		synchronized (getAggroList())
		{
			// Go through the aggroList of the L2Attackable
			for(AggroInfo ai : getAggroListRP().values())
			{
				if(ai == null)
				{
					continue;
				}
				
				if(ai._attacker.isAlikeDead() || !getKnownList().knowsObject(ai._attacker) || !ai._attacker.isVisible() || ai._attacker instanceof L2PcInstance && ((L2PcInstance)ai._attacker).isOffline())
				{
					ai._hate = 0;
				}

				if(ai._hate > maxHate)
				{
					mostHated = ai._attacker;
					maxHate = ai._hate;
				}
			}
		}
		
		if(mostHated!=null)
			_mostHated = mostHated;
		
		return mostHated;
	}

	/**
	 * useful to give correctly the item to players
	 * @return
	 */
	/*
	public L2Character getMostDamager(){
	
		
		if(getAggroListRP().isEmpty() || isAlikeDead())
			return null;

		//L2Character mostDamager = null;

		int maxDamage = 0;

		// While Interating over This Map Removing Object is Not Allowed
		synchronized (getAggroList())
		{
			// Go through the aggroList of the L2Attackable
			for(AggroInfo ai : getAggroListRP().values())
			{
				if(ai == null)
				{
					continue;
				}

				if(ai._attacker.isAlikeDead() || !getKnownList().knowsObject(ai._attacker) || !ai._attacker.isVisible() || ai._attacker instanceof L2PcInstance && ((L2PcInstance)ai._attacker).isOffline() )
				{
					ai._damage = 0;
				}

				if(ai._damage > maxDamage)
				{
					_mostDamager = ai._attacker;
					maxDamage = ai._damage;
				}
			}
		}
		return _mostDamager;
		
	}
	*/
	/**
	 * Return the hate level of the L2Attackable against this L2Character contained in _aggroList.<BR>
	 * <BR>
	 * 
	 * @param target The L2Character whose hate level must be returned
	 */
	public int getHating(L2Character target)
	{
		if(getAggroListRP().isEmpty())
			return 0;
			
		if(target==null)
			return 0;
		
		AggroInfo ai = getAggroListRP().get(target);

		if(ai == null)
			return 0;

		if(ai._attacker instanceof L2PcInstance && (((L2PcInstance) ai._attacker).getAppearance().getInvisible() || ai._attacker.isInvul()))
		{
			//Remove Object Should Use This Method and Can be Blocked While Interating
			getAggroList().remove(target);

			return 0;
		}

		if(!ai._attacker.isVisible())
		{
			getAggroList().remove(target);

			return 0;
		}

		if(ai._attacker.isAlikeDead())
		{
			ai._hate = 0;

			return 0;
		}

		return ai._hate;
	}

	/**
	 * Calculates quantity of items for specific drop acording to current situation <br>
	 * 
	 * @param drop The L2DropData count is being calculated for
	 * @param lastAttacker The L2PcInstance that has killed the L2Attackable
	 * @param deepBlueDrop Factor to divide the drop chance
	 * @param levelModifier level modifier in %'s (will be subtracted from drop chance)
	 */
	private RewardItem calculateRewardItem(L2PcInstance lastAttacker, L2DropData drop, int levelModifier, boolean isSweep)
	{
		// Get default drop chance
		if((Config.HIGH_RATE_SERVER_DROPS && !drop.isQuestDrop() && drop.getItemId()!=57)){ //it's not adena-->check if drop has an epic jewel
			
			//ant queen,orfen,core,frintezza,baium,antharas,valakas,zaken,stones
			if(drop.getItemId()==6660 || drop.getItemId()==6661 || drop.getItemId()==6662 || drop.getItemId()==8191 || drop.getItemId()==6658 || drop.getItemId()==6656 || drop.getItemId()==6657 || drop.getItemId()==6659 || (drop.getItemId()>=6360 && drop.getItemId()<=6362) || (drop.getItemId()>=8723 && drop.getItemId()<=8762)){
				//if epic jewel, seal stones or life stone, continue
			}else
				return null;
			
		}
		
		float dropChance = 0;
		
		switch(drop.getItemId()){
			case 6662:{ //core ring
				if(Config.CORE_RING_CHANCE>0)
					dropChance = ((float) 10000*Config.CORE_RING_CHANCE);
				else
					dropChance = drop.getChance();
			}
			break;
			case 6661:{ //orfen earring
				if(Config.ORFEN_EARRING_CHANCE>0)
					dropChance = ((float) 10000*Config.ORFEN_EARRING_CHANCE);
				else
					dropChance = drop.getChance();
			}
			break;
			case 6659:{ //zaken earring
				if(Config.ZAKEN_EARRING_CHANCE>0)
					dropChance = ((float) 10000*Config.ZAKEN_EARRING_CHANCE);
				else
					dropChance = drop.getChance();
			}
			break;
			case 6660:{ //aq ring
				if(Config.QA_RING_CHANCE>0)
					dropChance = ((float) 10000*Config.QA_RING_CHANCE);
				else
					dropChance = drop.getChance();
			}
			break;
			default:{
				dropChance = drop.getChance();
			}
		}
		
		int deepBlueDrop = 1;

		if(Config.DEEPBLUE_DROP_RULES)
		{
			if(levelModifier > 0)
			{
				// We should multiply by the server's drop rate, so we always get a low chance of drop for deep blue mobs.
				// NOTE: This is valid only for adena drops! Others drops will still obey server's rate
				deepBlueDrop = 3;

				if(drop.getItemId() == 57)
				{
					deepBlueDrop *= isRaid() ? (int) 1 : (int) Config.RATE_DROP_ITEMS;
				}
			}
		}

		if(deepBlueDrop == 0)
		{
			deepBlueDrop = 1;
		}

		// Check if we should apply our maths so deep blue mobs will not drop that easy
		if(Config.DEEPBLUE_DROP_RULES)
		{
			dropChance = (drop.getChance() - drop.getChance() * levelModifier / 100) / deepBlueDrop;
		}

		// Applies Drop rates
		if(drop.getItemId() == 57)
		{
			if(this instanceof L2RaidBossInstance)
			{
				dropChance *= Config.ADENA_RAID;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ADENA_RAID;
				}
			}
			else if(this instanceof L2GrandBossInstance)
			{
				dropChance *= Config.ADENA_BOSS;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ADENA_BOSS;
				}
			}
			else if(this instanceof L2MinionInstance)
			{
				dropChance *= Config.ADENA_MINON;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ADENA_MINON;
				}
			}
			else
			{
				dropChance *= Config.RATE_DROP_ADENA;

				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ADENA_RATE;
				}
			}
			
			
		}
		else if(isSweep)
		{
			if(this instanceof L2RaidBossInstance)
			{
				dropChance *= Config.SPOIL_RAID;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_SPOIL_RAID;
				}
			}
			else if(this instanceof L2GrandBossInstance)
			{
				dropChance *= Config.SPOIL_BOSS;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_SPOIL_BOSS;
				}
			}
			else if(this instanceof L2MinionInstance)
			{
				dropChance *= Config.SPOIL_MINON;
				
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_SPOIL_MINON;
				}
			}
			else
			{
				dropChance *= Config.RATE_DROP_SPOIL;

				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_SPOIL_RATE;
				}
			}
		}
		else
		{
			if(this instanceof L2RaidBossInstance)
			{
				dropChance *= Config.ITEMS_RAID;
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ITEMS_RAID;
				}
			}
			else if(this instanceof L2GrandBossInstance)
			{
				dropChance *= Config.ITEMS_BOSS;
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ITEMS_BOSS;
				}
			}
			else if(this instanceof L2MinionInstance)
			{
				dropChance *= Config.ITEMS_MINON;
				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_ITEMS_MINON;
				}
			}
			else
			{
				dropChance *= Config.RATE_DROP_ITEMS;

				if(lastAttacker.getPremiumService() == 1)
				{
					dropChance *= Config.PREMIUM_DROP_RATE;
				}
			}
		}

		if(Config.L2JMOD_CHAMPION_ENABLE && isChampion())
		{
			dropChance *= Config.L2JMOD_CHAMPION_REWARDS;
		}

		// Round drop chance
		dropChance = Math.round(dropChance);

		// Set our limits for chance of drop
		if(dropChance < 1)
		{
			dropChance = 1;
		}

		// If item is adena, dont drop multiple time
		//         if (drop.getItemId() == 57 && dropChance > L2DropData.MAX_CHANCE) 
		//         	dropChance = L2DropData.MAX_CHANCE; 

		// Get min and max Item quantity that can be dropped in one time
		int minCount = drop.getMinDrop();
		int maxCount = drop.getMaxDrop();
		int itemCount = 0;

		// Count and chance adjustment for high rate servers
		if(dropChance > L2DropData.MAX_CHANCE && !Config.PRECISE_DROP_CALCULATION)
		{
			int multiplier = (int) dropChance / L2DropData.MAX_CHANCE;

			if(minCount < maxCount)
			{
				itemCount += Rnd.get(minCount * multiplier, maxCount * multiplier);
			}
			else if(minCount == maxCount)
			{
				itemCount += minCount * multiplier;
			}
			else
			{
				itemCount += multiplier;
			}

			dropChance = dropChance % L2DropData.MAX_CHANCE;
		}

		// Check if the Item must be dropped
		int random = Rnd.get(L2DropData.MAX_CHANCE);

		while(random < dropChance)
		{
			// Get the item quantity dropped
			if(minCount < maxCount)
			{
				itemCount += Rnd.get(minCount, maxCount);
			}
			else if(minCount == maxCount)
			{
				itemCount += minCount;
			}
			else
			{
				itemCount++;
			}

			// Prepare for next iteration if dropChance > L2DropData.MAX_CHANCE
			dropChance -= L2DropData.MAX_CHANCE;
		}
		if(Config.L2JMOD_CHAMPION_ENABLE)
			if((drop.getItemId() == 57 || drop.getItemId() >= 6360 && drop.getItemId() <= 6362) && isChampion())
			{
				itemCount *= Config.L2JMOD_CHAMPION_ADENAS_REWARDS;
			}

		if(drop.getItemId() >= 6360 && drop.getItemId() <= 6362)
		{
			itemCount *= Config.RATE_DROP_SEAL_STONES;
		}

		if(itemCount > 0)
			return new RewardItem(drop.getItemId(), itemCount);
		else if(itemCount == 0 && Config.DEBUG)
		{
			_log.fine("Roll produced 0 items to drop...");
		}

		return null;
	}

	/**
	 * Calculates quantity of items for specific drop CATEGORY according to current situation <br>
	 * Only a max of ONE item from a category is allowed to be dropped.
	 * 
	 * @param drop The L2DropData count is being calculated for
	 * @param lastAttacker The L2PcInstance that has killed the L2Attackable
	 * @param deepBlueDrop Factor to divide the drop chance
	 * @param levelModifier level modifier in %'s (will be subtracted from drop chance)
	 */
	private RewardItem calculateCategorizedRewardItem(L2PcInstance lastAttacker, L2DropCategory categoryDrops, int levelModifier)
	{
		if(categoryDrops == null)
			return null;

		if((Config.HIGH_RATE_SERVER_DROPS && categoryDrops.getCategoryType()!=0)){ //it's not adena-->check if drop is quest or is an epic jewel
			
			boolean to_drop = false;
			
			for(L2DropData dd : categoryDrops.getAllDrops()){
				
				//quest_drop,ant queen,orfen,core,frintezza,baium,antharas,valakas,zaken, seal Stones, life stones
				if(dd.isQuestDrop() || dd.getItemId()==6660 || dd.getItemId()==6661 || dd.getItemId()==6662 || dd.getItemId()==8191 || dd.getItemId()==6658 || dd.getItemId()==6656 || dd.getItemId()==6657 || dd.getItemId()==6659 || (dd.getItemId()>=6360 && dd.getItemId()<=6362) || (dd.getItemId()>=8723 && dd.getItemId()<=8762) ){
					//if epic jewel, return just 1 from raid
					to_drop = true;
				}
				
			}
			
			if(!to_drop){
				return null;
			}
			
		}
		
		// Get default drop chance for the category (that's the sum of chances for all items in the category)
		// keep track of the base category chance as it'll be used later, if an item is drop from the category.
		// for everything else, use the total "categoryDropChance"
		int basecategoryDropChance = categoryDrops.getCategoryChance();
		int categoryDropChance = basecategoryDropChance;

		
		int deepBlueDrop = 1;

		if(Config.DEEPBLUE_DROP_RULES)
		{
			if(levelModifier > 0)
			{
				// We should multiply by the server's drop rate, so we always get a low chance of drop for deep blue mobs.
				// NOTE: This is valid only for adena drops! Others drops will still obey server's rate
				deepBlueDrop = 3;
			}
		}

		if(deepBlueDrop == 0)
		{
			deepBlueDrop = 1;
		}

		// Check if we should apply our maths so deep blue mobs will not drop that easy
		if(Config.DEEPBLUE_DROP_RULES)
		{
			categoryDropChance = (categoryDropChance - categoryDropChance * levelModifier / 100) / deepBlueDrop;
		}

		// Applies Drop rates
		if(this instanceof L2RaidBossInstance)
		{
			categoryDropChance *= Config.ITEMS_RAID;
		}
		else if(this instanceof L2GrandBossInstance)
		{
			categoryDropChance *= Config.ITEMS_BOSS;
		}
		else if(this instanceof L2MinionInstance)
		{
			categoryDropChance *= Config.ITEMS_MINON;
		}
		else
		{
			categoryDropChance *= Config.RATE_DROP_ITEMS;
		}

		if(Config.L2JMOD_CHAMPION_ENABLE && isChampion())
		{
			categoryDropChance *= Config.L2JMOD_CHAMPION_REWARDS;
		}

		// Round drop chance
		categoryDropChance = Math.round(categoryDropChance);

		
		// Set our limits for chance of drop
		if(categoryDropChance < 1)
		{
			categoryDropChance = 1;
		}

		// Check if an Item from this category must be dropped
		if(Rnd.get(L2DropData.MAX_CHANCE) < categoryDropChance)
		{
			L2DropData drop = categoryDrops.dropOne(isRaid());

			if(drop == null)
				return null;

			// Now decide the quantity to drop based on the rates and penalties.  To get this value
			// simply divide the modified categoryDropChance by the base category chance.  This
			// results in a chance that will dictate the drops amounts: for each amount over 100
			// that it is, it will give another chance to add to the min/max quantities.
			//
			// For example, If the final chance is 120%, then the item should drop between
			// its min and max one time, and then have 20% chance to drop again.  If the final
			// chance is 330%, it will similarly give 3 times the min and max, and have a 30%
			// chance to give a 4th time.
			// At least 1 item will be dropped for sure.  So the chance will be adjusted to 100%
			// if smaller.

			//int dropChance = drop.getChance();
			int dropChance = 0;
			
			switch(drop.getItemId()){
				case 6662:{ //core ring
					if(Config.CORE_RING_CHANCE>0)
						dropChance = (10000*Config.CORE_RING_CHANCE);
					else
						dropChance = drop.getChance();
				}
				break;
				case 6661:{ //orfen earring
					if(Config.ORFEN_EARRING_CHANCE>0)
						dropChance = (10000*Config.ORFEN_EARRING_CHANCE);
					else
						dropChance = drop.getChance();
				}
				break;
				case 6659:{ //zaken earring
					if(Config.ZAKEN_EARRING_CHANCE>0)
						dropChance = (10000*Config.ZAKEN_EARRING_CHANCE);
					else
						dropChance = drop.getChance();
				}
				break;
				case 6660:{ //aq ring
					if(Config.QA_RING_CHANCE>0)
						dropChance = (10000*Config.QA_RING_CHANCE);
					else
						dropChance = drop.getChance();
				}
				break;
				default:{
					dropChance = drop.getChance();
				}
			}
			
			if(drop.getItemId() == 57)
			{
				if(this instanceof L2RaidBossInstance)
				{
					dropChance *= Config.ADENA_RAID;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ADENA_RAID;
					}
				}
				else if(this instanceof L2GrandBossInstance)
				{
					dropChance *= Config.ADENA_BOSS;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ADENA_BOSS;
					}
				}
				else if(this instanceof L2MinionInstance)
				{
					dropChance *= Config.ADENA_MINON;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ADENA_MINON;
					}
				}
				else
				{
					dropChance *= Config.RATE_DROP_ADENA;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ADENA_RATE;
					}
				}
				
			}
			else
			{
				if(this instanceof L2RaidBossInstance)
				{
					dropChance *= Config.ITEMS_RAID;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ITEMS_RAID;
					}
				}
				else if(this instanceof L2GrandBossInstance)
				{
					dropChance *= Config.ITEMS_BOSS;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ITEMS_BOSS;
					}
				}
				else if(this instanceof L2MinionInstance)
				{
					dropChance *= Config.ITEMS_MINON;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_ITEMS_MINON;
					}
				}
				else
				{
					dropChance *= Config.RATE_DROP_ITEMS;
					if(lastAttacker.getPremiumService() == 1)
					{
						dropChance *= Config.PREMIUM_DROP_RATE;
					}
				}
			}

			if(Config.L2JMOD_CHAMPION_ENABLE && isChampion())
			{
				dropChance *= Config.L2JMOD_CHAMPION_REWARDS;
			}

			dropChance = Math.round(dropChance);

			if(dropChance < L2DropData.MAX_CHANCE)
			{
				dropChance = L2DropData.MAX_CHANCE;
			}

			// Get min and max Item quantity that can be dropped in one time
			int min = drop.getMinDrop();
			int max = drop.getMaxDrop();

			// Get the item quantity dropped
			int itemCount = 0;

			// Count and chance adjustment for high rate servers
			if(dropChance > L2DropData.MAX_CHANCE && !Config.PRECISE_DROP_CALCULATION)
			{
				int multiplier = (dropChance) / L2DropData.MAX_CHANCE;

				if(min < max)
				{
					itemCount += Rnd.get(min * multiplier, max * multiplier);
				}
				else if(min == max)
				{
					itemCount += min * multiplier;
				}
				else
				{
					itemCount += multiplier;
				}

				dropChance = dropChance % L2DropData.MAX_CHANCE;
			}

			// Check if the Item must be dropped
			int random = Rnd.get(L2DropData.MAX_CHANCE);

			while(random < dropChance)
			{
				// Get the item quantity dropped
				if(min < max)
				{
					itemCount += Rnd.get(min, max);
				}
				else if(min == max)
				{
					itemCount += min;
				}
				else
				{
					itemCount++;
				}

				// Prepare for next iteration if dropChance > L2DropData.MAX_CHANCE
				dropChance -= L2DropData.MAX_CHANCE;
			}
			if(Config.L2JMOD_CHAMPION_ENABLE)
				if((drop.getItemId() == 57 || drop.getItemId() >= 6360 && drop.getItemId() <= 6362) && isChampion())
				{
					itemCount *= Config.L2JMOD_CHAMPION_ADENAS_REWARDS;
				}

			if(drop.getItemId() >= 6360 && drop.getItemId() <= 6362)
			{
				itemCount *= Config.RATE_DROP_SEAL_STONES;
			}

			if(itemCount > 0)
				return new RewardItem(drop.getItemId(), itemCount);
			else if(itemCount == 0 && Config.DEBUG)
			{
				_log.fine("Roll produced 0 items to drop...");
			}

			drop = null;
		}
		return null;
	}

	/**
	 * Calculates the level modifier for drop<br>
	 * 
	 * @param lastAttacker The L2PcInstance that has killed the L2Attackable
	 */
	private int calculateLevelModifierForDrop(L2PcInstance lastAttacker)
	{
		if(Config.DEEPBLUE_DROP_RULES)
		{
			int highestLevel = lastAttacker.getLevel();

			// Check to prevent very high level player to nearly kill mob and let low level player do the last hit.
			if(getAttackByList() != null && !getAttackByList().isEmpty())
			{
				for(L2Character atkChar : getAttackByList())
					if(atkChar != null && atkChar.getLevel() > highestLevel)
					{
						highestLevel = atkChar.getLevel();
					}
			}

			// According to official data (Prima), deep blue mobs are 9 or more levels below players
			if(highestLevel - 9 >= getLevel())
				return (highestLevel - (getLevel() + 8)) * 9;
		}

		return 0;
	}

	public void doItemDrop(L2Character lastAttacker)
	{
		doItemDrop(getTemplate(), lastAttacker);
	}

	/**
	 * Manage Base, Quests and Special Events drops of L2Attackable (called by calculateRewards).<BR>
	 * <BR>
	 * <B><U> Concept</U> :</B><BR>
	 * <BR>
	 * During a Special Event all L2Attackable can drop extra Items. Those extra Items are defined in the table
	 * <B>allNpcDateDrops</B> of the EventDroplist. Each Special Event has a start and end date to stop to drop extra
	 * Items automaticaly. <BR>
	 * <BR>
	 * <B><U> Actions</U> : </B><BR>
	 * <BR>
	 * <li>Manage drop of Special Events created by GM for a defined period</li> <li>Get all possible drops of this
	 * L2Attackable from L2NpcTemplate and add it Quest drops</li> <li>For each possible drops (base + quests),
	 * calculate which one must be dropped (random)</li> <li>Get each Item quantity dropped (random)</li> <li>Create
	 * this or these L2ItemInstance corresponding to each Item Identifier dropped</li> <li>If the autoLoot mode is actif
	 * and if the L2Character that has killed the L2Attackable is a L2PcInstance, give this or these Item(s) to the
	 * L2PcInstance that has killed the L2Attackable</li> <li>If the autoLoot mode isn't actif or if the L2Character
	 * that has killed the L2Attackable is not a L2PcInstance, add this or these Item(s) in the world as a visible
	 * object at the position where mob was last</li><BR>
	 * <BR>
	 * 
	 * @param lastAttacker The L2Character that has killed the L2Attackable
	 */
	public void doItemDrop(L2NpcTemplate npcTemplate, L2Character lastAttacker)
	{
		L2PcInstance player = null;

		if(lastAttacker instanceof L2PcInstance)
		{
			player = (L2PcInstance) lastAttacker;
		}
		else if(lastAttacker instanceof L2Summon)
		{
			player = ((L2Summon) lastAttacker).getOwner();
		}

		// Don't drop anything if the last attacker or ownere isn't L2PcInstance
		if(player == null)
			return;

		int levelModifier = calculateLevelModifierForDrop(player); // level modifier in %'s (will be subtracted from drop chance)

		// Check the drop of a cursed weapon
		if(levelModifier == 0 && player.getLevel() > 20)
		{
			CursedWeaponsManager.getInstance().checkDrop(this, player);
		}

		// now throw all categorized drops and handle spoil.
		for(L2DropCategory cat : npcTemplate.getDropData())
		{
			RewardItem item = null;
			if(cat.isSweep())
			{
				// according to sh1ny, seeded mobs CAN be spoiled and swept.
				if(isSpoil()/* && !isSeeded() */)
				{
					FastList<RewardItem> sweepList = new FastList<RewardItem>();

					for(L2DropData drop : cat.getAllDrops())
					{
						item = calculateRewardItem(player, drop, levelModifier, true);

						if(item == null)
						{
							continue;
						}

						if(Config.DEBUG)
						{
							_log.fine("Item id to spoil: " + item.getItemId() + " amount: " + item.getCount());
						}

						sweepList.add(item);

						item = null;
					}

					// Set the table _sweepItems of this L2Attackable
					if(!sweepList.isEmpty())
					{
						_sweepItems = sweepList.toArray(new RewardItem[sweepList.size()]);
					}

					sweepList = null;
				}
			}
			else
			{
				if(isSeeded())
				{
					L2DropData drop = cat.dropSeedAllowedDropsOnly();

					if(drop == null)
					{
						continue;
					}

					item = calculateRewardItem(player, drop, levelModifier, false);
					drop = null;
				}
				else
				{
					item = calculateCategorizedRewardItem(player, cat, levelModifier);
				}

				if(item != null)
				{
					if(Config.DEBUG)
					{
						_log.fine("Item id to drop: " + item.getItemId() + " amount: " + item.getCount());
					}

					// Check if the autoLoot mode is active
					if(Config.AUTO_LOOT || player.isAutoLoot())
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(item_templ == null){
							_log.info("ERROR: Item id to autoloot " + item.getItemId() + " has not template into items/armor/weapon tables.. It cannot be dropped..");
							//DropItem(player, item);
						}else{
							
							if(!player.getInventory().validateCapacity(item_templ) || (!Config.AUTO_LOOT_BOSS && this instanceof L2RaidBossInstance) || (!Config.AUTO_LOOT_BOSS && this instanceof L2GrandBossInstance))
								DropItem(player, item);
							else
								player.doAutoLoot(this, item); // Give this or these Item(s) to the L2PcInstance that has killed the L2Attackable
						
						}
					
					}
					else
					{
						DropItem(player, item); // drop the item on the ground
					}

					// Broadcast message if RaidBoss was defeated
					if(this instanceof L2RaidBossInstance || this instanceof L2GrandBossInstance  )
					{
						SystemMessage sm;
						sm = new SystemMessage(SystemMessageId.S1_DIED_DROPPED_S3_S2);
						sm.addString(getName());
						sm.addItemName(item.getItemId());
						sm.addNumber(item.getCount());
						broadcastPacket(sm);
						sm = null;
					}
				}
			}

			item = null;
		}

		// Apply Special Item drop with rnd qty for champions
		if(Config.L2JMOD_CHAMPION_ENABLE && isChampion() && player.getLevel() <= getLevel()+3 && Config.L2JMOD_CHAMPION_REWARD > 0 && Rnd.get(100) < Config.L2JMOD_CHAMPION_REWARD)
		{
			int champqty = Rnd.get(Config.L2JMOD_CHAMPION_REWARD_QTY);
			champqty++; //quantity should actually vary between 1 and whatever admin specified as max, inclusive.

			RewardItem item = new RewardItem(Config.L2JMOD_CHAMPION_REWARD_ID, champqty);

			// Give this or these Item(s) to the L2PcInstance that has killed the L2Attackable
			if(Config.AUTO_LOOT)
			{
				L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
				
				if(!player.getInventory().validateCapacity(item_templ))
					DropItem(player, item);
				else
					player.addItem("ChampionLoot", item.getItemId(), item.getCount(), this, true);
					
			}
			else
			{
				DropItem(player, item);
			}

			item = null;
		}

		//Instant Item Drop :>
		double rateHp = getStat().calcStat(Stats.MAX_HP, 1, this, null);

		if(rateHp < 2 && String.valueOf(npcTemplate.type).contentEquals("L2Monster")) //only L2Monster with <= 1x HP can drop herbs
		{
			boolean _hp = false;
			boolean _mp = false;
			boolean _spec = false;

			//ptk - patk type enhance
			int random = Rnd.get(1000); // note *10

			if(random < Config.RATE_DROP_SPECIAL_HERBS && !_spec) // && !_spec useless yet
			{
				RewardItem item = new RewardItem(8612, 1); // Herb of Warrior

				if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
				{
					L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
					
					if(!player.getInventory().validateCapacity(item_templ))
						DropItem(player, item);
					else
						player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
				}
				else
				{
					DropItem(player, item);
				}

				item = null;
				_spec = true;
			}
			else
			{
				for(int i = 0; i < 3; i++)
				{
					random = Rnd.get(100);

					if(random < Config.RATE_DROP_COMMON_HERBS)
					{
						RewardItem item = null;
						if(i == 0)
						{
							item = new RewardItem(8606, 1); // Herb of Power
						}
						if(i == 1)
						{
							item = new RewardItem(8608, 1); // Herb of Atk. Spd.
						}
						if(i == 2)
						{
							item = new RewardItem(8610, 1); // Herb of Critical Attack
						}

						if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
						{
							L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
							
							if(!player.getInventory().validateCapacity(item_templ))
								DropItem(player, item);
							else
								player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
						}
						else
						{
							DropItem(player, item);
						}

						item = null;
						break;
					}
				}
			}

			//mtk - matk type enhance
			random = Rnd.get(1000); // note *10

			if(random < Config.RATE_DROP_SPECIAL_HERBS && !_spec)
			{
				RewardItem item = new RewardItem(8613, 1); // Herb of Mystic

				if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
				{
					L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
					
					if(!player.getInventory().validateCapacity(item_templ))
						DropItem(player, item);
					else
						player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
				}
				else
				{
					DropItem(player, item);
				}

				item = null;
				_spec = true;
			}
			else
			{
				for(int i = 0; i < 2; i++)
				{
					random = Rnd.get(100);

					if(random < Config.RATE_DROP_COMMON_HERBS)
					{
						RewardItem item = null;
						if(i == 0)
						{
							item = new RewardItem(8607, 1); // Herb of Magic
						}
						if(i == 1)
						{
							item = new RewardItem(8609, 1); // Herb of Casting Speed
						}

						if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
						{
							L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
							
							if(!player.getInventory().validateCapacity(item_templ))
								DropItem(player, item);
							else
								player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
						}
						else
						{
							DropItem(player, item);
						}

						item = null;
						break;
					}
				}
			}

			//hp+mp type
			random = Rnd.get(1000); // note *10

			if(random < Config.RATE_DROP_SPECIAL_HERBS && !_spec)
			{
				RewardItem item = new RewardItem(8614, 1); // Herb of Recovery

				if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
				{
					L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
					
					if(!player.getInventory().validateCapacity(item_templ))
						DropItem(player, item);
					else
						player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
				}
				else
				{
					DropItem(player, item);
				}

				item = null;
				_mp = true;
				_hp = true;
				_spec = true;
			}

			//hp - restore hp type
			if(!_hp)
			{
				random = Rnd.get(100);
				if(random < Config.RATE_DROP_MP_HP_HERBS)
				{
					RewardItem item = new RewardItem(8600, 1); // Herb of Life

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
					_hp = true;
				}
			}
			if(!_hp)
			{
				random = Rnd.get(100);

				if(random < Config.RATE_DROP_GREATER_HERBS)
				{
					RewardItem item = new RewardItem(8601, 1); // Greater Herb of Life

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
					_hp = true;
				}
			}
			if(!_hp)
			{
				random = Rnd.get(1000); // note *10

				if(random < Config.RATE_DROP_SUPERIOR_HERBS)
				{
					RewardItem item = new RewardItem(8602, 1); // Superior Herb of Life

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
				}
			}
			//mp - restore mp type
			if(!_mp)
			{
				random = Rnd.get(100);

				if(random < Config.RATE_DROP_MP_HP_HERBS)
				{
					RewardItem item = new RewardItem(8603, 1); // Herb of Mana

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
					_mp = true;
				}
			}
			if(!_mp)
			{
				random = Rnd.get(100);

				if(random < Config.RATE_DROP_GREATER_HERBS)
				{
					RewardItem item = new RewardItem(8604, 1); // Greater Herb of Mana

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
					_mp = true;
				}
			}
			if(!_mp)
			{
				random = Rnd.get(1000); // note *10

				if(random < Config.RATE_DROP_SUPERIOR_HERBS)
				{
					RewardItem item = new RewardItem(8605, 1); // Superior Herb of Mana

					if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
					{
						L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
						
						if(!player.getInventory().validateCapacity(item_templ))
							DropItem(player, item);
						else
							player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
					}
					else
					{
						DropItem(player, item);
					}

					item = null;
				}
			}
			// speed enhance type
			random = Rnd.get(100);

			if(random < Config.RATE_DROP_COMMON_HERBS)
			{
				RewardItem item = new RewardItem(8611, 1); // Herb of Speed

				if(Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
				{
					L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
					
					if(!player.getInventory().validateCapacity(item_templ))
						DropItem(player, item);
					else
						player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
				}
				else
				{
					DropItem(player, item);
				}

				item = null;
			}
		}
	}

	/**
	 * Manage Special Events drops created by GM for a defined period.<BR>
	 * <BR>
	 * <B><U> Concept</U> :</B><BR>
	 * <BR>
	 * During a Special Event all L2Attackable can drop extra Items. Those extra Items are defined in the table
	 * <B>allNpcDateDrops</B> of the EventDroplist. Each Special Event has a start and end date to stop to drop extra
	 * Items automaticaly. <BR>
	 * <BR>
	 * <B><U> Actions</U> : <I>If an extra drop must be generated</I></B><BR>
	 * <BR>
	 * <li>Get an Item Identifier (random) from the DateDrop Item table of this Event</li> <li>Get the Item quantity
	 * dropped (random)</li> <li>Create this or these L2ItemInstance corresponding to this Item Identifier</li> <li>If
	 * the autoLoot mode is actif and if the L2Character that has killed the L2Attackable is a L2PcInstance, give this
	 * or these Item(s) to the L2PcInstance that has killed the L2Attackable</li> <li>If the autoLoot mode isn't actif
	 * or if the L2Character that has killed the L2Attackable is not a L2PcInstance, add this or these Item(s) in the
	 * world as a visible object at the position where mob was last</li><BR>
	 * <BR>
	 * 
	 * @param lastAttacker The L2Character that has killed the L2Attackable
	 */
	public void doEventDrop(L2Character lastAttacker)
	{
		L2PcInstance player = null;

		if(lastAttacker instanceof L2PcInstance)
		{
			player = (L2PcInstance) lastAttacker;
		}
		else if(lastAttacker instanceof L2Summon)
		{
			player = ((L2Summon) lastAttacker).getOwner();
		}

		if(player == null)
			return; // Don't drop anything if the last attacker or ownere isn't L2PcInstance

		if(player.getLevel() - getLevel() > 9)
			return;

		// Go through DateDrop of EventDroplist allNpcDateDrops within the date range
		for(DateDrop drop : EventDroplist.getInstance().getAllDrops())
		{
			if(Rnd.get(L2DropData.MAX_CHANCE) < drop.chance)
			{
				RewardItem item = new RewardItem(drop.items[Rnd.get(drop.items.length)], Rnd.get(drop.min, drop.max));

				if(Config.AUTO_LOOT)
				{
					L2Item item_templ = ItemTable.getInstance().getTemplate(item.getItemId());
					
					if(!player.getInventory().validateCapacity(item_templ))
						DropItem(player, item);
					else
						player.doAutoLoot(this, item); // Give this or these Item(s) to the L2PcInstance that has killed the L2Attackable
				}
				else
				{
					DropItem(player, item); // drop the item on the ground
				}

				item = null;
			}
		}

		player = null;
	}

	/**
	 * Drop reward item.<BR>
	 * <BR>
	 */
	public L2ItemInstance DropItem(L2PcInstance mainDamageDealer, RewardItem item)
	{
		int randDropLim = 70;

		L2ItemInstance ditem = null;

		for(int i = 0; i < item.getCount(); i++)
		{
			// Randomize drop position
			int newX = getX() + Rnd.get(randDropLim * 2 + 1) - randDropLim;
			int newY = getY() + Rnd.get(randDropLim * 2 + 1) - randDropLim;
			int newZ = Math.max(getZ(), mainDamageDealer.getZ()) + 20; // TODO: temp hack, do somethign nicer when we have geodatas

			// Init the dropped L2ItemInstance and add it in the world as a visible object at the position where mob was last
			ditem = ItemTable.getInstance().createItem("Loot", item.getItemId(), item.getCount(), mainDamageDealer, this);
			ditem.getDropProtection().protect(mainDamageDealer); 
			ditem.dropMe(this, newX, newY, newZ);

			// Add drop to auto destroy item task
			if(!Config.LIST_PROTECTED_ITEMS.contains(item.getItemId()))
			{
				if(Config.AUTODESTROY_ITEM_AFTER > 0 && ditem.getItemType() != L2EtcItemType.HERB || Config.HERB_AUTO_DESTROY_TIME > 0 && ditem.getItemType() == L2EtcItemType.HERB)
				{
					ItemsAutoDestroy.getInstance().addItem(ditem);
				}
			}

			ditem.setProtected(false);

			// If stackable, end loop as entire count is included in 1 instance of item
			if(ditem.isStackable() || !Config.MULTIPLE_ITEM_DROP)
			{
				break;
			}
		}
		return ditem;
	}

	public L2ItemInstance DropItem(L2PcInstance lastAttacker, int itemId, int itemCount)
	{
		return DropItem(lastAttacker, new RewardItem(itemId, itemCount));
	}

	/**
	 * Return the active weapon of this L2Attackable (= null).<BR>
	 * <BR>
	 */
	public L2ItemInstance getActiveWeapon()
	{
		return null;
	}

	/**
	 * Return True if the _aggroList of this L2Attackable is Empty.<BR>
	 * <BR>
	 */
	public boolean noTarget()
	{
		return getAggroListRP().isEmpty();
	}

	/**
	 * Return True if the _aggroList of this L2Attackable contains the L2Character.<BR>
	 * <BR>
	 * 
	 * @param player The L2Character searched in the _aggroList of the L2Attackable
	 */
	public boolean containsTarget(L2Character player)
	{
		return getAggroListRP().containsKey(player);
	}

	/**
	 * Clear the _aggroList of the L2Attackable.<BR>
	 * <BR>
	 */
	public void clearAggroList()
	{
		getAggroList().clear();
	}

	/**
	 * Return True if a Dwarf use Sweep on the L2Attackable and if item can be spoiled.<BR>
	 * <BR>
	 */
	public boolean isSweepActive()
	{
		return _sweepItems != null;
	}

	/**
	 * Return table containing all L2ItemInstance that can be spoiled.<BR>
	 * <BR>
	 */
	public synchronized RewardItem[] takeSweep()
	{
		RewardItem[] sweep = _sweepItems;

		_sweepItems = null;

		return sweep;
	}

	/**
	 * Return table containing all L2ItemInstance that can be harvested.<BR>
	 * <BR>
	 */
	public synchronized RewardItem[] takeHarvest()
	{
		RewardItem[] harvest = _harvestItems;

		_harvestItems = null;

		return harvest;
	}

	/**
	 * Set the over-hit flag on the L2Attackable.<BR>
	 * <BR>
	 * 
	 * @param status The status of the over-hit flag
	 */
	public void overhitEnabled(boolean status)
	{
		_overhit = status;
	}

	/**
	 * Set the over-hit values like the attacker who did the strike and the ammount of damage done by the skill.<BR>
	 * <BR>
	 * 
	 * @param attacker The L2Character who hit on the L2Attackable using the over-hit enabled skill
	 * @param damage The ammount of damage done by the over-hit enabled skill on the L2Attackable
	 */
	public void setOverhitValues(L2Character attacker, double damage)
	{
		// Calculate the over-hit damage
		// Ex: mob had 10 HP left, over-hit skill did 50 damage total, over-hit damage is 40
		double overhitDmg = (getCurrentHp() - damage) * -1;

		if(overhitDmg < 0)
		{
			// we didn't killed the mob with the over-hit strike. (it wasn't really an over-hit strike)
			// let's just clear all the over-hit related values
			overhitEnabled(false);
			_overhitDamage = 0;
			_overhitAttacker = null;

			return;
		}

		overhitEnabled(true);
		_overhitDamage = overhitDmg;
		_overhitAttacker = attacker;
	}

	/**
	 * Return the L2Character who hit on the L2Attackable using an over-hit enabled skill.<BR>
	 * <BR>
	 * 
	 * @return L2Character attacker
	 */
	public L2Character getOverhitAttacker()
	{
		return _overhitAttacker;
	}

	/**
	 * Return the ammount of damage done on the L2Attackable using an over-hit enabled skill.<BR>
	 * <BR>
	 * 
	 * @return double damage
	 */
	public double getOverhitDamage()
	{
		return _overhitDamage;
	}

	/**
	 * Return True if the L2Attackable was hit by an over-hit enabled skill.<BR>
	 * <BR>
	 */
	public boolean isOverhit()
	{
		return _overhit;
	}

	/**
	 * Activate the absorbed soul condition on the L2Attackable.<BR>
	 * <BR>
	 */
	public void absorbSoul()
	{
		_absorbed = true;

	}

	/**
	 * Return True if the L2Attackable had his soul absorbed.<BR>
	 * <BR>
	 */
	public boolean isAbsorbed()
	{
		return _absorbed;
	}

	/**
	 * Adds an attacker that successfully absorbed the soul of this L2Attackable into the _absorbersList.<BR>
	 * <BR>
	 * 
	 * @param attacker - a valid L2PcInstance<br>
	 * <br>
	 * @param condition - an integer indicating the event when mob dies. This should be:<br>
	 *            = 0 - "the crystal scatters";<br>
	 *            = 1 - "the crystal failed to absorb. nothing happens";<br>
	 *            = 2 - "the crystal resonates because you got more than 1 crystal on you";<br>
	 *            = 3 - "the crystal cannot absorb the soul because the mob level is too low";<br>
	 *            = 4 - "the crystal successfuly absorbed the soul";<br>
	 */
	public void addAbsorber(L2PcInstance attacker, int crystalId)
	{
		// This just works for targets like L2MonsterInstance
		if(!(this instanceof L2MonsterInstance))
			return;

		// The attacker must not be null
		if(attacker == null)
			return;

		// This L2Attackable must be of one type in the _absorbingMOBS_levelXX tables.
		// OBS: This is done so to avoid triggering the absorbed conditions for mobs that can't be absorbed.
		if(getAbsorbLevel() == 0)
			return;

		// If we have no _absorbersList initiated, do it
		AbsorberInfo ai = _absorbersList.get(attacker);

		// If the L2Character attacker isn't already in the _absorbersList of this L2Attackable, add it
		if(ai == null)
		{
			ai = new AbsorberInfo(attacker, crystalId, getCurrentHp());
			_absorbersList.put(attacker, ai);
		}
		else
		{
			ai._absorber = attacker;
			ai._crystalId = crystalId;
			ai._absorbedHP = getCurrentHp();
		}

		// Set this L2Attackable as absorbed
		absorbSoul();

		ai = null;
	}

	/**
	 * Calculate the leveling chance of Soul Crystals based on the attacker that killed this L2Attackable
	 * 
	 * @param attacker The player that last killed this L2Attackable $ Rewrite 06.12.06 - Yesod
	 */
	private void levelSoulCrystals(L2Character attacker)
	{
		// Only L2PcInstance can absorb a soul
		if(!(attacker instanceof L2PcInstance) && !(attacker instanceof L2Summon))
		{
			resetAbsorbList();
			return;
		}

		int maxAbsorbLevel = getAbsorbLevel();
		int minAbsorbLevel = 0;

		// If this is not a valid L2Attackable, clears the _absorbersList and just return
		if(maxAbsorbLevel == 0)
		{
			resetAbsorbList();
			return;
		}

		// All boss mobs with maxAbsorbLevel 13 have minAbsorbLevel of 12 else 10
		if(maxAbsorbLevel > 10)
		{
			minAbsorbLevel = maxAbsorbLevel > 12 ? 12 : 10;
		}

		//Init some useful vars
		boolean isSuccess = true;
		boolean doLevelup = true;
		boolean isBossMob = maxAbsorbLevel > 10 ? true : false;

		L2NpcTemplate.AbsorbCrystalType absorbType = getTemplate().absorbType;

		L2PcInstance killer = attacker instanceof L2Summon ? ((L2Summon) attacker).getOwner() : (L2PcInstance) attacker;

		// If this mob is a boss, then skip some checkings
		if(!isBossMob)
		{
			// Fail if this L2Attackable isn't absorbed or there's no one in its _absorbersList
			if(!isAbsorbed() /*|| _absorbersList == null*/)
			{
				resetAbsorbList();
				return;
			}

			// Fail if the killer isn't in the _absorbersList of this L2Attackable and mob is not boss
			AbsorberInfo ai = _absorbersList.get(killer);
			if(ai == null || ai._absorber.getObjectId() != killer.getObjectId())
			{
				isSuccess = false;
			}

			// Check if the soul crystal was used when HP of this L2Attackable wasn't higher than half of it
			if(ai != null && ai._absorbedHP > getMaxHp() / 2.0)
			{
				isSuccess = false;
			}

			if(!isSuccess)
			{
				resetAbsorbList();
				return;
			}

			ai = null;
		}

		// ********
		String[] crystalNFO = null;
		String crystalNME = "";

		int dice = Rnd.get(100);
		int crystalQTY = 0;
		int crystalLVL = 0;
		int crystalOLD = 0;
		int crystalNEW = 0;

		// ********
		// Now we have four choices:
		// 1- The Monster level is too low for the crystal. Nothing happens.
		// 2- Everything is correct, but it failed. Nothing happens. (57.5%)
		// 3- Everything is correct, but it failed. The crystal scatters. A sound event is played. (10%)
		// 4- Everything is correct, the crystal level up. A sound event is played. (32.5%)

		List<L2PcInstance> players = new FastList<L2PcInstance>();

		if(absorbType == L2NpcTemplate.AbsorbCrystalType.FULL_PARTY && killer.isInParty())
		{
			players = killer.getParty().getPartyMembers();
		}
		else if(absorbType == L2NpcTemplate.AbsorbCrystalType.PARTY_ONE_RANDOM && killer.isInParty())
		{
			// This is a naive method for selecting a random member.  It gets any random party member and
			// then checks if the member has a valid crystal.  It does not select the random party member
			// among those who have crystals, only.  However, this might actually be correct (same as retail).
			players.add(killer.getParty().getPartyMembers().get(Rnd.get(killer.getParty().getMemberCount())));
		}
		else
		{
			players.add(killer);
		}

		for(L2PcInstance player : players)
		{
			if(player == null)
			{
				continue;
			}

			crystalQTY = 0;

			L2ItemInstance[] inv = player.getInventory().getItems();
			for(L2ItemInstance item : inv)
			{
				int itemId = item.getItemId();
				for(int id : SoulCrystal.SoulCrystalTable)
				{
					// Find any of the 39 possible crystals.
					if(id == itemId)
					{
						crystalQTY++;
						// Keep count but make sure the player has no more than 1 crystal
						if(crystalQTY > 1)
						{
							isSuccess = false;
							break;
						}

						// Validate if the crystal has already leveled
						if(id != SoulCrystal.RED_NEW_CRYSTAL && id != SoulCrystal.GRN_NEW_CYRSTAL && id != SoulCrystal.BLU_NEW_CRYSTAL)
						{
							try
							{
								if(item.getItem().getName().contains("Grade"))
								{
									// Split the name of the crystal into 'name' & 'level'
									crystalNFO = item.getItem().getName().trim().replace(" Grade ", "-").split("-");
									// Set Level to 13
									crystalLVL = 13;
									// Get Name
									crystalNME = crystalNFO[0].toLowerCase();
								}
								else
								{
									// Split the name of the crystal into 'name' & 'level'
									crystalNFO = item.getItem().getName().trim().replace(" Stage ", "").split("-");
									// Get Level
									crystalLVL = Integer.parseInt(crystalNFO[1].trim());
									// Get Name
									crystalNME = crystalNFO[0].toLowerCase();
								}
								// Allocate current and levelup ids' for higher level crystals
								if(crystalLVL > 9)
								{
									for(int[] element : SoulCrystal.HighSoulConvert)
										// Get the next stage above 10 using array.
										if(id == element[0])
										{
											crystalNEW = element[1];
											break;
										}
								}
								else
								{
									crystalNEW = id + 1;
								}
							}
							catch(NumberFormatException nfe)
							{
								_log.log(Level.WARNING, "An attempt to identify a soul crystal failed, " + "verify the names have not changed in etcitem table.", nfe);

								player.sendMessage("There has been an error handling your soul crystal." + " Please notify your server admin.");

								isSuccess = false;
								break;
							}
							catch(Exception e)
							{
								e.printStackTrace();
								isSuccess = false;
								break;
							}
						}
						else
						{
							crystalNME = item.getItem().getName().toLowerCase().trim();
							crystalNEW = id + 1;
						}

						// Done
						crystalOLD = id;
						break;
					}
				}

				if(!isSuccess)
				{
					break;
				}
			}

			inv = null;

			// If the crystal level is way too high for this mob, say that we can't increase it
			if(crystalLVL < minAbsorbLevel || crystalLVL >= maxAbsorbLevel)
			{
				doLevelup = false;
			}

			// The player doesn't have any crystals with him get to the next player.
			if(crystalQTY < 1 || crystalQTY > 1 || !isSuccess || !doLevelup)
			{
				// Too many crystals in inventory.
				if(crystalQTY > 1)
				{
					player.sendPacket(new SystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_FAILED_RESONATION));
				}
				// The soul crystal stage of the player is way too high
				else if(!doLevelup)
				{
					player.sendPacket(new SystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_REFUSED));
				}

				crystalQTY = 0;

				continue;
			}

			/* TODO: Confirm boss chance for crystal level up and for crystal breaking.
			 * It is known that bosses with FULL_PARTY crystal level ups have 100% success rate, but this is not
			 * the case for the other bosses (one-random or last-hit).
			 * While not confirmed, it is most reasonable that crystals leveled up at bosses will never break.
			 * Also, the chance to level up is guessed as around 70% if not higher.
			 */
			int chanceLevelUp = isBossMob ? 70 : SoulCrystal.LEVEL_CHANCE;

			// If succeeds or it is a full party absorb, level up the crystal.
			if(absorbType == L2NpcTemplate.AbsorbCrystalType.FULL_PARTY && doLevelup || dice <= chanceLevelUp)
			{
				// Give staged crystal
				exchangeCrystal(player, crystalOLD, crystalNEW, false);
			}

			// If true and not a last-hit mob, break the crystal.
			else if(!isBossMob && dice >= 100.0 - SoulCrystal.BREAK_CHANCE)
			{
				// Remove current crystal an give a broken open.
				if(crystalNME.startsWith("red"))
				{
					exchangeCrystal(player, crystalOLD, SoulCrystal.RED_BROKEN_CRYSTAL, true);
				}
				else if(crystalNME.startsWith("gre"))
				{
					exchangeCrystal(player, crystalOLD, SoulCrystal.GRN_BROKEN_CYRSTAL, true);
				}
				else if(crystalNME.startsWith("blu"))
				{
					exchangeCrystal(player, crystalOLD, SoulCrystal.BLU_BROKEN_CRYSTAL, true);
				}

				resetAbsorbList();
			}
			else
			{
				player.sendPacket(new SystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_FAILED));
			}
		}

		killer = null;
		players = null;
		crystalNFO = null;
		crystalNME = null;
	}

	private void exchangeCrystal(L2PcInstance player, int takeid, int giveid, boolean broke)
	{
		L2ItemInstance Item = player.getInventory().destroyItemByItemId("SoulCrystal", takeid, 1, player, this);

		if(Item != null)
		{
			// Prepare inventory update packet
			InventoryUpdate playerIU = new InventoryUpdate();
			playerIU.addRemovedItem(Item);

			// Add new crystal to the killer's inventory
			Item = player.getInventory().addItem("SoulCrystal", giveid, 1, player, this);
			playerIU.addItem(Item);

			// Send a sound event and text message to the player
			if(broke)
			{
				player.sendPacket(new SystemMessage(SystemMessageId.SOUL_CRYSTAL_BROKE));
			}
			else
			{
				player.sendPacket(new SystemMessage(SystemMessageId.SOUL_CRYSTAL_ABSORBING_SUCCEEDED));
			}

			// Send system message
			SystemMessage sms = new SystemMessage(SystemMessageId.EARNED_ITEM);
			sms.addItemName(giveid);
			player.sendPacket(sms);
			sms = null;

			// Send inventory update packet
			player.sendPacket(playerIU);

			playerIU = null;
		}
	}

	private void resetAbsorbList()
	{
		_absorbed = false;
		_absorbersList.clear();
	}

	/**
	 * Calculate the Experience and SP to distribute to attacker (L2PcInstance, L2SummonInstance or L2Party) of the
	 * L2Attackable.<BR>
	 * <BR>
	 * 
	 * @param diff The difference of level between attacker (L2PcInstance, L2SummonInstance or L2Party) and the
	 *            L2Attackable
	 * @param damage The damages given by the attacker (L2PcInstance, L2SummonInstance or L2Party)
	 */
	private int[] calculateExpAndSp(int diff, int damage)
	{
		double xp;
		double sp;

		if(diff < -5)
		{
			diff = -5; // makes possible to use ALT_GAME_EXPONENT configuration
		}

		xp = (double) getExpReward() * damage / getMaxHp();

		if(Config.ALT_GAME_EXPONENT_XP != 0)
		{
			xp *= Math.pow(2., -diff / Config.ALT_GAME_EXPONENT_XP);
		}

		sp = (double) getSpReward() * damage / getMaxHp();

		if(Config.ALT_GAME_EXPONENT_SP != 0)
		{
			sp *= Math.pow(2., -diff / Config.ALT_GAME_EXPONENT_SP);
		}

		if(Config.ALT_GAME_EXPONENT_XP == 0 && Config.ALT_GAME_EXPONENT_SP == 0)
		{
			if(diff > 5) // formula revised May 07
			{
				double pow = Math.pow((double) 5 / 6, diff - 5);
				xp = xp * pow;
				sp = sp * pow;
			}

			if(xp <= 0)
			{
				xp = 0;
				sp = 0;
			}
			else if(sp <= 0)
			{
				sp = 0;
			}
		}

		int[] tmp =
		{
				(int) xp, (int) sp
		};

		return tmp;
	}

	public long calculateOverhitExp(long normalExp)
	{
		// Get the percentage based on the total of extra (over-hit) damage done relative to the total (maximum) ammount of HP on the L2Attackable
		double overhitPercentage = getOverhitDamage() * 100 / getMaxHp();

		// Over-hit damage percentages are limited to 25% max
		if(overhitPercentage > 25)
		{
			overhitPercentage = 25;
		}

		// Get the overhit exp bonus according to the above over-hit damage percentage
		// (1/1 basis - 13% of over-hit damage, 13% of extra exp is given, and so on...)
		double overhitExp = overhitPercentage / 100 * normalExp;

		// Return the rounded ammount of exp points to be added to the player's normal exp reward
		long bonusOverhit = Math.round(overhitExp);

		return bonusOverhit;
	}

	/**
	 * Return True.<BR>
	 * <BR>
	 */
	@Override
	public boolean isAttackable()
	{
		return true;
	}

	@Override
	public void onSpawn()
	{
		super.onSpawn();
		// Clear mob spoil,seed
		setSpoil(false);
		// Clear all aggro char from list
		clearAggroList();
		// Clear Harvester Rewrard List
		_harvestItems = null;
		// Clear mod Seeded stat
		setSeeded(false);

		_sweepItems = null;
		resetAbsorbList();

		setWalking();

		// check the region where this mob is, do not activate the AI if region is inactive.
		if(!isInActiveRegion())
		{
			if(this instanceof L2SiegeGuardInstance)
			{
				((L2SiegeGuardAI) getAI()).stopAITask();
			}
			else if(this instanceof L2FortSiegeGuardInstance)
			{
				((L2FortSiegeGuardAI) getAI()).stopAITask();
			}
			else
			{
				((L2AttackableAI) getAI()).stopAITask();
			}
		}
	}

	/**
	 * Sets state of the mob to seeded. Paramets needed to be set before.
	 */
	public void setSeeded()
	{
		if(_seedType != 0 && _seeder != null)
		{
			setSeeded(_seedType, _seeder.getLevel());
		}
	}

	/**
	 * Sets the seed parametrs, but not the seed state
	 * 
	 * @param id - id of the seed
	 * @param seeder - player who is sowind the seed
	 */
	public void setSeeded(int id, L2PcInstance seeder)
	{
		if(!_seeded)
		{
			_seedType = id;
			_seeder = seeder;
		}
	}

	public void setSeeded(int id, int seederLvl)
	{
		_seeded = true;
		_seedType = id;
		int count = 1;

		Map<Integer, L2Skill> skills = getTemplate().getSkills();

		if(skills != null)
		{
			for(int skillId : skills.keySet())
			{
				switch(skillId)
				{
					case 4303: //Strong type x2
						count *= 2;
						break;
					case 4304: //Strong type x3
						count *= 3;
						break;
					case 4305: //Strong type x4
						count *= 4;
						break;
					case 4306: //Strong type x5
						count *= 5;
						break;
					case 4307: //Strong type x6
						count *= 6;
						break;
					case 4308: //Strong type x7
						count *= 7;
						break;
					case 4309: //Strong type x8
						count *= 8;
						break;
					case 4310: //Strong type x9
						count *= 9;
						break;
				}
			}
		}

		int diff = getLevel() - (L2Manor.getInstance().getSeedLevel(_seedType) - 5);

		// hi-lvl mobs bonus
		if(diff > 0)
		{
			count += diff;
		}

		FastList<RewardItem> harvested = new FastList<RewardItem>();

		harvested.add(new RewardItem(L2Manor.getInstance().getCropType(_seedType), count * Config.RATE_DROP_MANOR));

		_harvestItems = harvested.toArray(new RewardItem[harvested.size()]);

		skills = null;
		harvested = null;
	}

	public void setSeeded(boolean seeded)
	{
		_seeded = seeded;
	}

	public L2PcInstance getSeeder()
	{
		return _seeder;
	}

	public int getSeedType()
	{
		return _seedType;
	}

	public boolean isSeeded()
	{
		return _seeded;
	}

	private int getAbsorbLevel()
	{
		return getTemplate().absorbLevel;
	}

	/**
	 * Check if the server allows Random Animation.<BR>
	 * <BR>
	 */
	// This is located here because L2Monster and L2FriendlyMob both extend this class. The other non-pc instances extend either L2NpcInstance or L2MonsterInstance.
	@Override
	public boolean hasRandomAnimation()
	{
		return Config.MAX_MONSTER_ANIMATION > 0 && !(this instanceof L2GrandBossInstance);
	}

	@Override
	public boolean isMob()
	{
		return true; // This means we use MAX_MONSTER_ANIMATION instead of MAX_NPC_ANIMATION
	}

	protected void setCommandChannelTimer(CommandChannelTimer commandChannelTimer)
	{
		_commandChannelTimer = commandChannelTimer;
	}

	public CommandChannelTimer getCommandChannelTimer()
	{
		return _commandChannelTimer;
	}

	public L2CommandChannel getFirstCommandChannelAttacked()
	{
		return _firstCommandChannelAttacked;
	}

	public void setFirstCommandChannelAttacked(L2CommandChannel firstCommandChannelAttacked)
	{
		_firstCommandChannelAttacked = firstCommandChannelAttacked;
	}

	public long getCommandChannelLastAttack()
	{
		return _commandChannelLastAttack;
	}

	public void setCommandChannelLastAttack(long channelLastAttack)
	{
		_commandChannelLastAttack = channelLastAttack;
	}
	
	private static class CommandChannelTimer implements Runnable
	{
		private L2Attackable _monster;
		
		public CommandChannelTimer(L2Attackable monster)
		{
			_monster = monster;
		}
		
		/**
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run()
		{
			if ((System.currentTimeMillis() - _monster.getCommandChannelLastAttack()) > 900000)
			{
				_monster.setCommandChannelTimer(null);
				_monster.setFirstCommandChannelAttacked(null);
				_monster.setCommandChannelLastAttack(0);
			}
			else
				ThreadPoolManager.getInstance().scheduleGeneral(this, 10000); // 10sec
		}
	}
}
