Complete Fix #418 item flags

- Temporary item flag support (removes item 30 minutes from camp out)
Rule R_Player, TemporaryItemLogoutTime added for seconds to deletion of item
- Heirloom item flag support added (limited to group support)
Rule R_Player, HeirloomItemShareExpiration added for seconds to inability to trade item between prior group members(tbd raid)

SQL Updates:
CREATE TABLE `character_items_group_members` (
  `unique_id` int(10) unsigned NOT NULL default 0,
  `character_id` int(10) unsigned NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
alter table character_items add column last_saved timestamp default current_timestamp on update current_timestamp;
alter table character_items add column created timestamp default current_timestamp;
This commit is contained in:
Emagi 2022-06-30 20:08:25 -04:00
parent 7ac0861c98
commit 682e023635
13 changed files with 158 additions and 19 deletions

View file

@ -0,0 +1,4 @@
CREATE TABLE `character_items_group_members` (
`unique_id` int(10) unsigned NOT NULL default 0,
`character_id` int(10) unsigned NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

View file

@ -0,0 +1 @@
alter table character_items add column last_saved timestamp default current_timestamp on update current_timestamp;

View file

@ -0,0 +1 @@
update starting_languages set race=2 where race=3 and language_id=3; #dwarf (2) language id of 3 is not erudite race (3)

View file

@ -57,6 +57,24 @@ void Bot::GiveItem(int32 item_id) {
}
}
void Bot::GiveItem(Item* item) {
if (item) {
int8 slot = GetEquipmentList()->GetFreeSlot(item);
if (slot != 255) {
GetEquipmentList()->AddItem(slot, item);
SetEquipment(item, slot);
database.SaveBotItem(BotID, item->details.item_id, slot);
if (slot == 0) {
ChangePrimaryWeapon();
if (IsBot())
LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon.");
}
CalculateBonuses();
}
}
}
void Bot::RemoveItem(Item* item) {
int8 slot = GetEquipmentList()->GetSlotByItem(item);
if (slot != 255) {

View file

@ -15,6 +15,7 @@ public:
bool IsBot() { return true; }
void GiveItem(int32 item_id);
void GiveItem(Item* item);
void RemoveItem(Item* item);
void TradeItemAdded(Item* item);
void AddItemToTrade(int8 slot);

View file

@ -840,6 +840,7 @@ Item::Item(){
generic_info.condition = 100;
no_buy_back = false;
no_sale = false;
created = std::time(nullptr);
}
Item::Item(Item* in_item){
@ -857,6 +858,8 @@ Item::Item(Item* in_item){
spell_tier = in_item->spell_tier;
no_buy_back = in_item->no_buy_back;
no_sale = in_item->no_sale;
created = in_item->created;
grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end());
}
Item::~Item(){

View file

@ -21,6 +21,7 @@
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include <ctime>
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../../common/MiscFunctions.h"
@ -918,6 +919,10 @@ public:
string item_script;
bool no_buy_back;
bool no_sale;
bool needs_deletion;
std::time_t created;
std::map<int32, bool> grouped_char_ids;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);
int32 GetMaxSellValue();
@ -996,7 +1001,6 @@ public:
bool CheckFlag2(int32 flag);
void AddSlot(int8 slot_id);
void SetSlots(int32 slots);
bool needs_deletion;
};
class MasterItemList{
public:

View file

@ -27,8 +27,10 @@
#include "../WorldDatabase.h"
#include "Items.h"
#include "../World.h"
#include "../Rules/Rules.h"
extern World world;
extern RuleManager rule_manager;
// handle new database class til all functions are converted
void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
@ -1055,6 +1057,9 @@ void WorldDatabase::SaveItems(Client* client)
item = item_iter->second;
if(item) {
if(item->CheckFlag(TEMPORARY)) {
item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire
}
if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) {
DeleteItem(client->GetCharacterID(), item, 0);
client->GetPlayer()->item_list.DestroyItem(item->details.index);
@ -1080,6 +1085,9 @@ void WorldDatabase::SaveItems(Client* client)
if(item)
{
if(item->CheckFlag(TEMPORARY)) {
item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire
}
if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) {
DeleteItem(client->GetCharacterID(), item, 0);
client->GetPlayer()->item_list.DestroyItem(item->details.index);
@ -1107,6 +1115,9 @@ void WorldDatabase::SaveItems(Client* client)
if(item)
{
if(item->CheckFlag(TEMPORARY)) {
item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire
}
if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) {
DeleteItem(client->GetCharacterID(), item, 0);
client->GetPlayer()->item_list.DestroyItem(item->details.index);
@ -1126,6 +1137,9 @@ void WorldDatabase::SaveItems(Client* client)
for (int32 i = 0; i < overflow->size(); i++){
item = overflow->at(i);
if (item) {
if(item->CheckFlag(TEMPORARY)) {
item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire
}
if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) {
DeleteItem(client->GetCharacterID(), item, 0);
client->GetPlayer()->item_list.DestroyItem(item->details.index);
@ -1152,6 +1166,13 @@ void WorldDatabase::SaveItem(int32 account_id, int32 char_id, Item* item, const
string update_item = string("REPLACE INTO character_items (id, type, char_id, slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, account_id, login_checksum) VALUES (%u, '%s', %u, %i, %u, '%s', %i, %i, %i, %i, %i, %i, %i, %u, %u, %u, 0)");
query.AddQueryAsync(char_id, this, Q_REPLACE, update_item.c_str(), item->details.unique_id, type, char_id, item->details.slot_id, item->details.item_id,
getSafeEscapeString(item->creator.c_str()).c_str(),item->adorn0,item->adorn1,item->adorn2, item->generic_info.condition, item->CheckFlag(ATTUNED) ? 1 : 0, item->details.inv_slot_id, item->details.count, item->GetMaxSellValue(), item->no_sale, account_id);
if(item->CheckFlag2(HEIRLOOM)) {
std::map<int32, bool>::iterator itr;
for(itr = item->grouped_char_ids.begin(); itr != item->grouped_char_ids.end(); itr++) {
string addmembers_query = string("REPLACE INTO character_items_group_members (unique_id, character_id) VALUES (%u, %u)");
query.AddQueryAsync(char_id, this, Q_REPLACE, addmembers_query.c_str(), item->details.unique_id, itr->first);
}
}
}
void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
@ -1173,6 +1194,11 @@ void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u)");
query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id);
}
if(item->CheckFlag2(HEIRLOOM)) {
delete_item = string("DELETE FROM character_items_group_members WHERE unique_id = %u");
query.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id);
}
}
void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16 version)
@ -1181,7 +1207,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, id, slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale FROM character_items where char_id = %u or (bag_id = -4 and account_id = %u) ORDER BY slot asc", char_id, account_id);
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, id, slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, UNIX_TIMESTAMP(last_saved), UNIX_TIMESTAMP(created) FROM character_items where char_id = %u or (bag_id = -4 and account_id = %u) ORDER BY slot asc", char_id, account_id);
if(result)
{
@ -1189,9 +1215,8 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
while(result && (row = mysql_fetch_row(result)))
{
LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading character item: %u, slot: %i", strtoul(row[1], NULL, 0), atoi(row[2]));
LogWrite(ITEM__DEBUG, 5, "Items", "Loading character item: %u, slot: %i", strtoul(row[1], NULL, 0), atoi(row[2]));
Item* master_item = master_item_list.GetItem(strtoul(row[3], NULL, 0));
if(master_item)
{
Item* item = new Item(master_item);
@ -1203,6 +1228,19 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
item->save_needed = false;
// we need the items basics (unique id slot id bag id) to continue this temporary check
if(item->CheckFlag(TEMPORARY)) {
std::time_t last_saved = static_cast<std::time_t>(atoul(row[14]));
double timeInSeconds = std::difftime(std::time(nullptr), last_saved);
LogWrite(ITEM__INFO, 0, "Items", "Character ID %u has a temporary item %s time in seconds %f last saved.", char_id, item->name.c_str(), timeInSeconds);
if(timeInSeconds >= rule_manager.GetGlobalRule(R_Player, TemporaryItemLogoutTime)->GetFloat()) {
DeleteItem(char_id, item, 0);
LogWrite(ITEM__INFO, 0, "Items", "\tCharacter ID %u had a temporary item %s which was removed due to time limit.", char_id, item->name.c_str());
safe_delete(item);
continue;
}
}
if(row[4])
item->creator = string(row[4]);//creator
item->adorn0 = atoi(row[5]); //adorn0
@ -1221,12 +1259,29 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
item->generic_info.item_flags += ATTUNED;
}
if(item->CheckFlag2(HEIRLOOM)) {
MYSQL_ROW row2;
MYSQL_RES* result2 = query.RunQuery2(Q_SELECT, "SELECT character_id from character_items_group_members where unique_id = %u", item->details.unique_id);
if(result2)
{
bool ret = true;
while(result2 && (row2 = mysql_fetch_row(result2)))
{
item->grouped_char_ids.insert(std::make_pair(atoul(row2[0]),true));
}
}
}
item->details.inv_slot_id = atol(row[10]); //bag_id
item->details.count = atoi(row[11]); //count
item->SetMaxSellValue(atoul(row[12])); //max sell value
item->no_sale = (atoul(row[13]) == 1);
item->details.appearance_type = 0;
// position 14 is used for the last_saved timestamp (primarily for checking temporary items on login)
item->created = static_cast<std::time_t>(atoul(row[15]));
if(strncasecmp(row[0], "EQUIPPED", 8)==0)
ret = player->GetEquipmentList()->AddItem(item->details.slot_id, item);

View file

@ -212,7 +212,8 @@ void RuleManager::Init()
RULE_INIT(R_Player, MinLastNameLength, "4");
RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1");
RULE_INIT(R_Player, MentorItemDecayRate, ".05"); // 5% per level lost when mentoring
RULE_INIT(R_Player, TemporaryItemLogoutTime, "1800.0"); // time in seconds (double) for temporary item to decay after being logged out for a period of time, 30 min is the default
RULE_INIT(R_Player, HeirloomItemShareExpiration, "172800.0"); // 2 days ('48 hours') in seconds
/* PVP */
RULE_INIT(R_PVP, AllowPVP, "0");
RULE_INIT(R_PVP, LevelRange, "4");

View file

@ -72,6 +72,8 @@ enum RuleType {
MinLastNameLength,
DisableHouseAlignmentRequirement,
MentorItemDecayRate,
TemporaryItemLogoutTime,
HeirloomItemShareExpiration,
/* PVP */
AllowPVP,

View file

@ -3,9 +3,11 @@
#include "Entity.h"
#include "Bots/Bot.h"
#include "../common/Log.h"
#include "Rules/Rules.h"
extern ConfigReader configReader;
extern MasterItemList master_item_list;
extern RuleManager rule_manager;
Trade::Trade(Entity* trader1, Entity* trader2) {
this->trader1 = trader1;
@ -36,7 +38,7 @@ int8 Trade::AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 sl
}
Entity* other = GetTradee(character);
int8 result = CheckItem(character, item, other->IsBot());
int8 result = CheckItem(character, item, other);
if (result == 0) {
if (character == trader1) {
@ -59,11 +61,16 @@ int8 Trade::AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 sl
return result;
}
int8 Trade::CheckItem(Entity* trader, Item* item, bool other_is_bot) {
int8 Trade::CheckItem(Entity* trader, Item* item, Entity* other) {
int8 ret = 0;
map<int8, TradeItemInfo>* list = 0;
map<int8, TradeItemInfo>::iterator itr;
bool other_is_bot = false;
if(other)
other_is_bot = other->IsBot();
if (trader == trader1)
list = &trader1_items;
else if (trader == trader2)
@ -83,8 +90,17 @@ int8 Trade::CheckItem(Entity* trader, Item* item, bool other_is_bot) {
if (!other_is_bot) {
if (item->CheckFlag(NO_TRADE))
ret = 2;
if (item->CheckFlag2(HEIRLOOM))
ret = 3;
if (item->CheckFlag2(HEIRLOOM)) {
if(item->grouped_char_ids.find(((Player*)other)->GetCharacterID()) != item->grouped_char_ids.end()) {
double diffInSeconds = 0.0; std::difftime(std::time(nullptr), item->created);
if(item->CheckFlag(ATTUNED) || ((diffInSeconds = std::difftime(std::time(nullptr), item->created)) && diffInSeconds >= rule_manager.GetGlobalRule(R_Player, HeirloomItemShareExpiration)->GetFloat())) {
ret = 3; // denied heirloom cannot be transferred to outside of group after 48 hours (by rule setting) or if already attuned
}
}
else {
ret = 3; // not part of the group/raid
}
}
}
}
}
@ -243,8 +259,8 @@ void Trade::Trader2ItemAdd(Item* item, int8 quantity, int8 slot) {
void Trade::CompleteTrade() {
map<int8, TradeItemInfo>::iterator itr;
map<int32, int8> trader1_item_ids;
map<int32, int8>::iterator itr2;
vector<Item*> trader1_item_pass;
vector<Item*>::iterator itr2;
string log_string = "TradeComplete:\n";
if (trader1->IsPlayer()) {
@ -259,14 +275,19 @@ void Trade::CompleteTrade() {
player->RemoveCoins(trader1_coins);
for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) {
// client->RemoveItem can delete the item so we need to store the item id's and quantity to give to trader2
trader1_item_ids[itr->second.item->details.item_id] = itr->second.quantity;
Item* newitem = new Item(itr->second.item);
newitem->details.count = itr->second.quantity;
trader1_item_pass.push_back(newitem);
log_string += itr->second.item->name + " (" + to_string(itr->second.item->details.item_id) + ") x" + to_string(itr->second.quantity) + "\n";
client->RemoveItem(itr->second.item, itr->second.quantity);
}
player->AddCoins(trader2_coins);
for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) {
client->AddItem(itr->second.item->details.item_id, itr->second.quantity);
Item* newitem = new Item(itr->second.item);
newitem->details.count = itr->second.quantity;
client->AddItem(newitem, nullptr);
}
PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion());
@ -297,8 +318,8 @@ void Trade::CompleteTrade() {
}
player->AddCoins(trader1_coins);
for (itr2 = trader1_item_ids.begin(); itr2 != trader1_item_ids.end(); itr2++) {
client->AddItem(itr2->first, itr2->second);
for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) {
client->AddItem(*itr2, nullptr);
}
PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion());
@ -317,8 +338,8 @@ void Trade::CompleteTrade() {
bot->RemoveItem(itr->second.item);
}
for (itr2 = trader1_item_ids.begin(); itr2 != trader1_item_ids.end(); itr2++) {
bot->GiveItem(itr2->first);
for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) {
bot->GiveItem(*itr2);
}
bot->FinishTrade();
}

View file

@ -26,7 +26,7 @@ public:
bool HasAcceptedTrade(Entity* character);
void CancelTrade(Entity* character);
int8 CheckItem(Entity* trader, Item* item, bool other_is_bot);
int8 CheckItem(Entity* trader, Item* item, Entity* other);
private:

View file

@ -2536,6 +2536,34 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
}
if (player->item_list.HasFreeSlot() || player->item_list.CanStack(item)) {
if (player->item_list.AssignItemToFreeSlot(item)) {
if(item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support
GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo();
if (gmi && gmi->group_id)
{
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
if(members) {
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if ((member->GetZone() != this->GetPlayer()->GetZone()))
continue;
if(member->IsPlayer()) {
item->grouped_char_ids.insert(std::make_pair(((Player*)member)->GetCharacterID(), true));
item->save_needed = true;
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
}
int8 type = CHANNEL_LOOT;
if (entity) {
Message(type, "You loot %s from the corpse of %s", item->CreateItemLink(GetVersion()).c_str(), entity->GetName());