/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* http://www.gnu.org/copyleft/gpl.html
*/
package com.l2jfrozen.gameserver.managers;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.logging.Logger;
import javolution.util.FastList;
import com.l2jfrozen.gameserver.datatables.sql.NpcTable;
import com.l2jfrozen.gameserver.handler.AutoChatHandler;
import com.l2jfrozen.gameserver.idfactory.IdFactory;
import com.l2jfrozen.gameserver.model.L2World;
import com.l2jfrozen.gameserver.model.actor.instance.L2ItemInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2PcInstance;
import com.l2jfrozen.gameserver.model.actor.instance.L2SiegeGuardInstance;
import com.l2jfrozen.gameserver.model.entity.siege.Castle;
import com.l2jfrozen.gameserver.templates.L2NpcTemplate;
import com.l2jfrozen.gameserver.thread.ThreadPoolManager;
import com.l2jfrozen.util.CloseUtil;
import com.l2jfrozen.util.database.L2DatabaseFactory;
/**
* @author yellowperil & Fulminus 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. These tickets (aka badges) need to
* be readded 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. 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.
*/
public class MercTicketManager
{
protected static Logger _log = Logger.getLogger(MercTicketManager.class.getName());
// =========================================================
// =========================================================
// Data Field
private List<L2ItemInstance> _droppedTickets = new FastList<L2ItemInstance>();
public static final MercTicketManager getInstance()
{
return SingletonHolder._instance;
}
public MercTicketManager(){
_log.info("Initializing MercTicketManager");
_droppedTickets.clear();
load();
}
//TODO move all these values into siege.properties
// max tickets per merc type = 10 + (castleid * 2)?
// max ticker 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
};
// =========================================================
// Method - Public
// returns 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()
{
getDroppedTickets().clear();
load();
}
// =========================================================
// Method - Private
private final void load()
{
Connection con = null;
// load merc tickets into the world
try
{
PreparedStatement statement;
ResultSet rs;
con = L2DatabaseFactory.getInstance().getConnection(false);
statement = con.prepareStatement("SELECT * FROM castle_siege_guards Where isHired = 1");
rs = statement.executeQuery();
int npcId;
int itemId;
int x, y, z;
// start index to begin the search for the itemId corresponding to this NPC
// this will help with:
// 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.VOID);
dropticket.dropMe(null, x, y, z);
dropticket.setDropTime(0); //avoids it from beeing removed by the auto item destroyer
L2World.getInstance().storeObject(dropticket);
getDroppedTickets().add(dropticket);
dropticket = null;
}
break;
}
castle = null;
}
statement.close();
statement = null;
rs.close();
rs = null;
_log.info("Loaded: " + getDroppedTickets().size() + " Mercenary Tickets");
}
catch(Exception e)
{
_log.info("Exception: loadMercenaryData(): " + e.getMessage());
e.printStackTrace();
}
finally
{
CloseUtil.close(con);
}
}
// =========================================================
// Property - Public
/**
* Checks if the passed item has reached the limit of number of dropped tickets that this SPECIFIC item may have in
* its castle
* @param itemId
* @return
*/
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;
L2ItemInstance ticket;
for(int i = 0; i < getDroppedTickets().size(); i++)
{
ticket = getDroppedTickets().get(i);
if(ticket != null && ticket.getItemId() == itemId)
{
count++;
}
}
ticket = null;
if(count >= limit)
return true;
return false;
}
/**
* Checks if the passed item belongs to a castle which has reached its limit of number of dropped tickets.
* @param itemId
* @return
*/
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;
L2ItemInstance ticket;
for(int i = 0; i < getDroppedTickets().size(); i++)
{
ticket = getDroppedTickets().get(i);
if(ticket != null && getTicketCastleId(ticket.getItemId()) == castleId)
{
count++;
}
}
ticket = null;
if(count >= limit)
return true;
return false;
}
/**
* addTicket actions 1) find the npc that needs to be saved in the mercenary spawns, given this item 2) Use the
* passed character's location info to add the spawn 3) 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.
* @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;
//check if this item can be added here
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 caslte. 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);
dropticket = null;
return NPC_IDS[i];
}
}
castle = null;
return -1;
}
private 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)
{
ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() {
@Override
public void run()
{
npc.deleteMe();
}
}, despawnDelay);
}
}
template = null;
}
/**
* 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)
{
int i = 0;
while(i < getDroppedTickets().size())
{
L2ItemInstance item = getDroppedTickets().get(i);
if(item != null && getTicketCastleId(item.getItemId()) == castleId)
{
item.decayMe();
L2World.getInstance().removeObject(item);
// remove from the list
getDroppedTickets().remove(i);
}
else
{
i++;
}
item = null;
}
}
/**
* 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());
}
castle = null;
getDroppedTickets().remove(item);
}
public int[] getItemIds()
{
return ITEM_IDS;
}
public final List<L2ItemInstance> getDroppedTickets()
{
return _droppedTickets;
}
private static class SingletonHolder
{
protected static final MercTicketManager _instance = new MercTicketManager();
}
}