Merge plus some work on fixing stacking for merchants.

This commit is contained in:
KimLS 2015-03-15 14:02:13 -07:00
commit 21ce5c6daa
17 changed files with 425 additions and 178 deletions

View File

@ -1,5 +1,15 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) EQEMu Changelog (Started on Sept 24, 2003 15:50)
------------------------------------------------------- -------------------------------------------------------
== 03/04/2015 ==
Akkadius: Fix Spell Book Deletion
== 03/03/2015 ==
Uleat: Fix for 'Invalid Slot ID' messages. Bag slot count is now enforced during database saves to eliminate existing 'hidden' duplicated items..sorry MQ2 users...
Uleat: Fix for Item loss during corpse looting and possible item loss when purchasing items from LDoN or Adventure merchants. (cursor-related)
== 02/27/2015 ==
Uleat: Final 'tweak' for light sources until a valid issue arises. Wearable equipment now matches client behavior. Stackable light sources are bugged in the client..but, the current timer update implementation alleviates this condition.
== 02/26/2015 == == 02/26/2015 ==
Uleat: Updated light source criteria to (hopefully) match what the client uses (still needs tweaking) Uleat: Updated light source criteria to (hopefully) match what the client uses (still needs tweaking)
Uleat: Changed 'general' light source checks to accept the last valid light source (client behavior) Uleat: Changed 'general' light source checks to accept the last valid light source (client behavior)
@ -8,6 +18,13 @@ Notes:
- Wearable equipment types still register as valid light sources when in general slots (needs exemption criteria) - Wearable equipment types still register as valid light sources when in general slots (needs exemption criteria)
== 02/23/2015 == == 02/23/2015 ==
Noudess: Allow for a rule to set starting swimming && SenseHeading value.
I moved the swimming override to char create instead of setting it
every time a char enters a zone.
Also added rules to not ignore, but rather forrce sense heading packets to be
used to train it instead of maxing it out like before.
Uleat: Fix for RoF+ clients showing active 'Return Home' button when action is not available (swapped 'GoHome' and 'Enabled' fields in RoF-era CharacterSelectEntry structs) Uleat: Fix for RoF+ clients showing active 'Return Home' button when action is not available (swapped 'GoHome' and 'Enabled' fields in RoF-era CharacterSelectEntry structs)
== 02/21/2015 == == 02/21/2015 ==

View File

