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

Added Loyalty System (10.74) #4148

Closed
wants to merge 5 commits into from
Closed
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
16 changes: 16 additions & 0 deletions config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ experienceStages = {
{ minlevel = 101, multiplier = 3 }
}

-- Loyalty system
loyaltyEnabled = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop this flag and let users effectively disable the loyalty system by removing all tiers.

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
9 changes: 9 additions & 0 deletions data/creaturescripts/scripts/login.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ function onLogin(player)
elseif not promotion then
player:setVocation(vocation:getDemotion())
end

-- loyalty system
if configManager.getBoolean(configKeys.LOYALTY_SYSTEM) then
local loyalty = player:getLoyaltyStatus()
if loyalty.bonus > 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, loyalty.bonus * 100)
player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr)
end
end

-- Events
player:registerEvent("PlayerDeath")
Expand Down
14 changes: 14 additions & 0 deletions data/globalevents/scripts/startup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,18 @@ function onStartup()
local position = town:getTemplePosition()
db.query("INSERT INTO `towns` (`id`, `name`, `posx`, `posy`, `posz`) VALUES (" .. town:getId() .. ", " .. db.escapeString(town:getName()) .. ", " .. position.x .. ", " .. position.y .. ", " .. position.z .. ")")
end

-- loyalty system (update points)
if configManager.getBoolean(configKeys.LOYALTY_SYSTEM) then
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
if (os.time() - updatedAt) >= 86400 then
db.asyncQuery("UPDATE `accounts` SET `loyalty_points` = `loyalty_points` + 1 WHERE `premium_ends_at` > " .. os.time())
db.query("UPDATE `server_config` SET `value` = " .. os.time() .. " WHERE `config` = 'loyalty_updated'")
end
end
end
5 changes: 4 additions & 1 deletion data/migrations/33.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
function onUpdateDatabase()
return false
print("> Updating database to version 33 (loyalty system)")
db.query("ALTER TABLE `accounts` ADD COLUMN `loyalty_points` SMALLINT UNSIGNED NOT NULL DEFAULT 0 AFTER `premium_ends_at`")
db.query("INSERT INTO `server_config` (`config`, `value`) VALUES ('loyalty_updated', '0')")
return true
end
3 changes: 3 additions & 0 deletions data/migrations/34.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false
end
3 changes: 2 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 Down Expand Up @@ -362,7 +363,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', '32'), ('players_record', '0');
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '33'), ('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 @@ -13,6 +13,7 @@ struct Account
std::string key;
uint32_t id = 0;
time_t premiumEndsAt = 0;
uint16_t loyaltyPoints = 0;
AccountType_t accountType = ACCOUNT_TYPE_NORMAL;

Account() = default;
Expand Down
43 changes: 43 additions & 0 deletions src/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,32 @@ ConfigManager::ConfigManager() { string[CONFIG_FILE] = "config.lua"; }

namespace {

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);
uint16_t min = LuaScriptInterface::getField<uint16_t>(L, tableIndex, "min");
uint16_t max = LuaScriptInterface::getField<uint16_t>(L, tableIndex, "max");
float bonus = LuaScriptInterface::getField<float>(L, tableIndex, "bonus");

max = max == 0 ? std ::numeric_limits<std::uint16_t>::max() : max;
bonuses.emplace_back(min, max, bonus);
lua_pop(L, 4);
}
lua_pop(L, 1);

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

ExperienceStages loadLuaStages(lua_State* L)
{
ExperienceStages stages;
Expand Down Expand Up @@ -230,6 +256,7 @@ bool ConfigManager::load()
boolean[REMOVE_ON_DESPAWN] = getGlobalBoolean(L, "removeOnDespawn", true);
boolean[PLAYER_CONSOLE_LOGS] = getGlobalBoolean(L, "showPlayerLogInConsole", true);
boolean[TWO_FACTOR_AUTH] = getGlobalBoolean(L, "enableTwoFactorAuth", true);
boolean[LOYALTY_SYSTEM] = getGlobalBoolean(L, "loyaltyEnabled", true);

string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
string[SERVER_NAME] = getGlobalString(L, "serverName", "");
Expand Down Expand Up @@ -286,6 +313,9 @@ bool ConfigManager::load()
}
expStages.shrink_to_fit();

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

