Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
15 changes: 15 additions & 0 deletions config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ experienceStages = {
{ minlevel = 101, multiplier = 3 }
}

-- Loyalty system
loyaltyBonuses = {
{ min = 0, max = 360, bonus = 0 },
{ min = 360, max = 720, bonus = 0.05 },
{ min = 720, max = 1080, bonus = 0.1 },
{ min = 1080, max = 1440, bonus = 0.15 },
{ min = 1440, max = 1800, bonus = 0.2 },
{ min = 1800, max = 2160, bonus = 0.25 },
{ min = 2160, max = 2520, bonus = 0.3 },
{ min = 2520, max = 2880, bonus = 0.35 },
{ min = 2880, max = 3240, bonus = 0.4 },
{ min = 3240, max = 3600, bonus = 0.45 },
{ min = 3600, bonus = 0.5 }
}

-- Rates
-- NOTE: rateExp is not used if you have enabled stages above
rateExp = 5
Expand Down
6 changes: 6 additions & 0 deletions data/creaturescripts/scripts/login.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ function onLogin(player)
player:setStorageValue(PlayerStorageKeys.achievementsTotal, player:getAchievementPoints())
end

local loyaltyBonus = player:getLoyaltyBonus()
if loyaltyBonus > 0 then
loginStr = string.format("Due to your long-term loyalty to %s, you currently benefit from a %d%% bonus on all of your skills.", serverName, loyaltyBonus * 100)
player:sendTextMessage(MESSAGE_EVENT_DEFAULT, loginStr)
end

-- Events
player:registerEvent("PlayerDeath")
player:registerEvent("DropLoot")
Expand Down
17 changes: 17 additions & 0 deletions data/globalevents/scripts/startup.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
local function updateLoyaltyPoints()
local updatedAt = 0
local resultId = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'loyalty_updated'")
if resultId then
updatedAt = result.getNumber(resultId, "value")
result.free(resultId)
end
local now = os.time()
if (now - updatedAt) >= 24 * 60 * 60 then
db.asyncQuery("UPDATE `accounts` SET `loyalty_points` = `loyalty_points` + 1 WHERE `premium_ends_at` > " .. now)
db.query("UPDATE `server_config` SET `value` = " .. now .. " WHERE `config` = 'loyalty_updated'")
end
end

function onStartup()
db.query("TRUNCATE TABLE `players_online`")
db.asyncQuery("DELETE FROM `guild_wars` WHERE `status` = 0")
Expand Down Expand Up @@ -56,6 +70,9 @@ function onStartup()
end
end

-- update loyalty points
updateLoyaltyPoints()

-- setup highscores variables
setUpHighscores()
end
33 changes: 33 additions & 0 deletions data/lib/core/game.lua
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,36 @@ do
return transaction:commit()
end
end

do
LOYALTY_TITLES = {
{ points = 7000, article = "an ", title = "Enlightened" },
{ points = 6000, title = "Savant" },
{ points = 5000, title = "Sage" },
{ points = 4000, title = "Guardian" },
{ points = 3000, title = "Keeper" },
{ points = 2000, title = "Warrior" },
{ points = 1000, title = "Squire" },
{ points = 400, title = "Warden" },
{ points = 200, title = "Steward" },
{ points = 100, title = "Sentinel" },
{ points = 50, title = "Scout" }
}

function Game.getLoyaltyTitle(points, article)
article = article or false
for i = 1, #LOYALTY_TITLES do
if points >= LOYALTY_TITLES[i].points then
local s = ""
if article then
if not LOYALTY_TITLES[i].article then
LOYALTY_TITLES[i].article = "a "
end
s = LOYALTY_TITLES[i].article
end
return s .. LOYALTY_TITLES[i].title
end
end
return ""
end
end
16 changes: 12 additions & 4 deletions data/lib/core/highscores.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ HIGHSCORES_CATEGORY_DISTANCE_FIGHTING = 7
HIGHSCORES_CATEGORY_SHIELDING = 8
HIGHSCORES_CATEGORY_FISHING = 9
HIGHSCORES_CATEGORY_ACHIEVEMENTS = 10
HIGHSCORES_CATEGORY_LOYALTY = 11

