Merge branch 'master' into loot

This commit is contained in:
KimLS 2014-10-02 10:16:15 -07:00
commit 54fae508c5
31 changed files with 1458 additions and 1503 deletions

View File

@ -1,5 +1,12 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) EQEMu Changelog (Started on Sept 24, 2003 15:50)
------------------------------------------------------- -------------------------------------------------------
== 10/01/2014 ==
Kayen: Exported to PERL $client->SendColoredText(color, msg)
demonstar55: Exported SendColoredText to lua
== 09/30/2014 ==
Uleat: Implemented click-casting from bag slots for clients that natively support it (RoF)
== 09/28/2014 == == 09/28/2014 ==
demonstar55: Add support for post June 18, 2014 Hundred Hands Effect spells (they changed the formula and stuff) demonstar55: Add support for post June 18, 2014 Hundred Hands Effect spells (they changed the formula and stuff)
set Spells:Jun182014HundredHandsRevamp to true if you're using a spell file from June 18, 2014+ set Spells:Jun182014HundredHandsRevamp to true if you're using a spell file from June 18, 2014+

View File

@ -17,7 +17,8 @@ static const uint32 BIT_RoFAndLater = 0xFFFFFFE0;
static const uint32 BIT_RoF2AndLater = 0xFFFFFFC0; static const uint32 BIT_RoF2AndLater = 0xFFFFFFC0;
static const uint32 BIT_AllClients = 0xFFFFFFFF; static const uint32 BIT_AllClients = 0xFFFFFFFF;
typedef enum { typedef enum
{
EQClientUnknown = 0, EQClientUnknown = 0,
EQClient62, // Build: 'Aug 4 2005 15:40:59' EQClient62, // Build: 'Aug 4 2005 15:40:59'
EQClientTitanium, // Build: 'Oct 31 2005 10:33:37' EQClientTitanium, // Build: 'Oct 31 2005 10:33:37'
@ -26,17 +27,50 @@ typedef enum {
EQClientUnderfoot, // Build: 'Jun 8 2010 16:44:32' EQClientUnderfoot, // Build: 'Jun 8 2010 16:44:32'
EQClientRoF, // Build: 'Dec 10 2012 17:35:44' EQClientRoF, // Build: 'Dec 10 2012 17:35:44'
EQClientRoF2, // Build: 'May 10 2013 23:30:08' EQClientRoF2, // Build: 'May 10 2013 23:30:08'
_EQClientCount, // place new clients before this point (preferably, in release/attribute order) _EQClientCount, // place new clients before this point (preferably, in release/attribute order)
// Values below are not implemented, as yet... // Values below are not implemented, as yet...
EmuNPC = _EQClientCount, EmuNPC = _EQClientCount,
EmuMerc, EmuMerc,
EmuBot, EmuBot,
EmuPet, EmuPet,
_EmuClientCount // array size for EQLimits _EmuClientCount // array size for EQLimits
} EQClientVersion; } EQClientVersion;
static const char* EQClientVersionName(EQClientVersion version)
{
switch (version)
{
case EQClientUnknown:
return "EQClientUnknown";
case EQClient62:
return "EQClient62";
case EQClientTitanium:
return "EQClientTitanium";
case EQClientSoF:
return "EQClientSoF";
case EQClientSoD:
return "EQClientSoD";
case EQClientUnderfoot:
return "EQClientUnderfoot";
case EQClientRoF:
return "EQClientRoF";
case EQClientRoF2:
return "EQClientRoF2";
case EmuNPC:
return "EmuNPC";
case EmuMerc:
return "EmuMerc";
case EmuBot:
return "EmuBot";
case EmuPet:
return "EmuPet";
default:
return "ERROR: Invalid EQClientVersion";
};
}
#endif /* CLIENTVERSIONS_H */ #endif /* CLIENTVERSIONS_H */

View File

@ -254,6 +254,7 @@ N(OP_ItemLinkText),
N(OP_ItemName), N(OP_ItemName),
N(OP_ItemPacket), N(OP_ItemPacket),
N(OP_ItemPreview), N(OP_ItemPreview),
N(OP_ItemRecastDelay),
N(OP_ItemVerifyReply), N(OP_ItemVerifyReply),
N(OP_ItemVerifyRequest), N(OP_ItemVerifyRequest),
N(OP_ItemViewUnknown), N(OP_ItemViewUnknown),

View File

@ -1024,6 +1024,26 @@ bool EQLimits::AllowsEmptyBagInBag(uint32 version) {
//return local[ValidateMobVersion(version)]; //return local[ValidateMobVersion(version)];
} }
bool EQLimits::AllowsClickCastFromBag(uint32 version) {
static const bool local[_EmuClientCount] = {
/*Unknown*/ false,
/*62*/ Client62::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*Titanium*/ Titanium::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*SoF*/ SoF::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*SoD*/ SoD::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*Underfoot*/ Underfoot::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*RoF*/ RoF::limits::ALLOWS_CLICK_CAST_FROM_BAG,
/*RoF2*/ false,
/*NPC*/ false,
/*Merc*/ false,
/*Bot*/ false,
/*Pet*/ false
};
return local[ValidateMobVersion(version)];
}
// items // items
uint16 EQLimits::ItemCommonSize(uint32 version) { uint16 EQLimits::ItemCommonSize(uint32 version) {
static const uint16 local[_EmuClientCount] = { static const uint16 local[_EmuClientCount] = {

View File

@ -184,6 +184,7 @@ public:
static uint64 CursorBitmask(uint32 version); static uint64 CursorBitmask(uint32 version);
static bool AllowsEmptyBagInBag(uint32 version); static bool AllowsEmptyBagInBag(uint32 version);
static bool AllowsClickCastFromBag(uint32 version);
// items // items
static uint16 ItemCommonSize(uint32 version); static uint16 ItemCommonSize(uint32 version);

View File

@ -4264,6 +4264,13 @@ struct ItemVerifyReply_Struct {
/*012*/ /*012*/
}; };
struct ItemRecastDelay_Struct {
/*000*/ uint32 recast_delay; // in seconds
/*004*/ uint32 recast_type;
/*008*/ uint32 unknown008;
/*012*/
};
/** /**
* Shroud yourself. For yourself shrouding, this has your spawnId, spawnStruct, * Shroud yourself. For yourself shrouding, this has your spawnId, spawnStruct,
* bits of your charProfileStruct (no checksum, then charProfile up till * bits of your charProfileStruct (no checksum, then charProfile up till

View File

@ -911,6 +911,30 @@ bool Inventory::CanItemFitInContainer(const Item_Struct *ItemToTry, const Item_S
return true; return true;
} }
bool Inventory::SupportsClickCasting(int16 slot_id)
{
// there are a few non-potion items that identify as ItemTypePotion..so, we still need to ubiquitously include the equipment range
if ((uint16)slot_id <= EmuConstants::GENERAL_END || slot_id == MainPowerSource)
{
return true;
}
else if (slot_id >= EmuConstants::GENERAL_BAGS_BEGIN && slot_id <= EmuConstants::GENERAL_BAGS_END)
{
if (EQLimits::AllowsClickCastFromBag(m_version))
return true;
}
return false;
}
bool Inventory::SupportsPotionBeltCasting(int16 slot_id)
{
if ((uint16)slot_id <= EmuConstants::GENERAL_END || slot_id == MainPowerSource || (slot_id >= EmuConstants::GENERAL_BAGS_BEGIN && slot_id <= EmuConstants::GENERAL_BAGS_END))
return true;
return false;
}
// Test whether a given slot can support a container item // Test whether a given slot can support a container item
bool Inventory::SupportsContainers(int16 slot_id) bool Inventory::SupportsContainers(int16 slot_id)
{ {

View File

@ -121,8 +121,22 @@ public:
// Public Methods // Public Methods
/////////////////////////////// ///////////////////////////////
Inventory() { m_version = EQClientUnknown; m_versionset = false; }
~Inventory(); ~Inventory();
// Inventory v2 creep
bool SetInventoryVersion(EQClientVersion version) {
if (!m_versionset) {
m_version = version;
return (m_versionset = true);
}
else {
return false;
}
}
EQClientVersion GetInventoryVersion() { return m_version; }
static void CleanDirty(); static void CleanDirty();
static void MarkDirty(ItemInst *inst); static void MarkDirty(ItemInst *inst);
@ -132,7 +146,7 @@ public:
inline iter_queue cursor_begin() { return m_cursor.begin(); } inline iter_queue cursor_begin() { return m_cursor.begin(); }
inline iter_queue cursor_end() { return m_cursor.end(); } inline iter_queue cursor_end() { return m_cursor.end(); }
inline bool CursorEmpty() { return (m_cursor.size() == 0); } inline bool CursorEmpty() { return (m_cursor.size() == 0); }
// Retrieve a read-only item from inventory // Retrieve a read-only item from inventory
inline const ItemInst* operator[](int16 slot_id) const { return GetItem(slot_id); } inline const ItemInst* operator[](int16 slot_id) const { return GetItem(slot_id); }
@ -183,6 +197,10 @@ public:
static bool CanItemFitInContainer(const Item_Struct *ItemToTry, const Item_Struct *Container); static bool CanItemFitInContainer(const Item_Struct *ItemToTry, const Item_Struct *Container);
// Test for valid inventory casting slot
bool SupportsClickCasting(int16 slot_id);
bool SupportsPotionBeltCasting(int16 slot_id);
// Test whether a given slot can support a container item // Test whether a given slot can support a container item
static bool SupportsContainers(int16 slot_id); static bool SupportsContainers(int16 slot_id);
@ -229,7 +247,12 @@ protected:
std::map<int16, ItemInst*> m_bank; // Items in character bank std::map<int16, ItemInst*> m_bank; // Items in character bank
std::map<int16, ItemInst*> m_shbank; // Items in character shared bank std::map<int16, ItemInst*> m_shbank; // Items in character shared bank
std::map<int16, ItemInst*> m_trade; // Items in a trade session std::map<int16, ItemInst*> m_trade; // Items in a trade session
ItemInstQueue m_cursor; // Items on cursor: FIFO ItemInstQueue m_cursor; // Items on cursor: FIFO
private:
// Active inventory version
EQClientVersion m_version;
bool m_versionset;
}; };
class SharedDatabase; class SharedDatabase;

View File

@ -306,6 +306,7 @@ OUTz(OP_FinishWindow2);
//OUTv(OP_AdventureMerchantResponse, strlen(msg)+2); //OUTv(OP_AdventureMerchantResponse, strlen(msg)+2);
OUTv(OP_ItemPacket, ItemPacket_Struct); OUTv(OP_ItemPacket, ItemPacket_Struct);
OUTv(OP_ColoredText, ColoredText_Struct); OUTv(OP_ColoredText, ColoredText_Struct);
OUTv(OP_ItemRecastDelay, ItemRecastDelay_Struct);
OUTv(OP_FormattedMessage, FormattedMessage_Struct); OUTv(OP_FormattedMessage, FormattedMessage_Struct);
OUTv(OP_GuildMemberList, uint32); //variable length, but nasty OUTv(OP_GuildMemberList, uint32); //variable length, but nasty
OUTv(OP_InterruptCast, InterruptCast_Struct); OUTv(OP_InterruptCast, InterruptCast_Struct);

View File

@ -180,6 +180,7 @@ namespace Client62 {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = false; static const bool ALLOWS_EMPTY_BAG_IN_BAG = false;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = false;
static const bool COIN_HAS_WEIGHT = true; static const bool COIN_HAS_WEIGHT = true;
} }

View File

@ -184,6 +184,7 @@ namespace RoF {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = true; static const bool ALLOWS_EMPTY_BAG_IN_BAG = true;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = true;
static const bool COIN_HAS_WEIGHT = false; static const bool COIN_HAS_WEIGHT = false;
} }

View File

@ -181,6 +181,7 @@ namespace SoD {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = false; static const bool ALLOWS_EMPTY_BAG_IN_BAG = false;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = false;
static const bool COIN_HAS_WEIGHT = false; static const bool COIN_HAS_WEIGHT = false;
} }

View File

@ -181,6 +181,7 @@ namespace SoF {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = false; static const bool ALLOWS_EMPTY_BAG_IN_BAG = false;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = false;
static const bool COIN_HAS_WEIGHT = true; static const bool COIN_HAS_WEIGHT = true;
} }

View File

@ -180,6 +180,7 @@ namespace Titanium {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = false; static const bool ALLOWS_EMPTY_BAG_IN_BAG = false;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = false;
static const bool COIN_HAS_WEIGHT = true; static const bool COIN_HAS_WEIGHT = true;
} }

View File

@ -181,6 +181,7 @@ namespace Underfoot {
namespace limits { namespace limits {
static const bool ALLOWS_EMPTY_BAG_IN_BAG = false; static const bool ALLOWS_EMPTY_BAG_IN_BAG = false;
static const bool ALLOWS_CLICK_CAST_FROM_BAG = false;
static const bool COIN_HAS_WEIGHT = false; static const bool COIN_HAS_WEIGHT = false;
} }

View File

@ -523,9 +523,9 @@ void Database::ExpireMail() {
time(nullptr) - RuleI(Mail, ExpireTrash)); time(nullptr) - RuleI(Mail, ExpireTrash));
results = QueryDatabase(query); results = QueryDatabase(query);
if(results.Success()) if(results.Success())
_log(UCS__ERROR, "Error expiring trash messages, %s %s", query.c_str(), results.ErrorMessage().c_str());
else
_log(UCS__INIT, "Expired %i trash messages.", results.RowsAffected()); _log(UCS__INIT, "Expired %i trash messages.", results.RowsAffected());
else
_log(UCS__ERROR, "Error expiring trash messages, %s %s", query.c_str(), results.ErrorMessage().c_str());
} }

View File

@ -653,3 +653,5 @@ OP_RAWOutOfSession=0x0000
# we need to document the differences between these packets to make identifying them easier # we need to document the differences between these packets to make identifying them easier
OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs
OP_InitialHPUpdate=0x0000 OP_InitialHPUpdate=0x0000
OP_ItemRecastDelay=0x57ed

View File

@ -660,3 +660,5 @@ OP_RAWOutOfSession=0x0000 #
# we need to document the differences between these packets to make identifying them easier # we need to document the differences between these packets to make identifying them easier
OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs
OP_InitialHPUpdate=0x0000 # OP_InitialHPUpdate=0x0000 #
OP_ItemRecastDelay=0x15c4

View File

@ -654,3 +654,5 @@ OP_RAWOutOfSession=0x0000 #
# we need to document the differences between these packets to make identifying them easier # we need to document the differences between these packets to make identifying them easier
OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs
OP_InitialHPUpdate=0x0000 # OP_InitialHPUpdate=0x0000 #
OP_ItemRecastDelay=0x82d7

File diff suppressed because it is too large Load Diff

View File

@ -1307,7 +1307,13 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
conn_state = ReceivedZoneEntry; conn_state = ReceivedZoneEntry;
ClientVersion = Connection()->ClientVersion(); ClientVersion = Connection()->ClientVersion();
ClientVersionBit = 1 << (ClientVersion - 1); if (ClientVersion != EQClientUnknown)
ClientVersionBit = 1 << (ClientVersion - 1);
else
ClientVersionBit = 0;
bool siv = m_inv.SetInventoryVersion(ClientVersion);
LogFile->write(EQEMuLog::Debug, "%s inventory version to %s(%i)", (siv ? "Succeeded in setting" : "Failed to set"), EQClientVersionName(ClientVersion), ClientVersion);
/* Antighost code /* Antighost code
tmp var is so the search doesnt find this object tmp var is so the search doesnt find this object
@ -3999,15 +4005,16 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
else if ((castspell->slot == USE_ITEM_SPELL_SLOT) || (castspell->slot == POTION_BELT_SPELL_SLOT)) // ITEM or POTION cast else if ((castspell->slot == USE_ITEM_SPELL_SLOT) || (castspell->slot == POTION_BELT_SPELL_SLOT)) // ITEM or POTION cast
{ {
//discipline, using the item spell slot //discipline, using the item spell slot
if (castspell->inventoryslot == 0xFFFFFFFF) { if (castspell->inventoryslot == INVALID_INDEX) {
if (!UseDiscipline(castspell->spell_id, castspell->target_id)) { if (!UseDiscipline(castspell->spell_id, castspell->target_id)) {
LogFile->write(EQEMuLog::Debug, "Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id); LogFile->write(EQEMuLog::Debug, "Unknown ability being used by %s, spell being cast is: %i\n", GetName(), castspell->spell_id);
InterruptSpell(castspell->spell_id); InterruptSpell(castspell->spell_id);
} }
return; return;
} }
else if ((castspell->inventoryslot <= EmuConstants::GENERAL_END) || (castspell->slot == POTION_BELT_SPELL_SLOT)) // sanity check else if (m_inv.SupportsClickCasting(castspell->inventoryslot) || (castspell->slot == POTION_BELT_SPELL_SLOT)) // sanity check
{ {
// packet field types will be reviewed as packet transistions occur -U
const ItemInst* inst = m_inv[castspell->inventoryslot]; //slot values are int16, need to check packet on this field const ItemInst* inst = m_inv[castspell->inventoryslot]; //slot values are int16, need to check packet on this field
//bool cancast = true; //bool cancast = true;
if (inst && inst->IsType(ItemClassCommon)) if (inst && inst->IsType(ItemClassCommon))
@ -8555,7 +8562,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
LogFile->write(EQEMuLog::Debug, "OP ItemVerifyRequest: spell=%i, target=%i, inv=%i", spell_id, target_id, slot_id); LogFile->write(EQEMuLog::Debug, "OP ItemVerifyRequest: spell=%i, target=%i, inv=%i", spell_id, target_id, slot_id);
if ((slot_id < MainCursor) || (slot_id == MainPowerSource) || (slot_id > 250 && slot_id < 331 && ((item->ItemType == ItemTypePotion) || item->PotionBelt))) // sanity check if (m_inv.SupportsClickCasting(slot_id) || ((item->ItemType == ItemTypePotion || item->PotionBelt) && m_inv.SupportsPotionBeltCasting(slot_id))) // sanity check
{ {
ItemInst* p_inst = (ItemInst*)inst; ItemInst* p_inst = (ItemInst*)inst;
@ -10686,7 +10693,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app)
Client *i = entity_list.GetClientByName(ri->player_name); Client *i = entity_list.GetClientByName(ri->player_name);
if (i){ if (i){
if (IsRaidGrouped()){ if (IsRaidGrouped()){
i->Message_StringID(0, 5060); //group failed, must invite members not in raid... i->Message_StringID(0, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid...
return; return;
} }
Raid *r = entity_list.GetRaidByClient(i); Raid *r = entity_list.GetRaidByClient(i);

View File

@ -1239,6 +1239,11 @@ void Lua_Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in
self->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg); self->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg);
} }
void Lua_Client::SendColoredText(uint32 type, std::string msg) {
Lua_Safe_Call_Void();
self->SendColoredText(type, msg);
}
void Lua_Client::PlayMP3(std::string file) void Lua_Client::PlayMP3(std::string file)
{ {
Lua_Safe_Call_Void(); Lua_Safe_Call_Void();
@ -1492,6 +1497,7 @@ luabind::scope lua_register_client() {
.def("SetThirst", (void(Lua_Client::*)(int))&Lua_Client::SetThirst) .def("SetThirst", (void(Lua_Client::*)(int))&Lua_Client::SetThirst)
.def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption) .def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption)
.def("SendMarqueeMessage", (void(Lua_Client::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_Client::SendMarqueeMessage) .def("SendMarqueeMessage", (void(Lua_Client::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_Client::SendMarqueeMessage)
.def("SendColoredText", (void(Lua_Client::*)(uint32, std::string))&Lua_Client::SendColoredText)
.def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3); .def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3);
} }

View File

@ -275,6 +275,7 @@ public:
void SetThirst(int in_thirst); void SetThirst(int in_thirst);
void SetConsumption(int in_hunger, int in_thirst); void SetConsumption(int in_hunger, int in_thirst);
void SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg); void SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg);
void SendColoredText(uint32 type, std::string msg);
void PlayMP3(std::string file); void PlayMP3(std::string file);
}; };

View File

@ -965,240 +965,352 @@ NPC* NPC::SpawnNPC(const char* spawncommand, float in_x, float in_y, float in_z,
} }
} }
uint32 ZoneDatabase::NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn, uint32 extra) { uint32 ZoneDatabase::CreateNewNPCCommand(const char* zone, uint32 zone_version,Client *client, NPC* spawn, uint32 extra) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0; uint32 npc_type_id = 0;
MYSQL_RES *result;
MYSQL_ROW row; if (extra && client && client->GetZoneID())
uint32 tmp = 0; {
uint32 tmp2 = 0; // Set an npc_type ID within the standard range for the current zone if possible (zone_id * 1000)
int starting_npc_id = client->GetZoneID() * 1000;
std::string query = StringFormat("SELECT MAX(id) FROM npc_types WHERE id >= %i AND id < %i",
starting_npc_id, starting_npc_id + 1000);
auto results = QueryDatabase(query);
if (results.Success()) {
if (results.RowCount() != 0)
{
auto row = results.begin();
npc_type_id = atoi(row[0]) + 1;
// Prevent the npc_type id from exceeding the range for this zone
if (npc_type_id >= (starting_npc_id + 1000))
npc_type_id = 0;
}
else // No npc_type IDs set in this range yet
npc_type_id = starting_npc_id;
}
}
char tmpstr[64];
EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr)));
std::string query;
if (npc_type_id)
{
query = StringFormat("INSERT INTO npc_types (id, name, level, race, class, hp, gender, "
"texture, helmtexture, size, loottable_id, merchant_id, face, "
"runspeed, prim_melee_type, sec_melee_type) "
"VALUES(%i, \"%s\" , %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)",
npc_type_id, tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(),
spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(),
spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(),
spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
npc_type_id = results.LastInsertedID();
}
else
{
query = StringFormat("INSERT INTO npc_types (name, level, race, class, hp, gender, "
"texture, helmtexture, size, loottable_id, merchant_id, face, "
"runspeed, prim_melee_type, sec_melee_type) "
"VALUES(\"%s\", %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)",
tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(),
spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(),
spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(),
spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
npc_type_id = results.LastInsertedID();
}
if(client)
client->LogSQL(query.c_str());
query = StringFormat("INSERT INTO spawngroup (id, name) VALUES(%i, '%s-%s')", 0, zone, spawn->GetName());
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
uint32 spawngroupid = results.LastInsertedID();
if(client)
client->LogSQL(query.c_str());
query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) "
"VALUES('%s', %u, %f, %f, %f, %i, %f, %i)",
zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), 1200,
spawn->GetHeading(), spawngroupid);
results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
if(client)
client->LogSQL(query.c_str());
query = StringFormat("INSERT INTO spawnentry (spawngroupID, npcID, chance) VALUES(%i, %i, %i)",
spawngroupid, npc_type_id, 100);
results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
if(client)
client->LogSQL(query.c_str());
return true;
}
uint32 ZoneDatabase::AddNewNPCSpawnGroupCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 respawnTime) {
uint32 last_insert_id = 0;
std::string query = StringFormat("INSERT INTO spawngroup (name) VALUES('%s%s%i')",
zone, spawn->GetName(), Timer::GetCurrentTime());
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return 0;
}
last_insert_id = results.LastInsertedID();
if(client)
client->LogSQL(query.c_str());
uint32 respawntime = 0;
uint32 spawnid = 0;
if (respawnTime)
respawntime = respawnTime;
else if(spawn->respawn2 && spawn->respawn2->RespawnTimer() != 0)
respawntime = spawn->respawn2->RespawnTimer();
else
respawntime = 1200;
query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) "
"VALUES('%s', %u, %f, %f, %f, %i, %f, %i)",
zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), respawntime,
spawn->GetHeading(), last_insert_id);
results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return 0;
}
spawnid = results.LastInsertedID();
if(client)
client->LogSQL(query.c_str());
query = StringFormat("INSERT INTO spawnentry (spawngroupID, npcID, chance) VALUES(%i, %i, %i)",
last_insert_id, spawn->GetNPCTypeID(), 100);
results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "CreateNewNPCSpawnGroupCommand Error: %s %s", query.c_str(), results.ErrorMessage().c_str());
return 0;
}
if(client)
client->LogSQL(query.c_str());
return spawnid;
}
uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client *client, NPC* spawn) {
std::string query = StringFormat("UPDATE npc_types SET name = \"%s\", level = %i, race = %i, class = %i, "
"hp = %i, gender = %i, texture = %i, helmtexture = %i, size = %i, "
"loottable_id = %i, merchant_id = %i, face = %i, WHERE id = %i",
spawn->GetName(), spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(),
spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(),
spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(),
spawn->MerchantType, spawn->GetNPCTypeID());
auto results = QueryDatabase(query);
if (!results.Success() && client)
client->LogSQL(query.c_str());
return results.Success() == true? 1: 0;
}
uint32 ZoneDatabase::DeleteSpawnLeaveInNPCTypeTable(const char* zone, Client *client, NPC* spawn) {
uint32 id = 0;
uint32 spawngroupID = 0;
std::string query = StringFormat("SELECT id, spawngroupID FROM spawn2 WHERE "
"zone='%s' AND spawngroupID=%i", zone, spawn->GetSp2());
auto results = QueryDatabase(query);
if (!results.Success())
return 0;
if (results.RowCount() == 0)
return 0;
auto row = results.begin();
if (row[0])
id = atoi(row[0]);
if (row[1])
spawngroupID = atoi(row[1]);
query = StringFormat("DELETE FROM spawn2 WHERE id = '%i'", id);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
query = StringFormat("DELETE FROM spawngroup WHERE id = '%i'", spawngroupID);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
query = StringFormat("DELETE FROM spawnentry WHERE spawngroupID = '%i'", spawngroupID);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
return 1;
}
uint32 ZoneDatabase::DeleteSpawnRemoveFromNPCTypeTable(const char* zone, uint32 zone_version, Client *client, NPC* spawn) {
uint32 id = 0;
uint32 spawngroupID = 0;
std::string query = StringFormat("SELECT id, spawngroupID FROM spawn2 WHERE zone = '%s' "
"AND version = %u AND spawngroupID = %i",
zone, zone_version, spawn->GetSp2());
auto results = QueryDatabase(query);
if (!results.Success())
return 0;
if (results.RowCount() == 0)
return 0;
auto row = results.begin();
if (row[0])
id = atoi(row[0]);
if (row[1])
spawngroupID = atoi(row[1]);
query = StringFormat("DELETE FROM spawn2 WHERE id = '%i'", id);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
query = StringFormat("DELETE FROM spawngroup WHERE id = '%i'", spawngroupID);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
query = StringFormat("DELETE FROM spawnentry WHERE spawngroupID = '%i'", spawngroupID);
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
query = StringFormat("DELETE FROM npc_types WHERE id = '%i'", spawn->GetNPCTypeID());
results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
return 1;
}
uint32 ZoneDatabase::AddSpawnFromSpawnGroup(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID) {
uint32 last_insert_id = 0; uint32 last_insert_id = 0;
std::string query = StringFormat("INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) "
"VALUES('%s', %u, %f, %f, %f, %i, %f, %i)",
zone, zone_version, client->GetX(), client->GetY(), client->GetZ(),
120, client->GetHeading(), spawnGroupID);
auto results = QueryDatabase(query);
if (!results.Success())
return 0;
if(client)
client->LogSQL(query.c_str());
return 1;
}
uint32 ZoneDatabase::AddNPCTypes(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID) {
uint32 npc_type_id;
char numberlessName[64];
EntityList::RemoveNumbers(strn0cpy(numberlessName, spawn->GetName(), sizeof(numberlessName)));
std::string query = StringFormat("INSERT INTO npc_types (name, level, race, class, hp, gender, "
"texture, helmtexture, size, loottable_id, merchant_id, face, "
"runspeed, prim_melee_type, sec_melee_type) "
"VALUES(\"%s\", %i, %i, %i, %i, %i, %i, %i, %f, %i, %i, %i, %f, %i, %i)",
numberlessName, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(),
spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(),
spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(),
spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28);
auto results = QueryDatabase(query);
if (!results.Success())
return 0;
npc_type_id = results.LastInsertedID();
if(client)
client->LogSQL(query.c_str());
if(client)
client->Message(0, "%s npc_type ID %i created successfully!", numberlessName, npc_type_id);
return 1;
}
uint32 ZoneDatabase::NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn, uint32 extra) {
switch (command) { switch (command) {
case 0: { // Create a new NPC and add all spawn related data case 0: { // Create a new NPC and add all spawn related data
uint32 npc_type_id = 0; return CreateNewNPCCommand(zone, zone_version, c, spawn, extra);
uint32 spawngroupid;
if (extra && c && c->GetZoneID())
{
// Set an npc_type ID within the standard range for the current zone if possible (zone_id * 1000)
int starting_npc_id = c->GetZoneID() * 1000;
if (RunQuery(query, MakeAnyLenString(&query, "SELECT MAX(id) FROM npc_types WHERE id >= %i AND id < %i", starting_npc_id, (starting_npc_id + 1000)), errbuf, &result)) {
row = mysql_fetch_row(result);
if(row)
{
if (row[0])
{
npc_type_id = atoi(row[0]) + 1;
// Prevent the npc_type id from exceeding the range for this zone
if (npc_type_id >= (starting_npc_id + 1000))
{
npc_type_id = 0;
}
}
else
{
// row[0] is nullptr - No npc_type IDs set in this range yet
npc_type_id = starting_npc_id;
}
}
safe_delete_array(query);
mysql_free_result(result);
}
}
char tmpstr[64];
EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr)));
if (npc_type_id)
{
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (id, name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(%i,\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", npc_type_id, tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
}
else
{
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
}
if(c) c->LogSQL(query);
safe_delete_array(query);
snprintf(tmpstr, sizeof(tmpstr), "%s-%s", zone, spawn->GetName());
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawngroup (id, name) values(%i, '%s')", tmp, tmpstr), errbuf, 0, 0, &spawngroupid)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), 1200, spawn->GetHeading(), spawngroupid), errbuf, 0, 0, &tmp)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawnentry (spawngroupID, npcID, chance) values(%i, %i, %i)", spawngroupid, npc_type_id, 100), errbuf, 0)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
return true;
break;
} }
case 1:{ // Add new spawn group and spawn point for an existing NPC Type ID case 1:{ // Add new spawn group and spawn point for an existing NPC Type ID
tmp2 = spawn->GetNPCTypeID(); return AddNewNPCSpawnGroupCommand(zone, zone_version, c, spawn, extra);
char tmpstr[64];
snprintf(tmpstr, sizeof(tmpstr), "%s%s%i", zone, spawn->GetName(),Timer::GetCurrentTime());
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawngroup (name) values('%s')", tmpstr), errbuf, 0, 0, &last_insert_id)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
uint32 respawntime = 0;
uint32 spawnid = 0;
if (extra)
respawntime = extra;
else if(spawn->respawn2 && spawn->respawn2->RespawnTimer() != 0)
respawntime = spawn->respawn2->RespawnTimer();
else
respawntime = 1200;
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, spawn->GetX(), spawn->GetY(), spawn->GetZ(), respawntime, spawn->GetHeading(), last_insert_id), errbuf, 0, 0, &spawnid)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawnentry (spawngroupID, npcID, chance) values(%i, %i, %i)", last_insert_id, tmp2, 100), errbuf, 0)) {
LogFile->write(EQEMuLog::Error, "NPCSpawnDB Error: %s %s", query, errbuf);
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
return spawnid;
break;
} }
case 2: { // Update npc_type appearance and other data on targeted spawn case 2: { // Update npc_type appearance and other data on targeted spawn
if (!RunQuery(query, MakeAnyLenString(&query, "UPDATE npc_types SET name=\"%s\", level=%i, race=%i, class=%i, hp=%i, gender=%i, texture=%i, helmtexture=%i, size=%i, loottable_id=%i, merchant_id=%i, face=%i, WHERE id=%i", spawn->GetName(), spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, spawn->GetNPCTypeID()), errbuf, 0)) { return UpdateNPCTypeAppearance(c, spawn);
if(c) c->LogSQL(query);
safe_delete_array(query);
return true;
}
else {
safe_delete_array(query);
return false;
}
break;
} }
case 3: { // delete spawn from spawning, but leave in npc_types table case 3: { // delete spawn from spawning, but leave in npc_types table
if (!RunQuery(query, MakeAnyLenString(&query, "SELECT id,spawngroupID from spawn2 where zone='%s' AND spawngroupID=%i", zone, spawn->GetSp2()), errbuf, &result)) { return DeleteSpawnLeaveInNPCTypeTable(zone, c, spawn);
safe_delete_array(query);
return 0;
}
safe_delete_array(query);
row = mysql_fetch_row(result);
if (row == nullptr) return false;
if (row[0]) tmp = atoi(row[0]);
if (row[1]) tmp2 = atoi(row[1]);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawn2 WHERE id='%i'", tmp), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawngroup WHERE id='%i'", tmp2), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawnentry WHERE spawngroupID='%i'", tmp2), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
return true;
break;
} }
case 4: { //delete spawn from DB (including npc_type) case 4: { //delete spawn from DB (including npc_type)
if (!RunQuery(query, MakeAnyLenString(&query, "SELECT id,spawngroupID from spawn2 where zone='%s' AND version=%u AND spawngroupID=%i", zone, zone_version, spawn->GetSp2()), errbuf, &result)) { return DeleteSpawnRemoveFromNPCTypeTable(zone, zone_version, c, spawn);
safe_delete_array(query);
return(0);
}
safe_delete_array(query);
row = mysql_fetch_row(result);
if (row == nullptr) return false;
if (row[0]) tmp = atoi(row[0]);
if (row[1]) tmp2 = atoi(row[1]);
mysql_free_result(result);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawn2 WHERE id='%i'", tmp), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawngroup WHERE id='%i'", tmp2), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM spawnentry WHERE spawngroupID='%i'", tmp2), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if (!RunQuery(query, MakeAnyLenString(&query, "DELETE FROM npc_types WHERE id='%i'", spawn->GetNPCTypeID()), errbuf,0)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
return true;
break;
} }
case 5: { // add a spawn from spawngroup case 5: { // add a spawn from spawngroup
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO spawn2 (zone, version, x, y, z, respawntime, heading, spawngroupID) values('%s', %u, %f, %f, %f, %i, %f, %i)", zone, zone_version, c->GetX(), c->GetY(), c->GetZ(), 120, c->GetHeading(), extra), errbuf, 0, 0, &tmp)) { return AddSpawnFromSpawnGroup(zone, zone_version, c, spawn, extra);
safe_delete(query); }
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
return true;
break;
}
case 6: { // add npc_type case 6: { // add npc_type
uint32 npc_type_id; return AddNPCTypes(zone, zone_version, c, spawn, extra);
char tmpstr[64];
EntityList::RemoveNumbers(strn0cpy(tmpstr, spawn->GetName(), sizeof(tmpstr)));
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO npc_types (name, level, race, class, hp, gender, texture, helmtexture, size, loottable_id, merchant_id, face, runspeed, prim_melee_type, sec_melee_type) values(\"%s\",%i,%i,%i,%i,%i,%i,%i,%f,%i,%i,%i,%f,%i,%i)", tmpstr, spawn->GetLevel(), spawn->GetRace(), spawn->GetClass(), spawn->GetMaxHP(), spawn->GetGender(), spawn->GetTexture(), spawn->GetHelmTexture(), spawn->GetSize(), spawn->GetLoottableID(), spawn->MerchantType, 0, spawn->GetRunspeed(), 28, 28), errbuf, 0, 0, &npc_type_id)) {
safe_delete(query);
return false;
}
if(c) c->LogSQL(query);
safe_delete_array(query);
if(c) c->Message(0, "%s npc_type ID %i created successfully!", tmpstr, npc_type_id);
return true;
break;
} }
} }
return false; return false;
@ -1743,7 +1855,7 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
else else
ns->spawn.IsMercenary = 0; ns->spawn.IsMercenary = 0;
} }
//Not recommended if using above (However, this will work better on older clients). //Not recommended if using above (However, this will work better on older clients).
if (RuleB(Pets, UnTargetableSwarmPet)) { if (RuleB(Pets, UnTargetableSwarmPet)) {
if(GetOwnerID() || GetSwarmOwner()) { if(GetOwnerID() || GetSwarmOwner()) {

View File

@ -6020,6 +6020,32 @@ XS(XS_Client_SendMarqueeMessage)
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
XS(XS_Client_SendColoredText); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SendColoredText)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: Client::SendColoredText(color, message)");
{
Client * THIS;
uint32 color = (uint32)SvUV(ST(1));
std::string msg = (std::string)SvPV_nolen(ST(2));
dXSTARG;
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->SendColoredText(color, msg);
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
#endif #endif
@ -6259,6 +6285,7 @@ XS(boot_Client)
newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$"); newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$");
newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$"); newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$");
newXSproto(strcpy(buf, "SendMarqueeMessage"), XS_Client_SendMarqueeMessage, file, "$$$$$$$"); newXSproto(strcpy(buf, "SendMarqueeMessage"), XS_Client_SendMarqueeMessage, file, "$$$$$$$");
newXSproto(strcpy(buf, "SendColoredText"), XS_Client_SendColoredText, file, "$$$");
XSRETURN_YES; XSRETURN_YES;
} }

View File

@ -26,6 +26,7 @@
#include "string_ids.h" #include "string_ids.h"
#include "../common/misc_functions.h" #include "../common/misc_functions.h"
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "../common/string_util.h"
int Mob::GetKickDamage() { int Mob::GetKickDamage() {
@ -346,15 +347,23 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) {
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
//Live AA - Technique of Master Wu //Live AA - Technique of Master Wu
uint16 bDoubleSpecialAttack = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if (bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > MakeRandomInt(0, 99))) { if (wuchance) {
if (wuchance >= 100 || wuchance > MakeRandomInt(0, 99)) {
int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick };
MonkSpecialAttack(GetTarget(), MonkSPA[MakeRandomInt(0, 4)]); int extra = 1;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
// always 1/4 of the double attack chance, 25% at rank 5 (100/4) if (wuchance / 4 > MakeRandomInt(0, 99))
if ((bDoubleSpecialAttack / 4) > MakeRandomInt(0, 99)) extra++;
MonkSpecialAttack(GetTarget(), MonkSPA[MakeRandomInt(0, 4)]); // They didn't add a string ID for this.
std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
// live uses 400 here -- not sure if it's the best for all clients though
SendColoredText(400, msg);
while (extra) {
MonkSpecialAttack(GetTarget(), MonkSPA[MakeRandomInt(0, 4)]);
extra--;
}
}
} }
if(ReuseTime < 100) { if(ReuseTime < 100) {
@ -1743,18 +1752,21 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte)
return; return;
//Live AA - Technique of Master Wu //Live AA - Technique of Master Wu
uint16 bDoubleSpecialAttack = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if( bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > MakeRandomInt(0,100))) { if (wuchance) {
if (wuchance >= 100 || wuchance > MakeRandomInt(0, 99)) {
int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick };
MonkSpecialAttack(ca_target, MonkSPA[MakeRandomInt(0,4)]); int extra = 1;
if (wuchance / 4 > MakeRandomInt(0, 99))
int TripleChance = 25; extra++;
if (bDoubleSpecialAttack > 100) // They didn't add a string ID for this.
TripleChance += TripleChance*(100-bDoubleSpecialAttack)/100; std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
// live uses 400 here -- not sure if it's the best for all clients though
if(TripleChance > MakeRandomInt(0,100)) { SendColoredText(400, msg);
MonkSpecialAttack(ca_target, MonkSPA[MakeRandomInt(0,4)]); while (extra) {
MonkSpecialAttack(ca_target, MonkSPA[MakeRandomInt(0, 4)]);
extra--;
}
} }
} }
} }

View File

@ -3205,55 +3205,55 @@ snare has both of them negative, yet their range should work the same:
case 124: // check sign case 124: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += caster_level - 50; result += updownsign * (caster_level - 50);
break; break;
case 125: // check sign case 125: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 2 * (caster_level - 50); result += updownsign * 2 * (caster_level - 50);
break; break;
case 126: // check sign case 126: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 3 * (caster_level - 50); result += updownsign * 3 * (caster_level - 50);
break; break;
case 127: // check sign case 127: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 4 * (caster_level - 50); result += updownsign * 4 * (caster_level - 50);
break; break;
case 128: // check sign case 128: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 5 * (caster_level - 50); result += updownsign * 5 * (caster_level - 50);
break; break;
case 129: // check sign case 129: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 10 * (caster_level - 50); result += updownsign * 10 * (caster_level - 50);
break; break;
case 130: // check sign case 130: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 15 * (caster_level - 50); result += updownsign * 15 * (caster_level - 50);
break; break;
case 131: // check sign case 131: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 20 * (caster_level - 50); result += updownsign * 20 * (caster_level - 50);
break; break;
case 132: // check sign case 132: // check sign
result = ubase; result = ubase;
if (caster_level > 50) if (caster_level > 50)
result += 25 * (caster_level - 50); result += updownsign * 25 * (caster_level - 50);
break; break;
case 137: // used in berserker AA desperation case 137: // used in berserker AA desperation

View File

@ -484,6 +484,19 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot,
return false; return false;
} }
// This needs a bit more work for saving timer to PP for zoning etc
if (IsClient() && item_slot != INVALID_INDEX && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT))) {
ItemInst *itm = CastToClient()->GetInv().GetItem(item_slot);
if (itm && itm->GetItem()->RecastDelay) {
outapp = new EQApplicationPacket(OP_ItemRecastDelay, sizeof(ItemRecastDelay_Struct));
ItemRecastDelay_Struct *ird = (ItemRecastDelay_Struct *)outapp->pBuffer;
ird->recast_delay = itm->GetItem()->RecastDelay;
ird->recast_type = itm->GetItem()->RecastType;
CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
}
}
return(true); return(true);
} }

View File

@ -275,6 +275,7 @@
#define TOLD_NOT_ONLINE 5046 //%1 is not online at this time. #define TOLD_NOT_ONLINE 5046 //%1 is not online at this time.
#define PETITION_NO_DELETE 5053 //You do not have a petition in the queue. #define PETITION_NO_DELETE 5053 //You do not have a petition in the queue.
#define PETITION_DELETED 5054 //Your petition was successfully deleted. #define PETITION_DELETED 5054 //Your petition was successfully deleted.
#define ALREADY_IN_RAID 5060 //%1 is already in a raid.
#define GAIN_RAIDEXP 5085 //You gained raid experience! #define GAIN_RAIDEXP 5085 //You gained raid experience!
#define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there. #define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there.
#define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure. #define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure.

View File

@ -1615,36 +1615,36 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs,Client* Trader,const EQApplicat
} }
void Client::SendBazaarWelcome(){ void Client::SendBazaarWelcome()
{
const std::string query = "SELECT COUNT(DISTINCT char_id), count(char_id) FROM trader"; const std::string query = "SELECT COUNT(DISTINCT char_id), count(char_id) FROM trader";
auto results = database.QueryDatabase(query); auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() == 1){ if (results.Success() && results.RowCount() == 1){
auto row = results.begin(); auto row = results.begin();
EQApplicationPacket* outapp = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarWelcome_Struct)); EQApplicationPacket* outapp = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarWelcome_Struct));
memset(outapp->pBuffer,0,outapp->size); memset(outapp->pBuffer,0,outapp->size);
BazaarWelcome_Struct* bws = (BazaarWelcome_Struct*)outapp->pBuffer; BazaarWelcome_Struct* bws = (BazaarWelcome_Struct*)outapp->pBuffer;
bws->Beginning.Action = BazaarWelcome; bws->Beginning.Action = BazaarWelcome;
bws->Traders = atoi(row[0]); bws->Traders = atoi(row[0]);
bws->Items = atoi(row[1]); bws->Items = atoi(row[1]);
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
} }
const std::string buyerCountQuery = "SELECT COUNT(DISTINCT charid) FROM buyer"; const std::string buyerCountQuery = "SELECT COUNT(DISTINCT charid) FROM buyer";
results = database.QueryDatabase(query); results = database.QueryDatabase(buyerCountQuery);
if (!results.Success() || results.RowCount() != 1) if (!results.Success() || results.RowCount() != 1)
return; return;
auto row = results.begin(); auto row = results.begin();
Message(10, "There are %i Buyers waiting to purchase your loot. Type /barter to search for them, " Message(10, "There are %i Buyers waiting to purchase your loot. Type /barter to search for them, "
"or use /buyer to set up your own Buy Lines.", atoi(row[0])); "or use /buyer to set up your own Buy Lines.", atoi(row[0]));
} }

View File

@ -376,6 +376,13 @@ public:
/* NPCs */ /* NPCs */
const NPCType* GetNPCType(uint32 id); const NPCType* GetNPCType(uint32 id);
uint32 NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn = 0, uint32 extra = 0); // 0 = Create 1 = Add; 2 = Update; 3 = Remove; 4 = Delete uint32 NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn = 0, uint32 extra = 0); // 0 = Create 1 = Add; 2 = Update; 3 = Remove; 4 = Delete
uint32 CreateNewNPCCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 extra);
uint32 AddNewNPCSpawnGroupCommand(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 respawnTime);
uint32 DeleteSpawnLeaveInNPCTypeTable(const char* zone, Client *client, NPC* spawn);
uint32 DeleteSpawnRemoveFromNPCTypeTable(const char* zone, uint32 zone_version, Client *client, NPC* spawn);
uint32 AddSpawnFromSpawnGroup(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID);
uint32 AddNPCTypes(const char* zone, uint32 zone_version, Client *client, NPC* spawn, uint32 spawnGroupID);
uint32 UpdateNPCTypeAppearance(Client *client, NPC* spawn);
bool SetSpecialAttkFlag(uint8 id, const char* flag); bool SetSpecialAttkFlag(uint8 id, const char* flag);
bool GetPetEntry(const char *pet_type, PetRecord *into); bool GetPetEntry(const char *pet_type, PetRecord *into);
bool GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into); bool GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into);