endziu2222
Well-Known Member
- Joined
- Nov 2, 2010
- Messages
- 234
- Solutions
- 1
- Reaction score
- 63
So I have a major problem: sometimes players receive additional broken items like floors or walls along with boss loot, which can also cause crashes. Does anyone know what could possibly trigger this issue or how I can investigate it?
Canary.
I checked loot of each boss for any of the objects found inside the containers and none I can find in monster loot.
View attachment 93920
View attachment 93921
boss_death
Canary.
I checked loot of each boss for any of the objects found inside the containers and none I can find in monster loot.
View attachment 93920
View attachment 93921
LUA:
-- Unused function
function PushValues(buffer, sep, ...)
local argv = { ... }
local argc = #argv
for k, v in ipairs(argv) do
table.insert(buffer, v)
if k < argc and sep then
table.insert(buffer, sep)
end
end
end
function PushSeparated(buffer, sep, ...)
local argv = { ... }
local argc = #argv
for k, v in ipairs(argv) do
table.insert(buffer, v)
if k < argc and sep then
table.insert(buffer, sep)
end
end
end
function InsertItems(buffer, info, parent, items)
local start = info.running
for _, item in ipairs(items) do
if item ~= nil then
if item:getId() == ITEM_REWARD_CONTAINER then
table.insert(buffer, "(")
PushSeparated(buffer, ",", info.playerGuid, 0, parent, item:getId(), item:getSubType(), db.escapeString(item:serializeAttributes()))
table.insert(buffer, "),")
else
info.running = info.running + 1
table.insert(buffer, "(")
PushSeparated(buffer, ",", info.playerGuid, parent, info.running, item:getId(), item:getSubType(), db.escapeString(item:serializeAttributes()))
table.insert(buffer, "),")
end
if item:isContainer() then
local size = item:getSize()
if size > 0 then
local subItems = {}
for i = 1, size do
table.insert(subItems, item:getItem(i - 1))
end
InsertItems(buffer, info, info.running, subItems)
end
end
end
end
return info.running - start
end
function InsertRewardItems(playerGuid, timestamp, itemList)
local maxSidQueryResult = db.query('select max(`sid`) as max_sid from `player_rewards` where player_id = ' .. playerGuid .. ';')
local bagSid = (Result.getNumber(maxSidQueryResult, 'max_sid') or 0) + 1;
local nextSid = bagSid + 1;
local buffer = { 'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES' }
local info = {
playerGuid = playerGuid,
running = nextSid
}
local bag = Game.createItem(ITEM_REWARD_CONTAINER)
bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)
if itemList then
for _, p in ipairs(itemList) do
bag:addItem(p[1], p[2])
end
end
local total = InsertItems(buffer, info, bagSid, { bag })
if total ~= 0 then
local insertItemsQuery = table.concat(buffer):sub(1, -2) .. ";";
db.query(insertItemsQuery)
end
end
function GetPlayerStats(bossId, playerGuid, autocreate)
local ret = GlobalBosses[bossId][playerGuid]
if not ret and autocreate then
ret = {
bossId = bossId,
damageIn = 0, -- damage taken from the boss
healing = 0, -- healing (other players) done
}
GlobalBosses[bossId][playerGuid] = ret
return ret
end
return ret
end
function ResetAndSetTargetList(creature)
if not creature then
return
end
local bossId = creature:getId()
local info = GlobalBosses[bossId]
-- Reset all players' status
for _, player in pairs(info) do
player.active = false
end
-- Set all players in boss' target list as active in the fight
local targets = creature:getTargetList()
for _, target in ipairs(targets) do
if target:isPlayer() then
local stats = GetPlayerStats(bossId, target:getGuid(), true)
stats.playerId = target:getId() -- Update player id
stats.active = true
end
end
end
boss_death
Code:
local bossDeath = CreatureEvent("BossDeath")
function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
-- Deny summons and players
if not creature or creature:isPlayer() or creature:getMaster() then
return true
end
-- Boss function
local monsterType = creature:getType()
-- Make sure it is a boss
if monsterType and monsterType:isRewardBoss() then
if not corpse:isContainer() then
logger.warn("[bossDeath.onDeath] Corpse (id: {}) for reward boss {} is not a container.", corpse:getId(), creature:getName())
end
corpse:registerReward()
local bossId = creature:getId()
local rewardId = corpse:getAttribute(ITEM_ATTRIBUTE_DATE)
ResetAndSetTargetList(creature)
-- Avoid dividing by zero
local totalDamageOut, totalDamageIn, totalHealing = 0.1, 0.1, 0.1
local scores = {}
local info = GlobalBosses[bossId]
local damageMap = creature:getDamageMap()
for guid, stats in pairs(info) do
local player = Player(stats.playerId)
local part = damageMap[stats.playerId]
local damageOut, damageIn, healing = (stats.damageOut or 0) + (part and part.total or 0), stats.damageIn or 0, stats.healing or 0
totalDamageOut = totalDamageOut + damageOut
totalDamageIn = totalDamageIn + damageIn
totalHealing = totalHealing + healing
table.insert(scores, {
player = player,
guid = guid,
damageOut = damageOut,
damageIn = damageIn,
healing = healing,
})
end
local participants = 0
for _, con in ipairs(scores) do
local score = (con.damageOut / totalDamageOut) + (con.damageIn / totalDamageIn) + (con.healing / totalHealing)
-- Normalize to 0-1
con.score = score / 3
if score ~= 0 then
participants = participants + 1
end
end
table.sort(scores, function(a, b) return a.score > b.score end)
local expectedScore = 1 / participants
for _, con in ipairs(scores) do
-- Ignoring stamina for now because I heard you get receive rewards even when it's depleted
if con.score ~= 0 then
local reward, stamina, player
if con.player then
player = con.player;
else
player = Game.getOfflinePlayer(con.guid)
end
reward = player:getReward(rewardId, true)
stamina = player:getStamina()
local lootFactor = 1
-- Tone down the loot a notch if there are many participants
lootFactor = lootFactor / participants ^ (1 / 3)
-- Increase the loot multiplicatively by how many times the player surpassed the expected score
lootFactor = lootFactor * (1 + lootFactor) ^ (con.score / expectedScore)
-- Bosstiary Loot Bonus
local rolls = 1
local isBoostedBoss = creature:getName():lower() == (Game.getBoostedBoss()):lower()
local bossRaceIds = { player:getSlotBossId(1), player:getSlotBossId(2) }
local isBoss = table.contains(bossRaceIds, monsterType:bossRaceId()) or isBoostedBoss
if isBoss and monsterType:bossRaceId() ~= 0 then
if monsterType:bossRaceId() == player:getSlotBossId(1) then
rolls = rolls + player:getBossBonus(1) / 100
elseif monsterType:bossRaceId() == player:getSlotBossId(2) then
rolls = rolls + player:getBossBonus(2) / 100
else
rolls = rolls + configManager.getNumber(configKeys.BOOSTED_BOSS_LOOT_BONUS) / 100
end
end
-- decide if we get an extra roll
if math.random(0, 100) < (rolls % 1) * 100 then
rolls = math.ceil(rolls)
else
rolls = math.floor(rolls)
end
local playerLoot = monsterType:getBossReward(lootFactor, _ == 1, false, {})
for _ = 2, rolls do
playerLoot = monsterType:getBossReward(lootFactor, false, true, playerLoot)
end
for id, lootInfo in pairs(playerLoot) do
reward:addItem(id, lootInfo.count)
end
if con.player then
local lootMessage = ("The following items dropped by %s are available in your reward chest: %s"):format(creature:getName(), reward:getContentDescription())
if rolls > 1 then
lootMessage = lootMessage .. " (boss bonus)"
end
if stamina > 840 then
reward:getContentDescription(lootMessage)
end
player:sendTextMessage(MESSAGE_LOOT, lootMessage)
else
player:save()
end
end
end
GlobalBosses[bossId] = nil
end
return true
end
bossDeath:register()
C++:
bool IOLoginDataSave::saveRewardItems(Player* player) {
if (!player) {
g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__);
return false;
}
std::ostringstream query;
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);
ItemRewardList rewardListItems;
if (!rewardList.empty()) {
for (const auto &rewardId : rewardList) {
auto reward = player->getReward(rewardId, false);
if (!reward->empty() && (getTimeMsNow() - rewardId <= 1000 * 60 * 60 * 24 * 7)) {
rewardListItems.emplace_back(0, reward);
}
}
DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
PropWriteStream propWriteStream;
if (!saveItems(player, rewardListItems, rewardQuery, propWriteStream)) {
return false;
}
}
return true;
}
C++:
void IOLoginDataLoad::loadRewardItems(Player* player) {
if (!player) {
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
return;
}
ItemsMap rewardItems;
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())) {
loadItems(rewardItems, result, *player);
bindRewardBag(player, rewardItems);
insertItemsIntoRewardBag(rewardItems);
}
}
C++:
void Monster::dropLoot(Container* corpse, Creature*) {
if (corpse && lootDrop) {
// Only fiendish drops sliver
if (ForgeClassifications_t classification = getMonsterForgeClassification();
// Condition
classification == ForgeClassifications_t::FORGE_FIENDISH_MONSTER) {
auto minSlivers = g_configManager().getNumber(FORGE_MIN_SLIVERS);
auto maxSlivers = g_configManager().getNumber(FORGE_MAX_SLIVERS);
auto sliverCount = static_cast<uint16_t>(uniform_random(minSlivers, maxSlivers));
Item* sliver = Item::CreateItem(ITEM_FORGE_SLIVER, sliverCount);
if (g_game().internalAddItem(corpse, sliver) != RETURNVALUE_NOERROR) {
corpse->internalAddThing(sliver);
}
}
if (!this->isRewardBoss() && g_configManager().getNumber(RATE_LOOT) > 0) {
g_callbacks().executeCallback(EventCallback_t::monsterOnDropLoot, &EventCallback::monsterOnDropLoot, this, corpse);
g_callbacks().executeCallback(EventCallback_t::monsterPostDropLoot, &EventCallback::monsterPostDropLoot, this, corpse);
}
}
}
Last edited: