Package l2p.gameserver.model.instances

Source Code of l2p.gameserver.model.instances.L2MonsterInstance

package l2p.gameserver.model.instances;

import javolution.util.FastMap;
import l2p.Config;
import l2p.common.ThreadPoolManager;
import l2p.extensions.multilang.CustomMessage;
import l2p.gameserver.ai.CtrlEvent;
import l2p.gameserver.ai.CtrlIntention;
import l2p.gameserver.cache.Msg;
import l2p.gameserver.instancemanager.CursedWeaponsManager;
import l2p.gameserver.model.L2Character;
import l2p.gameserver.model.L2Drop;
import l2p.gameserver.model.L2DropData;
import l2p.gameserver.model.L2DropGroup;
import l2p.gameserver.model.L2Manor;
import l2p.gameserver.model.L2ObjectTasks.SoulConsumeTask;
import l2p.gameserver.model.L2ObjectsStorage;
import l2p.gameserver.model.L2Party;
import l2p.gameserver.model.L2Playable;
import l2p.gameserver.model.L2Player;
import l2p.gameserver.model.base.Experience;
import l2p.gameserver.model.base.ItemToDrop;
import l2p.gameserver.model.items.L2ItemInstance;
import l2p.gameserver.model.items.L2ItemInstance.ItemLocation;
import l2p.gameserver.model.quest.Quest;
import l2p.gameserver.model.quest.QuestEventType;
import l2p.gameserver.model.quest.QuestState;
import l2p.gameserver.serverpackets.SocialAction;
import l2p.gameserver.serverpackets.SpawnEmitter;
import l2p.gameserver.serverpackets.SystemMessage;
import l2p.gameserver.skills.Stats;
import l2p.gameserver.tables.ItemTable;
import l2p.gameserver.tables.SkillTable;
import l2p.gameserver.templates.L2Item;
import l2p.gameserver.templates.L2NpcTemplate;
import l2p.util.GArray;
import l2p.util.Location;
import l2p.util.MinionList;
import l2p.util.Rnd;
import l2p.util.Util;

import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;

/**
* This class manages all Monsters.
* <p/>
* L2MonsterInstance :<BR><BR>
* <li>L2MinionInstance</li>
* <li>L2RaidBossInstance </li>
*/
public class L2MonsterInstance extends L2NpcInstance
{
  protected static final class RewardInfo
  {
    protected L2Character _attacker;
    protected int _dmg = 0;

    public RewardInfo(final L2Character attacker, final int dmg)
    {
      _attacker = attacker;
      _dmg = dmg;
    }

    public void addDamage(int dmg)
    {
      if(dmg < 0)
      {
        dmg = 0;
      }
      _dmg += dmg;
    }

    @Override
    public int hashCode()
    {
      return _attacker.getObjectId();
    }
  }
  private boolean _dead = false, _dying = false;
  private final ReentrantLock dieLock = new ReentrantLock(), dyingLock = new ReentrantLock(),
  sweepLock = new ReentrantLock(), harvestLock = new ReentrantLock();
  /**
   * Stores the extra (over-hit) damage done to the L2NpcInstance when the attacker uses an over-hit enabled skill
   */
  private double _overhitDamage;
  /**
   * Stores the attacker who used the over-hit enabled skill on the L2NpcInstance
   */
  protected MinionList _minionList;
  private ScheduledFuture<?> minionMaintainTask;
  private static final int MONSTER_MAINTENANCE_INTERVAL = 1000;
  /**
   * Максимальный уровень мобов
   */
  private static final int MONSTER_MAX_LEVEL = 200;
  private GArray<L2ItemInstance> _inventory;
  /**
   * crops
   */
  private L2ItemInstance _harvestItem;
  private L2Item _seeded;
  private long seederStoreId, spoilerStoreId, overhitAttackerStoreId;
  /**
   * The table containing all players objectID that successfully absorbed the soul of this L2NpcInstance
   */
  private GArray<Integer> _absorbersList;
  /**
   * Table containing all Items that a Dwarf can Sweep on this L2NpcInstance
   */
  private L2ItemInstance[] _sweepItems;
  /**
   * Static tables containing all mobIDs that can have their souls absorbed, divided by max soul crystal level
   */
  private static final int[] _absorbingMOBS_level4 =
  {
    20583, 20584, 20585, 20586, 20587, 20588, 20636, 20637, 20638,
    20639, 20640, 20641, 20642, 20643, 20644, 20645
  };
  private static final int[] _absorbingMOBS_level8 =
  {
    20646, 20647, 20648, 20649, 20650, 21006, 21007, 21008
  };
  private static final int[] _absorbingMOBS_level10 =
  {
    20627, 20628, 20629, 20674, 20761, 20762, 20821, 20823, 20826,
    20827, 20828, 20829, 20830, 20831, 20858, 20859, 20860, 21009, 21010, 21062, 21063, 21068,
    21070
  };
  /*
  60 Zaken 11,12 100% (nighttime raid)
  70 Beast Lord Behemoth 11,12 random
  70 Roaring Skylancer 11,12 random
  70 Meanas Anor 11,12 random
  71 Eilhalder von Hellmann 11,12 random
  74 Krokian Padisha Sobekk 11,12 random
  74 Antharas Priest Cloe 11,12 random
  75 Baium 11,12 100%
  80 Lilith 11,12 100%
  80 Anakim 11,12 100%
  80 Tyrannosaurus 11,12 random
  80 Sailren 11,12 random
  78 Anakazel 13 random
  80 Ember 13 random
  83 Baylor 14 random Cursed
  81 Aenkinel 13,14 extremely random
  82 Aenkinel 13,14 extremely random
  84 Aenkinel 13,14 extremely random
  83 Hekaton Prime 13,14 random
  84 Stakato Queen Shyeed 13,14 random
  79 Antharas 13-16 100%
  80 Scarlet van Halisha 13-16 100%
  85 Valakas 13-16 100%
  82 Tiat 15,16 very random
  82 Ekimus 15,16 very random
  83 Beleth 15,16 very random
   */
  private static final int[] _absorbingMOBS_level12 =
  {
    29022, // Zaken
    25163, // Roaring Skylancer
    25269, // Beast Lord Behemoth
    25453, // Meanas Anor
    25328, // Eilhalder von Hellmann
    25109, // Antharas Priest Cloe
    25202, // Krokian Padisha Sobekk
    29020, // Baium
    25283, // Lilith
    25286, // Anakim
    22215, // Tyrannosaurus
    22216, // Tyrannosaurus
    22217, // Tyrannosaurus
    29065, // Sailren
  };
  private static final int[] _absorbingMOBS_level13 =
  {
    25338, // Anakazel 78 lvl
    29019, // Antharas
    29066, // Antharas
    29067, // Antharas
    29068, // Antharas
    25319, // Ember
    29028, // Valakas
    29046, // Scarlet van Halisha
    29047, // Scarlet van Halisha
    25693, // Aenkinel lvl 81
    25694, // Aenkinel lvl 82
    25695, // Aenkinel lvl 84
    25687, // Hekaton Prime lvl 83
    25671, // Queen Shyeed lvl 84
  };
  private static final int[] _absorbingMOBS_level14 =
  {
    29019, // Antharas
    29066, // Antharas
    29067, // Antharas
    29068, // Antharas
    29028, // Valakas
    29046, // Scarlet van Halisha
    29047, // Scarlet van Halisha
    29118, // Beleth
    29119, // Beleth
    29099, // Baylor
    29103, // Baylor
    25693, // Aenkinel lvl 81
    25694, // Aenkinel lvl 82
    25695, // Aenkinel lvl 84
    25687, // Hekaton Prime lvl 83
    25671, // Queen Shyeed lvl 84
  };
  private static final int[] _absorbingMOBS_level15 =
  {
    29019, // Antharas
    29066, // Antharas
    29067, // Antharas
    29068, // Antharas
    29028, // Valakas
    29046, // Scarlet van Halisha
    29047, // Scarlet van Halisha
    29118, // Beleth
    29119, // Beleth
    29163, // Tiat
    29175, // Tiat
    32525, // Tiat
    29150, // Ekimus
  };
  private static final int[] _absorbingMOBS_level16 =
  {
    29019, // Antharas
    29066, // Antharas
    29067, // Antharas
    29068, // Antharas
    29028, // Valakas
    29046, // Scarlet van Halisha
    29047, // Scarlet van Halisha
    29118, // Beleth
    29119, // Beleth
    29163, // Tiat
    29175, // Tiat
    32525, // Tiat
    29150, // Ekimus
  };
  private static final int[] _randomLeveling =
  {
    25163, // Roaring Skylancer
    25269, // Beast Lord Behemoth
    25453, // Meanas Anor
    25328, // Eilhalder von Hellmann
    25109, // Antharas Priest Cloe
    25202, // Krokian Padisha Sobekk
    22215, // Tyrannosaurus
    22216, // Tyrannosaurus
    22217, // Tyrannosaurus
    29065, // Sailren
    25338, // Anakazel 78 lvl
    25319, // Ember
    29099, // Baylor
    29103, // Baylor
    25693, // Aenkinel lvl 81
    25694, // Aenkinel lvl 82
    25695, // Aenkinel lvl 84
    25687, // Hekaton Prime lvl 83
    25671, // Queen Shyeed lvl 84
    29118, // Beleth
    29119, // Beleth
    29163, // Tiat
    29175, // Tiat
    32525, // Tiat
    29150, // Ekimus
  };
  /**
   * Soul Crystal Basic Informations
   */
  // First ID of each soul crystal
  private static final int[] _REDCRYSTALS =
  {
    4629, 4630, 4631, 4632, 4633, 4634, 4635, 4636, 4637, 4638, 4639, 5577,
    5580, 5908, 9570, 10480, 13071
  };
  private static final int[] _GREENCRYSTALS =
  {
    4640, 4641, 4642, 4643, 4644, 4645, 4646, 4647, 4648, 4649, 4650, 5578,
    5581, 5911, 9572, 10482, 13073
  };
  private static final int[] _BLUECRYSTALS =
  {
    4651, 4652, 4653, 4654, 4655, 4656, 4657, 4658, 4659, 4660, 4661, 5579,
    5582, 5914, 9571, 10481, 13072
  };
  private static final short _REDCURSEDCRYSTAL_LVL14 = 10160;
  private static final short _BLUECURSEDCRYSTAL_LVL14 = 10161;
  private static final short _GREENCURSEDCRYSTAL_LVL14 = 10162;
  // Max number of levels a soul crystal may reach
  private static final short _MAX_CRYSTALS_LEVEL = 16;
  /**
   * End of Soul Crystal Basic Informations
   */
  // For ALT_GAME_MATHERIALSDROP
  protected static final L2DropData[] _matdrop = new L2DropData[]
  {
    //                                           Item              Price Chance
    new L2DropData(1864, 1, 1, 50000, 1), // Stem              100   5%
    new L2DropData(1865, 1, 1, 25000, 1), // Varnish           200   2.5%
    new L2DropData(1866, 1, 1, 16666, 1), // Suede             300   1.6666%
    new L2DropData(1867, 1, 1, 33333, 1), // Animal Skin       150   3.3333%
    new L2DropData(1868, 1, 1, 50000, 1), // Thread            100   5%
    new L2DropData(1869, 1, 1, 25000, 1), // Iron Ore          200   2.5%
    new L2DropData(1870, 1, 1, 25000, 1), // Coal              200   2.5%
    new L2DropData(1871, 1, 1, 25000, 1), // Charcoal          200   2.5%
    new L2DropData(1872, 1, 1, 50000, 1), // Animal Bone       150   5%
    new L2DropData(1873, 1, 1, 10000, 1), // Silver Nugget     500   1%
    new L2DropData(1874, 1, 1, 1666, 20), // Oriharukon Ore    3000  0.1666%
    new L2DropData(1875, 1, 1, 1666, 20), // Stone of Purity   3000  0.1666%
    new L2DropData(1876, 1, 1, 5000, 20), // Mithril Ore       1000  0.5%
    new L2DropData(1877, 1, 1, 1000, 20), // Adamantite Nugget 5000  0.1%
    new L2DropData(4039, 1, 1, 833, 40), //  Mold Glue         6000  0.0833%
    new L2DropData(4040, 1, 1, 500, 40), //  Mold Lubricant    10000 0.05%
    new L2DropData(4041, 1, 1, 217, 40), //  Mold Hardener     23000 0.0217%
    new L2DropData(4042, 1, 1, 417, 40), //  Enria             12000 0.0417%
    new L2DropData(4043, 1, 1, 833, 40), //  Asofe             6000  0.0833%
    new L2DropData(4044, 1, 1, 833, 40) //   Thons             6000  0.0833%
  };
  protected static final GArray<L2DropGroup> _herbs = new GArray<L2DropGroup>(3);

