diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 85365e36..c42be004 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -1000,8 +1000,8 @@ struct FFXIVIpcPlayerClassInfo : uint16_t classId; uint8_t unknown; uint8_t isSpecialist; - uint16_t level; // Locks actions, equipment, prob more - uint16_t level1; // Locks roles, prob more + uint16_t syncedLevel; // Locks actions, equipment, prob more. Player's current level (synced). + uint16_t classLevel; // Locks roles, prob more. Player's actual unsynced level. uint32_t roleActions[10]; }; @@ -1014,7 +1014,10 @@ struct FFXIVIpcModelEquip : { /* 0000 */ uint64_t mainWeapon; /* 0008 */ uint64_t offWeapon; - /* 0010 */ uint32_t padding1; + /* 0010 */ uint8_t unk1; + /* 0011 */ uint8_t classJobId; + /* 0012 */ uint8_t level; + /* 0013 */ uint8_t unk2; /* 0014 */ uint32_t models[10]; /* 003C */ uint32_t padding2; }; diff --git a/src/servers/sapphire_api/PlayerMinimal.cpp b/src/servers/sapphire_api/PlayerMinimal.cpp index d117eea0..db375d03 100644 --- a/src/servers/sapphire_api/PlayerMinimal.cpp +++ b/src/servers/sapphire_api/PlayerMinimal.cpp @@ -319,6 +319,7 @@ void PlayerMinimal::saveAsNew() createInvDbContainer( InventoryType::ArmoryWrist ); createInvDbContainer( InventoryType::ArmoryRing ); createInvDbContainer( InventoryType::ArmoryMain ); + createInvDbContainer( InventoryType::ArmorySoulCrystal ); createInvDbContainer( InventoryType::Currency ); createInvDbContainer( InventoryType::Crystal ); diff --git a/src/servers/sapphire_zone/Actor/Player.cpp b/src/servers/sapphire_zone/Actor/Player.cpp index 6efdf86a..e9b06529 100644 --- a/src/servers/sapphire_zone/Actor/Player.cpp +++ b/src/servers/sapphire_zone/Actor/Player.cpp @@ -746,7 +746,8 @@ void Core::Entity::Player::setClassJob( Common::ClassJob classJob ) auto classInfoPacket = makeZonePacket< FFXIVIpcPlayerClassInfo >( getId() ); classInfoPacket->data().classId = static_cast< uint8_t >( getClass() ); - classInfoPacket->data().level = getLevel(); + classInfoPacket->data().classLevel = getLevel(); + classInfoPacket->data().syncedLevel = getLevel(); queuePacket( classInfoPacket ); sendToInRangeSet( makeActorControl142( getId(), ClassJobChange, 0x04 ), true ); @@ -1550,8 +1551,8 @@ void Core::Entity::Player::sendZonePackets() auto classInfoPacket = makeZonePacket< FFXIVIpcPlayerClassInfo >( getId() ); classInfoPacket->data().classId = static_cast< uint8_t >( getClass() ); classInfoPacket->data().unknown = 1; - classInfoPacket->data().level = getLevel(); - classInfoPacket->data().level1 = getLevel(); + classInfoPacket->data().syncedLevel = getLevel(); + classInfoPacket->data().classLevel = getLevel(); queuePacket( classInfoPacket ); m_itemLevel = calculateEquippedGearItemLevel(); @@ -1733,4 +1734,3 @@ bool Core::Entity::Player::isOnEnterEventDone() const { return m_onEnterEventDone; } - diff --git a/src/servers/sapphire_zone/Actor/Player.h b/src/servers/sapphire_zone/Actor/Player.h index fd5f4a65..bb17af0a 100644 --- a/src/servers/sapphire_zone/Actor/Player.h +++ b/src/servers/sapphire_zone/Actor/Player.h @@ -324,7 +324,13 @@ public: void unequipItem( Common::GearSetSlot equipSlotId, ItemPtr pItem ); /*! equip a weapon, possibly forcing a job change */ - void equipWeapon( ItemPtr pItem ); + void equipWeapon( ItemPtr pItem, bool updateClass ); + + /*! equip a soul crystal, possibly forcing a job change*/ + void equipSoulCrystal( ItemPtr pItem, bool updateClass ); + + /*! unequip a soul crystal, returning to the base class*/ + void unequipSoulCrystal( ItemPtr pItem ); /*! get player ilvl */ uint16_t getItemLevel() const; @@ -356,7 +362,7 @@ public: /*! return the current amount of crystals of type */ uint32_t getCrystal( uint8_t type ) const; - void updateModels( Common::GearSetSlot equipSlotId, const Core::ItemPtr& pItem ); + void updateModels( Common::GearSetSlot equipSlotId, const Core::ItemPtr& pItem, bool updateClass ); Common::GearModelSlot equipSlotToModelSlot( Common::GearSetSlot slot ); diff --git a/src/servers/sapphire_zone/Actor/PlayerInventory.cpp b/src/servers/sapphire_zone/Actor/PlayerInventory.cpp index a40ce99f..6589a9b1 100644 --- a/src/servers/sapphire_zone/Actor/PlayerInventory.cpp +++ b/src/servers/sapphire_zone/Actor/PlayerInventory.cpp @@ -114,7 +114,7 @@ void Core::Entity::Player::sendItemLevel() queuePacket( makeActorControl142( getId(), SetItemLevel, getItemLevel(), 0 ) ); } -void Core::Entity::Player::equipWeapon( ItemPtr pItem ) +void Core::Entity::Player::equipWeapon( ItemPtr pItem, bool updateClass ) { auto exdData = g_fw.get< Core::Data::ExdDataGenerated >(); if( !exdData ) @@ -122,16 +122,31 @@ void Core::Entity::Player::equipWeapon( ItemPtr pItem ) auto itemInfo = exdData->get< Core::Data::Item >( pItem->getId() ); auto itemClassJob = itemInfo->classJobUse; - - auto currentClass = getClass(); + auto classJobInfo = exdData->get< Core::Data::ClassJob >( static_cast< uint32_t >( getClass() ) ); + auto currentParentClass = static_cast< ClassJob >( classJobInfo->classJobParent ); auto newClassJob = static_cast< ClassJob >( itemClassJob ); - if( isClassJobUnlocked( newClassJob ) ) + if( ( isClassJobUnlocked( newClassJob ) ) && ( currentParentClass != newClassJob ) ) + { + if ( updateClass ) + setClassJob( newClassJob ); + else + return; + } +} + +void Core::Entity::Player::equipSoulCrystal( ItemPtr pItem, bool updateJob ) +{ + auto exdData = g_fw.get< Core::Data::ExdDataGenerated >(); + if ( !exdData ) return; - // todo: check if soul crystal is equipped and use job instead + auto itemInfo = exdData->get< Core::Data::Item >( pItem->getId() ); + auto itemClassJob = itemInfo->classJobUse; + auto newClassJob = static_cast< ClassJob >( itemClassJob ); - setClassJob( newClassJob ); + if ( isClassJobUnlocked( newClassJob ) && updateJob ) + setClassJob( newClassJob ); } // equip an item @@ -139,18 +154,18 @@ void Core::Entity::Player::equipItem( Common::GearSetSlot equipSlotId, ItemPtr p { //g_framework.getLogger().debug( "Equipping into slot " + std::to_string( equipSlotId ) ); - - updateModels( equipSlotId, pItem ); - if( sendUpdate ) { + updateModels( equipSlotId, pItem, true ); this->sendModel(); m_itemLevel = calculateEquippedGearItemLevel(); sendItemLevel(); } + else + updateModels( equipSlotId, pItem, false ); } -void Core::Entity::Player::updateModels( GearSetSlot equipSlotId, const Core::ItemPtr& pItem ) +void Core::Entity::Player::updateModels( GearSetSlot equipSlotId, const Core::ItemPtr& pItem, bool updateClass ) { uint64_t model = pItem->getModelId1(); uint64_t model2 = pItem->getModelId2(); @@ -160,8 +175,7 @@ void Core::Entity::Player::updateModels( GearSetSlot equipSlotId, const Core::It case MainHand: m_modelMainWeapon = model; m_modelSubWeapon = model2; - // TODO: add job change upon changing weapon if needed - // equipWeapon( pItem ); + equipWeapon( pItem, updateClass ); break; case OffHand: @@ -169,8 +183,7 @@ void Core::Entity::Player::updateModels( GearSetSlot equipSlotId, const Core::It break; case SoulCrystal: - // TODO: add Job change on equipping crystal - // change job + equipSoulCrystal( pItem, updateClass ); break; case Waist: @@ -228,6 +241,20 @@ void Core::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, ItemPtr m_itemLevel = calculateEquippedGearItemLevel(); sendItemLevel(); + + if ( equipSlotId == SoulCrystal ) + unequipSoulCrystal( pItem ); +} + +void Core::Entity::Player::unequipSoulCrystal( ItemPtr pItem ) +{ + auto exdData = g_fw.get< Core::Data::ExdDataGenerated >(); + if ( !exdData ) + return; + + auto currentClassJob = exdData->get< Core::Data::ClassJob >( static_cast< uint32_t >( getClass() ) ); + auto parentClass = static_cast< ClassJob >( currentClassJob->classJobParent ); + setClassJob( parentClass ); } // TODO: these next functions are so similar that they could likely be simplified diff --git a/src/servers/sapphire_zone/Actor/PlayerSql.cpp b/src/servers/sapphire_zone/Actor/PlayerSql.cpp index 5b6abae1..1f98fa7b 100644 --- a/src/servers/sapphire_zone/Actor/PlayerSql.cpp +++ b/src/servers/sapphire_zone/Actor/PlayerSql.cpp @@ -608,7 +608,7 @@ bool Core::Entity::Player::loadInventory() { uint16_t storageId = res->getUInt16( 1 ); - for( uint32_t i = 1; i <= 13; i++ ) + for( uint32_t i = 1; i <= 14; i++ ) { uint64_t uItemId = res->getUInt64( i + 1 ); if( uItemId == 0 ) diff --git a/src/servers/sapphire_zone/Network/PacketWrappers/ModelEquipPacket.h b/src/servers/sapphire_zone/Network/PacketWrappers/ModelEquipPacket.h index fd2801fa..f0bb7ebe 100644 --- a/src/servers/sapphire_zone/Network/PacketWrappers/ModelEquipPacket.h +++ b/src/servers/sapphire_zone/Network/PacketWrappers/ModelEquipPacket.h @@ -28,6 +28,8 @@ private: { m_data.mainWeapon = player.getModelMainWeapon(); m_data.offWeapon = player.getModelSubWeapon(); + m_data.classJobId = static_cast< uint8_t >( player.getClass() ); + m_data.level = player.getLevel(); m_data.models[ Common::GearModelSlot::ModelHead ] = player.getModelForSlot( Common::GearModelSlot::ModelHead ); m_data.models[ Common::GearModelSlot::ModelBody ] = player.getModelForSlot( Common::GearModelSlot::ModelBody ); m_data.models[ Common::GearModelSlot::ModelHands ] = player.getModelForSlot( Common::GearModelSlot::ModelHands );