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
4 changes: 4 additions & 0 deletions config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ depotPremiumLimit = 15000
questTrackerFreeLimit = 10
questTrackerPremiumLimit = 15

-- VIP Group limits
vipGroupFreeLimit = 3
vipGroupPremiumLimit = 8

-- World Light
-- NOTE: if defaultWorldLight is set to true the world light algorithm will
-- be handled in the sources. set it to false to avoid conflicts if you wish
Expand Down
25 changes: 24 additions & 1 deletion data/migrations/36.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
function onUpdateDatabase()
return false
print("> Updating database to version 37 (vipgroups)")
db.query("CREATE TABLE IF NOT EXISTS `account_vipgroups` (`id` int NOT NULL AUTO_INCREMENT, `account_id` int NOT NULL, `name` varchar(128) NOT NULL DEFAULT '', `editable` tinyint NOT NULL DEFAULT '1', PRIMARY KEY (`id`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB;")
db.query("ALTER TABLE `account_viplist` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST")
db.query("CREATE TABLE IF NOT EXISTS `account_vipgroup_entry` (`group_id` int NOT NULL, `entry_id` int NOT NULL, UNIQUE KEY `group_entry_index` (`group_id`, `entry_id`), FOREIGN KEY (`group_id`) REFERENCES `account_vipgroups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`entry_id`) REFERENCES `account_viplist` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB;")

local resultId = db.storeQuery("SELECT `accounts`.`id` AS `account_id` FROM `accounts`")
if resultId ~= false then
local stmt = "INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES "
repeat
stmt = stmt .. "(" .. result.getNumber(resultId, "account_id") .. ", 'Enemies', 0),"
stmt = stmt .. "(" .. result.getNumber(resultId, "account_id") .. ", 'Friends', 0),"
stmt = stmt .. "(" .. result.getNumber(resultId, "account_id") .. ", 'Trading Partners', 0),"
until not result.next(resultId)
result.free(resultId)

local stmtLen = string.len(stmt)
if stmtLen > 74 then
stmt = string.sub(stmt, 1, stmtLen - 1)
db.query(stmt)
end
end

