diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index 668c67f92b9..f5b2d75f5ba 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1569,21 +1569,3 @@ function Creature:applyZoneEffect(var, combat, zoneName) return true end - -function string.toPosition(str) - local patterns = { - -- table format - "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", - -- Position format - "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", - -- x, y, z format - "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", - } - - for _, pattern in ipairs(patterns) do - local x, y, z = string.match(str, pattern) - if x and y and z then - return Position(tonumber(x), tonumber(y), tonumber(z)) - end - end -end diff --git a/data-otservbr-global/scripts/quests/dangerous_depth/movements_boss_entrance.lua b/data-otservbr-global/scripts/quests/dangerous_depth/movements_boss_entrance.lua index 20300f3ac5f..a33dccd762d 100644 --- a/data-otservbr-global/scripts/quests/dangerous_depth/movements_boss_entrance.lua +++ b/data-otservbr-global/scripts/quests/dangerous_depth/movements_boss_entrance.lua @@ -37,7 +37,7 @@ function bossEntrance.onStepIn(creature, item, position, fromPosition, toPositio if timeLeft > 0 then player:teleportTo(fromPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. bossName .. " again!") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. Game.getTimeInWords(timeLeft) .. " to face " .. bossName .. " again!") player:getPosition():sendMagicEffect(CONST_ME_POFF) return true end diff --git a/data-otservbr-global/scripts/quests/feaster_of_souls/actions_portal_brain_head.lua b/data-otservbr-global/scripts/quests/feaster_of_souls/actions_portal_brain_head.lua index aebc0b3ca7a..badaffad5a2 100644 --- a/data-otservbr-global/scripts/quests/feaster_of_souls/actions_portal_brain_head.lua +++ b/data-otservbr-global/scripts/quests/feaster_of_souls/actions_portal_brain_head.lua @@ -124,7 +124,7 @@ function teleportBoss.onStepIn(creature, item, position, fromPosition) if timeLeft > 0 then player:teleportTo(config.exitPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. Game.getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!") player:getPosition():sendMagicEffect(CONST_ME_POFF) return false end diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index ec1a92f3528..eee28b632d6 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -216,7 +216,7 @@ function Player:onLookInBattleList(creature, distance) if master and table.contains(summons, creature:getName():lower()) then local familiarSummonTime = master:kv():get("familiar-summon-time") or 0 description = description .. " (Master: " .. master:getName() .. "). \z - It will disappear in " .. getTimeInWords(familiarSummonTime - os.time()) + It will disappear in " .. Game.getTimeInWords(familiarSummonTime - os.time()) end end if self:getGroup():getAccess() then diff --git a/data/libs/functions/boss_lever.lua b/data/libs/functions/boss_lever.lua index 9cd577ee911..b1141619ce2 100644 --- a/data/libs/functions/boss_lever.lua +++ b/data/libs/functions/boss_lever.lua @@ -191,7 +191,7 @@ function BossLever:onUse(player) local currentTime = os.time() if lastEncounter and currentTime < lastEncounter then local timeLeft = lastEncounter - currentTime - local timeMessage = getTimeInWords(timeLeft) .. " to face " .. self.name .. " again!" + local timeMessage = Game.getTimeInWords(timeLeft) .. " to face " .. self.name .. " again!" local message = "You have to wait " .. timeMessage if currentPlayer ~= player then diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua index c2349ce7fd6..2327a1cf937 100644 --- a/data/libs/functions/functions.lua +++ b/data/libs/functions/functions.lua @@ -65,43 +65,6 @@ function getTitle(uid) return false end -function getTimeInWords(secsParam) - local secs = tonumber(secsParam) - local days = math.floor(secs / (24 * 3600)) - secs = secs - (days * 24 * 3600) - local hours, minutes, seconds = getHours(secs), getMinutes(secs), getSeconds(secs) - local timeStr = "" - - if days > 0 then - timeStr = days .. (days > 1 and " days" or " day") - end - - if hours > 0 then - if timeStr ~= "" then - timeStr = timeStr .. ", " - end - - timeStr = timeStr .. hours .. (hours > 1 and " hours" or " hour") - end - - if minutes > 0 then - if timeStr ~= "" then - timeStr = timeStr .. ", " - end - - timeStr = timeStr .. minutes .. (minutes > 1 and " minutes" or " minute") - end - - if seconds > 0 then - if timeStr ~= "" then - timeStr = timeStr .. " and " - end - - timeStr = timeStr .. seconds .. (seconds > 1 and " seconds" or " second") - end - return timeStr -end - function getLootRandom(modifier) local multi = (configManager.getNumber(configKeys.RATE_LOOT) * SCHEDULE_LOOT_RATE) * (modifier or 1) return math.random(0, MAX_LOOTCHANCE) * 100 / math.max(1, multi) @@ -949,31 +912,6 @@ function SetInfluenced(monsterType, monster, player, influencedLevel) monster:setForgeStack(influencedLevel) end -function getHours(seconds) - return math.floor((seconds / 60) / 60) -end - -function getMinutes(seconds) - return math.floor(seconds / 60) % 60 -end - -function getSeconds(seconds) - return seconds % 60 -end - -function getTime(seconds) - local hours, minutes = getHours(seconds), getMinutes(seconds) - if minutes > 59 then - minutes = minutes - hours * 60 - end - - if minutes < 10 then - minutes = "0" .. minutes - end - - return hours .. ":" .. minutes .. "h" -end - function ReloadDataEvent(cid) local player = Player(cid) if not player then diff --git a/data/libs/functions/game.lua b/data/libs/functions/game.lua index e4d40bef318..a7c7f7617ce 100644 --- a/data/libs/functions/game.lua +++ b/data/libs/functions/game.lua @@ -133,3 +133,37 @@ function Game.setStorageValue(key, value) globalStorageTable[key] = value end + +function Game.getTimeInWords(seconds) + local days = math.floor(seconds / (24 * 3600)) + seconds = seconds % (24 * 3600) + local hours = math.floor(seconds / 3600) + seconds = seconds % 3600 + local minutes = math.floor(seconds / 60) + seconds = seconds % 60 + + local timeParts = {} + + if days > 0 then + table.insert(timeParts, days .. (days > 1 and " days" or " day")) + end + + if hours > 0 then + table.insert(timeParts, hours .. (hours > 1 and " hours" or " hour")) + end + + if minutes > 0 then + table.insert(timeParts, minutes .. (minutes > 1 and " minutes" or " minute")) + end + + if seconds > 0 or #timeParts == 0 then + table.insert(timeParts, seconds .. (seconds > 1 and " seconds" or " second")) + end + + local timeStr = table.concat(timeParts, ", ") + local lastComma = timeStr:find(", [%a%d]+$") + if lastComma then + timeStr = timeStr:sub(1, lastComma - 1) .. " and" .. timeStr:sub(lastComma + 1) + end + return timeStr +end diff --git a/data/libs/functions/string.lua b/data/libs/functions/string.lua index 9d746b82e3a..42b7a8bab4b 100644 --- a/data/libs/functions/string.lua +++ b/data/libs/functions/string.lua @@ -129,3 +129,19 @@ end string.capitalize = function(str) return str:gsub("%f[%a].", string.upper) end + +function string.toPosition(inputString) + local positionPatterns = { + "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", + "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", + "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", + } + + for _, pattern in ipairs(positionPatterns) do + local posX, posY, posZ = string.match(inputString, pattern) + if posX and posY and posZ then + return Position(tonumber(posX), tonumber(posY), tonumber(posZ)) + end + end + return nil +end diff --git a/data/libs/systems/concoctions.lua b/data/libs/systems/concoctions.lua index 72547a8a1ab..ee856e16482 100644 --- a/data/libs/systems/concoctions.lua +++ b/data/libs/systems/concoctions.lua @@ -158,7 +158,7 @@ function Concoction:init(player, sendMessage) return end eventPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your concoction " .. name .. " is still active for another " .. duration .. ".") - end, 500, player:getId(), self.name, getTimeInWords(self:timeLeft(player))) + end, 500, player:getId(), self.name, Game.getTimeInWords(self:timeLeft(player))) end end @@ -180,7 +180,7 @@ function Concoction:activate(player, item) local cooldown = self:cooldown() if self:lastActivatedAt(player) + cooldown > os.time() then local cooldownLeft = self:lastActivatedAt(player) + cooldown - os.time() - player:sendTextMessage(MESSAGE_FAILURE, "You must wait " .. getTimeInWords(cooldownLeft) .. " before using " .. item:getName() .. " again.") + player:sendTextMessage(MESSAGE_FAILURE, "You must wait " .. Game.getTimeInWords(cooldownLeft) .. " before using " .. item:getName() .. " again.") return true end self:timeLeft(player, self:totalDuration()) @@ -191,7 +191,7 @@ function Concoction:activate(player, item) self.config.callback(player, self.config) else self:addCondition(player) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have activated " .. item:getName() .. ". It will last for " .. getTimeInWords(self:totalDuration()) .. consumptionString .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have activated " .. item:getName() .. ". It will last for " .. Game.getTimeInWords(self:totalDuration()) .. consumptionString .. ".") if self:tickType() == ConcoctionTickType.Online then addEvent(tick, updateInterval * 1000, self.id, player:getId(), updateInterval) end diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua index 68824cb9b44..e0a1ab86788 100644 --- a/data/scripts/eventcallbacks/player/on_look.lua +++ b/data/scripts/eventcallbacks/player/on_look.lua @@ -44,7 +44,7 @@ local function handleCreatureDescription(inspectedThing, lookDistance) local monsterMaster = inspectedThing:getMaster() if monsterMaster and table.contains({ "sorcerer familiar", "knight familiar", "druid familiar", "paladin familiar" }, inspectedThing:getName():lower()) then local summonTimeRemaining = monsterMaster:kv():get("familiar-summon-time") or 0 - descriptionText = string.format("%s (Master: %s). It will disappear in %s", descriptionText, monsterMaster:getName(), getTimeInWords(summonTimeRemaining - os.time())) + descriptionText = string.format("%s (Master: %s). It will disappear in %s", descriptionText, monsterMaster:getName(), Game.getTimeInWords(summonTimeRemaining - os.time())) end end diff --git a/src/io/fileloader.hpp b/src/io/fileloader.hpp index fc4f71023e1..863837464ae 100644 --- a/src/io/fileloader.hpp +++ b/src/io/fileloader.hpp @@ -73,9 +73,12 @@ class PropStream { return false; } - std::span sourceSpan(reinterpret_cast(p), sizeof(T)); - std::array tempBuffer; - std::ranges::copy(sourceSpan, tempBuffer.begin()); + std::span charSpan { p, sizeof(T) }; + auto byteSpan = std::as_bytes(charSpan); + + std::array tempBuffer; + std::ranges::copy(byteSpan, tempBuffer.begin()); + ret = std::bit_cast(tempBuffer); p += sizeof(T); @@ -93,10 +96,11 @@ class PropStream { return false; } - std::vector tempBuffer(strLen); - std::span sourceSpan(reinterpret_cast(p), strLen); + std::vector tempBuffer(strLen); + std::span sourceSpan(p, strLen); std::ranges::copy(sourceSpan, tempBuffer.begin()); - ret.assign(reinterpret_cast(tempBuffer.data()), strLen); + + ret.assign(tempBuffer.begin(), tempBuffer.end()); p += strLen; @@ -136,9 +140,11 @@ class PropWriteStream { template void write(T add) { - char* addr = reinterpret_cast(&add); - std::span sourceSpan(addr, sizeof(T)); - std::ranges::copy(sourceSpan, std::back_inserter(buffer)); + static_assert(std::is_trivially_copyable_v, "Type T must be trivially copyable"); + + auto byteArray = std::bit_cast>(add); + std::span charSpan(byteArray); + std::ranges::copy(charSpan, std::back_inserter(buffer)); } void writeString(const std::string &str) { diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index e4ff1e93b8e..05e72ba856e 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -741,10 +741,6 @@ void IOLoginDataLoad::loadPlayerPreyClass(const std::shared_ptr &player, query << "SELECT * FROM `player_prey` WHERE `player_id` = " << player->getGUID(); if ((result = db.storeQuery(query.str()))) { do { - auto selectedRaceId = result->getNumber("raceid"); - if (selectedRaceId == 0) { - continue; - } auto slot = std::make_unique(static_cast(result->getNumber("slot"))); auto state = static_cast(result->getNumber("state")); if (slot->id == PreySlot_Two && state == PreyDataState_Locked) { @@ -756,7 +752,7 @@ void IOLoginDataLoad::loadPlayerPreyClass(const std::shared_ptr &player, } else { slot->state = state; } - slot->selectedRaceId = selectedRaceId; + slot->selectedRaceId = result->getNumber("raceid"); slot->option = static_cast(result->getNumber("option")); slot->bonus = static_cast(result->getNumber("bonus_type")); slot->bonusRarity = static_cast(result->getNumber("bonus_rarity")); @@ -792,10 +788,6 @@ void IOLoginDataLoad::loadPlayerTaskHuntingClass(const std::shared_ptr & query << "SELECT * FROM `player_taskhunt` WHERE `player_id` = " << player->getGUID(); if ((result = db.storeQuery(query.str()))) { do { - auto selectedRaceId = result->getNumber("raceid"); - if (selectedRaceId == 0) { - continue; - } auto slot = std::make_unique(static_cast(result->getNumber("slot"))); auto state = static_cast(result->getNumber("state")); if (slot->id == PreySlot_Two && state == PreyTaskDataState_Locked) { @@ -807,7 +799,7 @@ void IOLoginDataLoad::loadPlayerTaskHuntingClass(const std::shared_ptr & } else { slot->state = state; } - slot->selectedRaceId = selectedRaceId; + slot->selectedRaceId = result->getNumber("raceid"); slot->upgrade = result->getNumber("upgrade"); slot->rarity = static_cast(result->getNumber("rarity")); slot->currentKills = result->getNumber("kills");