#include #include #include #include #include #include #include #include #include #include #include #include "Actor/Player.h" #include "Actor/GameObject.h" #include "Actor/EventObject.h" #include "Land.h" #include "House.h" #include "Inventory/HousingItem.h" #include "Inventory/ItemContainer.h" #include "WorldServer.h" #include "Forwards.h" #include "HousingZone.h" #include "Manager/HousingMgr.h" using namespace Sapphire::Common; using namespace Sapphire::Network::Packets; using namespace Sapphire::Network::Packets::WorldPackets::Server; using namespace Sapphire::World::Manager; Sapphire::HousingZone::HousingZone( uint8_t wardNum, uint16_t territoryTypeId, const std::string& internalName, const std::string& contentName ) : Territory( territoryTypeId, ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardNum, internalName, contentName ), m_wardNum( wardNum ), m_landSetId( ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardNum ) { } bool Sapphire::HousingZone::init() { auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); { auto res = db.query( "SELECT * FROM landset WHERE landsetid = " + std::to_string( m_landSetId ) ); if( !res->next() ) { db.directExecute( "INSERT INTO landset ( landsetid ) VALUES ( " + std::to_string( m_landSetId ) + " );" ); } } int housingIndex; if( m_territoryTypeId == 339 ) housingIndex = 0; else if( m_territoryTypeId == 340 ) housingIndex = 1; else if( m_territoryTypeId == 341 ) housingIndex = 2; else if( m_territoryTypeId == 641 ) housingIndex = 3; auto& exdData = Common::Service< Data::ExdData >::ref(); auto info = exdData.getRow< Excel::HousingLandSet >( housingIndex ); // build yard objects array indices int16_t cursor = -1; uint16_t index = 0; for( const auto entry : info->data().Lands ) { uint16_t itemMax = 0; switch( entry.Size ) { case 0: itemMax = 20; break; case 1: itemMax = 30; break; case 2: itemMax = 40; break; } int16_t start = cursor + 1; int16_t end = cursor + itemMax; m_yardObjectArrayBounds[ index++ ] = std::make_pair( start, end ); // reset cursor for subdivision if( index == 30 ) { cursor = -1; continue; } cursor += itemMax; } // zero out the yard obj arrays so we don't leak memory like SE does :^) Common::Furniture obj {}; memset( &obj, 0x0, sizeof( Common::Furniture ) ); for( auto& arr : m_yardObjects ) { memcpy( &arr, &m_yardObjects, sizeof( Common::Furniture ) ); } auto& housingMgr = Common::Service< World::Manager::HousingMgr >::ref(); auto landCache = housingMgr.getLandCacheMap(); // make sure the landset exists auto landSetCache = landCache.find( m_landSetId ); if( landSetCache == landCache.end() ) { Logger::fatal( "LandSet {0} is missing from the land cache.", m_landSetId ); return false; } // init the lands for( HousingMgr::LandCacheEntry& entry : landSetCache->second ) { auto land = make_Land( m_territoryTypeId, getWardNum(), entry.m_landId, m_landSetId, info ); // setup house if( entry.m_houseId ) { auto house = make_House( entry.m_houseId, m_landSetId, land->getLandIdent(), entry.m_estateName, entry.m_estateComment ); housingMgr.updateHouseModels( house ); land->setHouse( house ); } land->init( entry.m_type, entry.m_size, entry.m_status, entry.m_currentPrice, entry.m_ownerId, entry.m_houseId ); m_landPtrMap[ entry.m_landId ] = land; // TODO: Fixme // if( entry.m_houseId > 0 ) // registerEstateEntranceEObj( entry.m_landId ); updateYardObjects( land->getLandIdent() ); } return true; } void Sapphire::HousingZone::onPlayerZoneIn( Entity::Player& player ) { auto& server = Common::Service< World::WorldServer >::ref(); Logger::debug( "HousingZone::onPlayerZoneIn: Territory#{0}|{1}, Entity#{2}", getGuId(), getTerritoryTypeId(), player.getId() ); uint32_t yardPacketNum; sendLandSet( player ); for( yardPacketNum = 1; yardPacketNum > 2; yardPacketNum++ ) { auto housingObjectInit = makeZonePacket< FFXIVIpcYardObjectList >( player.getId() ); housingObjectInit->data().PacketIndex = yardPacketNum; auto yardObjectSize = sizeof( Common::Furniture ); auto& objects = m_yardObjects[ 0 ]; memcpy( &housingObjectInit->data().YardObjects, &objects + ( yardPacketNum * 400 ), yardObjectSize * 400 ); server.queueForPlayer( player.getCharacterId(), housingObjectInit ); } } void Sapphire::HousingZone::sendLandSet( Entity::Player& player ) { auto& server = Common::Service< World::WorldServer >::ref(); auto landsetInitializePacket = makeZonePacket< FFXIVIpcHouseList >( player.getId() ); landsetInitializePacket->data().LandSetId.wardNum = m_wardNum; landsetInitializePacket->data().LandSetId.landId = m_landSetId; landsetInitializePacket->data().LandSetId.territoryTypeId = m_territoryTypeId; //TODO: get current WorldId landsetInitializePacket->data().LandSetId.worldId = server.getWorldId(); for( uint8_t i = 0, count = 0; i < 30; ++i, ++count ) { auto pLand = getLand( i ); // todo: move this and sendLandUpdate building logic to its own function auto& landData = landsetInitializePacket->data().Houses[ count ]; landData.size = pLand->getSize(); landData.status = pLand->getStatus(); landData.flags = pLand->getSharing(); /* //disbaled until we managed fc's landData.fcCrestId = pLand->getFcId(); landData.fcIcon = pLand->getFcIcon(); landData.fcIconColor = pLand->getFcColor(); */ if( auto house = pLand->getHouse() ) { landData.flags = 1; auto& parts = house->getHouseModels(); for( auto ii = 0; ii != parts.size(); ++ii ) { landData.patternIds[ ii ] = parts[ ii ].first; landData.colors[ ii ] = parts[ ii ].second; } } } server.queueForPlayer( player.getCharacterId(), landsetInitializePacket ); } void Sapphire::HousingZone::sendLandUpdate( uint8_t landId ) { auto& server = Common::Service< World::WorldServer >::ref(); auto pLand = getLand( landId ); for( const auto& playerIt : m_playerMap ) { auto pPlayer = playerIt.second; auto landUpdatePacket = makeZonePacket< FFXIVIpcHouse >( pPlayer->getId() ); landUpdatePacket->data().Block = landId; auto& landData = landUpdatePacket->data().House; landData.size = pLand->getSize(); landData.status = pLand->getStatus(); landData.flags = pLand->getSharing(); /* landData.fcId = pLand->getFcId(); landData.fcIcon = pLand->getFcIcon(); landData.fcIconColor = pLand->getFcColor(); */ if( auto house = pLand->getHouse() ) { landData.flags = 1; auto& parts = house->getHouseModels(); for( auto i = 0; i != parts.size(); ++i ) { landData.patternIds[ i ] = parts[ i ].first; landData.colors[ i ] = parts[ i ].second; } } server.queueForPlayer( pPlayer->getCharacterId(), landUpdatePacket ); } } void Sapphire::HousingZone::onUpdate( uint64_t tickCount ) { for( auto pLandItr : m_landPtrMap ) { pLandItr.second->update( tickCount ); } } uint8_t Sapphire::HousingZone::getWardNum() const { return m_wardNum; } uint32_t Sapphire::HousingZone::getLandSetId() const { return m_landSetId; } Sapphire::LandPtr Sapphire::HousingZone::getLand( uint8_t id ) { auto it = m_landPtrMap.find( id ); if( it == m_landPtrMap.end() ) return nullptr; return it->second; } Sapphire::Entity::EventObjectPtr Sapphire::HousingZone::registerEstateEntranceEObj( uint8_t landId ) { auto land = getLand( landId ); assert( land ); auto eObj = Entity::make_EventObject( getNextEObjId(), 2002737, 0, 0, 4, land->getMapMarkerPosition(), 0.f, "entrance", 0 ); eObj->setHousingLink( landId << 8 ); eObj->setScale( 1.f ); addEObj( eObj ); return eObj; } void Sapphire::HousingZone::updateYardObjects( Sapphire::Common::LandIdent ident ) { auto& housingMgr = Common::Service< World::Manager::HousingMgr >::ref(); auto& landStorage = housingMgr.getEstateInventory( ident ); auto yardContainer = landStorage[ InventoryType::HousingExteriorPlacedItems ]; auto arrayBounds = m_yardObjectArrayBounds[ ident.landId ]; for( const auto& item : yardContainer->getItemMap() ) { auto housingItem = std::dynamic_pointer_cast< Inventory::HousingItem >( item.second ); assert( housingItem ); auto idx = item.first + arrayBounds.first; m_yardObjects[ idx ] = housingMgr.getYardObjectForItem( housingItem ); } } void Sapphire::HousingZone::spawnYardObject( uint8_t landId, uint16_t slotId, Inventory::HousingItem& item ) { auto& server = Common::Service< World::WorldServer >::ref(); auto bounds = m_yardObjectArrayBounds[ landId ]; auto offset = bounds.first + slotId; Common::Furniture obj {}; obj.patternId = item.getAdditionalData(); obj.dir = Util::floatToUInt16Rot( item.getRot() ); obj.pos[ 0 ] = Util::floatToUInt16( item.getPos().x ); obj.pos[ 1 ] = Util::floatToUInt16( item.getPos().y ); obj.pos[ 2 ] = Util::floatToUInt16( item.getPos().z ); // link obj m_yardObjects[ offset ] = obj; // spawn obj in zone for( const auto& player : m_playerMap ) { auto packet = makeZonePacket< FFXIVIpcYardObject >( player.second->getId() ); packet->data().YardObject = obj; server.queueForPlayer( player.second->getCharacterId(), packet ); } } void Sapphire::HousingZone::updateYardObjectPos( Entity::Player& sourcePlayer, uint16_t slot, uint16_t landId, Inventory::HousingItem& item ) { auto& server = Common::Service< World::WorldServer >::ref(); auto bounds = m_yardObjectArrayBounds[ landId ]; auto offset = bounds.first + slot; auto& obj = m_yardObjects[ offset ]; obj.dir = Util::floatToUInt16( item.getRot() ); obj.pos[ 0 ] = Util::floatToUInt16( item.getPos().x ); obj.pos[ 1 ] = Util::floatToUInt16( item.getPos().y ); obj.pos[ 2 ] = Util::floatToUInt16( item.getPos().z ); for( const auto& player : m_playerMap ) { // don't send it from the origin player, it already has the position from the move request if( player.second->getId() == sourcePlayer.getId() ) continue; auto packet = makeZonePacket< FFXIVIpcHousingObjectTransform >( player.second->getId() ); packet->data().Dir = Util::floatToUInt16( item.getRot() ); packet->data().Pos[ 0 ] = Util::floatToUInt16( item.getPos().x ); packet->data().Pos[ 1 ] = Util::floatToUInt16( item.getPos().y ); packet->data().Pos[ 2 ] = Util::floatToUInt16( item.getPos().z ); packet->data().UserData1 = static_cast< uint8_t >( landId ); packet->data().UserData2 = static_cast< uint8_t >( slot ); server.queueForPlayer( player.second->getCharacterId(), packet ); } } void Sapphire::HousingZone::despawnYardObject( uint16_t landId, uint16_t slot ) { auto& server = Common::Service< World::WorldServer >::ref(); auto bounds = m_yardObjectArrayBounds[ landId ]; auto offset = bounds.first + slot; memset( &m_yardObjects[ offset ], 0x00, sizeof( Common::Furniture ) ); for( const auto& player : m_playerMap ) { auto param = ( landId << 16 ) | slot; auto pkt = makeActorControlSelf( player.second->getId(), Network::ActorControl::RemoveExteriorHousingItem, param ); server.queueForPlayer( player.second->getCharacterId(), pkt ); } }