  static
  {
    L2DropGroup d = new L2DropGroup(0);
    d.addDropItem(new L2DropData(8600, 1, 1, 120000, 1)); // of Life                    15%
    d.addDropItem(new L2DropData(8603, 1, 1, 120000, 1)); // of Mana                    15%
    d.addDropItem(new L2DropData(8601, 1, 1, 40000, 1)); //  Greater of Life            5%
    d.addDropItem(new L2DropData(8604, 1, 1, 40000, 1)); //  Greater of Mana            5%
    d.addDropItem(new L2DropData(8602, 1, 1, 12000, 1)); //  Superior of Life           1.6%
    d.addDropItem(new L2DropData(8605, 1, 1, 12000, 1)); //  Superior of Mana           1.6%
    d.addDropItem(new L2DropData(8614, 1, 1, 3000, 1)); //   of Recovery                0.3%
    _herbs.add(d);
    d = new L2DropGroup(0);
    d.addDropItem(new L2DropData(8611, 1, 1, 50000, 1)); //  of Speed                   5%
    d.addDropItem(new L2DropData(8606, 1, 1, 50000, 1)); //  of Power                   5%
    d.addDropItem(new L2DropData(8608, 1, 1, 50000, 1)); //  of Atk. Spd.               5%
    d.addDropItem(new L2DropData(8610, 1, 1, 50000, 1)); //  of Critical Attack         5%
    d.addDropItem(new L2DropData(10656, 1, 1, 50000, 1)); // of Critical Attack - Power 5%
    d.addDropItem(new L2DropData(10655, 1, 1, 50000, 1)); // of Life Force Absorption   5%
    d.addDropItem(new L2DropData(8607, 1, 1, 50000, 1)); //  of Magic                   5%
    d.addDropItem(new L2DropData(8609, 1, 1, 50000, 1)); //  of Casting Speed           5%
    d.addDropItem(new L2DropData(8612, 1, 1, 10000, 1)); //  of Warrior                 1%
    d.addDropItem(new L2DropData(8613, 1, 1, 10000, 1)); //  of Mystic                  1%
    _herbs.add(d);
    d = new L2DropGroup(0);
    d.addDropItem(new L2DropData(10657, 1, 1, 3000, 1)); //  of Doubt                   0.3%
    d.addDropItem(new L2DropData(13028, 1, 1, 2000, 1)); //  of Vitality                0.2%
    _herbs.add(d);
  }
  protected static final L2DropData[] _lifestones = new L2DropData[]
  {
    //
    new L2DropData(8723, 1, 1, 200, 44, 46), // Life Stone: level 46
    new L2DropData(8724, 1, 1, 200, 47, 49), // Life Stone: level 49
    new L2DropData(8725, 1, 1, 200, 50, 52), // Life Stone: level 52
    new L2DropData(8726, 1, 1, 200, 53, 55), // Life Stone: level 55
    new L2DropData(8727, 1, 1, 200, 56, 58), // Life Stone: level 58
    new L2DropData(8728, 1, 1, 200, 59, 61), // Life Stone: level 61
    new L2DropData(8729, 1, 1, 200, 62, 66), // Life Stone: level 64
    new L2DropData(8730, 1, 1, 200, 67, 72), // Life Stone: level 67
    new L2DropData(8731, 1, 1, 200, 73, 75), // Life Stone: level 70
    new L2DropData(8732, 1, 1, 200, 76, 79), // Life Stone: level 76
    new L2DropData(9573, 1, 1, 150, 80, 81), // Life Stone: level 80
    new L2DropData(10483, 1, 1, 120, 82, 83), // Life Stone: level 82
    new L2DropData(14166, 1, 1, 100, 84, MONSTER_MAX_LEVEL), // Life Stone: level 84
    new L2DropData(8733, 1, 1, 100, 44, 46), // Mid-Grade Life Stone: level 46
    new L2DropData(8734, 1, 1, 100, 47, 49), // Mid-Grade Life Stone: level 49
    new L2DropData(8735, 1, 1, 100, 50, 52), // Mid-Grade Life Stone: level 52
    new L2DropData(8736, 1, 1, 100, 53, 55), // Mid-Grade Life Stone: level 55
    new L2DropData(8737, 1, 1, 100, 56, 58), // Mid-Grade Life Stone: level 58
    new L2DropData(8738, 1, 1, 100, 59, 61), // Mid-Grade Life Stone: level 61
    new L2DropData(8739, 1, 1, 100, 62, 66), // Mid-Grade Life Stone: level 64
    new L2DropData(8740, 1, 1, 100, 67, 72), // Mid-Grade Life Stone: level 67
    new L2DropData(8741, 1, 1, 100, 73, 75), // Mid-Grade Life Stone: level 70
    new L2DropData(8742, 1, 1, 100, 76, 79), // Mid-Grade Life Stone: level 76
    new L2DropData(9574, 1, 1, 80, 80, 81), // Mid-Grade Life Stone: level 80
    new L2DropData(10484, 1, 1, 60, 82, 83), // Mid-Grade Life Stone: level 82
    new L2DropData(14167, 1, 1, 40, 84, MONSTER_MAX_LEVEL), // Mid-Grade Life Stone: level 84
    new L2DropData(8743, 1, 1, 30, 44, 46), // High-Grade Life Stone: level 46
    new L2DropData(8744, 1, 1, 30, 47, 49), // High-Grade Life Stone: level 49
    new L2DropData(8745, 1, 1, 30, 50, 52), // High-Grade Life Stone: level 52
    new L2DropData(8746, 1, 1, 30, 53, 55), // High-Grade Life Stone: level 55
    new L2DropData(8747, 1, 1, 30, 56, 58), // High-Grade Life Stone: level 58
    new L2DropData(8748, 1, 1, 30, 59, 61), // High-Grade Life Stone: level 61
    new L2DropData(8749, 1, 1, 30, 62, 66), // High-Grade Life Stone: level 64
    new L2DropData(8750, 1, 1, 30, 67, 72), // High-Grade Life Stone: level 67
    new L2DropData(8751, 1, 1, 30, 73, 75), // High-Grade Life Stone: level 70
    new L2DropData(8752, 1, 1, 30, 76, 79), // High-Grade Life Stone: level 76
    new L2DropData(9575, 1, 1, 25, 80, 81), // High-Grade Life Stone: level 80
    new L2DropData(10485, 1, 1, 20, 82, 83), // High-Grade Life Stone: level 82
    new L2DropData(14168, 1, 1, 30, 84, MONSTER_MAX_LEVEL), // High-Grade Life Stone: level 84
  };
  protected static final L2DropData[] _toplifestones = new L2DropData[]
  {
    //
    new L2DropData(8753, 1, 1, 100000, 44, 46), // Top-Grade Life Stone: level 46
    new L2DropData(8754, 1, 1, 100000, 47, 49), // Top-Grade Life Stone: level 49
    new L2DropData(8755, 1, 1, 100000, 50, 52), // Top-Grade Life Stone: level 52
    new L2DropData(8756, 1, 1, 100000, 53, 55), // Top-Grade Life Stone: level 55
    new L2DropData(8757, 1, 1, 100000, 56, 58), // Top-Grade Life Stone: level 58
    new L2DropData(8758, 1, 1, 100000, 59, 61), // Top-Grade Life Stone: level 61
    new L2DropData(8759, 1, 1, 100000, 62, 66), // Top-Grade Life Stone: level 64
    new L2DropData(8760, 1, 1, 100000, 67, 72), // Top-Grade Life Stone: level 67
    new L2DropData(8761, 1, 1, 100000, 73, 75), // Top-Grade Life Stone: level 70
    new L2DropData(8762, 1, 1, 100000, 76, 79), // Top-Grade Life Stone: level 76
    new L2DropData(9576, 1, 1, 85000, 80, 81), // Top-Grade Life Stone: level 80
    new L2DropData(10486, 1, 1, 65000, 82, 83), // Top-Grade Life Stone: level 82
    new L2DropData(14169, 1, 1, 50000, 84, MONSTER_MAX_LEVEL), // Top-Grade Life Stone: level 84
  };
  protected static final L2DropData[] _raiditems = new L2DropData[]
  {
    //
    new L2DropData(9814, 1, 2, 300000, 40, 74), // Memento Mori
    new L2DropData(9815, 1, 2, 300000, 40, 70), // Dragon Heart
    new L2DropData(9816, 1, 2, 300000, 40, 74), // Earth Egg
    new L2DropData(9817, 1, 2, 300000, 40, 74), // Nonliving Nucleus
    new L2DropData(9818, 1, 2, 300000, 40, 70), // Angelic Essence
    new L2DropData(8176, 1, 2, 300000, 40, 74) //  Destruction Tombstone
  };

