Skip to content

Commit ca85547

Browse files
authored
Merge pull request #12 from jprzimba/house-auction
[FATURE] House auction system
2 parents 29665b7 + 3f7fef3 commit ca85547

30 files changed

+2039
-1067
lines changed

config.lua.dist

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,14 @@ fairFightTimeRange = 5 * 60 * 1000
375375
Setting this to false may pose risks; if a house is abandoned and contains a large number of items on the floor, those items will be transferred to the player's depot instantly.
376376
• This could potentially freeze the server due to the heavy operation. It's advised to keep this setting enabled (true) to minimize risks.
377377
]]
378+
-- NOTE: When toggleCyclopediaHouseAuction = true, the !buyhouse commmand does not work.
378379
-- Periods: daily/weekly/monthly/yearly/never
379380
-- Base: sqm,rent,sqm+rent
381+
toggleCyclopediaHouseAuction = false
382+
daysToCloseBid = 7
380383
housePriceRentMultiplier = 0.0
381384
housePriceEachSQM = 1000
382-
houseRentPeriod = "never"
385+
houseRentPeriod = "monthly"
383386
houseRentRate = 1.0
384387
houseOwnedByAccount = false
385388
houseBuyLevel = 100

data-global/world/world-house.xml

Lines changed: 984 additions & 993 deletions
Large diffs are not rendered by default.

data/migrations/48.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
function onUpdateDatabase()
2-
logger.info("Updating database to version 47 (player statements)")
2+
logger.info("Updating database to version 48 (player statements)")
33

