1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 14:37:44 +00:00
sapphire/src/servers/sapphire_zone/Manager/HousingMgr.cpp

507 lines
16 KiB
C++
Raw Normal View History

2018-11-10 19:00:13 +01:00
#include "HousingMgr.h"
#include <Logging/Logger.h>
#include <Database/DatabaseDef.h>
#include <Exd/ExdDataGenerated.h>
#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>
#include <cstring>
2018-11-10 19:00:13 +01:00
#include "Actor/Player.h"
#include "Actor/EventObject.h"
2018-11-10 19:00:13 +01:00
2018-11-19 11:55:29 +01:00
#include "TerritoryMgr.h"
#include "Territory/Zone.h"
#include "Territory/HousingZone.h"
#include "Territory/Housing/HousingInteriorTerritory.h"
2018-11-10 19:00:13 +01:00
#include "HousingMgr.h"
#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"
#include "Territory/House.h"
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
extern Sapphire::Framework g_fw;
2018-11-10 19:00:13 +01:00
Sapphire::World::Manager::HousingMgr::HousingMgr()
2018-11-10 19:00:13 +01:00
{
}
Sapphire::World::Manager::HousingMgr::~HousingMgr()
2018-11-10 19:00:13 +01:00
{
}
bool Sapphire::World::Manager::HousingMgr::init()
2018-11-10 19:00:13 +01:00
{
return true;
}
uint32_t Sapphire::World::Manager::HousingMgr::toLandSetId( uint16_t territoryTypeId, uint8_t wardId ) const
{
return ( static_cast< uint32_t >( territoryTypeId ) << 16 ) | wardId;
}
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
}
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-12-05 16:55:14 +11:00
void Sapphire::World::Manager::HousingMgr::sendLandSignOwned( Entity::Player& player, const Common::LandIdent ident )
{
2018-12-05 16:55:14 +11:00
player.setActiveLand( ident.landId, ident.wardNum );
2018-12-05 16:55:14 +11:00
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
auto hZone = getHousingZoneByLandSetId( landSetId );
if( !hZone )
return;
2018-12-05 16:55:14 +11:00
auto land = hZone->getLand( ident.landId );
if( !land )
{
land = getLandByOwnerId( player.getId() );
}
auto landInfoSignPacket = makeZonePacket< Server::FFXIVIpcLandInfoSign >( player.getId() );
uint32_t playerId = land->getPlayerOwner();
std::string playerName = g_fw.get< Sapphire::ServerMgr >()->getPlayerNameFromDb( playerId );
//memcpy( &landInfoSignPacket->data().estateGreeting, "Hello World", 11 );
//memcpy( &landInfoSignPacket->data().estateName, land->getLandName().c_str(), land->getLandName().size() );
2018-11-14 10:00:16 +01:00
landInfoSignPacket->data().houseSize = land->getSize();
landInfoSignPacket->data().houseType = static_cast< uint8_t >( land->getLandType() );
2018-12-05 16:55:14 +11:00
landInfoSignPacket->data().landIdent = ident;
landInfoSignPacket->data().houseIconAdd = land->getSharing();
landInfoSignPacket->data().ownerId = player.getContentId(); // should be real owner contentId, not player.contentId()
if( auto house = land->getHouse() )
{
std::strcpy( landInfoSignPacket->data().estateName, house->getHouseName().c_str() );
std::strcpy( landInfoSignPacket->data().estateGreeting, house->getHouseGreeting().c_str() );
}
memcpy( &landInfoSignPacket->data().ownerName, playerName.c_str(), playerName.size() );
2018-11-26 23:15:42 +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-12-05 16:55:14 +11:00
player.setActiveLand( ident.landId, ident.wardNum );
2018-12-05 16:55:14 +11:00
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
auto hZone = getHousingZoneByLandSetId( landSetId );
if( !hZone )
return;
2018-12-05 16:55:14 +11:00
auto land = hZone->getLand( ident.landId );
auto plotPricePacket = makeZonePacket< Server::FFXIVIpcLandPriceUpdate >( player.getId() );
plotPricePacket->data().price = land->getCurrentPrice();
plotPricePacket->data().timeLeft = land->getDevaluationTime();
player.queuePacket( plotPricePacket );
}
Sapphire::LandPurchaseResult Sapphire::World::Manager::HousingMgr::purchaseLand( Entity::Player& player, uint8_t plot, uint8_t state )
{
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;
if( gilAvailable < plotPrice )
return LandPurchaseResult::ERR_NOT_ENOUGH_GIL;
switch( static_cast< LandPurchaseMode >( state ) )
{
case LandPurchaseMode::FC:
player.sendDebug( "Free company house purchase aren't supported at this time." );
return LandPurchaseResult::ERR_INTERNAL;
case LandPurchaseMode::PRIVATE:
{
auto pOldLand = getLandByOwnerId( player.getId() );
if( pOldLand )
return LandPurchaseResult::ERR_NO_MORE_LANDS_FOR_CHAR;
player.removeCurrency( CurrencyType::Gil, plotPrice );
pLand->setPlayerOwner( player.getId() );
pLand->setState( HouseState::sold );
pLand->setLandType( Common::LandType::Private );
player.setLandFlags( LandFlagsSlot::Private, 0x00, plot,
pHousing->getWardNum(), pHousing->getTerritoryTypeId() );
player.sendLandFlagsSlot( LandFlagsSlot::Private );
//pLand->setLandName( "Private Estate" + std::to_string( pHousing->getWardNum() ) + "-" + std::to_string( plot ) );
pLand->updateLandDb();
pHousing->sendLandUpdate( plot );
return LandPurchaseResult::SUCCESS;
}
default:
return LandPurchaseResult::ERR_INTERNAL;
}
}
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-11-20 21:21:22 +01:00
auto landOwnerId = pLand->getPlayerOwner();
// 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() );
pLand->setPlayerOwner( 0 );
pLand->setState( HouseState::forSale );
pLand->setLandType( Common::LandType::none );
pLand->updateLandDb();
player.setLandFlags( LandFlagsSlot::Private, 0x00, 0xFF, 0xFF, 0xFF );
2018-11-17 01:16:44 +01:00
player.sendLandFlagsSlot( LandFlagsSlot::Private );
2018-11-17 01:16:44 +01:00
auto screenMsgPkt2 = makeActorControl143( player.getId(), ActorControl::LogMsg, 3351, 0x1AA,
pLand->getWardNum() + 1, plot + 1 );
player.queuePacket( screenMsgPkt2 );
pHousing->sendLandUpdate( plot );
return true;
}
void Sapphire::World::Manager::HousingMgr::sendWardLandInfo( Entity::Player& player, uint8_t wardId, uint16_t territoryTypeId )
{
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;
// todo: properly get worldId
wardInfoPacket->data().landIdent.worldId = 67;
for( int i = 0; i < 60; i++ )
{
auto land = hZone->getLand( i );
assert( land );
auto& entry = wardInfoPacket->data().houseInfoEntry[ 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.housePrice = land->getCurrentPrice();
if( land->getState() == Common::HouseState::forSale )
continue;
if( auto house = land->getHouse() )
{
if( !house->getHouseGreeting().empty() )
entry.infoFlags |= WardlandFlags::HasEstateGreeting;
}
switch( land->getLandType() )
{
case LandType::FreeCompany:
entry.infoFlags |= Common::WardlandFlags::IsEstateOwned | Common::WardlandFlags::IsFreeCompanyEstate;
// todo: send FC name
break;
case LandType::Private:
entry.infoFlags |= Common::WardlandFlags::IsEstateOwned;
auto owner = land->getPlayerOwner();
auto playerName = g_fw.get< Sapphire::ServerMgr >()->getPlayerNameFromDb( owner );
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 );
}
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 );
}
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
if( pLand->getPlayerOwner() != player.getId() )
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 );
// todo: wtf are these flags
player.playScene( 0x000B0095, 0, 4164955899, 0, 1, plotNum, nullptr );
player.setLandFlags( LandFlagsSlot::Private, EstateBuilt, pLand->getLandId(), pLand->getWardNum(), pLand->getTerritoryTypeId() );
player.sendLandFlagsSlot( LandFlagsSlot::Private );
auto eobj = hZone->registerEObj( "entrance", 2002737, 0, 4, pLand->getMapMarkerPosition(), 1.f, 0.f );
eobj->setHousingLink( plotNum << 8 );
2018-11-24 15:17:18 +11:00
}
2018-12-05 16:55:14 +11:00
void Sapphire::World::Manager::HousingMgr::requestEstateRename( Entity::Player& player, const Common::LandIdent ident )
{
2018-12-05 16:55:14 +11:00
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
auto hZone = getHousingZoneByLandSetId( landSetId );
if( !hZone )
return;
2018-12-05 16:55:14 +11:00
auto land = hZone->getLand( ident.landId );
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;
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-12-05 16:55:14 +11:00
auto landSetId = toLandSetId( ident.territoryTypeId, ident.wardNum );
auto hZone = getHousingZoneByLandSetId( landSetId );
if( !hZone )
return;
2018-12-05 16:55:14 +11:00
auto land = hZone->getLand( ident.landId );
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;
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
if( land->getPlayerOwner() != player.getId() )
return;
auto house = land->getHouse();
if( !house )
return;
house->setHouseGreeting( greeting );
// 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
if( land->getPlayerOwner() != player.getId() )
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
Sapphire::Common::LandIdent Sapphire::World::Manager::HousingMgr::clientTriggerParamsToLandIdent( uint32_t param11, uint32_t param12 ) const
{
Common::LandIdent ident;
ident.worldId = param11 >> 16;
ident.territoryTypeId = param11 & 0xFFFF;
ident.wardNum = param12 >> 16;
ident.landId = param12 & 0xFFFF;
return ident;
}
void Sapphire::World::Manager::HousingMgr::sendHousingInventory( Entity::Player& player, uint16_t inventoryType, uint8_t plotNum )
{
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
if( targetLand->getPlayerOwner() != player.getId() )
return;
player.sendDebug( "got inventory for plot: " + targetLand->getHouse()->getHouseName() );
2018-12-05 16:55:14 +11:00
}