db.query("CREATE TRIGGER `oncreate_accounts` AFTER INSERT ON `accounts` FOR EACH ROW BEGIN INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES (NEW.id, 'Enemies', 0), (NEW.id, 'Friends', 0), (NEW.id, 'Trading Partners', 0); END")
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
29 changes: 28 additions & 1 deletion schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,35 @@ CREATE TABLE IF NOT EXISTS `player_namelocks` (
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

CREATE TABLE IF NOT EXISTS `account_viplist` (
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL COMMENT 'id of account whose viplist entry it is',
`player_id` int NOT NULL COMMENT 'id of target player of viplist entry',
`description` varchar(128) NOT NULL DEFAULT '',
`icon` tinyint unsigned NOT NULL DEFAULT '0',
`notify` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `account_player_index` (`account_id`,`player_id`),
FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE,
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

CREATE TABLE IF NOT EXISTS `account_vipgroups` (
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`name` varchar(128) NOT NULL DEFAULT '',
`editable` tinyint NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

CREATE TABLE IF NOT EXISTS `account_vipgroup_entry` (
`group_id` int NOT NULL,
`entry_id` int NOT NULL,
UNIQUE KEY `group_entry_index` (`group_id`, `entry_id`),
FOREIGN KEY (`group_id`) REFERENCES `account_vipgroups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`entry_id`) REFERENCES `account_viplist` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

CREATE TABLE IF NOT EXISTS `guilds` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
Expand Down Expand Up @@ -379,10 +398,11 @@ 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', '37'), ('players_record', '0');

DROP TRIGGER IF EXISTS `ondelete_players`;
DROP TRIGGER IF EXISTS `oncreate_guilds`;
DROP TRIGGER IF EXISTS `oncreate_accounts`;

DELIMITER //
CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players`
Expand All @@ -397,4 +417,11 @@ CREATE TRIGGER `oncreate_guilds` AFTER INSERT ON `guilds`
INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Member', 1, NEW.`id`);
END
//
CREATE TRIGGER `oncreate_accounts` AFTER INSERT ON `accounts`
FOR EACH ROW BEGIN
INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES (NEW.`id`, 'Enemies', 0);
INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES (NEW.`id`, 'Friends', 0);
INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES (NEW.`id`, 'Trading Partners', 0);
END
//
DELIMITER ;
2 changes: 2 additions & 0 deletions src/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ bool ConfigManager::load()
integer[QUEST_TRACKER_PREMIUM_LIMIT] = getGlobalNumber(L, "questTrackerPremiumLimit", 15);
integer[STAMINA_REGEN_MINUTE] = getGlobalNumber(L, "timeToRegenMinuteStamina", 3 * 60);
integer[STAMINA_REGEN_PREMIUM] = getGlobalNumber(L, "timeToRegenMinutePremiumStamina", 6 * 60);
integer[VIPGROUP_FREE_LIMIT] = getGlobalNumber(L, "vipGroupFreeLimit", 3);
integer[VIPGROUP_PREMIUM_LIMIT] = getGlobalNumber(L, "vipGroupPremiumLimit", 8);

expStages = loadXMLStages();
if (expStages.empty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/configmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ enum integer_config_t
QUEST_TRACKER_PREMIUM_LIMIT,
STAMINA_REGEN_MINUTE,
STAMINA_REGEN_PREMIUM,
VIPGROUP_FREE_LIMIT,
VIPGROUP_PREMIUM_LIMIT,

LAST_INTEGER_CONFIG /* this must be the last one */
};
Expand Down
7 changes: 7 additions & 0 deletions src/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ enum VipStatus_t : uint8_t
VIPSTATUS_TRAINING = 3
};

enum VipGroupAction_t : uint8_t
{
VIPGROUPACTION_CREATE = 1,
VIPGROUPACTION_EDIT = 2,
VIPGROUPACTION_REMOVE = 3
};

enum MarketAction_t
{
MARKETACTION_BUY = 0,
Expand Down
34 changes: 32 additions & 2 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3328,14 +3328,44 @@ void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid)
}

void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon,
bool notify)
bool notify, const std::vector<uint16_t>& groupIds)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}

player->editVIP(guid, description, icon, notify);
player->editVIP(guid, description, icon, notify, groupIds);
}

void Game::playerRequestAddVipGroup(uint32_t playerId, const std::string& name)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}

player->addVIPGroup(name);
}

void Game::playerRequestEditVipGroup(uint32_t playerId, uint16_t vipGroupId, const std::string& name)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}

player->editVIPGroup(vipGroupId, name);
}

void Game::playerRequestRemoveVipGroup(uint32_t playerId, uint16_t vipGroupId)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}

player->removeVIPGroup(vipGroupId);
}

void Game::playerTurn(uint32_t playerId, Direction dir)
Expand Down
5 changes: 4 additions & 1 deletion src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,10 @@ class Game
void playerRequestAddVip(uint32_t playerId, const std::string& name);
void playerRequestRemoveVip(uint32_t playerId, uint32_t guid);
void playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon,
bool notify);
bool notify, const std::vector<uint16_t>& groupIds);
void playerRequestAddVipGroup(uint32_t playerId, const std::string& name);
void playerRequestEditVipGroup(uint32_t playerId, uint16_t vipGroupId, const std::string& name);
void playerRequestRemoveVipGroup(uint32_t playerId, uint16_t vipGroupId);
void playerTurn(uint32_t playerId, Direction dir);
void playerRequestOutfit(uint32_t playerId);
void playerRequestEditPodium(uint32_t playerId, const Position& position, uint8_t stackPos,
Expand Down
1 change: 1 addition & 0 deletions src/groups.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ bool Groups::load()
group.access = groupNode.attribute("access").as_bool();
group.maxDepotItems = pugi::cast<uint32_t>(groupNode.attribute("maxdepotitems").value());
group.maxVipEntries = pugi::cast<uint32_t>(groupNode.attribute("maxvipentries").value());
group.maxVipGroups = pugi::cast<uint32_t>(groupNode.attribute("maxvipgroups").value());
group.flags = pugi::cast<uint64_t>(groupNode.attribute("flags").value());
if (pugi::xml_node node = groupNode.child("flags")) {
for (auto flagNode : node.children()) {
Expand Down
1 change: 1 addition & 0 deletions src/groups.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Group
uint64_t flags;
uint32_t maxDepotItems;
uint32_t maxVipEntries;
uint32_t maxVipGroups;
uint16_t id;
bool access;
};
Expand Down
114 changes: 106 additions & 8 deletions src/iologindata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,14 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
} while (result->next());
}