  /**
   * Constructor<?> of L2MonsterInstance (use L2Character and L2NpcInstance constructor).<BR><BR>
   * <p/>
   * <B><U> Actions</U> :</B><BR><BR>
   * <li>Call the L2Character constructor to set the _template of the L2MonsterInstance (copy skills from template to object and link _calculators to NPC_STD_CALCULATOR) </li>
   * <li>Set the name of the L2MonsterInstance</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 template to apply to the NPC
   */
  public L2MonsterInstance(int objectId, L2NpcTemplate template)
  {
    super(objectId, template);
  }

  @Override
  public boolean isMovementDisabled()
  {
    // Невозможность ходить для этих мобов
    return getNpcId() == 18344 || getNpcId() == 18345 || super.isMovementDisabled();
  }

  @Override
  public boolean isLethalImmune()
  {
    return _isChampion > 0 || getNpcId() == 22215 || getNpcId() == 22216 || getNpcId() == 22217 || super.isLethalImmune();
  }

  @Override
  public boolean isFearImmune()
  {
    return _isChampion > 0 || super.isFearImmune();
  }

  @Override
  public boolean isParalyzeImmune()
  {
    return _isChampion > 0 || super.isParalyzeImmune();
  }

  /**
   * Return True if the attacker is not another L2MonsterInstance.<BR><BR>
   */
  @Override
  public boolean isAutoAttackable(L2Character attacker)
  {
    return !attacker.isMonster();
  }
  private int _isChampion;

  public int getChampion()
  {
    return _isChampion;
  }

  public void setChampion(int level)
  {
    if(level == 0)
    {
      removeSkillById(4407);
      _isChampion = 0;
    }
    else
    {
      addSkill(SkillTable.getInstance().getInfo(4407, level));
      _isChampion = level;
      setCurrentHp(getMaxHp(), false);
    }
  }

  public boolean canChampion()
  {
    return getTemplate().revardExp > 0;
  }

  @Override
  public int getTeam()
  {
    return getChampion();
  }

