forgottenserver/src/house.cpp
Ramon Bernardo f849a53294
refactor(tile): unify house tile access and checks with removing dynamic cast (#5054)
Co-authored-by: Ranieri Althoff <1993083+ranisalt@users.noreply.github.com>
2025-11-19 08:58:14 +01:00

704 lines
15 KiB
C++

// Copyright 2023 The Forgotten Server Authors. All rights reserved.
// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
#include "otpch.h"
#include "house.h"
#include "bed.h"
#include "configmanager.h"
#include "game.h"
#include "housetile.h"
#include "inbox.h"
#include "iologindata.h"
#include "pugicast.h"
extern Game g_game;
House::House(uint32_t houseId) : id(houseId) {}
void House::addTile(HouseTile* tile)
{
tile->setFlag(TILESTATE_PROTECTIONZONE);
houseTiles.push_back(tile);
}
void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, Player* player /* = nullptr*/)
{
if (updateDatabase && owner != guid) {
Database& db = Database::getInstance();
db.executeQuery(fmt::format(
"UPDATE `houses` SET `owner` = {:d}, `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = {:d}",
guid, id));
}
if (isLoaded && owner == guid) {
return;
}
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;
ownerName.clear();
setAccessList(SUBOWNER_LIST, "");
setAccessList(GUEST_LIST, "");
for (Door* door : doorSet) {
door->setAccessList("");
}
} else {
std::string strRentPeriod = boost::algorithm::to_lower_copy(getString(ConfigManager::HOUSE_RENT_PERIOD));
time_t currentTime = time(nullptr);
if (strRentPeriod == "yearly") {
currentTime += 24 * 60 * 60 * 365;
} else if (strRentPeriod == "monthly") {
currentTime += 24 * 60 * 60 * 30;
} else if (strRentPeriod == "weekly") {
currentTime += 24 * 60 * 60 * 7;
} else if (strRentPeriod == "daily") {
currentTime += 24 * 60 * 60;
} else {
currentTime = 0;
}
paidUntil = currentTime;
}
rentWarnings = 0;
if (guid != 0) {
std::string name = IOLoginData::getNameByGuid(guid);
if (!name.empty()) {
owner = guid;
ownerName = name;
ownerAccountId = IOLoginData::getAccountIdByPlayerName(name);
}
}
}
AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) const
{
if (!player) {
return HOUSE_OWNER;
}
if (getBoolean(ConfigManager::HOUSE_OWNED_BY_ACCOUNT)) {
if (ownerAccountId == player->getAccount()) {
return HOUSE_OWNER;
}
}
if (player->hasFlag(PlayerFlag_CanEditHouses)) {
return HOUSE_OWNER;
}
if (player->getGUID() == owner) {
return HOUSE_OWNER;
}
if (subOwnerList.isInList(player)) {
return HOUSE_SUBOWNER;
}
if (guestList.isInList(player)) {
return HOUSE_GUEST;
}
return HOUSE_NOT_INVITED;
}
bool House::kickPlayer(Player* player, Player* target)
{
if (!target) {
return false;
}
const auto tile = target->getTile();
if (!tile) {
return false;
}
const auto houseTile = tile->getHouseTile();
if (!houseTile || houseTile->getHouse() != this) {
return false;
}
if (getHouseAccessLevel(player) < getHouseAccessLevel(target) || target->hasFlag(PlayerFlag_CanEditHouses)) {
return false;
}
Position oldPosition = target->getPosition();
if (g_game.internalTeleport(target, getEntryPosition()) == RETURNVALUE_NOERROR) {
g_game.addMagicEffect(oldPosition, CONST_ME_POFF);
g_game.addMagicEffect(getEntryPosition(), CONST_ME_TELEPORT);
}
return true;
}
void House::setAccessList(uint32_t listId, std::string_view textlist)
{
if (listId == GUEST_LIST) {
guestList.parseList(textlist);
} else if (listId == SUBOWNER_LIST) {
subOwnerList.parseList(textlist);
} else {
Door* door = getDoorByNumber(listId);
if (door) {
door->setAccessList(textlist);
}
// We do not have to kick anyone
return;
}
// kick uninvited players
for (HouseTile* tile : houseTiles) {
if (CreatureVector* creatures = tile->getCreatures()) {
for (int32_t i = creatures->size(); --i >= 0;) {
Player* player = (*creatures)[i]->getPlayer();
if (player && !isInvited(player)) {
kickPlayer(nullptr, player);
}
}
}
}
}
bool House::transferToDepot() const
{
if (townId == 0 || owner == 0) {
return false;
}
Player* player = g_game.getPlayerByGUID(owner);
if (player) {
transferToDepot(player);
} else {
Player tmpPlayer(nullptr);
if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) {
return false;
}
transferToDepot(&tmpPlayer);
IOLoginData::savePlayer(&tmpPlayer);
}
return true;
}
bool House::transferToDepot(Player* player) const
{
if (townId == 0 || owner == 0) {
return false;
}
ItemList moveItemList;
for (HouseTile* tile : houseTiles) {
if (const TileItemVector* items = tile->getItemList()) {
for (Item* item : *items) {
if (item->isPickupable()) {
moveItemList.push_back(item);
} else {
Container* container = item->getContainer();
if (container) {
for (Item* containerItem : container->getItemList()) {
moveItemList.push_back(containerItem);
}
}
}
}
}
}
for (Item* item : moveItemList) {
g_game.internalMoveItem(item->getParent(), player->getInbox().get(), INDEX_WHEREEVER, item,
item->getItemCount(), nullptr, FLAG_NOLIMIT);
}
return true;
}
bool House::getAccessList(uint32_t listId, std::string& list) const
{
if (listId == GUEST_LIST) {
guestList.getList(list);
return true;
} else if (listId == SUBOWNER_LIST) {
subOwnerList.getList(list);
return true;
}
Door* door = getDoorByNumber(listId);
if (!door) {
return false;
}
return door->getAccessList(list);
}
bool House::isInvited(const Player* player) const { return getHouseAccessLevel(player) != HOUSE_NOT_INVITED; }
void House::addDoor(Door* door)
{
door->incrementReferenceCounter();
doorSet.insert(door);
door->setHouse(this);
}
void House::removeDoor(Door* door)
{
auto it = doorSet.find(door);
if (it != doorSet.end()) {
door->decrementReferenceCounter();
doorSet.erase(it);
}
}
void House::addBed(BedItem* bed)
{
bedsList.push_back(bed);
bed->setHouse(this);
}
Door* House::getDoorByNumber(uint32_t doorId) const
{
for (Door* door : doorSet) {
if (door->getDoorId() == doorId) {
return door;
}
}
return nullptr;
}
Door* House::getDoorByPosition(const Position& pos)
{
for (Door* door : doorSet) {
if (door->getPosition() == pos) {
return door;
}
}
return nullptr;
}
bool House::canEditAccessList(uint32_t listId, const Player* player)
{
switch (getHouseAccessLevel(player)) {
case HOUSE_OWNER:
return true;
case HOUSE_SUBOWNER:
return listId == GUEST_LIST;
default:
return false;
}
}
HouseTransferItem* House::getTransferItem()
{
if (transferItem) {
return nullptr;
}
transfer_container.setParent(nullptr);
transferItem = HouseTransferItem::createHouseTransferItem(this);
transfer_container.addThing(transferItem);
return transferItem;
}
void House::resetTransferItem()
{
if (transferItem) {
Item* tmpItem = transferItem;
transferItem = nullptr;
transfer_container.setParent(nullptr);
transfer_container.removeThing(tmpItem, tmpItem->getItemCount());
g_game.ReleaseItem(tmpItem);
}
}
HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house)
{
HouseTransferItem* transferItem = new HouseTransferItem(house);
transferItem->incrementReferenceCounter();
transferItem->setID(ITEM_DOCUMENT_RO);
transferItem->setSubType(1);
transferItem->setSpecialDescription(fmt::format("It is a house transfer document for '{:s}'.", house->getName()));
return transferItem;
}
void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner)
{
if (event == ON_TRADE_TRANSFER) {
if (house) {
house->executeTransfer(this, owner);
}
g_game.internalRemoveItem(this, 1);
} else if (event == ON_TRADE_CANCEL) {
if (house) {
house->resetTransferItem();
}
}
}
bool House::executeTransfer(HouseTransferItem* item, Player* newOwner)
{
if (transferItem != item) {
return false;
}
setOwner(newOwner->getGUID());
transferItem = nullptr;
return true;
}
void AccessList::parseList(std::string_view list)
{
playerList.clear();
guildRankList.clear();
allowEveryone = false;
this->list = list;
if (list.empty()) {
return;
}
std::istringstream listStream{this->list};
std::string line;
uint16_t lineNo = 1;
while (getline(listStream, line)) {
if (++lineNo > 100) {
break;
}
boost::algorithm::trim(line);
if (line.empty() || line.front() == '#' || line.length() > 100) {
continue;
}
std::string::size_type at_pos = line.find("@");
if (at_pos != std::string::npos) {
if (at_pos == 0) {
addGuild(line.substr(1));
} else {
addGuildRank(line.substr(0, at_pos), line.substr(at_pos + 1));
}
} else if (line == "*") {
allowEveryone = true;
} else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos ||
line.find("?") != std::string::npos) {
continue; // regexp no longer supported
} else {
addPlayer(line);
}
}
}
void AccessList::addPlayer(const std::string& name)
{
Player* player = g_game.getPlayerByName(name);
if (player) {
playerList.insert(player->getGUID());
} else {
uint32_t guid = IOLoginData::getGuidByName(name);
if (guid != 0) {
playerList.insert(guid);
}
}
}
namespace {
const Guild_ptr getGuildByName(const std::string& name)
{
uint32_t guildId = IOGuild::getGuildIdByName(name);
if (guildId == 0) {
return nullptr;
}
if (const auto& guild = g_game.getGuild(guildId)) {
return guild;
}
return IOGuild::loadGuild(guildId);
}
} // namespace
void AccessList::addGuild(const std::string& name)
{
if (const auto& guild = getGuildByName(name)) {
for (const auto& rank : guild->getRanks()) {
guildRankList.insert(rank->id);
}
}
}
void AccessList::addGuildRank(const std::string& name, const std::string& rankName)
{
if (const auto& guild = getGuildByName(name)) {
if (const auto& rank = guild->getRankByName(rankName)) {
guildRankList.insert(rank->id);
}
}
}
bool AccessList::isInList(const Player* player) const
{
if (allowEveryone) {
return true;
}
auto playerIt = playerList.find(player->getGUID());
if (playerIt != playerList.end()) {
return true;
}
const auto& rank = player->getGuildRank();
return rank && guildRankList.find(rank->id) != guildRankList.end();
}
void AccessList::getList(std::string& list) const { list = this->list; }
Door::Door(uint16_t type) : Item(type) {}
Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream)
{
if (attr == ATTR_HOUSEDOORID) {
uint8_t doorId;
if (!propStream.read<uint8_t>(doorId)) {
return ATTR_READ_ERROR;
}
setDoorId(doorId);
return ATTR_READ_CONTINUE;
}
return Item::readAttr(attr, propStream);
}
void Door::setHouse(House* house)
{
if (this->house) {
return;
}
this->house = house;
if (!accessList) {
accessList.reset(new AccessList());
}
}
bool Door::canUse(const Player* player)
{
if (!house) {
return true;
}
if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) {
return true;
}
return accessList->isInList(player);
}
void Door::setAccessList(std::string_view textlist)
{
if (!accessList) {
accessList.reset(new AccessList());
}
accessList->parseList(textlist);
}
bool Door::getAccessList(std::string& list) const
{
if (!house) {
return false;
}
accessList->getList(list);
return true;
}
void Door::onRemoved()
{
Item::onRemoved();
if (house) {
house->removeDoor(this);
}
}
House* Houses::getHouseByPlayerId(uint32_t playerId)
{
for (const auto& it : houseMap) {
if (it.second->getOwner() == playerId) {
return it.second;
}
}
return nullptr;
}
bool Houses::loadHousesXML(const std::string& filename)
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(filename.c_str());
if (!result) {
printXMLError("Error - Houses::loadHousesXML", filename, result);
return false;
}
for (auto houseNode : doc.child("houses").children()) {
pugi::xml_attribute houseIdAttribute = houseNode.attribute("houseid");
if (!houseIdAttribute) {
return false;
}
int32_t houseId = pugi::cast<int32_t>(houseIdAttribute.value());
House* house = getHouse(houseId);
if (!house) {
std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << houseId << std::endl;
return false;
}
house->setName(houseNode.attribute("name").as_string());
Position entryPos(pugi::cast<uint16_t>(houseNode.attribute("entryx").value()),
pugi::cast<uint16_t>(houseNode.attribute("entryy").value()),
pugi::cast<uint16_t>(houseNode.attribute("entryz").value()));
if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) {
std::cout << "[Warning - Houses::loadHousesXML] House entry not set - Name: " << house->getName()
<< " - House id: " << houseId << std::endl;
}
house->setEntryPos(entryPos);
house->setRent(pugi::cast<uint32_t>(houseNode.attribute("rent").value()));
house->setTownId(pugi::cast<uint32_t>(houseNode.attribute("townid").value()));
house->setOwner(0, false);
}
return true;
}
void Houses::payHouses(RentPeriod_t rentPeriod) const
{
if (rentPeriod == RENTPERIOD_NEVER) {
return;
}
time_t currentTime = time(nullptr);
for (const auto& it : houseMap) {
House* house = it.second;
if (house->getOwner() == 0) {
continue;
}
const uint32_t rent = house->getRent();
if (rent == 0 || house->getPaidUntil() > currentTime) {
continue;
}
const uint32_t ownerId = house->getOwner();
const Town* town = g_game.map.towns.getTown(house->getTownId());
if (!town) {
continue;
}
Player player(nullptr);
if (!IOLoginData::loadPlayerById(&player, ownerId)) {
// Player doesn't exist, reset house owner
house->setOwner(0);
continue;
}
if (player.getBankBalance() >= rent) {
player.setBankBalance(player.getBankBalance() - rent);
time_t paidUntil = currentTime;
switch (rentPeriod) {
case RENTPERIOD_DAILY:
paidUntil += 24 * 60 * 60;
break;
case RENTPERIOD_WEEKLY:
paidUntil += 24 * 60 * 60 * 7;
break;
case RENTPERIOD_MONTHLY:
paidUntil += 24 * 60 * 60 * 30;
break;
case RENTPERIOD_YEARLY:
paidUntil += 24 * 60 * 60 * 365;
break;
default:
break;
}
house->setPaidUntil(paidUntil);
house->setPayRentWarnings(0);
} else {
if (house->getPayRentWarnings() < 7) {
int32_t daysLeft = 7 - house->getPayRentWarnings();
Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
std::string period;
switch (rentPeriod) {
case RENTPERIOD_DAILY:
period = "daily";
break;
case RENTPERIOD_WEEKLY:
period = "weekly";
break;
case RENTPERIOD_MONTHLY:
period = "monthly";
break;
case RENTPERIOD_YEARLY:
period = "annual";
break;
default:
break;
}
letter->setText(fmt::format(
"Warning! \nThe {:s} rent of {:d} gold for your house \"{:s}\" is payable. Have it within {:d} days or you will lose this house.",
period, house->getRent(), house->getName(), daysLeft));
g_game.internalAddItem(player.getInbox().get(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
house->setPayRentWarnings(house->getPayRentWarnings() + 1);
} else {
house->setOwner(0, true, &player);
}
}
IOLoginData::savePlayer(&player);
}
}