@ -126,6 +126,14 @@ struct LDoNTrapTemplate
// All clients translate the character select information to some degree // All clients translate the character select information to some degree
struct Inventory_Slot_Struct
{
int16 type;
int16 slot;
int16 bag;
int16 aug;
};
struct Color_Struct struct Color_Struct
{ {
union { union {
@ -1969,13 +1977,11 @@ Unknowns:
struct Merchant_Sell_Struct { struct Merchant_Sell_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id uint32 npcid;
/*004*/ uint32 playerid; // Player's entity id uint32 playerid;
/*008*/ uint32 itemslot; uint32 itemslot;
uint32 unknown12; int32 quantity;
/*016*/ uint8 quantity; // Already sold uint32 price;
/*017*/ uint8 Unknown016[3];
/*020*/ uint32 price;
}; };
struct Merchant_Purchase_Struct { struct Merchant_Purchase_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id /*000*/ uint32 npcid; // Merchant NPC's entity id

View File

@ -366,6 +366,43 @@ bool EQEmu::Inventory::Swap(const InventorySlot &src, const InventorySlot &dest,
return true; return true;
} }
bool EQEmu::Inventory::TryStacking(std::shared_ptr<EQEmu::ItemInstance> inst, const InventorySlot &slot) {
auto target_inst = Get(slot);
if(!inst || !target_inst ||
!inst->IsStackable() || !target_inst->IsStackable())
{
return false;
}
if(inst->GetBaseItem()->ID != target_inst->GetBaseItem()->ID) {
return false;
}
int stack_avail = target_inst->GetBaseItem()->StackSize - target_inst->GetCharges();
if(stack_avail <= 0) {
return false;
}
impl_->data_model_->Begin();
if(inst->GetCharges() <= stack_avail) {
inst->SetCharges(0);
target_inst->SetCharges(target_inst->GetCharges() + inst->GetCharges());
impl_->data_model_->Delete(slot);
impl_->data_model_->Insert(slot, target_inst);
} else {
inst->SetCharges(inst->GetCharges() - stack_avail);
target_inst->SetCharges(target_inst->GetCharges() + stack_avail);
impl_->data_model_->Delete(slot);
impl_->data_model_->Insert(slot, target_inst);
}
impl_->data_model_->Commit();
return true;
}
bool EQEmu::Inventory::Summon(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst) { bool EQEmu::Inventory::Summon(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst) {
if(!inst) if(!inst)
return false; return false;

View File

@ -135,9 +135,12 @@ namespace EQEmu
std::shared_ptr<ItemInstance> Get(const InventorySlot &slot); std::shared_ptr<ItemInstance> Get(const InventorySlot &slot);
bool Put(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst); bool Put(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst);
bool Swap(const InventorySlot &src, const InventorySlot &dest, int charges); bool Swap(const InventorySlot &src, const InventorySlot &dest, int charges);
bool TryStacking(std::shared_ptr<EQEmu::ItemInstance> inst, const InventorySlot &slot);
bool Summon(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst); bool Summon(const InventorySlot &slot, std::shared_ptr<ItemInstance> inst);
bool PushToCursorBuffer(std::shared_ptr<ItemInstance> inst); bool PushToCursorBuffer(std::shared_ptr<ItemInstance> inst);
bool PopFromCursorBuffer(); bool PopFromCursorBuffer();
bool PutStackInInventory(std::shared_ptr<ItemInstance> inst, bool try_worn, bool try_cursor);
InventorySlot PutItemInInventory(std::shared_ptr<ItemInstance> inst, bool try_worn, bool try_cursor);
//utility //utility
static int CalcMaterialFromSlot(const InventorySlot &slot); static int CalcMaterialFromSlot(const InventorySlot &slot);

View File

@ -133,6 +133,9 @@ RULE_INT ( Skills, MaxTrainTradeskills, 21 )
RULE_BOOL ( Skills, UseLimitTradeskillSearchSkillDiff, true ) RULE_BOOL ( Skills, UseLimitTradeskillSearchSkillDiff, true )
RULE_INT ( Skills, MaxTradeskillSearchSkillDiff, 50 ) RULE_INT ( Skills, MaxTradeskillSearchSkillDiff, 50 )
RULE_INT ( Skills, MaxTrainSpecializations, 50 ) // Max level a GM trainer will train casting specializations RULE_INT ( Skills, MaxTrainSpecializations, 50 ) // Max level a GM trainer will train casting specializations
RULE_INT ( Skills, SwimmingStartValue, 100 )
RULE_BOOL ( Skills, TrainSenseHeading, false )
RULE_INT ( Skills, SenseHeadingStartValue, 200 )
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY( Pets ) RULE_CATEGORY( Pets )

View File

@ -256,7 +256,9 @@ bool SharedDatabase::UpdateSharedBankSlot(uint32 char_id, const ItemInst* inst,
// Save bag contents, if slot supports bag contents // Save bag contents, if slot supports bag contents
if (inst->IsType(ItemClassContainer) && InventoryOld::SupportsContainers(slot_id)) { if (inst->IsType(ItemClassContainer) && InventoryOld::SupportsContainers(slot_id)) {
for (uint8 idx = SUB_BEGIN; idx < EmuConstants::ITEM_CONTAINER_SIZE; idx++) { // Limiting to bag slot count will get rid of 'hidden' duplicated items and 'Invalid Slot ID'
// messages through attrition (and the modded code in SaveInventory)
for (uint8 idx = SUB_BEGIN; idx < inst->GetItem()->BagSlots && idx < EmuConstants::ITEM_CONTAINER_SIZE; idx++) {
const ItemInst* baginst = inst->GetItem(idx); const ItemInst* baginst = inst->GetItem(idx);
SaveInventory(char_id, baginst, InventoryOld::CalcSlotId(slot_id, idx)); SaveInventory(char_id, baginst, InventoryOld::CalcSlotId(slot_id, idx));
} }

View File

@ -1430,7 +1430,10 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc)
SetRaceStartingSkills(&pp); SetRaceStartingSkills(&pp);
SetClassStartingSkills(&pp); SetClassStartingSkills(&pp);
SetClassLanguages(&pp); SetClassLanguages(&pp);
pp.skills[SkillSenseHeading] = 200;
pp.skills[SkillSwimming] = RuleI(Skills, SwimmingStartValue);
pp.skills[SkillSenseHeading] = RuleI(Skills, SenseHeadingStartValue);
// strcpy(pp.servername, WorldConfig::get()->ShortName.c_str()); // strcpy(pp.servername, WorldConfig::get()->ShortName.c_str());

View File

@ -81,6 +81,8 @@ void Client::CalcBonuses()
CalcAABonuses(&aabonuses); //we're not quite ready for this CalcAABonuses(&aabonuses); //we're not quite ready for this
Log.Out(Logs::Detail, Logs::AA, "Finished calculating AA Bonuses for %s.", this->GetCleanName()); Log.Out(Logs::Detail, Logs::AA, "Finished calculating AA Bonuses for %s.", this->GetCleanName());
ProcessItemCaps(); // caps that depend on spell/aa bonuses
RecalcWeight(); RecalcWeight();
CalcAC(); CalcAC();
@ -183,16 +185,24 @@ void Client::CalcItemBonuses(StatBonuses* newbon) {
AdditiveWornBonuses(inst, newbon); AdditiveWornBonuses(inst, newbon);
} }
} }
}
// Caps // These item stat caps depend on spells/AAs so we process them after those are processed
if(newbon->HPRegen > CalcHPRegenCap()) void Client::ProcessItemCaps()
newbon->HPRegen = CalcHPRegenCap(); {
itembonuses.HPRegen = std::min(itembonuses.HPRegen, CalcHPRegenCap());
itembonuses.ManaRegen = std::min(itembonuses.ManaRegen, CalcManaRegenCap());
itembonuses.EnduranceRegen = std::min(itembonuses.EnduranceRegen, CalcEnduranceRegenCap());
if(newbon->ManaRegen > CalcManaRegenCap()) // The Sleeper Tomb Avatar proc counts towards item ATK
newbon->ManaRegen = CalcManaRegenCap(); // The client uses a 100 here, so using a 100 here the client and server will agree
// For example, if you set the effect to be 200 it will get 100 item ATK and 100 spell ATK
if (IsValidSpell(2434) && FindBuff(2434)) {
itembonuses.ATK += 100;
spellbonuses.ATK -= 100;
}
if(newbon->EnduranceRegen > CalcEnduranceRegenCap()) itembonuses.ATK = std::min(itembonuses.ATK, CalcItemATKCap());
newbon->EnduranceRegen = CalcEnduranceRegenCap();
} }
void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAug, bool isTribute) { void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAug, bool isTribute) {
@ -225,6 +235,7 @@ void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAu
newbon->HP += item->HP; newbon->HP += item->HP;
newbon->Mana += item->Mana; newbon->Mana += item->Mana;
newbon->Endurance += item->Endur; newbon->Endurance += item->Endur;
newbon->ATK += item->Attack;
newbon->STR += (item->AStr + item->HeroicStr); newbon->STR += (item->AStr + item->HeroicStr);
newbon->STA += (item->ASta + item->HeroicSta); newbon->STA += (item->ASta + item->HeroicSta);
newbon->DEX += (item->ADex + item->HeroicDex); newbon->DEX += (item->ADex + item->HeroicDex);
@ -278,6 +289,7 @@ void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAu
newbon->HP += CalcRecommendedLevelBonus( lvl, reclvl, item->HP ); newbon->HP += CalcRecommendedLevelBonus( lvl, reclvl, item->HP );
newbon->Mana += CalcRecommendedLevelBonus( lvl, reclvl, item->Mana ); newbon->Mana += CalcRecommendedLevelBonus( lvl, reclvl, item->Mana );
newbon->Endurance += CalcRecommendedLevelBonus( lvl, reclvl, item->Endur ); newbon->Endurance += CalcRecommendedLevelBonus( lvl, reclvl, item->Endur );
newbon->ATK += CalcRecommendedLevelBonus( lvl, reclvl, item->Attack );
newbon->STR += CalcRecommendedLevelBonus( lvl, reclvl, (item->AStr + item->HeroicStr) ); newbon->STR += CalcRecommendedLevelBonus( lvl, reclvl, (item->AStr + item->HeroicStr) );
newbon->STA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ASta + item->HeroicSta) ); newbon->STA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ASta + item->HeroicSta) );
newbon->DEX += CalcRecommendedLevelBonus( lvl, reclvl, (item->ADex + item->HeroicDex) ); newbon->DEX += CalcRecommendedLevelBonus( lvl, reclvl, (item->ADex + item->HeroicDex) );
@ -335,16 +347,6 @@ void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAu
if(item->EnduranceRegen > 0) if(item->EnduranceRegen > 0)
newbon->EnduranceRegen += item->EnduranceRegen; newbon->EnduranceRegen += item->EnduranceRegen;
if(item->Attack > 0) {
int cap = RuleI(Character, ItemATKCap);
cap += itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
if((newbon->ATK + item->Attack) > cap)
newbon->ATK = RuleI(Character, ItemATKCap);
else
newbon->ATK += item->Attack;
}
if(item->DamageShield > 0) { if(item->DamageShield > 0) {
if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap))
newbon->DamageShield = RuleI(Character, ItemDamageShieldCap); newbon->DamageShield = RuleI(Character, ItemDamageShieldCap);

