Skip to content

Commit

Permalink
fix: empty reward bags should disappear and others things (opentibiab…
Browse files Browse the repository at this point in the history
…r#846)

This change aims to resolve the issue with reward bags in the game. Currently, reward bags do not disappear after closing and reopening the reward chest. On the global tibia, it is possible to have more than 100 reward bags, but this leads to strange behaviour if you re-login. Additionally, when multiple bosses are killed at the same time, the rewards are mixed and only one reward bag is created with the combined loot.

The goal is to make the reward bags disappear properly after closing and reopening the reward chest, fix the limit of 100 reward bag, and prevent the mixing of rewards when multiple bosses are killed at the same time.
  • Loading branch information
ElimarCosta authored Feb 13, 2023
1 parent a26cd9c commit 8a1046a
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 154 deletions.
2 changes: 1 addition & 1 deletion data-canary/scripts/reward_chest/boss_death.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn
-- Make sure it is a boss
if monsterType and monsterType:isRewardBoss() then
local bossId = creature:getId()
local timestamp = os.time()
local timestamp = systemTime()

ResetAndSetTargetList(creature)

Expand Down
4 changes: 3 additions & 1 deletion data-otservbr-global/migrations/25.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
Spdlog.info("Updating database to version 26 (reward bag fix)")
db.query("UPDATE player_rewards SET pid = 0 WHERE itemtype = 19202;")
return true
end
3 changes: 3 additions & 0 deletions data-otservbr-global/migrations/26.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
end
2 changes: 1 addition & 1 deletion data-otservbr-global/scripts/reward_chest/boss_death.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn
-- Make sure it is a boss
if monsterType and monsterType:isRewardBoss() then
local bossId = creature:getId()
local timestamp = os.time()
local timestamp = systemTime()

ResetAndSetTargetList(creature)

Expand Down
20 changes: 8 additions & 12 deletions data/libs/reward_boss/reward_boss.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ function InsertItems(buffer, info, parent, items, bagSid)
if _ ~= 1 or parent > 100 then
table.insert(buffer, ",")
end
if item:getId() == ITEM_REWARD_CONTAINER then
if item:getId() == ITEM_REWARD_CONTAINER then
table.insert(buffer, "(")
PushSeparated(buffer, ",", info.playerGuid, parent, bagSid, item:getId(), item:getSubType(), db.escapeString(item:serializeAttributes()))
PushSeparated(buffer, ",", info.playerGuid, 0, bagSid, item:getId(), item:getSubType(), db.escapeString(item:serializeAttributes()))
table.insert(buffer, ")")
else
info.running = info.running + 1
Expand All @@ -56,19 +56,15 @@ function InsertItems(buffer, info, parent, items, bagSid)
end

function InsertRewardItems(playerGuid, timestamp, itemList)
db.asyncStoreQuery('select `pid`, `sid`, (SELECT max(`sid`) as sid from `player_rewards` where player_id = '..playerGuid..') as max_sid from `player_rewards` where `pid` = (select max(`pid`) from `player_rewards` where player_id = ' .. playerGuid .. ' and `pid` < 100);',
db.asyncStoreQuery('select max(`sid`) as max_sid from `player_rewards` where player_id = '..playerGuid..';',
function(query)
local lastPid = Result.getDataInt(query, 'pid') or 0
local bagSid = Result.getDataInt(query, 'sid') or 100
local lastSid = Result.getDataInt(query, 'max_sid') or 101
if lastPid ~= 0 then
db.query('UPDATE `player_rewards` SET `sid` = `sid`+1 WHERE `sid`> '..bagSid..' ORDER BY `sid` DESC')
lastSid = lastSid+1
end
local lastSid = Result.getDataInt(query, 'max_sid') or 0;
local bagSid = lastSid + 1;
local nextSid = bagId + 1;
local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'}
local info = {
playerGuid = playerGuid,
running = lastSid
running = nextSid
}
local bag = Game.createItem(ITEM_REWARD_CONTAINER)
bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)
Expand All @@ -77,7 +73,7 @@ function InsertRewardItems(playerGuid, timestamp, itemList)
bag:addItem(p[1], p[2])
end
end
local total = InsertItems(buffer, info, lastPid + 1, {bag}, bagSid+1)
local total = InsertItems(buffer, info, lastPid + 1, {bag}, bagSid)
table.insert(buffer, ";")

if total ~= 0 then
Expand Down
45 changes: 32 additions & 13 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ Player::~Player() {
it.second->decrementReferenceCounter();
}

for (const auto &it : rewardMap) {
it.second->decrementReferenceCounter();
}

for (const auto &it : quickLootContainers) {
it.second->decrementReferenceCounter();
}
Expand Down Expand Up @@ -469,6 +465,7 @@ void Player::updateInventoryImbuement() {
bool isInProtectionZone = playerTile && playerTile->hasFlag(TILESTATE_PROTECTIONZONE);
// Check if the player is in fight mode
bool isInFightMode = hasCondition(CONDITION_INFIGHT);

// Iterate through all items in the player's inventory
for (auto item : getAllInventoryItems()) {
// Iterate through all imbuement slots on the item
Expand Down Expand Up @@ -660,11 +657,36 @@ void Player::closeContainer(uint8_t cid) {
Container* container = openContainer.container;
openContainers.erase(it);

if (container && container->getID() == ITEM_BROWSEFIELD) {
if (!container) {
return;
}

if (container->isAnykindOfRewardContainer() && !hasAnykindOfRewardContainerOpen()) {
removeEmptyRewards();
}

if (container->getID() == ITEM_BROWSEFIELD) {
container->decrementReferenceCounter();
}
}

void Player::removeEmptyRewards() {
std::erase_if(rewardMap, [this](const auto &rewardBag) {
auto [id, reward] = rewardBag;
if (reward->empty()) {
this->getRewardChest()->removeThing(reward.get(), 1);
return true;
}
return false;
});
}

bool Player::hasAnykindOfRewardContainerOpen() const {
return std::ranges::any_of(openContainers.begin(), openContainers.end(), [](const auto &containerPair) {
return containerPair.second.container->isAnykindOfRewardContainer();
});
}

void Player::setContainerIndex(uint8_t cid, uint16_t index) {
auto it = openContainers.find(cid);
if (it == openContainers.end()) {
Expand Down Expand Up @@ -1034,31 +1056,28 @@ RewardChest* Player::getRewardChest() {
return rewardChest;
}

Reward* Player::getReward(uint32_t rewardId, bool autoCreate) {
std::shared_ptr<Reward> Player::getReward(const uint64_t rewardId, const bool autoCreate) {
auto it = rewardMap.find(rewardId);
if (it != rewardMap.end()) {
return it->second;
}

if (!autoCreate) {
return nullptr;
}

Reward* reward = new Reward();
reward->incrementReferenceCounter();
const auto reward = std::make_shared<Reward>();
reward->setAttribute(ItemAttribute_t::DATE, rewardId);
rewardMap[rewardId] = reward;

g_game().internalAddItem(getRewardChest(), reward, INDEX_WHEREEVER, FLAG_NOLIMIT);
g_game().internalAddItem(getRewardChest(), reward.get(), INDEX_WHEREEVER, FLAG_NOLIMIT);

return reward;
}

void Player::removeReward(uint32_t rewardId) {
void Player::removeReward(uint64_t rewardId) {
rewardMap.erase(rewardId);
}

void Player::getRewardList(std::vector<uint32_t> &rewards) {
void Player::getRewardList(std::vector<uint64_t> &rewards) const {
rewards.reserve(rewardMap.size());
for (auto &it : rewardMap) {
rewards.push_back(it.first);
Expand Down
11 changes: 7 additions & 4 deletions src/creatures/players/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,9 @@ class Player final : public Creature, public Cylinder {
void addConditionSuppressions(uint32_t conditions);
void removeConditionSuppressions(uint32_t conditions);

Reward* getReward(uint32_t rewardId, bool autoCreate);
void removeReward(uint32_t rewardId);
void getRewardList(std::vector<uint32_t> &rewards);
std::shared_ptr<Reward> getReward(const uint64_t rewardId, const bool autoCreate);
void removeReward(uint64_t rewardId);
void getRewardList(std::vector<uint64_t> &rewards) const;
RewardChest* getRewardChest();

DepotChest* getDepotChest(uint32_t depotId, bool autoCreate);
Expand Down Expand Up @@ -2299,7 +2299,7 @@ class Player final : public Creature, public Cylinder {
{ SKILL_CRITICAL_HIT_CHANCE, g_configManager().getNumber(CRITICALCHANCE) }
};

std::map<uint32_t, Reward*> rewardMap;
std::map<uint64_t, std::shared_ptr<Reward>> rewardMap;

std::map<ObjectCategory_t, Container*> quickLootContainers;
std::vector<ForgeHistory> forgeHistoryVector;
Expand Down Expand Up @@ -2562,6 +2562,9 @@ class Player final : public Creature, public Cylinder {
void updateDamageReductionFromItemImbuement(std::array<double_t, COMBAT_COUNT> &combatReductionMap, Item* item, uint16_t combatTypeIndex) const;
void updateDamageReductionFromItemAbility(std::array<double_t, COMBAT_COUNT> &combatReductionMap, const Item* item, uint16_t combatTypeIndex) const;
double_t calculateDamageReduction(double_t currentTotal, int16_t resistance) const;

void removeEmptyRewards();
bool hasAnykindOfRewardContainerOpen() const;
};

#endif // SRC_CREATURES_PLAYERS_PLAYER_H_
48 changes: 48 additions & 0 deletions src/io/functions/iologindata_load_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,51 @@ void IOLoginDataLoad::loadPlayerForgeHistory(Player* player, DBResult_ptr result
} while (result->next());
}
}

void IOLoginDataLoad::loadRewardItems(Player* player) {
ItemMap itemMap;
std::ostringstream query;
query.str(std::string());
query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_rewards` WHERE `player_id` = "
<< player->getGUID() << " ORDER BY `pid`, `sid` ASC";
if (auto result = Database::getInstance().storeQuery(query.str())) {
IOLoginData::loadItems(itemMap, result, *player);
bindRewardBag(player, itemMap);
insertItemsIntoRewardBag(itemMap);
}
}

void IOLoginDataLoad::bindRewardBag(Player* player, IOLoginData::ItemMap &itemMap) {
for (auto &[id, itemPair] : itemMap) {
const auto [item, pid] = itemPair;
if (pid == 0) {
auto reward = player->getReward(item->getAttribute<uint64_t>(ItemAttribute_t::DATE), true);
if (reward) {
itemPair = std::pair<Item*, int32_t>(reward->getItem(), player->getRewardChest()->getID());
}
} else {
break;
}
}
}

void IOLoginDataLoad::insertItemsIntoRewardBag(const IOLoginData::ItemMap &itemMap) {
for (const auto &it : std::views::reverse(itemMap)) {
const std::pair<Item*, int32_t> &pair = it.second;
Item* item = pair.first;
int32_t pid = pair.second;
if (pid == 0) {
break;
}

ItemMap::const_iterator it2 = itemMap.find(pid);
if (it2 == itemMap.end()) {
continue;
}

Container* container = it2->second.first->getContainer();
if (container) {
container->internalAddThing(item);
}
}
}
5 changes: 5 additions & 0 deletions src/io/functions/iologindata_load_player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
class IOLoginDataLoad : public IOLoginData {
public:
static void loadPlayerForgeHistory(Player* player, DBResult_ptr result);
static void loadRewardItems(Player* player);

private:
static void bindRewardBag(Player* player, ItemMap &itemMap);
static void insertItemsIntoRewardBag(const ItemMap &itemMap);
};

#endif // SRC_IO_FUNCTIONS_IOLOGINDATALOAD_HPP_
30 changes: 30 additions & 0 deletions src/io/functions/iologindata_save_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,33 @@ bool IOLoginDataSave::savePlayerForgeHistory(Player* player) {

return true;
}

bool IOLoginDataSave::saveRewardItems(Player* player) {
std::ostringstream query;
query.str(std::string());
query << "DELETE FROM `player_rewards` WHERE `player_id` = " << player->getGUID();

if (!Database::getInstance().executeQuery(query.str())) {
return false;
}

std::vector<uint64_t> rewardList;
player->getRewardList(rewardList);

ItemBlockList itemList;
if (!rewardList.empty()) {
for (const auto &rewardId : rewardList) {
auto reward = player->getReward(rewardId, false);
if (!reward->empty() && (getTimeNow() - rewardId / 1000 <= 60 * 60 * 24 * 7)) {
itemList.emplace_back(0, reward.get());
}
}

DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
PropWriteStream propWriteStream;
if (!saveItems(player, itemList, rewardQuery, propWriteStream)) {
return false;
}
}
return true;
}
1 change: 1 addition & 0 deletions src/io/functions/iologindata_save_player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class IOLoginDataSave : public IOLoginData {
public:
static bool savePlayerForgeHistory(Player* player);
static bool saveRewardItems(Player* player);
};

#endif // SRC_IO__FUNCTIONS_IOLOGINDATASAVE_HPP_
Loading

0 comments on commit 8a1046a

Please sign in to comment.