From bb9e300ecb6d58d930f05182e9ccd7fd6ecc0e24 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 18 Sep 2023 16:52:46 -0300 Subject: [PATCH] fix: house transfer owner only in startup --- data-otservbr-global/lib/compat/compat.lua | 2 +- data-otservbr-global/migrations/39.lua | 4 +- data-otservbr-global/migrations/40.lua | 3 + data/scripts/talkactions/god/house_owner.lua | 4 +- data/scripts/talkactions/player/buy_house.lua | 2 +- .../talkactions/player/leave_house.lua | 8 +- schema.sql | 1 + src/canary_server.cpp | 1 + src/game/game.cpp | 37 +++++++ src/game/game.hpp | 5 + src/io/iologindata.cpp | 3 +- src/io/iomapserialize.cpp | 22 ++++- src/lua/functions/map/house_functions.cpp | 46 ++++++++- src/lua/functions/map/house_functions.hpp | 8 +- src/map/house/house.cpp | 98 ++++++++++++------- src/map/house/house.hpp | 29 +++++- 16 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 data-otservbr-global/migrations/40.lua diff --git a/data-otservbr-global/lib/compat/compat.lua b/data-otservbr-global/lib/compat/compat.lua index 14e28d6e503..1de91b5ab8a 100644 --- a/data-otservbr-global/lib/compat/compat.lua +++ b/data-otservbr-global/lib/compat/compat.lua @@ -1231,7 +1231,7 @@ end function setHouseOwner(id, guid) local h = House(id) - return h and h:setOwnerGuid(guid) or false + return h and h:setHouseOwner(guid) or false end function getHouseRent(id) diff --git a/data-otservbr-global/migrations/39.lua b/data-otservbr-global/migrations/39.lua index 86a6d8ffec1..dd287a63f00 100644 --- a/data-otservbr-global/migrations/39.lua +++ b/data-otservbr-global/migrations/39.lua @@ -1,3 +1,5 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 39 (house transfer ownership on startup)") + db.query("ALTER TABLE `houses` ADD `new_owner` int(11) NOT NULL DEFAULT '-1';") + return true end diff --git a/data-otservbr-global/migrations/40.lua b/data-otservbr-global/migrations/40.lua new file mode 100644 index 00000000000..86a6d8ffec1 --- /dev/null +++ b/data-otservbr-global/migrations/40.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false -- true = There are others migrations file | false = this is the last migration file +end diff --git a/data/scripts/talkactions/god/house_owner.lua b/data/scripts/talkactions/god/house_owner.lua index 3af95895010..9ac3f7a9678 100644 --- a/data/scripts/talkactions/god/house_owner.lua +++ b/data/scripts/talkactions/god/house_owner.lua @@ -12,7 +12,7 @@ function houseOwner.onSay(player, words, param) end if param == "" or param == "none" then - house:setOwnerGuid(0) + house:setHouseOwner(0) return true end @@ -22,7 +22,7 @@ function houseOwner.onSay(player, words, param) return true end - house:setOwnerGuid(targetPlayer:getGuid()) + house:setHouseOwner(targetPlayer:getGuid()) return true end diff --git a/data/scripts/talkactions/player/buy_house.lua b/data/scripts/talkactions/player/buy_house.lua index f1293164dbb..066f9c8a49f 100644 --- a/data/scripts/talkactions/player/buy_house.lua +++ b/data/scripts/talkactions/player/buy_house.lua @@ -46,7 +46,7 @@ function buyHouse.onSay(player, words, param) return false end - house:setOwnerGuid(player:getGuid()) + house:setHouseOwner(player:getGuid()) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully bought this house, be sure to have the money for the rent in the bank.") return false end diff --git a/data/scripts/talkactions/player/leave_house.lua b/data/scripts/talkactions/player/leave_house.lua index e575283af48..335898d0944 100644 --- a/data/scripts/talkactions/player/leave_house.lua +++ b/data/scripts/talkactions/player/leave_house.lua @@ -16,6 +16,12 @@ function leaveHouse.onSay(player, words, param) return false end + if house:haveNewOwnership() then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You cannot leave this house. Ownership is already scheduled to be transferred upon the next server restart.") + playerPosition:sendMagicEffect(CONST_ME_POFF) + return false + end + -- Move hireling back to lamp local tiles = house:getTiles() if tiles then @@ -30,7 +36,7 @@ function leaveHouse.onSay(player, words, param) end end - house:setOwnerGuid(0) + house:setNewOwnerGuid(0) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully left your house.") playerPosition:sendMagicEffect(CONST_ME_POFF) return false diff --git a/schema.sql b/schema.sql index d4a69c8c830..c5f1779ec2d 100644 --- a/schema.sql +++ b/schema.sql @@ -404,6 +404,7 @@ CREATE TABLE IF NOT EXISTS `guild_membership` ( CREATE TABLE IF NOT EXISTS `houses` ( `id` int(11) NOT NULL AUTO_INCREMENT, `owner` int(11) NOT NULL, + `new_owner` int(11) NOT NULL DEFAULT '-1', `paid` int(10) UNSIGNED NOT NULL DEFAULT '0', `warnings` int(11) NOT NULL DEFAULT '0', `name` varchar(255) NOT NULL, diff --git a/src/canary_server.cpp b/src/canary_server.cpp index 49678c298ac..a1c59739050 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -68,6 +68,7 @@ int CanaryServer::run() { g_game().setGameState(GAME_STATE_INIT); setupHousesRent(); + g_game().transferHouseItemsToDepot(); IOMarket::checkExpiredOffers(); IOMarket::getInstance().updateStatistics(); diff --git a/src/game/game.cpp b/src/game/game.cpp index 7889c412cec..20d94512aad 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9854,6 +9854,43 @@ const std::unique_ptr &Game::getIOWheel() const { return m_IOWheel; } +void Game::transferHouseItemsToDepot() { + if (transferHouseItemsToPlayer.size() != 0) { + g_logger().info("Initializing house transfer items"); + } + + uint16_t transferSuccess = 0; + for (const auto &[houseId, playerGuid] : transferHouseItemsToPlayer) { + House* house = map.houses.getHouse(houseId); + if (house) { + auto offlinePlayer = new Player(nullptr); + if (!IOLoginData::loadPlayerById(offlinePlayer, playerGuid)) { + delete offlinePlayer; + continue; + } + + if (!offlinePlayer) { + continue; + } + + g_logger().info("Tranfering items to the inbox from player '{}'", offlinePlayer->getName()); + if (house->tryTransferOwnership(offlinePlayer, false)) { + transferSuccess++; + house->setNewOwnerGuid(-1, true); + } + } + } + if (transferSuccess > 0) { + g_logger().info("Finished house transfer items from '{}' players", transferSuccess); + transferHouseItemsToPlayer.clear(); + map.save(); + } +} + +void Game::setTransferPlayerHouseItems(uint32_t houseId, uint32_t playerId) { + transferHouseItemsToPlayer[houseId] = playerId; +} + template phmap::parallel_flat_hash_set setDifference(const phmap::parallel_flat_hash_set &setA, const phmap::parallel_flat_hash_set &setB) { phmap::parallel_flat_hash_set setResult; diff --git a/src/game/game.hpp b/src/game/game.hpp index 047a7f47484..a916ba38aa7 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -640,6 +640,9 @@ class Game { std::unique_ptr &getIOWheel(); const std::unique_ptr &getIOWheel() const; + void setTransferPlayerHouseItems(uint32_t houseId, uint32_t playerId); + void transferHouseItemsToDepot(); + private: std::map forgeMonsterEventIds; std::set fiendishMonsters; @@ -769,6 +772,8 @@ class Game { std::map monsters; std::vector forgeableMonsters; + std::map transferHouseItemsToPlayer; + std::map teamFinderMap; // [leaderGUID] = TeamFinder* // list of items that are in trading state, mapped to the player diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 3314cd5cf56..f0ad9209e06 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -101,7 +101,8 @@ bool IOLoginData::loadPlayerByName(Player* player, const std::string &name, bool bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result, bool disable /* = false*/) { if (!result || !player) { - g_logger().warn("[IOLoginData::loadPlayer] - Player or Resultnullptr: {}", __FUNCTION__); + std::string nullptrType = !result ? "Result" : "Player"; + g_logger().warn("[{}] - {} is nullptr", __FUNCTION__, nullptrType); return false; } diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index 8c9d8dc3125..ff479416ff5 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -10,6 +10,7 @@ #include "pch.hpp" #include "io/iomapserialize.hpp" +#include "io/iologindata.hpp" #include "game/game.hpp" #include "items/bed.hpp" @@ -259,15 +260,30 @@ void IOMapSerialize::saveTile(PropWriteStream &stream, const Tile* tile) { bool IOMapSerialize::loadHouseInfo() { Database &db = Database::getInstance(); - DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `new_owner`, `paid`, `warnings` FROM `houses`"); if (!result) { return false; } do { - House* house = g_game().map.houses.getHouse(result->getNumber("id")); + auto houseId = result->getNumber("id"); + House* house = g_game().map.houses.getHouse(houseId); if (house) { - house->setOwner(result->getNumber("owner"), false); + uint32_t owner = result->getNumber("owner"); + int32_t newOwner = result->getNumber("new_owner"); + // Transfer house owner + if (newOwner >= 0) { + g_game().setTransferPlayerHouseItems(houseId, owner); + if (newOwner == 0) { + g_logger().debug("Removing house id '{}' owner", houseId); + house->setOwner(0); + } else { + g_logger().debug("Setting house id '{}' owner to player GUID '{}'", houseId, newOwner); + house->setOwner(newOwner); + } + } else { + house->setOwner(owner, false); + } house->setPaidUntil(result->getNumber("paid")); house->setPayRentWarnings(result->getNumber("warnings")); } diff --git a/src/lua/functions/map/house_functions.cpp b/src/lua/functions/map/house_functions.cpp index 72129001ca5..2b188a509f6 100644 --- a/src/lua/functions/map/house_functions.cpp +++ b/src/lua/functions/map/house_functions.cpp @@ -114,8 +114,8 @@ int HouseFunctions::luaHouseGetOwnerGuid(lua_State* L) { return 1; } -int HouseFunctions::luaHouseSetOwnerGuid(lua_State* L) { - // house:setOwnerGuid(guid[, updateDatabase = true]) +int HouseFunctions::luaHouseSetHouseOwner(lua_State* L) { + // house:setHouseOwner(guid[, updateDatabase = true]) House* house = getUserdata(L, 1); if (house) { uint32_t guid = getNumber(L, 2); @@ -128,6 +128,41 @@ int HouseFunctions::luaHouseSetOwnerGuid(lua_State* L) { return 1; } +int HouseFunctions::luaHouseSetNewOwnerGuid(lua_State* L) { + // house:setNewOwnerGuid(guid) + House* house = getUserdata(L, 1); + if (house) { + if (house->haveNewOwnership()) { + Player* player = g_game().getPlayerByGUID(house->getOwner()); + if (player) { + player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You cannot leave this house. Ownership is already scheduled to be transferred upon the next server restart."); + } + lua_pushnil(L); + return 1; + } + + uint32_t guid = getNumber(L, 2, 0); + house->setNewOwnerGuid(guid, false); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int HouseFunctions::luaHouseHaveNewOwnership(lua_State* L) { + // house:haveNewOwnership(guid) + House* house = getUserdata(L, 1); + if (!house) { + reportErrorFunc("House not found"); + lua_pushnil(L); + return 1; + } + + pushBoolean(L, house->haveNewOwnership()); + return 1; +} + int HouseFunctions::luaHouseStartTrade(lua_State* L) { // house:startTrade(player, tradePartner) House* house = getUserdata(L, 1); @@ -165,6 +200,13 @@ int HouseFunctions::luaHouseStartTrade(lua_State* L) { return 1; } + if (house->haveNewOwnership()) { + tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You cannot buy this house. Ownership is already scheduled to be transferred upon the next server restart."); + player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You cannot sell this house. Ownership is already scheduled to be transferred upon the next server restart."); + lua_pushnumber(L, RETURNVALUE_YOUCANNOTTRADETHISHOUSE); + return 1; + } + transferItem->getParent()->setParent(player); if (!g_game().internalStartTrade(player, tradePartner, transferItem)) { house->resetTransferItem(); diff --git a/src/lua/functions/map/house_functions.hpp b/src/lua/functions/map/house_functions.hpp index 31ef31eb3f6..d085d6c62b1 100644 --- a/src/lua/functions/map/house_functions.hpp +++ b/src/lua/functions/map/house_functions.hpp @@ -25,7 +25,9 @@ class HouseFunctions final : LuaScriptInterface { registerMethod(L, "House", "getPrice", HouseFunctions::luaHouseGetPrice); registerMethod(L, "House", "getOwnerGuid", HouseFunctions::luaHouseGetOwnerGuid); - registerMethod(L, "House", "setOwnerGuid", HouseFunctions::luaHouseSetOwnerGuid); + registerMethod(L, "House", "setHouseOwner", HouseFunctions::luaHouseSetHouseOwner); + registerMethod(L, "House", "setNewOwnerGuid", HouseFunctions::luaHouseSetNewOwnerGuid); + registerMethod(L, "House", "haveNewOwnership", HouseFunctions::luaHouseHaveNewOwnership); registerMethod(L, "House", "startTrade", HouseFunctions::luaHouseStartTrade); registerMethod(L, "House", "getBeds", HouseFunctions::luaHouseGetBeds); @@ -58,7 +60,9 @@ class HouseFunctions final : LuaScriptInterface { static int luaHouseGetPrice(lua_State* L); static int luaHouseGetOwnerGuid(lua_State* L); - static int luaHouseSetOwnerGuid(lua_State* L); + static int luaHouseSetHouseOwner(lua_State* L); + static int luaHouseSetNewOwnerGuid(lua_State* L); + static int luaHouseHaveNewOwnership(lua_State* L); static int luaHouseStartTrade(lua_State* L); static int luaHouseGetBeds(lua_State* L); diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index 87ab09c857c..2cf2e66a5a1 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -24,12 +24,61 @@ void House::addTile(HouseTile* tile) { updateDoorDescription(); } +void House::setNewOwnerGuid(int32_t newOwnerGuid, bool serverStartup) { + std::ostringstream query; + query << "UPDATE `houses` SET `new_owner` = " << newOwnerGuid << " WHERE `id` = " << id; + + Database &db = Database::getInstance(); + db.executeQuery(query.str()); + if (!serverStartup) { + setNewOwnership(); + } +} + +bool House::tryTransferOwnership(Player* player, bool serverStartup) { + for (HouseTile* tile : houseTiles) { + if (const CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + kickPlayer(nullptr, (*creatures)[i]->getPlayer()); + } + } + } + + // Remove players from beds + for (BedItem* bed : bedsList) { + if (bed->getSleeper() != 0) { + bed->wakeUp(nullptr); + } + } + + // Clean access lists + if (serverStartup) { + owner = 0; + ownerAccountId = 0; + } + setAccessList(SUBOWNER_LIST, ""); + setAccessList(GUEST_LIST, ""); + + for (Door* door : doorList) { + door->setAccessList(""); + } + + bool transferSuccess = false; + if (player) { + transferSuccess = transferToDepot(player); + } else { + transferSuccess = transferToDepot(); + } + + return transferSuccess; +} + void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, Player* player /* = nullptr*/) { if (updateDatabase && owner != guid) { Database &db = Database::getInstance(); std::ostringstream query; - query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; + query << "UPDATE `houses` SET `owner` = " << guid << ", `new_owner` = -1, `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; db.executeQuery(query.str()); } @@ -40,37 +89,7 @@ void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, Player* pla isLoaded = true; if (owner != 0) { - // Send items to depot - if (player) { - transferToDepot(player); - } else { - transferToDepot(); - } - - for (HouseTile* tile : houseTiles) { - if (const CreatureVector* creatures = tile->getCreatures()) { - for (int32_t i = creatures->size(); --i >= 0;) { - kickPlayer(nullptr, (*creatures)[i]->getPlayer()); - } - } - } - - // Remove players from beds - for (BedItem* bed : bedsList) { - if (bed->getSleeper() != 0) { - bed->wakeUp(nullptr); - } - } - - // clean access lists - owner = 0; - ownerAccountId = 0; - setAccessList(SUBOWNER_LIST, ""); - setAccessList(GUEST_LIST, ""); - - for (Door* door : doorList) { - door->setAccessList(""); - } + tryTransferOwnership(player, true); } else { std::string strRentPeriod = asLowerCaseString(g_configManager().getString(HOUSE_RENT_PERIOD)); time_t currentTime = time(nullptr); @@ -217,7 +236,7 @@ void House::setAccessList(uint32_t listId, const std::string &textlist) { } bool House::transferToDepot() const { - if (townId == 0 || owner == 0) { + if (townId == 0) { return false; } @@ -237,7 +256,7 @@ bool House::transferToDepot() const { } bool House::transferToDepot(Player* player) const { - if (townId == 0 || owner == 0) { + if (townId == 0) { return false; } ItemList moveItemList; @@ -396,6 +415,11 @@ HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house) { void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner) { if (event == ON_TRADE_TRANSFER) { if (house) { + owner->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully bought the house. The ownership will be transferred upon server restart."); + auto oldOwner = g_game().getPlayerByGUID(house->getOwner()); + if (oldOwner) { + oldOwner->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully sold your house. The ownership will be transferred upon server restart."); + } house->executeTransfer(this, owner); } @@ -412,7 +436,11 @@ bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) { return false; } - setOwner(newOwner->getGUID()); + if (setNewOwnerOnStartup) { + return false; + } + + setNewOwnerGuid(newOwner->getGUID(), false); transferItem = nullptr; return true; } diff --git a/src/map/house/house.hpp b/src/map/house/house.hpp index 3095e7e1692..0e7c33486a7 100644 --- a/src/map/house/house.hpp +++ b/src/map/house/house.hpp @@ -133,6 +133,22 @@ class House { return houseName; } + /** + * @brief Set the new owner's GUID for the house. + * + * This function updates the new owner's GUID in the database. + * It also sets the `setNewOwnerOnStartup` flag if the given guid is positive. + * + * @param guid The new owner's GUID. Default value is 0. + * @param serverStartup If set to false, further changes to ownership will be blocked. + * + * @note The guid "0" is used when the player uses the "leavehouse" command, + * indicating that the house is not being transferred to anyone. + * @note The guid "-1" represents the default value and will not execute any actions. + * @note The actual transfer of ownership will occur upon server restart if `serverStartup` is set to false. + */ + void setNewOwnerGuid(int32_t newOwnerGuid, bool serverStartup); + bool tryTransferOwnership(Player* player, bool serverStartup); void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr); uint32_t getOwner() const { return owner; @@ -210,9 +226,18 @@ class House { return maxBeds; } + bool haveNewOwnership() { + return setNewOwnerOnStartup; + } + + void setNewOwnership() { + setNewOwnerOnStartup = true; + } + + bool transferToDepot(Player* player) const; + private: bool transferToDepot() const; - bool transferToDepot(Player* player) const; AccessList guestList; AccessList subOwnerList; @@ -226,6 +251,8 @@ class House { std::string houseName; std::string ownerName; + bool setNewOwnerOnStartup = false; + HouseTransferItem* transferItem = nullptr; time_t paidUntil = 0;