Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: badge system #2533

Merged
merged 12 commits into from
May 4, 2024
Merged
33 changes: 33 additions & 0 deletions data/scripts/talkactions/god/manage_badge.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local addBadge = TalkAction("/addbadge")

function addBadge.onSay(player, words, param)
-- create log
logCommand(player, words, param)

if param == "" then
player:sendCancelMessage("Command param required.")
return true
end

local split = param:split(",")
if not split[2] then
player:sendCancelMessage("Insufficient parameters. Usage: /addbadge playerName, badgeID")
return true
end

local target = Player(split[1])
if not target then
player:sendCancelMessage("A player with that name is not online.")
return true
end

-- Trim left
split[2] = split[2]:gsub("^%s*(.-)$", "%1")
local id = tonumber(split[2])
target:addBadge(id)
return true
end

addBadge:separator(" ")
addBadge:groupType("god")
addBadge:register()
25 changes: 25 additions & 0 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,31 @@ CREATE TABLE IF NOT EXISTS `player_storage` (
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_summary`
CREATE TABLE IF NOT EXISTS `player_summary` (
`player_id` int(11) NOT NULL,
`title` VARCHAR(250) NOT NULL DEFAULT '0',
`charms` VARCHAR(250) NOT NULL DEFAULT '0',
`goshnar` VARCHAR(250) NOT NULL DEFAULT '0',
`drome` VARCHAR(250) NOT NULL DEFAULT '0',
`xp_boosts` VARCHAR(250) NOT NULL DEFAULT '0',
`rewards_collection` VARCHAR(250) NOT NULL DEFAULT '0',
`prey_cards` VARCHAR(250) NOT NULL DEFAULT '0',
`hirelings` VARCHAR(250) NOT NULL DEFAULT '0',
`achievements_points` VARCHAR(250) NOT NULL DEFAULT '0',
`login_streak` VARCHAR(250) NOT NULL DEFAULT '0',
`task_points` VARCHAR(250) NOT NULL DEFAULT '0',
`map_area` VARCHAR(250) NOT NULL DEFAULT '0',
`hireling_outfits` BLOB NULL,
`hireling_jobs` BLOB NULL,
`house_items` BLOB NULL,
`blessings` BLOB NULL,
`achievements_unlockeds` BLOB NULL,
INDEX `player_id` (`player_id`),
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `store_history`
CREATE TABLE IF NOT EXISTS `store_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
Expand Down
1 change: 1 addition & 0 deletions src/canary_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ void CanaryServer::loadModules() {
g_game().loadBoostedCreature();
g_ioBosstiary().loadBoostedBoss();
g_ioprey().initializeTaskHuntOptions();
g_game().getCyclopediaStatistics();
}

void CanaryServer::modulesLoadHelper(bool loaded, std::string moduleName) {
Expand Down
1 change: 1 addition & 0 deletions src/creatures/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ target_sources(${PROJECT_NAME}_lib PRIVATE
players/storages/storages.cpp
players/player.cpp
players/achievement/player_achievement.cpp
players/cyclopedia/player_badge.cpp
players/wheel/player_wheel.cpp
players/wheel/wheel_gems.cpp
players/vocations/vocation.cpp
Expand Down
160 changes: 160 additions & 0 deletions src/creatures/players/cyclopedia/player_badge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <[email protected]>
* 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 "player_badge.hpp"

#include "creatures/players/player.hpp"
#include "game/game.hpp"
#include "kv/kv.hpp"

PlayerBadge::PlayerBadge(Player &player) :
m_player(player) { }

bool PlayerBadge::hasBadge(uint8_t id) const {
if (id == 0) {
return false;
}

if (auto it = std::find_if(m_badgesUnlocked.begin(), m_badgesUnlocked.end(), [id](auto badge_it) {
return badge_it.first.m_id == id;
});
it != m_badgesUnlocked.end()) {
return true;
}

return false;
}

bool PlayerBadge::add(uint8_t id, uint32_t timestamp /* = 0*/) {
if (hasBadge(id)) {
return false;
}

const Badge &badge = g_game().getBadgeByIdOrName(id);
if (badge.m_id == 0) {
return false;
}

int toSaveTimeStamp = timestamp != 0 ? timestamp : (OTSYS_TIME() / 1000);
getUnlockedKV()->set(badge.m_name, toSaveTimeStamp);
m_badgesUnlocked.push_back({ badge, toSaveTimeStamp });
m_badgesUnlocked.shrink_to_fit();
return true;
}

std::vector<std::pair<Badge, uint32_t>> PlayerBadge::getUnlockedBadges() const {

Check warning on line 53 in src/creatures/players/cyclopedia/player_badge.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

[cppcheck] reported by reviewdog 🐶 The function 'getUnlockedBadges' is never used. Raw Output: src/creatures/players/cyclopedia/player_badge.cpp:53:The function 'getUnlockedBadges' is never used.
return m_badgesUnlocked;
}

void PlayerBadge::checkAndUpdateNewBadges() {
// CyclopediaBadgeType_t::ACCOUNT_AGE
for (const auto &badge : g_game().getBadgesByType(CyclopediaBadgeType_t::ACCOUNT_AGE)) {
if (accountAge(badge.m_amount)) {
add(badge.m_id);
}
}

// CyclopediaBadgeType_t::LOYALTY
for (const auto &badge : g_game().getBadgesByType(CyclopediaBadgeType_t::LOYALTY)) {
if (loyalty(badge.m_amount)) {
add(badge.m_id);
}
}

// CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL
for (const auto &badge : g_game().getBadgesByType(CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL)) {
if (accountAllLevel(badge.m_amount)) {
add(badge.m_id);
}
}

// CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS
for (const auto &badge : g_game().getBadgesByType(CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS)) {
if (accountAllVocations(badge.m_amount)) {
add(badge.m_id);
}
}

loadUnlockedBadges();
}

void PlayerBadge::loadUnlockedBadges() {
const auto &unlockedBadges = getUnlockedKV()->keys();
g_logger().debug("[{}] - Loading unlocked badges: {}", __FUNCTION__, unlockedBadges.size());
for (const auto &badgeName : unlockedBadges) {
const Badge &badge = g_game().getBadgeByIdOrName(0, badgeName);
if (badge.m_id == 0) {
g_logger().error("[{}] - Badge {} not found.", __FUNCTION__, badgeName);
continue;
}

g_logger().debug("[{}] - Badge {} found for player {}.", __FUNCTION__, badge.m_name, m_player.getName());

m_badgesUnlocked.push_back({ badge, getUnlockedKV()->get(badgeName)->getNumber() });
}
}

const std::shared_ptr<KV> &PlayerBadge::getUnlockedKV() {
if (m_badgeUnlockedKV == nullptr) {
m_badgeUnlockedKV = m_player.kv()->scoped("badges")->scoped("unlocked");
}

return m_badgeUnlockedKV;
}

// Badge Calculate Functions
bool PlayerBadge::accountAge(uint8_t amount) {
return std::floor(m_player.getLoyaltyPoints() / 365) >= amount;
elsongabriel marked this conversation as resolved.
Show resolved Hide resolved
}

bool PlayerBadge::loyalty(uint8_t amount) {
return m_player.getLoyaltyPoints() >= amount;
}

bool PlayerBadge::accountAllLevel(uint8_t amount) {
uint8_t total = 0;
for (const auto &player : g_game().getPlayersByAccount(m_player.getAccount(), true)) {
total = total + player->getLevel();

Check warning on line 125 in src/creatures/players/cyclopedia/player_badge.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

[cppcheck] reported by reviewdog 🐶 Consider using std::accumulate algorithm instead of a raw loop. Raw Output: src/creatures/players/cyclopedia/player_badge.cpp:125:Consider using std::accumulate algorithm instead of a raw loop.
}
return total >= amount;
}

bool PlayerBadge::accountAllVocations(uint8_t amount) {
auto knight = false;
auto paladin = false;
auto druid = false;
auto sorcerer = false;
for (const auto &player : g_game().getPlayersByAccount(m_player.getAccount(), true)) {
if (player->getLevel() >= amount) {
auto vocationEnum = player->getPlayerVocationEnum();
if (vocationEnum == Vocation_t::VOCATION_KNIGHT_CIP) {
knight = true;
} else if (vocationEnum == Vocation_t::VOCATION_SORCERER_CIP) {
sorcerer = true;
} else if (vocationEnum == Vocation_t::VOCATION_PALADIN_CIP) {
paladin = true;
} else if (vocationEnum == Vocation_t::VOCATION_DRUID_CIP) {
druid = true;
}
}
}
return knight && paladin && druid && sorcerer;
}

bool PlayerBadge::tournamentParticipation(uint8_t skill) {

Check warning on line 152 in src/creatures/players/cyclopedia/player_badge.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

[cppcheck] reported by reviewdog 🐶 The function 'tournamentParticipation' is never used. Raw Output: src/creatures/players/cyclopedia/player_badge.cpp:152:The function 'tournamentParticipation' is never used.
// todo check if is used
return false;
}

bool PlayerBadge::tournamentPoints(uint8_t race) {

Check warning on line 157 in src/creatures/players/cyclopedia/player_badge.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

[cppcheck] reported by reviewdog 🐶 The function 'tournamentPoints' is never used. Raw Output: src/creatures/players/cyclopedia/player_badge.cpp:157:The function 'tournamentPoints' is never used.
// todo check if is used
return false;
}
64 changes: 64 additions & 0 deletions src/creatures/players/cyclopedia/player_badge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <[email protected]>
* 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/
*/

#pragma once

class Player;
class KV;

struct Badge {
uint8_t m_id = 0;
CyclopediaBadgeType_t m_type;

Check failure on line 17 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-20.04-linux-debug

‘CyclopediaBadgeType_t’ does not name a type

Check failure on line 17 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

‘CyclopediaBadgeType_t’ does not name a type
std::string m_name;
uint16_t m_amount = 0;

Badge() = default;

Badge(uint8_t id, CyclopediaBadgeType_t type, const std::string &name, uint16_t amount) :

Check failure on line 23 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-20.04-linux-debug

‘CyclopediaBadgeType_t’ has not been declared

Check failure on line 23 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

‘CyclopediaBadgeType_t’ has not been declared
m_id(id), m_type(type), m_name(name), m_amount(amount) { }

Check failure on line 24 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-20.04-linux-debug

class ‘Badge’ does not have any field named ‘m_type’

Check failure on line 24 in src/creatures/players/cyclopedia/player_badge.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

class ‘Badge’ does not have any field named ‘m_type’

bool operator==(const Badge &other) const {
return m_id == other.m_id;
}
};

namespace std {
template <>
struct hash<Badge> {
std::size_t operator()(const Badge &b) const {
return hash<uint8_t>()(b.m_id); // Use o ID como base para o hash
}
};
}

class PlayerBadge {
public:
explicit PlayerBadge(Player &player);

[[nodiscard]] bool hasBadge(uint8_t id) const;
bool add(uint8_t id, uint32_t timestamp = 0);
[[nodiscard]] std::vector<std::pair<Badge, uint32_t>> getUnlockedBadges() const;
void checkAndUpdateNewBadges();
void loadUnlockedBadges();
const std::shared_ptr<KV> &getUnlockedKV();

// Badge Calculate Functions
bool accountAge(uint8_t amount);
bool loyalty(uint8_t amount);
bool accountAllLevel(uint8_t amount);
bool accountAllVocations(uint8_t amount);
bool tournamentParticipation(uint8_t skill);
bool tournamentPoints(uint8_t race);

private:
// {badge ID, time when it was unlocked}
std::shared_ptr<KV> m_badgeUnlockedKV;
std::vector<std::pair<Badge, uint32_t>> m_badgesUnlocked;
Player &m_player;
};
Loading
Loading