Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,4 @@ database_backup
# CLION
cmake-build-*
compile_commands.json
/.trae
2 changes: 1 addition & 1 deletion data-otservbr-global/lib/core/storages.lua
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Storage = {
-- Reserved in Global.Storage.FamiliarSummonEvent10 = 30054
-- Reserved in Global.Storage.FamiliarSummonEvent60 = 30055
ChayenneKeyTime = 30056,
FreeQuests = 30057,
-- Reserved in Global.Storage.FreeQuests = 30057 : Free Quests System cpp
ShrineEntrance = 30060,
PlayerWeaponReward = 30061,
--[[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
local stage = configManager.getNumber(configKeys.FREE_QUEST_STAGE)

local questTable = {
questTable = {
{ storageName = "BigfootsBurden.QuestLine", storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 2 },
{ storageName = "BigfootsBurden.QuestLine", storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 4 },
{ storageName = "BigfootsBurden.QuestLine", storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 7 },
Expand Down Expand Up @@ -362,51 +360,3 @@ local questTable = {
{ storageName = "TheWhiteRavenMonastery.Diary", storage = Storage.Quest.U7_24.TheWhiteRavenMonastery.Diary, storageValue = 2 },
{ storageName = "TheWhiteRavenMonastery.Door", storage = Storage.Quest.U7_24.TheWhiteRavenMonastery.Door, storageValue = 1 },
}

-- from Position: (33201, 31762, 1)
-- to Position: (33356, 31309, 4)
local function playerFreeQuestStart(playerId, index)
local player = Player(playerId)
if not player then
return
end

for i = 1, 5 do
index = index + 1
if not questTable[index] then
player:sendTextMessage(MESSAGE_LOOK, "Adding free quests completed.")
player:setStorageValue(Storage.FreeQuests, stage)
return
end

local questData = questTable[index]
local currentStorageValue = player:getStorageValue(questData.storage)

if not questData.storage then
logger.warn("[Freequest System]: error storage for '" .. questData.storageName .. "' is nil for the index")
elseif currentStorageValue ~= questData.storageValue then
player:setStorageValue(questData.storage, questData.storageValue)
elseif currentStorageValue == -1 then
logger.warn("[Freequest System]: warning Storage '" .. questData.storageName .. "' currently nil for player ID " .. playerId)
end
end

addEvent(playerFreeQuestStart, 500, playerId, index)
end

local freeQuests = CreatureEvent("FreeQuests")

function freeQuests.onLogin(player)
if not configManager.getBoolean(configKeys.TOGGLE_FREE_QUEST) or player:getStorageValue(Storage.FreeQuests) == stage then
return true
end

player:sendTextMessage(MESSAGE_LOOK, "Adding free acccess quests to your character.")
addEvent(playerFreeQuestStart, 500, player:getId(), 0)
player:addOutfit(251, 0)
player:addOutfit(252, 0)

return true
end

freeQuests:register()
40 changes: 40 additions & 0 deletions data/global.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
math.randomseed(os.time())

dofile(DATA_DIRECTORY .. "/lib/lib.lua")
dofile(DATA_DIRECTORY .. "/scripts/creaturescripts/customs/freequests.lua")
local startupFile = io.open(DATA_DIRECTORY .. "/startup/startup.lua", "r")
if startupFile ~= nil then
io.close(startupFile)
Expand Down Expand Up @@ -106,6 +107,45 @@ table.contains = function(array, value)
return false
end

-- Free Quests System Integration
function setTableQuestFree(questTable)
if not questTable or type(questTable) ~= "table" then
logger.error("[ERROR] setTableQuestFree: Invalid questTable parameter")
return false
end

local validQuests = 0
local invalidQuests = 0

-- Validate and process each quest entry
for i, quest in ipairs(questTable) do
if type(quest) == "table" and quest.storageName and type(quest.storageName) == "string" and quest.storage and type(quest.storage) == "number" and quest.storageValue and type(quest.storageValue) == "number" then
if Game.addFreeQuestData then
if Game.addFreeQuestData(quest.storageName, quest.storage, quest.storageValue) then
validQuests = validQuests + 1
else
logger.warning(string.format("[WARNING] Failed to add quest: %s (storage: %d, value: %d)", quest.storageName, quest.storage, quest.storageValue))
invalidQuests = invalidQuests + 1
end
else
logger.error("[ERROR] Game.addFreeQuestData function not available")
return false
end
else
logger.warning(string.format("[WARNING] Invalid quest entry at index %d", i))
invalidQuests = invalidQuests + 1
end
end

logger.info(string.format("FreeQuests loaded %d valid quests, %d invalid entries", validQuests, invalidQuests))
return validQuests > 0
end

-- Load Free Quests data if questTable is available and system is enabled
if questTable and type(questTable) == "table" and configManager.getBoolean(configKeys.TOGGLE_FREE_QUEST) then
setTableQuestFree(questTable)
end

-- for use of: data\scripts\globalevents\customs\save_interval.lua
SAVE_INTERVAL_TYPE = configManager.getString(configKeys.SAVE_INTERVAL_TYPE)
SAVE_INTERVAL_CONFIG_TIME = configManager.getNumber(configKeys.SAVE_INTERVAL_TIME)
Expand Down
4 changes: 4 additions & 0 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "enums/player_icons.hpp"
#include "enums/player_cyclopedia.hpp"
#include "game/game.hpp"
#include "game/functions/game_freequests.hpp"
#include "game/modal_window/modal_window.hpp"
#include "game/scheduling/dispatcher.hpp"

Expand Down Expand Up @@ -6042,6 +6043,9 @@ void Player::onPlacedCreature() {
removePlayer(true);
}

// Apply free quests system
g_gameFreeQuests().applyFreeQuestsToPlayer(static_self_cast<Player>());

this->onChangeZone(this->getZoneType());

sendUnjustifiedPoints();
Expand Down
5 changes: 2 additions & 3 deletions src/database/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ void Database::createDatabaseBackup(bool compress) const {

// Get current time for formatting
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::string formattedDate = fmt::format("{:%Y-%m-%d}", fmt::localtime(now_c));
std::string formattedTime = fmt::format("{:%H-%M-%S}", fmt::localtime(now_c));
std::string formattedDate = fmt::format("{:%Y-%m-%d}", now);
std::string formattedTime = fmt::format("{:%H-%M-%S}", now);

// Create a backup directory based on the current date
std::string backupDir = fmt::format("database_backup/{}/", formattedDate);
Expand Down
1 change: 1 addition & 0 deletions src/game/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
target_sources(
${PROJECT_NAME}_lib
PRIVATE functions/game_reload.cpp
functions/game_freequests.cpp
game.cpp
bank/bank.cpp
movement/position.cpp
Expand Down
127 changes: 127 additions & 0 deletions src/game/functions/game_freequests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <opentibiabr@outlook.com>
* Repository: https://github.com/opentibiabr/canary
* License: https://github.com/opentibiabr/canary/blob/main/LICENSE
* Contributors: https://github.com/opentibiabr/canary/graphs/contributors
* Website: https://docs.opentibiabr.com/
*/

#include "pch.hpp"

#include "config/configmanager.hpp"
#include "creatures/players/player.hpp"
#include "game/functions/game_freequests.hpp"
#include "game/scheduling/dispatcher.hpp"
#include "lib/di/container.hpp"

GameFreeQuests &GameFreeQuests::getInstance() {

Check warning on line 18 in src/game/functions/game_freequests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Declare this method "[[nodiscard]]" as it does not modify the current object and doesn't have any parameter.

See more on https://sonarcloud.io/project/issues?id=opentibiabr_canary&issues=AZq8sdo3grgfoeWa7TQ3&open=AZq8sdo3grgfoeWa7TQ3&pullRequest=3732
return inject<GameFreeQuests>();
}

bool GameFreeQuests::applyFreeQuestsToPlayer(std::shared_ptr<Player> player) {
if (!player || !isEnabled()) {
return false;
}

if (player->getStorageValue(FreeQuests) == m_currentStage) {
return true;
}

player->sendTextMessage(MESSAGE_LOOK, "Adding free access quests to your character.");

player->addOutfit(251, 0); // Male citizen
player->addOutfit(252, 0); // Female citizen

g_dispatcher().addEvent([this, playerId = player->getGUID()]() { applyQuestStoragesAsync(playerId); }, "GameFreeQuests::applyQuestStoragesAsync");

return true;
}

void GameFreeQuests::applyQuestStoragesAsync(uint32_t playerId) {
auto player = g_game().getPlayerByGUID(playerId);

Check failure on line 42 in src/game/functions/game_freequests.cpp

View workflow job for this annotation

GitHub Actions / macos-macos-debug

use of undeclared identifier 'g_game'

Check failure on line 42 in src/game/functions/game_freequests.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-debug

‘g_game’ was not declared in this scope

Check failure on line 42 in src/game/functions/game_freequests.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

‘g_game’ was not declared in this scope
if (!player) {
return;
}

for (const auto &quest : m_questData) {
player->addStorageValue(quest.storage, quest.storageValue, true);
}

player->addStorageValue(FreeQuests, m_currentStage);
player->sendTextMessage(MESSAGE_LOOK, "Free quests have been applied to your character!");

g_logger().info("[{}] Applied {} free quests to player: {}", __FUNCTION__, m_questData.size(), player->getName());
}

bool GameFreeQuests::isEnabled() const {
return g_configManager().getBoolean(TOGGLE_FREE_QUEST);
}

uint32_t GameFreeQuests::getCurrentStage() const {

Check warning on line 61 in src/game/functions/game_freequests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Declare this method "[[nodiscard]]" as it is "const" and doesn't have any parameter.

See more on https://sonarcloud.io/project/issues?id=opentibiabr_canary&issues=AZq8sdo3grgfoeWa7TQ4&open=AZq8sdo3grgfoeWa7TQ4&pullRequest=3732
return m_currentStage;
}

size_t GameFreeQuests::getQuestCount() const {

Check warning on line 65 in src/game/functions/game_freequests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Declare this method "[[nodiscard]]" as it is "const" and doesn't have any parameter.

See more on https://sonarcloud.io/project/issues?id=opentibiabr_canary&issues=AZq8sdo3grgfoeWa7TQ5&open=AZq8sdo3grgfoeWa7TQ5&pullRequest=3732
return m_questData.size();
}

void GameFreeQuests::clear() {
m_questData.clear();
m_currentStage = 0;
}

bool GameFreeQuests::isValidQuestData(const QuestData &questData) const {
if (questData.storageName.empty()) {
g_logger().warn("[{}] Invalid quest data: empty storageName, storage={}, storageValue={}", __FUNCTION__, questData.storage, questData.storageValue);
return false;
}

if (questData.storage == 0) {
g_logger().warn("[{}] Invalid quest data: storageName='{}', storage=0, storageValue={}", __FUNCTION__, questData.storageName, questData.storageValue);
return false;
}

return true;
}

bool GameFreeQuests::addQuestData(const std::string &storageName, uint32_t storage, int32_t storageValue) {
static bool systemInitialized = false;
if (!systemInitialized) {
if (!g_configManager().getBoolean(TOGGLE_FREE_QUEST)) {
g_logger().info("[{}] Free quest system is disabled", __FUNCTION__);
return false;
}

m_currentStage = g_configManager().getNumber(FREE_QUEST_STAGE);
if (m_currentStage <= 0) {
g_logger().warn("[{}] Invalid free quest stage: {}", __FUNCTION__, m_currentStage);
return false;
}

systemInitialized = true;
}

// Validate parameters
if (storageName.empty()) {
g_logger().warn("[GameFreeQuests::addQuestData] - Storage name is empty");
return false;
}

if (storage == 0) {
g_logger().warn("[GameFreeQuests::addQuestData] - Storage ID cannot be 0 for quest: {}", storageName);
return false;
}

for (const auto &existingQuest : m_questData) {
if (existingQuest.storage == storage && existingQuest.storageValue == storageValue) {
g_logger().warn("[GameFreeQuests::addQuestData] - Duplicate quest entry: {} (storage: {}, value: {}) already exists", storageName, storage, storageValue);
return false;
}
}

m_questData.emplace_back(storageName, storage, storageValue);

Check warning on line 123 in src/game/functions/game_freequests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the value returned from "emplace_back".

See more on https://sonarcloud.io/project/issues?id=opentibiabr_canary&issues=AZq8sdo3grgfoeWa7TQ6&open=AZq8sdo3grgfoeWa7TQ6&pullRequest=3732
g_logger().debug("[GameFreeQuests::addQuestData] - Added quest: {} (storage: {}, value: {})", storageName, storage, storageValue);

return true;
}
60 changes: 60 additions & 0 deletions src/game/functions/game_freequests.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <opentibiabr@outlook.com>
* Repository: https://github.com/opentibiabr/canary
* License: https://github.com/opentibiabr/canary/blob/main/LICENSE
* Contributors: https://github.com/opentibiabr/canary/graphs/contributors
* Website: https://docs.opentibiabr.com/
*/

/**
* @class GameFreeQuests
* @brief Manages free quests for players.
*
* This class handles the application of free quests to players, including
* loading quest data, applying quest storages, and managing the current stage
* of free quests.
*
* The class is implemented as a singleton and provides thread-safe
* asynchronous application of quest storages to players.
*/

#pragma once

struct QuestData {
std::string storageName;
uint32_t storage;
int32_t storageValue;

QuestData(const std::string &name, uint32_t storageId, int32_t value) :
storageName(name), storage(storageId), storageValue(value) { }
};

class GameFreeQuests {
public:
static GameFreeQuests &getInstance();

GameFreeQuests() = default;
~GameFreeQuests() = default;

// Non-copyable
GameFreeQuests(const GameFreeQuests &) = delete;
GameFreeQuests &operator=(const GameFreeQuests &) = delete;

bool applyFreeQuestsToPlayer(std::shared_ptr<Player> player);
bool addQuestData(const std::string &storageName, uint32_t storage, int32_t storageValue);
bool isEnabled() const;
uint32_t getCurrentStage() const;
size_t getQuestCount() const;
void clear();

private:
std::vector<QuestData> m_questData;
uint32_t m_currentStage = 0;
uint16_t FreeQuests = 30057;

void applyQuestStoragesAsync(uint32_t playerId);
bool isValidQuestData(const QuestData &questData) const;
};

constexpr auto g_gameFreeQuests = &GameFreeQuests::getInstance;
Loading
Loading