// load vip groups
if ((result = db.storeQuery(
fmt::format("SELECT `id` FROM `account_vipgroups` WHERE `account_id` = {:d}", player->getAccount())))) {
do {
player->addVIPGroupInternal(result->getNumber<uint32_t>("id"));
} while (result->next());
}

player->updateBaseSpeed();
player->updateInventoryWeight();
player->updateItemsLight(true);
Expand Down Expand Up @@ -1100,20 +1108,54 @@ bool IOLoginData::hasBiddedOnHouse(uint32_t guid)
return db.storeQuery(fmt::format("SELECT `id` FROM `houses` WHERE `highest_bidder` = {:d} LIMIT 1", guid)).get();
}

std::forward_list<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId)
std::vector<VIPGroup> IOLoginData::getVIPGroups(uint32_t accountId)
{
std::forward_list<VIPEntry> entries;
std::vector<VIPGroup> groups;

DBResult_ptr result = Database::getInstance().storeQuery(fmt::format(
"SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {:d}",
DBResult_ptr result = Database::getInstance().storeQuery(
fmt::format("SELECT `id`, `name`, `editable` FROM `account_vipgroups` WHERE `account_id` = {:d}", accountId));
if (result) {
do {
groups.emplace_back(result->getNumber<uint32_t>("id"), result->getString("name"),
result->getNumber<uint16_t>("editable") != 0);
} while (result->next());
}
return groups;
}

std::vector<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId)
{
Database& db = Database::getInstance();
std::vector<VIPEntry> entries;

DBResult_ptr result = db.storeQuery(fmt::format(
"SELECT `id`, `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {:d}",
accountId));
if (result) {
do {
entries.emplace_front(result->getNumber<uint32_t>("player_id"), result->getString("name"),
result->getString("description"), result->getNumber<uint32_t>("icon"),
result->getNumber<uint16_t>("notify") != 0);
entries.emplace_back(result->getNumber<uint32_t>("id"), result->getNumber<uint32_t>("player_id"),
result->getString("name"), result->getString("description"),
result->getNumber<uint32_t>("icon"), result->getNumber<uint16_t>("notify") != 0);
} while (result->next());
}

std::vector<std::pair<uint32_t, uint16_t>> entryGroups;
if (result = db.storeQuery(fmt::format(
"SELECT `entry_id`, `group_id` FROM `account_vipgroup_entry` INNER JOIN `account_viplist` ON `id` = `entry_id` WHERE `account_id` = {:d}",
accountId))) {
do {
entryGroups.emplace_back(result->getNumber<uint32_t>("entry_id"), result->getNumber<uint16_t>("group_id"));
} while (result->next());
}

for (VIPEntry& entry : entries) {
for (auto [entryId, groupId] : entryGroups) {
if (entry.id == entryId) {
entry.groupIds.push_back(groupId);
}
}
}

return entries;
}

Expand All @@ -1127,12 +1169,36 @@ void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::stri
}

