1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-27 22:57:45 +00:00

Merge pull request #459 from NotAdam/housing

[wip] housing object placement!!!!
This commit is contained in:
Mordred 2018-12-29 01:12:07 +01:00 committed by GitHub
commit f030e759af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2102 additions and 292 deletions

View file

@ -404,9 +404,6 @@ CREATE TABLE `house` (
`Comment` binary(193) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`HouseName` binary(23) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`Endorsements` bigint(20) DEFAULT NULL,
`HousePartModels` binary(32) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`HousePartColours` binary(8) DEFAULT '\0\0\0\0\0\0\0\0',
`HouseInteriorModels` binary(40) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(`HouseId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -530,13 +527,15 @@ CREATE TABLE `landset` (
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `houseiteminventory` (
`LandIdent` BIGINT(20) UNSIGNED NOT NULL,
`ContainerId` INT(10) UNSIGNED NOT NULL,
`ItemId` INT(20) NOT NULL,
`SlotId` INT(10) UNSIGNED NOT NULL,
INDEX `landIdent` (`landIdent`)
`LandIdent` BIGINT(20) UNSIGNED NOT NULL,
`ContainerId` INT(10) UNSIGNED NOT NULL,
`SlotId` INT(10) UNSIGNED NOT NULL,
`ItemId` INT(20) NOT NULL,
PRIMARY KEY (`LandIdent`, `ContainerId`, `SlotId`),
INDEX `landIdent` (`LandIdent`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `spawngroup` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`territoryTypeId` int(5) NOT NULL,
@ -564,3 +563,14 @@ CREATE TABLE `zonepositions` (
`radius` int(11) NOT NULL DEFAULT '2',
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
CREATE TABLE `landplaceditems` (
`ItemId` INT(20) UNSIGNED NOT NULL,
`PosX` INT(10) NOT NULL,
`PosY` INT(10) NOT NULL,
`PosZ` INT(10) NOT NULL,
`Rotation` INT(10) NOT NULL,
PRIMARY KEY (`ItemId`)
)
COLLATE='latin1_swedish_ci' ENGINE=InnoDB;

View file

@ -409,6 +409,7 @@ void PlayerMinimal::insertDbGlobalItem( uint32_t itemId, uint64_t uniqueId ) con
stmtItemGlobal->setInt( 1, m_id );
stmtItemGlobal->setInt64( 2, uniqueId );
stmtItemGlobal->setInt( 3, itemId );
stmtItemGlobal->setInt( 4, 1 ); // stack of 1
g_charaDb.directExecute( stmtItemGlobal );
}

View file

@ -26,6 +26,13 @@ namespace Sapphire::Common
float z;
};
struct FFXIVARR_POSITION3_U16
{
uint16_t x;
uint16_t y;
uint16_t z;
};
struct ActiveLand
{
uint8_t ward;
@ -225,13 +232,15 @@ namespace Sapphire::Common
// housing interior containers
HousingInteriorAppearance = 25002,
// 50 in each container max, 300 slots max
// 50 in each container max, 400 slots max
HousingInteriorPlacedItems1 = 25003,
HousingInteriorPlacedItems2 = 25004,
HousingInteriorPlacedItems3 = 25005,
HousingInteriorPlacedItems4 = 25006,
HousingInteriorPlacedItems5 = 25007,
HousingInteriorPlacedItems6 = 25008,
HousingInteriorPlacedItems7 = 25009,
HousingInteriorPlacedItems8 = 25010,
// 50 max per container, 400 slots max
// slot limit increased 'temporarily' for relocation for all estates
@ -247,9 +256,9 @@ namespace Sapphire::Common
// housing exterior containers
HousingOutdoorPlacedItems = 25001,
HousingOutdoorAppearance = 25000,
HousingOutdoorStoreroom = 27000,
HousingExteriorAppearance = 25000,
HousingExteriorPlacedItems = 25001,
HousingExteriorStoreroom = 27000,
};
@ -769,8 +778,9 @@ namespace Sapphire::Common
MountSkill = 0xD,
};
enum HousePartSlot
enum HouseExteriorSlot
{
HousePermit,
ExteriorRoof,
ExteriorWall,
ExteriorWindow,
@ -842,13 +852,11 @@ namespace Sapphire::Common
uint32_t unkown1; //12
};
struct YardObject
struct HousingObject
{
uint32_t itemId;
uint16_t itemRotation;
uint16_t pos_x;
uint16_t pos_y;
uint16_t pos_z;
Common::FFXIVARR_POSITION3_U16 pos;
};
enum HouseSize : uint8_t

View file

@ -172,8 +172,8 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
/// ITEM GLOBAL
prepareStatement( CHARA_ITEMGLOBAL_INS,
"INSERT INTO charaglobalitem ( CharacterId, ItemId, catalogId, UPDATE_DATE ) VALUES ( ?, ?, ?, NOW() );",
CONNECTION_BOTH );
"INSERT INTO charaglobalitem ( CharacterId, ItemId, catalogId, stack, UPDATE_DATE ) VALUES ( ?, ?, ?, ?, NOW() );",
CONNECTION_SYNC );
/// BNPC TEMPLATES
prepareStatement( ZONE_SEL_BNPCTEMPLATES,
@ -193,7 +193,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
/// HOUSING
prepareStatement( HOUSING_HOUSE_INS,
"INSERT INTO house ( LandSetId, HouseId ) VALUES ( ?, ? );",
"INSERT INTO house ( LandSetId, HouseId, HouseName ) VALUES ( ?, ?, ? );",
CONNECTION_BOTH );
prepareStatement( HOUSING_HOUSE_UP,
@ -201,10 +201,13 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
CONNECTION_BOTH );
prepareStatement( LAND_INV_SEL_ALL,
"SELECT houseiteminventory.*, charaglobalitem.catalogId, charaglobalitem.stain, charaglobalitem.CharacterId "
"SELECT houseiteminventory.*, charaglobalitem.catalogId, charaglobalitem.stain, charaglobalitem.CharacterId, "
"landplaceditems.PosX, landplaceditems.PosY, landplaceditems.PosZ, landplaceditems.Rotation "
"FROM houseiteminventory "
"LEFT JOIN charaglobalitem "
"ON houseiteminventory.ItemId = charaglobalitem.itemId;",
"ON houseiteminventory.ItemId = charaglobalitem.itemId "
"LEFT JOIN landplaceditems "
"ON houseiteminventory.ItemId = landplaceditems.ItemId;",
CONNECTION_BOTH );
prepareStatement( LAND_INV_SEL_HOUSE,
@ -222,6 +225,28 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
"ON land.HouseId = house.HouseId;",
CONNECTION_SYNC );
prepareStatement( LAND_INV_UP,
"INSERT INTO houseiteminventory ( LandIdent, ContainerId, SlotId, ItemId ) "
"VALUES ( ?, ?, ?, ? ) "
"ON DUPLICATE KEY UPDATE ItemId = ?;",
CONNECTION_BOTH );
prepareStatement( LAND_INV_DEL,
"DELETE FROM houseiteminventory "
"WHERE LandIdent = ? AND ContainerId = ? AND SlotId = ?;",
CONNECTION_BOTH );
prepareStatement( LAND_INV_UP_ITEMPOS,
"INSERT INTO landplaceditems ( ItemId, PosX, PosY, PosZ, Rotation ) "
"VALUES ( ?, ?, ?, ?, ? ) "
"ON DUPLICATE KEY UPDATE PosX = ?, PosY = ?, PosZ = ?, Rotation = ?;",
CONNECTION_BOTH );
prepareStatement( LAND_INV_DEL_ITEMPOS,
"DELETE FROM landplaceditems "
"WHERE ItemId = ?;",
CONNECTION_BOTH );
/*prepareStatement( LAND_INS,
"INSERT INTO land ( LandSetId ) VALUES ( ? );",
CONNECTION_BOTH );

View file

@ -91,6 +91,8 @@ namespace Sapphire::Db
LAND_INV_SEL_HOUSE,
LAND_INV_DEL,
LAND_INV_UP,
LAND_INV_UP_ITEMPOS,
LAND_INV_DEL_ITEMPOS,
MAX_STATEMENTS

View file

@ -213,8 +213,34 @@ enum ActorControlType : uint16_t
// Housing
ShowHousingItemUI = 0x3F7,
ShowBuildPresetUI = 0x3E9,
/*!
* param1 = plot id
*/
ShowEstateExternalAppearanceUI = 0x3EA,
ShowEstateInternalAppearanceUI = 0x3EB,
BuildPresetResponse = 0x3ED,
/*!
* param1 = u16 landid
* u16 slotid
*/
RemoveExteriorHousingItem = 0x3EF,
/*!
* param1 = object array index
*/
RemoveInteriorHousingItem = 0x3F1,
/*!
* param1 = identity shit
* u16 1 - container id
* u16 2 - plot id
* param2 = item shit
* u16 1 - slot
*/
HousingItemMoveConfirm = 0x3F9,
OpenEstateSettingsUI = 0x3FF,
/*!
* param1 = outdoor furnishings
* u8 0 - relocation available, 1 = available
@ -306,6 +332,8 @@ enum ActorControlType : uint16_t
SetEstateLightingLevel = 0x40B, // param1 = light level 0 - 5 maps to UI val 5-0
RequestHousingBuildPreset = 0x44C,
RequestEstateExteriorRemodel = 0x044D, // param11 = land id
RequestEstateInteriorRemodel = 0x44E,
RequestEstateHallRemoval = 0x44F,
RequestBuildPreset = 0x450, // no idea what this is, it gets sent with BuildPresetHandler and has the plot id in param1
RequestLandSignFree = 0x451,
@ -313,6 +341,7 @@ enum ActorControlType : uint16_t
RequestWardLandInfo = 0x453,
RequestLandRelinquish = 0x454,
RequestLandInventory = 0x0458,
RequestHousingItemRemove = 0x0459,
RequestEstateRename = 0x45A,
RequestEstateEditGreeting = 0x45B,
RequestEstateGreeting = 0x45C, // sends FFXIVIpcHousingEstateGreeting in return
@ -322,6 +351,7 @@ enum ActorControlType : uint16_t
RequestHousingItemUI = 0x463,
RequestSharedEstateSettings = 0x46F,
UpdateEstateLightingLevel = 0x471,
HousingItemSelectedInUI = 0x47E,
CompanionAction = 0x6A4,
CompanionSetBarding = 0x6A5,

View file

@ -169,7 +169,7 @@ namespace Sapphire::Network::Packets
// Set the values of static fields.
// The size must be the sum of the segment header and the content
m_segHdr.size = sizeof( FFXIVARR_PACKET_SEGMENT_HEADER ) + getContentSize();
m_segHdr.size = static_cast< uint32_t >( sizeof( FFXIVARR_PACKET_SEGMENT_HEADER ) + getContentSize() );
m_segHdr.type = getSegmentType();
}

View file

@ -24,7 +24,7 @@ void Sapphire::Network::Packets::PacketContainer::addPacket( Sapphire::Network::
{
m_entryList.push_back( entry );
m_ipcHdr.size += entry->getSize();
m_ipcHdr.size += static_cast< uint32_t >( entry->getSize() );
m_ipcHdr.count++;
}

View file

@ -201,9 +201,10 @@ namespace Sapphire::Network::Packets
HousingShowEstateGuestAccess = 0x022A, // updated 4.4
HousingObjectInitialize = 0x022C, // updated 4.4
HousingInternalObjectSpawn = 0x22D, // updated 4.4
HousingWardInfo = 0x022F, // updated 4.4
YardObjectMove = 0x0230, // updated 4.4
HousingObjectMove = 0x0230, // updated 4.4
SharedEstateSettingsResponse = 0x023C, // updated 4.4
@ -294,6 +295,8 @@ namespace Sapphire::Network::Packets
InventoryModifyHandler = 0x0142, // updated 4.4
ReqPlaceHousingItem = 0x145, // updated 4.4
BuildPresetHandler = 0x014A, // updated 4.4
TalkEventHandler = 0x014B, // updated 4.4
EmoteEventHandler = 0x014C, // updated 4.4
@ -311,6 +314,7 @@ namespace Sapphire::Network::Packets
LandRenameHandler = 0x0171, // updated 4.4
HousingUpdateHouseGreeting = 0x0172, // updated 4.4
HousingUpdateObjectPosition = 0x0173, // updated 4.4
SetSharedEstateSettings = 0x0177, // updated 4.4

View file

@ -41,7 +41,8 @@ struct FFXIVIpcClientTrigger :
/* 0004 */ uint32_t param11;
/* 0008 */ uint32_t param12;
/* 000C */ uint32_t param2;
/* 0010 */ char unk_10[8];
/* 0010 */ uint32_t param4; // todo: really?
/* 0014 */ uint32_t param5;
/* 0018 */ uint64_t param3;
};
@ -240,6 +241,35 @@ struct FFXIVIpcMarketBoardRequestItemListings :
/* 0004 */ uint32_t padding;
};
struct FFXIVIpcReqPlaceHousingItem :
FFXIVIpcBasePacket< ReqPlaceHousingItem >
{
/* 0000 */ uint16_t landId; // 0 when plot 0 or inside an estate
/* 0002 */ uint16_t unknown1;
/* 0004 */ uint32_t unknown2;
/* 0008 */ uint16_t sourceInvContainerId;
/* 000A */ uint16_t sourceInvSlotId;
/* 000C */ Common::FFXIVARR_POSITION3 position;
/* 0018 */ float rotation;
/* 001C */ uint32_t unknown3; // always 1?
/* 0020 */ uint32_t unknown4[2]; // always 0 it looks like
};
struct FFXIVIpcHousingUpdateObjectPosition :
FFXIVIpcBasePacket< HousingUpdateObjectPosition >
{
/* 0000 */ Common::LandIdent ident;
/* 0008 */ uint16_t slot;
/* 000A */ uint16_t unk;
/* 000C */ Common::FFXIVARR_POSITION3 pos;
/* 0018 */ float rotation;
/* 001C */ uint32_t padding;
};
}
}
}

View file

@ -1679,26 +1679,20 @@ struct FFXIVIpcLandSetInitialize : FFXIVIpcBasePacket< LandSetInitialize >
LandStruct land[ 30 ];
};
struct FFXIVIpcYardObjectSpawn : FFXIVIpcBasePacket<YardObjectSpawn>
struct FFXIVIpcYardObjectSpawn : FFXIVIpcBasePacket< YardObjectSpawn >
{
uint8_t landSetId;
uint8_t landId;
uint8_t objectArray;
uint16_t unknown1;
uint32_t itemId;
uint16_t itemRotation;
uint16_t pos_x;
uint16_t pos_y;
uint16_t pos_z;
Common::HousingObject object;
};
struct FFXIVIpcYardObjectMove : FFXIVIpcBasePacket<YardObjectMove>
struct FFXIVIpcHousingObjectMove : FFXIVIpcBasePacket< HousingObjectMove >
{
uint16_t itemRotation;
uint8_t objectArray;
uint8_t landSetId;
uint16_t pos_x;
uint16_t pos_y;
uint16_t pos_z;
uint8_t landId;
Common::FFXIVARR_POSITION3_U16 pos;
uint16_t unknown1;
uint16_t unknown2;
uint16_t unknown3;
@ -1711,10 +1705,23 @@ struct FFXIVIpcHousingObjectInitialize : FFXIVIpcBasePacket< HousingObjectInitia
uint8_t packetNum;
uint8_t packetTotal;
uint8_t u2; //Outdoor 0 / Indoor 100(?)
Common::YardObject object[100];
Common::HousingObject object[100];
uint32_t unknown4; //unused
};
struct FFXIVIpcHousingInternalObjectSpawn : FFXIVIpcBasePacket< HousingInternalObjectSpawn >
{
uint16_t containerId;
uint8_t containerOffset;
uint8_t pad1;
uint16_t itemId;
uint8_t unk2;
uint8_t pad2;
uint16_t rotation;
Common::FFXIVARR_POSITION3_U16 pos;
};
struct FFXIVIpcHousingIndoorInitialize : FFXIVIpcBasePacket< HousingIndoorInitialize >
{
uint16_t u1;

View file

@ -365,6 +365,10 @@ namespace Sapphire::Entity
Common::GearModelSlot equipSlotToModelSlot( Common::GearSetSlot slot );
bool getFreeInventoryContainerSlot( Inventory::InventoryContainerPair& containerPair ) const;
void insertInventoryItem( Common::InventoryType type, uint16_t slot, const Sapphire::ItemPtr item );
/*!
* Collect real item handins from container
* @param itemIds a vector of each catalog id to collect
@ -919,6 +923,8 @@ namespace Sapphire::Entity
void setActiveLand( uint8_t land, uint8_t ward );
Common::ActiveLand getActiveLand() const;
Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId );
//////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -754,7 +754,7 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS
void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint8_t fromSlotId )
{
// i am not entirely sure how this should be generated or if it even is important for us...
uint32_t transactionId = 1;
uint32_t transactionId = getNextInventorySequence();
auto fromItem = m_storageMap[ fromInventoryId ]->getItem( fromSlotId );
@ -869,3 +869,70 @@ uint32_t Sapphire::Entity::Player::getNextInventorySequence()
{
return m_inventorySequence++;
}
Sapphire::ItemPtr Sapphire::Entity::Player::dropInventoryItem( Sapphire::Common::InventoryType type, uint16_t slotId )
{
auto& container = m_storageMap[ type ];
auto item = container->getItem( slotId );
if( !item )
return nullptr;
// unlink item
container->removeItem( slotId );
updateContainer( type, slotId, nullptr );
auto seq = getNextInventorySequence();
// send inv update
auto invTransPacket = makeZonePacket< FFXIVIpcInventoryTransaction >( getId() );
invTransPacket->data().transactionId = seq;
invTransPacket->data().ownerId = getId();
invTransPacket->data().storageId = type;
invTransPacket->data().catalogId = item->getId();
invTransPacket->data().stackSize = item->getStackSize();
invTransPacket->data().slotId = slotId;
invTransPacket->data().type = 7;
queuePacket( invTransPacket );
auto invTransFinPacket = makeZonePacket< FFXIVIpcInventoryTransactionFinish >( getId() );
invTransFinPacket->data().transactionId = seq;
invTransFinPacket->data().transactionId1 = seq;
queuePacket( invTransFinPacket );
return item;
}
bool Sapphire::Entity::Player::getFreeInventoryContainerSlot( Inventory::InventoryContainerPair& containerPair ) const
{
for( auto bagId : { Bag0, Bag1, Bag2, Bag3 } )
{
auto needle = m_storageMap.find( bagId );
if( needle == m_storageMap.end() )
continue;
auto& container = needle->second;
for( uint16_t idx = 0; idx < container->getMaxSize(); idx++ )
{
auto item = container->getItem( idx );
if( !item )
{
containerPair = std::make_pair( bagId, idx );
return true;
}
}
}
return false;
}
void Sapphire::Entity::Player::insertInventoryItem( Sapphire::Common::InventoryType type, uint16_t slot,
const Sapphire::ItemPtr item )
{
updateContainer( type, slot, item );
auto slotUpdate = std::make_shared< UpdateInventorySlotPacket >( getId(), slot, type, *item );
queuePacket( slotUpdate );
}

View file

@ -84,6 +84,17 @@ bool Sapphire::Entity::Player::load( uint32_t charId, SessionPtr pSession )
pCurrZone = pTeriMgr->getZoneByTerritoryTypeId( zoneId );
}
}
else if( pTeriMgr->isInternalEstateTerritory( zoneId ) )
{
// todo: this needs to go to the area just outside of the plot door
pCurrZone = pTeriMgr->getZoneByLandSetId( m_prevTerritoryId );
zoneId = m_prevTerritoryTypeId;
m_pos.x = m_prevPos.x;
m_pos.y = m_prevPos.y;
m_pos.z = m_prevPos.z;
setRot( m_prevRot );
}
else if( pTeriMgr->isHousingTerritory( zoneId ) )
{
pCurrZone = pTeriMgr->getZoneByLandSetId( m_territoryId );
@ -580,10 +591,7 @@ Sapphire::ItemPtr Sapphire::Entity::Player::createItem( uint32_t catalogId, uint
uint8_t flags = 0;
ItemPtr pItem = make_Item( Items::Util::getNextUId(),
catalogId,
itemInfo->modelMain,
itemInfo->modelSub );
ItemPtr pItem = make_Item( Items::Util::getNextUId(), catalogId );
pItem->setStackSize( quantity );

View file

@ -35,6 +35,13 @@ namespace World::Territory::Housing
TYPE_FORWARD( HousingInteriorTerritory );
}
namespace Inventory
{
using InventoryContainerPair = std::pair< Common::InventoryType, uint8_t >;
using InventoryTypeList = std::vector< Common::InventoryType >;
TYPE_FORWARD( HousingItem );
}
namespace World::Manager
{
TYPE_FORWARD( HousingMgr );

View file

@ -0,0 +1,29 @@
#include "HousingItem.h"
Sapphire::Inventory::HousingItem::HousingItem( uint64_t uId, uint32_t catalogId ) :
Sapphire::Item( uId, catalogId, false )
{
m_stackSize = 1;
m_spiritBond = 1;
m_reservedFlag = 1092616192; // wat?
}
uint16_t Sapphire::Inventory::HousingItem::getRot() const
{
return m_rotation;
}
void Sapphire::Inventory::HousingItem::setRot( uint16_t rot )
{
m_rotation = rot;
}
Sapphire::Common::FFXIVARR_POSITION3_U16 Sapphire::Inventory::HousingItem::getPos() const
{
return m_position;
}
void Sapphire::Inventory::HousingItem::setPos( Sapphire::Common::FFXIVARR_POSITION3_U16 pos )
{
m_position = pos;
}

View file

@ -0,0 +1,26 @@
#ifndef SAPPHIRE_HOUSINGITEM_H
#define SAPPHIRE_HOUSINGITEM_H
#include "Item.h"
namespace Sapphire::Inventory
{
class HousingItem : public Item
{
public:
HousingItem( uint64_t uId, uint32_t catalogId );
virtual ~HousingItem() = default;
void setRot( uint16_t rot );
uint16_t getRot() const;
void setPos( Common::FFXIVARR_POSITION3_U16 pos );
Common::FFXIVARR_POSITION3_U16 getPos() const;
private:
Common::FFXIVARR_POSITION3_U16 m_position;
uint16_t m_rotation;
};
}
#endif //SAPPHIRE_HOUSINGITEM_H

View file

@ -7,14 +7,14 @@
extern Sapphire::Framework g_fw;
Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq ) :
Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, bool isHq ) :
m_id( catalogId ),
m_uId( uId ),
m_model1( model1 ),
m_model2( model2 ),
m_isHq( isHq ),
m_stain( 0 ),
m_durability( 30000 )
m_durability( 30000 ),
m_spiritBond( 0 ),
m_reservedFlag( 0 )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
auto itemInfo = pExdData->get< Sapphire::Data::Item >( catalogId );
@ -22,11 +22,14 @@ Sapphire::Item::Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_
m_delayMs = itemInfo->delayms;
m_physicalDmg = itemInfo->damagePhys;
m_magicalDmg = itemInfo->damageMag;
m_model1 = itemInfo->modelMain;
m_model2 = itemInfo->modelSub;
m_weaponDmg = ( m_physicalDmg != 0 ) ? m_physicalDmg : m_magicalDmg;
m_autoAttackDmg = static_cast< float >( m_weaponDmg * m_delayMs ) / 3000;
m_category = static_cast< Common::ItemUICategory >( itemInfo->itemUICategory );
m_itemLevel = itemInfo->levelItem;
m_maxStackSize = itemInfo->stackSize;
m_additionalData = itemInfo->additionalData;
}
float Sapphire::Item::getAutoAttackDmg() const
@ -154,3 +157,28 @@ void Sapphire::Item::setStain( uint16_t stain )
{
m_stain = stain;
}
uint32_t Sapphire::Item::getAdditionalData() const
{
return m_additionalData;
}
uint16_t Sapphire::Item::getSpiritbond() const
{
return m_spiritBond;
}
void Sapphire::Item::setSpiritbond( uint16_t spiritbond )
{
m_spiritBond = spiritbond;
}
uint32_t Sapphire::Item::getReservedFlag() const
{
return m_reservedFlag;
}
void Sapphire::Item::setReservedFlag( uint32_t flag )
{
m_reservedFlag = flag;
}

View file

@ -10,9 +10,9 @@ namespace Sapphire
{
public:
Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t model2, bool isHq = false );
Item( uint64_t uId, uint32_t catalogId, bool isHq = false );
~Item() = default;
virtual ~Item() = default;
uint32_t getId() const;
@ -62,6 +62,14 @@ namespace Sapphire
uint16_t getStain() const;
void setStain( uint16_t stain );
uint32_t getAdditionalData() const;
void setSpiritbond( uint16_t spiritbond );
uint16_t getSpiritbond() const;
void setReservedFlag( uint32_t flag );
uint32_t getReservedFlag() const;
protected:
uint32_t m_id;
@ -87,6 +95,10 @@ namespace Sapphire
uint16_t m_itemLevel;
uint16_t m_durability;
uint16_t m_stain;
uint16_t m_spiritBond;
uint32_t m_reservedFlag;
uint32_t m_additionalData;
};

View file

@ -12,12 +12,12 @@
extern Sapphire::Framework g_fw;
Sapphire::ItemContainer::ItemContainer( uint16_t storageId, uint16_t maxSize, const std::string& tableName,
bool isMultiStorage, bool isPersistentStorage ) :
bool isMultiStorage, bool removeItemOnContainerRemoval ) :
m_id( storageId ),
m_size( maxSize ),
m_tableName( tableName ),
m_bMultiStorage( isMultiStorage ),
m_isPersistentStorage( isPersistentStorage )
m_removeItemOnContainerRemove( removeItemOnContainerRemoval )
{
}
@ -45,7 +45,7 @@ void Sapphire::ItemContainer::removeItem( uint16_t slotId )
if( it != m_itemMap.end() )
{
if( m_isPersistentStorage )
if( m_removeItemOnContainerRemove )
pDb->execute( "DELETE FROM charaglobalitem WHERE itemId = " + std::to_string( it->second->getUId() ) );
m_itemMap.erase( it );
@ -118,7 +118,7 @@ bool Sapphire::ItemContainer::isMultiStorage() const
bool Sapphire::ItemContainer::isPersistentStorage() const
{
return m_isPersistentStorage;
return m_removeItemOnContainerRemove;
}

View file

@ -15,7 +15,7 @@ namespace Sapphire
public:
ItemContainer( uint16_t storageId, uint16_t maxSize, const std::string& tableName, bool isMultiStorage,
bool isPersistentStorage = true );
bool removeItemOnContainerRemoval = true );
~ItemContainer();
@ -48,7 +48,7 @@ namespace Sapphire
uint16_t m_size;
std::string m_tableName;
bool m_bMultiStorage;
bool m_isPersistentStorage;
bool m_removeItemOnContainerRemove;
ItemMap m_itemMap;
Entity::PlayerPtr m_pOwner;
};

View file

@ -132,11 +132,7 @@ Sapphire::ItemPtr Sapphire::Items::Util::loadItem( uint64_t uId )
auto itemInfo = pExdData->get< Sapphire::Data::Item >( itemRes->getUInt( 1 ) );
bool isHq = itemRes->getUInt( 3 ) == 1;
ItemPtr pItem = make_Item( uId,
itemRes->getUInt( 1 ),
itemInfo->modelMain,
itemInfo->modelSub,
isHq );
ItemPtr pItem = make_Item( uId, itemRes->getUInt( 1 ), isHq );
pItem->setStackSize( itemRes->getUInt( 2 ) );

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@
#include "Territory/HousingZone.h"
#include <set>
#include <unordered_map>
#include <array>
namespace Sapphire::Data
{
@ -18,11 +19,15 @@ namespace Sapphire::World::Manager
public:
/*!
* @brief Structure that is generated and filled on world launch. Used to store housing data during init
* so we don't need to query them individually.
*/
struct LandCacheEntry
{
// land table
uint64_t m_landSetId;
uint64_t m_landId;
uint16_t m_landId;
Common::LandType m_type;
uint8_t m_size;
@ -42,6 +47,9 @@ namespace Sapphire::World::Manager
uint64_t m_buildTime;
uint64_t m_endorsements;
uint16_t m_maxPlacedExternalItems;
uint16_t m_maxPlacedInternalItems;
};
/*!
@ -106,6 +114,13 @@ namespace Sapphire::World::Manager
*/
void sendEstateInventory( Entity::Player& player, uint16_t inventoryType, uint8_t plotNum );
/*!
* @brief Sends all the available internal inventories in one go. Used to initially populate the menu.
* @param player The player to send the containers to
* @param storeroom True if we should send the storeroom, false we send the placed items
*/
void sendInternalEstateInventoryBatch( Entity::Player& player, bool storeroom = false );
/*!
* @brief Get the land & house data that was cached on world startup.
* @return
@ -131,13 +146,167 @@ namespace Sapphire::World::Manager
* @return A map containing container ids to ItemContainerPtr
*/
ContainerIdToContainerMap& getEstateInventory( Common::LandIdent ident );
/**
* @brief Sets up inventories and spawns the base items for the house appearance
* @param land The house to update
*/
bool initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId );
void reqPlaceHousingItem( Entity::Player& player, uint16_t landId, uint16_t containerId, uint16_t slotId,
Common::FFXIVARR_POSITION3 pos, float rotation );
/*!
* @brief Returns the equivalent YardObject for a HousingItem
* @param item The item to convert into a YardObject
* @return The resultant YardObject
*/
Common::HousingObject getYardObjectForItem( Inventory::HousingItemPtr item ) const;
void reqMoveHousingItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot,
Common::FFXIVARR_POSITION3 pos, float rot );
void reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot,
uint16_t containerId, uint16_t slot,
bool sendToStoreroom );
void reqEstateExteriorRemodel( Entity::Player& player, uint16_t plot );
void reqEstateInteriorRemodel( Entity::Player& player );
private:
void loadLandCache();
ItemContainerPtr getFreeEstateInventorySlot( Common::LandIdent ident,
Inventory::InventoryContainerPair& pair,
Inventory::InventoryTypeList bagList );
/*!
*
* @param player
* @param terri
* @param containerId
* @param slotId
* @param sendToStoreroom
* @return
*/
bool removeInternalItem( Entity::Player& player, Territory::Housing::HousingInteriorTerritory& terri,
uint16_t containerId, uint16_t slotId, bool sendToStoreroom );
/*!
*
* @param player
* @param terri
* @param slotId
* @param sendToStoreroom
* @return
*/
bool removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land,
Common::InventoryType containerType, uint16_t slotId,
bool sendToStoreroom );
/*!
* @brief Processes the movement of an item placed in a HousingZone
*
* This function assumes that the player has permission to move the item.
*
* @param player The player who placed the item
* @param ident The ident of the land that the item belongs to
* @param containerIdx The index of the container
* @param slot The slot of the item
* @param pos The new position
* @param rot The new rotation
* @return true if moved successfully
*/
bool moveExternalItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot,
Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos, float rot );
/*!
* @brief Processes the movement of an item placed inside a HousingInteriorTerritory
*
* This function assumes that the player has permission to move the item.
*
* @param player The player who placed the item
* @param ident The ident of the land that the item belongs to
* @param slot The index of the container
* @param slotIdx The slot of the item
* @param pos The new position
* @param rot The new rotation
* @return true if moved successfully
*/
bool moveInternalItem( Entity::Player& player, Common::LandIdent ident,
Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot,
Common::FFXIVARR_POSITION3 pos, float rot );
/*!
* @brief Processes the spawning and linking of a newly placed housing item for external items
* @param player The player who is placing the item
* @param item The item that we're placing
* @param ident The land that is going to own the item
* @return true if the item was placed successfully, false if there's no free container slots to place it
*/
bool placeExternalItem( Entity::Player& player, Inventory::HousingItemPtr item, Common::LandIdent ident );
/*!
* @brief Processing the spawning and linking of a newly placed item for interior items
* @param player The player who is placing the item
* @param item The item that we're placing
* @return true if the item was placed successfully, false if there's no free spots to place it
*/
bool placeInteriorItem( Entity::Player& player, Inventory::HousingItemPtr item );
/*!
* @brief Creates a house and saves the minimum amount required to persist a house through restarts.
*
* Any other changes will be covered by the usual saving logic and can be safely ignored here.
*
* @param house The house to create in the house table
*/
void createHouse( HousePtr house ) const;
/*!
* @brief Gets the next available house id
* @return The next available house id
*/
uint64_t getNextHouseId();
/*!
* @brief Loads all the land entries from the database and caches them to speed up housing territory init
*/
void initLandCache();
/*!
* @brief Loads all the inventories for every estate on the world and sets up their containers
* @return True if it was successful
*/
bool loadEstateInventories();
/*!
* @brief Gets the additionalData field from item.exd for an item
* @param catalogId The item id to lookup in item.exd
* @return The additionalData field from item.exd
*/
uint32_t getItemAdditionalData( uint32_t catalogId );
/*!
* @brief Checks whether an estate inventory contains items that are placed and have a world position
*
* Eg, any item inside the 'placed' items container _should_ have a world position and can be spawned.
*
* @param type The inventory type that contains items
* @return true if contains items that would have a world position
*/
bool isPlacedItemsInventory( Common::InventoryType type );
LandSetLandCacheMap m_landCache;
LandIdentToInventoryContainerMap m_estateInventories;
Inventory::InventoryTypeList m_internalPlacedItemContainers;
Inventory::InventoryTypeList m_internalStoreroomContainers;
std::array< std::pair< Common::InventoryType, Common::InventoryType >, 8 > m_containerMap;
};
}

View file

@ -3,10 +3,18 @@
#include <Common.h>
#include "Actor/Player.h"
#include "Inventory/ItemContainer.h"
#include "Inventory/Item.h"
#include "Inventory/HousingItem.h"
#include "Inventory/ItemUtil.h"
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#include <Network/GamePacketNew.h>
#include <Database/DatabaseDef.h>
#include <Exd/ExdDataGenerated.h>
#include "Framework.h"
extern Sapphire::Framework g_fw;
using namespace Sapphire::Network::Packets;
void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::Entity::Player& player,
@ -41,8 +49,9 @@ void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::E
itemInfoPacket->data().quantity = itM->second->getStackSize();
itemInfoPacket->data().catalogId = itM->second->getId();
itemInfoPacket->data().condition = itM->second->getDurability();
itemInfoPacket->data().spiritBond = 0;
itemInfoPacket->data().hqFlag = itM->second->isHq() ? 1 : 0;
itemInfoPacket->data().spiritBond = itM->second->getSpiritbond();
itemInfoPacket->data().reservedFlag = itM->second->getReservedFlag();
itemInfoPacket->data().hqFlag = static_cast< uint8_t >( itM->second->isHq() ? 1 : 0 );
itemInfoPacket->data().stain = itM->second->getStain();
player.queuePacket( itemInfoPacket );
@ -55,4 +64,121 @@ void Sapphire::World::Manager::InventoryMgr::sendInventoryContainer( Sapphire::E
containerInfoPacket->data().containerId = container->getId();
player.queuePacket( containerInfoPacket );
}
Sapphire::ItemPtr Sapphire::World::Manager::InventoryMgr::createItem( Entity::Player& player,
uint32_t catalogId, uint32_t quantity )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto itemInfo = pExdData->get< Sapphire::Data::Item >( catalogId );
if( !itemInfo )
return nullptr;
auto item = make_Item( Items::Util::getNextUId(), catalogId );
item->setStackSize( std::max< uint32_t >( 1, quantity ) );
saveItem( player, item );
return item;
}
void Sapphire::World::Manager::InventoryMgr::saveHousingContainer( Common::LandIdent ident,
Sapphire::ItemContainerPtr container )
{
auto u64ident = *reinterpret_cast< uint64_t* >( &ident );
for( auto& item : container->getItemMap() )
{
saveHousingContainerItem( u64ident, container->getId(), item.first, item.second->getUId() );
}
}
void Sapphire::World::Manager::InventoryMgr::removeItemFromHousingContainer( Sapphire::Common::LandIdent ident,
uint16_t containerId,
uint16_t slotId )
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto stmt = pDb->getPreparedStatement( Db::LAND_INV_DEL );
auto u64ident = *reinterpret_cast< uint64_t* >( &ident );
stmt->setUInt64( 1, u64ident );
stmt->setUInt( 2, containerId );
stmt->setUInt( 3, slotId );
pDb->directExecute( stmt );
}
void Sapphire::World::Manager::InventoryMgr::saveHousingContainerItem( uint64_t ident,
uint16_t containerId, uint16_t slotId,
uint64_t itemId )
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto stmt = pDb->getPreparedStatement( Db::LAND_INV_UP );
// LandIdent, ContainerId, SlotId, ItemId, ItemId
stmt->setUInt64( 1, ident );
stmt->setUInt( 2, containerId );
stmt->setUInt( 3, slotId );
stmt->setUInt64( 4, itemId );
// see query, we have to pass itemid in twice
// the second time is for the ON DUPLICATE KEY UPDATE condition
stmt->setUInt64( 5, itemId );
pDb->execute( stmt );
}
void Sapphire::World::Manager::InventoryMgr::updateHousingItemPosition( Sapphire::Inventory::HousingItemPtr item )
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto stmt = pDb->getPreparedStatement( Db::LAND_INV_UP_ITEMPOS );
// ItemId, PosX, PosY, PosZ, Rotation, PosX, PosY, PosZ, Rotation
auto pos = item->getPos();
auto rot = item->getRot();
stmt->setUInt64( 1, item->getUId() );
stmt->setUInt( 2, pos.x );
stmt->setUInt( 3, pos.y );
stmt->setUInt( 4, pos.z );
stmt->setInt( 5, rot );
stmt->setUInt( 6, pos.x );
stmt->setUInt( 7, pos.y );
stmt->setUInt( 8, pos.z );
stmt->setInt( 9, rot );
pDb->execute( stmt );
}
void Sapphire::World::Manager::InventoryMgr::removeHousingItemPosition( Sapphire::Inventory::HousingItem& item )
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto stmt = pDb->getPreparedStatement( Db::LAND_INV_DEL_ITEMPOS );
stmt->setUInt64( 1, item.getUId() );
pDb->directExecute( stmt );
}
void Sapphire::World::Manager::InventoryMgr::saveItem( Sapphire::Entity::Player& player, Sapphire::ItemPtr item )
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto stmt = pDb->getPreparedStatement( Db::CHARA_ITEMGLOBAL_INS );
stmt->setUInt( 1, player.getId() );
stmt->setUInt64( 2, item->getUId() );
stmt->setUInt( 3, item->getId() );
stmt->setUInt( 4, item->getStackSize() );
pDb->directExecute( stmt );
}

View file

@ -9,7 +9,83 @@ namespace Sapphire::World::Manager
class InventoryMgr
{
public:
/*!
* @brief Sends an item container to a player
*
* This does no checks on the container itself. You can send another players inventory to a different
* player if you so wish - not that you should
*
* Automagically manages inventory packet sequencing.
*
* @param player The player to send the container to
* @param container The container to send to the player
*/
void sendInventoryContainer( Sapphire::Entity::Player& player, Sapphire::ItemContainerPtr container );
/*!
* @brief Creates an item, saves it to the global item table and returns a ptr to the created item
* @param player The player that will 'own' the item
* @param catalogId The ID for the item, see item.exd
* @param quantity how much item to make
* @return An ItemPtr to the newly created item
*/
Sapphire::ItemPtr createItem( Entity::Player& player, uint32_t catalogId, uint32_t quantity = 1 );
/*!
* @brief Commits a housing containers contents to the db
* @param ident The identity of the owner of the container
* @param container The container to save to the db
*/
void saveHousingContainer( Common::LandIdent ident, Sapphire::ItemContainerPtr container );
/*!
* @brief Unlinks an item from the housing container in the db
* @param ident The identity of the estate that owns the item
* @param containerId The container the item is in
* @param slotId The slot the item is in
*/
void removeItemFromHousingContainer( Common::LandIdent ident, uint16_t containerId, uint16_t slotId );
/*!
* @brief Update an item in the db
* @param item The item to commit to the db
*/
void updateItem( Sapphire::ItemPtr item );
/*!
* @brief Updates the position/rotation of a housing object
* @param item The item to update
*/
void updateHousingItemPosition( Sapphire::Inventory::HousingItemPtr item );
/*!
* @brief Removes the position/rotation from a housing object
* @param item The item to remove the position from.
*/
void removeHousingItemPosition( Sapphire::Inventory::HousingItem& item );
/*!
* @brief Saves an item to the global item table
* @param player The player which owns the item
* @param item The item to save
*/
void saveItem( Entity::Player& player, ItemPtr item );
private:
/*!
* @brief Saves an individual item to the db.
*
* This will create a new row in the event the slot doesn't exist in the db, otherwise this
* will overwrite the itemid in the existing row.
*
* @param ident
* @param containerId
* @param slotId
* @param itemId
*/
void saveHousingContainerItem( uint64_t ident,
uint16_t containerId, uint16_t slotId,
uint64_t itemId );
};
}

View file

@ -106,11 +106,20 @@ bool Sapphire::World::Manager::TerritoryMgr::isPrivateTerritory( uint32_t territ
return pTeri->territoryIntendedUse == TerritoryIntendedUse::OpeningArea ||
pTeri->territoryIntendedUse == TerritoryIntendedUse::Inn ||
pTeri->territoryIntendedUse == TerritoryIntendedUse::HousingPrivateArea ||
pTeri->territoryIntendedUse == TerritoryIntendedUse::JailArea ||
pTeri->territoryIntendedUse == TerritoryIntendedUse::MSQPrivateArea;
}
bool Sapphire::World::Manager::TerritoryMgr::isInternalEstateTerritory( uint32_t territoryTypeId ) const
{
auto pTeri = getTerritoryDetail( territoryTypeId );
if( !pTeri )
return false;
return pTeri->territoryIntendedUse == TerritoryIntendedUse::HousingPrivateArea;
}
bool Sapphire::World::Manager::TerritoryMgr::isDefaultTerritory( uint32_t territoryTypeId ) const
{
auto pTeri = getTerritoryDetail( territoryTypeId );

View file

@ -87,6 +87,13 @@ namespace Sapphire::World::Manager
/*! returns true if the territoryType in question is not a private zone */
bool isPrivateTerritory( uint32_t territoryTypeId ) const;
/*!
* @brief Checks if a territory type is an internal housing area
* @param territoryTypeId The territory to test
* @return true if it is a housing area, false if not
*/
bool isInternalEstateTerritory( uint32_t territoryTypeId ) const;
/*! returns true if the territoryType is a default non-instanced zone */
bool isDefaultTerritory( uint32_t territoryTypeId ) const;

View file

@ -88,6 +88,9 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH
setZoneHandler( ClientZoneIpcType::LandRenameHandler, "LandRenameHandler", &GameConnection::landRenameHandler );
setZoneHandler( ClientZoneIpcType::HousingUpdateHouseGreeting, "HousingUpdateHouseGreeting",
&GameConnection::housingUpdateGreetingHandler );
setZoneHandler( ClientZoneIpcType::ReqPlaceHousingItem, "ReqPlaceHousingItem", &GameConnection::reqPlaceHousingItem );
setZoneHandler( ClientZoneIpcType::HousingUpdateObjectPosition, "HousingUpdateObjectPosition",
&GameConnection::reqMoveHousingItem );
setZoneHandler( ClientZoneIpcType::TalkEventHandler, "EventHandlerTalk", &GameConnection::eventHandlerTalk );
setZoneHandler( ClientZoneIpcType::EmoteEventHandler, "EventHandlerEmote", &GameConnection::eventHandlerEmote );

View file

@ -171,6 +171,10 @@ namespace Sapphire::Network
DECLARE_HANDLER( tellHandler );
DECLARE_HANDLER( reqPlaceHousingItem );
DECLARE_HANDLER( reqMoveHousingItem );
};
}

View file

@ -79,12 +79,16 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX
const auto param12 = packet.data().param12;
const auto param2 = packet.data().param2;
const auto param3 = packet.data().param3;
const auto param4 = packet.data().param4;
const auto param5 = packet.data().param5;
pLog->debug( "[" + std::to_string( m_pSession->getId() ) + "] Incoming action: " +
Util::intToHexString( static_cast< uint32_t >( commandId & 0xFFFF ), 4 ) +
"\nparam1: " + Util::intToHexString( static_cast< uint64_t >( param1 & 0xFFFFFFFFFFFFFFF ), 16 ) +
"\nparam2: " + Util::intToHexString( static_cast< uint32_t >( param2 & 0xFFFFFFFF ), 8 ) +
"\nparam3: " + Util::intToHexString( static_cast< uint64_t >( param3 & 0xFFFFFFFFFFFFFFF ), 16 )
"\nparam3: " + Util::intToHexString( static_cast< uint64_t >( param3 & 0xFFFFFFFFFFFFFFF ), 16 ) +
"\nparam4: " + Util::intToHexString( static_cast< uint32_t >( param4 & 0xFFFFFFFF ), 8 ) +
"\nparam5: " + Util::intToHexString( static_cast< uint32_t >( param5 & 0xFFFFFFFF ), 8 )
);
@ -425,14 +429,13 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX
{
uint8_t plot = ( param12 & 0xFF );
auto housingMgr = g_fw.get< HousingMgr >();
if( !housingMgr )
break;
uint16_t inventoryType = Common::InventoryType::HousingOutdoorPlacedItems;
uint16_t inventoryType = Common::InventoryType::HousingExteriorPlacedItems;
if( param2 == 1 )
inventoryType = Common::InventoryType::HousingOutdoorStoreroom;
inventoryType = Common::InventoryType::HousingExteriorStoreroom;
housingMgr->sendEstateInventory( player, inventoryType, plot );
@ -440,15 +443,45 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX
}
case ClientTriggerType::RequestEstateInventory:
{
// only sent if param1 is 1, because the client sends this with 0 when you open the ui for whatever reason
if( param1 != 1 )
return;
auto housingMgr = g_fw.get< HousingMgr >();
if( !housingMgr )
break;
// housingMgr->sendHousingInventory( player, Common::InventoryType::HousingInd, 255 );
// param1 = 1 - storeroom
// param1 = 0 - placed items
if( param1 == 1 )
housingMgr->sendInternalEstateInventoryBatch( player, true );
else
housingMgr->sendInternalEstateInventoryBatch( player );
break;
}
case ClientTriggerType::RequestHousingItemRemove:
{
auto housingMgr = g_fw.get< HousingMgr >();
auto slot = param4 & 0xFF;
auto sendToStoreroom = ( param4 >> 16 ) != 0;
//player, plot, containerId, slot, sendToStoreroom
housingMgr->reqRemoveHousingItem( player, param12, param2, slot, sendToStoreroom );
break;
}
case ClientTriggerType::RequestEstateExteriorRemodel:
{
auto housingMgr = g_fw.get< HousingMgr >();
housingMgr->reqEstateExteriorRemodel( player, param11 );
break;
}
case ClientTriggerType::RequestEstateInteriorRemodel:
{
auto housingMgr = g_fw.get< HousingMgr >();
housingMgr->reqEstateInteriorRemodel( player );
break;
}

View file

@ -705,3 +705,26 @@ void Sapphire::Network::GameConnection::housingUpdateGreetingHandler( const Pack
pHousingMgr->updateEstateGreeting( player, packet.data().ident, std::string( packet.data().greeting ) );
}
void Sapphire::Network::GameConnection::reqPlaceHousingItem( const Packets::FFXIVARR_PACKET_RAW& inPacket,
Entity::Player& player )
{
auto housingMgr = g_fw.get< HousingMgr >();
const auto packet = ZoneChannelPacket< Client::FFXIVIpcReqPlaceHousingItem >( inPacket );
const auto& data = packet.data();
housingMgr->reqPlaceHousingItem( player, data.landId, data.sourceInvContainerId, data.sourceInvSlotId,
data.position, data.rotation );
}
void Sapphire::Network::GameConnection::reqMoveHousingItem( const Packets::FFXIVARR_PACKET_RAW& inPacket,
Entity::Player& player )
{
auto housingMgr = g_fw.get< HousingMgr >();
const auto packet = ZoneChannelPacket< Client::FFXIVIpcHousingUpdateObjectPosition >( inPacket );
const auto& data = packet.data();
housingMgr->reqMoveHousingItem( player, data.ident, data.slot, data.pos, data.rotation );
}

View file

@ -29,11 +29,11 @@ namespace Sapphire::Network::Packets::Server
m_data.slot = slot;
m_data.quantity = item.getStackSize();
m_data.catalogId = item.getId();
m_data.reservedFlag = 0; // no idea
m_data.reservedFlag = item.getReservedFlag(); // no idea
m_data.signatureId = 0;
m_data.hqFlag = item.isHq() ? 1 : 0;
m_data.condition = 60000; // 200%
m_data.spiritBond = 0;
m_data.spiritBond = item.getSpiritbond();
m_data.color = 0;
m_data.glamourCatalogId = 0;
m_data.materia1 = 0;

View file

@ -20,9 +20,7 @@ Sapphire::House::House( uint32_t houseId, uint32_t landSetId, Common::LandIdent
m_landIdent( ident ),
m_estateName( estateName ),
m_estateComment( estateComment )
{
}
{}
Sapphire::House::~House() = default;
@ -55,44 +53,14 @@ Sapphire::Common::LandIdent Sapphire::House::getLandIdent() const
return m_landIdent;
}
uint32_t Sapphire::House::getHouseId() const
uint32_t Sapphire::House::getId() const
{
return m_houseId;
}
uint8_t Sapphire::House::getHousePartColor( Common::HousePartSlot slot ) const
{
return m_houseModelsCache[ slot ].second;
}
uint32_t Sapphire::House::getHouseInteriorPart( Common::HousingInteriorSlot slot ) const
{
return m_houseInteriorModels[ slot ];
}
void Sapphire::House::setHousePart( Common::HousePartSlot slot, uint32_t id )
{
m_houseModelsCache[ slot ].first = id;
}
void Sapphire::House::setHousePartColor( Common::HousePartSlot slot, uint32_t id )
{
m_houseModelsCache[ slot ].second = id;
}
void Sapphire::House::setHouseInteriorPart( Common::HousingInteriorSlot slot, uint32_t id )
{
m_houseInteriorModels[ slot ] = id;
}
uint32_t Sapphire::House::getHousePart( Common::HousePartSlot slot ) const
{
return m_houseModelsCache[ slot ].first;
}
Sapphire::House::HouseModelsArray const& Sapphire::House::getHouseModels() const
{
return m_houseModelsCache;
return m_exteriorModelCache;
}
const std::string& Sapphire::House::getHouseName() const
@ -117,4 +85,24 @@ void Sapphire::House::setHouseName( const std::string& name )
m_estateName = name;
updateHouseDb();
}
void Sapphire::House::setExteriorModel( Sapphire::Common::HouseExteriorSlot slot, uint32_t modelId, uint16_t stain )
{
m_exteriorModelCache[ slot ] = std::make_pair( modelId, stain );
}
Sapphire::House::HousePart Sapphire::House::getExteriorModel( Sapphire::Common::HouseExteriorSlot slot )
{
return m_exteriorModelCache[ slot ];
}
void Sapphire::House::setInteriorModel( Sapphire::Common::HousingInteriorSlot slot, uint32_t modelId )
{
m_interiorModelCache[ slot ] = modelId;
}
uint32_t Sapphire::House::getInteriorModel( Sapphire::Common::HousingInteriorSlot slot )
{
return m_interiorModelCache[ slot ];
}

View file

@ -16,13 +16,13 @@ namespace Sapphire
const std::string& estateComment );
virtual ~House();
using HousePart = std::pair< uint32_t, uint8_t >;
using HousePart = std::pair< uint32_t, uint16_t >;
using HouseModelsArray = std::array< HousePart, 8 >;
//gerneral
uint32_t getLandSetId() const;
Common::LandIdent getLandIdent() const;
uint32_t getHouseId() const;
uint32_t getId() const;
const std::string& getHouseName() const;
void setHouseName( const std::string& name );
@ -31,12 +31,11 @@ namespace Sapphire
void setHouseGreeting( const std::string& greeting );
//functions
void setHousePart( Common::HousePartSlot slot, uint32_t id );
void setHousePartColor( Common::HousePartSlot slot, uint32_t id );
void setHouseInteriorPart( Common::HousingInteriorSlot slot, uint32_t id );
uint32_t getHousePart( Common::HousePartSlot slot ) const;
uint8_t getHousePartColor( Common::HousePartSlot slot ) const;
uint32_t getHouseInteriorPart( Common::HousingInteriorSlot slot ) const;
void setExteriorModel( Common::HouseExteriorSlot slot, uint32_t modelId, uint16_t stain );
HousePart getExteriorModel( Common::HouseExteriorSlot slot );
void setInteriorModel( Common::HousingInteriorSlot slot, uint32_t modelId );
uint32_t getInteriorModel( Common::HousingInteriorSlot slot );
HouseModelsArray const& getHouseModels() const;
@ -49,8 +48,8 @@ namespace Sapphire
uint64_t m_buildTime;
HouseModelsArray m_houseModelsCache;
uint32_t m_houseInteriorModels[10];
HouseModelsArray m_exteriorModelCache;
uint32_t m_interiorModelCache[10];
std::string m_estateComment;
std::string m_estateName;

View file

@ -6,6 +6,8 @@
#include <Exd/ExdDataGenerated.h>
#include <Network/GamePacketNew.h>
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#include <Network/PacketWrappers/ActorControlPacket143.h>
#include <Network/CommonActorControl.h>
#include "Actor/Player.h"
#include "Actor/Actor.h"
@ -13,6 +15,8 @@
#include "Manager/HousingMgr.h"
#include "Territory/Land.h"
#include "Territory/House.h"
#include "Inventory/ItemContainer.h"
#include "Inventory/HousingItem.h"
#include "Forwards.h"
#include "HousingInteriorTerritory.h"
@ -28,27 +32,27 @@ using namespace Sapphire::World::Manager;
using namespace Sapphire;
using namespace Sapphire::World::Territory;
Housing::HousingInteriorTerritory::HousingInteriorTerritory( Common::LandIdent ident, uint16_t territoryTypeId,
uint32_t guId,
const std::string& internalName,
const std::string& contentName ) :
Sapphire::World::Territory::Housing::HousingInteriorTerritory::HousingInteriorTerritory( Common::LandIdent ident,
uint16_t territoryTypeId,
uint32_t guId,
const std::string& internalName,
const std::string& contentName ) :
Zone( territoryTypeId, guId, internalName, contentName ),
m_landIdent( ident )
{
m_lastActivityTime = static_cast< uint32_t >( Util::getTimeSeconds() );
}
Housing::HousingInteriorTerritory::~HousingInteriorTerritory()
{
Housing::HousingInteriorTerritory::~HousingInteriorTerritory() = default;
bool Sapphire::World::Territory::Housing::HousingInteriorTerritory::init()
{
updateHousingObjects();
return true;
}
bool Housing::HousingInteriorTerritory::init()
{
return false;
}
void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player )
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player )
{
auto pHousingMgr = g_fw.get< HousingMgr >();
auto pLog = g_fw.get< Logger >();
@ -56,7 +60,7 @@ void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player )
"HousingInteriorTerritory::onPlayerZoneIn: Zone#" + std::to_string( getGuId() ) + "|" + std::to_string( getTerritoryTypeId() ) +
", Entity#" + std::to_string( player.getId() ) );
auto indoorInitPacket = makeZonePacket< FFXIVIpcHousingIndoorInitialize >( player.getId() );
auto indoorInitPacket = makeZonePacket< Server::FFXIVIpcHousingIndoorInitialize >( player.getId() );
indoorInitPacket->data().u1 = 0;
indoorInitPacket->data().u2 = 0;
indoorInitPacket->data().u3 = 0;
@ -68,43 +72,159 @@ void Housing::HousingInteriorTerritory::onPlayerZoneIn( Entity::Player& player )
for( auto i = 0; i < 10; i++ )
{
indoorInitPacket->data().indoorItems[ i ] = pHouse->getHouseInteriorPart( static_cast< Common::HousingInteriorSlot >( i ) );
indoorInitPacket->data().indoorItems[ i ] = pHouse->getInteriorModel(
static_cast< Common::HousingInteriorSlot >( i ) );
}
uint32_t yardPacketNum;
uint32_t yardPacketTotal = 2 + pLand->getSize();
player.queuePacket( indoorInitPacket );
for( yardPacketNum = 0; yardPacketNum < yardPacketTotal; yardPacketNum++ )
auto yardPacketTotal = static_cast< uint8_t >( 2 + pLand->getSize() );
for( uint8_t yardPacketNum = 0; yardPacketNum < yardPacketTotal; yardPacketNum++ )
{
auto objectInitPacket = makeZonePacket< FFXIVIpcHousingObjectInitialize >( player.getId() );
auto objectInitPacket = makeZonePacket< Server::FFXIVIpcHousingObjectInitialize >( player.getId() );
memcpy( &objectInitPacket->data().landIdent, &m_landIdent, sizeof( Common::LandIdent ) );
objectInitPacket->data().u1 = 0;
objectInitPacket->data().u2 = 100;
objectInitPacket->data().packetNum = yardPacketNum;
objectInitPacket->data().packetTotal = yardPacketTotal;
//TODO: Add Objects here
auto yardObjectSize = sizeof( Common::HousingObject );
memcpy( &objectInitPacket->data().object, m_housingObjects.data() + ( yardPacketNum * 100 ), yardObjectSize * 100 );
player.queuePacket( objectInitPacket );
}
}
void Housing::HousingInteriorTerritory::onUpdate( uint32_t currTime )
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::onUpdate( uint32_t currTime )
{
if( m_playerMap.size() > 0 )
m_lastActivityTime = currTime;
}
uint32_t Housing::HousingInteriorTerritory::getLastActivityTime() const
uint32_t Sapphire::World::Territory::Housing::HousingInteriorTerritory::getLastActivityTime() const
{
return m_lastActivityTime;
}
const Common::LandIdent Housing::HousingInteriorTerritory::getIdent() const
const Common::LandIdent Sapphire::World::Territory::Housing::HousingInteriorTerritory::getLandIdent() const
{
return m_landIdent;
}
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::updateHousingObjects()
{
auto housingMgr = g_fw.get< Manager::HousingMgr >();
auto containerIds = {
InventoryType::HousingInteriorPlacedItems1,
InventoryType::HousingInteriorPlacedItems2,
InventoryType::HousingInteriorPlacedItems3,
InventoryType::HousingInteriorPlacedItems4,
InventoryType::HousingInteriorPlacedItems5,
InventoryType::HousingInteriorPlacedItems6,
InventoryType::HousingInteriorPlacedItems7,
InventoryType::HousingInteriorPlacedItems8,
};
// zero out the array
// there's some really weird behaviour where *some* values will cause the linkshell invite notification to pop up
// for some reason
Common::HousingObject obj {};
memset( &obj, 0x0, sizeof( Common::HousingObject ) );
m_housingObjects.fill( obj );
auto containers = housingMgr->getEstateInventory( getLandIdent() );
uint8_t containerIdx = 0;
for( auto containerId : containerIds )
{
auto container = containers.find( containerId );
if( container == containers.end() )
// no more containers left
break;
for( const auto& item : container->second->getItemMap() )
{
auto housingItem = std::dynamic_pointer_cast< Inventory::HousingItem >( item.second );
assert( housingItem );
auto offset = item.first + ( containerIdx * 50 );
auto obj = housingMgr->getYardObjectForItem( housingItem );
m_housingObjects[ offset ] = obj;
}
containerIdx++;
}
}
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::spawnHousingObject( uint8_t containerIdx,
uint16_t slot,
uint16_t containerType,
Inventory::HousingItemPtr item )
{
auto housingMgr = g_fw.get< Manager::HousingMgr >();
auto offset = ( containerIdx * 50 ) + slot;
auto obj = housingMgr->getYardObjectForItem( item );
m_housingObjects[ offset ] = obj;
for( const auto& player : m_playerMap )
{
auto objectSpawnPkt = makeZonePacket< Server::FFXIVIpcHousingInternalObjectSpawn >( player.second->getId() );
objectSpawnPkt->data().containerId = containerType;
objectSpawnPkt->data().containerOffset = slot;
objectSpawnPkt->data().itemId = item->getAdditionalData() & 0xFFFF;
objectSpawnPkt->data().rotation = item->getRot();
objectSpawnPkt->data().pos = item->getPos();
player.second->queuePacket( objectSpawnPkt );
}
}
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::updateHousingObjectPosition( Entity::Player& sourcePlayer,
uint16_t slot,
Common::FFXIVARR_POSITION3_U16 pos,
uint16_t rot )
{
auto& obj = m_housingObjects[ slot ];
obj.pos = pos;
obj.itemRotation = rot;
// todo: how does this update on other clients?
for( const auto& player : m_playerMap )
{
if( player.second->getId() == sourcePlayer.getId() )
continue;
auto moveObjPkt = makeZonePacket< Server::FFXIVIpcHousingObjectMove >( player.second->getId() );
moveObjPkt->data().itemRotation = obj.itemRotation;
moveObjPkt->data().pos = obj.pos;
// todo: how does this work when an item is in a slot >50 or u8 max? my guess is landid is the container index, but not sure...
moveObjPkt->data().objectArray = static_cast< uint8_t >( slot % 50 );
moveObjPkt->data().landId = static_cast< uint8_t >( slot / 50 );
player.second->queuePacket( moveObjPkt );
}
}
void Sapphire::World::Territory::Housing::HousingInteriorTerritory::removeHousingObject( uint16_t slot )
{
memset( m_housingObjects.data() + slot, 0x0, sizeof( Common::HousingObject ) );
for( const auto& player : m_playerMap )
{
auto pkt = Server::makeActorControl143( player.second->getId(), Network::ActorControl::RemoveInteriorHousingItem, slot );
player.second->queuePacket( pkt );
}
}

View file

@ -1,5 +1,7 @@
#include "ForwardsZone.h"
#include "Territory/Zone.h"
#include "Common.h"
#include <array>
namespace Sapphire::World::Territory::Housing
{
@ -20,10 +22,19 @@ namespace Sapphire::World::Territory::Housing
uint32_t getLastActivityTime() const;
const Common::LandIdent getIdent() const;
const Common::LandIdent getLandIdent() const;
void updateHousingObjects();
void spawnHousingObject( uint8_t containerIdx, uint16_t slot, uint16_t containerType,
Inventory::HousingItemPtr item );
void updateHousingObjectPosition(
Entity::Player& sourcePlayer, uint16_t slot, Sapphire::Common::FFXIVARR_POSITION3_U16 pos, uint16_t rot );
void removeHousingObject( uint16_t slot );
private:
Common::LandIdent m_landIdent;
uint32_t m_lastActivityTime;
std::array< Sapphire::Common::HousingObject, 400 > m_housingObjects;
};
}

View file

@ -6,12 +6,16 @@
#include <Exd/ExdDataGenerated.h>
#include <Network/GamePacketNew.h>
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#include <Network/PacketWrappers/ActorControlPacket143.h>
#include <Network/CommonActorControl.h>
#include "Actor/Player.h"
#include "Actor/Actor.h"
#include "Actor/EventObject.h"
#include "Land.h"
#include "House.h"
#include "Inventory/HousingItem.h"
#include "Inventory/ItemContainer.h"
#include "Forwards.h"
#include "HousingZone.h"
@ -51,7 +55,7 @@ bool Sapphire::HousingZone::init()
}
int housingIndex;
uint32_t housingIndex = 0;
if( m_territoryTypeId == 339 )
housingIndex = 0;
else if( m_territoryTypeId == 340 )
@ -64,6 +68,52 @@ bool Sapphire::HousingZone::init()
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
auto info = pExdData->get< Sapphire::Data::HousingLandSet >( housingIndex );
// build yard objects array indices
int16_t cursor = -1;
uint16_t index = 0;
for( const auto size : info->plotSize )
{
uint16_t itemMax = 0;
switch( 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::HousingObject obj {};
memset( &obj, 0x0, sizeof( Common::HousingObject ) );
for( auto& arr : m_yardObjects )
{
arr.fill( obj );
}
auto housingMgr = g_fw.get< World::Manager::HousingMgr >();
auto landCache = housingMgr->getLandCacheMap();
@ -83,7 +133,8 @@ bool Sapphire::HousingZone::init()
// setup house
if( entry.m_houseId )
{
auto house = make_House( entry.m_houseId, m_landSetId, land->getLandIdent(), entry.m_estateName, entry.m_estateComment );
auto house = make_House( entry.m_houseId, m_landSetId, land->getLandIdent(), entry.m_estateName,
entry.m_estateComment );
housingMgr->updateHouseModels( house );
land->setHouse( house );
@ -94,7 +145,9 @@ bool Sapphire::HousingZone::init()
m_landPtrMap[ entry.m_landId ] = land;
if( entry.m_houseId > 0 )
registerHouseEntranceEObj( entry.m_landId );
registerEstateEntranceEObj( entry.m_landId );
updateYardObjects( land->getLandIdent() );
}
return true;
@ -109,6 +162,8 @@ void Sapphire::HousingZone::onPlayerZoneIn( Entity::Player& player )
"HousingZone::onPlayerZoneIn: Zone#" + std::to_string( getGuId() ) + "|" + std::to_string( getTerritoryTypeId() ) +
", Entity#" + std::to_string( player.getId() ) );
auto isInSubdivision = isPlayerSubInstance( player ) ? true : false;
uint32_t yardPacketNum;
uint32_t yardPacketTotal = 8;
@ -122,14 +177,20 @@ void Sapphire::HousingZone::onPlayerZoneIn( Entity::Player& player )
housingObjectInit->data().packetNum = yardPacketNum;
housingObjectInit->data().packetTotal = yardPacketTotal;
//TODO: Add Objects here
auto yardObjectSize = sizeof( Common::HousingObject );
auto& objects = m_yardObjects[ isInSubdivision ? 1 : 0 ];
memcpy( &housingObjectInit->data().object, objects.data() + ( yardPacketNum * 100 ), yardObjectSize * 100 );
player.queuePacket( housingObjectInit );
}
auto landSetMap = makeZonePacket< FFXIVIpcLandSetMap >( player.getId() );
landSetMap->data().subdivision = !isPlayerSubInstance( player ) ? 2 : 1;
uint8_t startIndex = !isPlayerSubInstance( player ) ? 0 : 30;
landSetMap->data().subdivision = isInSubdivision ? 1 : 2;
uint8_t startIndex = isInSubdivision ? 30 : 0;
for( uint8_t i = startIndex, count = 0; i < ( startIndex + 30 ); i++, count++ )
{
landSetMap->data().landInfo[ count ].status = 1;
@ -252,16 +313,112 @@ Sapphire::LandPtr Sapphire::HousingZone::getLand( uint8_t id )
return it->second;
}
Sapphire::Entity::EventObjectPtr Sapphire::HousingZone::registerHouseEntranceEObj( uint8_t plotId )
Sapphire::Entity::EventObjectPtr Sapphire::HousingZone::registerEstateEntranceEObj( uint8_t landId )
{
auto land = getLand( plotId );
auto land = getLand( landId );
assert( land );
auto eObj = Entity::make_EventObject( getNextEObjId(), 2002737, 0, 4, land->getMapMarkerPosition(), 0.f, "entrance" );
eObj->setHousingLink( plotId << 8 );
eObj->setHousingLink( landId << 8 );
eObj->setScale( 1.f );
registerEObj( eObj );
return eObj;
}
void Sapphire::HousingZone::updateYardObjects( Sapphire::Common::LandIdent ident )
{
auto housingMgr = g_fw.get< World::Manager::HousingMgr >();
auto& landStorage = housingMgr->getEstateInventory( ident );
auto yardContainer = landStorage[ InventoryType::HousingExteriorPlacedItems ];
auto arrayBounds = m_yardObjectArrayBounds[ ident.landId ];
auto yardMapIndex = ident.landId <= 29 ? 0 : 1;
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[ yardMapIndex ][ idx ] = housingMgr->getYardObjectForItem( housingItem );
}
}
void Sapphire::HousingZone::spawnYardObject( uint8_t landId, uint16_t slotId, Inventory::HousingItem& item )
{
auto bounds = m_yardObjectArrayBounds[ landId ];
auto offset = bounds.first + slotId;
Common::HousingObject obj {};
obj.itemId = item.getAdditionalData();
obj.itemRotation = item.getRot();
obj.pos = item.getPos();
// link obj
auto yardMapIndex = landId <= 29 ? 0 : 1;
m_yardObjects[ yardMapIndex ][ offset ] = obj;
// spawn obj in zone
for( const auto& player : m_playerMap )
{
auto packet = makeZonePacket< Server::FFXIVIpcYardObjectSpawn >( player.second->getId() );
packet->data().landId = landId;
packet->data().objectArray = static_cast< uint8_t >( slotId );
packet->data().object = obj;
player.second->queuePacket( packet );
}
}
void Sapphire::HousingZone::updateYardObjectPos( Entity::Player& sourcePlayer, uint16_t slot, uint16_t landId,
Inventory::HousingItem& item )
{
auto bounds = m_yardObjectArrayBounds[ landId ];
auto offset = bounds.first + slot;
auto yardMapIndex = landId <= 29 ? 0 : 1;
auto& obj = m_yardObjects[ yardMapIndex ][ offset ];
obj.itemRotation = item.getRot();
obj.pos = item.getPos();
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< Server::FFXIVIpcHousingObjectMove >( player.second->getId() );
packet->data().itemRotation = item.getRot();
packet->data().pos = item.getPos();
packet->data().landId = landId;
packet->data().objectArray = slot;
player.second->queuePacket( packet );
}
}
void Sapphire::HousingZone::despawnYardObject( uint16_t landId, uint16_t slot )
{
auto bounds = m_yardObjectArrayBounds[ landId ];
auto offset = bounds.first + slot;
auto yardMapIndex = landId <= 29 ? 0 : 1;
memset( &m_yardObjects[ yardMapIndex ][ offset ], 0x00, sizeof( Common::HousingObject ) );
for( const auto& player : m_playerMap )
{
auto param = ( landId << 16 ) | slot;
auto pkt = Server::makeActorControl143( player.second->getId(), Network::ActorControl::RemoveExteriorHousingItem, param );
player.second->queuePacket( pkt );
}
}

View file

@ -4,6 +4,8 @@
#include "Zone.h"
#include "Forwards.h"
#include <array>
namespace Sapphire
{
enum class LandPurchaseResult
@ -50,15 +52,40 @@ namespace Sapphire
uint32_t getLandSetId() const;
Sapphire::LandPtr getLand( uint8_t id );
Entity::EventObjectPtr registerHouseEntranceEObj( uint8_t plotId );
Entity::EventObjectPtr registerEstateEntranceEObj( uint8_t landId );
void updateYardObjects( Common::LandIdent ident );
void spawnYardObject( uint8_t landId, uint16_t slotId, Sapphire::Inventory::HousingItem& item );
void updateYardObjectPos( Entity::Player& sourcePlayer, uint16_t slot, uint16_t landId,
Inventory::HousingItem& item );
void despawnYardObject( uint16_t landId, uint16_t slot );
private:
using LandPtrMap = std::unordered_map< uint8_t, Sapphire::LandPtr >;
using YardObjectArray = std::array< Common::HousingObject, 800 >;
using YardObjectSubdivisionArray = std::array< YardObjectArray, 2 >;
/*!
* @brief Maps the start and end index of the yard object array for a specific plot
*
* pair.first = start index
* pair.second = end index
*/
using YardObjectArrayBoundsPair = std::pair< uint16_t, uint16_t >;
/*!
* @brief Maps each plot to a YardObjectArrayBoundsPair to the start/end index of the yard object array.
*/
using YardObjectArrayBoundsArray = std::array< YardObjectArrayBoundsPair, 60 >;
const uint32_t m_landSetMax = 18;
LandPtrMap m_landPtrMap;
uint8_t m_wardNum;
uint32_t m_landSetId;
uint32_t m_territoryTypeId;
YardObjectSubdivisionArray m_yardObjects;
YardObjectArrayBoundsArray m_yardObjectArrayBounds;
};
}

View file

@ -70,43 +70,6 @@ void Sapphire::Land::init( Common::LandType type, uint8_t size, uint8_t state, u
m_mapMarkerPosition.y = info->y;
m_mapMarkerPosition.z = info->z;
}
switch( m_size )
{
case HouseSize::Cottage:
m_maxPlacedExternalItems = 20;
m_maxPlacedInternalItems = 200;
break;
case HouseSize::House:
m_maxPlacedExternalItems = 30;
m_maxPlacedInternalItems = 300;
break;
case HouseSize::Mansion:
m_maxPlacedExternalItems = 40;
m_maxPlacedInternalItems = 400;
break;
default:
break;
}
// init item containers
// auto setupContainer = [ this ]( InventoryType type, uint16_t maxSize )
// {
// m_landInventoryMap[ type ] = make_ItemContainer( type, maxSize, "houseiteminventory", true, true );
// };
//
// setupContainer( InventoryType::HousingOutdoorAppearance, 8 );
// setupContainer( InventoryType::HousingOutdoorPlacedItems, m_maxPlacedExternalItems );
// setupContainer( InventoryType::HousingOutdoorStoreroom, m_maxPlacedExternalItems );
//
// setupContainer( InventoryType::HousingInteriorAppearance, 9 );
//
// // nb: so we're going to store these internally in one container because SE is fucked in the head
// // but when an inventory is requested, we will split them into groups of 50
// setupContainer( InventoryType::HousingInteriorPlacedItems1, m_maxPlacedInternalItems );
// setupContainer( InventoryType::HousingInteriorStoreroom1, m_maxPlacedInternalItems );
//
// loadItemContainerContents();
}
uint32_t Sapphire::Land::convertItemIdToHousingItemId( uint32_t itemId )
@ -251,7 +214,7 @@ void Sapphire::Land::updateLandDb()
uint32_t houseId = 0;
if( getHouse() )
houseId = getHouse()->getHouseId();
houseId = getHouse()->getId();
// todo: change to prepared statement
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
@ -281,54 +244,7 @@ void Sapphire::Land::update( uint32_t currTime )
}
}
uint32_t Sapphire::Land::getNextHouseId()
Sapphire::Land::InvMaxItemsPair Sapphire::Land::getInventoryItemMax() const
{
auto pDb = g_fw.get< Db::DbWorkerPool< Db::ZoneDbConnection > >();
auto pQR = pDb->query( "SELECT MAX( HouseId ) FROM house" );
if( !pQR->next() )
return 0;
return pQR->getUInt( 1 ) + 1;
}
bool Sapphire::Land::setPreset( uint32_t itemId )
{
auto housingItemId = convertItemIdToHousingItemId( itemId );
auto exdData = g_fw.get< Sapphire::Data::ExdDataGenerated >();
if( !exdData )
return false;
auto housingPreset = exdData->get< Sapphire::Data::HousingPreset >( housingItemId );
if( !housingPreset )
return false;
if( !getHouse() )
{
// todo: i guess we'd create a house here?
auto newId = getNextHouseId();
m_pHouse = make_House( newId, getLandSetId(), getLandIdent(), "Estate #" + std::to_string( m_landIdent.landId + 1 ), "" );
}
getHouse()->setHousePart( Common::HousePartSlot::ExteriorRoof, convertItemIdToHousingItemId( housingPreset->exteriorRoof ) );
getHouse()->setHousePart( Common::HousePartSlot::ExteriorWall, convertItemIdToHousingItemId( housingPreset->exteriorWall ) );
getHouse()->setHousePart( Common::HousePartSlot::ExteriorWindow, convertItemIdToHousingItemId( housingPreset->exteriorWindow ) );
getHouse()->setHousePart( Common::HousePartSlot::ExteriorDoor, convertItemIdToHousingItemId( housingPreset->exteriorDoor ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall, convertItemIdToHousingItemId( housingPreset->interiorWall ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor, convertItemIdToHousingItemId( housingPreset->interiorFlooring ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight, convertItemIdToHousingItemId( housingPreset->interiorLighting ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorWall ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorFlooring ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Attic, convertItemIdToHousingItemId( housingPreset->otherFloorLighting ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorWall_Basement, convertItemIdToHousingItemId( housingPreset->basementWall ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorFloor_Basement, convertItemIdToHousingItemId( housingPreset->basementFlooring ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Basement, convertItemIdToHousingItemId( housingPreset->basementLighting ) );
getHouse()->setHouseInteriorPart( Common::HousingInteriorSlot::InteriorLight_Mansion, convertItemIdToHousingItemId( housingPreset->mansionLighting ) );
return true;
return std::make_pair( m_maxPlacedExternalItems, m_maxPlacedInternalItems );
}

View file

@ -20,6 +20,7 @@ namespace Sapphire
void init( Common::LandType type, uint8_t size, uint8_t state, uint32_t currentPrice, uint64_t ownerId, uint64_t houseId );
using LandInventoryMap = std::unordered_map< uint16_t, ItemContainerPtr >;
using InvMaxItemsPair = std::pair< uint16_t, uint16_t >;
//Primary state
void setSize( uint8_t size );
@ -61,6 +62,8 @@ namespace Sapphire
void setLandTag( uint8_t slot, uint8_t tag );
uint8_t getLandTag( uint8_t slot );
InvMaxItemsPair getInventoryItemMax() const;
private:
uint32_t convertItemIdToHousingItemId( uint32_t itemId );
uint32_t getNextHouseId();