  /**
   * Очищает флаги состояний смерти. Дает или очищает статус чемпиона. Восстанавливает HP. Спавнит миньонов. Отключает аггр на 10 секунд.
   */
  @Override
  public void onSpawn()
  {
    _dead = false;
    _dying = false;
    overhitAttackerStoreId = 0;
    int level = getLevel();
    setChampion(0);
    if(level >= Config.ALT_CHAMPION_MIN_LEVEL && level <= Config.ALT_CHAMPION_MAX_LEVEL)
    {
      if(getReflection().canChampions() && canChampion())
      {
        double random = Rnd.nextDouble();
        if(Config.ALT_CHAMPION_CHANCE2 / 100. >= random)
        {
          setChampion(2);
        }
        else if((Config.ALT_CHAMPION_CHANCE1 + Config.ALT_CHAMPION_CHANCE2) / 100. >= random)
        {
          setChampion(1);
        }
      }
    }
    setCurrentHpMp(getMaxHp(), getMaxMp(), true);
    super.onSpawn();
    spawnMinions();
    getAI().setGlobalAggro(System.currentTimeMillis() + 10000);
    // Clear mob spoil, absorbs, seed
    setSpoiled(false, null);
    _sweepItems = null;
    _absorbersList = null;
    _seeded = null;
    seederStoreId = 0;
    spoilerStoreId = 0;
  }

  protected int getMaintenanceInterval()
  {
    return MONSTER_MAINTENANCE_INTERVAL;
  }

  public MinionList getMinionList()
  {
    return _minionList;
  }

  public void setNewMinionList()
  {
    _minionList = new MinionList(this);
  }

  public class MinionMaintainTask implements Runnable
  {
    @Override
    public void run()
    {
      if(L2MonsterInstance.this == null || L2MonsterInstance.this.isDead())
      {
        return;
      }
      try
      {
        if(L2MonsterInstance.this._minionList == null)
        {
          L2MonsterInstance.this.setNewMinionList();
        }
        L2MonsterInstance.this._minionList.maintainMinions();
      }
      catch(Throwable e)
      {
        e.printStackTrace();
      }
    }
  }

  public void spawnMinions()
  {
    if(getTemplate().getMinionData().size() > 0)
    {
      if(minionMaintainTask != null)
      {
        minionMaintainTask.cancel(true);
        minionMaintainTask = null;
      }
      minionMaintainTask = ThreadPoolManager.getInstance().scheduleAi(new MinionMaintainTask(), getMaintenanceInterval(), false);
    }
  }

  public Location getMinionPosition()
  {
    return Location.getAroundPosition(this, this, 100, 150, 10);
  }

