diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 0d92d010..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]; }; 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 ce984909..bb17af0a 100644 --- a/src/servers/sapphire_zone/Actor/Player.h +++ b/src/servers/sapphire_zone/Actor/Player.h @@ -326,6 +326,12 @@ public: /*! equip a weapon, possibly forcing a job change */ 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; diff --git a/src/servers/sapphire_zone/Actor/PlayerInventory.cpp b/src/servers/sapphire_zone/Actor/PlayerInventory.cpp index f8aef788..6589a9b1 100644 --- a/src/servers/sapphire_zone/Actor/PlayerInventory.cpp +++ b/src/servers/sapphire_zone/Actor/PlayerInventory.cpp @@ -135,6 +135,20 @@ void Core::Entity::Player::equipWeapon( ItemPtr pItem, bool updateClass ) } } +void Core::Entity::Player::equipSoulCrystal( ItemPtr pItem, bool updateJob ) +{ + auto exdData = g_fw.get< Core::Data::ExdDataGenerated >(); + if ( !exdData ) + return; + + auto itemInfo = exdData->get< Core::Data::Item >( pItem->getId() ); + auto itemClassJob = itemInfo->classJobUse; + auto newClassJob = static_cast< ClassJob >( itemClassJob ); + + if ( isClassJobUnlocked( newClassJob ) && updateJob ) + setClassJob( newClassJob ); +} + // equip an item void Core::Entity::Player::equipItem( Common::GearSetSlot equipSlotId, ItemPtr pItem, bool sendUpdate ) { @@ -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