1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "pugicast.h"
23
24#include "house.h"
25#include "iologindata.h"
26#include "game.h"
27#include "configmanager.h"
28#include "bed.h"
29
30extern ConfigManager g_config;
31extern Game g_game;
32
33House::House(uint32_t houseId) : id(houseId) {}
34
35void House::addTile(HouseTile* tile)
36{
37 tile->setFlag(TILESTATE_PROTECTIONZONE);
38 houseTiles.push_back(tile);
39}
40
41void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/)
42{
43 if (updateDatabase && owner != guid) {
44 Database& db = Database::getInstance();
45
46 std::ostringstream query;
47 query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id;
48 db.executeQuery(query.str());
49 }
50
51 if (isLoaded && owner == guid) {
52 return;
53 }
54
55 isLoaded = true;
56
57 if (owner != 0) {
58 //send items to depot
59 if (player) {
60 transferToDepot(player);
61 } else {
62 transferToDepot();
63 }
64
65 for (HouseTile* tile : houseTiles) {
66 if (const CreatureVector* creatures = tile->getCreatures()) {
67 for (int32_t i = creatures->size(); --i >= 0;) {
68 kickPlayer(nullptr, (*creatures)[i]->getPlayer());
69 }
70 }
71 }
72
73 // Remove players from beds
74 for (BedItem* bed : bedsList) {
75 if (bed->getSleeper() != 0) {
76 bed->wakeUp(nullptr);
77 }
78 }
79
80 //clean access lists
81 owner = 0;
82 ownerAccountId = 0;
83 setAccessList(SUBOWNER_LIST, "");
84 setAccessList(GUEST_LIST, "");
85
86 for (Door* door : doorSet) {
87 door->setAccessList("");
88 }
89 } else {
90 std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD));
91 time_t currentTime = time(nullptr);
92 if (strRentPeriod == "yearly") {
93 currentTime += 24 * 60 * 60 * 365;
94 } else if (strRentPeriod == "monthly") {
95 currentTime += 24 * 60 * 60 * 30;
96 } else if (strRentPeriod == "weekly") {
97 currentTime += 24 * 60 * 60 * 7;
98 } else if (strRentPeriod == "daily") {
99 currentTime += 24 * 60 * 60;
100 } else {
101 currentTime = 0;
102 }
103
104 paidUntil = currentTime;
105 }
106
107 rentWarnings = 0;
108
109 if (guid != 0) {
110 std::string name = IOLoginData::getNameByGuid(guid);
111 if (!name.empty()) {
112 owner = guid;
113 ownerName = name;
114 ownerAccountId = IOLoginData::getAccountIdByPlayerName(name);
115 }
116 }
117
118 updateDoorDescription();
119}
120
121void House::updateDoorDescription() const
122{
123 std::ostringstream ss;
124 if (owner != 0) {
125 ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house.";
126 } else {
127 ss << "It belongs to house '" << houseName << "'. Nobody owns this house.";
128
129 const int32_t housePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE);
130 if (housePrice != -1 && g_config.getBoolean(ConfigManager::HOUSE_DOOR_SHOW_PRICE)) {
131 ss << " It costs " << (houseTiles.size() * housePrice) << " gold coins.";
132 }
133 }
134
135 for (const auto& it : doorSet) {
136 it->setSpecialDescription(ss.str());
137 }
138}
139
140AccessHouseLevel_t House::getHouseAccessLevel(const Player* player)
141{
142 if (!player) {
143 return HOUSE_OWNER;
144 }
145
146 if (g_config.getBoolean(ConfigManager::HOUSE_OWNED_BY_ACCOUNT)) {
147 if (ownerAccountId == player->getAccount()) {
148 return HOUSE_OWNER;
149 }
150 }
151
152 if (player->hasFlag(PlayerFlag_CanEditHouses)) {
153 return HOUSE_OWNER;
154 }
155
156 if (player->getGUID() == owner) {
157 return HOUSE_OWNER;
158 }
159
160 if (subOwnerList.isInList(player)) {
161 return HOUSE_SUBOWNER;
162 }
163
164 if (guestList.isInList(player)) {
165 return HOUSE_GUEST;
166 }
167
168 return HOUSE_NOT_INVITED;
169}
170
171bool House::kickPlayer(Player* player, Player* target)
172{
173 if (!target) {
174 return false;
175 }
176
177 HouseTile* houseTile = dynamic_cast<HouseTile*>(target->getTile());
178 if (!houseTile || houseTile->getHouse() != this) {
179 return false;
180 }
181
182 if (getHouseAccessLevel(player) < getHouseAccessLevel(target) || target->hasFlag(PlayerFlag_CanEditHouses)) {
183 return false;
184 }
185
186 Position oldPosition = target->getPosition();
187 if (g_game.internalTeleport(target, getEntryPosition()) == RETURNVALUE_NOERROR) {
188 g_game.addMagicEffect(oldPosition, CONST_ME_POFF);
189 g_game.addMagicEffect(getEntryPosition(), CONST_ME_TELEPORT);
190 }
191 return true;
192}
193
194void House::setAccessList(uint32_t listId, const std::string& textlist)
195{
196 if (listId == GUEST_LIST) {
197 guestList.parseList(textlist);
198 } else if (listId == SUBOWNER_LIST) {
199 subOwnerList.parseList(textlist);
200 } else {
201 Door* door = getDoorByNumber(listId);
202 if (door) {
203 door->setAccessList(textlist);
204 }
205
206 // We dont have kick anyone
207 return;
208 }
209
210 //kick uninvited players
211 for (HouseTile* tile : houseTiles) {
212 if (CreatureVector* creatures = tile->getCreatures()) {
213 for (int32_t i = creatures->size(); --i >= 0;) {
214 Player* player = (*creatures)[i]->getPlayer();
215 if (player && !isInvited(player)) {
216 kickPlayer(nullptr, player);
217 }
218 }
219 }
220 }
221}
222
223bool House::transferToDepot() const
224{
225 if (townId == 0 || owner == 0) {
226 return false;
227 }
228
229 Player* player = g_game.getPlayerByGUID(owner);
230 if (player) {
231 transferToDepot(player);
232 } else {
233 Player tmpPlayer(nullptr);
234 if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) {
235 return false;
236 }
237
238 transferToDepot(&tmpPlayer);
239 IOLoginData::savePlayer(&tmpPlayer);
240 }
241 return true;
242}
243
244bool House::transferToDepot(Player* player) const
245{
246 if (townId == 0 || owner == 0) {
247 return false;
248 }
249
250 ItemList moveItemList;
251 for (HouseTile* tile : houseTiles) {
252 if (const TileItemVector* items = tile->getItemList()) {
253 for (Item* item : *items) {
254 if (item->isPickupable()) {
255 moveItemList.push_back(item);
256 } else {
257 Container* container = item->getContainer();
258 if (container) {
259 for (Item* containerItem : container->getItemList()) {
260 moveItemList.push_back(containerItem);
261 }
262 }
263 }
264 }
265 }
266 }
267
268 for (Item* item : moveItemList) {
269 g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT);
270 }
271 return true;
272}
273
274bool House::getAccessList(uint32_t listId, std::string& list) const
275{
276 if (listId == GUEST_LIST) {
277 guestList.getList(list);
278 return true;
279 } else if (listId == SUBOWNER_LIST) {
280 subOwnerList.getList(list);
281 return true;
282 }
283
284 Door* door = getDoorByNumber(listId);
285 if (!door) {
286 return false;
287 }
288
289 return door->getAccessList(list);
290}
291
292bool House::isInvited(const Player* player)
293{
294 return getHouseAccessLevel(player) != HOUSE_NOT_INVITED;
295}
296
297void House::addDoor(Door* door)
298{
299 door->incrementReferenceCounter();
300 doorSet.insert(door);
301 door->setHouse(this);
302 updateDoorDescription();
303}
304
305void House::removeDoor(Door* door)
306{
307 auto it = doorSet.find(door);
308 if (it != doorSet.end()) {
309 door->decrementReferenceCounter();
310 doorSet.erase(it);
311 }
312}
313
314void House::addBed(BedItem* bed)
315{
316 bedsList.push_back(bed);
317 bed->setHouse(this);
318}
319
320Door* House::getDoorByNumber(uint32_t doorId) const
321{
322 for (Door* door : doorSet) {
323 if (door->getDoorId() == doorId) {
324 return door;
325 }
326 }
327 return nullptr;
328}
329
330Door* House::getDoorByPosition(const Position& pos)
331{
332 for (Door* door : doorSet) {
333 if (door->getPosition() == pos) {
334 return door;
335 }
336 }
337 return nullptr;
338}
339
340bool House::canEditAccessList(uint32_t listId, const Player* player)
341{
342 switch (getHouseAccessLevel(player)) {
343 case HOUSE_OWNER:
344 return true;
345
346 case HOUSE_SUBOWNER:
347 return listId == GUEST_LIST;
348
349 default:
350 return false;
351 }
352}
353
354HouseTransferItem* House::getTransferItem()
355{
356 if (transferItem != nullptr) {
357 return nullptr;
358 }
359
360 transfer_container.setParent(nullptr);
361 transferItem = HouseTransferItem::createHouseTransferItem(this);
362 transfer_container.addThing(transferItem);
363 return transferItem;
364}
365
366void House::resetTransferItem()
367{
368 if (transferItem) {
369 Item* tmpItem = transferItem;
370 transferItem = nullptr;
371 transfer_container.setParent(nullptr);
372
373 transfer_container.removeThing(tmpItem, tmpItem->getItemCount());
374 g_game.ReleaseItem(tmpItem);
375 }
376}
377
378HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house)
379{
380 HouseTransferItem* transferItem = new HouseTransferItem(house);
381 transferItem->incrementReferenceCounter();
382 transferItem->setID(ITEM_DOCUMENT_RO);
383 transferItem->setSubType(1);
384 std::ostringstream ss;
385 ss << "It is a house transfer document for '" << house->getName() << "'.";
386 transferItem->setSpecialDescription(ss.str());
387 return transferItem;
388}
389
390void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner)
391{
392 if (event == ON_TRADE_TRANSFER) {
393 if (house) {
394 house->executeTransfer(this, owner);
395 }
396
397 g_game.internalRemoveItem(this, 1);
398 } else if (event == ON_TRADE_CANCEL) {
399 if (house) {
400 house->resetTransferItem();
401 }
402 }
403}
404
405bool House::executeTransfer(HouseTransferItem* item, Player* newOwner)
406{
407 if (transferItem != item) {
408 return false;
409 }
410
411 setOwner(newOwner->getGUID());
412 transferItem = nullptr;
413 return true;
414}
415
416void AccessList::parseList(const std::string& list)
417{
418 playerList.clear();
419 guildRankList.clear();
420 allowEveryone = false;
421 this->list = list;
422 if (list.empty()) {
423 return;
424 }
425
426 std::istringstream listStream(list);
427 std::string line;
428
429 int lineNo = 1;
430 while (getline(listStream, line)) {
431 if (++lineNo > 100) {
432 break;
433 }
434
435 trimString(line);
436 trim_left(line, '\t');
437 trim_right(line, '\t');
438 trimString(line);
439
440 if (line.empty() || line.front() == '#' || line.length() > 100) {
441 continue;
442 }
443
444 toLowerCaseString(line);
445
446 std::string::size_type at_pos = line.find("@");
447 if (at_pos != std::string::npos) {
448 if (at_pos == 0) {
449 addGuild(line.substr(1));
450 } else {
451 addGuildRank(line.substr(0, at_pos - 1), line.substr(at_pos + 1));
452 }
453 } else if (line == "*") {
454 allowEveryone = true;
455 } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) {
456 continue; // regexp no longer supported
457 } else {
458 addPlayer(line);
459 }
460 }
461}
462
463void AccessList::addPlayer(const std::string& name)
464{
465 Player* player = g_game.getPlayerByName(name);
466 if (player) {
467 playerList.insert(player->getGUID());
468 } else {
469 uint32_t guid = IOLoginData::getGuidByName(name);
470 if (guid != 0) {
471 playerList.insert(guid);
472 }
473 }
474}
475
476namespace {
477
478const Guild* getGuildByName(const std::string& name)
479{
480 uint32_t guildId = IOGuild::getGuildIdByName(name);
481 if (guildId == 0) {
482 return nullptr;
483 }
484
485 const Guild* guild = g_game.getGuild(guildId);
486 if (guild) {
487 return guild;
488 }
489
490 return IOGuild::loadGuild(guildId);
491}
492
493}
494
495void AccessList::addGuild(const std::string& name)
496{
497 const Guild* guild = getGuildByName(name);
498 if (guild) {
499 for (auto rank : guild->getRanks()) {
500 guildRankList.insert(rank->id);
501 }
502 }
503}
504
505void AccessList::addGuildRank(const std::string& name, const std::string& rankName)
506{
507 const Guild* guild = getGuildByName(name);
508 if (guild) {
509 GuildRank_ptr rank = guild->getRankByName(rankName);
510 if (rank) {
511 guildRankList.insert(rank->id);
512 }
513 }
514}
515
516bool AccessList::isInList(const Player* player)
517{
518 if (allowEveryone) {
519 return true;
520 }
521
522 auto playerIt = playerList.find(player->getGUID());
523 if (playerIt != playerList.end()) {
524 return true;
525 }
526
527 GuildRank_ptr rank = player->getGuildRank();
528 return rank && guildRankList.find(rank->id) != guildRankList.end();
529}
530
531void AccessList::getList(std::string& list) const
532{
533 list = this->list;
534}
535
536Door::Door(uint16_t type) : Item(type) {}
537
538Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream)
539{
540 if (attr == ATTR_HOUSEDOORID) {
541 uint8_t doorId;
542 if (!propStream.read<uint8_t>(doorId)) {
543 return ATTR_READ_ERROR;
544 }
545
546 setDoorId(doorId);
547 return ATTR_READ_CONTINUE;
548 }
549 return Item::readAttr(attr, propStream);
550}
551
552void Door::setHouse(House* house)
553{
554 if (this->house != nullptr) {
555 return;
556 }
557
558 this->house = house;
559
560 if (!accessList) {
561 accessList.reset(new AccessList());
562 }
563}
564
565bool Door::canUse(const Player* player)
566{
567 if (!house) {
568 return true;
569 }
570
571 if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) {
572 return true;
573 }
574
575 return accessList->isInList(player);
576}
577
578void Door::setAccessList(const std::string& textlist)
579{
580 if (!accessList) {
581 accessList.reset(new AccessList());
582 }
583
584 accessList->parseList(textlist);
585}
586
587bool Door::getAccessList(std::string& list) const
588{
589 if (!house) {
590 return false;
591 }
592
593 accessList->getList(list);
594 return true;
595}
596
597void Door::onRemoved()
598{
599 Item::onRemoved();
600
601 if (house) {
602 house->removeDoor(this);
603 }
604}
605
606House* Houses::getHouseByPlayerId(uint32_t playerId)
607{
608 for (const auto& it : houseMap) {
609 if (it.second->getOwner() == playerId) {
610 return it.second;
611 }
612 }
613 return nullptr;
614}
615
616bool Houses::loadHousesXML(const std::string& filename)
617{
618 pugi::xml_document doc;
619 pugi::xml_parse_result result = doc.load_file(filename.c_str());
620 if (!result) {
621 printXMLError("Error - Houses::loadHousesXML", filename, result);
622 return false;
623 }
624
625 for (auto houseNode : doc.child("houses").children()) {
626 pugi::xml_attribute houseIdAttribute = houseNode.attribute("houseid");
627 if (!houseIdAttribute) {
628 return false;
629 }
630
631 int32_t houseId = pugi::cast<int32_t>(houseIdAttribute.value());
632
633 House* house = getHouse(houseId);
634 if (!house) {
635 std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << houseId << std::endl;
636 return false;
637 }
638
639 house->setName(houseNode.attribute("name").as_string());
640
641 Position entryPos(
642 pugi::cast<uint16_t>(houseNode.attribute("entryx").value()),
643 pugi::cast<uint16_t>(houseNode.attribute("entryy").value()),
644 pugi::cast<uint16_t>(houseNode.attribute("entryz").value())
645 );
646 if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) {
647 std::cout << "[Warning - Houses::loadHousesXML] House entry not set"
648 << " - Name: " << house->getName()
649 << " - House id: " << houseId << std::endl;
650 }
651 house->setEntryPos(entryPos);
652
653 house->setRent(pugi::cast<uint32_t>(houseNode.attribute("rent").value()));
654 house->setTownId(pugi::cast<uint32_t>(houseNode.attribute("townid").value()));
655
656 house->setOwner(0, false);
657 }
658 return true;
659}
660
661void Houses::payHouses(RentPeriod_t rentPeriod) const
662{
663 if (rentPeriod == RENTPERIOD_NEVER) {
664 return;
665 }
666
667 time_t currentTime = time(nullptr);
668 for (const auto& it : houseMap) {
669 House* house = it.second;
670 if (house->getOwner() == 0) {
671 continue;
672 }
673
674 const uint32_t rent = house->getRent();
675 if (rent == 0 || house->getPaidUntil() > currentTime) {
676 continue;
677 }
678
679 const uint32_t ownerId = house->getOwner();
680 Town* town = g_game.map.towns.getTown(house->getTownId());
681 if (!town) {
682 continue;
683 }
684
685 Player player(nullptr);
686 if (!IOLoginData::loadPlayerById(&player, ownerId)) {
687 // Player doesn't exist, reset house owner
688 house->setOwner(0);
689 continue;
690 }
691
692 if (player.getBankBalance() >= rent) {
693 player.setBankBalance(player.getBankBalance() - rent);
694
695 time_t paidUntil = currentTime;
696 switch (rentPeriod) {
697 case RENTPERIOD_DAILY:
698 paidUntil += 24 * 60 * 60;
699 break;
700 case RENTPERIOD_WEEKLY:
701 paidUntil += 24 * 60 * 60 * 7;
702 break;
703 case RENTPERIOD_MONTHLY:
704 paidUntil += 24 * 60 * 60 * 30;
705 break;
706 case RENTPERIOD_YEARLY:
707 paidUntil += 24 * 60 * 60 * 365;
708 break;
709 default:
710 break;
711 }
712
713 house->setPaidUntil(paidUntil);
714 } else {
715 if (house->getPayRentWarnings() < 7) {
716 int32_t daysLeft = 7 - house->getPayRentWarnings();
717
718 Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
719 std::string period;
720
721 switch (rentPeriod) {
722 case RENTPERIOD_DAILY:
723 period = "daily";
724 break;
725
726 case RENTPERIOD_WEEKLY:
727 period = "weekly";
728 break;
729
730 case RENTPERIOD_MONTHLY:
731 period = "monthly";
732 break;
733
734 case RENTPERIOD_YEARLY:
735 period = "annual";
736 break;
737
738 default:
739 break;
740 }
741
742 std::ostringstream ss;
743 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.";
744 letter->setText(ss.str());
745 g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
746 house->setPayRentWarnings(house->getPayRentWarnings() + 1);
747 } else {
748 house->setOwner(0, true, &player);
749 }
750 }
751
752 IOLoginData::savePlayer(&player);
753 }
754}
755