SabrehavenServer/src/house.cpp

692 lines
15 KiB
C++

/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "otpch.h"
#include "pugicast.h"
#include "house.h"
#include "iologindata.h"
#include "game.h"
#include "configmanager.h"
#include "bed.h"
extern ConfigManager g_config;
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();
std::ostringstream query;
query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id;
db->executeQuery(query.str());
}
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;
setAccessList(SUBOWNER_LIST, "");
setAccessList(GUEST_LIST, "");
for (Door* door : doorSet) {
door->setAccessList("");
}
//reset paid date
paidUntil = 0;
rentWarnings = 0;
}
if (guid != 0) {
std::string name = IOLoginData::getNameByGuid(guid);
if (!name.empty()) {
owner = guid;
ownerName = name;
}
}
updateDoorDescription();
}
void House::updateDoorDescription() const
{
std::ostringstream ss;
if (owner != 0) {
ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house.";
} else {
ss << "It belongs to house '" << houseName << "'. Nobody owns this house.";
}
for (const auto& it : doorSet) {
it->setSpecialDescription(ss.str());
}
}
AccessHouseLevel_t House::getHouseAccessLevel(const Player* player)
{
if (!player) {
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;
}
HouseTile* houseTile = dynamic_cast<HouseTile*>(target->getTile());
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, const std::string& 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 dont have 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->getDepotLocker(getTownId(), true), 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)
{
return getHouseAccessLevel(player) != HOUSE_NOT_INVITED;
}
void House::addDoor(Door* door)
{
door->incrementReferenceCounter();
doorSet.insert(door);
door->setHouse(this);
updateDoorDescription();
}
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 != nullptr) {
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);
std::ostringstream ss;
ss << "It is a house transfer document for '" << house->getName() << "'.";
transferItem->setSpecialDescription(ss.str());
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(const std::string& list)
{
playerList.clear();
guildList.clear();
allowEveryone = false;
this->list = list;
if (list.empty()) {
return;
}
std::istringstream listStream(list);
std::string line;
int lineNo = 1;
while (getline(listStream, line)) {
if (++lineNo > 100) {
break;
}
trimString(line);
trim_left(line, '\t');
trim_right(line, '\t');
trimString(line);
if (line.empty() || line.front() == '#' || line.length() > 100) {
continue;
}
toLowerCaseString(line);
std::string::size_type at_pos = line.find("@");
if (at_pos != std::string::npos) {
addGuild(line.substr(at_pos + 1));
} 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);
}
}
}
void AccessList::addGuild(const std::string& name)
{
uint32_t guildId = IOGuild::getGuildIdByName(name);
if (guildId != 0) {
guildList.insert(guildId);
}
}
bool AccessList::isInList(const Player* player)
{
if (allowEveryone) {
return true;
}
auto playerIt = playerList.find(player->getGUID());
if (playerIt != playerList.end()) {
return true;
}
const Guild* guild = player->getGuild();
return guild && guildList.find(guild->getId()) != guildList.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 != nullptr) {
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(const std::string& 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();
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);
} 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;
}
std::ostringstream ss;
ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house.";
letter->setText(ss.str());
g_game.internalAddItem(player.getDepotLocker(house->getTownId(), true), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
house->setPayRentWarnings(house->getPayRentWarnings() + 1);
} else {
house->setOwner(0, true, &player);
}
}
IOLoginData::savePlayer(&player);
}
}