HIGHSCORES_ACTION_BROWSE = 0
HIGHSCORES_ACTION_OWN = 1
Expand All @@ -62,7 +63,8 @@ HIGHSCORES_CATEGORIES = {
[HIGHSCORES_CATEGORY_DISTANCE_FIGHTING] = { name = "Distance Fighting", type = HIGHSCORES_TYPE_SKILLS },
[HIGHSCORES_CATEGORY_SHIELDING] = { name = "Shielding", type = HIGHSCORES_TYPE_SKILLS },
[HIGHSCORES_CATEGORY_FISHING] = { name = "Fishing", type = HIGHSCORES_TYPE_SKILLS },
[HIGHSCORES_CATEGORY_ACHIEVEMENTS] = { name = "Achievement Points", type = HIGHSCORES_TYPE_POINTS }
[HIGHSCORES_CATEGORY_ACHIEVEMENTS] = { name = "Achievement Points", type = HIGHSCORES_TYPE_POINTS },
[HIGHSCORES_CATEGORY_LOYALTY] = { name = "Loyalty Points", type = HIGHSCORES_TYPE_POINTS }
}

HIGHSCORES_QUERIES = {
Expand Down Expand Up @@ -108,6 +110,12 @@ HIGHSCORES_QUERIES = {
LEFT JOIN `players` ON `players`.`id` = `storages`.`player_id`
WHERE `storages`.`key` = ]] .. PlayerStorageKeys.achievementsTotal .. [[
AND `deletion` = 0 %s
ORDER BY `points` DESC, `name` ASC LIMIT ]] .. highscoresMaxResults,
[HIGHSCORES_CATEGORY_LOYALTY] = [[
SELECT `accounts`.`id`, `players`.`name`, `vocation`, `level`, `accounts`.`loyalty_points` AS `points`
FROM `accounts`
INNER JOIN `players` ON `players`.`account_id` = `accounts`.`id` AND `players`.`main_character` = 1
AND `players`.`deletion` = 0 %s
ORDER BY `points` DESC, `name` ASC LIMIT ]] .. highscoresMaxResults
}

Expand Down Expand Up @@ -163,7 +171,7 @@ local function render(self, player)
end

if self.params.action == HIGHSCORES_ACTION_OWN then
self.params.page = self:findPlayer(player:getGuid())
self.params.page = self:findPlayer(self.params.category == HIGHSCORES_CATEGORY_LOYALTY and player:getAccountId() or player:getGuid())
self.params.action = HIGHSCORES_ACTION_BROWSE
end

Expand Down Expand Up @@ -202,14 +210,14 @@ local function fetch(self)
id = result.getNumber(resultId, "id"),
rank = rank,
name = result.getString(resultId, "name"),
title = "", -- TODO: loyalty system
title = "",
vocation = clientIdsVocation[result.getNumber(resultId, "vocation")] or VOCATION_NONE,
world = world,
level = result.getNumber(resultId, "level"),
points = result.getNumber(resultId, "points")
}
rank = rank + 1
until not result.next(resultId)
until not result.next(resultId)
result.free(resultId)
end

Expand Down
27 changes: 24 additions & 3 deletions data/lib/core/player.lua
Original file line number Diff line number Diff line change
Expand Up @@ -614,16 +614,16 @@ function Player.sendHighscores(self, entries, params)
for _, entry in pairs(entries.data) do
msg:addU32(entry.rank)
msg:addString(entry.name)
msg:addString(entry.title)
msg:addString(params.category == HIGHSCORES_CATEGORY_LOYALTY and (Game.getLoyaltyTitle(entry.points) .. " of " .. entry.world) or "")
msg:addByte(entry.vocation)
msg:addString(entry.world)
msg:addU16(entry.level)
msg:addByte(self:getGuid() == entry.id and 0x01 or 0x00)
msg:addByte(entry.id == (params.category == HIGHSCORES_CATEGORY_LOYALTY and self:getAccountId() or self:getGuid()) and 0x01 or 0x00)
msg:addU64(entry.points)
end

msg:addByte(0xFF) -- unknown
msg:addByte(0x00) -- display loyalty title column
msg:addByte(params.category == HIGHSCORES_CATEGORY_LOYALTY and 0x01 or 0x00)
msg:addByte(HIGHSCORES_CATEGORIES[params.category].type or 0x00)

msg:addU32(entries.ts)
Expand Down Expand Up @@ -737,3 +737,24 @@ function Player.disableLoginMusic(self)
msg:delete()
return true
end

function Player.addLoyaltyPoints(self, points)
self:setLoyaltyPoints(self:getLoyaltyPoints() + points)
return true
end

