Skip to content

Commit

Permalink
* FEATURE: Cyclopedia House Auction system from Canary
Browse files Browse the repository at this point in the history
  • Loading branch information
jprzimba committed Dec 31, 2024
1 parent 0eb6395 commit d59d96b
Show file tree
Hide file tree
Showing 28 changed files with 1,056 additions and 73 deletions.
4 changes: 3 additions & 1 deletion config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,11 @@ Setting this to false may pose risks; if a house is abandoned and contains a lar
]]
-- Periods: daily/weekly/monthly/yearly/never
-- Base: sqm,rent,sqm+rent
toggleCyclopediaHouseAuction = true
daysToCloseBid = 7
housePriceRentMultiplier = 0.0
housePriceEachSQM = 1000
houseRentPeriod = "never"
houseRentPeriod = "monthly"
houseRentRate = 1.0
houseOwnedByAccount = false
houseBuyLevel = 100
Expand Down
28 changes: 27 additions & 1 deletion data/migrations/47.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
logger.info("Updating database to version 48 (House Auction)")

db.query([[
ALTER TABLE `houses`
DROP `bid`,
DROP `bid_end`,
DROP `last_bid`,
DROP `highest_bidder`
]])

db.query([[
ALTER TABLE `houses`
ADD `bidder` int(11) NOT NULL DEFAULT '0',
ADD `bidder_name` varchar(255) NOT NULL DEFAULT '',
ADD `highest_bid` int(11) NOT NULL DEFAULT '0',
ADD `internal_bid` int(11) NOT NULL DEFAULT '0',
ADD `bid_end_date` int(11) NOT NULL DEFAULT '0',
ADD `state` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
ADD `transfer_status` tinyint(1) DEFAULT '0'
]])

db.query([[
ALTER TABLE `accounts`
ADD `house_bid_id` int(11) NOT NULL DEFAULT '0'
]])

return true
end
3 changes: 3 additions & 0 deletions data/migrations/48.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
end
24 changes: 0 additions & 24 deletions data/scripts/globalevents/server_initialization.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,6 @@ local function moveExpiredBansToHistory()
end
end

-- Function to check and process house auctions
local function processHouseAuctions()
local resultId = db.storeQuery("SELECT `id`, `highest_bidder`, `last_bid`, " .. "(SELECT `balance` FROM `players` WHERE `players`.`id` = `highest_bidder`) AS `balance` " .. "FROM `houses` WHERE `owner` = 0 AND `bid_end` != 0 AND `bid_end` < " .. os.time())
if resultId then
repeat
local house = House(Result.getNumber(resultId, "id"))
if house then
local highestBidder = Result.getNumber(resultId, "highest_bidder")
local balance = Result.getNumber(resultId, "balance")
local lastBid = Result.getNumber(resultId, "last_bid")
if balance >= lastBid then
db.query("UPDATE `players` SET `balance` = " .. (balance - lastBid) .. " WHERE `id` = " .. highestBidder)
house:setHouseOwner(highestBidder)
end

db.asyncQuery("UPDATE `houses` SET `last_bid` = 0, `bid_end` = 0, `highest_bidder` = 0, `bid` = 0 " .. "WHERE `id` = " .. house:getId())
end
until not Result.next(resultId)

Result.free(resultId)
end
end

-- Function to store towns in the database
local function storeTownsInDatabase()
db.query("TRUNCATE TABLE `towns`")
Expand Down Expand Up @@ -150,7 +127,6 @@ function serverInitialization.onStartup()

cleanupDatabase()
moveExpiredBansToHistory()
processHouseAuctions()
storeTownsInDatabase()
checkAndLogDuplicateValues({ "Global", "GlobalStorage", "Storage" })
updateEventRates()
Expand Down
5 changes: 5 additions & 0 deletions data/scripts/talkactions/player/buy_house.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
local buyHouse = TalkAction("!buyhouse")

function buyHouse.onSay(player, words, param)
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
return true
end

local housePrice = configManager.getNumber(configKeys.HOUSE_PRICE_PER_SQM)
if housePrice == -1 then
return true
Expand Down
5 changes: 5 additions & 0 deletions data/scripts/talkactions/player/leave_house.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
local leaveHouse = TalkAction("!leavehouse")

function leaveHouse.onSay(player, words, param)
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
return true
end

