diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index c5a32413..d144e83c 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -372,7 +372,7 @@ namespace Sapphire::Network::ActorControl Timers = 0x1AB, - DyeItem = 0x1B5, + DyeItem = 0x1B0, // updated 5.21 RequestChocoboInventory = 0x1C4, diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index 6e715282..e04cb1fe 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -1203,7 +1203,8 @@ namespace Sapphire::Network::Packets::Server uint32_t appearanceCatalogId; uint64_t crafterId; uint8_t quality; - uint8_t unknown[3]; + uint8_t stain; + uint8_t unknown[2]; struct Materia { uint16_t materiaId; diff --git a/src/scripts/action/common/ActionDye2472.cpp b/src/scripts/action/common/ActionDye2472.cpp new file mode 100644 index 00000000..b1049a8d --- /dev/null +++ b/src/scripts/action/common/ActionDye2472.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include + +class ActionDye2472 : + public Sapphire::ScriptAPI::ActionScript +{ +public: + ActionDye2472() : + Sapphire::ScriptAPI::ActionScript( 2472 ) + { + } + + void onExecute( Sapphire::World::Action::Action& action ) override + { + auto sourceChara = action.getSourceChara(); + + if( !sourceChara->isPlayer() ) + return; + + //TODO: Effect + sourceChara->getAsPlayer()->dyeItemFromDyeingInfo(); + } +}; + +EXPOSE_SCRIPT(ActionDye2472); \ No newline at end of file diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index dc616600..5eee8372 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -1926,6 +1926,36 @@ uint8_t Sapphire::Entity::Player::getNextObjSpawnIndexForActorId( uint32_t actor return index; } +void Sapphire::Entity::Player::setDyeingInfo( uint32_t itemToDyeContainer, uint32_t itemToDyeSlot, uint32_t dyeBagContainer, uint32_t dyeBagSlot) +{ + m_dyeingInfo.itemToDyeContainer = itemToDyeContainer; + m_dyeingInfo.itemToDyeSlot = itemToDyeSlot; + m_dyeingInfo.dyeBagContainer = dyeBagContainer; + m_dyeingInfo.dyeBagSlot = dyeBagSlot; +} + +void Sapphire::Entity::Player::dyeItemFromDyeingInfo() +{ + uint32_t itemToDyeContainer = m_dyeingInfo.itemToDyeContainer; + uint32_t itemToDyeSlot = m_dyeingInfo.itemToDyeSlot; + uint32_t dyeBagContainer = m_dyeingInfo.dyeBagContainer; + uint32_t dyeBagSlot = m_dyeingInfo.dyeBagSlot; + + sendStateFlags(); // Retail sends all 0s to unlock player after a dye? Possibly not setting a flag when the action is started in the backend..? + auto itemToDye = getItemAt(itemToDyeContainer, itemToDyeSlot); + auto dyeToUse = getItemAt(dyeBagContainer, dyeBagSlot); + + if (!itemToDye || !dyeToUse) return; + + uint32_t stainColorID = dyeToUse->getAdditionalData(); + itemToDye->setStain(stainColorID); + + //TODO: subtract/remove dye used + + insertInventoryItem((Sapphire::Common::InventoryType)itemToDyeContainer, (uint16_t)itemToDyeSlot, itemToDye); + writeItem(itemToDye); +} + void Sapphire::Entity::Player::resetObjSpawnIndex() { m_objSpawnIndexAllocator.freeAllSpawnIndexes(); diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 26984e17..65cd334f 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -536,6 +536,9 @@ namespace Sapphire::Entity void clearTeleportQuery(); + void setDyeingInfo(uint32_t itemToDyeContainer, uint32_t itemToDyeSlot, uint32_t dyeBagContainer, uint32_t dyeBagSlot); + void dyeItemFromDyeingInfo(); + /*! prepares zoning / fades out the screen */ void prepareZoning( uint16_t targetZone, bool fadeOut, uint8_t fadeOutTime = 0, uint16_t animation = 0 ); @@ -1114,6 +1117,14 @@ namespace Sapphire::Entity Common::PlayerTeleportQuery m_teleportQuery; + struct PlayerDyeingInfo + { + uint32_t itemToDyeContainer; + uint32_t itemToDyeSlot; + uint32_t dyeBagContainer; + uint32_t dyeBagSlot; + }; PlayerDyeingInfo m_dyeingInfo; + Common::Util::SpawnIndexAllocator< uint8_t > m_objSpawnIndexAllocator; Common::Util::SpawnIndexAllocator< uint8_t > m_actorSpawnIndexAllocator; diff --git a/src/world/Network/Handlers/ClientTriggerHandler.cpp b/src/world/Network/Handlers/ClientTriggerHandler.cpp index b6decf1b..e87be3c4 100644 --- a/src/world/Network/Handlers/ClientTriggerHandler.cpp +++ b/src/world/Network/Handlers/ClientTriggerHandler.cpp @@ -279,10 +279,7 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( FrameworkPtr pFw, } case ClientTriggerType::DyeItem: // Dye item { - // param11 = item to dye container - // param12 = item to dye slot - // param2 = dye bag container - // param4 = dye bag slot + player.setDyeingInfo(param11, param12, param2, param4); break; } case ClientTriggerType::DirectorInitFinish: // Director init finish diff --git a/src/world/Network/PacketWrappers/ExaminePacket.h b/src/world/Network/PacketWrappers/ExaminePacket.h index 37baee37..a42b11ea 100644 --- a/src/world/Network/PacketWrappers/ExaminePacket.h +++ b/src/world/Network/PacketWrappers/ExaminePacket.h @@ -74,6 +74,7 @@ namespace Sapphire::Network::Packets::Server auto& entry = m_data.entries[i]; entry.catalogId = pItem->getId(); entry.quality = pItem->isHq(); + entry.stain = (uint8_t)pItem->getStain(); //NOTE: More of this packet may be dye info? //entry.appearanceCatalogId = pItem->getGlamourId() // todo: glamour/materia etc. } diff --git a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h index 3c6e8c1c..c16042d2 100644 --- a/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h +++ b/src/world/Network/PacketWrappers/UpdateInventorySlotPacket.h @@ -34,7 +34,7 @@ namespace Sapphire::Network::Packets::Server m_data.hqFlag = item.isHq() ? 1 : 0; m_data.condition = 60000; // 200% m_data.spiritBond = item.getSpiritbond(); - m_data.color = 0; + m_data.color = item.getStain(); m_data.glamourCatalogId = 0; m_data.materia1 = 0; m_data.materia2 = 0;