loaded = true;
lua_close(L);

Expand Down Expand Up @@ -334,6 +364,19 @@ 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](LoyaltyBonuses::value_type bonus) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto it = std::find_if(loyaltyBonuses.begin(), loyaltyBonuses.end(), [points](const auto& bonus) {?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be (auto bonus), otherwise 👍

return points >= std::get<0>(bonus) && points <= std::get<1>(bonus);
});

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

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

bool ConfigManager::setString(string_config_t what, const std::string& value)
{
if (what >= LAST_STRING_CONFIG) {
Expand Down
4 changes: 4 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 @@ -51,6 +52,7 @@ class ConfigManager
REMOVE_ON_DESPAWN,
PLAYER_CONSOLE_LOGS,
TWO_FACTOR_AUTH,
LOYALTY_SYSTEM,

LAST_BOOLEAN_CONFIG /* this must be the last one */
};
Expand Down Expand Up @@ -131,6 +133,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, const std::string& value);
bool setNumber(integer_config_t what, int32_t value);
Expand All @@ -142,6 +145,7 @@ class ConfigManager
bool boolean[LAST_BOOLEAN_CONFIG] = {};

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

bool loaded = false;
};
Expand Down
35 changes: 34 additions & 1 deletion src/iologindata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Account IOLoginData::loadAccount(uint32_t accno)
Account account;

DBResult_ptr result = Database::getInstance().storeQuery(fmt::format(
"SELECT `id`, `name`, `password`, `type`, `premium_ends_at` FROM `accounts` WHERE `id` = {:d}", accno));
"SELECT `id`, `name`, `password`, `type`, `premium_ends_at`, `loyalty_points` FROM `accounts` WHERE `id` = {:d}",
accno));
if (!result) {
return account;
}
Expand All @@ -29,6 +30,7 @@ Account IOLoginData::loadAccount(uint32_t accno)
account.name = result->getString("name");
account.accountType = static_cast<AccountType_t>(result->getNumber<int32_t>("type"));
account.premiumEndsAt = result->getNumber<time_t>("premium_ends_at");
account.loyaltyPoints = result->getNumber<uint16_t>("loyalty_points");
return account;
}

Expand Down Expand Up @@ -176,6 +178,12 @@ void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType)
static_cast<uint16_t>(accountType), accountId));
}

void IOLoginData::updateLoyalty(uint32_t accountId, uint16_t points)
{
Database::getInstance().executeQuery(
fmt::format("UPDATE `accounts` SET `loyalty_points` = {:d} WHERE `id` = {:d}", points, accountId));
}

void IOLoginData::updateOnlineStatus(uint32_t guid, bool login)
{
if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
Expand Down Expand Up @@ -329,6 +337,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
return false;
}

if (g_config.getBoolean(ConfigManager::LOYALTY_SYSTEM)) {
player->loyaltyBonus = g_config.getLoyaltyBonus(acc.loyaltyPoints);
player->loyaltyPoints = acc.loyaltyPoints;
}

player->mana = result->getNumber<uint32_t>("mana");
player->manaMax = result->getNumber<uint32_t>("manamax");
player->magLevel = result->getNumber<uint32_t>("maglevel");
Expand All @@ -342,6 +355,15 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->manaSpent = manaSpent;
player->magLevelPercent = Player::getPercentLevel(player->manaSpent, nextManaCount);

player->bonusMagLevel = player->magLevel;
player->bonusManaSpent = manaSpent;

player->totalManaSpent = player->getAccumulatedManaSpent();
player->totalBonusManaSpent = player->totalManaSpent * (1 + player->loyaltyBonus);

uint64_t manaDiff = player->totalBonusManaSpent - player->totalManaSpent;
player->updateLoyaltyMagLevel(manaDiff, true);

player->health = result->getNumber<int32_t>("health");
player->healthMax = result->getNumber<int32_t>("healthmax");

Expand Down Expand Up @@ -416,7 +438,16 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)