function Player.removeLoyaltyPoints(self, points)
return self:addLoyaltyPoints(-points)
end

function Player.getLoyaltyTitle(self, article)
return Game.getLoyaltyTitle(self:getLoyaltyPoints(), article or false)
end

function Player.getLoyaltyTitleDescription(self, distance)
local pronoun = (self:getSex() == PLAYERSEX_MALE and "He" or "She") .. " is "
if distance == -1 then
pronoun = "You are "
end
return pronoun .. string.format("%s of %s.", self:getLoyaltyTitle(true), configManager.getString(configKeys.SERVER_NAME))
end
6 changes: 5 additions & 1 deletion data/migrations/36.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
function onUpdateDatabase()
return false
print("> Updating database to version 37 (loyalty system)")
db.query("ALTER TABLE `accounts` ADD COLUMN `loyalty_points` SMALLINT UNSIGNED NOT NULL DEFAULT 0 AFTER `premium_ends_at`")
db.query("ALTER TABLE `players` ADD COLUMN `main_character` TINYINT(1) NOT NULL DEFAULT 0 AFTER `account_id`")
Comment thread
reyaleman marked this conversation as resolved.
Outdated
db.query("INSERT INTO `server_config` (`config`, `value`) VALUES ('loyalty_updated', '0')")
return true
end
3 changes: 3 additions & 0 deletions data/migrations/37.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false
end
8 changes: 8 additions & 0 deletions data/scripts/eventcallbacks/player/default_onLook.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ local ec = EventCallback

ec.onLook = function(self, thing, position, distance, description)
local description = "You see " .. thing:getDescription(distance)

if thing:isPlayer() then
local loyaltyDescription = thing:getLoyaltyTitleDescription(distance)
if loyaltyDescription ~= "" then
description = description .. "\n" .. loyaltyDescription
end
end

if self:getGroup():getAccess() then
if thing:isItem() then
description = string.format("%s\nItem ID: %d", description, thing:getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ local ec = EventCallback

ec.onLookInBattleList = function(self, creature, distance)
local description = "You see " .. creature:getDescription(distance)

if creature:isPlayer() then
local loyaltyDescription = creature:getLoyaltyTitleDescription(distance)
if loyaltyDescription ~= "" then
description = description .. "\n" .. loyaltyDescription
end
end

if self:getGroup():getAccess() then
local str = "%s\nHealth: %d / %d"
if creature:isPlayer() and creature:getMaxMana() > 0 then
Expand Down
12 changes: 5 additions & 7 deletions data/scripts/network/cyclopedia_character.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,15 @@ local function sendGeneralStats(self, msg)
msg:addByte(CYCLOPEDIA_SKILL_MAGIC)
msg:addU16(self:getMagicLevel())
msg:addU16(self:getBaseMagicLevel())
msg:addU16(self:getBaseMagicLevel()) -- base + loyalty bonus
msg:addU16(self:getMagicLevelPercent() * 100)
msg:addU16(self:getLoyaltyMagicLevel())
msg:addU16(self:getLoyaltyMagicLevelPercent())

for i = SKILL_FIST, SKILL_FISHING, 1 do
msg:addByte(clientSkillsId[i])
msg:addU16(self:getEffectiveSkillLevel(i))
msg:addU16(self:getSkillLevel(i))

-- base + loyalty bonus
msg:addU16(self:getSkillLevel(i))
msg:addU16(self:getSkillPercent(i) * 100)
msg:addU16(self:getLoyaltySkillLevel(i))
msg:addU16(self:getLoyaltySkillPercent(i))
end

msg:addByte(0) -- magic boost (element and value)
Expand Down Expand Up @@ -177,7 +175,7 @@ local function sendBadges(self, msg)
msg:addByte(0x01) -- show account info
msg:addByte(0x01) -- account online
msg:addByte(self:isPremium() and 1 or 0)
msg:addString("") -- loyalty title
msg:addString(self:getLoyaltyTitle() .. " of " .. configManager.getString(configKeys.SERVER_NAME))

msg:addByte(0) -- badges count
-- structure:
Expand Down
4 changes: 3 additions & 1 deletion schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS `accounts` (
`secret` char(16) DEFAULT NULL,
`type` int NOT NULL DEFAULT '1',
`premium_ends_at` int unsigned NOT NULL DEFAULT '0',
`loyalty_points` smallint unsigned NOT NULL DEFAULT '0',
`email` varchar(255) NOT NULL DEFAULT '',
`creation` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
Expand All @@ -16,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `players` (
`name` varchar(255) NOT NULL,
`group_id` int NOT NULL DEFAULT '1',
`account_id` int NOT NULL DEFAULT '0',
`main_character` tinyint NOT NULL DEFAULT '0',
Comment thread
reyaleman marked this conversation as resolved.
Outdated
`level` int NOT NULL DEFAULT '1',
`vocation` int NOT NULL DEFAULT '0',
`health` int NOT NULL DEFAULT '150',
Expand Down Expand Up @@ -379,7 +381,7 @@ CREATE TABLE IF NOT EXISTS `towns` (
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '36'), ('players_record', '0');
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '36'), ('players_record', '0'), ('loyalty_updated', '0');

DROP TRIGGER IF EXISTS `ondelete_players`;
DROP TRIGGER IF EXISTS `oncreate_guilds`;
Expand Down
1 change: 1 addition & 0 deletions src/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct Account
uint32_t id = 0;
time_t premiumEndsAt = 0;
AccountType_t accountType = ACCOUNT_TYPE_NORMAL;
uint16_t loyaltyPoints = 0;

Account() = default;
};
Expand Down
41 changes: 41 additions & 0 deletions src/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,30 @@ ExperienceStages loadXMLStages()
return stages;
}

