mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-27 22:57:45 +00:00
1808 lines
No EOL
59 KiB
C++
1808 lines
No EOL
59 KiB
C++
#include "HousingMgr.h"
|
|
#include <Logging/Logger.h>
|
|
#include <Database/DatabaseDef.h>
|
|
#include <Exd/ExdData.h>
|
|
#include <Network/PacketContainer.h>
|
|
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
|
#include <Network/PacketWrappers/ActorControlPacket.h>
|
|
#include <Network/PacketWrappers/ActorControlSelfPacket.h>
|
|
#include <Network/CommonActorControl.h>
|
|
#include <Network/GameConnection.h>
|
|
|
|
#include <unordered_map>
|
|
#include <cstring>
|
|
#include <Service.h>
|
|
#include <Session.h>
|
|
|
|
#include "Actor/Player.h"
|
|
#include "Actor/EventObject.h"
|
|
#include "Event/EventDefs.h"
|
|
|
|
#include "TerritoryMgr.h"
|
|
#include "Territory/HousingZone.h"
|
|
#include "Territory/Housing/HousingInteriorTerritory.h"
|
|
#include "EventMgr.h"
|
|
#include "Territory/Land.h"
|
|
#include "WorldServer.h"
|
|
#include "Territory/House.h"
|
|
#include "InventoryMgr.h"
|
|
#include "PlayerMgr.h"
|
|
#include "Inventory/HousingItem.h"
|
|
#include "Inventory/ItemContainer.h"
|
|
#include "Util/UtilMath.h"
|
|
|
|
using namespace Sapphire;
|
|
using namespace Sapphire::World::Manager;
|
|
using namespace Sapphire::Network;
|
|
using namespace Sapphire::Network::Packets;
|
|
using namespace Sapphire::Network::Packets::WorldPackets::Server;
|
|
|
|
HousingMgr::HousingMgr()
|
|
{
|
|
m_containerMap[ 0 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems1, Common::InventoryType::HousingInteriorStoreroom1 );
|
|
m_containerMap[ 1 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems2, Common::InventoryType::HousingInteriorStoreroom2 );
|
|
m_containerMap[ 2 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems3, Common::InventoryType::HousingInteriorStoreroom3 );
|
|
m_containerMap[ 3 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems4, Common::InventoryType::HousingInteriorStoreroom4 );
|
|
m_containerMap[ 4 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems5, Common::InventoryType::HousingInteriorStoreroom5 );
|
|
m_containerMap[ 5 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems6, Common::InventoryType::HousingInteriorStoreroom6 );
|
|
m_containerMap[ 6 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems7, Common::InventoryType::HousingInteriorStoreroom7 );
|
|
m_containerMap[ 7 ] = std::make_pair( Common::InventoryType::HousingInteriorPlacedItems8, Common::InventoryType::HousingInteriorStoreroom8 );
|
|
|
|
m_internalPlacedItemContainers =
|
|
{
|
|
Common::InventoryType::HousingInteriorPlacedItems1,
|
|
Common::InventoryType::HousingInteriorPlacedItems2,
|
|
Common::InventoryType::HousingInteriorPlacedItems3,
|
|
Common::InventoryType::HousingInteriorPlacedItems4,
|
|
Common::InventoryType::HousingInteriorPlacedItems5,
|
|
Common::InventoryType::HousingInteriorPlacedItems6,
|
|
Common::InventoryType::HousingInteriorPlacedItems7,
|
|
Common::InventoryType::HousingInteriorPlacedItems8,
|
|
};
|
|
|
|
m_internalStoreroomContainers =
|
|
{
|
|
Common::InventoryType::HousingInteriorStoreroom1,
|
|
Common::InventoryType::HousingInteriorStoreroom2,
|
|
Common::InventoryType::HousingInteriorStoreroom3,
|
|
Common::InventoryType::HousingInteriorStoreroom4,
|
|
Common::InventoryType::HousingInteriorStoreroom5,
|
|
Common::InventoryType::HousingInteriorStoreroom6,
|
|
Common::InventoryType::HousingInteriorStoreroom7,
|
|
Common::InventoryType::HousingInteriorStoreroom8,
|
|
};
|
|
}
|
|
|
|
HousingMgr::~HousingMgr() = default;
|
|
|
|
bool HousingMgr::init()
|
|
{
|
|
Logger::info( "HousingMgr: Caching housing land init data" );
|
|
//LAND_SEL_ALL
|
|
|
|
// 12 wards per territory, 2 territories
|
|
m_landCache.reserve( 12 * 2 );
|
|
|
|
initLandCache();
|
|
|
|
Logger::debug( "HousingMgr: Checking land counts" );
|
|
|
|
size_t houseCount = 0;
|
|
for( auto& landSet : m_landCache )
|
|
{
|
|
auto count = landSet.second.size();
|
|
|
|
houseCount += count;
|
|
|
|
if( landSet.second.size() != 60 )
|
|
{
|
|
Logger::fatal( "LandSet {0} is missing land entries. Only have {1} land entries.", landSet.first, count );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Logger::info( "HousingMgr: Cached {0} houses", houseCount );
|
|
|
|
/////
|
|
|
|
if( !loadEstateInventories() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HousingMgr::loadEstateInventories()
|
|
{
|
|
Logger::info( "HousingMgr: Loading inventories for estates" );
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto stmt = db.getPreparedStatement( Db::LAND_INV_SEL_ALL );
|
|
auto res = db.query( stmt );
|
|
|
|
uint32_t itemCount = 0;
|
|
while( res->next() )
|
|
{
|
|
//uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq
|
|
auto ident = res->getUInt64( "LandIdent" );
|
|
auto containerId = res->getUInt16( "ContainerId" );
|
|
auto itemId = res->getUInt64( "ItemId" );
|
|
auto slot = res->getUInt16( "SlotId" );
|
|
auto catalogId = res->getUInt( "catalogId" );
|
|
auto stain = res->getUInt8( "stain" );
|
|
auto characterId = res->getUInt64( "CharacterId" );
|
|
|
|
auto item = Inventory::make_HousingItem( itemId, catalogId );
|
|
item->setStain( stain );
|
|
item->setStackSize( 1 );
|
|
// todo: need to set the owner character id on the item
|
|
|
|
// set world pos on item if its in an placed item container
|
|
if( isPlacedItemsInventory( static_cast< Common::InventoryType >( containerId ) ) )
|
|
{
|
|
item->setPos( {
|
|
res->getFloat( "PosX" ),
|
|
res->getFloat( "PosY" ),
|
|
res->getFloat( "PosZ" )
|
|
} );
|
|
|
|
item->setRot( res->getFloat( "Rotation" ) );
|
|
}
|
|
|
|
ContainerIdToContainerMap& estateInv = m_estateInventories[ ident ];
|
|
|
|
// check if containerId exists, it always should - if it doesn't, something went wrong
|
|
auto container = estateInv.find( containerId );
|
|
if( container == estateInv.end() )
|
|
{
|
|
Logger::warn( "Skipping item#{0} for ident#{1} - container#{2} doesn't exist for estate.",
|
|
itemId, ident, containerId );
|
|
|
|
continue;
|
|
}
|
|
|
|
container->second->setItem( static_cast< uint8_t >( slot ), item );
|
|
|
|
itemCount++;
|
|
}
|
|
|
|
Logger::debug( "HousingMgr: Loaded {0} inventory items", itemCount );
|
|
|
|
return true;
|
|
}
|
|
|
|
void HousingMgr::initLandCache()
|
|
{
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto stmt = db.getPreparedStatement( Db::LAND_SEL_ALL );
|
|
auto res = db.query( stmt );
|
|
|
|
while( res->next() )
|
|
{
|
|
LandCacheEntry entry;
|
|
|
|
// land stuff
|
|
entry.m_landSetId = res->getUInt64( "LandSetId" );
|
|
entry.m_landId = static_cast< uint16_t >( res->getUInt( "LandId" ) );
|
|
|
|
entry.m_type = static_cast< Common::LandType >( res->getUInt( "Type" ) );
|
|
entry.m_size = static_cast< Common::HouseSize >( res->getUInt8( "Size" ) );
|
|
entry.m_status = static_cast< Common::HouseStatus >( res->getUInt8( "Status" ) );
|
|
entry.m_currentPrice = res->getUInt64( "LandPrice" );
|
|
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" );
|
|
entry.m_estateName = res->getString( "HouseName" );
|
|
entry.m_buildTime = res->getUInt64( "BuildTime" );
|
|
entry.m_endorsements = res->getUInt64( "Endorsements" );
|
|
entry.m_hasAetheryte = res->getBoolean( "Aetheryte" );
|
|
|
|
m_landCache[ entry.m_landSetId ].push_back( entry );
|
|
|
|
uint16_t maxExternalItems = 0;
|
|
uint16_t maxInternalItems = 0;
|
|
|
|
// init inventory containers
|
|
switch( entry.m_size )
|
|
{
|
|
case Common::HouseSize::HOUSE_SIZE_S:
|
|
entry.m_maxPlacedExternalItems = 20;
|
|
entry.m_maxPlacedInternalItems = 100;
|
|
break;
|
|
case Common::HouseSize::HOUSE_SIZE_M:
|
|
entry.m_maxPlacedExternalItems = 30;
|
|
entry.m_maxPlacedInternalItems = 150;
|
|
break;
|
|
case Common::HouseSize::HOUSE_SIZE_L:
|
|
entry.m_maxPlacedExternalItems = 40;
|
|
entry.m_maxPlacedInternalItems = 200;
|
|
break;
|
|
default:
|
|
// this should never ever happen, if it does the db is fucked
|
|
Logger::error( "HousingMgr: Plot {0} in landset {1} has an invalid land size, defaulting to cottage.",
|
|
entry.m_landId, entry.m_landSetId );
|
|
entry.m_maxPlacedExternalItems = 20;
|
|
entry.m_maxPlacedInternalItems = 200;
|
|
break;
|
|
}
|
|
|
|
// setup containers
|
|
// todo: this is pretty garbage
|
|
Common::LandIdent ident;
|
|
ident.territoryTypeId = static_cast< int16_t >( entry.m_landSetId >> 16 );
|
|
ident.wardNum = static_cast< int16_t >( entry.m_landSetId & 0xFFFF );
|
|
ident.landId = static_cast< int16_t >( entry.m_landId );
|
|
ident.worldId = 67;
|
|
|
|
auto& containers = getEstateInventory( ident );
|
|
|
|
auto makeContainer = [ &containers ]( Common::InventoryType type, uint16_t size )
|
|
{
|
|
containers[ type ] = make_ItemContainer( type, size, "houseiteminventory", false, false );
|
|
};
|
|
|
|
uint16_t count = 0;
|
|
for( int i = 0; i < 8; ++i )
|
|
{
|
|
if( count > entry.m_maxPlacedInternalItems )
|
|
break;
|
|
|
|
auto& pair = m_containerMap[ i ];
|
|
|
|
makeContainer( pair.first, 50 );
|
|
makeContainer( pair.second, 50 );
|
|
|
|
count += 50;
|
|
}
|
|
|
|
// exterior
|
|
makeContainer( Common::InventoryType::HousingExteriorPlacedItems, entry.m_maxPlacedExternalItems );
|
|
makeContainer( Common::InventoryType::HousingExteriorStoreroom, entry.m_maxPlacedExternalItems );
|
|
|
|
// fixed containers
|
|
makeContainer( Common::InventoryType::HousingInteriorAppearance, 20 );
|
|
makeContainer( Common::InventoryType::HousingExteriorAppearance, 20 );
|
|
|
|
}
|
|
}
|
|
|
|
uint64_t HousingMgr::getNextHouseId()
|
|
{
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
auto pQR = db.query( "SELECT MAX( HouseId ) FROM house" );
|
|
|
|
if( !pQR->next() )
|
|
return 0;
|
|
|
|
return pQR->getUInt64( 1 ) + 1;
|
|
}
|
|
|
|
uint32_t HousingMgr::toLandSetId( int16_t territoryTypeId, int16_t wardId ) const
|
|
{
|
|
return ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardId;
|
|
}
|
|
|
|
|
|
LandPtr HousingMgr::getLandByOwnerId( uint64_t id )
|
|
{
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
auto res = db.query( "SELECT LandSetId, LandId FROM land WHERE OwnerId = " + std::to_string( id ) );
|
|
|
|
if( !res->next() )
|
|
return nullptr;
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( res->getUInt( 1 ) );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
if( !hZone )
|
|
return nullptr;
|
|
|
|
return hZone->getLand( static_cast< uint8_t >( res->getUInt( 2 ) ) );
|
|
}
|
|
|
|
void HousingMgr::sendLandSignOwned( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
|
|
player.setActiveLand( static_cast< uint8_t >( ident.landId ), static_cast< uint8_t >( ident.wardNum ) );
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
if( !land )
|
|
return;
|
|
|
|
auto landInfoSignPacket = makeZonePacket< FFXIVIpcHousingProfile >( player.getId() );
|
|
landInfoSignPacket->data().Size = land->getSize();
|
|
landInfoSignPacket->data().Welcome = static_cast< uint8_t >( land->getLandType() );
|
|
landInfoSignPacket->data().LandId = ident;
|
|
//landInfoSignPacket->data().houseIconAdd = land->getSharing();
|
|
landInfoSignPacket->data().OwnerId = player.getCharacterId(); // todo: should be real owner contentId, not player.contentId()
|
|
|
|
if( auto house = land->getHouse() )
|
|
{
|
|
std::strcpy( landInfoSignPacket->data().Name, house->getHouseName().c_str() );
|
|
std::strcpy( landInfoSignPacket->data().Greeting, house->getHouseGreeting().c_str() );
|
|
}
|
|
|
|
uint64_t characterId = land->getOwnerId();
|
|
|
|
std::string playerName = server.getPlayerNameFromDb( characterId );
|
|
|
|
memcpy( &landInfoSignPacket->data().OwnerName, playerName.c_str(), playerName.size() );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), landInfoSignPacket );
|
|
}
|
|
|
|
void HousingMgr::sendLandSignFree( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
player.setActiveLand( static_cast< uint8_t >( ident.landId ), static_cast< uint8_t >( ident.wardNum ) );
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
auto plotPricePacket = makeZonePacket< FFXIVIpcHousingAuction >( player.getId() );
|
|
plotPricePacket->data().Price = land->getCurrentPrice();
|
|
plotPricePacket->data().Timer = land->getDevaluationTime();
|
|
|
|
server.queueForPlayer( player.getCharacterId(), plotPricePacket );
|
|
}
|
|
|
|
LandPurchaseResult HousingMgr::purchaseLand( Entity::Player& player, HousingZone& zone, uint8_t plot, uint8_t state )
|
|
{
|
|
|
|
auto plotPrice = zone.getLand( plot )->getCurrentPrice();
|
|
auto gilAvailable = player.getCurrency( Common::CurrencyType::Gil );
|
|
auto pLand = zone.getLand( plot );
|
|
|
|
if( !pLand )
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
|
|
|
if( pLand->getStatus() != Common::HouseStatus::ForSale )
|
|
return LandPurchaseResult::ERR_NOT_AVAILABLE;
|
|
|
|
if( gilAvailable < plotPrice )
|
|
return LandPurchaseResult::ERR_NOT_ENOUGH_GIL;
|
|
|
|
|
|
switch( static_cast< LandPurchaseMode >( state ) )
|
|
{
|
|
case LandPurchaseMode::FC:
|
|
PlayerMgr::sendDebug( player, "Free company house purchase aren't supported at this time." );
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
|
case LandPurchaseMode::PRIVATE:
|
|
{
|
|
|
|
auto pOldLand = getLandByOwnerId( player.getCharacterId() );
|
|
|
|
if( pOldLand )
|
|
return LandPurchaseResult::ERR_NO_MORE_LANDS_FOR_CHAR;
|
|
|
|
player.removeCurrency( Common::CurrencyType::Gil, plotPrice );
|
|
pLand->setOwnerId( player.getCharacterId() );
|
|
pLand->setStatus( Common::HouseStatus::Sold );
|
|
pLand->setLandType( Common::LandType::Private );
|
|
|
|
player.setLandFlags( Common::LandFlagsSlot::Private, 0x00, pLand->getLandIdent() );
|
|
|
|
sendLandFlagsSlot( player, Common::LandFlagsSlot::Private );
|
|
|
|
//pLand->setLandName( "Private Estate" + std::to_string( pHousing->getWardNum() ) + "-" + std::to_string( plot ) );
|
|
pLand->updateLandDb();
|
|
|
|
zone.sendLandUpdate( plot );
|
|
return LandPurchaseResult::SUCCESS;
|
|
}
|
|
|
|
default:
|
|
return LandPurchaseResult::ERR_INTERNAL;
|
|
}
|
|
|
|
}
|
|
|
|
bool HousingMgr::relinquishLand( Entity::Player& player, HousingZone& zone, uint8_t plot )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
// TODO: Fix "permissions" being sent incorrectly
|
|
// TODO: Add checks for land state before relinquishing
|
|
|
|
auto pLand = zone.getLand( plot );
|
|
auto plotMaxPrice = pLand->getCurrentPrice();
|
|
|
|
// can't relinquish when you are not the owner
|
|
// TODO: actually use permissions here for FC houses
|
|
if( !hasPermission( player, *pLand, 0 ) )
|
|
{
|
|
auto msgPkt = makeActorControlSelf( player.getId(), ActorControl::LogMsg, 3304, 0 );
|
|
server.queueForPlayer( player.getCharacterId(), msgPkt );
|
|
return false;
|
|
}
|
|
|
|
// unable to relinquish if there is a house built
|
|
// TODO: additionally check for yard items
|
|
if( pLand->getHouse() )
|
|
{
|
|
auto msgPkt = makeActorControlSelf( player.getId(), ActorControl::LogMsg, 3315, 0 );
|
|
server.queueForPlayer( player.getCharacterId(), msgPkt );
|
|
return false;
|
|
}
|
|
|
|
pLand->setCurrentPrice( pLand->getMaxPrice() );
|
|
pLand->setOwnerId( 0 );
|
|
pLand->setStatus( Common::HouseStatus::ForSale );
|
|
pLand->setLandType( Common::LandType::none );
|
|
pLand->updateLandDb();
|
|
|
|
Common::LandIdent ident { 0xFF, 0xFF, 0xFF, 0xFF };
|
|
|
|
player.setLandFlags( Common::LandFlagsSlot::Private, 0x00, ident );
|
|
|
|
sendLandFlagsSlot( player, Common::LandFlagsSlot::Private );
|
|
|
|
auto screenMsgPkt2 = makeActorControlSelf( player.getId(), ActorControl::LogMsg, 3351, 0x1AA,
|
|
pLand->getLandIdent().wardNum + 1, plot + 1 );
|
|
server.queueForPlayer( player.getCharacterId(), screenMsgPkt2 );
|
|
zone.sendLandUpdate( plot );
|
|
|
|
return true;
|
|
}
|
|
|
|
void HousingMgr::sendWardLandInfo( Entity::Player& player, uint8_t wardId, uint16_t territoryTypeId )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto landSetId = toLandSetId( territoryTypeId, wardId );
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto wardInfoPacket = makeZonePacket< FFXIVIpcHousingProfileList >( player.getId() );
|
|
wardInfoPacket->data().LandSetId.wardNum = wardId;
|
|
wardInfoPacket->data().LandSetId.territoryTypeId = static_cast< int16_t >( territoryTypeId );
|
|
|
|
// todo: properly get worldId
|
|
wardInfoPacket->data().LandSetId.worldId = 67;
|
|
|
|
for( int i = 0; i < 30; i++ )
|
|
{
|
|
auto land = hZone->getLand( i );
|
|
assert( land );
|
|
|
|
auto& entry = wardInfoPacket->data().ProfileList[ i ];
|
|
|
|
// retail always sends the house price in this packet, even after the house has been Sold
|
|
// so I guess we do the same
|
|
entry.price = land->getCurrentPrice();
|
|
|
|
if( land->getStatus() == Common::HouseStatus::ForSale )
|
|
continue;
|
|
|
|
if( auto house = land->getHouse() )
|
|
{
|
|
if( !house->getHouseGreeting().empty() )
|
|
entry.status |= Common::WardlandFlags::HasEstateGreeting;
|
|
}
|
|
|
|
switch( land->getLandType() )
|
|
{
|
|
case Common::LandType::FreeCompany:
|
|
entry.status |= Common::WardlandFlags::IsEstateOwned | Common::WardlandFlags::IsFreeCompanyEstate;
|
|
|
|
// todo: send FC name
|
|
|
|
break;
|
|
|
|
case Common::LandType::Private:
|
|
entry.status |= Common::WardlandFlags::IsEstateOwned;
|
|
/* //Disabled. No more name in Info
|
|
auto owner = land->getOwnerId();
|
|
auto playerName = server.getPlayerNameFromDb( static_cast< uint32_t >( owner ) );
|
|
memcpy( &entry.fcTag, playerName.c_str(), playerName.size() );
|
|
*/
|
|
break;
|
|
}
|
|
|
|
// todo: check we have an estate message and set the flag
|
|
// todo: check if estate allows public entry
|
|
}
|
|
|
|
server.queueForPlayer( player.getCharacterId(), wardInfoPacket );
|
|
}
|
|
|
|
void HousingMgr::sendEstateGreeting( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
if( !land )
|
|
return;
|
|
|
|
auto house = land->getHouse();
|
|
if( !house )
|
|
return;
|
|
|
|
auto greetingPacket = makeZonePacket< FFXIVIpcHousingGreeting >( player.getId() );
|
|
|
|
greetingPacket->data().LandId = ident;
|
|
|
|
auto greeting = house->getHouseGreeting();
|
|
memcpy( &greetingPacket->data().Greeting, greeting.c_str(), greeting.size() );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), greetingPacket );
|
|
}
|
|
|
|
bool HousingMgr::initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId )
|
|
{
|
|
auto house = land->getHouse();
|
|
assert( house );
|
|
|
|
// add containers to inv collection
|
|
auto& houseInventory = getEstateInventory( house->getLandIdent() );
|
|
houseInventory[ Common::InventoryType::HousingInteriorAppearance ];// = intContainer;
|
|
houseInventory[ Common::InventoryType::HousingExteriorAppearance ];// = extContainer;
|
|
|
|
auto& exdData = Common::Service< Sapphire::Data::ExdData >::ref();
|
|
auto preset = exdData.getRow< Excel::HousingPreset >( getItemAdditionalData( presetCatalogId ) );
|
|
if( !preset )
|
|
return false;
|
|
|
|
// remove preset item
|
|
Inventory::InventoryContainerPair foundItem;
|
|
if( !player.findFirstItemWithId( presetCatalogId, foundItem ) )
|
|
return false;
|
|
|
|
auto item = getHousingItemFromPlayer( player, foundItem.first, foundItem.second );
|
|
if( !item )
|
|
return false;
|
|
|
|
// move preset item into ext appearance container
|
|
houseInventory[ Common::InventoryType::HousingExteriorAppearance ]->setItem( Common::HouseExteriorSlot::HousePermit, item );
|
|
|
|
// high iq shit
|
|
auto invMap = std::map< uint16_t, std::map< uint32_t, int32_t > >
|
|
{
|
|
// external
|
|
{
|
|
Common::InventoryType::HousingExteriorAppearance,
|
|
{
|
|
{ Common::HouseExteriorSlot::ExteriorRoof, preset->data().Roof },
|
|
{ Common::HouseExteriorSlot::ExteriorWall, preset->data().Wall },
|
|
{ Common::HouseExteriorSlot::ExteriorWindow, preset->data().Window },
|
|
{ Common::HouseExteriorSlot::ExteriorDoor, preset->data().Door }
|
|
}
|
|
},
|
|
|
|
// internal
|
|
{
|
|
Common::InventoryType::HousingInteriorAppearance,
|
|
{
|
|
// lobby/middle floor
|
|
{ Common::HouseInteriorSlot::InteriorWall, preset->data().Interior[ 0 ] },
|
|
{ Common::HouseInteriorSlot::InteriorFloor, preset->data().Interior[ 1 ] },
|
|
{ Common::HouseInteriorSlot::InteriorLight, preset->data().Interior[ 2 ] },
|
|
|
|
// attic
|
|
{ Common::HouseInteriorSlot::InteriorWall_Attic, preset->data().Interior[ 3 ] },
|
|
{ Common::HouseInteriorSlot::InteriorFloor_Attic, preset->data().Interior[ 4 ] },
|
|
{ Common::HouseInteriorSlot::InteriorLight_Attic, preset->data().Interior[ 5 ] },
|
|
|
|
// basement
|
|
{ Common::HouseInteriorSlot::InteriorWall_Basement, preset->data().Interior[ 6 ] },
|
|
{ Common::HouseInteriorSlot::InteriorFloor_Basement, preset->data().Interior[ 7 ] },
|
|
{ Common::HouseInteriorSlot::InteriorLight_Basement, preset->data().Interior[ 8 ] },
|
|
}
|
|
}
|
|
};
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
// create and link items
|
|
for( auto& destContainer : invMap )
|
|
{
|
|
auto& container = houseInventory[ destContainer.first ];
|
|
|
|
for( auto& itemIt : destContainer.second )
|
|
{
|
|
// small houses attic is just 0, ignore them
|
|
if( itemIt.second == 0 )
|
|
continue;
|
|
|
|
auto pItem = invMgr.createItem( player, static_cast< uint32_t >( itemIt.second ) );
|
|
|
|
container->setItem( static_cast< uint8_t >( itemIt.first ), pItem );
|
|
}
|
|
|
|
invMgr.saveHousingContainer( land->getLandIdent(), container );
|
|
}
|
|
|
|
// lift off
|
|
updateHouseModels( house );
|
|
|
|
return true;
|
|
}
|
|
|
|
void HousingMgr::createHouse( Sapphire::HousePtr house ) const
|
|
{
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto stmt = db.getPreparedStatement( Db::HOUSING_HOUSE_INS );
|
|
// LandSetId, HouseId, HouseName
|
|
|
|
stmt->setUInt( 1, house->getLandSetId() );
|
|
stmt->setUInt( 2, house->getId() );
|
|
stmt->setString( 3, house->getHouseName() );
|
|
|
|
db.execute( stmt );
|
|
}
|
|
|
|
void HousingMgr::deleteHouse( Sapphire::HousePtr house ) const
|
|
{
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
auto stmt = db.getPreparedStatement( Db::HOUSING_HOUSE_DEL );
|
|
stmt->setUInt( 1, house->getId() );
|
|
db.execute( stmt );
|
|
}
|
|
|
|
void HousingMgr::buildPresetEstate( Entity::Player& player, HousingZone& zone, uint8_t plotNum, uint32_t presetCatalogId )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto pLand = zone.getLand( plotNum );
|
|
if( !pLand )
|
|
return;
|
|
|
|
if( !hasPermission( player, *pLand, 0 ) )
|
|
return;
|
|
|
|
// create house
|
|
auto ident = pLand->getLandIdent();
|
|
auto house = make_House( getNextHouseId(), pLand->getLandSetId(), ident, "Estate #" + std::to_string( ident.landId + 1 ), "" );
|
|
|
|
pLand->setHouse( house );
|
|
|
|
// create inventory items
|
|
if( !initHouseModels( player, pLand, presetCatalogId ) )
|
|
{
|
|
pLand->setHouse( nullptr );
|
|
Logger::error( "House failed building because of initHouseModels failed" );
|
|
return;
|
|
}
|
|
|
|
auto& eventMgr = Common::Service< World::Manager::EventMgr >::ref();
|
|
|
|
createHouse( house );
|
|
|
|
pLand->setStatus( Common::HouseStatus::PrivateEstate );
|
|
pLand->setLandType( Common::LandType::Private );
|
|
zone.sendLandUpdate( plotNum );
|
|
|
|
auto pSuccessBuildingPacket = makeActorControl( player.getId(), ActorControl::BuildPresetResponse, plotNum );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), pSuccessBuildingPacket );
|
|
|
|
pLand->updateLandDb();
|
|
|
|
// start house built event
|
|
// CmnDefHousingBuildHouse_00149
|
|
eventMgr.eventStart( player, player.getId(), 0x000B0095, Event::EventHandler::EventType::Housing, 1, 1 );
|
|
eventMgr.playScene( player, 0x000B0095, 0, static_cast< uint32_t >( SET_BASE | HIDE_HOTBAR ), { 1, plotNum } );
|
|
|
|
player.setLandFlags( Common::LandFlagsSlot::Private, Common::HOUSING_LAND_STATUS::HOUSING_LAND_STATUS_BUILDHOUSE, ident );
|
|
sendLandFlagsSlot( player, Common::LandFlagsSlot::Private );
|
|
|
|
zone.registerEstateEntranceEObj( plotNum );
|
|
}
|
|
|
|
void HousingMgr::requestEstateRename( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
|
|
auto house = land->getHouse();
|
|
if( !house )
|
|
return;
|
|
|
|
auto landRenamePacket = makeZonePacket< FFXIVIpcHousingHouseName >( player.getId() );
|
|
|
|
landRenamePacket->data().LandId = ident;
|
|
memcpy( &landRenamePacket->data().Name, house->getHouseName().c_str(), 20 );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), landRenamePacket );
|
|
}
|
|
|
|
void HousingMgr::requestEstateEditGreeting( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
if( !land )
|
|
return;
|
|
|
|
auto house = land->getHouse();
|
|
if( !house )
|
|
return;
|
|
|
|
auto estateGreetingPacket = makeZonePacket< FFXIVIpcHousingGreeting >( player.getId() );
|
|
|
|
estateGreetingPacket->data().LandId = ident;
|
|
memcpy( &estateGreetingPacket->data().Greeting, house->getHouseGreeting().c_str(), sizeof( estateGreetingPacket->data().Greeting ) );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), estateGreetingPacket );
|
|
}
|
|
|
|
void HousingMgr::updateEstateGreeting( Entity::Player& player, const Common::LandIdent ident, const std::string& greeting )
|
|
{
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto house = land->getHouse();
|
|
if( !house )
|
|
return;
|
|
|
|
house->setHouseGreeting( greeting );
|
|
|
|
// Greeting updated.
|
|
PlayerMgr::sendLogMessage( player, 3381 );
|
|
}
|
|
|
|
void HousingMgr::requestEstateEditGuestAccess( Entity::Player& player, const Common::LandIdent ident )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
auto land = hZone->getLand( ident.landId );
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto packet = makeZonePacket< FFXIVIpcHousingWelcome >( player.getId() );
|
|
packet->data().LandId = ident;
|
|
packet->data().Welcome = land->getSharing();
|
|
|
|
server.queueForPlayer( player.getCharacterId(), packet );
|
|
}
|
|
|
|
Common::LandIdent HousingMgr::clientTriggerParamsToLandIdent( uint32_t param11, uint32_t param12, bool use16bits ) const
|
|
{
|
|
Common::LandIdent ident{};
|
|
ident.worldId = static_cast< int16_t >( param11 >> 16 );
|
|
ident.territoryTypeId = static_cast< int16_t >( param11 & 0xFFFF );
|
|
|
|
if( use16bits )
|
|
{
|
|
ident.wardNum = static_cast< int16_t >( param12 >> 16 );
|
|
ident.landId = static_cast< int16_t >( param12 & 0xFFFF );
|
|
}
|
|
else
|
|
{
|
|
ident.wardNum = (param12 >> 8) & 0xFF;
|
|
ident.landId = param12 & 0xFF;
|
|
}
|
|
|
|
return ident;
|
|
}
|
|
|
|
void HousingMgr::sendEstateInventory( Entity::Player& player, uint16_t inventoryType, uint8_t plotNum )
|
|
{
|
|
Sapphire::LandPtr targetLand;
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
// 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 >( pZone );
|
|
if( !internalZone )
|
|
return;
|
|
|
|
auto ident = internalZone->getLandIdent();
|
|
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
if( !hZone )
|
|
return;
|
|
|
|
targetLand = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
}
|
|
else
|
|
{
|
|
auto zone = std::dynamic_pointer_cast< HousingZone >( pZone );
|
|
if( !zone )
|
|
return;
|
|
|
|
targetLand = zone->getLand( plotNum );
|
|
}
|
|
|
|
if( !targetLand )
|
|
return;
|
|
|
|
if( !hasPermission( player, *targetLand, 0 ) )
|
|
return;
|
|
|
|
auto& containers = getEstateInventory( targetLand->getLandIdent() );
|
|
auto it = containers.find( inventoryType );
|
|
if( it == containers.end() )
|
|
return;
|
|
|
|
auto& invMgr = Common::Service< Manager::InventoryMgr >::ref();
|
|
invMgr.sendInventoryContainer( player, it->second );
|
|
}
|
|
|
|
const HousingMgr::LandSetLandCacheMap& HousingMgr::getLandCacheMap()
|
|
{
|
|
return m_landCache;
|
|
}
|
|
|
|
HousingMgr::LandIdentToInventoryContainerMap& HousingMgr::getEstateInventories()
|
|
{
|
|
return m_estateInventories;
|
|
}
|
|
|
|
HousingMgr::ContainerIdToContainerMap& HousingMgr::getEstateInventory( uint64_t ident )
|
|
{
|
|
return m_estateInventories[ ident ];
|
|
}
|
|
|
|
HousingMgr::ContainerIdToContainerMap& HousingMgr::getEstateInventory( Sapphire::Common::LandIdent ident )
|
|
{
|
|
auto u64ident = *reinterpret_cast< uint64_t* >( &ident );
|
|
|
|
return getEstateInventory( u64ident );
|
|
}
|
|
|
|
void HousingMgr::updateHouseModels( HousePtr house )
|
|
{
|
|
assert( house );
|
|
|
|
auto& containers = getEstateInventory( house->getLandIdent() );
|
|
|
|
auto extContainer = containers.find( static_cast< uint16_t >( Common::InventoryType::HousingExteriorAppearance ) );
|
|
if( extContainer != containers.end() )
|
|
{
|
|
for( auto& item : extContainer->second->getItemMap() )
|
|
{
|
|
// in the Slot array, the first slot is actually the permit
|
|
// but the models array starts from the 2nd entry of the enum
|
|
// so we skip the first one, and then any subsequent entries is slotid - 1
|
|
|
|
auto slotId = item.first - 1;
|
|
if( slotId < 0 )
|
|
continue;
|
|
|
|
house->setExteriorModel( static_cast< Common::HouseExteriorSlot >( slotId ),
|
|
getItemAdditionalData( item.second->getId() ), item.second->getStain() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::error( "Plot {0} has an invalid inventory configuration for outdoor appearance.", house->getLandIdent().landId );
|
|
}
|
|
|
|
auto intContainer = containers.find( static_cast< uint16_t >( Common::InventoryType::HousingInteriorAppearance ) );
|
|
if( intContainer != containers.end() )
|
|
{
|
|
for( auto& item : intContainer->second->getItemMap() )
|
|
{
|
|
house->setInteriorModel( static_cast< Common::HouseInteriorSlot >( item.first ), getItemAdditionalData( item.second->getId() ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::error( "Plot {0} has an invalid inventory configuration for indoor appearance.", house->getLandIdent().landId );
|
|
}
|
|
}
|
|
|
|
uint32_t HousingMgr::getItemAdditionalData( uint32_t catalogId )
|
|
{
|
|
auto& pExdData = Common::Service< Data::ExdData >::ref();
|
|
auto info = pExdData.getRow< Excel::Item >( catalogId );
|
|
return info->data().CategoryArg;
|
|
}
|
|
|
|
bool HousingMgr::isPlacedItemsInventory( Sapphire::Common::InventoryType type )
|
|
{
|
|
return type == Common::InventoryType::HousingExteriorPlacedItems ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems1 ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems2 ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems3 ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems4 ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems5 ||
|
|
type == Common::InventoryType::HousingInteriorPlacedItems6;
|
|
}
|
|
|
|
void HousingMgr::reqPlaceHousingItem( Entity::Player& player, uint16_t landId, uint16_t containerId, uint8_t slotId,
|
|
Common::FFXIVARR_POSITION3 pos, float rotation )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
// retail process is:
|
|
// - unlink item from current container
|
|
// - add it to destination container
|
|
// - resend container
|
|
// - send spawn packet
|
|
// - send actrl 3f3, all params are 0
|
|
|
|
LandPtr land;
|
|
bool isOutside = false;
|
|
|
|
// inside housing territory
|
|
if( auto zone = std::dynamic_pointer_cast< HousingZone >( pZone ) )
|
|
{
|
|
land = zone->getLand( static_cast< uint8_t >( landId ) );
|
|
|
|
isOutside = true;
|
|
}
|
|
// otherwise, inside a house. landId is 0 when inside a plot
|
|
else if( auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone ) )
|
|
{
|
|
// todo: this whole process is retarded and needs to be fixed
|
|
// perhaps maintain a list of estates by ident inside housingmgr?
|
|
auto ident = zone->getLandIdent();
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
}
|
|
// wtf?
|
|
else
|
|
return;
|
|
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
// todo: check item position and make sure it's not outside the plot
|
|
// anecdotal evidence on reddit seems to imply retail uses a radius based check
|
|
|
|
// unlink item
|
|
Inventory::HousingItemPtr item;
|
|
|
|
if( containerId == Common::InventoryType::Bag0 ||
|
|
containerId == Common::InventoryType::Bag1 ||
|
|
containerId == Common::InventoryType::Bag2 ||
|
|
containerId == Common::InventoryType::Bag3 )
|
|
{
|
|
item = getHousingItemFromPlayer( player, static_cast< Common::InventoryType >( containerId ), slotId );
|
|
if( !item )
|
|
return;
|
|
|
|
// set params
|
|
item->setPos( pos );
|
|
item->setRot( rotation );
|
|
}
|
|
else
|
|
{
|
|
PlayerMgr::sendUrgent( player, "The inventory you are using to place an item is not supported." );
|
|
return;
|
|
}
|
|
|
|
auto ident = land->getLandIdent();
|
|
|
|
bool status = false;
|
|
|
|
if( isOutside )
|
|
status = placeExternalItem( player, item, ident );
|
|
else
|
|
status = placeInteriorItem( player, item );
|
|
|
|
if( status )
|
|
server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), 0x3f3 ) );
|
|
else
|
|
PlayerMgr::sendUrgent( player, "An internal error occurred when placing the item." );
|
|
}
|
|
|
|
void HousingMgr::reqPlaceItemInStore( Entity::Player& player, uint16_t landId, uint16_t containerId, uint8_t slotId )
|
|
{
|
|
LandPtr land;
|
|
bool isOutside = false;
|
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
if( auto zone = std::dynamic_pointer_cast< HousingZone >( pZone ) )
|
|
{
|
|
land = zone->getLand( static_cast< uint8_t >( landId ) );
|
|
isOutside = true;
|
|
}
|
|
else if( auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone ) )
|
|
{
|
|
// todo: this whole process is retarded and needs to be fixed
|
|
// perhaps maintain a list of estates by ident inside housingmgr?
|
|
auto ident = zone->getLandIdent();
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
}
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
auto ident = land->getLandIdent();
|
|
auto& containers = getEstateInventory( ident );
|
|
|
|
if( isOutside )
|
|
{
|
|
auto& container = containers[ Common::InventoryType::HousingExteriorStoreroom ];
|
|
|
|
auto freeSlot = container->getFreeSlot();
|
|
if( freeSlot == -1 )
|
|
return;
|
|
|
|
auto item = getHousingItemFromPlayer( player, static_cast< Common::InventoryType >( containerId ), slotId );
|
|
if( !item )
|
|
return;
|
|
|
|
container->setItem( static_cast< uint8_t >( freeSlot ), item );
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.saveHousingContainer( ident, container );
|
|
}
|
|
else
|
|
{
|
|
for( auto houseContainer : m_internalStoreroomContainers )
|
|
{
|
|
auto it = containers.find( houseContainer );
|
|
if( it == containers.end() )
|
|
continue;
|
|
|
|
auto container = it->second;
|
|
auto freeSlot = container->getFreeSlot();
|
|
if( freeSlot == -1 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto item = getHousingItemFromPlayer( player, static_cast< Common::InventoryType >( containerId ), slotId );
|
|
if( !item )
|
|
return;
|
|
|
|
container->setItem( static_cast< uint8_t >( freeSlot ), item );
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.saveHousingContainer( ident, container );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HousingMgr::placeExternalItem( Entity::Player& player, Inventory::HousingItemPtr item, Common::LandIdent ident )
|
|
{
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
auto& container = getEstateInventory( ident )[ Common::InventoryType::HousingExteriorPlacedItems ];
|
|
|
|
auto freeSlot = container->getFreeSlot();
|
|
|
|
// todo: what happens when this fails? at the moment the player will just lose the item
|
|
if( freeSlot == -1 )
|
|
return false;
|
|
|
|
// add item to inv
|
|
container->setItem( static_cast< uint8_t >( freeSlot ), item );
|
|
|
|
// we need to save the item again as removing it from the container on the player will remove it from charaglobalitem
|
|
// todo: this needs to be handled a bit better as it might be possible to overwrite another item that is created in the meantime
|
|
invMgr.saveItem( player, item );
|
|
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.saveHousingContainer( ident, container );
|
|
invMgr.updateHousingItemPosition( item );
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
// add to zone and spawn
|
|
auto zone = std::dynamic_pointer_cast< HousingZone >( pZone );
|
|
assert( zone );
|
|
|
|
zone->spawnYardObject( static_cast< uint8_t >( ident.landId ), static_cast< uint16_t >( freeSlot ), *item );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HousingMgr::placeInteriorItem( Entity::Player& player, Inventory::HousingItemPtr item )
|
|
{
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone );
|
|
assert( zone );
|
|
|
|
auto ident = zone->getLandIdent();
|
|
|
|
auto& containers = getEstateInventory( ident );
|
|
|
|
// find first free container
|
|
uint8_t containerIdx = 0;
|
|
for( auto containerId : m_internalPlacedItemContainers )
|
|
{
|
|
auto it = containers.find( containerId );
|
|
if( it == containers.end() )
|
|
continue;
|
|
|
|
auto container = it->second;
|
|
auto freeSlot = container->getFreeSlot();
|
|
if( freeSlot == -1 )
|
|
{
|
|
containerIdx++;
|
|
continue;
|
|
}
|
|
|
|
// have a free slot
|
|
container->setItem( static_cast< uint8_t >( freeSlot ), item );
|
|
|
|
// resend container
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.saveHousingContainer( ident, container );
|
|
invMgr.updateHousingItemPosition( item );
|
|
|
|
zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone );
|
|
assert( zone );
|
|
|
|
zone->spawnHousingObject( containerIdx, static_cast< uint16_t >( freeSlot ), containerId, item );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Common::Furniture HousingMgr::getYardObjectForItem( Inventory::HousingItemPtr item ) const
|
|
{
|
|
Common::Furniture obj {};
|
|
|
|
obj.pos[ 0 ] = Common::Util::floatToUInt16( item->getPos().x );
|
|
obj.pos[ 1 ] = Common::Util::floatToUInt16( item->getPos().y );
|
|
obj.pos[ 2 ] = Common::Util::floatToUInt16( item->getPos().z );
|
|
obj.dir = Common::Util::floatToUInt16Rot( item->getRot() );
|
|
obj.patternId = item->getAdditionalData();
|
|
|
|
return obj;
|
|
}
|
|
|
|
void HousingMgr::sendInternalEstateInventoryBatch( Entity::Player& player, bool storeroom )
|
|
{
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
auto zone = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone );
|
|
if( !zone )
|
|
return;
|
|
|
|
// todo: perms check
|
|
|
|
Inventory::InventoryTypeList containerIds;
|
|
|
|
if( storeroom )
|
|
containerIds = m_internalStoreroomContainers;
|
|
else
|
|
containerIds = m_internalPlacedItemContainers;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
auto& containers = getEstateInventory( zone->getLandIdent() );
|
|
|
|
for( auto containerId : containerIds )
|
|
{
|
|
auto container = containers.find( containerId );
|
|
if( container == containers.end() )
|
|
break;
|
|
|
|
invMgr.sendInventoryContainer( player, container->second );
|
|
}
|
|
}
|
|
|
|
void HousingMgr::reqMoveHousingItem( Entity::Player& player, Common::LandIdent ident, uint8_t slot, Common::FFXIVARR_POSITION3 pos, float rot )
|
|
{
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
// todo: what happens when either of these fail? how does the server let the client know that the moment failed
|
|
// as is, if it does fail, the client will be locked and unable to move any item until reentering the territory
|
|
if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone ) )
|
|
{
|
|
moveInternalItem( player, ident, *terri, slot, pos, rot );
|
|
}
|
|
else if( auto terri = std::dynamic_pointer_cast< HousingZone >( pZone ) )
|
|
{
|
|
moveExternalItem( player, ident, slot, *terri, pos, rot );
|
|
}
|
|
}
|
|
|
|
bool HousingMgr::moveInternalItem( Entity::Player& player, Common::LandIdent ident, Territory::Housing::HousingInteriorTerritory& terri, uint8_t slot,
|
|
Common::FFXIVARR_POSITION3 pos, float rot )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto containerIdx = static_cast< uint16_t >( slot / 50 );
|
|
auto slotIdx = slot % 50;
|
|
|
|
uint16_t containerId = 0;
|
|
try
|
|
{
|
|
containerId = m_internalPlacedItemContainers.at( containerIdx );
|
|
}
|
|
catch( const std::out_of_range& )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto& containers = getEstateInventory( ident );
|
|
|
|
auto it = containers.find( containerId );
|
|
if( it == containers.end() )
|
|
return false;
|
|
|
|
auto container = it->second;
|
|
|
|
auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( static_cast< uint8_t >( slotIdx ) ) );
|
|
if( !item )
|
|
return false;
|
|
|
|
item->setPos( pos );
|
|
item->setRot( rot );
|
|
|
|
// save
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
invMgr.updateHousingItemPosition( item );
|
|
|
|
terri.updateHousingObjectPosition( player, slot, item->getPos(), static_cast< uint16_t >( item->getRot() ) );
|
|
|
|
// send confirmation to player
|
|
uint32_t param1 = static_cast< uint32_t >( ( ident.landId << 16 ) | containerId );
|
|
|
|
server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), ActorControl::HousingItemMoveConfirm, param1, slotIdx ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HousingMgr::moveExternalItem( Entity::Player& player, Common::LandIdent ident, uint8_t slot, HousingZone& terri, Common::FFXIVARR_POSITION3 pos, float rot )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto land = terri.getLand( static_cast< uint8_t >( ident.landId ) );
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return false;
|
|
|
|
auto& containers = getEstateInventory( ident );
|
|
auto it = containers.find( Common::InventoryType::HousingExteriorPlacedItems );
|
|
if( it == containers.end() )
|
|
return false;
|
|
|
|
auto container = it->second;
|
|
|
|
auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( slot ) );
|
|
if( !item )
|
|
return false;
|
|
|
|
item->setPos( pos );
|
|
item->setRot( rot );
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
invMgr.updateHousingItemPosition( item );
|
|
|
|
terri.updateYardObjectPos( player, slot, static_cast< uint16_t >( ident.landId ), *item );
|
|
|
|
uint32_t param1 = static_cast< uint32_t >( ( ident.landId << 16 ) | Common::InventoryType::HousingExteriorPlacedItems );
|
|
server.queueForPlayer( player.getCharacterId(), makeActorControlSelf( player.getId(), ActorControl::HousingItemMoveConfirm, param1, slot ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
void HousingMgr::reqRemoveHousingItem( Entity::Player& player, uint16_t plot, uint16_t containerId, uint8_t slot, bool sendToStoreroom )
|
|
{
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone ) )
|
|
{
|
|
auto ident = terri->getLandIdent();
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
removeInternalItem( player, *terri, containerId, slot, sendToStoreroom );
|
|
}
|
|
else if( auto terri = std::dynamic_pointer_cast< HousingZone >( pZone ) )
|
|
{
|
|
auto land = terri->getLand( static_cast< uint8_t >( plot ) );
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto containerType = static_cast< Common::InventoryType >( containerId );
|
|
|
|
removeExternalItem( player, *terri, *land, containerType, slot, sendToStoreroom );
|
|
}
|
|
}
|
|
|
|
bool HousingMgr::removeInternalItem( Entity::Player& player, Territory::Housing::HousingInteriorTerritory& terri, uint16_t containerId, uint16_t slotId,
|
|
bool sendToStoreroom )
|
|
{
|
|
auto& containers = getEstateInventory( terri.getLandIdent() );
|
|
|
|
int8_t containerIdx = 0;
|
|
|
|
if( isPlacedItemsInventory( static_cast< Common::InventoryType >( containerId ) ) )
|
|
{
|
|
for( auto cId : m_internalPlacedItemContainers )
|
|
{
|
|
if( containerId == cId )
|
|
break;
|
|
|
|
containerIdx++;
|
|
}
|
|
}
|
|
else
|
|
containerIdx = -1;
|
|
|
|
// its possible to remove an item from any container in basically all these remove functions
|
|
// eg, remove a permit and reuse it elsewhere
|
|
// I'm not going to bother fixing it for now, but worth noting for future reference
|
|
|
|
auto it = containers.find( containerId );
|
|
if( it == containers.end() )
|
|
return false;
|
|
|
|
auto container = it->second;
|
|
|
|
auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( container->getItem( static_cast< uint8_t >( slotId ) ) );
|
|
if( !item )
|
|
return false;
|
|
|
|
if( !sendToStoreroom )
|
|
{
|
|
// make sure the player has a free inv slot first
|
|
Inventory::InventoryContainerPair containerPair;
|
|
if( !player.getFreeInventoryContainerSlot( containerPair ) )
|
|
return false;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
// remove it from housing inventory
|
|
container->removeItem( static_cast< uint8_t >( slotId ) );
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.removeHousingItemPosition( *item );
|
|
invMgr.removeItemFromHousingContainer( terri.getLandIdent(), containerId, slotId );
|
|
|
|
// add to player inv
|
|
player.insertInventoryItem( containerPair.first, containerPair.second, item );
|
|
|
|
// todo: set item as bound/unsellable/untradable
|
|
}
|
|
else
|
|
{
|
|
ItemContainerPtr freeContainer;
|
|
Inventory::InventoryContainerPair freeSlotPair;
|
|
freeContainer = getFreeEstateInventorySlot( terri.getLandIdent(), freeSlotPair, m_internalStoreroomContainers );
|
|
|
|
if( !freeContainer )
|
|
return false;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
container->removeItem( static_cast< uint8_t >( slotId ) );
|
|
invMgr.sendInventoryContainer( player, container );
|
|
invMgr.removeHousingItemPosition( *item );
|
|
invMgr.removeItemFromHousingContainer( terri.getLandIdent(), containerId, slotId );
|
|
|
|
freeContainer->setItem( static_cast< uint8_t >( slotId ), item );
|
|
invMgr.sendInventoryContainer( player, freeContainer );
|
|
invMgr.saveHousingContainer( terri.getLandIdent(), freeContainer );
|
|
}
|
|
|
|
// despawn
|
|
if( containerIdx != -1 )
|
|
{
|
|
auto arraySlot = ( containerIdx * 50 ) + slotId;
|
|
terri.removeHousingObject( static_cast< uint16_t >( arraySlot ) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HousingMgr::removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land, Common::InventoryType containerType, uint8_t slotId,
|
|
bool sendToStoreroom )
|
|
{
|
|
auto& containers = getEstateInventory( land.getLandIdent() );
|
|
|
|
auto it = containers.find( containerType );
|
|
if( it == containers.end() )
|
|
return false;
|
|
|
|
auto& sourceContainer = it->second;
|
|
|
|
auto item = std::dynamic_pointer_cast< Inventory::HousingItem >( sourceContainer->getItem( slotId ) );
|
|
if( !item )
|
|
return false;
|
|
|
|
bool shouldDespawnItem = containerType != Common::InventoryType::HousingExteriorStoreroom;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
if( sendToStoreroom )
|
|
{
|
|
auto& storeroomContainer = containers[ containerType ];
|
|
auto freeSlot = storeroomContainer->getFreeSlot();
|
|
|
|
if( freeSlot == -1 )
|
|
return false;
|
|
|
|
sourceContainer->removeItem( slotId );
|
|
invMgr.sendInventoryContainer( player, sourceContainer );
|
|
invMgr.removeItemFromHousingContainer( land.getLandIdent(), sourceContainer->getId(), slotId );
|
|
invMgr.removeHousingItemPosition( *item );
|
|
|
|
storeroomContainer->setItem( static_cast< uint8_t >( freeSlot ), item );
|
|
invMgr.sendInventoryContainer( player, storeroomContainer );
|
|
invMgr.saveHousingContainer( land.getLandIdent(), storeroomContainer );
|
|
}
|
|
else
|
|
{
|
|
Inventory::InventoryContainerPair containerPair;
|
|
if( !player.getFreeInventoryContainerSlot( containerPair ) )
|
|
return false;
|
|
|
|
// remove from housing inv
|
|
sourceContainer->removeItem( slotId );
|
|
invMgr.sendInventoryContainer( player, sourceContainer );
|
|
invMgr.removeHousingItemPosition( *item );
|
|
invMgr.removeItemFromHousingContainer( land.getLandIdent(), sourceContainer->getId(), slotId );
|
|
|
|
// add to player inv
|
|
player.insertInventoryItem( containerPair.first, containerPair.second, item );
|
|
}
|
|
|
|
if( shouldDespawnItem )
|
|
terri.despawnYardObject( static_cast< uint16_t >( land.getLandIdent().landId ), slotId );
|
|
|
|
return true;
|
|
}
|
|
|
|
ItemContainerPtr HousingMgr::getFreeEstateInventorySlot( Common::LandIdent ident, Inventory::InventoryContainerPair& pair, Inventory::InventoryTypeList bagList )
|
|
{
|
|
auto& estateContainers = getEstateInventory( ident );
|
|
|
|
for( auto bag : bagList )
|
|
{
|
|
auto it = estateContainers.find( bag );
|
|
if( it == estateContainers.end() )
|
|
continue;
|
|
|
|
auto container = it->second;
|
|
|
|
auto freeSlot = container->getFreeSlot();
|
|
|
|
if( freeSlot == -1 )
|
|
continue;
|
|
|
|
pair = std::make_pair( bag, freeSlot );
|
|
return container;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void HousingMgr::reqEstateExteriorRemodel( Entity::Player& player, uint16_t plot )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
auto terri = std::dynamic_pointer_cast< HousingZone >( pZone );
|
|
if( !terri )
|
|
return;
|
|
|
|
auto land = terri->getLand( static_cast< uint8_t >( plot ) );
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto& inv = getEstateInventory( land->getLandIdent() );
|
|
auto it = inv.find( Common::InventoryType::HousingExteriorAppearance );
|
|
if( it == inv.end() )
|
|
return;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
invMgr.sendInventoryContainer( player, it->second );
|
|
|
|
auto pkt = makeActorControlSelf( player.getId(), Network::ActorControl::ShowEstateExternalAppearanceUI, plot );
|
|
server.queueForPlayer( player.getCharacterId(), pkt );
|
|
|
|
}
|
|
|
|
void HousingMgr::reqEstateInteriorRemodel( Entity::Player& player )
|
|
{
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( pZone );
|
|
if( !terri )
|
|
return;
|
|
|
|
auto ident = terri->getLandIdent();
|
|
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
|
|
|
auto pTeri = teriMgr.getTerritoryByGuId( landSetId );
|
|
auto hZone = std::dynamic_pointer_cast< HousingZone >( pTeri );
|
|
|
|
auto land = hZone->getLand( static_cast< uint8_t >( ident.landId ) );
|
|
|
|
if( !land )
|
|
return;
|
|
|
|
if( !hasPermission( player, *land, 0 ) )
|
|
return;
|
|
|
|
auto& inv = getEstateInventory( land->getLandIdent() );
|
|
auto it = inv.find( Common::InventoryType::HousingInteriorAppearance );
|
|
if( it == inv.end() )
|
|
return;
|
|
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
invMgr.sendInventoryContainer( player, it->second );
|
|
|
|
auto pkt = makeActorControlSelf( player.getId(), Network::ActorControl::ShowEstateInternalAppearanceUI );
|
|
server.queueForPlayer( player.getCharacterId(), pkt );
|
|
}
|
|
|
|
bool HousingMgr::hasPermission( Entity::Player& player, Sapphire::Land& land, uint32_t permission )
|
|
{
|
|
// todo: proper perms checks pls
|
|
if( land.getOwnerId() == player.getCharacterId() )
|
|
return true;
|
|
|
|
// todo: check perms here
|
|
|
|
return false;
|
|
}
|
|
|
|
Inventory::HousingItemPtr HousingMgr::getHousingItemFromPlayer( Entity::Player& player, Common::InventoryType type, uint8_t slot )
|
|
{
|
|
auto tmpItem = player.dropInventoryItem( type, slot );
|
|
if( !tmpItem )
|
|
return nullptr;
|
|
|
|
return Inventory::make_HousingItem( tmpItem->getUId(), tmpItem->getId() );
|
|
}
|
|
|
|
void HousingMgr::removeHouse( Entity::Player& player, uint16_t plot )
|
|
{
|
|
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
if( !pZone )
|
|
return;
|
|
auto terri = std::dynamic_pointer_cast< HousingZone >( pZone );
|
|
if( !terri )
|
|
return;
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto pSession = server.getSession( player.getCharacterId() );
|
|
|
|
auto pLand = terri->getLand( plot );
|
|
if( !pLand )
|
|
return;
|
|
|
|
// can't remove when you are not the owner
|
|
// TODO: actually use permissions here for FC houses
|
|
if( !hasPermission( player, *pLand, 0 ) )
|
|
{
|
|
auto msgPkt = makeActorControlSelf( player.getId(), ActorControl::LogMsg, 3305, 0 );
|
|
server.queueForPlayer( player.getCharacterId(), msgPkt );
|
|
return;
|
|
}
|
|
|
|
auto land = terri->getLand( static_cast< uint8_t >( plot ) );
|
|
if( !land || !land->getHouse() )
|
|
return;
|
|
|
|
auto& interiorContainer = getEstateInventory( land->getLandIdent() )[ Common::InventoryType::HousingInteriorAppearance ];
|
|
auto& invMgr = Common::Service< InventoryMgr >::ref();
|
|
|
|
std::unordered_map< Common::InventoryType, ItemContainerPtr > changedContainerSet = {};
|
|
|
|
for( int i = 0; i < interiorContainer->getMaxSize(); i++ )
|
|
{
|
|
auto item = interiorContainer->getItem( i );
|
|
if( !item )
|
|
continue;
|
|
|
|
Inventory::InventoryContainerPair freeSlotPair;
|
|
auto freeContainer = getFreeEstateInventorySlot( land->getLandIdent(), freeSlotPair, m_internalStoreroomContainers );
|
|
if ( !freeContainer )
|
|
{
|
|
// not sure what to do
|
|
interiorContainer->removeItem( i, true );
|
|
}
|
|
else
|
|
{
|
|
interiorContainer->removeItem( i, false );
|
|
freeContainer->setItem( freeSlotPair.second , item );
|
|
changedContainerSet[ freeSlotPair.first ] = freeContainer;
|
|
}
|
|
}
|
|
|
|
invMgr.sendInventoryContainer( player, interiorContainer );
|
|
invMgr.saveHousingContainer( land->getLandIdent(), interiorContainer );
|
|
for( auto& entry : changedContainerSet )
|
|
{
|
|
invMgr.sendInventoryContainer( player, entry.second );
|
|
invMgr.saveHousingContainer( land->getLandIdent(), entry.second );
|
|
}
|
|
|
|
deleteHouse( land->getHouse() );
|
|
land->setHouse( nullptr );
|
|
|
|
land->setStatus( Common::HouseStatus::Sold );
|
|
land->updateLandDb();
|
|
terri->sendLandUpdate( plot );
|
|
|
|
player.setLandFlags( Common::LandFlagsSlot::Private, Common::HOUSING_LAND_STATUS::HOUSING_LAND_STATUS_NOINIT, land->getLandIdent() );
|
|
sendLandFlagsSlot( player, Common::LandFlagsSlot::Private );
|
|
|
|
terri->removeEstateEntranceEObj( plot );
|
|
return;
|
|
}
|
|
|
|
void HousingMgr::sendLandFlagsSlot( Entity::Player& player, Common::LandFlagsSlot slot )
|
|
{
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
auto landFlags = makeZonePacket< FFXIVIpcCharaHousingLandData >( player.getId() );
|
|
|
|
Common::LandType type;
|
|
|
|
switch( slot )
|
|
{
|
|
case Common::LandFlagsSlot::Private:
|
|
type = Common::LandType::Private;
|
|
break;
|
|
|
|
case Common::LandFlagsSlot::FreeCompany:
|
|
type = Common::LandType::FreeCompany;
|
|
break;
|
|
|
|
default:
|
|
// todo: other/unsupported land types
|
|
return;
|
|
}
|
|
|
|
auto landData = player.getCharaLandData( slot );
|
|
landFlags->data().Index = static_cast< uint32_t >( type );
|
|
landFlags->data().LandData.landId = landData.landId;
|
|
landFlags->data().LandData.landFlags = landData.landFlags;
|
|
|
|
server.queueForPlayer( player.getCharacterId(), landFlags );
|
|
}
|
|
|
|
void HousingMgr::sendLandFlags( Entity::Player& player )
|
|
{
|
|
auto landFlags = makeZonePacket< FFXIVIpcCharaHousing >( player.getId() );
|
|
|
|
landFlags->data().FcLands = player.getCharaLandData( Common::LandFlagsSlot::FreeCompany );
|
|
landFlags->data().CharaLands = player.getCharaLandData( Common::LandFlagsSlot::Private );
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
server.queueForPlayer( player.getCharacterId(), landFlags );
|
|
} |