void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon,
bool notify)
bool notify, const std::vector<uint16_t>& groupIds)
{
Database& db = Database::getInstance();
db.executeQuery(fmt::format(
"UPDATE `account_viplist` SET `description` = {:s}, `icon` = {:d}, `notify` = {:d} WHERE `account_id` = {:d} AND `player_id` = {:d}",
db.escapeString(description), icon, notify, accountId, guid));

DBResult_ptr result = db.storeQuery(fmt::format(
"SELECT `id` FROM `account_viplist` WHERE `account_id` = {:d} AND `player_id` = {:d}", accountId, guid));
if (!result) {
return;
}

uint16_t entryId = result->getNumber<uint16_t>("id");

db.executeQuery(fmt::format("DELETE FROM `account_vipgroup_entry` WHERE `entry_id` = {:d}", entryId));
if (groupIds.empty()) {
return;
}

DBInsert insertQuery("INSERT INTO `account_vipgroup_entry` (`group_id`, `entry_id`) VALUES ");
for (const uint16_t& groupId : groupIds) {
if (!insertQuery.addRow(fmt::format("{:d}, {:d}", groupId, entryId))) {
return;
}
}

if (!insertQuery.execute()) {
return;
}
}

void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid)
Expand All @@ -1141,6 +1207,38 @@ void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid)
fmt::format("DELETE FROM `account_viplist` WHERE `account_id` = {:d} AND `player_id` = {:d}", accountId, guid));
}

bool IOLoginData::checkVIPGroupName(uint32_t accountId, const std::string& name)
{
Database& db = Database::getInstance();
return db
.storeQuery(
fmt::format("SELECT `id` FROM `account_vipgroups` WHERE `account_id` = {:d} AND `name` = {:s} LIMIT 1",
accountId, db.escapeString(name)))
.get();
}

uint32_t IOLoginData::addVIPGroup(uint32_t accountId, const std::string& name, bool isEditable)
{
Database& db = Database::getInstance();
db.executeQuery(
fmt::format("INSERT INTO `account_vipgroups` (`account_id`, `name`, `editable`) VALUES ({:d}, {:s}, {:d})",
accountId, db.escapeString(name), isEditable ? 1 : 0));

return db.getLastInsertId();
}

void IOLoginData::editVIPGroup(uint16_t vipGroupId, const std::string& name)
{
Database& db = Database::getInstance();
db.executeQuery(fmt::format("UPDATE `account_vipgroups` SET `name` = {:s} WHERE `id` = {:d}", db.escapeString(name),
vipGroupId));
}

void IOLoginData::removeVIPGroup(uint16_t vipGroupId)
{
Database::getInstance().executeQuery(fmt::format("DELETE FROM `account_vipgroups` WHERE `id` = {:d}", vipGroupId));
}

void IOLoginData::updatePremiumTime(uint32_t accountId, time_t endTime)
{
Database::getInstance().executeQuery(
Expand Down
10 changes: 8 additions & 2 deletions src/iologindata.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Item;
class Player;
class PropWriteStream;
struct VIPEntry;
struct VIPGroup;

using ItemBlockList = std::list<std::pair<int32_t, Item*>>;

Expand Down Expand Up @@ -43,12 +44,17 @@ class IOLoginData
static void increaseBankBalance(uint32_t guid, uint64_t bankBalance);
static bool hasBiddedOnHouse(uint32_t guid);

static std::forward_list<VIPEntry> getVIPEntries(uint32_t accountId);
static std::vector<VIPGroup> getVIPGroups(uint32_t accountId);
static std::vector<VIPEntry> getVIPEntries(uint32_t accountId);
static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon,
bool notify);
static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon,
bool notify);
bool notify, const std::vector<uint16_t>& groupIds);
static void removeVIPEntry(uint32_t accountId, uint32_t guid);
static bool checkVIPGroupName(uint32_t accountId, const std::string& name);
static uint32_t addVIPGroup(uint32_t accountId, const std::string& name, bool isEditable);
static void editVIPGroup(uint16_t vipGroupId, const std::string& name);
static void removeVIPGroup(uint16_t vipGroupId);

static void updatePremiumTime(uint32_t accountId, time_t endTime);

Expand Down
Loading