local playerPosition = player:getPosition()
local playerTile = Tile(playerPosition)
local house = playerTile and playerTile:getHouse()
Expand Down
5 changes: 5 additions & 0 deletions data/scripts/talkactions/player/sell_house.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
local sellHouse = TalkAction("!sellhouse")

function sellHouse.onSay(player, words, param)
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
return true
end

local tradePartner = Player(param)
if not tradePartner or tradePartner == player then
player:sendCancelMessage("Trade player not found.")
Expand Down
1 change: 1 addition & 0 deletions markdowns/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Add new configurable featurees in `config.lua`: `chainSystemVipOnly`, `fieldOwnershipDuration`, `bedsOnlyPremium`, `loginProtectionPeriod`, `chainSystemModifyMagic`. ([Tryller](https://github.com/jprzimba))
- Added a new commands for players: `!randomoutfit`, `!spellwords`. ([Tryller](https://github.com/jprzimba))
- Moved emote spells to `kv` instead of `storage`. ([Tryller](https://github.com/jprzimba))
- Cyclopedia House Auction system. ([murilo09](https://github.com/murilo09))

## Added files

Expand Down
14 changes: 9 additions & 5 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '46'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '48'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');

-- Table structure `accounts`
CREATE TABLE IF NOT EXISTS `accounts` (
Expand All @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS `accounts` (
`tournament_coins` int(12) UNSIGNED NOT NULL DEFAULT '0',
`creation` int(11) UNSIGNED NOT NULL DEFAULT '0',
`recruiter` INT(6) DEFAULT 0,
`house_bid_id` int(11) NOT NULL DEFAULT '0',
CONSTRAINT `accounts_pk` PRIMARY KEY (`id`),
CONSTRAINT `accounts_unique` UNIQUE (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Expand Down Expand Up @@ -449,13 +450,16 @@ CREATE TABLE IF NOT EXISTS `houses` (
`name` varchar(255) NOT NULL,
`rent` int(11) NOT NULL DEFAULT '0',
`town_id` int(11) NOT NULL DEFAULT '0',
`bid` int(11) NOT NULL DEFAULT '0',
`bid_end` int(11) NOT NULL DEFAULT '0',
`last_bid` int(11) NOT NULL DEFAULT '0',
`highest_bidder` int(11) NOT NULL DEFAULT '0',
`size` int(11) NOT NULL DEFAULT '0',
`guildid` int(11),
`beds` int(11) NOT NULL DEFAULT '0',
`bidder` int(11) NOT NULL DEFAULT '0',
`bidder_name` varchar(255) NOT NULL DEFAULT '',
`highest_bid` int(11) NOT NULL DEFAULT '0',
`internal_bid` int(11) NOT NULL DEFAULT '0',
`bid_end_date` int(11) NOT NULL DEFAULT '0',
`state` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
`transfer_status` tinyint(1) DEFAULT '0',
INDEX `owner` (`owner`),
INDEX `town_id` (`town_id`),
CONSTRAINT `houses_pk` PRIMARY KEY (`id`)
Expand Down
8 changes: 8 additions & 0 deletions src/account/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,11 @@ uint32_t Account::getAccountAgeInDays() const {
[[nodiscard]] time_t Account::getPremiumLastDay() const {
return m_account->premiumLastDay;
}

uint32_t Account::getHouseBidId() const {
return m_account->houseBidId;
}

void Account::setHouseBidId(uint32_t houseId) {
m_account->houseBidId = houseId;
}
4 changes: 4 additions & 0 deletions src/account/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ class Account {

std::tuple<phmap::flat_hash_map<std::string, uint64_t>, AccountErrors_t> getAccountPlayers() const;

void setHouseBidId(uint32_t houseId);

uint32_t getHouseBidId() const;

// Old protocol compat
void setProtocolCompat(bool toggle);

Expand Down
1 change: 1 addition & 0 deletions src/account/account_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ struct AccountInfo {
time_t sessionExpires = 0;
uint32_t premiumDaysPurchased = 0;
uint32_t creationTime = 0;
uint32_t houseBidId = 0;
};
3 changes: 2 additions & 1 deletion src/account/account_repository_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ bool AccountRepositoryDB::loadBySession(const std::string &sessionKey, std::uniq
bool AccountRepositoryDB::save(const std::unique_ptr<AccountInfo> &accInfo) {
bool successful = g_database().executeQuery(
fmt::format(
"UPDATE `accounts` SET `type` = {}, `premdays` = {}, `lastday` = {}, `creation` = {}, `premdays_purchased` = {} WHERE `id` = {}",
"UPDATE `accounts` SET `type` = {}, `premdays` = {}, `lastday` = {}, `creation` = {}, `premdays_purchased` = {}, `house_bid_id` = {} WHERE `id` = {}",
accInfo->accountType,
accInfo->premiumRemainingDays,
accInfo->premiumLastDay,
accInfo->creationTime,
accInfo->premiumDaysPurchased,
accInfo->houseBidId,
accInfo->id
)
);
Expand Down
2 changes: 2 additions & 0 deletions src/config/config_enums.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,6 @@ enum ConfigKey_t : uint16_t {
BEDS_ONLY_PREMIUM,
LOGIN_PROTECTION,
SPELL_NAME_INSTEAD_WORDS,
CYCLOPEDIA_HOUSE_AUCTION,
DAYS_TO_CLOSE_BID,
};
2 changes: 2 additions & 0 deletions src/config/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ bool ConfigManager::load() {
loadBoolConfig(L, CHAIN_SYSTEM_VIP_ONLY, "chainSystemVipOnly", false);
loadBoolConfig(L, BEDS_ONLY_PREMIUM, "bedsOnlyPremium", true);
loadBoolConfig(L, SPELL_NAME_INSTEAD_WORDS, "spellNameInsteadOfWords", false);
loadBoolConfig(L, CYCLOPEDIA_HOUSE_AUCTION, "toggleCyclopediaHouseAuction", true);

loadFloatConfig(L, BESTIARY_RATE_CHARM_SHOP_PRICE, "bestiaryRateCharmShopPrice", 1.0);
loadFloatConfig(L, COMBAT_CHAIN_SKILL_FORMULA_AXE, "combatChainSkillFormulaAxe", 0.9);
Expand Down Expand Up @@ -366,6 +367,7 @@ bool ConfigManager::load() {
loadIntConfig(L, BLACK_SKULL_DEATH_MANA, "blackSkulledDeathMana", 0);
loadIntConfig(L, FIELD_OWNERSHIP, "fieldOwnershipDuration", 5 * 1000);
loadIntConfig(L, LOGIN_PROTECTION, "loginProtectionPeriod", 10 * 1000);
loadIntConfig(L, DAYS_TO_CLOSE_BID, "daysToCloseBid", 7);

loadStringConfig(L, CORE_DIRECTORY, "coreDirectory", "data");
loadStringConfig(L, DATA_DIRECTORY, "dataPackDirectory", "data-global");
Expand Down
102 changes: 102 additions & 0 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "enums/object_category.hpp"
#include "enums/player_blessings.hpp"
#include "enums/player_icons.hpp"
#include "enums/player_cyclopedia.hpp"
#include "game/game.hpp"
#include "game/modal_window/modal_window.hpp"
#include "game/scheduling/dispatcher.hpp"
Expand Down Expand Up @@ -2273,6 +2274,23 @@ void Player::sendOutfitWindow() const {
}
}

// House auction
void Player::sendCyclopediaHouseList(const HouseMap &houses) const {
if (client) {
client->sendCyclopediaHouseList(houses);
}
}
void Player::sendResourceBalance(Resource_t resourceType, uint64_t value) const {
if (client) {
client->sendResourceBalance(resourceType, value);
}
}
void Player::sendHouseAuctionMessage(uint32_t houseId, HouseAuctionType type, uint8_t index, bool bidSuccess /* = false*/) const {
if (client) {
client->sendHouseAuctionMessage(houseId, type, index, bidSuccess);
}
}

// Imbuements

void Player::onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr<Item> &item, uint8_t slot, bool protectionCharm) {
Expand Down Expand Up @@ -10607,3 +10625,87 @@ uint16_t Player::getPlayerVocationEnum() const {

return Vocation_t::VOCATION_NONE;
}

BidErrorMessage Player::canBidHouse(uint32_t houseId) {
using enum BidErrorMessage;
const auto house = g_game().map.houses.getHouseByClientId(houseId);
if (!house) {
return Internal;
}
if (getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
return Rookgaard;
}
if (!isPremium()) {
return Premium;
}
if (getAccount()->getHouseBidId() != 0) {
return OnlyOneBid;
}
if (getBankBalance() < (house->getRent() + house->getHighestBid())) {
return NotEnoughMoney;
}
if (house->isGuildhall()) {
if (getGuildRank() && getGuildRank()->level != 3) {
return Guildhall;
}
if (getGuild() && getGuild()->getBankBalance() < (house->getRent() + house->getHighestBid())) {
return NotEnoughGuildMoney;
}
}
return NoError;
}

TransferErrorMessage Player::canTransferHouse(uint32_t houseId, uint32_t newOwnerGUID) {
using enum TransferErrorMessage;
const auto house = g_game().map.houses.getHouseByClientId(houseId);
if (!house) {
return Internal;
}
if (getGUID() != house->getOwner()) {
return NotHouseOwner;
}
if (getGUID() == newOwnerGUID) {
return AlreadyTheOwner;
}
const auto newOwner = g_game().getPlayerByGUID(newOwnerGUID, true);
if (!newOwner) {
return CharacterNotExist;
}
if (newOwner->getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
return Rookgaard;
}
if (!newOwner->isPremium()) {
return Premium;
}
if (newOwner->getAccount()->getHouseBidId() != 0) {
return OnlyOneBid;
}
return Success;
}

AcceptTransferErrorMessage Player::canAcceptTransferHouse(uint32_t houseId) {
using enum AcceptTransferErrorMessage;
const auto house = g_game().map.houses.getHouseByClientId(houseId);
if (!house) {
return Internal;
}
if (getGUID() != house->getBidder()) {
return NotNewOwner;
}
if (!isPremium()) {
return Premium;
}
if (getAccount()->getHouseBidId() != 0) {
return AlreadyBid;
}
if (getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
return Rookgaard;
}
if (getBankBalance() < (house->getRent() + house->getInternalBid())) {
return Frozen;
}
if (house->getTransferStatus()) {
return AlreadyAccepted;
}
return Success;
}
15 changes: 15 additions & 0 deletions src/creatures/players/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,23 @@ struct HighscoreCharacter;

enum class PlayerIcon : uint8_t;
enum class IconBakragore : uint8_t;
enum class HouseAuctionType : uint8_t;
enum class BidErrorMessage : uint8_t;
enum class TransferErrorMessage : uint8_t;
enum class AcceptTransferErrorMessage : uint8_t;
enum ObjectCategory_t : uint8_t;
enum PreySlot_t : uint8_t;
enum SpeakClasses : uint8_t;
enum ChannelEvent_t : uint8_t;
enum SquareColor_t : uint8_t;
enum Resource_t : uint8_t;

using GuildWarVector = std::vector<uint32_t>;
using StashContainerList = std::vector<std::pair<std::shared_ptr<Item>, uint32_t>>;
using ItemVector = std::vector<std::shared_ptr<Item>>;
using UsersMap = std::map<uint32_t, std::shared_ptr<Player>>;
using InvitedMap = std::map<uint32_t, std::shared_ptr<Player>>;
using HouseMap = std::map<uint32_t, std::shared_ptr<House>>;

struct ForgeHistory {
ForgeAction_t actionType = ForgeAction_t::FUSION;
Expand Down Expand Up @@ -894,6 +900,15 @@ class Player final : public Creature, public Cylinder, public Bankable {
void sendOpenPrivateChannel(const std::string &receiver) const;
void sendExperienceTracker(int64_t rawExp, int64_t finalExp) const;
void sendOutfitWindow() const;

// House Auction
BidErrorMessage canBidHouse(uint32_t houseId);
TransferErrorMessage canTransferHouse(uint32_t houseId, uint32_t newOwnerGUID);
AcceptTransferErrorMessage canAcceptTransferHouse(uint32_t houseId);
void sendCyclopediaHouseList(const HouseMap &houses) const;
void sendResourceBalance(Resource_t resourceType, uint64_t value) const;
void sendHouseAuctionMessage(uint32_t houseId, HouseAuctionType type, uint8_t index, bool bidSuccess = false) const;

// Imbuements
void onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr<Item> &item, uint8_t slot, bool protectionCharm);
void onClearImbuement(const std::shared_ptr<Item> &item, uint8_t slot);
Expand Down
Loading

0 comments on commit d59d96b

Please sign in to comment.