View File

@ -245,6 +245,8 @@ public:
virtual bool IsClient() const { return true; } virtual bool IsClient() const { return true; }
void CompleteConnect(); void CompleteConnect();
bool TryStacking(ItemInst* item, uint8 type = ItemPacketTrade, bool try_worn = true, bool try_cursor = true); bool TryStacking(ItemInst* item, uint8 type = ItemPacketTrade, bool try_worn = true, bool try_cursor = true);
bool TryStacking(std::shared_ptr<EQEmu::ItemInstance> item, uint8 type = ItemPacketTrade, bool try_worn = true, bool try_cursor = true);
bool TryStacking(std::shared_ptr<EQEmu::ItemInstance> item, const EQEmu::InventorySlot &slot, uint8 type = ItemPacketTrade);
void SendTraderPacket(Client* trader, uint32 Unknown72 = 51); void SendTraderPacket(Client* trader, uint32 Unknown72 = 51);
void SendBuyerPacket(Client* Buyer); void SendBuyerPacket(Client* Buyer);
GetItems_Struct* GetTraderItems(); GetItems_Struct* GetTraderItems();
@ -1284,6 +1286,7 @@ protected:
void CalcEdibleBonuses(StatBonuses* newbon); void CalcEdibleBonuses(StatBonuses* newbon);
void CalcAABonuses(StatBonuses* newbon); void CalcAABonuses(StatBonuses* newbon);
void ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon); void ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon);
void ProcessItemCaps();
void MakeBuffFadePacket(uint16 spell_id, int slot_id, bool send_message = true); void MakeBuffFadePacket(uint16 spell_id, int slot_id, bool send_message = true);
bool client_data_loaded; bool client_data_loaded;
@ -1337,6 +1340,7 @@ private:
int32 GetACMit(); int32 GetACMit();
int32 GetACAvoid(); int32 GetACAvoid();
int32 CalcATK(); int32 CalcATK();
int32 CalcItemATKCap();
int32 CalcHaste(); int32 CalcHaste();
int32 CalcAlcoholPhysicalEffect(); int32 CalcAlcoholPhysicalEffect();

View File

