/*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*/
package silentium.gameserver.instancemanager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Iterator;
import javolution.util.FastList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import silentium.commons.database.DatabaseFactory;
import silentium.gameserver.idfactory.IdFactory;
import silentium.gameserver.model.AutoChatHandler;
import silentium.gameserver.model.L2ItemInstance;
import silentium.gameserver.model.L2World;
import silentium.gameserver.model.actor.instance.L2PcInstance;
import silentium.gameserver.model.actor.instance.L2SiegeGuardInstance;
import silentium.gameserver.model.entity.Castle;
import silentium.gameserver.tables.NpcTable;
import silentium.gameserver.templates.chars.L2NpcTemplate;
/**
* This class is similar to the SiegeGuardManager, except it handles the loading of the mercenary tickets that are dropped on castle floors by
* the castle lords.<br>
* <br>
* These tickets (aka badges) need to be readed after each server reboot except when the server crashed in the middle of an ongoig siege. In
* addition, this class keeps track of the added tickets, in order to properly limit the number of mercenaries in each castle and the number of
* mercenaries from each mercenary type.<br>
* <br>
* Finally, we provide auxilary functions to identify the castle in which each item (and its corresponding NPC) belong to, in order to help avoid
* mixing them up.
*
* @author yellowperil & Fulminus
*/
public class MercTicketManager
{
protected static Logger _log = LoggerFactory.getLogger(MercTicketManager.class.getName());
public static final MercTicketManager getInstance()
{
return SingletonHolder._instance;
}
private static final FastList<L2ItemInstance> _droppedTickets = new FastList<>();
// max tickets per merc type = 10 + (castleid * 2)?
// max tickets per castle = 40 + (castleid * 20)?
private static final int[] MAX_MERC_PER_TYPE = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Gludio
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // Dion
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Giran
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Oren
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Aden
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Innadril
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Goddard
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Rune
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
// Schuttgart
};
private static final int[] MERCS_MAX_PER_CASTLE = { 100, // Gludio
150, // Dion
200, // Giran
300, // Oren
400, // Aden
400, // Innadril
400, // Goddard
400, // Rune
400
// Schuttgart
};
private static final int[] ITEM_IDS = { 3960, 3961, 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, // Gludio
3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6060, // Dion
3986, 3987, 3988, 3989, 3990, 3991, 3992, 3993, 3994, 3995, 6064, 6065, 6066, 6067, 6068, 6069, 6070, 6071, 6072, 6073, // Giran
3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 6077, 6078, 6079, 6080, 6081, 6082, 6083, 6084, 6085, 6086, // Oren
4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 6090, 6091, 6092, 6093, 6094, 6095, 6096, 6097, 6098, 6099, // Aden
5205, 5206, 5207, 5208, 5209, 5210, 5211, 5212, 5213, 5214, 6105, 6106, 6107, 6108, 6109, 6110, 6111, 6112, 6113, 6114, // Innadril
6779, 6780, 6781, 6782, 6783, 6784, 6785, 6786, 6787, 6788, 6792, 6793, 6794, 6795, 6796, 6797, 6798, 6799, 6800, 6801, // Goddard
7973, 7974, 7975, 7976, 7977, 7978, 7979, 7980, 7981, 7982, 7988, 7989, 7990, 7991, 7992, 7993, 7994, 7995, 7996, 7997, // Rune
7918, 7919, 7920, 7921, 7922, 7923, 7924, 7925, 7926, 7927, 7931, 7932, 7933, 7934, 7935, 7936, 7937, 7938, 7939, 7940
// Schuttgart
};
private static final int[] NPC_IDS = { 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Gludio
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Dion
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Giran
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Oren
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Aden
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Innadril
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Goddard
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039, // Rune
35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35030, 35031, 35032, 35033, 35034, 35035, 35036, 35037, 35038, 35039
// Schuttgart
};
protected MercTicketManager()
{
_droppedTickets.shared();
load();
}
/**
* @param itemId
* The itemID to check.
* @return the castleId for the passed ticket item id.
*/
public int getTicketCastleId(int itemId)
{
if ((itemId >= ITEM_IDS[0] && itemId <= ITEM_IDS[9]) || (itemId >= ITEM_IDS[10] && itemId <= ITEM_IDS[19]))
return 1; // Gludio
if ((itemId >= ITEM_IDS[20] && itemId <= ITEM_IDS[29]) || (itemId >= ITEM_IDS[30] && itemId <= ITEM_IDS[39]))
return 2; // Dion
if ((itemId >= ITEM_IDS[40] && itemId <= ITEM_IDS[49]) || (itemId >= ITEM_IDS[50] && itemId <= ITEM_IDS[59]))
return 3; // Giran
if ((itemId >= ITEM_IDS[60] && itemId <= ITEM_IDS[69]) || (itemId >= ITEM_IDS[70] && itemId <= ITEM_IDS[79]))
return 4; // Oren
if ((itemId >= ITEM_IDS[80] && itemId <= ITEM_IDS[89]) || (itemId >= ITEM_IDS[90] && itemId <= ITEM_IDS[99]))
return 5; // Aden
if ((itemId >= ITEM_IDS[100] && itemId <= ITEM_IDS[109]) || (itemId >= ITEM_IDS[110] && itemId <= ITEM_IDS[119]))
return 6; // Innadril
if ((itemId >= ITEM_IDS[120] && itemId <= ITEM_IDS[129]) || (itemId >= ITEM_IDS[130] && itemId <= ITEM_IDS[139]))
return 7; // Goddard
if ((itemId >= ITEM_IDS[140] && itemId <= ITEM_IDS[149]) || (itemId >= ITEM_IDS[150] && itemId <= ITEM_IDS[159]))
return 8; // Rune
if ((itemId >= ITEM_IDS[160] && itemId <= ITEM_IDS[169]) || (itemId >= ITEM_IDS[170] && itemId <= ITEM_IDS[179]))
return 9; // Schuttgart
return -1;
}
public void reload()
{
_droppedTickets.clear();
load();
}
private final static void load()
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = con.prepareStatement("SELECT * FROM castle_siege_guards WHERE isHired=1");
ResultSet rs = statement.executeQuery();
int npcId;
int itemId;
int x, y, z;
// start index to begin the search for the itemId corresponding to this NPC :
// a) skip unnecessary iterations in the search loop
// b) avoid finding the wrong itemId whenever tickets of different spawn the same npc!
int startindex = 0;
while (rs.next())
{
npcId = rs.getInt("npcId");
x = rs.getInt("x");
y = rs.getInt("y");
z = rs.getInt("z");
Castle castle = CastleManager.getInstance().getCastle(x, y, z);
if (castle != null)
startindex = 10 * (castle.getCastleId() - 1);
// find the FIRST ticket itemId with spawns the saved NPC in the saved location
for (int i = startindex; i < NPC_IDS.length; i++)
{
if (NPC_IDS[i] == npcId) // Find the index of the item used
{
// only handle tickets if a siege is not ongoing in this npc's castle
if ((castle != null) && !(castle.getSiege().getIsInProgress()))
{
itemId = ITEM_IDS[i];
// create the ticket in the gameworld
L2ItemInstance dropticket = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
dropticket.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
dropticket.dropMe(null, x, y, z);
dropticket.setDropTime(0); // avoids it from beeing removed by the auto item destroyer
L2World.getInstance().storeObject(dropticket);
_droppedTickets.add(dropticket);
}
break;
}
}
}
statement.close();
}
catch (Exception e)
{
_log.warn("Exception: loadMercenaryData(): " + e.getMessage(), e);
}
_log.info("MercTicketManager: Loaded " + _droppedTickets.size() + " tickets.");
}
/**
* @param itemId
* The ticket id to make checks on.
* @return true if the passed item has reached the limit of number of dropped tickets that this SPECIFIC item may have in its castle.
*/
public boolean isAtTypeLimit(int itemId)
{
int limit = -1;
// find the max value for this item
for (int i = 0; i < ITEM_IDS.length; i++)
if (ITEM_IDS[i] == itemId) // Find the index of the item used
{
limit = MAX_MERC_PER_TYPE[i];
break;
}
if (limit <= 0)
return true;
int count = 0;
for (L2ItemInstance ticket : _droppedTickets)
{
if (ticket != null && ticket.getItemId() == itemId)
count++;
}
if (count >= limit)
return true;
return false;
}
/**
* @param itemId
* The ticket id to make checks on.
* @return true if the passed item belongs to a castle which has reached its limit of number of dropped tickets.
*/
public boolean isAtCasleLimit(int itemId)
{
int castleId = getTicketCastleId(itemId);
if (castleId <= 0)
return true;
int limit = MERCS_MAX_PER_CASTLE[castleId - 1];
if (limit <= 0)
return true;
int count = 0;
for (L2ItemInstance ticket : _droppedTickets)
{
if ((ticket != null) && (getTicketCastleId(ticket.getItemId()) == castleId))
count++;
}
if (count >= limit)
return true;
return false;
}
public int getMaxAllowedMerc(int castleId)
{
return MERCS_MAX_PER_CASTLE[castleId - 1];
}
public boolean isTooCloseToAnotherTicket(int x, int y, int z)
{
for (L2ItemInstance item : _droppedTickets)
{
double dx = x - item.getX();
double dy = y - item.getY();
double dz = z - item.getZ();
if ((dx * dx + dy * dy + dz * dz) < 25 * 25)
return true;
}
return false;
}
/**
* Adda mercenary ticket.
* <ul>
* <li>find the npc that needs to be saved in the mercenary spawns, given this item</li>
* <li>Use the passed character's location info to add the spawn</li>
* <li>create a copy of the item to drop in the world returns the id of the mercenary npc that was added to the spawn returns -1 if this
* fails.</li>
* </ul>
*
* @param itemId
* @param activeChar
* @param messages
* @return
*/
public int addTicket(int itemId, L2PcInstance activeChar, String[] messages)
{
int x = activeChar.getX();
int y = activeChar.getY();
int z = activeChar.getZ();
int heading = activeChar.getHeading();
Castle castle = CastleManager.getInstance().getCastle(activeChar);
if (castle == null) // this should never happen at this point
return -1;
for (int i = 0; i < ITEM_IDS.length; i++)
{
if (ITEM_IDS[i] == itemId) // Find the index of the item used
{
spawnMercenary(NPC_IDS[i], x, y, z, 3000, messages, 0);
// Hire merc for this castle. NpcId is at the same index as the item used.
castle.getSiege().getSiegeGuardManager().hireMerc(x, y, z, heading, NPC_IDS[i]);
// create the ticket in the gameworld
L2ItemInstance dropticket = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
dropticket.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
dropticket.dropMe(null, x, y, z);
dropticket.setDropTime(0); // avoids it from beeing removed by the auto item destroyer
L2World.getInstance().storeObject(dropticket); // add to the world
// and keep track of this ticket in the list
_droppedTickets.add(dropticket);
return NPC_IDS[i];
}
}
return -1;
}
private static void spawnMercenary(int npcId, int x, int y, int z, int despawnDelay, String[] messages, int chatDelay)
{
L2NpcTemplate template = NpcTable.getInstance().getTemplate(npcId);
if (template != null)
{
final L2SiegeGuardInstance npc = new L2SiegeGuardInstance(IdFactory.getInstance().getNextId(), template);
npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp());
npc.setDecayed(false);
npc.spawnMe(x, y, (z + 20));
if (messages != null && messages.length > 0)
AutoChatHandler.getInstance().registerChat(npc, messages, chatDelay);
if (despawnDelay > 0)
npc.scheduleDespawn(despawnDelay);
}
}
/**
* Delete all tickets from a castle; remove the items from the world and remove references to them from this class
*
* @param castleId
*/
public void deleteTickets(int castleId)
{
Iterator<L2ItemInstance> it = _droppedTickets.iterator();
while (it.hasNext())
{
L2ItemInstance item = it.next();
if ((item != null) && (getTicketCastleId(item.getItemId()) == castleId))
{
item.decayMe();
L2World.getInstance().removeObject(item);
it.remove();
}
}
}
/**
* remove a single ticket and its associated spawn from the world (used when the castle lord picks up a ticket, for example)
*
* @param item
*/
public void removeTicket(L2ItemInstance item)
{
int itemId = item.getItemId();
int npcId = -1;
// find the FIRST ticket itemId with spawns the saved NPC in the saved location
for (int i = 0; i < ITEM_IDS.length; i++)
if (ITEM_IDS[i] == itemId) // Find the index of the item used
{
npcId = NPC_IDS[i];
break;
}
// find the castle where this item is
Castle castle = CastleManager.getInstance().getCastleById(getTicketCastleId(itemId));
if (npcId > 0 && castle != null)
{
(new SiegeGuardManager(castle)).removeMerc(npcId, item.getX(), item.getY(), item.getZ());
}
_droppedTickets.remove(item);
}
public int[] getItemIds()
{
return ITEM_IDS;
}
private static class SingletonHolder
{
protected static final MercTicketManager _instance = new MercTicketManager();
}
}