/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ package com.l2jserver.gameserver.util; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javolution.util.FastList; import javolution.util.FastSet; import com.l2jserver.Config; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.datatables.NpcTable; import com.l2jserver.gameserver.idfactory.IdFactory; import com.l2jserver.gameserver.model.L2MinionData; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance; import com.l2jserver.gameserver.templates.chars.L2NpcTemplate; import com.l2jserver.util.Rnd; /** * * @author luisantonioa, DS * */ public class MinionList { private static Logger _log = Logger.getLogger(MinionList.class.getName()); private final L2MonsterInstance _master; /** List containing the current spawned minions */ private final List _minionReferences; /** List containing the cached deleted minions for reuse */ private List _reusedMinionReferences = null; public MinionList(L2MonsterInstance pMaster) { if (pMaster == null) throw new NullPointerException("MinionList: master is null"); _master = pMaster; _minionReferences = new FastList().shared(); } /** * Returns list of the spawned (alive) minions. */ public List getSpawnedMinions() { return _minionReferences; } /** * Manage the spawn of Minions.

* * Actions :

*
  • Get the Minion data of all Minions that must be spawn
  • *
  • For each Minion type, spawn the amount of Minion needed


  • */ public final void spawnMinions() { if (_master.isAlikeDead()) return; List minions = _master.getTemplate().getMinionData(); if (minions == null) return; int minionCount, minionId, minionsToSpawn; for (L2MinionData minion : minions) { minionCount = minion.getAmount(); minionId = minion.getMinionId(); minionsToSpawn = minionCount - countSpawnedMinionsById(minionId); if (minionsToSpawn > 0) { for (int i = 0; i < minionsToSpawn; i++) spawnMinion(minionId); } } // remove non-needed minions deleteReusedMinions(); } /** * Delete all spawned minions and try to reuse them. */ public void deleteSpawnedMinions() { if (!_minionReferences.isEmpty()) { for (L2MonsterInstance minion : _minionReferences) { if (minion != null) { minion.setLeader(null); minion.deleteMe(); if (_reusedMinionReferences != null) _reusedMinionReferences.add(minion); } } _minionReferences.clear(); } } /** * Delete all reused minions to prevent memory leaks. */ public void deleteReusedMinions() { if (_reusedMinionReferences != null) _reusedMinionReferences.clear(); } // hooks /** * Called on the master spawn * Old minions (from previous spawn) are deleted. * If master can respawn - enabled reuse of the killed minions. */ public void onMasterSpawn() { deleteSpawnedMinions(); // if master has spawn and can respawn - try to reuse minions if (_reusedMinionReferences == null && _master.getTemplate().getMinionData() != null && _master.getSpawn() != null && _master.getSpawn().isRespawnEnabled()) _reusedMinionReferences = new FastList().shared(); } /** * Called on the minion spawn * and added them in the list of the spawned minions. */ public void onMinionSpawn(L2MonsterInstance minion) { _minionReferences.add(minion); } /** * Called on the master death/delete. * @param force if true - force delete of the spawned minions * By default minions deleted only for raidbosses */ public void onMasterDie(boolean force) { if (_master.isRaid() || force) deleteSpawnedMinions(); } /** * Called on the minion death/delete. * Removed minion from the list of the spawned minions and reuse if possible. * @param respawnTime (ms) enable respawning of this minion while master is alive. * -1 - use default value: 0 (disable) for mobs and config value for raids. */ public void onMinionDie(L2MonsterInstance minion, int respawnTime) { minion.setLeader(null); // prevent memory leaks _minionReferences.remove(minion); if (_reusedMinionReferences != null) _reusedMinionReferences.add(minion); final int time = respawnTime < 0 ? _master.isRaid() ? (int)Config.RAID_MINION_RESPAWN_TIMER : 0 : respawnTime; if (time > 0 && !_master.isAlikeDead()) ThreadPoolManager.getInstance().scheduleGeneral(new MinionRespawnTask(minion), time); } /** * Called if master/minion was attacked. * Master and all free minions receive aggro against attacker. */ public void onAssist(L2Character caller, L2Character attacker) { if (attacker == null) return; if (!_master.isAlikeDead() && !_master.isInCombat()) _master.addDamageHate(attacker, 0, 1); final boolean callerIsMaster = caller == _master; int aggro = callerIsMaster ? 10 : 1; if (_master.isRaid()) aggro *= 10; for (L2MonsterInstance minion : _minionReferences) { if (minion != null && !minion.isDead() && (callerIsMaster || !minion.isInCombat())) minion.addDamageHate(attacker, 0, aggro); } } /** * Called from onTeleported() of the master * Alive and able to move minions teleported to master. */ public void onMasterTeleported() { final int offset = 200; final int minRadius = (int)_master.getCollisionRadius() + 30; for (L2MonsterInstance minion : _minionReferences) { if (minion != null && !minion.isDead() && !minion.isMovementDisabled()) { int newX = Rnd.get(minRadius * 2, offset * 2); // x int newY = Rnd.get(newX, offset * 2); // distance newY = (int)Math.sqrt(newY*newY - newX*newX); // y if (newX > offset + minRadius) newX = _master.getX() + newX - offset; else newX = _master.getX() - newX + minRadius; if (newY > offset + minRadius) newY = _master.getY() + newY - offset; else newY = _master.getY() - newY + minRadius; minion.teleToLocation(newX, newY, _master.getZ()); } } } private final void spawnMinion(int minionId) { if (minionId == 0) return; // searching in reused minions if (_reusedMinionReferences != null && !_reusedMinionReferences.isEmpty()) { L2MonsterInstance minion; Iterator iter = _reusedMinionReferences.iterator(); while (iter.hasNext()) { minion = iter.next(); if (minion != null && minion.getNpcId() == minionId) { iter.remove(); minion.refreshID(); initializeNpcInstance(_master, minion); return; } } } // not found in cache spawnMinion(_master, minionId); } private final class MinionRespawnTask implements Runnable { private final L2MonsterInstance _minion; public MinionRespawnTask(L2MonsterInstance minion) { _minion = minion; } public void run() { if (!_master.isAlikeDead() && _master.isVisible()) { // minion can be already spawned or deleted if (!_minion.isVisible()) { if (_reusedMinionReferences != null) _reusedMinionReferences.remove(_minion); _minion.refreshID(); initializeNpcInstance(_master, _minion); } } } } /** * Init a Minion and add it in the world as a visible object.

    * * Actions :

    *
  • Get the template of the Minion to spawn
  • *
  • Create and Init the Minion and generate its Identifier
  • *
  • Set the Minion HP, MP and Heading
  • *
  • Set the Minion leader to this RaidBoss
  • *
  • Init the position of the Minion and add it in the world as a visible object


  • * * @param master L2MonsterInstance used as master for this minion * @param minionid The L2NpcTemplate Identifier of the Minion to spawn * */ public static final L2MonsterInstance spawnMinion(L2MonsterInstance master, int minionId) { // Get the template of the Minion to spawn L2NpcTemplate minionTemplate = NpcTable.getInstance().getTemplate(minionId); if (minionTemplate == null) return null; // Create and Init the Minion and generate its Identifier L2MonsterInstance minion = new L2MonsterInstance(IdFactory.getInstance().getNextId(), minionTemplate); return initializeNpcInstance(master, minion); } private static final L2MonsterInstance initializeNpcInstance(L2MonsterInstance master, L2MonsterInstance minion) { minion.stopAllEffects(); minion.setIsDead(false); minion.setDecayed(false); // Set the Minion HP, MP and Heading minion.setCurrentHpMp(minion.getMaxHp(), minion.getMaxMp()); minion.setHeading(master.getHeading()); // Set the Minion leader to this RaidBoss minion.setLeader(master); //move monster to masters instance minion.setInstanceId(master.getInstanceId()); // Init the position of the Minion and add it in the world as a visible object final int offset = 200; final int minRadius = (int)master.getCollisionRadius() + 30; int newX = Rnd.get(minRadius * 2, offset * 2); // x int newY = Rnd.get(newX, offset * 2); // distance newY = (int)Math.sqrt(newY*newY - newX*newX); // y if (newX > offset + minRadius) newX = master.getX() + newX - offset; else newX = master.getX() - newX + minRadius; if (newY > offset + minRadius) newY = master.getY() + newY - offset; else newY = master.getY() - newY + minRadius; minion.spawnMe(newX, newY, master.getZ()); if (Config.DEBUG) _log.fine("Spawned minion template " + minion.getNpcId() + " with objid: " + minion.getObjectId() + " to boss " + master.getObjectId() + " ,at: " + minion.getX() + " x, " + minion.getY() + " y, " + minion.getZ() + " z"); return minion; } // Statistics part private final int countSpawnedMinionsById(int minionId) { int count = 0; for (L2MonsterInstance minion : _minionReferences) { if (minion != null && minion.getNpcId() == minionId) count++; } return count; } public final int countSpawnedMinions() { return _minionReferences.size(); } public final int lazyCountSpawnedMinionsGroups() { Set seenGroups = new FastSet(); for (L2MonsterInstance minion : _minionReferences) { if (minion == null) continue; seenGroups.add(minion.getNpcId()); } return seenGroups.size(); } }