//////////////////////////////////////////////////////////////////////// // OpenTibia - an opensource roleplaying game //////////////////////////////////////////////////////////////////////// // 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 3 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, see . //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include "house.h" #include "tools.h" #include "database.h" #include "beds.h" #include "town.h" #include "iologindata.h" #include "ioguild.h" #include "iomapserialize.h" #include "configmanager.h" #include "game.h" extern ConfigManager g_config; extern Game g_game; House::House(uint32_t _houseId) { guild = pendingTransfer = false; houseName = "Forgotten headquarter (Flat 1, Area 42)"; posEntry = Position(); houseId = _houseId; rent = price = townId = paidUntil = houseOwner = rentWarnings = lastWarning = 0; syncFlags = HOUSE_SYNC_NAME | HOUSE_SYNC_TOWN | HOUSE_SYNC_SIZE | HOUSE_SYNC_PRICE | HOUSE_SYNC_RENT | HOUSE_SYNC_GUILD; } void House::addTile(HouseTile* tile) { tile->setFlag(TILESTATE_PROTECTIONZONE); houseTiles.push_back(tile); } void House::addBed(BedItem* bed) { bedsList.push_back(bed); bed->setHouse(this); } void House::addDoor(Door* door) { door->useThing2(); doorList.push_back(door); door->setHouse(this); updateDoorDescription(); } void House::removeDoor(Door* door) { HouseDoorList::iterator it = std::find(doorList.begin(), doorList.end(), door); if(it != doorList.end()) { (*it)->releaseThing2(); doorList.erase(it); } } Door* House::getDoorByNumber(uint32_t doorId) const { for(HouseDoorList::const_iterator it = doorList.begin(); it != doorList.end(); ++it) { if((*it)->getDoorId() == doorId) return (*it); } return NULL; } Door* House::getDoorByPosition(const Position& pos) { for(HouseDoorList::iterator it = doorList.begin(); it != doorList.end(); ++it) { if((*it)->getPosition() == pos) return (*it); } return NULL; } void House::setPrice(uint32_t _price, bool update/* = false*/) { price = _price; if(update && !houseOwner) updateDoorDescription(); } void House::setHouseOwner(uint32_t guid) { houseOwner = guid; updateDoorDescription(); } bool House::setHouseOwnerEx(uint32_t guid, bool transfer) { if(houseOwner == guid) return true; if(isGuild() && guid) { Player* player = g_game.getPlayerByGuidEx(guid); if(!player) return false; guid = player->getGuildId(); } if(houseOwner) { rentWarnings = paidUntil = 0; if(transfer) clean(); setAccessList(SUBOWNER_LIST, "", !transfer); setAccessList(GUEST_LIST, "", !transfer); for(HouseDoorList::iterator it = doorList.begin(); it != doorList.end(); ++it) (*it)->setAccessList(""); } setHouseOwner(guid); if(guid) lastWarning = time(NULL); else lastWarning = 0; Database* db = Database::getInstance(); DBTransaction trans(db); if(!trans.begin()) return false; IOMapSerialize::getInstance()->saveHouse(db, this); return trans.commit(); } bool House::isGuild() const { return g_config.getBool(ConfigManager::GUILD_HALLS) && guild; } void House::updateDoorDescription(std::string name/* = ""*/) { std::string tmp = "house"; if(isGuild()) tmp = "hall"; char houseDescription[200]; if(houseOwner) { if(isGuild()) IOGuild::getInstance()->getGuildNameById(name, houseOwner); else if(name.empty()) IOLoginData::getInstance()->getNameByGuid(houseOwner, name); sprintf(houseDescription, "It belongs to %s '%s'. %s owns this %s.", tmp.c_str(), houseName.c_str(), name.c_str(), tmp.c_str()); } else sprintf(houseDescription, "It belongs to %s '%s'. Nobody owns this %s. It costs %d gold coins.", tmp.c_str(), houseName.c_str(), tmp.c_str(), price); for(HouseDoorList::iterator it = doorList.begin(); it != doorList.end(); ++it) (*it)->setSpecialDescription(houseDescription); } void House::removePlayer(Player* player, bool ignoreRights) { if(!ignoreRights && player->hasFlag(PlayerFlag_CanEditHouses)) return; Position tmp = player->getPosition(); Position toPos = g_game.getClosestFreeTile(player, getEntryPosition(), false, false); if(toPos.x && g_game.internalTeleport(player, toPos, true) == RET_NOERROR && !player->isGhost()) { g_game.addMagicEffect(tmp, NM_ME_POFF); g_game.addMagicEffect(player->getPosition(), NM_ME_TELEPORT); } } void House::removePlayers(bool ignoreInvites) { PlayerVector kickList; for(HouseTileList::iterator it = houseTiles.begin(); it != houseTiles.end(); ++it) { CreatureVector* creatures = (*it)->getCreatures(); if(!creatures) continue; for(CreatureVector::iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { Player* player = (*cit)->getPlayer(); if(player && !player->isRemoved() && (ignoreInvites || !isInvited(player))) kickList.push_back(player); } } if(kickList.size()) { for(PlayerVector::iterator it = kickList.begin(); it != kickList.end(); ++it) removePlayer((*it), false); } } bool House::kickPlayer(Player* player, Player* target) { if(!target || target->isRemoved()) return false; HouseTile* houseTile = target->getTile()->getHouseTile(); if(!houseTile || houseTile->getHouse() != this) return false; if(player == target) { removePlayer(target, true); return true; } if(getHouseAccessLevel(player) >= getHouseAccessLevel(target)) { removePlayer(target, false); return true; } return false; } void House::clean() { transferToDepot(); removePlayers(true); for(HouseBedItemList::iterator bit = bedsList.begin(); bit != bedsList.end(); ++bit) { if((*bit)->getSleeper()) (*bit)->wakeUp(); } } bool House::transferToDepot() { if(!townId) return false; Player* player = NULL; if(houseOwner) { uint32_t owner = houseOwner; if(isGuild() && !IOGuild::getInstance()->swapGuildIdToOwner(owner)) owner = 0; if(owner) player = g_game.getPlayerByGuidEx(owner); } Item* item = NULL; Container* tmpContainer = NULL; ItemList moveItemList; for(HouseTileList::iterator it = houseTiles.begin(); it != houseTiles.end(); ++it) { for(uint32_t i = 0; i < (*it)->getThingCount(); ++i) { if(!(item = (*it)->__getThing(i)->getItem())) continue; if(item->isPickupable()) moveItemList.push_back(item); else if((tmpContainer = item->getContainer())) { for(ItemList::const_iterator it = tmpContainer->getItems(); it != tmpContainer->getEnd(); ++it) moveItemList.push_back(*it); } } } if(player) { Depot* depot = player->getDepot(townId, true); for(ItemList::iterator it = moveItemList.begin(); it != moveItemList.end(); ++it) g_game.internalMoveItem(NULL, (*it)->getParent(), depot, INDEX_WHEREEVER, (*it), (*it)->getItemCount(), NULL, FLAG_NOLIMIT); if(player->isVirtual()) { IOLoginData::getInstance()->savePlayer(player); delete player; } } else { for(ItemList::iterator it = moveItemList.begin(); it != moveItemList.end(); ++it) g_game.internalRemoveItem(NULL, (*it), (*it)->getItemCount(), false, FLAG_NOLIMIT); } return true; } bool House::isInvited(const Player* player) { return getHouseAccessLevel(player) != HOUSE_NO_INVITED; } AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) { if(!player) return HOUSE_NO_INVITED; if(player->hasFlag(PlayerFlag_CanEditHouses)) return HOUSE_OWNER; if(!houseOwner) return HOUSE_NO_INVITED; if(isGuild()) { if(player->getGuildId() == houseOwner) { switch(player->getGuildLevel()) { case GUILDLEVEL_LEADER: return HOUSE_OWNER; case GUILDLEVEL_VICE: return HOUSE_SUBOWNER; default: return HOUSE_GUEST; } } } else if(player->getGUID() == houseOwner) return HOUSE_OWNER; if(subOwnerList.isInList(player)) return HOUSE_SUBOWNER; if(guestList.isInList(player)) return HOUSE_GUEST; return HOUSE_NO_INVITED; } 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: break; } return false; } bool House::getAccessList(uint32_t listId, std::string& list) const { if(listId == GUEST_LIST) { guestList.getList(list); return true; } if(listId == SUBOWNER_LIST) { subOwnerList.getList(list); return true; } if(Door* door = getDoorByNumber(listId)) return door->getAccessList(list); #ifdef __DEBUG_HOUSES__ std::cout << "[Failure - House::getAccessList] door == NULL, listId = " << listId <setAccessList(textlist); #ifdef __DEBUG_HOUSES__ else std::cout << "[Failure - House::setAccessList] door == NULL, listId = " << listId <useThing2(); transferItem->setID(ITEM_HOUSE_TRANSFER); char buffer[150]; sprintf(buffer, "It is a %s transfer document for '%s'.", house->isGuild() ? "guild hall" : "house", house->getName().c_str()); transferItem->setSpecialDescription(buffer); transferItem->setSubType(1); return transferItem; } bool HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner, Player* seller) { switch(event) { case ON_TRADE_TRANSFER: { if(house) house->setHouseOwnerEx(owner->getGUID(), true); g_game.internalRemoveItem(NULL, this, 1); seller->transferContainer.setParent(NULL); break; } case ON_TRADE_CANCEL: { owner->transferContainer.setParent(NULL); owner->transferContainer.__removeThing(this, getItemCount()); g_game.FreeThing(this); break; } default: return false; } return true; } void AccessList::getList(std::string& _list) const { _list = list; } bool AccessList::parseList(const std::string& _list) { playerList.clear(); guildList.clear(); expressionList.clear(); regExList.clear(); list = _list; if(_list.empty()) return true; std::stringstream listStream(_list); std::string line; while(getline(listStream, line)) { trimString(line); trim_left(line, "\t"); trim_right(line, "\t"); trimString(line); toLowerCaseString(line); if(line.substr(0, 1) == "#" || line.length() > 100) continue; if(line.find("@") != std::string::npos) { std::string::size_type pos = line.find("@"); addGuild(line.substr(pos + 1), line.substr(0, pos)); } else if(line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) addExpression(line); else addPlayer(line); } return true; } bool AccessList::isInList(const Player* player) { std::string name = player->getName(); boost::cmatch what; try { toLowerCaseString(name); for(RegExList::iterator it = regExList.begin(); it != regExList.end(); ++it) { if(boost::regex_match(name.c_str(), what, it->first)) return it->second; } } catch(...) {} if(playerList.find(player->getGUID()) != playerList.end()) return true; for(GuildList::iterator git = guildList.begin(); git != guildList.end(); ++git) { if(git->first == player->getGuildId() && ((uint32_t)git->second == player->getGuildRankId() || git->second == -1)) return true; } return false; } bool AccessList::addPlayer(std::string& name) { std::string tmp = name; uint32_t guid; if(!IOLoginData::getInstance()->getGuidByName(guid, tmp) || playerList.find(guid) != playerList.end()) return false; playerList.insert(guid); return true; } bool AccessList::addGuild(const std::string& guildName, const std::string& rankName) { uint32_t guildId; if(!IOGuild::getInstance()->getGuildIdByName(guildId, guildName)) return false; std::string tmp = rankName; int32_t rankId; if(!IOGuild::getInstance()->getRankIdByGuildIdAndName((uint32_t&)rankId, tmp, guildId) && (tmp.find("?") == std::string::npos || tmp.find("!") == std::string::npos || tmp.find("*") == std::string::npos)) rankId = -1; if(rankId != 0) { for(GuildList::iterator git = guildList.begin(); git != guildList.end(); ++git) { if(git->first == guildId && git->second == rankId) return true; } guildList.push_back(std::make_pair(guildId, rankId)); return true; } return false; } bool AccessList::addExpression(const std::string& expression) { for(ExpressionList::iterator it = expressionList.begin(); it != expressionList.end(); ++it) { if((*it) == expression) return false; } std::string outExp; std::string metachars = ".[{}()\\+|^$"; for(std::string::const_iterator it = expression.begin(); it != expression.end(); ++it) { if(metachars.find(*it) != std::string::npos) outExp += "\\"; outExp += (*it); } replaceString(outExp, "*", ".*"); replaceString(outExp, "?", ".?"); try { if(outExp.length() > 0) { expressionList.push_back(outExp); if(outExp.substr(0, 1) == "!") { if(outExp.length() > 1) regExList.push_front(std::make_pair(boost::regex(outExp.substr(1)), false)); } else regExList.push_back(std::make_pair(boost::regex(outExp), true)); } } catch(...) {} return true; } Door::Door(uint16_t _type): Item(_type) { house = NULL; accessList = NULL; } Door::~Door() { if(accessList) delete accessList; } Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) { if(ATTR_HOUSEDOORID == attr) { uint8_t _doorId = 0; if(!propStream.GET_UCHAR(_doorId)) return ATTR_READ_ERROR; setDoorId(_doorId); return ATTR_READ_CONTINUE; } return Item::readAttr(attr, propStream); } void Door::copyAttributes(Item* item) { Item::copyAttributes(item); if(Door* door = item->getDoor()) { std::string list; if(door->getAccessList(list)) setAccessList(list); } } void Door::onRemoved() { Item::onRemoved(); if(house) house->removeDoor(this); } bool Door::canUse(const Player* player) { if(!house || house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) return true; return accessList->isInList(player); } void Door::setHouse(House* _house) { if(house) { #ifdef __DEBUG_HOUSES__ std::cout << "[Warning - Door::setHouse] house != NULL" << std::endl; #endif return; } house = _house; if(!accessList) accessList = new AccessList(); } bool Door::getAccessList(std::string& list) const { if(!house) { #ifdef __DEBUG_HOUSES__ std::cout << "[Failure - Door::getAccessList] house == NULL" << std::endl; #endif return false; } accessList->getList(list); return true; } void Door::setAccessList(const std::string& textlist) { if(!accessList) accessList = new AccessList(); accessList->parseList(textlist); } Houses::Houses() { rentPeriod = RENTPERIOD_NEVER; std::string strValue = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); if(strValue == "yearly") rentPeriod = RENTPERIOD_YEARLY; else if(strValue == "monthly") rentPeriod = RENTPERIOD_MONTHLY; else if(strValue == "weekly") rentPeriod = RENTPERIOD_WEEKLY; else if(strValue == "daily") rentPeriod = RENTPERIOD_DAILY; } bool Houses::loadFromXml(std::string filename) { xmlDocPtr doc = xmlParseFile(filename.c_str()); if(!doc) { std::cout << "[Warning - Houses::loadFromXml] Cannot load houses file." << std::endl; std::cout << getLastXMLError() << std::endl; return false; } xmlNodePtr houseNode, root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name,(const xmlChar*)"houses")) { std::cout << "[Error - Houses::loadFromXml] Malformed houses file." << std::endl; xmlFreeDoc(doc); return false; } int32_t intValue; std::string strValue; houseNode = root->children; while(houseNode) { if(xmlStrcmp(houseNode->name,(const xmlChar*)"house")) { houseNode = houseNode->next; continue; } int32_t houseId = 0; if(!readXMLInteger(houseNode, "houseid", houseId)) { std::cout << "[Error - Houses::loadFromXml] Could not read houseId" << std::endl; xmlFreeDoc(doc); return false; } House* house = Houses::getInstance().getHouse(houseId); if(!house) { std::cout << "[Error - Houses::loadFromXml] Unknown house with id: " << houseId << std::endl; xmlFreeDoc(doc); return false; } Position entryPos(0, 0, 0); if(readXMLInteger(houseNode, "entryx", intValue)) entryPos.x = intValue; if(readXMLInteger(houseNode, "entryy", intValue)) entryPos.y = intValue; if(readXMLInteger(houseNode, "entryz", intValue)) entryPos.z = intValue; house->setEntryPos(entryPos); if(!entryPos.x || !entryPos.y) { std::cout << "[Warning - Houses::loadFromXml] House entry not set for: "; std::cout << house->getName() << " (" << houseId << ")" << std::endl; } if(readXMLString(houseNode, "name", strValue)) house->setName(strValue); else house->resetSyncFlag(House::HOUSE_SYNC_NAME); if(readXMLInteger(houseNode, "townid", intValue)) house->setTownId(intValue); else house->resetSyncFlag(House::HOUSE_SYNC_TOWN); if(readXMLInteger(houseNode, "size", intValue)) house->setSize(intValue); else house->resetSyncFlag(House::HOUSE_SYNC_SIZE); if(readXMLString(houseNode, "guildhall", strValue)) house->setGuild(booleanString(strValue)); else house->resetSyncFlag(House::HOUSE_SYNC_GUILD); uint32_t rent = 0; if(readXMLInteger(houseNode, "rent", intValue)) rent = intValue; uint32_t price = house->getTilesCount() * g_config.getNumber(ConfigManager::HOUSE_PRICE); if(g_config.getBool(ConfigManager::HOUSE_RENTASPRICE)) { uint32_t tmp = rent; if(!tmp) tmp = price; house->setPrice(tmp); } else house->setPrice(price); if(g_config.getBool(ConfigManager::HOUSE_PRICEASRENT)) house->setRent(price); else house->setRent(rent); house->setHouseOwner(0); houseNode = houseNode->next; } xmlFreeDoc(doc); return true; } bool Houses::reloadPrices() { if(g_config.getBool(ConfigManager::HOUSE_RENTASPRICE)) return true; const uint32_t tilePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE); for(HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) it->second->setPrice(tilePrice * it->second->getTilesCount(), true); return true; } void Houses::payHouses() { if(rentPeriod == RENTPERIOD_NEVER) return; uint64_t start = OTSYS_TIME(); std::cout << "> Paying houses..." << std::endl; time_t currentTime = time(NULL); for(HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) payHouse(it->second, currentTime, 0); std::cout << "> Houses paid in " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; } bool Houses::payRent(Player* player, House* house, uint32_t bid, time_t _time/* = 0*/) { if(rentPeriod == RENTPERIOD_NEVER || !house->getHouseOwner() || house->getPaidUntil() > _time || !house->getRent() || player->hasCustomFlag(PlayerCustomFlag_IgnoreHouseRent)) return true; Town* town = Towns::getInstance().getTown(house->getTownId()); if(!town) return false; bool paid = false; uint32_t amount = house->getRent() + bid; if(g_config.getBool(ConfigManager::BANK_SYSTEM) && player->balance >= amount) { player->balance -= amount; paid = true; } else if(Depot* depot = player->getDepot(town->getTownID(), true)) paid = g_game.removeMoney(depot, amount, FLAG_NOLIMIT); if(!paid) return false; if(!_time) _time = time(NULL); uint32_t paidUntil = _time; switch(rentPeriod) { case RENTPERIOD_DAILY: paidUntil += 86400; break; case RENTPERIOD_WEEKLY: paidUntil += 7 * 86400; break; case RENTPERIOD_MONTHLY: paidUntil += 30 * 86400; break; case RENTPERIOD_YEARLY: paidUntil += 365 * 86400; break; default: break; } house->setPaidUntil(paidUntil); return true; } bool Houses::payHouse(House* house, time_t _time, uint32_t bid) { if(rentPeriod == RENTPERIOD_NEVER || !house->getHouseOwner() || house->getPaidUntil() > _time || !house->getRent()) return true; Town* town = Towns::getInstance().getTown(house->getTownId()); if(!town) return false; uint32_t owner = house->getHouseOwner(); if(house->isGuild() && !IOGuild::getInstance()->swapGuildIdToOwner(owner)) { house->setHouseOwnerEx(0, true); return false; } std::string name; if(!IOLoginData::getInstance()->getNameByGuid(owner, name)) { house->setHouseOwnerEx(0, true); return false; } Player* player = g_game.getPlayerByNameEx(name); if(!player) return false; if(!player->isPremium() && g_config.getBool(ConfigManager::HOUSE_NEED_PREMIUM)) { house->setHouseOwnerEx(0, true); return false; } bool paid = payRent(player, house, bid, _time), savePlayer = false; if(!paid && _time >= (house->getLastWarning() + 86400)) { uint32_t warningsLimit = 7; switch(rentPeriod) { case RENTPERIOD_DAILY: warningsLimit = 1; break; case RENTPERIOD_WEEKLY: warningsLimit = 3; break; case RENTPERIOD_MONTHLY: warningsLimit = 7; break; case RENTPERIOD_YEARLY: warningsLimit = 14; break; default: break; } uint32_t warnings = house->getRentWarnings(); if(warnings < warningsLimit) { Depot* depot = player->getDepot(town->getTownID(), true); Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED); if(depot && letter) { 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::stringstream s; s << "Warning!\nThe " << period << " rent of " << house->getRent() << " gold for your " << (house->isGuild() ? "guild hall" : "house") << " \"" << house->getName() << "\" has to be paid. Have it within " << (warningsLimit - warnings) << " days or you will lose your " << (house->isGuild() ? "guild hall" : "house") << "."; letter->setText(s.str().c_str()); if(g_game.internalAddItem(NULL, depot, letter, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) g_game.FreeThing(letter); else savePlayer = true; } house->setRentWarnings(++warnings); house->setLastWarning(_time); } else house->setHouseOwnerEx(0, true); } if(player->isVirtual()) { if(savePlayer) IOLoginData::getInstance()->savePlayer(player); delete player; } return paid; } House* Houses::getHouse(uint32_t houseId, bool add/*= false*/) { HouseMap::iterator it = houseMap.find(houseId); if(it != houseMap.end()) return it->second; if(!add) return NULL; houseMap[houseId] = new House(houseId); return houseMap[houseId]; } House* Houses::getHouseByPlayer(Player* player) { if(!player || player->isRemoved()) return NULL; HouseTile* houseTile = player->getTile()->getHouseTile(); if(!houseTile) return NULL; if(House* house = houseTile->getHouse()) return house; return NULL; } House* Houses::getHouseByPlayerId(uint32_t playerId) { for(HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) { if(!it->second->isGuild() && it->second->getHouseOwner() == playerId) return it->second; } return NULL; } House* Houses::getHouseByGuildId(uint32_t guildId) { for(HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) { if(it->second->isGuild() && it->second->getHouseOwner() == guildId) return it->second; } return NULL; } uint32_t Houses::getHousesCount(uint32_t accId) { Account account = IOLoginData::getInstance()->loadAccount(accId); uint32_t guid, count = 0; for(Characters::iterator it = account.charList.begin(); it != account.charList.end(); ++it) { #ifndef __LOGIN_SERVER__ if(IOLoginData::getInstance()->getGuidByName(guid, (*it)) && getHouseByPlayerId(guid)) #else if(IOLoginData::getInstance()->getGuidByName(guid, (std::string&)it->first) && getHouseByPlayerId(guid)) #endif count++; } return count; }