player->skills[i].level = skillLevel;
player->skills[i].tries = skillTries;
player->skills[i].totalTries = player->getAccumulatedSkillTries(i);
player->skills[i].percent = Player::getPercentLevel(skillTries, nextSkillTries);

player->skills[i].bonusLevel = skillLevel;
player->skills[i].bonusTries = skillTries;

player->skills[i].totalBonusTries = player->skills[i].totalTries * (1 + player->loyaltyBonus);
uint64_t triesDiff = player->skills[i].totalBonusTries - player->skills[i].totalTries;

player->updateLoyaltySkill(i, triesDiff, true);
}

if ((result = db.storeQuery(
Expand Down Expand Up @@ -958,6 +989,8 @@ bool IOLoginData::savePlayer(Player* player)
return false;
}

updateLoyalty(player->accountNumber, player->loyaltyPoints);

// End the transaction
return transaction.commit();
}
Expand Down
1 change: 1 addition & 0 deletions src/iologindata.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class IOLoginData

static AccountType_t getAccountType(uint32_t accountId);
static void setAccountType(uint32_t accountId, AccountType_t accountType);
static void updateLoyalty(uint32_t accountId, uint16_t points);
static void updateOnlineStatus(uint32_t guid, bool login);
static bool preloadPlayer(Player* player, const std::string& name);

Expand Down
48 changes: 48 additions & 0 deletions src/luascript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2108,6 +2108,7 @@ void LuaScriptInterface::registerFunctions()
registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLOSE);
registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN);
registerEnumIn("configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST);
registerEnumIn("configKeys", ConfigManager::LOYALTY_SYSTEM);

registerEnumIn("configKeys", ConfigManager::MAP_NAME);
registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD);
Expand Down Expand Up @@ -2699,6 +2700,10 @@ void LuaScriptInterface::registerFunctions()

registerMethod("Player", "getIdleTime", LuaScriptInterface::luaPlayerGetIdleTime);

registerMethod("Player", "getLoyaltyStatus", LuaScriptInterface::luaPlayerGetLoyaltyStatus);
registerMethod("Player", "addLoyalty", LuaScriptInterface::luaPlayerAddLoyalty);
registerMethod("Player", "removeLoyalty", LuaScriptInterface::luaPlayerRemoveLoyalty);

// Monster
registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate);
registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare);
Expand Down Expand Up @@ -10800,6 +10805,49 @@ int LuaScriptInterface::luaPlayerGetIdleTime(lua_State* L)
return 1;
}

int LuaScriptInterface::luaPlayerGetLoyaltyStatus(lua_State* L)
{
// player:getLoyaltyStatus()
const Player* const player = getUserdata<Player>(L, 1);
if (player) {
lua_createtable(L, 0, 2);
setField(L, "points", player->getLoyaltyPoints());
setField(L, "bonus", player->getLoyaltyBonus());
} else {
lua_pushnil(L);
}

return 1;
}

int LuaScriptInterface::luaPlayerAddLoyalty(lua_State* L)
{
// player:addLoyalty([points = 1])
Player* player = getUserdata<Player>(L, 1);
if (!player) {
lua_pushnil(L);
}

uint16_t points = getNumber<uint16_t>(L, 2, 1);
pushBoolean(L, player->addLoyalty(points));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove newline

return 1;
}

int LuaScriptInterface::luaPlayerRemoveLoyalty(lua_State* L)
{
// player:removeLoyalty([points = 1])
Player* player = getUserdata<Player>(L, 1);
if (!player) {
lua_pushnil(L);
}

uint16_t points = getNumber<uint16_t>(L, 2, 1);
pushBoolean(L, player->addLoyalty(-points));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove newline

return 1;
}

// Monster
int LuaScriptInterface::luaMonsterCreate(lua_State* L)
{
Expand Down
4 changes: 4 additions & 0 deletions src/luascript.h
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,10 @@ class LuaScriptInterface

static int luaPlayerGetIdleTime(lua_State* L);

static int luaPlayerGetLoyaltyStatus(lua_State* L);
static int luaPlayerAddLoyalty(lua_State* L);
static int luaPlayerRemoveLoyalty(lua_State* L);

// Monster
static int luaMonsterCreate(lua_State* L);

Expand Down
Loading