  @Override
  public void callMinionsToAssist(L2Character attacker)
  {
    if(_minionList != null && _minionList.hasMinions())
    {
      for(L2MinionInstance minion : _minionList.getSpawnedMinions())
      {
        if(minion != null && minion.getAI().getIntention() != CtrlIntention.AI_INTENTION_ATTACK && !minion.isDead())
        {
          minion.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, Rnd.get(1, 100));
        }
      }
    }
  }

  public void setDead(boolean dead)
  {
    _dead = dead;
  }

  public void removeMinions()
  {
    if(minionMaintainTask != null)
    {
      minionMaintainTask.cancel(true);
      minionMaintainTask = null;
    }
    if(_minionList != null)
    {
      _minionList.maintainLonelyMinions();
    }
    _minionList = null;
  }

  public int getTotalSpawnedMinionsInstances()
  {
    return _minionList == null ? 0 : _minionList.countSpawnedMinions();
  }

  public void notifyMinionDied(L2MinionInstance minion)
  {
    if(_minionList != null)
    {
      _minionList.removeSpawnedMinion(minion);
    }
  }

  @Override
  public boolean hasMinions()
  {
    return _minionList != null && _minionList.hasMinions();
  }

  @Override
  public void setReflection(long i)
  {
    super.setReflection(i);
    if(hasMinions())
    {
      for(L2MinionInstance m : _minionList.getSpawnedMinions())
      {
        m.setReflection(i);
      }
    }
  }

  @Override
  public void deleteMe()
  {
    removeMinions();
    if(_inventory != null)
    {
      synchronized(_inventory)
      {
        for(L2ItemInstance item : _inventory)
        {
          getTemplate().giveItem(item, false);
        }
        _inventory = null;
      }
    }
    super.deleteMe();
  }

  @Override
  public void doDie(final L2Character killer)
  {
    if(minionMaintainTask != null)
    {
      minionMaintainTask.cancel(true);
      minionMaintainTask = null;
    }
    if(_dead)
    {
      return;
    }
    dieLock.lock();
    try
    {
      if(_dead)
      {
        return;
      }
      _dieTime = System.currentTimeMillis();
      _dead = true;
      if(this instanceof L2ChestInstance && !((L2ChestInstance) this).isFake())
      {
        super.doDie(killer);
        return;
      }
      try
      {
        dyingLock.lock();
        _dying = true;
        calculateRewards(killer);
      }
      catch(final Exception e)
      {
        e.printStackTrace();
      }
      finally
      {
        _dying = false;
        dyingLock.unlock();
      }
    }
    finally
    {
      dieLock.unlock();
    }
    super.doDie(killer);
  }

  public void calculateRewards(L2Character lastAttacker)
  {
    HashMap<L2Playable, AggroInfo> aggroList = getAggroMap();
    L2Character topDamager = getTopDamager(aggroList.values());
    if(lastAttacker == null && topDamager != null)
    {
      lastAttacker = topDamager;
    }
    if(lastAttacker == null || aggroList.isEmpty())
    {
      return;
    }
    L2Player killer = lastAttacker.getPlayer();
    if(killer == null)
    {
      return;
    }
    if(topDamager == null)
    {
      topDamager = lastAttacker;
    }
    // Notify the Quest Engine of the L2NpcInstance death if necessary
    try
    {
      if(Config.KILL_COUNTER)
      {
        killer.incrementKillsCounter(getNpcId());
      }
      getTemplate().killscount++;
      if(getTemplate().hasQuestEvents())
      {
        GArray<L2Player> players = null; // массив с игроками, которые могут быть заинтересованы в квестах
        if(isRaid() && Config.ALT_NO_LASTHIT) // Для альта на ластхит берем всех игроков вокруг
        {
          players = new GArray<L2Player>();
          for(L2Playable pl : aggroList.keySet())
          {
            if(pl.isPlayer() && !pl.isDead() && pl.getReflectionId() == getReflectionId() && (pl.isInRange(this, Config.ALT_PARTY_DISTRIBUTION_RANGE) || pl.isInRange(killer, Config.ALT_PARTY_DISTRIBUTION_RANGE)) && Math.abs(pl.getZ() - getZ()) < 400)
            {
              players.add((L2Player) pl);
            }
          }
        }
        else if(killer.getParty() != null) // если пати то собираем всех кто подходит
        {
          players = new GArray<L2Player>(killer.getParty().getMemberCount());
          for(L2Player pl : killer.getParty().getPartyMembers())
          {
            if(!pl.isDead() && pl.getReflectionId() == getReflectionId() && (pl.isInRange(this, Config.ALT_PARTY_DISTRIBUTION_RANGE) || pl.isInRange(killer, Config.ALT_PARTY_DISTRIBUTION_RANGE)) && Math.abs(pl.getZ() - getZ()) < 400)
            {
              players.add(pl);
            }
          }
        }
        if(getTemplate().getEventQuests(QuestEventType.MOBKILLED) != null)
        {
          for(Quest quest : getTemplate().getEventQuests(QuestEventType.MOBKILLED))
          {
            L2Player toReward = killer;
            if(quest.getParty() != Quest.PARTY_NONE && players != null)
            {
              if(isRaid() || quest.getParty() == Quest.PARTY_ALL) // если цель рейд или квест для всей пати награждаем всех участников
              {
                for(L2Player pl : players)
                {
                  QuestState qs = pl.getQuestState(quest.getName());
                  if(qs != null && !qs.isCompleted())
                  {
                    quest.notifyKill(this, qs);
                  }
                }
                toReward = null;
              }
              else
              { // иначе выбираем одного
                GArray<L2Player> interested = new GArray<L2Player>(players.size());
                for(L2Player pl : players)
                {
                  QuestState qs = pl.getQuestState(quest.getName());
                  if(qs != null && !qs.isCompleted()) // из тех, у кого взят квест
                  {
                    interested.add(pl);
                  }
                }
                if(interested.isEmpty())
                {
                  continue;
                }
                toReward = interested.get(Rnd.get(interested.size()));
                if(toReward == null)
                {
                  toReward = killer;
                }
              }
            }
            if(toReward != null)
            {
              QuestState qs = toReward.getQuestState(quest.getName());
              if(qs != null && !qs.isCompleted())
              {
                quest.notifyKill(this, qs);
              }
            }
          }
        }
      }
    }
    catch(final Exception e)
    {
      e.printStackTrace();
    }
    // Distribute Exp and SP rewards to L2Player (including Summon owner) that hit the L2NpcInstance and to their Party members
    FastMap<L2Player, RewardInfo> rewards = new FastMap<L2Player, RewardInfo>().setShared(true);
    for(AggroInfo info : aggroList.values())
    {
      if(info.damage <= 1)
      {
        continue;
      }
      L2Character attacker = info.attacker;
      if(attacker == null || !attacker.isPlayer())
      {
        continue;
      }
      L2Player player = attacker.getPlayer();
      if(player != null)
      {
        RewardInfo reward = rewards.get(player);
        if(reward == null)
        {
          rewards.put(player, new RewardInfo(player, info.damage));
        }
        else
        {
          reward.addDamage(info.damage);
        }
      }
    }
    for(FastMap.Entry<L2Player, RewardInfo> e = rewards.head(), end = rewards.tail(); e != null && (e = e.getNext()) != end && e != null;)
    {
      L2Player attacker = e.getKey();
      RewardInfo reward = e.getValue();
      if(attacker == null || attacker.isDead() || reward == null)
      {
        continue;
      }
      L2Party party = attacker.getParty();
      int maxHp = getMaxHp();
      if(party == null)
      {
        int damage = Math.min(reward._dmg, maxHp);
        if(damage > 0)
        {
          double[] xpsp = calculateExpAndSp(attacker, attacker.getLevel(), damage);
          double neededExp = attacker.calcStat(Stats.SOULS_CONSUME_EXP, 0, this, null); // Начисление душ камаэлянам
          if(neededExp > 0 && xpsp[0] > neededExp)
          {
            broadcastPacket(new SpawnEmitter(this, attacker));
            ThreadPoolManager.getInstance().scheduleGeneral(new SoulConsumeTask(attacker), 1000);
          }
          xpsp[0] = applyOverhit(killer, xpsp[0]);
          xpsp = attacker.applyVitality(this, xpsp[0], xpsp[1], 1.0);
          attacker.addExpAndSp((long) xpsp[0], (long) xpsp[1], false, true);
        }
        rewards.remove(attacker);
      }
      else
      {
        int partyDmg = 0;
        int partylevel = 1;
        GArray<L2Player> rewardedMembers = new GArray<L2Player>();
        for(L2Player partyMember : party.getPartyMembers())
        {
          RewardInfo ai = rewards.remove(partyMember);
          if(partyMember.isDead() || !partyMember.isInRange(lastAttacker, Config.ALT_PARTY_DISTRIBUTION_RANGE))
          {
            continue;
          }
          if(ai != null)
          {
            partyDmg += ai._dmg;
          }
          rewardedMembers.add(partyMember);
          if(partyMember.getLevel() > partylevel)
          {
            partylevel = partyMember.getLevel();
          }
        }
        partyDmg = Math.min(partyDmg, maxHp);
        if(partyDmg > 0)
        {
          double[] xpsp = calculateExpAndSp(attacker, partylevel, partyDmg);
          double partyMul = (double) partyDmg / maxHp;
          xpsp[0] *= partyMul;
          xpsp[1] *= partyMul;
          xpsp[0] = applyOverhit(killer, xpsp[0]);
          party.distributeXpAndSp(xpsp[0], xpsp[1], rewardedMembers, lastAttacker, this);
        }
      }
    }
    // Check the drop of a cursed weapon
    CursedWeaponsManager.getInstance().dropAttackable(this, killer);
    // Manage Base, Quests and Special Events drops of the L2NpcInstance
    doItemDrop(topDamager);
    // Manage Sweep drops of the L2NpcInstance
    if(isSpoiled())
    {
      doSweepDrop(lastAttacker, topDamager);
    }
    if(!isRaid()) // С рейдов падают только топовые лайфстоны
    {
      double chancemod = ((L2NpcTemplate) _template).rateHp * Experience.penaltyModifier(calculateLevelDiffForDrop(topDamager.getLevel()), 9);
      // Дополнительный дроп материалов
      if(Config.ALT_GAME_MATHERIALSDROP && chancemod > 0 && (!isSeeded() || _seeded.isAltSeed()))
      {
        for(L2DropData d : _matdrop)
        {
          if(getLevel() >= d.getMinLevel())
          {
            long count = Util.rollDrop(d.getMinDrop(), d.getMaxDrop(), d.getChance() * chancemod * Config.RATE_DROP_ITEMS * killer.getRateItems(), true);
            if(count > 0)
            {
              dropItem(killer, d.getItemId(), count);
            }
          }
        }
      }
      // Хербы
      if(((L2NpcTemplate) _template).isDropHerbs && chancemod > 0)
      {
        for(L2DropGroup h : _herbs)
        {
          Collection<ItemToDrop> itdl = h.rollFixedQty(0, this, killer, chancemod);
          if(itdl != null)
          {
            for(ItemToDrop itd : itdl)
            {
              dropItem(killer, itd.itemId, 1);
            }
          }
        }
      }
      // Лайфстоуны
      if(chancemod > 0)
      {
        for(L2DropData l : _lifestones)
        {
          if(getLevel() >= l.getMinLevel() && getLevel() <= l.getMaxLevel() && Rnd.get(1, L2Drop.MAX_CHANCE) <= l.getChance() * Config.RATE_DROP_ITEMS * killer.getRateItems() * chancemod)
          {
            dropItem(killer, l.getItemId(), 1);
            break;
          }
        }
      }
    }
    else if(isRaid())
    {
      // Лайфстоуны с рейдов
      for(L2DropData l : _toplifestones)
      {
        if(getLevel() >= l.getMinLevel() && getLevel() <= l.getMaxLevel())
        {
          GArray<ItemToDrop> itd = l.roll(killer, isBoss() ? 15 : 1, true);
          for(ItemToDrop t : itd)
          {
            for(int i = 0; i < t.count; i++)
            {
              dropItem(killer, t.itemId, 1);
            }
          }
        }
      }
      // предметы для изучения клановых скилов (не падают с эпиков)
      if(!isBoss())
      {
        for(L2DropData l : _raiditems)
        {
          if(getLevel() >= l.getMinLevel() && getLevel() <= l.getMaxLevel() && Rnd.get(1, L2Drop.MAX_CHANCE) <= l.getChance())
          {
            int mod = (int) (getLevel() * Config.RATE_DROP_RAIDBOSS / 20);
            dropItem(killer, l.getItemId(), Rnd.get(l.getMinDrop() * mod, l.getMaxDrop() * mod));
          }
        }
      }
    }
    // Enhance soul crystals of the attacker if this L2NpcInstance had its soul absorbed
    try
    {
      levelSoulCrystals(killer, aggroList);
    }
    catch(final Exception e)
    {
      e.printStackTrace();
    }
  }

  /**
   * Моб уже формально мертв, но его труп еще нельзя использовать поскольку не закончен подсчет наград
   */
  public boolean isDying()
  {
    return _dying;
  }

  public void giveItem(L2ItemInstance item, boolean store)
  {
    if(_inventory == null)
    {
      _inventory = new GArray<L2ItemInstance>();
    }
    synchronized(_inventory)
    {
      if(item.isStackable())
      {
        for(L2ItemInstance i : _inventory)
        {
          if(i.getItemId() == item.getItemId())
          {
            i.setCount(item.getCount() + i.getCount());
            if(store)
            {
              i.updateDatabase(true, false);
            }
            return;
          }
        }
      }
      _inventory.add(item);
      if(store)
      {
        item.setOwnerId(getNpcId());
        item.setLocation(ItemLocation.MONSTER);
        item.updateDatabase();
      }
    }
  }

  @Override
  public void onRandomAnimation()
  {
    // Action id для живности 1-3
    broadcastPacket(new SocialAction(getObjectId(), Rnd.get(1, 3)));
  }

  @Override
  public int getKarma()
  {
    return 0;
  }

  /**
   * Adds an attacker that successfully absorbed the soul of this L2NpcInstance into the _absorbersList.<BR><BR>
   * <p/>
   * params:  attacker  - a valid L2Player
   * condition - an integer indicating the event when mob dies. This should be:
   * = 0   - "the crystal scatters";
   * = 1   - "the crystal failed to absorb. nothing happens";
   * = 2   - "the crystal resonates because you got more than 1 crystal on you";
   * = 3   - "the crystal cannot absorb the soul because the mob level is too low";
   * = 4   - "the crystal successfuly absorbed the soul";
   */
  public void addAbsorber(final L2Player attacker)
  {
    // The attacker must not be null
    if(attacker == null)
    {
      return;
    }
    // This L2NpcInstance must be of one type in the _absorbingMOBS_levelXX tables.
    if(getMaxLevelCrystal() == 0 || getCurrentHpPercents() > 50)
    {
      return;
    }
    if(_absorbersList == null)
    {
      _absorbersList = new GArray<Integer>();
    }
    if(!_absorbersList.contains(attacker.getObjectId()))
    {
      _absorbersList.add(attacker.getObjectId());
    }
  }

  /**
   * Возвращает минимально возможный уровень прокачки кристала для этого моба.
   */
  private int getMinLevelCrystal()
  {
    int mobId = getNpcId();
    for(int id : _absorbingMOBS_level4)
    {
      if(id == mobId)
      {
        return 0;
      }
    }
    for(int id : _absorbingMOBS_level8)
    {
      if(id == mobId)
      {
        return 0;
      }
    }
    for(int id : _absorbingMOBS_level10)
    {
      if(id == mobId)
      {
        return 0;
      }
    }
    for(int id : _absorbingMOBS_level12)
    {
      if(id == mobId)
      {
        return 10;
      }
    }
    for(int id : _absorbingMOBS_level13)
    {
      if(id == mobId)
      {
        return 12;
      }
    }
    for(int id : _absorbingMOBS_level14)
    {
      if(id == mobId)
      {
        return 13;
      }
    }
    for(int id : _absorbingMOBS_level15)
    {
      if(id == mobId)
      {
        return 14;
      }
    }
    for(int id : _absorbingMOBS_level16)
    {
      if(id == mobId)
      {
        return 15;
      }
    }
    return 0;
  }

  /**
   * Возвращает максимально возможный уровень прокачки кристала для этого моба.
   */
  private int getMaxLevelCrystal()
  {
    int mobId = getNpcId();
    for(int id : _absorbingMOBS_level16)
    {
      if(id == mobId)
      {
        return 16;
      }
    }
    for(int id : _absorbingMOBS_level15)
    {
      if(id == mobId)
      {
        return 15;
      }
    }
    for(int id : _absorbingMOBS_level14)
    {
      if(id == mobId)
      {
        return 14;
      }
    }
    for(int id : _absorbingMOBS_level13)
    {
      if(id == mobId)
      {
        return 13;
      }
    }
    for(int id : _absorbingMOBS_level12)
    {
      if(id == mobId)
      {
        return 12;
      }
    }
    for(int id : _absorbingMOBS_level10)
    {
      if(id == mobId)
      {
        return 10;
      }
    }
    for(int id : _absorbingMOBS_level8)
    {
      if(id == mobId)
      {
        return 8;
      }
    }
    for(int id : _absorbingMOBS_level4)
    {
      if(id == mobId)
      {
        return 4;
      }
    }
    return 0;
  }

  /**
   * Calculate the leveling chance of Soul Crystals based on the attacker that killed this L2NpcInstance
   *
   * @param attacker The player that last hitted (killed) this L2NpcInstance
   */
  private void levelSoulCrystals(final L2Character attacker, HashMap<L2Playable, AggroInfo> aggroList)
  {
    // Only player can absorb a soul
    if(attacker == null || !attacker.isPlayable())
    {
      _absorbersList = null;
      return;
    }
    // Init some useful vars
    boolean levelPartyCrystals = false;
    final L2Player killer = attacker.getPlayer();
    // Check if this L2NpcInstance isn't within any of the groups of mobs that can be absorbed
    int minCrystalLevel = getMinLevelCrystal();
    int maxCrystalLevel = getMaxLevelCrystal();
    // If this mob is a boss, then skip some checkings
    if(minCrystalLevel >= 10 && maxCrystalLevel > 10)
    {
      levelPartyCrystals = true;
    }
    // If this is not a valid L2NpcInstance, clears the _absorbersList and just return
    // Fail if this L2NpcInstance isn't absorbed or there's no one in its _absorbersList or killer isn't in the _absorbersList and mob is not boss
    else if(maxCrystalLevel == 0 || _absorbersList == null || !_absorbersList.contains(killer.getObjectId()))
    {
      _absorbersList = null;
      return;
    }
    _absorbersList = null;
    // Now we got 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%)
    GArray<L2Player> players = null;
    if(levelPartyCrystals)
    {
      if(Config.ALT_NO_LASTHIT)
      {
        players = new GArray<L2Player>();
        for(L2Playable p : aggroList.keySet())
        {
          if(p.isPlayer())
          {
            players.add((L2Player) p);
          }
        }
      }
      else if(killer.isInParty())
      {
        players = killer.getParty().getPartyMembers();
      }
    }
    if(players == null)
    {
      players = new GArray<L2Player>();
      players.add(killer);
    }
    // Кристаллы качаются с шансом
    for(int id : _randomLeveling)
    {
      if(getNpcId() == id)
      {
        levelPartyCrystals = false;
        break;
      }
    }
    for(final L2Player player : players)
    {
      // Для прокачки кристалла обязательно должен быть взят квест _350_EnhanceYourWeapon
      if(player.getQuestState("_350_EnhanceYourWeapon") == null)
      {
        continue;
      }
      if(!player.isInRange(this, Config.ALT_PARTY_DISTRIBUTION_RANGE) && !player.isInRange(killer, Config.ALT_PARTY_DISTRIBUTION_RANGE))
      {
        continue;
      }
      int oldCrystalId = 0;
      int newCrystalId = 0;
      int crystalsCount = 0;
      int crystalLevel;
      boolean canIncreaseCrystal = false;
      boolean resonated = false;
      // Check how many soul crystals the player has in his inventory and which soul crystal he has
      for(final L2ItemInstance item : player.getInventory().getItems())
      {
        final int itemId = item.getItemId();
        if(!isSoulCrystal(itemId))
        {
          continue;
        }
        if(++crystalsCount > 1)
        {
          resonated = true;
          break;
        }
        if(crystalsCount < 1)
        {
          continue;
        }
        if((newCrystalId = getNextLevelCrystalId(itemId)) != 0)
        {
          crystalLevel = getCrystalLevel(itemId);
          canIncreaseCrystal = crystalLevel >= minCrystalLevel && crystalLevel < maxCrystalLevel;
          oldCrystalId = itemId;
        }
      }
      // The player has more than one soul crystal with him, the crystal resonates and we skip this player
      if(resonated)
      {
        if(!levelPartyCrystals)
        {
          player.sendPacket(Msg.THE_SOUL_CRYSTALS_CAUSED_RESONATION_AND_FAILED_AT_ABSORBING_A_SOUL);
        }
        continue;
      }
      // The soul crystal stage of the player is way too high, refuse to increase it
      if(!canIncreaseCrystal)
      {
        if(!levelPartyCrystals)
        {
          player.sendPacket(Msg.THE_SOUL_CRYSTAL_IS_REFUSING_TO_ABSORB_A_SOUL);
        }
        continue;
      }
      // If the killer succeeds or it is a boss mob, level up the crystal
      if(levelPartyCrystals || Rnd.chance(Config.ChanceLevelUpSA))
      {
        L2ItemInstance oldCrystal = player.getInventory().getItemByItemId(oldCrystalId);
        if(oldCrystal == null)
        {
          continue;
        }
        // Baylor с 50% шансом апает курсед кристал
        if((getNpcId() == 29099 || getNpcId() == 29103) && Rnd.chance(50))
        {
          newCrystalId = getCursedCrystal(newCrystalId);
        }
        player.sendPacket(SystemMessage.removeItems(oldCrystal.getItemId(), 1), Msg.THE_SOUL_CRYSTAL_SUCCEEDED_IN_ABSORBING_A_SOUL, SystemMessage.obtainItems(newCrystalId, 1, 0));
        player.getInventory().destroyItem(oldCrystal, 1, true);
        player.getInventory().addItem(ItemTable.getInstance().createItem(newCrystalId));
        // извещаем окружающих если получен кристал выше 10-ого уровня
        if(!(player.isGM() && player.isInvisible()))
        {
          String newCrystalColor = getCrystalColor(newCrystalId);
          if(newCrystalColor != null)
          {
            int newCrystalLvl = getCrystalLevel(newCrystalId);
            CustomMessage cm = new CustomMessage("l2p.gameserver.model.instances.L2MonsterInstance.levelSoulCrystals", player);
            cm.addCharName(player).addString(newCrystalColor).addNumber(newCrystalLvl);
            player.broadcastPacketToOthers(new SystemMessage(cm));
          }
        }
        continue;
      }
      player.sendPacket(Msg.THE_SOUL_CRYSTAL_IS_REFUSING_TO_ABSORB_A_SOUL);
    }
  }

  private static String getCrystalColor(int crystalId)
  {
    for(int id : _REDCRYSTALS)
    {
      if(id == crystalId)
      {
        return "red";
      }
    }
    for(int id : _BLUECRYSTALS)
    {
      if(id == crystalId)
      {
        return "blue";
      }
    }
    for(int id : _GREENCRYSTALS)
    {
      if(id == crystalId)
      {
        return "green";
      }
    }
    switch(crystalId)
    {
      case _REDCURSEDCRYSTAL_LVL14:
        return "red";
      case _BLUECURSEDCRYSTAL_LVL14:
        return "blue";
      case _GREENCURSEDCRYSTAL_LVL14:
        return "green";
    }
    return null;
  }

  private static int getCrystalLevel(int crystalId)
  {
    for(int i = 0; i < _REDCRYSTALS.length; i++)
    {
      if(_REDCRYSTALS[i] == crystalId)
      {
        return i;
      }
    }
    for(int i = 0; i < _BLUECRYSTALS.length; i++)
    {
      if(_BLUECRYSTALS[i] == crystalId)
      {
        return i;
      }
    }
    for(int i = 0; i < _GREENCRYSTALS.length; i++)
    {
      if(_GREENCRYSTALS[i] == crystalId)
      {
        return i;
      }
    }
    switch(crystalId)
    {
      case _REDCURSEDCRYSTAL_LVL14:
      case _BLUECURSEDCRYSTAL_LVL14:
      case _GREENCURSEDCRYSTAL_LVL14:
        return 14;
    }
    return Integer.MAX_VALUE;
  }

  private static int getNextLevelCrystalId(int crystalId)
  {
    for(int i = 0; i < _REDCRYSTALS.length; i++)
    {
      if(_REDCRYSTALS[i] == crystalId)
      {
        return i >= _MAX_CRYSTALS_LEVEL ? _REDCRYSTALS[_MAX_CRYSTALS_LEVEL] : _REDCRYSTALS[i + 1];
      }
    }
    for(int i = 0; i < _GREENCRYSTALS.length; i++)
    {
      if(_GREENCRYSTALS[i] == crystalId)
      {
        return i >= _MAX_CRYSTALS_LEVEL ? _GREENCRYSTALS[_MAX_CRYSTALS_LEVEL] : _GREENCRYSTALS[i + 1];
      }
    }
    for(int i = 0; i < _BLUECRYSTALS.length; i++)
    {
      if(_BLUECRYSTALS[i] == crystalId)
      {
        return i >= _MAX_CRYSTALS_LEVEL ? _BLUECRYSTALS[_MAX_CRYSTALS_LEVEL] : _BLUECRYSTALS[i + 1];
      }
    }
    return 0;
  }

  private static int getCursedCrystal(int soulCrystalLvl14)
  {
    if(soulCrystalLvl14 == _REDCRYSTALS[14])
    {
      return _REDCURSEDCRYSTAL_LVL14;
    }
    if(soulCrystalLvl14 == _GREENCRYSTALS[14])
    {
      return _GREENCURSEDCRYSTAL_LVL14;
    }
    if(soulCrystalLvl14 == _BLUECRYSTALS[14])
    {
      return _BLUECURSEDCRYSTAL_LVL14;
    }
    return soulCrystalLvl14;
  }

  private static boolean isSoulCrystal(int crystalId)
  {
    for(int id : _REDCRYSTALS)
    {
      if(id == crystalId)
      {
        return true;
      }
    }
    for(int id : _BLUECRYSTALS)
    {
      if(id == crystalId)
      {
        return true;
      }
    }
    for(int id : _GREENCRYSTALS)
    {
      if(id == crystalId)
      {
        return true;
      }
    }
    return false;
  }

  public L2ItemInstance takeHarvest()
  {
    harvestLock.lock();
    final L2ItemInstance harvest = _harvestItem;
    _harvestItem = null;
    _seeded = null;
    seederStoreId = 0;
    harvestLock.unlock();
    return harvest;
  }

  public void setSeeded(L2Item seed, L2Player player)
  {
    if(player == null)
    {
      return;
    }
    harvestLock.lock();
    try
    {
      _seeded = seed;
      seederStoreId = player.getStoredId();
      _harvestItem = ItemTable.getInstance().createItem(L2Manor.getInstance().getCropType(seed.getItemId()));
      // Количество всходов от xHP до (xHP + xHP/2)
      if(getTemplate().rateHp <= 1)
      {
        _harvestItem.setCount(1);
      }
      else
      {
        _harvestItem.setCount(Rnd.get(Math.round(getTemplate().rateHp), Math.round(1.5 * getTemplate().rateHp)));
      }
    }
    finally
    {
      harvestLock.unlock();
    }
  }

  public boolean isSeeded(L2Player seeder)
  {
    if(seederStoreId == 0)
    {
      return false;
    }
    L2Player _seeder = L2ObjectsStorage.getAsPlayer(seederStoreId);
    return seeder != null && seeder == _seeder || _dieTime + 10000 < System.currentTimeMillis();
  }

  public boolean isSeeded()
  {
    return _seeded != null;
  }
  /**
   * True if a Dwarf has used Spoil on this L2NpcInstance
   */
  private boolean _isSpoiled;

  /**
   * Return True if this L2NpcInstance has drops that can be sweeped.<BR><BR>
   */
  public boolean isSpoiled()
  {
    return _isSpoiled; //FIXME возможно нужно делать sweepLock.lock() ... но тут же только чтение одного поля...
  }

  public boolean isSpoiled(L2Player spoiler)
  {
    L2Player this_spoiler;
    sweepLock.lock();
    try
    {
      if(!_isSpoiled) // если не заспойлен то false
      {
        return false;
      }
      this_spoiler = L2ObjectsStorage.getAsPlayer(spoilerStoreId);
    }
    finally
    {
      sweepLock.unlock();
    }
    if(this_spoiler == null || spoiler.getObjectId() == this_spoiler.getObjectId() || _dieTime + 10000 < System.currentTimeMillis())
    {
      return true;
    }
    if(getDistance(this_spoiler) > Config.ALT_PARTY_DISTRIBUTION_RANGE) // если спойлер слишком далеко разрешать
    {
      return true;
    }
    if(spoiler.getParty() != null && spoiler.getParty().containsMember(this_spoiler)) // сопартийцам тоже можно
    {
      return true;
    }
    return false;
  }

  /**
   * Set the spoil state of this L2NpcInstance.<BR><BR>
   *
   * @param spoiler
   */
  public void setSpoiled(boolean isSpoiled, L2Player spoiler)
  {
    sweepLock.lock();
    try
    {
      _isSpoiled = isSpoiled;
      spoilerStoreId = spoiler != null ? spoiler.getStoredId() : 0;
    }
    finally
    {
      sweepLock.unlock();
    }
  }

  public void doItemDrop(L2Character topDamager)
  {
    L2Player player = topDamager.getPlayer();
    if(player == null)
    {
      return;
    }
    double mod = calcStat(Stats.DROP, 1., topDamager, null);
    if(getTemplate().getDropData() != null)
    {
      GArray<ItemToDrop> drops = getTemplate().getDropData().rollDrop(calculateLevelDiffForDrop(topDamager.getLevel()), this, player, mod);
      for(ItemToDrop drop : drops)
      {
        // Если в моба посеяно семя, причем не альтернативное - не давать никакого дропа, кроме адены.
        if(_seeded != null && !_seeded.isAltSeed() && !drop.isAdena)
        {
          continue;
        }
        dropItem(player, drop.itemId, drop.count);
      }
    }
    if(_inventory != null)
    {
      synchronized(_inventory)
      {
        for(L2ItemInstance drop : _inventory)
        {
          if(drop != null)
          {
            player.sendMessage(new CustomMessage("l2p.gameserver.model.instances.L2MonsterInstance.ItemBelongedToOther", player).addString(drop.getName()));
            dropItem(player, drop);
          }
        }
        if(_inventory != null)
        {
          _inventory.clear();
        }
        _inventory = null;
      }
    }
    GArray<L2ItemInstance> templateInv = getTemplate().takeInventory();
    if(templateInv != null)
    {
      for(L2ItemInstance drop : templateInv)
      {
        if(drop != null)
        {
          player.sendMessage(new CustomMessage("l2p.gameserver.model.instances.L2MonsterInstance.ItemBelongedToOther", player).addString(drop.getName()));
          dropItem(player, drop);
        }
      }
      if(_inventory != null)
      {
        _inventory.clear();
      }
      _inventory = null;
    }
  }

  private void doSweepDrop(final L2Character lastAttacker, L2Character topDamager)
  {
    final L2Player player = lastAttacker.getPlayer();
    if(player == null)
    {
      return;
    }
    final int levelDiff = calculateLevelDiffForDrop(topDamager.getLevel());
    final GArray<L2ItemInstance> spoiled = new GArray<L2ItemInstance>();
    if(getTemplate().getDropData() != null)
    {
      double mod = calcStat(Stats.DROP, 1., lastAttacker, null);
      final GArray<ItemToDrop> spoils = getTemplate().getDropData().rollSpoil(levelDiff, this, player, mod);
      for(final ItemToDrop spoil : spoils)
      {
        final L2ItemInstance dropit = ItemTable.getInstance().createItem(spoil.itemId);
        dropit.setCount(spoil.count);
        spoiled.add(dropit);
      }
    }
    if(spoiled.size() > 0)
    {
      _sweepItems = spoiled.toArray(new L2ItemInstance[spoiled.size()]);
    }
  }

  private double[] calculateExpAndSp(L2Character attacker, int level, long damage)
  {
    if(!isInRange(attacker, Config.ALT_PARTY_DISTRIBUTION_RANGE) && Math.abs(attacker.getZ() - getZ()) < 400)
    {
      return new double[]
      {
        0., 0.
      };
    }
    int diff = level - getLevel();
    if(level > 77 && diff > 3 && diff <= 5) // kamael exp penalty
    {
      diff += 3;
    }
    double xp = getExpReward() * damage / getMaxHp();
    double sp = getSpReward() * damage / getMaxHp();
    if(diff > 5)
    {
      double mod = Math.pow(.83, diff - 5);
      xp *= mod;
      sp *= mod;
    }
    xp = Math.max(0, xp);
    sp = Math.max(0, sp);
    return new double[]
    {
      xp, sp
    };
  }

  private double applyOverhit(L2Player killer, double xp)
  {
    if(xp > 0 && getOverhitAttacker() != null && killer == getOverhitAttacker())
    {
      int overHitExp = calculateOverhitExp(xp);
      killer.sendPacket(Msg.OVER_HIT, new SystemMessage(SystemMessage.ACQUIRED_S1_BONUS_EXPERIENCE_THROUGH_OVER_HIT).addNumber(overHitExp));
      xp += overHitExp;
    }
    return xp;
  }

  public L2Character getOverhitAttacker()
  {
    return overhitAttackerStoreId == 0 ? null : L2ObjectsStorage.getAsCharacter(overhitAttackerStoreId);
  }

  @Override
  public void setOverhitAttacker(L2Character overhitAttacker)
  {
    overhitAttackerStoreId = overhitAttacker == null ? 0 : overhitAttacker.getStoredId();
  }

  public double getOverhitDamage()
  {
    return _overhitDamage;
  }

  @Override
  public void setOverhitDamage(double damage)
  {
    _overhitDamage = damage;
  }

  public int calculateOverhitExp(final double normalExp)
  {
    double overhitPercentage = getOverhitDamage() * 100 / getMaxHp();
    if(overhitPercentage > 25)
    {
      overhitPercentage = 25;
    }
    double overhitExp = overhitPercentage / 100 * normalExp;
    overhitAttackerStoreId = 0;
    setOverhitDamage(0);
    return (int) Math.round(overhitExp);
  }

  /**
   * Return True if a Dwarf use Sweep on the L2NpcInstance and if item can be spoiled.<BR><BR>
   */
  public boolean isSweepActive()
  {
    dyingLock.lock();
    try
    {
      return _sweepItems != null && _sweepItems.length > 0;
    }
    finally
    {
      dyingLock.unlock();
    }
  }

  /**
   * Return table containing all L2ItemInstance that can be spoiled.<BR><BR>
   */
  public L2ItemInstance[] takeSweep()
  {
    L2ItemInstance[] sweep;
    sweepLock.lock();
    try
    {
      sweep = (_sweepItems == null || _sweepItems.length == 0) ? null : _sweepItems.clone();
      _sweepItems = null;
    }
    finally
    {
      sweepLock.unlock();
    }
    return sweep;
  }

  @Override
  public boolean isInvul()
  {
    return _isInvul;
  }

  @Override
  public boolean isAggressive()
  {
    return (Config.ALT_CHAMPION_CAN_BE_AGGRO || getChampion() == 0) && super.isAggressive();
  }

  @Override
  public String getFactionId()
  {
    return Config.ALT_CHAMPION_CAN_BE_SOCIAL || getChampion() == 0 ? super.getFactionId() : "";
  }

  @Override
  public String toString()
  {
    return "Mob " + getName() + " [" + getNpcId() + "] / " + getObjectId();
  }
}
TOP

Related Classes of l2p.gameserver.model.instances.L2MonsterInstance

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.