@ -2148,6 +2148,12 @@ int32 Client::CalcEnduranceRegenCap()
return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100);
} }
int32 Client::CalcItemATKCap()
{
int cap = RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
return cap;
}
int Client::GetRawACNoShield(int &shield_ac) const int Client::GetRawACNoShield(int &shield_ac) const
{ {
int ac = itembonuses.AC + spellbonuses.AC + aabonuses.AC; int ac = itembonuses.AC + spellbonuses.AC + aabonuses.AC;

View File

@ -333,7 +333,13 @@ void MapOpcodes()
ConnectedOpcodes[OP_Save] = &Client::Handle_OP_Save; ConnectedOpcodes[OP_Save] = &Client::Handle_OP_Save;
ConnectedOpcodes[OP_SaveOnZoneReq] = &Client::Handle_OP_SaveOnZoneReq; ConnectedOpcodes[OP_SaveOnZoneReq] = &Client::Handle_OP_SaveOnZoneReq;
ConnectedOpcodes[OP_SelectTribute] = &Client::Handle_OP_SelectTribute; ConnectedOpcodes[OP_SelectTribute] = &Client::Handle_OP_SelectTribute;
ConnectedOpcodes[OP_SenseHeading] = &Client::Handle_OP_Ignore;
// Use or Ignore sense heading based on rule.
bool train=RuleB(Skills, TrainSenseHeading);
ConnectedOpcodes[OP_SenseHeading] =
(train) ? &Client::Handle_OP_SenseHeading : &Client::Handle_OP_Ignore;
ConnectedOpcodes[OP_SenseTraps] = &Client::Handle_OP_SenseTraps; ConnectedOpcodes[OP_SenseTraps] = &Client::Handle_OP_SenseTraps;
ConnectedOpcodes[OP_SetGuildMOTD] = &Client::Handle_OP_SetGuildMOTD; ConnectedOpcodes[OP_SetGuildMOTD] = &Client::Handle_OP_SetGuildMOTD;
ConnectedOpcodes[OP_SetRunMode] = &Client::Handle_OP_SetRunMode; ConnectedOpcodes[OP_SetRunMode] = &Client::Handle_OP_SetRunMode;
@ -1437,10 +1443,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if (m_pp.ldon_points_tak < 0 || m_pp.ldon_points_tak > 2000000000){ m_pp.ldon_points_tak = 0; } if (m_pp.ldon_points_tak < 0 || m_pp.ldon_points_tak > 2000000000){ m_pp.ldon_points_tak = 0; }
if (m_pp.ldon_points_available < 0 || m_pp.ldon_points_available > 2000000000){ m_pp.ldon_points_available = 0; } if (m_pp.ldon_points_available < 0 || m_pp.ldon_points_available > 2000000000){ m_pp.ldon_points_available = 0; }
/* Set Swimming Skill 100 by default if under 100 */
if (GetSkill(SkillSwimming) < 100)
SetSkill(SkillSwimming, 100);
/* Initialize AA's : Move to function eventually */ /* Initialize AA's : Move to function eventually */
for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ aa[a] = &m_pp.aa_array[a]; } for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ aa[a] = &m_pp.aa_array[a]; }
query = StringFormat( query = StringFormat(
@ -5081,6 +5083,7 @@ void Client::Handle_OP_DeleteSpell(const EQApplicationPacket *app)
if (m_pp.spell_book[dss->spell_slot] != SPELLBOOK_UNKNOWN) { if (m_pp.spell_book[dss->spell_slot] != SPELLBOOK_UNKNOWN) {
m_pp.spell_book[dss->spell_slot] = SPELLBOOK_UNKNOWN; m_pp.spell_book[dss->spell_slot] = SPELLBOOK_UNKNOWN;
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[dss->spell_slot], dss->spell_slot);
dss->success = 1; dss->success = 1;
} }
else else
@ -11605,6 +11608,27 @@ void Client::Handle_OP_SelectTribute(const EQApplicationPacket *app)
return; return;
} }
void Client::Handle_OP_SenseHeading(const EQApplicationPacket *app)
{
if (!HasSkill(SkillSenseHeading))
return;
int chancemod=0;
// The client seems to limit sense heading packets based on skill
// level. So if we're really low, we don't hit this routine very often.
// I think it's the GUI deciding when to skill you up.
// So, I'm adding a mod here which is larger at lower levels so
// very low levels get a much better chance to skill up when the GUI
// eventually sends a message.
if (GetLevel() <= 8)
chancemod += (9-level) * 10;
CheckIncreaseSkill(SkillSenseHeading, nullptr, chancemod);
return;
}
void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app)
{ {
if (!HasSkill(SkillSenseTraps)) if (!HasSkill(SkillSenseTraps))
@ -11914,19 +11938,13 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
sizeof(Merchant_Sell_Struct), app->size); sizeof(Merchant_Sell_Struct), app->size);
return; return;
} }
RDTSC_Timer t1;
t1.start();
Merchant_Sell_Struct* mp = (Merchant_Sell_Struct*)app->pBuffer; Merchant_Sell_Struct* mp = (Merchant_Sell_Struct*)app->pBuffer;
#if EQDEBUG >= 5
Log.Out(Logs::General, Logs::None, "%s, purchase item..", GetName());
DumpPacket(app);
#endif
int merchantid; int merchantid;
bool tmpmer_used = false; bool tmpmer_used = false;
Mob* tmp = entity_list.GetMob(mp->npcid); Mob* tmp = entity_list.GetMob(mp->npcid);
if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != MERCHANT) if (!tmp || !tmp->IsNPC() || tmp->GetClass() != MERCHANT)
return; return;
if (mp->quantity < 1) return; if (mp->quantity < 1) return;
@ -11986,7 +12004,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
safe_delete(delitempacket); safe_delete(delitempacket);
return; return;
} }
if (CheckLoreConflict(item)) if (m_inventory.CheckLoreConflict(item))
{ {
Message(15, "You can only have one of a lore item."); Message(15, "You can only have one of a lore item.");
return; return;
@ -12017,7 +12035,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
else else
charges = item->MaxCharges; charges = item->MaxCharges;
ItemInst* inst = database.CreateItemOld(item, charges); auto inst = database.CreateItem(item->ID, charges);
int SinglePrice = 0; int SinglePrice = 0;
if (RuleB(Merchant, UsePriceMod)) if (RuleB(Merchant, UsePriceMod))
@ -12033,7 +12051,6 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
if (mpo->price < 0) if (mpo->price < 0)
{ {
safe_delete(outapp); safe_delete(outapp);
safe_delete(inst);
return; return;
} }
@ -12049,126 +12066,119 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName()); database.SetMQDetectionFlag(AccountName(), GetName(), hacker_str, zone->GetShortName());
safe_delete_array(hacker_str); safe_delete_array(hacker_str);
safe_delete(outapp); safe_delete(outapp);
safe_delete(inst);
return; return;
} }
bool stacked = TryStacking(inst); bool stacked = TryStacking(inst);
if (!stacked) //if (!stacked)
freeslotid = m_inv.FindFreeSlot(false, true, item->Size); // freeslotid = m_inv.FindFreeSlot(false, true, item->Size);
//
// shouldn't we be reimbursing if these two fail? //// shouldn't we be reimbursing if these two fail?
//
//make sure we are not completely full... ////make sure we are not completely full...
if (freeslotid == MainCursor) { //if (freeslotid == MainCursor) {
if (m_inv.GetItem(MainCursor) != nullptr) { // if (m_inv.GetItem(MainCursor) != nullptr) {
Message(13, "You do not have room for any more items."); // Message(13, "You do not have room for any more items.");
safe_delete(outapp); // safe_delete(outapp);
safe_delete(inst); // return;
return; // }
} //}
} //
//if (!stacked && freeslotid == INVALID_INDEX)
if (!stacked && freeslotid == INVALID_INDEX) //{
{ // Message(13, "You do not have room for any more items.");
Message(13, "You do not have room for any more items."); // safe_delete(outapp);
safe_delete(outapp); // return;
safe_delete(inst); //}
return; //
} //std::string packet;
//if (!stacked && inst) {
std::string packet; // PutItemInInventory(freeslotid, *inst);
if (!stacked && inst) { // SendItemPacket(freeslotid, inst, ItemPacketTrade);
PutItemInInventory(freeslotid, *inst); //}
SendItemPacket(freeslotid, inst, ItemPacketTrade); //else if (!stacked){
} // Log.Out(Logs::General, Logs::Error, "OP_ShopPlayerBuy: item->ItemClass Unknown! Type: %i", item->ItemClass);
else if (!stacked){ //}
Log.Out(Logs::General, Logs::Error, "OP_ShopPlayerBuy: item->ItemClass Unknown! Type: %i", item->ItemClass); //QueuePacket(outapp);
} //if (inst && tmpmer_used){
QueuePacket(outapp); // int32 new_charges = prevcharges - mp->quantity;
if (inst && tmpmer_used){ // zone->SaveTempItem(merchantid, tmp->GetNPCTypeID(), item_id, new_charges);
int32 new_charges = prevcharges - mp->quantity; // if (new_charges <= 0){
zone->SaveTempItem(merchantid, tmp->GetNPCTypeID(), item_id, new_charges); // EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct));
if (new_charges <= 0){ // Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer;
EQApplicationPacket* delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct)); // delitem->itemslot = mp->itemslot;
Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer; // delitem->npcid = mp->npcid;
delitem->itemslot = mp->itemslot; // delitem->playerid = mp->playerid;
delitem->npcid = mp->npcid; // delitempacket->priority = 6;
delitem->playerid = mp->playerid; // entity_list.QueueClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update
delitempacket->priority = 6; // safe_delete(delitempacket);
entity_list.QueueClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update // }
safe_delete(delitempacket); // else {
} // // Update the charges/quantity in the merchant window
else { // inst->SetCharges(new_charges);
// Update the charges/quantity in the merchant window // inst->SetPrice(SinglePrice);
inst->SetCharges(new_charges); // inst->SetMerchantSlot(mp->itemslot);
inst->SetPrice(SinglePrice); // inst->SetMerchantCount(new_charges);
inst->SetMerchantSlot(mp->itemslot); //
inst->SetMerchantCount(new_charges); // SendItemPacket(mp->itemslot, inst, ItemPacketMerchant);
// }
SendItemPacket(mp->itemslot, inst, ItemPacketMerchant); //}
} //safe_delete(inst);
} //safe_delete(outapp);
safe_delete(inst); //
safe_delete(outapp); //// start QS code
//// stacking purchases not supported at this time - entire process will need some work to catch them properly
// start QS code //if (RuleB(QueryServ, PlayerLogMerchantTransactions)) {
// stacking purchases not supported at this time - entire process will need some work to catch them properly // ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct)+sizeof(QSTransactionItems_Struct));
if (RuleB(QueryServ, PlayerLogMerchantTransactions)) { // QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer;
ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct)+sizeof(QSTransactionItems_Struct)); //
QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; // qsaudit->zone_id = zone->GetZoneID();
// qsaudit->merchant_id = tmp->CastToNPC()->MerchantType;
qsaudit->zone_id = zone->GetZoneID(); // qsaudit->merchant_money.platinum = 0;
qsaudit->merchant_id = tmp->CastToNPC()->MerchantType; // qsaudit->merchant_money.gold = 0;
qsaudit->merchant_money.platinum = 0; // qsaudit->merchant_money.silver = 0;
qsaudit->merchant_money.gold = 0; // qsaudit->merchant_money.copper = 0;
qsaudit->merchant_money.silver = 0; // qsaudit->merchant_count = 1;
qsaudit->merchant_money.copper = 0; // qsaudit->char_id = character_id;
qsaudit->merchant_count = 1; // qsaudit->char_money.platinum = (mpo->price / 1000);
qsaudit->char_id = character_id; // qsaudit->char_money.gold = (mpo->price / 100) % 10;
qsaudit->char_money.platinum = (mpo->price / 1000); // qsaudit->char_money.silver = (mpo->price / 10) % 10;
qsaudit->char_money.gold = (mpo->price / 100) % 10; // qsaudit->char_money.copper = mpo->price % 10;
qsaudit->char_money.silver = (mpo->price / 10) % 10; // qsaudit->char_count = 0;
qsaudit->char_money.copper = mpo->price % 10; //
qsaudit->char_count = 0; // qsaudit->items[0].char_slot = freeslotid == INVALID_INDEX ? 0 : freeslotid;
// qsaudit->items[0].item_id = item->ID;
qsaudit->items[0].char_slot = freeslotid == INVALID_INDEX ? 0 : freeslotid; // qsaudit->items[0].charges = mpo->quantity;
qsaudit->items[0].item_id = item->ID; //
qsaudit->items[0].charges = mpo->quantity; // if (freeslotid == INVALID_INDEX) {
// qsaudit->items[0].aug_1 = 0;
if (freeslotid == INVALID_INDEX) { // qsaudit->items[0].aug_2 = 0;
qsaudit->items[0].aug_1 = 0; // qsaudit->items[0].aug_3 = 0;
qsaudit->items[0].aug_2 = 0; // qsaudit->items[0].aug_4 = 0;
qsaudit->items[0].aug_3 = 0; // qsaudit->items[0].aug_5 = 0;
qsaudit->items[0].aug_4 = 0; // }
qsaudit->items[0].aug_5 = 0; // else {
} // qsaudit->items[0].aug_1 = m_inv[freeslotid]->GetAugmentItemID(0);
else { // qsaudit->items[0].aug_2 = m_inv[freeslotid]->GetAugmentItemID(1);
qsaudit->items[0].aug_1 = m_inv[freeslotid]->GetAugmentItemID(0); // qsaudit->items[0].aug_3 = m_inv[freeslotid]->GetAugmentItemID(2);
qsaudit->items[0].aug_2 = m_inv[freeslotid]->GetAugmentItemID(1); // qsaudit->items[0].aug_4 = m_inv[freeslotid]->GetAugmentItemID(3);
qsaudit->items[0].aug_3 = m_inv[freeslotid]->GetAugmentItemID(2); // qsaudit->items[0].aug_5 = m_inv[freeslotid]->GetAugmentItemID(4);
qsaudit->items[0].aug_4 = m_inv[freeslotid]->GetAugmentItemID(3); // }
qsaudit->items[0].aug_5 = m_inv[freeslotid]->GetAugmentItemID(4); //
} // qspack->Deflate();
// if (worldserver.Connected()) { worldserver.SendPacket(qspack); }
qspack->Deflate(); // safe_delete(qspack);
if (worldserver.Connected()) { worldserver.SendPacket(qspack); } //}
safe_delete(qspack); //// end QS code
} //
// end QS code //if (RuleB(EventLog, RecordBuyFromMerchant))
// LogMerchant(this, tmp, mpo->quantity, mpo->price, item, true);
if (RuleB(EventLog, RecordBuyFromMerchant)) //
LogMerchant(this, tmp, mpo->quantity, mpo->price, item, true); //if ((RuleB(Character, EnableDiscoveredItems)))
//{
if ((RuleB(Character, EnableDiscoveredItems))) // if (!GetGM() && !IsDiscovered(item_id))
{ // DiscoverItem(item_id);
if (!GetGM() && !IsDiscovered(item_id)) //}
DiscoverItem(item_id);
}
t1.stop();
std::cout << "At 1: " << t1.getDuration() << std::endl;
return;
} }
void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
{ {

View File

@ -28,7 +28,7 @@
set to nullptr and 0 respectively since they aren't used when adding set to nullptr and 0 respectively since they aren't used when adding
an alias. The function pointers being equal is makes it an alias. an alias. The function pointers being equal is makes it an alias.
The access level you set with command_add is only a default if The access level you set with command_add is only a default if
the command isn't listed in the addon.ini file. the command isn't listed in the commands db table.
*/ */

View File

@ -1217,9 +1217,9 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) {
linker.SetLinkType(linker.linkItemInst); linker.SetLinkType(linker.linkItemInst);
linker.SetItemInst(inst); linker.SetItemInst(inst);
auto item_link = linker.GenerateLink(); auto item_link = linker.GenerateLink();
client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str()); client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str());
if (!IsPlayerCorpse()) { if (!IsPlayerCorpse()) {
Group *g = client->GetGroup(); Group *g = client->GetGroup();

View File

@ -2737,7 +2737,7 @@ void EntityList::WriteEntityIDs()
BulkZoneSpawnPacket::BulkZoneSpawnPacket(Client *iSendTo, uint32 iMaxSpawnsPerPacket) BulkZoneSpawnPacket::BulkZoneSpawnPacket(Client *iSendTo, uint32 iMaxSpawnsPerPacket)
{ {
data = 0; data = nullptr;
pSendTo = iSendTo; pSendTo = iSendTo;
pMaxSpawnsPerPacket = iMaxSpawnsPerPacket; pMaxSpawnsPerPacket = iMaxSpawnsPerPacket;
} }
@ -2745,7 +2745,7 @@ BulkZoneSpawnPacket::BulkZoneSpawnPacket(Client *iSendTo, uint32 iMaxSpawnsPerPa
BulkZoneSpawnPacket::~BulkZoneSpawnPacket() BulkZoneSpawnPacket::~BulkZoneSpawnPacket()
{ {
SendBuffer(); SendBuffer();
safe_delete_array(data) safe_delete_array(data);
} }
bool BulkZoneSpawnPacket::AddSpawn(NewSpawn_Struct *ns) bool BulkZoneSpawnPacket::AddSpawn(NewSpawn_Struct *ns)
@ -2764,7 +2764,8 @@ bool BulkZoneSpawnPacket::AddSpawn(NewSpawn_Struct *ns)
return false; return false;
} }
void BulkZoneSpawnPacket::SendBuffer() { void BulkZoneSpawnPacket::SendBuffer()
{
if (!data) if (!data)
return; return;

View File

@ -882,34 +882,71 @@ bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client
void Client::PutLootInInventory(int16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data) void Client::PutLootInInventory(int16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data)
{ {
Log.Out(Logs::Detail, Logs::Inventory, "Putting loot item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); Log.Out(Logs::Detail, Logs::Inventory, "Putting loot item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);
m_inv.PutItem(slot_id, inst);
SendLootItemInPacket(&inst, slot_id); bool cursor_empty = m_inv.CursorEmpty();
if (slot_id == MainCursor) { if (slot_id == MainCursor) {
m_inv.PushCursor(inst);
auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
database.SaveCursor(this->CharacterID(), s, e); database.SaveCursor(this->CharacterID(), s, e);
} }
else { else {
m_inv.PutItem(slot_id, inst);
database.SaveInventory(this->CharacterID(), &inst, slot_id); database.SaveInventory(this->CharacterID(), &inst, slot_id);
} }
if(bag_item_data) { // bag contents // Subordinate items in cursor buffer must be sent via ItemPacketSummonItem or we just overwrite the visible cursor and desync the client
int16 interior_slot; if (slot_id == MainCursor && !cursor_empty) {
// our bag went into slot_id, now let's pack the contents in // RoF+ currently has a specialized cursor handler
for(int i = SUB_BEGIN; i < EmuConstants::ITEM_CONTAINER_SIZE; i++) { if (GetClientVersion() < ClientVersion::RoF)
if(bag_item_data[i] == nullptr) SendItemPacket(slot_id, &inst, ItemPacketSummonItem);
}
else {
SendLootItemInPacket(&inst, slot_id);
}
if (bag_item_data) {
for (int index = 0; index < EmuConstants::ITEM_CONTAINER_SIZE; ++index) {
if (bag_item_data[index] == nullptr)
continue; continue;
const ItemInst *bagitem = database.CreateItemOld(bag_item_data[i]->item_id, bag_item_data[i]->charges, bag_item_data[i]->aug_1, bag_item_data[i]->aug_2, bag_item_data[i]->aug_3, bag_item_data[i]->aug_4, bag_item_data[i]->aug_5, bag_item_data[i]->aug_6, bag_item_data[i]->attuned);
interior_slot = InventoryOld::CalcSlotId(slot_id, i); const ItemInst *bagitem = database.CreateItemOld(
Log.Out(Logs::Detail, Logs::Inventory, "Putting bag loot item %s (%d) into slot %d (bag slot %d)", inst.GetItem()->Name, inst.GetItem()->ID, interior_slot, i); bag_item_data[index]->item_id,
PutLootInInventory(interior_slot, *bagitem); bag_item_data[index]->charges,
bag_item_data[index]->aug_1,
bag_item_data[index]->aug_2,
bag_item_data[index]->aug_3,
bag_item_data[index]->aug_4,
bag_item_data[index]->aug_5,
bag_item_data[index]->aug_6,
bag_item_data[index]->attuned
);
// Dump bag contents to cursor in the event that owning bag is not the first cursor item
// (This assumes that the data passed is correctly associated..no safety checks are implemented)
if (slot_id == MainCursor && !cursor_empty) {
Log.Out(Logs::Detail, Logs::Inventory,
"Putting bag loot item %s (%d) into slot %d (non-empty cursor override)",
inst.GetItem()->Name, inst.GetItem()->ID, MainCursor);
PutLootInInventory(MainCursor, *bagitem);
}
else {
auto bag_slot = InventoryOld::CalcSlotId(slot_id, index);
Log.Out(Logs::Detail, Logs::Inventory,
"Putting bag loot item %s (%d) into slot %d (bag slot %d)",
inst.GetItem()->Name, inst.GetItem()->ID, bag_slot, index);
PutLootInInventory(bag_slot, *bagitem);
}
safe_delete(bagitem); safe_delete(bagitem);
} }
} }
CalcBonuses(); CalcBonuses();
} }
bool Client::TryStacking(ItemInst* item, uint8 type, bool try_worn, bool try_cursor){ bool Client::TryStacking(ItemInst* item, uint8 type, bool try_worn, bool try_cursor){
if(!item || !item->IsStackable() || item->GetCharges()>=item->GetItem()->StackSize) if(!item || !item->IsStackable() || item->GetCharges()>=item->GetItem()->StackSize)
return false; return false;
@ -944,6 +981,47 @@ bool Client::TryStacking(ItemInst* item, uint8 type, bool try_worn, bool try_cur
return false; return false;
} }
bool Client::TryStacking(std::shared_ptr<EQEmu::ItemInstance> item, uint8 type, bool try_worn, bool try_cursor) {
if(!item || !item->IsStackable() || item->GetCharges() >= item->GetItem()->StackSize)
return false;
if(try_worn) {
for(int i = EQEmu::PersonalSlotCharm; i <= EQEmu::PersonalSlotAmmo; ++i) {
if(TryStacking(item, EQEmu::InventorySlot(EQEmu::InvTypePersonal, i), type)) {
return true;
}
}
}
for(int i = EQEmu::PersonalSlotGeneral1; i <= EQEmu::PersonalSlotGeneral10; ++i) {
if(TryStacking(item, EQEmu::InventorySlot(EQEmu::InvTypePersonal, i), type)) {
return true;
}
}
if(try_cursor) {
if(TryStacking(item, EQEmu::InventorySlot(EQEmu::InvTypePersonal, EQEmu::PersonalSlotCursor), type)) {
return true;
}
}
return false;
}
bool Client::TryStacking(std::shared_ptr<EQEmu::ItemInstance> item, const EQEmu::InventorySlot &slot, uint8 type) {
//make this a function
uint32 item_id = item->GetItem()->ID;
auto tmp_inst = m_inventory.Get(slot);
if(tmp_inst && tmp_inst->GetItem()->ID == item_id && tmp_inst->GetCharges() < tmp_inst->GetItem()->StackSize) {
bool v = m_inventory.TryStacking(item, slot);
if(v) {
SendItemPacket(slot, item, ItemPacketTrade);
}
}
return item->GetCharges() == 0;
}
// Locate an available space in inventory to place an item // Locate an available space in inventory to place an item
// and then put the item there // and then put the item there
// The change will be saved to the database // The change will be saved to the database

View File

@ -2409,6 +2409,78 @@ XS(XS_NPC_AddDefensiveProc) {
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
XS(XS_NPC_RemoveMeleeProc);
XS(XS_NPC_RemoveMeleeProc) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::RemoveMeleeProc(THIS,spellid)");
{
NPC * THIS;
int spell_id = (int)SvIV(ST(1));
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->RemoveProcFromWeapon(spell_id, false);
}
XSRETURN_EMPTY;
}
XS(XS_NPC_RemoveRangedProc);
XS(XS_NPC_RemoveRangedProc) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::RemoveRangedProc(THIS,spellid)");
{
NPC * THIS;
int spell_id = (int)SvIV(ST(1));
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->RemoveRangedProc(spell_id, false);
}
XSRETURN_EMPTY;
}
XS(XS_NPC_RemoveDefensiveProc);
XS(XS_NPC_RemoveDefensiveProc) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::RemoveDefensiveProc(THIS,spellid)");
{
NPC * THIS;
int spell_id = (int)SvIV(ST(1));
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->RemoveDefensiveProc(spell_id, false);
}
XSRETURN_EMPTY;
}
XS(XS_NPC_ChangeLastName); /* prototype to pass -Wmissing-prototypes */ XS(XS_NPC_ChangeLastName); /* prototype to pass -Wmissing-prototypes */
XS(XS_NPC_ChangeLastName) XS(XS_NPC_ChangeLastName)
{ {
@ -2566,6 +2638,9 @@ XS(boot_NPC)
newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$"); newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$");
newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$"); newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$");
newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$"); newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$");
newXSproto(strcpy(buf, "RemoveMeleeProc"), XS_NPC_RemoveMeleeProc, file, "$$");
newXSproto(strcpy(buf, "RemoveRangedProc"), XS_NPC_RemoveRangedProc, file, "$$");
newXSproto(strcpy(buf, "RemoveDefensiveProc"), XS_NPC_RemoveDefensiveProc, file, "$$");
newXSproto(strcpy(buf, "ChangeLastName"), XS_NPC_ChangeLastName, file, "$:$"); newXSproto(strcpy(buf, "ChangeLastName"), XS_NPC_ChangeLastName, file, "$:$");
newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$"); newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$");
XSRETURN_YES; XSRETURN_YES;