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();
}
}