2018-11-10 19:00:13 +01:00
|
|
|
#include "HousingMgr.h"
|
|
|
|
#include <Logging/Logger.h>
|
|
|
|
#include <Database/DatabaseDef.h>
|
|
|
|
#include <Exd/ExdDataGenerated.h>
|
2018-11-13 23:46:10 +01:00
|
|
|
#include <Network/PacketContainer.h>
|
|
|
|
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
|
|
|
#include <Network/PacketWrappers/ActorControlPacket142.h>
|
|
|
|
#include <Network/PacketWrappers/ActorControlPacket143.h>
|
|
|
|
#include <Network/CommonActorControl.h>
|
2018-11-10 19:00:13 +01:00
|
|
|
|
|
|
|
#include <unordered_map>
|
2018-11-27 21:45:29 +11:00
|
|
|
#include <cstring>
|
2018-11-10 19:00:13 +01:00
|
|
|
|
|
|
|
#include "Actor/Player.h"
|
2018-11-30 22:52:08 +11:00
|
|
|
#include "Actor/EventObject.h"
|
2018-12-11 23:40:47 +01:00
|
|
|
#include "Event/EventDefs.h"
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-11-19 11:55:29 +01:00
|
|
|
#include "TerritoryMgr.h"
|
2018-12-01 00:27:16 +11:00
|
|
|
#include "Territory/Zone.h"
|
|
|
|
#include "Territory/HousingZone.h"
|
2018-12-05 19:58:43 +11:00
|
|
|
#include "Territory/Housing/HousingInteriorTerritory.h"
|
2018-11-10 19:00:13 +01:00
|
|
|
#include "HousingMgr.h"
|
2018-12-01 00:27:16 +11:00
|
|
|
#include "Territory/Land.h"
|
2018-11-10 19:00:13 +01:00
|
|
|
#include "Framework.h"
|
2018-11-20 21:32:13 +01:00
|
|
|
#include "ServerMgr.h"
|
2018-12-01 00:27:16 +11:00
|
|
|
#include "Territory/House.h"
|
2018-12-20 20:41:16 +11:00
|
|
|
#include "InventoryMgr.h"
|
2018-12-22 15:58:54 +11:00
|
|
|
#include "Inventory/Item.h"
|
|
|
|
#include "Inventory/ItemContainer.h"
|
2018-11-13 23:46:10 +01:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
using namespace Sapphire::Common;
|
|
|
|
using namespace Sapphire::Network;
|
|
|
|
using namespace Sapphire::Network::Packets;
|
|
|
|
using namespace Sapphire::Network::Packets::Server;
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
extern Sapphire::Framework g_fw;
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-12-22 11:33:21 +11:00
|
|
|
Sapphire::World::Manager::HousingMgr::HousingMgr() = default;
|
|
|
|
Sapphire::World::Manager::HousingMgr::~HousingMgr() = default;
|
|
|
|
|
|
|
|
bool Sapphire::World::Manager::HousingMgr::init()
|
2018-11-10 19:00:13 +01:00
|
|
|
{
|
2018-12-22 11:33:21 +11:00
|
|
|
auto log = g_fw.get< Sapphire::Logger >();
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-12-22 14:35:42 +11:00
|
|
|
log->info( "HousingMgr: Caching housing land init data" );
|
2018-12-22 11:33:21 +11:00
|
|
|
//LAND_SEL_ALL
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-12-22 11:33:21 +11:00
|
|
|
// 18 wards per territory, 4 territories
|
|
|
|
m_landCache.reserve( 18 * 4 );
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-12-22 14:35:42 +11:00
|
|
|
loadLandCache();
|
|
|
|
|
2018-12-22 15:58:54 +11:00
|
|
|
log->debug( "HousingMgr: Checking land counts" );
|
2018-12-22 14:35:42 +11:00
|
|
|
|
|
|
|
uint32_t houseCount = 0;
|
|
|
|
for( auto& landSet : m_landCache )
|
|
|
|
{
|
|
|
|
auto count = landSet.second.size();
|
|
|
|
|
|
|
|
houseCount += count;
|
|
|
|
|
|
|
|
if( landSet.second.size() != 60 )
|
|
|
|
{
|
|
|
|
log->fatal( "LandSet " + std::to_string( landSet.first ) + " is missing land entries. Only have " + std::to_string( count ) + " land entries." );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log->info( "HousingMgr: Cached " + std::to_string( houseCount ) + " houses" );
|
|
|
|
|
|
|
|
/////
|
|
|
|
|
|
|
|
if( !loadEstateInventories() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::World::Manager::HousingMgr::loadEstateInventories()
|
|
|
|
{
|
|
|
|
auto log = g_fw.get< Sapphire::Logger >();
|
|
|
|
|
|
|
|
log->info( "HousingMgr: Loading inventories for estates" );
|
|
|
|
|
2018-12-22 15:58:54 +11:00
|
|
|
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
|
|
|
|
|
|
|
auto stmt = pDb->getPreparedStatement( Db::LAND_INV_SEL_ALL );
|
|
|
|
auto res = pDb->query( stmt );
|
|
|
|
|
|
|
|
uint32_t itemCount = 0;
|
|
|
|
while( res->next() )
|
|
|
|
{
|
|
|
|
//uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq
|
|
|
|
uint64_t ident = res->getUInt64( "LandIdent" );
|
|
|
|
uint16_t containerId = res->getUInt16( "ContainerId" );
|
|
|
|
uint64_t itemId = res->getUInt64( "ItemId" );
|
|
|
|
uint16_t slot = res->getUInt16( "SlotId" );
|
|
|
|
uint32_t catalogId = res->getUInt( "catalogId" );
|
|
|
|
uint8_t stain = res->getUInt8( "stain" );
|
|
|
|
uint64_t characterId = res->getUInt64( "CharacterId" );
|
|
|
|
|
|
|
|
auto item = make_Item( itemId, catalogId, 0, 0, 0 );
|
|
|
|
item->setStain( stain );
|
|
|
|
// todo: need to set the owner character id on the item
|
|
|
|
|
2018-12-22 17:22:24 +11:00
|
|
|
ContainerIdToContainerMap& estateInv = m_estateInventories[ ident ];
|
2018-12-22 15:58:54 +11:00
|
|
|
|
|
|
|
// check if containerId exists
|
|
|
|
auto container = estateInv.find( containerId );
|
|
|
|
if( container == estateInv.end() )
|
|
|
|
{
|
|
|
|
// create container
|
|
|
|
// todo: how to handle this max slot stuff? override it on land init?
|
|
|
|
auto ic = make_ItemContainer( containerId, 400, "houseiteminventory", true );
|
|
|
|
ic->setItem( slot, item );
|
|
|
|
|
|
|
|
estateInv[ containerId ] = ic;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
container->second->setItem( slot, item );
|
|
|
|
|
|
|
|
itemCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
log->debug( "HousingMgr: Loaded " + std::to_string( itemCount ) + " inventory items" );
|
|
|
|
|
2018-12-22 14:35:42 +11:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::World::Manager::HousingMgr::loadLandCache()
|
|
|
|
{
|
2018-12-22 11:33:21 +11:00
|
|
|
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
|
|
|
|
|
|
|
auto stmt = pDb->getPreparedStatement( Db::LAND_SEL_ALL );
|
|
|
|
auto res = pDb->query( stmt );
|
|
|
|
|
|
|
|
while( res->next() )
|
|
|
|
{
|
|
|
|
LandCacheEntry entry;
|
|
|
|
|
|
|
|
// land stuff
|
|
|
|
entry.m_landSetId = res->getUInt64( "LandSetId" );
|
|
|
|
entry.m_landId = res->getUInt64( "LandId" );
|
|
|
|
|
2018-12-22 12:00:03 +11:00
|
|
|
entry.m_type = static_cast< Common::LandType >( res->getUInt( "Type" ) );
|
2018-12-22 11:33:21 +11:00
|
|
|
entry.m_size = res->getUInt8( "Size" );
|
|
|
|
entry.m_status = res->getUInt8( "Status" );
|
2018-12-22 12:00:03 +11:00
|
|
|
entry.m_currentPrice = res->getUInt64( "LandPrice" );
|
2018-12-22 11:33:21 +11:00
|
|
|
entry.m_updateTime = res->getUInt64( "UpdateTime" );
|
|
|
|
entry.m_ownerId = res->getUInt64( "OwnerId" );
|
|
|
|
|
|
|
|
entry.m_houseId = res->getUInt64( "HouseId" );
|
|
|
|
|
|
|
|
// house stuff
|
|
|
|
entry.m_estateWelcome = res->getString( "Welcome" );
|
|
|
|
entry.m_estateComment = res->getString( "Comment" );
|
2018-12-22 14:35:42 +11:00
|
|
|
entry.m_estateName = res->getString( "HouseName" );
|
2018-12-22 11:33:21 +11:00
|
|
|
entry.m_buildTime = res->getUInt64( "BuildTime" );
|
|
|
|
entry.m_endorsements = res->getUInt64( "Endorsements" );
|
|
|
|
|
|
|
|
m_landCache[ entry.m_landSetId ].push_back( entry );
|
|
|
|
}
|
2018-11-10 19:00:13 +01:00
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
uint32_t Sapphire::World::Manager::HousingMgr::toLandSetId( uint16_t territoryTypeId, uint8_t wardId ) const
|
2018-11-16 17:07:22 +01:00
|
|
|
{
|
|
|
|
return ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardId;
|
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
Sapphire::Data::HousingZonePtr Sapphire::World::Manager::HousingMgr::getHousingZoneByLandSetId( uint32_t id )
|
2018-11-10 19:00:13 +01:00
|
|
|
{
|
2018-11-19 11:55:29 +01:00
|
|
|
auto pTeriMgr = g_fw.get< TerritoryMgr >();
|
|
|
|
return std::dynamic_pointer_cast< HousingZone >( pTeriMgr->getZoneByLandSetId( id ) );
|
2018-11-10 19:00:13 +01:00
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
Sapphire::LandPtr Sapphire::World::Manager::HousingMgr::getLandByOwnerId( uint32_t id )
|
2018-11-10 19:00:13 +01:00
|
|
|
{
|
2018-11-19 11:55:29 +01:00
|
|
|
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
|
|
|
|
auto res = pDb->query( "SELECT LandSetId, LandId FROM land WHERE OwnerId = " + std::to_string( id ) );
|
|
|
|
|
|
|
|
if( !res->next() )
|
2018-11-10 19:00:13 +01:00
|
|
|
return nullptr;
|
|
|
|
|
2018-11-19 11:55:29 +01:00
|
|
|
auto hZone = getHousingZoneByLandSetId( res->getUInt( 1 ) );
|
2018-11-10 19:00:13 +01:00
|
|
|
|
2018-11-19 11:55:29 +01:00
|
|
|
if( !hZone )
|
|
|
|
return nullptr;
|
2018-11-11 14:27:39 +01:00
|
|
|
|
2018-11-19 11:55:29 +01:00
|
|
|
return hZone->getLand( res->getUInt( 2 ) );
|
2018-11-10 19:00:13 +01:00
|
|
|
}
|
2018-11-13 23:46:10 +01:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::sendLandSignOwned( Entity::Player& player, const Common::LandIdent ident )
|
2018-11-13 23:46:10 +01:00
|
|
|
{
|
2018-12-05 16:55:14 +11:00
|
|
|
player.setActiveLand( ident.landId, ident.wardNum );
|
2018-11-13 23:46:10 +01:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
2018-11-16 17:07:22 +01:00
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
2018-11-13 23:46:10 +01:00
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto land = hZone->getLand( ident.landId );
|
2018-11-13 23:46:10 +01:00
|
|
|
if( !land )
|
2018-12-19 00:54:25 +11:00
|
|
|
return;
|
2018-11-13 23:46:10 +01:00
|
|
|
|
|
|
|
auto landInfoSignPacket = makeZonePacket< Server::FFXIVIpcLandInfoSign >( player.getId() );
|
2018-11-14 10:00:16 +01:00
|
|
|
landInfoSignPacket->data().houseSize = land->getSize();
|
2018-11-13 23:46:10 +01:00
|
|
|
landInfoSignPacket->data().houseType = static_cast< uint8_t >( land->getLandType() );
|
2018-12-05 16:55:14 +11:00
|
|
|
landInfoSignPacket->data().landIdent = ident;
|
2018-11-27 21:45:29 +11:00
|
|
|
landInfoSignPacket->data().houseIconAdd = land->getSharing();
|
2018-12-19 00:54:25 +11:00
|
|
|
landInfoSignPacket->data().ownerId = player.getContentId(); // todo: should be real owner contentId, not player.contentId()
|
2018-11-27 21:45:29 +11:00
|
|
|
|
|
|
|
if( auto house = land->getHouse() )
|
|
|
|
{
|
|
|
|
std::strcpy( landInfoSignPacket->data().estateName, house->getHouseName().c_str() );
|
|
|
|
std::strcpy( landInfoSignPacket->data().estateGreeting, house->getHouseGreeting().c_str() );
|
|
|
|
}
|
|
|
|
|
2018-12-19 16:03:35 +11:00
|
|
|
uint32_t playerId = land->getOwnerId();
|
2018-12-19 00:54:25 +11:00
|
|
|
std::string playerName = g_fw.get< Sapphire::ServerMgr >()->getPlayerNameFromDb( playerId );
|
|
|
|
|
2018-11-13 23:46:10 +01:00
|
|
|
memcpy( &landInfoSignPacket->data().ownerName, playerName.c_str(), playerName.size() );
|
2018-11-26 23:15:42 +01:00
|
|
|
|
2018-11-13 23:46:10 +01:00
|
|
|
player.queuePacket( landInfoSignPacket );
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::sendLandSignFree( Entity::Player& player, const Common::LandIdent ident )
|
2018-11-13 23:46:10 +01:00
|
|
|
{
|
2018-12-05 16:55:14 +11:00
|
|
|
player.setActiveLand( ident.landId, ident.wardNum );
|
2018-11-13 23:46:10 +01:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
2018-11-16 17:07:22 +01:00
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
2018-11-13 23:46:10 +01:00
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto land = hZone->getLand( ident.landId );
|
2018-11-13 23:46:10 +01:00
|
|
|
auto plotPricePacket = makeZonePacket< Server::FFXIVIpcLandPriceUpdate >( player.getId() );
|
|
|
|
plotPricePacket->data().price = land->getCurrentPrice();
|
|
|
|
plotPricePacket->data().timeLeft = land->getDevaluationTime();
|
|
|
|
player.queuePacket( plotPricePacket );
|
|
|
|
}
|
2018-11-15 12:40:02 +01:00
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
Sapphire::LandPurchaseResult Sapphire::World::Manager::HousingMgr::purchaseLand( Entity::Player& player, uint8_t plot, uint8_t state )
|
2018-11-15 12:40:02 +01:00
|
|
|
{
|
|
|
|
auto pHousing = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() );
|
|
|
|
|
|
|
|
auto plotPrice = pHousing->getLand( plot )->getCurrentPrice();
|
|
|
|
auto gilAvailable = player.getCurrency( CurrencyType::Gil );
|
|
|
|
auto pLand = pHousing->getLand( plot );
|
|
|
|
|
|
|
|
if( !pLand )
|
|
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
|
|
|
|
|
|
|
if( pLand->getState() != HouseState::forSale )
|
|
|
|
return LandPurchaseResult::ERR_NOT_AVAILABLE;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
if( gilAvailable < plotPrice )
|
|
|
|
return LandPurchaseResult::ERR_NOT_ENOUGH_GIL;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
switch( static_cast< LandPurchaseMode >( state ) )
|
|
|
|
{
|
|
|
|
case LandPurchaseMode::FC:
|
|
|
|
player.sendDebug( "Free company house purchase aren't supported at this time." );
|
|
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
case LandPurchaseMode::PRIVATE:
|
|
|
|
{
|
|
|
|
|
|
|
|
auto pOldLand = getLandByOwnerId( player.getId() );
|
|
|
|
|
|
|
|
if( pOldLand )
|
|
|
|
return LandPurchaseResult::ERR_NO_MORE_LANDS_FOR_CHAR;
|
2018-11-15 22:30:59 +01:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
player.removeCurrency( CurrencyType::Gil, plotPrice );
|
2018-12-19 16:03:35 +11:00
|
|
|
pLand->setOwnerId( player.getId() );
|
2018-11-15 12:40:02 +01:00
|
|
|
pLand->setState( HouseState::sold );
|
|
|
|
pLand->setLandType( Common::LandType::Private );
|
2018-11-15 22:30:59 +01:00
|
|
|
|
2018-12-21 22:23:49 +11:00
|
|
|
player.setLandFlags( LandFlagsSlot::Private, 0x00, pLand->getLandIdent() );
|
2018-11-15 22:30:59 +01:00
|
|
|
|
2018-11-28 23:29:55 +11:00
|
|
|
player.sendLandFlagsSlot( LandFlagsSlot::Private );
|
2018-11-15 22:30:59 +01:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
//pLand->setLandName( "Private Estate" + std::to_string( pHousing->getWardNum() ) + "-" + std::to_string( plot ) );
|
|
|
|
pLand->updateLandDb();
|
2018-11-15 22:30:59 +01:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
pHousing->sendLandUpdate( plot );
|
|
|
|
return LandPurchaseResult::SUCCESS;
|
|
|
|
}
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
default:
|
|
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
|
|
|
}
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-11-15 12:40:02 +01:00
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
bool Sapphire::World::Manager::HousingMgr::relinquishLand( Entity::Player& player, uint8_t plot )
|
2018-11-17 01:16:44 +01:00
|
|
|
{
|
|
|
|
// TODO: Fix "permissions" being sent incorrectly
|
|
|
|
// TODO: Add checks for land state before relinquishing
|
|
|
|
auto pHousing = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() );
|
|
|
|
|
|
|
|
auto pLand = pHousing->getLand( plot );
|
|
|
|
auto plotMaxPrice = pLand->getCurrentPrice();
|
2018-12-19 16:03:35 +11:00
|
|
|
auto landOwnerId = pLand->getOwnerId();
|
2018-11-20 21:21:22 +01:00
|
|
|
|
|
|
|
// can't relinquish when you are not the owner
|
|
|
|
// TODO: actually use permissions here for FC houses
|
|
|
|
if( landOwnerId != player.getId() )
|
|
|
|
{
|
|
|
|
auto msgPkt = makeActorControl143( player.getId(), ActorControl::LogMsg, 3304, 0 );
|
|
|
|
player.queuePacket( msgPkt );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unable to relinquish if there is a house built
|
|
|
|
// TODO: additionally check for yard items
|
|
|
|
if( pLand->getHouse() )
|
|
|
|
{
|
|
|
|
auto msgPkt = makeActorControl143( player.getId(), ActorControl::LogMsg, 3315, 0 );
|
|
|
|
player.queuePacket( msgPkt );
|
|
|
|
return false;
|
|
|
|
}
|
2018-11-17 01:16:44 +01:00
|
|
|
|
|
|
|
pLand->setCurrentPrice( pLand->getMaxPrice() );
|
2018-12-19 16:03:35 +11:00
|
|
|
pLand->setOwnerId( 0 );
|
2018-11-17 01:16:44 +01:00
|
|
|
pLand->setState( HouseState::forSale );
|
|
|
|
pLand->setLandType( Common::LandType::none );
|
|
|
|
pLand->updateLandDb();
|
|
|
|
|
2018-12-21 22:23:49 +11:00
|
|
|
Common::LandIdent ident { 0xFF, 0xFF, 0xFF, 0xFF };
|
|
|
|
|
|
|
|
player.setLandFlags( LandFlagsSlot::Private, 0x00, ident );
|
2018-11-17 01:16:44 +01:00
|
|
|
|
2018-11-28 23:29:55 +11:00
|
|
|
player.sendLandFlagsSlot( LandFlagsSlot::Private );
|
2018-11-17 01:16:44 +01:00
|
|
|
|
|
|
|
auto screenMsgPkt2 = makeActorControl143( player.getId(), ActorControl::LogMsg, 3351, 0x1AA,
|
2018-12-21 22:08:05 +11:00
|
|
|
pLand->getLandIdent().wardNum + 1, plot + 1 );
|
2018-11-17 01:16:44 +01:00
|
|
|
player.queuePacket( screenMsgPkt2 );
|
|
|
|
pHousing->sendLandUpdate( plot );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::sendWardLandInfo( Entity::Player& player, uint8_t wardId, uint16_t territoryTypeId )
|
2018-11-20 22:52:57 +11:00
|
|
|
{
|
|
|
|
auto landSetId = toLandSetId( territoryTypeId, wardId );
|
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto wardInfoPacket = makeZonePacket< Server::FFXIVIpcHousingWardInfo >( player.getId() );
|
2018-11-26 23:15:42 +01:00
|
|
|
wardInfoPacket->data().landIdent.wardNum = wardId;
|
|
|
|
wardInfoPacket->data().landIdent.territoryTypeId = territoryTypeId;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-12-04 22:20:41 +11:00
|
|
|
// todo: properly get worldId
|
|
|
|
wardInfoPacket->data().landIdent.worldId = 67;
|
|
|
|
|
2018-11-20 22:52:57 +11:00
|
|
|
for( int i = 0; i < 60; i++ )
|
|
|
|
{
|
|
|
|
auto land = hZone->getLand( i );
|
|
|
|
assert( land );
|
|
|
|
|
|
|
|
auto& entry = wardInfoPacket->data().houseInfoEntry[ i ];
|
|
|
|
|
2018-11-23 17:45:37 +11:00
|
|
|
// retail always sends the house price in this packet, even after the house has been sold
|
|
|
|
// so I guess we do the same
|
2018-11-20 22:52:57 +11:00
|
|
|
entry.housePrice = land->getCurrentPrice();
|
|
|
|
|
2018-11-23 17:45:37 +11:00
|
|
|
if( land->getState() == Common::HouseState::forSale )
|
|
|
|
continue;
|
|
|
|
|
2018-12-04 22:20:41 +11:00
|
|
|
if( auto house = land->getHouse() )
|
|
|
|
{
|
|
|
|
if( !house->getHouseGreeting().empty() )
|
|
|
|
entry.infoFlags |= WardlandFlags::HasEstateGreeting;
|
|
|
|
}
|
|
|
|
|
2018-11-20 22:52:57 +11:00
|
|
|
switch( land->getLandType() )
|
|
|
|
{
|
|
|
|
case LandType::FreeCompany:
|
2018-12-04 22:20:41 +11:00
|
|
|
entry.infoFlags |= Common::WardlandFlags::IsEstateOwned | Common::WardlandFlags::IsFreeCompanyEstate;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
|
|
|
// todo: send FC name
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LandType::Private:
|
2018-12-04 22:20:41 +11:00
|
|
|
entry.infoFlags |= Common::WardlandFlags::IsEstateOwned;
|
2018-11-20 22:52:57 +11:00
|
|
|
|
2018-12-19 16:03:35 +11:00
|
|
|
auto owner = land->getOwnerId();
|
2018-12-04 22:20:41 +11:00
|
|
|
auto playerName = g_fw.get< Sapphire::ServerMgr >()->getPlayerNameFromDb( owner );
|
2018-11-20 22:52:57 +11:00
|
|
|
memcpy( &entry.estateOwnerName, playerName.c_str(), playerName.size() );
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// todo: check we have an estate message and set the flag
|
|
|
|
// todo: check if estate allows public entry
|
|
|
|
}
|
|
|
|
|
|
|
|
player.queuePacket( wardInfoPacket );
|
|
|
|
}
|
|
|
|
|
2018-12-04 22:20:41 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::sendEstateGreeting( Entity::Player& player, const Common::LandIdent ident )
|
|
|
|
{
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto land = hZone->getLand( ident.landId );
|
|
|
|
if( !land )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto house = land->getHouse();
|
|
|
|
if( !house )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto greetingPacket = makeZonePacket< FFXIVIpcHousingEstateGreeting >( player.getId() );
|
|
|
|
|
|
|
|
greetingPacket->data().landIdent = ident;
|
|
|
|
|
|
|
|
auto greeting = house->getHouseGreeting();
|
|
|
|
memcpy( &greetingPacket->data().message, greeting.c_str(), greeting.size() );
|
|
|
|
|
|
|
|
player.queuePacket( greetingPacket );
|
|
|
|
}
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::buildPresetEstate( Entity::Player& player, uint8_t plotNum, uint32_t presetItem )
|
2018-11-24 15:17:18 +11:00
|
|
|
{
|
|
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto pLand = hZone->getLand( plotNum );
|
|
|
|
if( !pLand )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// todo: when doing FC houses, look up the type from the original purchase and check perms from FC and set state accordingly
|
2018-12-19 16:03:35 +11:00
|
|
|
if( pLand->getOwnerId() != player.getId() )
|
2018-11-24 15:17:18 +11:00
|
|
|
return;
|
|
|
|
|
|
|
|
// todo: check if permit is in inventory and remove one
|
|
|
|
|
2018-11-25 01:55:53 +11:00
|
|
|
if( !pLand->setPreset( presetItem ) )
|
|
|
|
return;
|
|
|
|
|
2018-11-24 15:17:18 +11:00
|
|
|
pLand->setState( HouseState::privateHouse );
|
|
|
|
pLand->setLandType( LandType::Private );
|
|
|
|
hZone->sendLandUpdate( plotNum );
|
|
|
|
|
|
|
|
auto pSuccessBuildingPacket = makeActorControl142( player.getId(), ActorControl::BuildPresetResponse, plotNum );
|
|
|
|
|
|
|
|
player.queuePacket( pSuccessBuildingPacket );
|
2018-11-26 23:32:22 +11:00
|
|
|
|
|
|
|
pLand->updateLandDb();
|
|
|
|
|
|
|
|
// start house built event
|
|
|
|
// CmnDefHousingBuildHouse_00149
|
|
|
|
player.eventStart( player.getId(), 0x000B0095, Event::EventHandler::EventType::Housing, 1, 1 );
|
2018-12-11 23:40:47 +01:00
|
|
|
player.playScene( 0x000B0095, 0, SET_BASE | HIDE_HOTBAR , 0, 1, plotNum, nullptr );
|
2018-11-26 23:32:22 +11:00
|
|
|
|
2018-12-21 22:23:49 +11:00
|
|
|
player.setLandFlags( LandFlagsSlot::Private, EstateBuilt, pLand->getLandIdent() );
|
2018-11-28 23:29:55 +11:00
|
|
|
player.sendLandFlagsSlot( LandFlagsSlot::Private );
|
2018-11-30 22:52:08 +11:00
|
|
|
|
2018-12-07 20:36:52 +11:00
|
|
|
hZone->registerHouseEntranceEObj( plotNum );
|
2018-11-24 15:17:18 +11:00
|
|
|
}
|
2018-11-28 21:24:00 +11:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::requestEstateRename( Entity::Player& player, const Common::LandIdent ident )
|
2018-11-28 21:24:00 +11:00
|
|
|
{
|
2018-12-05 16:55:14 +11:00
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
2018-11-28 21:24:00 +11:00
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto land = hZone->getLand( ident.landId );
|
2018-11-28 21:24:00 +11:00
|
|
|
|
|
|
|
auto house = land->getHouse();
|
|
|
|
if( !house )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto landRenamePacket = makeZonePacket< Server::FFXIVIpcLandRename >( player.getId() );
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
landRenamePacket->data().landIdent = ident;
|
2018-11-28 21:24:00 +11:00
|
|
|
memcpy( &landRenamePacket->data().houseName, house->getHouseName().c_str(), 20 );
|
|
|
|
|
|
|
|
player.queuePacket( landRenamePacket );
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::requestEstateEditGreeting( Entity::Player& player, const Common::LandIdent ident )
|
2018-11-28 21:24:00 +11:00
|
|
|
{
|
2018-12-05 16:55:14 +11:00
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
2018-11-28 21:24:00 +11:00
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto land = hZone->getLand( ident.landId );
|
2018-11-28 21:24:00 +11:00
|
|
|
if( !land )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto house = land->getHouse();
|
|
|
|
if( !house )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto estateGreetingPacket = makeZonePacket< Server::FFXIVIpcHousingEstateGreeting >( player.getId() );
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
estateGreetingPacket->data().landIdent = ident;
|
2018-11-28 21:24:00 +11:00
|
|
|
memcpy( &estateGreetingPacket->data().message, house->getHouseGreeting().c_str(), sizeof( estateGreetingPacket->data().message ) );
|
|
|
|
|
|
|
|
player.queuePacket( estateGreetingPacket );
|
|
|
|
}
|
2018-11-28 21:59:28 +11:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::updateEstateGreeting( Entity::Player& player, const Common::LandIdent ident, const std::string& greeting )
|
2018-11-28 21:59:28 +11:00
|
|
|
{
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto zone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !zone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto land = zone->getLand( ident.landId );
|
|
|
|
if( !land )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// todo: implement proper permissions checks
|
2018-12-19 16:03:35 +11:00
|
|
|
if( land->getOwnerId() != player.getId() )
|
2018-11-28 21:59:28 +11:00
|
|
|
return;
|
|
|
|
|
|
|
|
auto house = land->getHouse();
|
|
|
|
if( !house )
|
|
|
|
return;
|
|
|
|
|
|
|
|
house->setHouseGreeting( greeting );
|
|
|
|
|
2018-11-28 23:29:55 +11:00
|
|
|
// Greeting updated.
|
2018-11-28 21:59:28 +11:00
|
|
|
player.sendLogMessage( 3381 );
|
|
|
|
}
|
2018-11-29 00:19:37 +11:00
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::requestEstateEditGuestAccess( Entity::Player& player, const Common::LandIdent ident )
|
2018-11-29 00:19:37 +11:00
|
|
|
{
|
2018-12-05 16:55:14 +11:00
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
2018-11-29 00:19:37 +11:00
|
|
|
auto hZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !hZone )
|
|
|
|
return;
|
|
|
|
|
2018-12-05 16:55:14 +11:00
|
|
|
auto land = hZone->getLand( ident.landId );
|
2018-11-29 00:19:37 +11:00
|
|
|
if( !land )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// todo: add proper permission check
|
2018-12-19 16:03:35 +11:00
|
|
|
if( land->getOwnerId() != player.getId() )
|
2018-11-29 00:19:37 +11:00
|
|
|
return;
|
|
|
|
|
|
|
|
auto packet = makeZonePacket< FFXIVIpcHousingShowEstateGuestAccess >( player.getId() );
|
2018-12-05 16:55:14 +11:00
|
|
|
packet->data().ident = ident;
|
2018-11-29 00:19:37 +11:00
|
|
|
|
|
|
|
player.queuePacket( packet );
|
|
|
|
}
|
2018-12-05 16:55:14 +11:00
|
|
|
|
2018-12-19 01:03:41 +11:00
|
|
|
Sapphire::Common::LandIdent Sapphire::World::Manager::HousingMgr::clientTriggerParamsToLandIdent( uint32_t param11, uint32_t param12, bool use16bits ) const
|
2018-12-05 16:55:14 +11:00
|
|
|
{
|
|
|
|
Common::LandIdent ident;
|
|
|
|
ident.worldId = param11 >> 16;
|
|
|
|
ident.territoryTypeId = param11 & 0xFFFF;
|
2018-12-19 01:03:41 +11:00
|
|
|
|
|
|
|
if( use16bits )
|
|
|
|
{
|
|
|
|
ident.wardNum = param12 >> 16;
|
|
|
|
ident.landId = param12 & 0xFFFF;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ident.wardNum = (param12 >> 8) & 0xFF;
|
|
|
|
ident.landId = param12 & 0xFF;
|
|
|
|
}
|
2018-12-05 16:55:14 +11:00
|
|
|
|
|
|
|
return ident;
|
2018-12-05 19:58:43 +11:00
|
|
|
}
|
|
|
|
|
2018-12-22 11:33:21 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::sendEstateInventory( Entity::Player& player, uint16_t inventoryType,
|
|
|
|
uint8_t plotNum )
|
2018-12-05 19:58:43 +11:00
|
|
|
{
|
|
|
|
Sapphire::LandPtr targetLand;
|
|
|
|
|
|
|
|
// plotNum will be 255 in the event that it's an internal zone
|
|
|
|
// and we have to switch up our way of getting the LandPtr
|
|
|
|
if( plotNum == 255 )
|
|
|
|
{
|
|
|
|
auto internalZone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() );
|
|
|
|
if( !internalZone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto ident = internalZone->getIdent();
|
|
|
|
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto exteriorZone = getHousingZoneByLandSetId( landSetId );
|
|
|
|
|
|
|
|
if( !exteriorZone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
targetLand = exteriorZone->getLand( ident.landId );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto zone = std::dynamic_pointer_cast< HousingZone >( player.getCurrentZone() );
|
|
|
|
if( !zone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
targetLand = zone->getLand( plotNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !targetLand )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// todo: add proper permissions checks
|
2018-12-19 16:03:35 +11:00
|
|
|
if( targetLand->getOwnerId() != player.getId() )
|
2018-12-05 19:58:43 +11:00
|
|
|
return;
|
|
|
|
|
2018-12-22 15:58:54 +11:00
|
|
|
auto container = getEstateInventory( targetLand->getLandIdent() )[ inventoryType ];
|
2018-12-20 20:41:16 +11:00
|
|
|
if( !container )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto invMgr = g_fw.get< Manager::InventoryMgr >();
|
|
|
|
invMgr->sendInventoryContainer( player, container );
|
2018-12-22 11:33:21 +11:00
|
|
|
}
|
|
|
|
|
2018-12-22 14:35:42 +11:00
|
|
|
const Sapphire::World::Manager::HousingMgr::LandSetLandCacheMap&
|
2018-12-22 17:25:30 +11:00
|
|
|
Sapphire::World::Manager::HousingMgr::getLandCacheMap()
|
2018-12-22 11:33:21 +11:00
|
|
|
{
|
|
|
|
return m_landCache;
|
2018-12-22 14:35:42 +11:00
|
|
|
}
|
|
|
|
|
2018-12-22 17:25:30 +11:00
|
|
|
Sapphire::World::Manager::HousingMgr::LandIdentToInventoryContainerMap&
|
|
|
|
Sapphire::World::Manager::HousingMgr::getEstateInventories()
|
2018-12-22 14:35:42 +11:00
|
|
|
{
|
|
|
|
return m_estateInventories;
|
|
|
|
}
|
|
|
|
|
2018-12-22 17:25:30 +11:00
|
|
|
Sapphire::World::Manager::HousingMgr::ContainerIdToContainerMap&
|
|
|
|
Sapphire::World::Manager::HousingMgr::getEstateInventory( uint64_t ident )
|
2018-12-22 14:35:42 +11:00
|
|
|
{
|
|
|
|
auto map = m_estateInventories.find( ident );
|
|
|
|
|
|
|
|
assert( map != m_estateInventories.end() );
|
|
|
|
|
|
|
|
return map->second;
|
|
|
|
}
|
|
|
|
|
2018-12-22 17:25:30 +11:00
|
|
|
Sapphire::World::Manager::HousingMgr::ContainerIdToContainerMap&
|
|
|
|
Sapphire::World::Manager::HousingMgr::getEstateInventory( Sapphire::Common::LandIdent ident )
|
2018-12-22 14:35:42 +11:00
|
|
|
{
|
|
|
|
auto u64ident = *reinterpret_cast< uint64_t* >( &ident );
|
|
|
|
|
2018-12-22 17:54:56 +11:00
|
|
|
return getEstateInventory( u64ident );
|
2018-12-22 17:22:24 +11:00
|
|
|
}
|
|
|
|
|
2018-12-23 15:23:22 +11:00
|
|
|
void Sapphire::World::Manager::HousingMgr::updateHouseModelsFromDb( Sapphire::HousePtr house )
|
2018-12-22 17:22:24 +11:00
|
|
|
{
|
|
|
|
assert( house );
|
|
|
|
|
|
|
|
auto getItemData = []( uint32_t itemId )
|
|
|
|
{
|
|
|
|
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
|
|
|
|
auto info = pExdData->get< Sapphire::Data::Item >( itemId );
|
|
|
|
return info->additionalData;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto containers = getEstateInventory( house->getLandIdent() );
|
|
|
|
|
|
|
|
auto extContainer = containers.find( static_cast< uint16_t >( InventoryType::HousingOutdoorAppearance ) );
|
|
|
|
if( extContainer != containers.end() )
|
|
|
|
{
|
|
|
|
for( auto& item : extContainer->second->getItemMap() )
|
|
|
|
{
|
2018-12-23 15:23:22 +11:00
|
|
|
house->setExteriorModel( static_cast< Common::HousePartSlot >( item.first ),
|
|
|
|
getItemData( item.second->getId() ), item.second->getStain() );
|
2018-12-22 17:22:24 +11:00
|
|
|
}
|
|
|
|
}
|
2018-12-22 21:20:12 +11:00
|
|
|
else
|
|
|
|
{
|
|
|
|
g_fw.get< Logger >()->error( "Plot " + std::to_string( house->getLandIdent().landId ) + " has an invalid inventory configuration for outdoor appearance." );
|
|
|
|
}
|
2018-12-22 17:22:24 +11:00
|
|
|
|
|
|
|
auto intContainer = containers.find( static_cast< uint16_t >( InventoryType::HousingInteriorAppearance ) );
|
|
|
|
if( intContainer != containers.end() )
|
|
|
|
{
|
|
|
|
for( auto& item : intContainer->second->getItemMap() )
|
|
|
|
{
|
2018-12-23 15:23:22 +11:00
|
|
|
house->setInteriorModel( static_cast< Common::HousingInteriorSlot >( item.first ),
|
|
|
|
getItemData( item.second->getId() ) );
|
2018-12-22 17:22:24 +11:00
|
|
|
}
|
|
|
|
}
|
2018-12-22 21:20:12 +11:00
|
|
|
else
|
|
|
|
{
|
|
|
|
g_fw.get< Logger >()->error( "Plot " + std::to_string( house->getLandIdent().landId ) + " has an invalid inventory configuration for indoor appearance." );
|
|
|
|
}
|
2018-12-22 17:54:56 +11:00
|
|
|
}
|