LoyaltyBonuses loadLuaLoyaltyBonuses(lua_State* L)
{
LoyaltyBonuses bonuses;

lua_getglobal(L, "loyaltyBonuses");
if (!lua_istable(L, -1)) {
return {};
}

lua_pushnil(L);
while (lua_next(L, -2) != 0) {
const auto tableIndex = lua_gettop(L);
auto min = LuaScriptInterface::getField<uint16_t>(L, tableIndex, "min", 0);
auto max = LuaScriptInterface::getField<uint16_t>(L, tableIndex, "max", std::numeric_limits<uint16_t>::max());
auto bonus = LuaScriptInterface::getField<float>(L, tableIndex, "bonus", 0.f);
bonuses.emplace_back(min, max, bonus);
lua_pop(L, 4);
}
lua_pop(L, 1);

std::sort(bonuses.begin(), bonuses.end());
return bonuses;
}

} // namespace

bool ConfigManager::load()
Expand Down Expand Up @@ -296,6 +320,9 @@ bool ConfigManager::load()
}
expStages.shrink_to_fit();

loyaltyBonuses = loadLuaLoyaltyBonuses(L);
loyaltyBonuses.shrink_to_fit();

loaded = true;
lua_close(L);

Expand Down Expand Up @@ -345,6 +372,20 @@ float ConfigManager::getExperienceStage(uint32_t level) const
return std::get<2>(*it);
}

float ConfigManager::getLoyaltyBonus(uint16_t points) const
{
auto it = std::find_if(loyaltyBonuses.begin(), loyaltyBonuses.end(), [points](auto&& bonus) {
auto&& [minPoints, maxPoints, _] = bonus;

Check notice

Code scanning / CodeQL

Unused local variable

Variable (unnamed local variable) is not used.

Check notice

Code scanning / CodeQL

Unused local variable

Variable _ is not used.
return points >= minPoints && points <= maxPoints;
});

if (it == loyaltyBonuses.end()) {
return 0.f;
}

return std::get<2>(*it);
}

bool ConfigManager::setString(string_config_t what, std::string_view value)
{
if (what >= LAST_STRING_CONFIG) {
Expand Down
3 changes: 3 additions & 0 deletions src/configmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define FS_CONFIGMANAGER_H

using ExperienceStages = std::vector<std::tuple<uint32_t, uint32_t, float>>;
using LoyaltyBonuses = std::vector<std::tuple<uint16_t, uint16_t, float>>;

class ConfigManager
{
Expand Down Expand Up @@ -135,6 +136,7 @@ class ConfigManager
int32_t getNumber(integer_config_t what) const;
bool getBoolean(boolean_config_t what) const;
float getExperienceStage(uint32_t level) const;
float getLoyaltyBonus(uint16_t points) const;

bool setString(string_config_t what, std::string_view value);
bool setNumber(integer_config_t what, int32_t value);
Expand All @@ -146,6 +148,7 @@ class ConfigManager
bool boolean[LAST_BOOLEAN_CONFIG] = {};

ExperienceStages expStages = {};
LoyaltyBonuses loyaltyBonuses = {};

bool loaded = false;
};
Expand Down
Loading