44
db.query([[
55
CREATE TABLE IF NOT EXISTS `player_statements`(

data/migrations/50.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function onUpdateDatabase()
2+
logger.info("Updating database to version 50 (House Auction)")
3+
4+
db.query([[
5+
ALTER TABLE `houses`
6+
DROP `bid`,
7+
DROP `bid_end`,
8+
DROP `last_bid`,
9+
DROP `highest_bidder`
10+
]])
11+
12+
db.query([[
13+
ALTER TABLE `houses`
14+
ADD `bidder` int(11) NOT NULL DEFAULT '0',
15+
ADD `bidder_name` varchar(255) NOT NULL DEFAULT '',
16+
ADD `highest_bid` int(11) NOT NULL DEFAULT '0',
17+
ADD `internal_bid` int(11) NOT NULL DEFAULT '0',
18+
ADD `bid_end_date` int(11) NOT NULL DEFAULT '0',
19+
ADD `state` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
20+
ADD `transfer_status` tinyint(1) DEFAULT '0'
21+
]])
22+
23+
db.query([[
24+
ALTER TABLE `accounts`
25+
ADD `house_bid_id` int(11) NOT NULL DEFAULT '0'
26+
]])
27+
end

data/scripts/globalevents/server_initialization.lua

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,6 @@ local function moveExpiredBansToHistory()
2727
end
2828
end
2929

30-
-- Function to check and process house auctions
31-
local function processHouseAuctions()
32-
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())
33-
if resultId then
34-
repeat
35-
local house = House(Result.getNumber(resultId, "id"))
36-
if house then
37-
local highestBidder = Result.getNumber(resultId, "highest_bidder")
38-
local balance = Result.getNumber(resultId, "balance")
39-
local lastBid = Result.getNumber(resultId, "last_bid")
40-
if balance >= lastBid then
41-
db.query("UPDATE `players` SET `balance` = " .. (balance - lastBid) .. " WHERE `id` = " .. highestBidder)
42-
house:setHouseOwner(highestBidder)
43-
end
44-
45-
db.asyncQuery("UPDATE `houses` SET `last_bid` = 0, `bid_end` = 0, `highest_bidder` = 0, `bid` = 0 " .. "WHERE `id` = " .. house:getId())
46-
end
47-
until not Result.next(resultId)
48-
49-
Result.free(resultId)
50-
end
51-
end
52-
5330
-- Function to store towns in the database
5431
local function storeTownsInDatabase()
5532
db.query("TRUNCATE TABLE `towns`")
@@ -150,7 +127,6 @@ function serverInitialization.onStartup()
150127

151128
cleanupDatabase()
152129
moveExpiredBansToHistory()
153-
processHouseAuctions()
154130
storeTownsInDatabase()
155131
checkAndLogDuplicateValues({ "Global", "GlobalStorage", "Storage" })
156132
updateEventRates()

data/scripts/talkactions/player/buy_house.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
local buyHouse = TalkAction("!buyhouse")
22

33
function buyHouse.onSay(player, words, param)
4+
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
5+
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
6+
return true
7+
end
8+
49
local housePrice = configManager.getNumber(configKeys.HOUSE_PRICE_PER_SQM)
510
if housePrice == -1 then
611
return true

data/scripts/talkactions/player/leave_house.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
local leaveHouse = TalkAction("!leavehouse")
22

33
function leaveHouse.onSay(player, words, param)
4+
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
5+
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
6+
return true
7+
end
8+
49
local playerPosition = player:getPosition()
510
local playerTile = Tile(playerPosition)
611
local house = playerTile and playerTile:getHouse()

data/scripts/talkactions/player/sell_house.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
local sellHouse = TalkAction("!sellhouse")
22

33
function sellHouse.onSay(player, words, param)
4+
if configManager.getBoolean(configKeys.CYCLOPEDIA_HOUSE_AUCTION) then
5+
player:sendTextMessage(MESSAGE_FAILURE, "Command have been disabled by the administrator.")
6+
return true
7+
end
8+
49
local tradePartner = Player(param)
510
if not tradePartner or tradePartner == player then
611
player:sendCancelMessage("Trade player not found.")

markdowns/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
- Add new configurable featurees in `config.lua`: `chainSystemVipOnly`, `fieldOwnershipDuration`, `bedsOnlyPremium`, `loginProtectionPeriod`, `chainSystemModifyMagic`, `logPlayersStatements`. ([Tryller](https://github.com/jprzimba))
7373
- Added a new commands for players: `!randomoutfit`, `!spellwords`. ([Tryller](https://github.com/jprzimba))
7474
- Moved emote spells to `kv` instead of `storage`. ([Tryller](https://github.com/jprzimba))
75+
- Cyclopedia House Auction system. ([murilo09](https://github.com/murilo09))
7576
- Updated npcs and spells from 13.40 updates. ([murilo09](https://github.com/murilo09))
7677
- Added a Rook system with configurations in `config.lua`. ([Tryller](https://github.com/jprzimba))
7778
- Added a new group `game tester` with flag `isgametester` in `groups.xml` and a new player flag `PlayerFlag_IsGameTester`. ([Tryller](https://github.com/jprzimba))

schema.sql

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
55
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
66
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
77

8-
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '49'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
8+
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '50'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
99

1010
-- Table structure `accounts`
1111
CREATE TABLE IF NOT EXISTS `accounts` (
@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS `accounts` (
2222
`tournament_coins` int(12) UNSIGNED NOT NULL DEFAULT '0',
2323
`creation` int(11) UNSIGNED NOT NULL DEFAULT '0',
2424
`recruiter` INT(6) DEFAULT 0,
25+
`house_bid_id` int(11) NOT NULL DEFAULT '0',
2526
CONSTRAINT `accounts_pk` PRIMARY KEY (`id`),
2627
CONSTRAINT `accounts_unique` UNIQUE (`name`)
2728
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -450,13 +451,16 @@ CREATE TABLE IF NOT EXISTS `houses` (
450451
`name` varchar(255) NOT NULL,
451452
`rent` int(11) NOT NULL DEFAULT '0',
452453
`town_id` int(11) NOT NULL DEFAULT '0',
453-
`bid` int(11) NOT NULL DEFAULT '0',
454-
`bid_end` int(11) NOT NULL DEFAULT '0',
455-
`last_bid` int(11) NOT NULL DEFAULT '0',
456-
`highest_bidder` int(11) NOT NULL DEFAULT '0',
457454
`size` int(11) NOT NULL DEFAULT '0',
458455
`guildid` int(11),
459456
`beds` int(11) NOT NULL DEFAULT '0',
457+
`bidder` int(11) NOT NULL DEFAULT '0',
458+
`bidder_name` varchar(255) NOT NULL DEFAULT '',
459+
`highest_bid` int(11) NOT NULL DEFAULT '0',
460+
`internal_bid` int(11) NOT NULL DEFAULT '0',
461+
`bid_end_date` int(11) NOT NULL DEFAULT '0',
462+
`state` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
463+
`transfer_status` tinyint(1) DEFAULT '0',
460464
INDEX `owner` (`owner`),
461465
INDEX `town_id` (`town_id`),
462466
CONSTRAINT `houses_pk` PRIMARY KEY (`id`)

src/account/account.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,11 @@ uint32_t Account::getAccountAgeInDays() const {
308308
[[nodiscard]] time_t Account::getPremiumLastDay() const {
309309
return m_account->premiumLastDay;
310310
}
311+
312+
uint32_t Account::getHouseBidId() const {
313+
return m_account->houseBidId;
314+
}
315+
316+
void Account::setHouseBidId(uint32_t houseId) {
317+
m_account->houseBidId = houseId;
318+
}

src/account/account.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ class Account {
127127

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

130+
void setHouseBidId(uint32_t houseId);
131+
132+
uint32_t getHouseBidId() const;
133+
130134
// Old protocol compat
131135
void setProtocolCompat(bool toggle);
132136

src/account/account_info.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ struct AccountInfo {
3636
time_t sessionExpires = 0;
3737
uint32_t premiumDaysPurchased = 0;
3838
uint32_t creationTime = 0;
39+
uint32_t houseBidId = 0;
3940
};

src/account/account_repository_db.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,13 @@ bool AccountRepositoryDB::loadBySession(const std::string &sessionKey, std::uniq
5555
bool AccountRepositoryDB::save(const std::unique_ptr<AccountInfo> &accInfo) {
5656
bool successful = g_database().executeQuery(
5757
fmt::format(
58-
"UPDATE `accounts` SET `type` = {}, `premdays` = {}, `lastday` = {}, `creation` = {}, `premdays_purchased` = {} WHERE `id` = {}",
58+
"UPDATE `accounts` SET `type` = {}, `premdays` = {}, `lastday` = {}, `creation` = {}, `premdays_purchased` = {}, `house_bid_id` = {} WHERE `id` = {}",
5959
accInfo->accountType,
6060
accInfo->premiumRemainingDays,
6161
accInfo->premiumLastDay,
6262
accInfo->creationTime,
6363
accInfo->premiumDaysPurchased,
64+
accInfo->houseBidId,
6465
accInfo->id
6566
)
6667
);

src/config/config_enums.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ enum ConfigKey_t : uint16_t {
352352
BEDS_ONLY_PREMIUM,
353353
LOGIN_PROTECTION,
354354
SPELL_NAME_INSTEAD_WORDS,
355+
CYCLOPEDIA_HOUSE_AUCTION,
356+
DAYS_TO_CLOSE_BID,
355357
LOG_PLAYERS_STATEMENTS,
356358
ROOK_SYSTEM,
357359
ROOK_TOWN,

src/config/configmanager.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ bool ConfigManager::load() {
173173
loadBoolConfig(L, CHAIN_SYSTEM_VIP_ONLY, "chainSystemVipOnly", false);
174174
loadBoolConfig(L, BEDS_ONLY_PREMIUM, "bedsOnlyPremium", true);
175175
loadBoolConfig(L, SPELL_NAME_INSTEAD_WORDS, "spellNameInsteadOfWords", false);
176+
loadBoolConfig(L, CYCLOPEDIA_HOUSE_AUCTION, "toggleCyclopediaHouseAuction", true);
176177
loadBoolConfig(L, LOG_PLAYERS_STATEMENTS, "logPlayersStatements", false);
177178
loadBoolConfig(L, ROOK_SYSTEM, "toggleRookSystem", false);
178179
loadBoolConfig(L, TOGGLE_ADD_ROOK_ITEMS, "toggleAddRookItems", false);
@@ -386,6 +387,7 @@ bool ConfigManager::load() {
386387
loadIntConfig(L, ROOK_SLOT_RIGHT, "rookSlotRight", 0);
387388
loadIntConfig(L, ROOK_SLOT_LEFT, "rookSlotLeft", 0);
388389
loadIntConfig(L, ROOK_SLOT_AMMO, "rookSlotAmmo", 0);
390+
loadIntConfig(L, DAYS_TO_CLOSE_BID, "daysToCloseBid", 7);
389391
loadIntConfig(L, ANIMUS_MASTERY_MONSTERS_TO_INCREASE_XP_MULTIPLIER, "animusMasteryMonstersToIncreaseXpMultiplier", 10);
390392

391393
loadStringConfig(L, CORE_DIRECTORY, "coreDirectory", "data");

src/creatures/players/player.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "enums/object_category.hpp"
4747
#include "enums/player_blessings.hpp"
4848
#include "enums/player_icons.hpp"
49+
#include "enums/player_cyclopedia.hpp"
4950
#include "game/game.hpp"
5051
#include "game/modal_window/modal_window.hpp"
5152
#include "game/scheduling/dispatcher.hpp"
@@ -2293,6 +2294,23 @@ void Player::sendOutfitWindow() const {
22932294
}
22942295
}
22952296

2297+
// House auction
2298+
void Player::sendCyclopediaHouseList(const HouseMap &houses) const {
2299+
if (client) {
2300+
client->sendCyclopediaHouseList(houses);
2301+
}
2302+
}
2303+
void Player::sendResourceBalance(Resource_t resourceType, uint64_t value) const {
2304+
if (client) {
2305+
client->sendResourceBalance(resourceType, value);
2306+
}
2307+
}
2308+
void Player::sendHouseAuctionMessage(uint32_t houseId, HouseAuctionType type, uint8_t index, bool bidSuccess /* = false*/) const {
2309+
if (client) {
2310+
client->sendHouseAuctionMessage(houseId, type, index, bidSuccess);
2311+
}
2312+
}
2313+
22962314
// Imbuements
22972315

22982316
void Player::onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr<Item> &item, uint8_t slot, bool protectionCharm) {
@@ -10933,3 +10951,87 @@ void Player::removeDeflectCondition(const std::string_view &source, const Condit
1093310951
void Player::addDeflectCondition(std::string source, ConditionType_t conditionType, uint8_t chance) {
1093410952
deflectConditions.emplace_back(source, conditionType, chance);
1093510953
}
10954+
10955+
BidErrorMessage Player::canBidHouse(uint32_t houseId) {
10956+
using enum BidErrorMessage;
10957+
const auto house = g_game().map.houses.getHouseByClientId(houseId);
10958+
if (!house) {
10959+
return Internal;
10960+
}
10961+
if (getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
10962+
return Rookgaard;
10963+
}
10964+
if (!isPremium()) {
10965+
return Premium;
10966+
}
10967+
if (getAccount()->getHouseBidId() != 0) {
10968+
return OnlyOneBid;
10969+
}
10970+
if (getBankBalance() < (house->getRent() + house->getHighestBid())) {
10971+
return NotEnoughMoney;
10972+
}
10973+
if (house->isGuildhall()) {
10974+
if (getGuildRank() && getGuildRank()->level != 3) {
10975+
return Guildhall;
10976+
}
10977+
if (getGuild() && getGuild()->getBankBalance() < (house->getRent() + house->getHighestBid())) {
10978+
return NotEnoughGuildMoney;
10979+
}
10980+
}
10981+
return NoError;
10982+
}
10983+
10984+
TransferErrorMessage Player::canTransferHouse(uint32_t houseId, uint32_t newOwnerGUID) {
10985+
using enum TransferErrorMessage;
10986+
const auto house = g_game().map.houses.getHouseByClientId(houseId);
10987+
if (!house) {
10988+
return Internal;
10989+
}
10990+
if (getGUID() != house->getOwner()) {
10991+
return NotHouseOwner;
10992+
}
10993+
if (getGUID() == newOwnerGUID) {
10994+
return AlreadyTheOwner;
10995+
}
10996+
const auto newOwner = g_game().getPlayerByGUID(newOwnerGUID, true);
10997+
if (!newOwner) {
10998+
return CharacterNotExist;
10999+
}
11000+
if (newOwner->getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
11001+
return Rookgaard;
11002+
}
11003+
if (!newOwner->isPremium()) {
11004+
return Premium;
11005+
}
11006+
if (newOwner->getAccount()->getHouseBidId() != 0) {
11007+
return OnlyOneBid;
11008+
}
11009+
return Success;
11010+
}
11011+
11012+
AcceptTransferErrorMessage Player::canAcceptTransferHouse(uint32_t houseId) {
11013+
using enum AcceptTransferErrorMessage;
11014+
const auto house = g_game().map.houses.getHouseByClientId(houseId);
11015+
if (!house) {
11016+
return Internal;
11017+
}
11018+
if (getGUID() != house->getBidder()) {
11019+
return NotNewOwner;
11020+
}
11021+
if (!isPremium()) {
11022+
return Premium;
11023+
}
11024+
if (getAccount()->getHouseBidId() != 0) {
11025+
return AlreadyBid;
11026+
}
11027+
if (getPlayerVocationEnum() == Vocation_t::VOCATION_NONE) {
11028+
return Rookgaard;
11029+
}
11030+
if (getBankBalance() < (house->getRent() + house->getInternalBid())) {
11031+
return Frozen;
11032+
}
11033+
if (house->getTransferStatus()) {
11034+
return AlreadyAccepted;
11035+
}
11036+
return Success;
11037+
}

src/creatures/players/player.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,23 @@ struct HighscoreCharacter;
7474

7575
enum class PlayerIcon : uint8_t;
7676
enum class IconBakragore : uint8_t;
77+
enum class HouseAuctionType : uint8_t;
78+
enum class BidErrorMessage : uint8_t;
79+
enum class TransferErrorMessage : uint8_t;
80+
enum class AcceptTransferErrorMessage : uint8_t;
7781
enum ObjectCategory_t : uint8_t;
7882
enum PreySlot_t : uint8_t;
7983
enum SpeakClasses : uint8_t;
8084
enum ChannelEvent_t : uint8_t;
8185
enum SquareColor_t : uint8_t;
86+
enum Resource_t : uint8_t;
8287

8388
using GuildWarVector = std::vector<uint32_t>;
8489
using StashContainerList = std::vector<std::pair<std::shared_ptr<Item>, uint32_t>>;
8590
using ItemVector = std::vector<std::shared_ptr<Item>>;
8691
using UsersMap = std::map<uint32_t, std::shared_ptr<Player>>;
8792
using InvitedMap = std::map<uint32_t, std::shared_ptr<Player>>;
93+
using HouseMap = std::map<uint32_t, std::shared_ptr<House>>;
8894

8995
struct ForgeHistory {
9096
ForgeAction_t actionType = ForgeAction_t::FUSION;
@@ -908,6 +914,15 @@ class Player final : public Creature, public Cylinder, public Bankable {
908914
void sendOpenPrivateChannel(const std::string &receiver) const;
909915
void sendExperienceTracker(int64_t rawExp, int64_t finalExp) const;
910916
void sendOutfitWindow() const;
917+
918+
// House Auction
919+
BidErrorMessage canBidHouse(uint32_t houseId);
920+
TransferErrorMessage canTransferHouse(uint32_t houseId, uint32_t newOwnerGUID);
921+
AcceptTransferErrorMessage canAcceptTransferHouse(uint32_t houseId);
922+
void sendCyclopediaHouseList(const HouseMap &houses) const;
923+
void sendResourceBalance(Resource_t resourceType, uint64_t value) const;
924+
void sendHouseAuctionMessage(uint32_t houseId, HouseAuctionType type, uint8_t index, bool bidSuccess = false) const;
925+
911926
// Imbuements
912927
void onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr<Item> &item, uint8_t slot, bool protectionCharm);
913928
void onClearImbuement(const std::shared_ptr<Item> &item, uint8_t slot);

0 commit comments

Comments
 (0)