diff --git a/src/servers/Server_Common/Common.h b/src/servers/Server_Common/Common.h index f5b72fd6..1675c443 100644 --- a/src/servers/Server_Common/Common.h +++ b/src/servers/Server_Common/Common.h @@ -602,7 +602,8 @@ namespace Core { MpGain = 11, TpLoss = 12, TpGain = 13, - GpGain = 14 + GpGain = 14, + Mount = 38 }; enum class ActionHitSeverityType : uint8_t @@ -951,7 +952,8 @@ namespace Core { GearSetEquipMsg = 0x321, - ToggleOrchestrionUnlock = 0x396 + ToggleOrchestrionUnlock = 0x396, + Dismount = 0x3a0 }; enum struct ChatType : uint16_t @@ -1068,6 +1070,12 @@ namespace Core { Visor = 0x40, }; + enum SkillType : uint8_t + { + Normal = 0x1, + MountSkill = 0xD, + }; + struct ServerEntry { uint32_t serverId; diff --git a/src/servers/Server_Common/Network/GamePacket.cpp b/src/servers/Server_Common/Network/GamePacket.cpp index f468e90d..c67f4fb6 100644 --- a/src/servers/Server_Common/Network/GamePacket.cpp +++ b/src/servers/Server_Common/Network/GamePacket.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "Server_Common/Util/Util.h" Core::Network::Packets::GamePacket::GamePacket( uint16_t subType, uint16_t size, uint32_t id1, uint32_t id2, uint16_t type ) { @@ -97,16 +98,5 @@ void Core::Network::Packets::GamePacket::savePacket() std::string Core::Network::Packets::GamePacket::toString() const { - - std::string str = "\n"; - for( uint32_t i = 0; i < getSize(); i++ ) - { - str += boost::str( boost::format( "%|02X|" ) % ( int32_t ) ( m_dataBuf[i] & 0xFF ) ) + " "; - - if( ( i + 1 ) % 16 == 0 ) - str += "\n"; - } - str += "\n"; - - return str; + return Core::Util::binaryToHexDump( const_cast( &m_dataBuf[0] ), getSize() ); } diff --git a/src/servers/Server_Common/Network/PacketDef/Ipcs.h b/src/servers/Server_Common/Network/PacketDef/Ipcs.h index bf479ec8..be752a6c 100644 --- a/src/servers/Server_Common/Network/PacketDef/Ipcs.h +++ b/src/servers/Server_Common/Network/PacketDef/Ipcs.h @@ -116,6 +116,7 @@ namespace Packets { ActorSpawn = 0x0190, // todo: split into playerspawn/actorspawn and use opcode 0x110/0x111 ActorFreeSpawn = 0x0191, // unchanged for sb InitZone = 0x019A, // unchanged for sb + Mount = 0x019F, WeatherChange = 0x01AF, // updated for sb PlayerTitleList = 0x01BD, // updated for 4.1 Discovery = 0x01BE, // updated for 4.1 diff --git a/src/servers/Server_Common/Network/PacketDef/Zone/ServerZoneDef.h b/src/servers/Server_Common/Network/PacketDef/Zone/ServerZoneDef.h index 48b4388e..d5cb7675 100644 --- a/src/servers/Server_Common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/servers/Server_Common/Network/PacketDef/Zone/ServerZoneDef.h @@ -604,7 +604,8 @@ struct FFXIVIpcActorSetPos : FFXIVIpcBasePacket struct FFXIVIpcActorCast : FFXIVIpcBasePacket { uint16_t action_id; - uint16_t unknown; + Common::SkillType skillType; + uint8_t unknown; uint32_t unknown_1; // Also action id float cast_time; uint32_t target_id; @@ -1303,6 +1304,15 @@ struct FFXIVIpcEquipDisplayFlags : FFXIVIpcBasePacket uint8_t bitmask; }; +/** +* Structural representation of the packet sent by the server +* to mount a player +*/ +struct FFXIVIpcMount : FFXIVIpcBasePacket +{ + uint32_t id; +}; + } /* Server */ } /* Packets */ diff --git a/src/servers/Server_Common/Util/Util.cpp b/src/servers/Server_Common/Util/Util.cpp index 60903c30..99d5ceda 100644 --- a/src/servers/Server_Common/Util/Util.cpp +++ b/src/servers/Server_Common/Util/Util.cpp @@ -1,9 +1,9 @@ #include "Util.h" #include +#include std::string Core::Util::binaryToHexString( uint8_t* pBinData, uint16_t size ) { - std::string outStr; for( uint32_t i = 0; i < size; i++ ) @@ -15,6 +15,67 @@ std::string Core::Util::binaryToHexString( uint8_t* pBinData, uint16_t size ) } +std::string Core::Util::binaryToHexDump( uint8_t* pBinData, uint16_t size ) +{ + int bytesPerLine = 16; + constexpr char hexChars[] = "0123456789ABCDEF"; + + int offsetBlock = 8 + 3; + int byteBlock = offsetBlock + bytesPerLine * 3 + ( bytesPerLine - 1 ) / 8 + 2; + int lineLength = byteBlock + bytesPerLine + 1; + + std::string line ( lineLength, ' ' ); + int numLines = ( size + bytesPerLine - 1 ) / bytesPerLine; + + + std::string outStr; + + for( uint32_t i = 0; i < size; i += bytesPerLine ) + { + line[0] = hexChars[( i >> 28 ) & 0xF]; + line[1] = hexChars[( i >> 24 ) & 0xF]; + line[2] = hexChars[( i >> 20 ) & 0xF]; + line[3] = hexChars[( i >> 16 ) & 0xF]; + line[4] = hexChars[( i >> 12 ) & 0xF]; + line[5] = hexChars[( i >> 8 ) & 0xF]; + line[6] = hexChars[( i >> 4 ) & 0xF]; + line[7] = hexChars[( i >> 0 ) & 0xF]; + + int hexColumn = offsetBlock; + int charColumn = byteBlock; + + for( int j = 0; j < bytesPerLine; j++ ) + { + if( j > 0 && ( j & 7 ) == 0) + { + hexColumn++; + } + + if( i + j >= size ) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + uint8_t by = pBinData[i + j]; + line[hexColumn] = hexChars[( by >> 4 ) & 0xF]; + line[hexColumn + 1] = hexChars[by & 0xF]; + line[charColumn] = by < 32 ? '.' : static_cast( by ); + } + + hexColumn += 3; + charColumn++; + } + + outStr += line + "\n"; + } + + return outStr; + +} + uint64_t Core::Util::getTimeMs() { std::chrono::milliseconds epoch = std::chrono::duration_cast< std::chrono::milliseconds >(std::chrono::system_clock::now().time_since_epoch()); diff --git a/src/servers/Server_Common/Util/Util.h b/src/servers/Server_Common/Util/Util.h index 306b3d94..2c38f55f 100644 --- a/src/servers/Server_Common/Util/Util.h +++ b/src/servers/Server_Common/Util/Util.h @@ -9,6 +9,8 @@ namespace Util { std::string binaryToHexString( uint8_t* pBinData, uint16_t size ); +std::string binaryToHexDump( uint8_t* pBinData, uint16_t size ); + uint64_t getTimeMs(); uint64_t getTimeSeconds(); diff --git a/src/servers/Server_Zone/Action/ActionCast.cpp b/src/servers/Server_Zone/Action/ActionCast.cpp index a28125c7..c27b9db2 100644 --- a/src/servers/Server_Zone/Action/ActionCast.cpp +++ b/src/servers/Server_Zone/Action/ActionCast.cpp @@ -53,7 +53,7 @@ void Core::Action::ActionCast::onStart() GamePacketNew< FFXIVIpcActorCast, ServerZoneIpcType > castPacket( m_pSource->getId() ); castPacket.data().action_id = m_id; - castPacket.data().unknown = 1; + castPacket.data().skillType = Common::SkillType::Normal; castPacket.data().unknown_1 = m_id; castPacket.data().cast_time = static_cast< float >( m_castTime / 1000 ); // This is used for the cast bar above the target bar of the caster. castPacket.data().target_id = m_pTarget->getId(); diff --git a/src/servers/Server_Zone/Action/ActionMount.cpp b/src/servers/Server_Zone/Action/ActionMount.cpp new file mode 100644 index 00000000..50462d19 --- /dev/null +++ b/src/servers/Server_Zone/Action/ActionMount.cpp @@ -0,0 +1,111 @@ +#include "ActionMount.h" + +#include +#include +#include +#include +#include + +#include "src/servers/Server_Zone/Network/PacketWrappers/ActorControlPacket142.h" +#include "src/servers/Server_Zone/Network/PacketWrappers/ActorControlPacket143.h" +#include "src/servers/Server_Zone/Network/PacketWrappers/ActorControlPacket144.h" +#include "src/servers/Server_Zone/Actor/Player.h" +#include "src/servers/Server_Zone/Script/ScriptManager.h" + +using namespace Core::Common; +using namespace Core::Network; +using namespace Core::Network::Packets; +using namespace Core::Network::Packets::Server; + +extern Core::Data::ExdData g_exdData; +extern Core::Logger g_log; +extern Core::Scripting::ScriptManager g_scriptMgr; + +Core::Action::ActionMount::ActionMount() +{ + m_handleActionType = Common::HandleActionType::Event; +} + +Core::Action::ActionMount::ActionMount( Entity::ActorPtr pActor, uint32_t mountId ) +{ + m_startTime = 0; + m_id = mountId; + m_handleActionType = HandleActionType::Spell; + m_castTime = 1000; + m_pSource = pActor; + m_bInterrupt = false; +} + +Core::Action::ActionMount::~ActionMount() +{ + +} + +void Core::Action::ActionMount::onStart() +{ + if( !m_pSource ) + return; + + m_pSource->getAsPlayer()->sendDebug( "ActionMount::onStart()" ); + m_startTime = Util::getTimeMs(); + + GamePacketNew< FFXIVIpcActorCast, ServerZoneIpcType > castPacket( m_pSource->getId() ); + + castPacket.data().action_id = m_id; + castPacket.data().skillType = Common::SkillType::MountSkill; + castPacket.data().unknown_1 = m_id; + castPacket.data().cast_time = static_cast< float >( m_castTime / 1000 ); // This is used for the cast bar above the target bar of the caster. + castPacket.data().target_id = m_pSource->getAsPlayer()->getId(); + + m_pSource->sendToInRangeSet( castPacket, true ); + m_pSource->getAsPlayer()->setStateFlag( PlayerStateFlag::Casting ); + m_pSource->getAsPlayer()->sendStateFlags(); + +} + +void Core::Action::ActionMount::onFinish() +{ + if( !m_pSource ) + return; + + auto pPlayer = m_pSource->getAsPlayer(); + pPlayer->sendDebug( "ActionMount::onFinish()" ); + + pPlayer->unsetStateFlag( PlayerStateFlag::Casting ); + pPlayer->sendStateFlags(); + + GamePacketNew< FFXIVIpcEffect, ServerZoneIpcType > effectPacket(pPlayer->getId()); + effectPacket.data().targetId = pPlayer->getId(); + effectPacket.data().actionAnimationId = m_id; + effectPacket.data().unknown_62 = 13; // Affects displaying action name next to number in floating text + effectPacket.data().actionTextId = 4; + effectPacket.data().numEffects = 1; + effectPacket.data().rotation = Math::Util::floatToUInt16Rot(pPlayer->getRotation()); + effectPacket.data().effectTarget = INVALID_GAME_OBJECT_ID; + effectPacket.data().effects[0].effectType = ActionEffectType::Mount; + effectPacket.data().effects[0].hitSeverity = ActionHitSeverityType::CritDamage; + effectPacket.data().effects[0].value = m_id; + + pPlayer->sendToInRangeSet(effectPacket, true); + + pPlayer->mount( m_id ); +} + +void Core::Action::ActionMount::onInterrupt() +{ + if( !m_pSource ) + return; + + m_pSource->getAsPlayer()->unsetStateFlag( PlayerStateFlag::Occupied1 ); + m_pSource->getAsPlayer()->unsetStateFlag( PlayerStateFlag::Casting ); + m_pSource->getAsPlayer()->sendStateFlags(); + + auto control = ActorControlPacket142( m_pSource->getId(), ActorControlType::CastInterrupt, + 0x219, 1, m_id, 0 ); + + // Note: When cast interrupt from taking too much damage, set the last value to 1. This enables the cast interrupt effect. Example: + // auto control = ActorControlPacket142( m_pSource->getId(), ActorControlType::CastInterrupt, 0x219, 1, m_id, 0 ); + + m_pSource->sendToInRangeSet( control, true ); + +} diff --git a/src/servers/Server_Zone/Action/ActionMount.h b/src/servers/Server_Zone/Action/ActionMount.h new file mode 100644 index 00000000..cdd68e3d --- /dev/null +++ b/src/servers/Server_Zone/Action/ActionMount.h @@ -0,0 +1,28 @@ +#ifndef _ACTIONMOUNT_H_ +#define _ACTIONMOUNT_H_ + +#include "src/servers/Server_Zone/Forwards.h" +#include "Action.h" + +namespace Core { namespace Action { + + class ActionMount : public Action + { + private: + + public: + ActionMount(); + ~ActionMount(); + + ActionMount( Entity::ActorPtr pActor, uint32_t mountId ); + + void onStart() override; + void onFinish() override; + void onInterrupt() override; + + }; + +} +} + +#endif \ No newline at end of file diff --git a/src/servers/Server_Zone/Actor/Player.cpp b/src/servers/Server_Zone/Actor/Player.cpp index 0199f3ca..39d5d709 100644 --- a/src/servers/Server_Zone/Actor/Player.cpp +++ b/src/servers/Server_Zone/Actor/Player.cpp @@ -1486,6 +1486,31 @@ uint8_t Core::Entity::Player::getEquipDisplayFlags() const return m_equipDisplayFlags; } +void Core::Entity::Player::mount( uint32_t id ) +{ + m_mount = id; + sendToInRangeSet( ActorControlPacket142( getId(), ActorControlType::SetStatus, static_cast< uint8_t >( Entity::Actor::ActorStatus::Mounted )), true ); + sendToInRangeSet( ActorControlPacket143( getId(), 0x39e, 12 ), true ); //? + + GamePacketNew< FFXIVIpcMount, ServerZoneIpcType > mountPacket( getId() ); + mountPacket.data().id = m_mount; + sendToInRangeSet( mountPacket, true ); + setSyncFlag( PlayerSyncFlags::Status ); +} + +void Core::Entity::Player::dismount() +{ + sendToInRangeSet( ActorControlPacket142( getId(), ActorControlType::SetStatus, static_cast< uint8_t >( Entity::Actor::ActorStatus::Idle )), true ); + sendToInRangeSet( ActorControlPacket143( getId(), ActorControlType::Dismount, 1 ), true ); + m_mount = 0; + setSyncFlag( PlayerSyncFlags::Status ); +} + +uint8_t Core::Entity::Player::getCurrentMount() const +{ + return m_mount; +} + void Core::Entity::Player::autoAttack( ActorPtr pTarget ) { diff --git a/src/servers/Server_Zone/Actor/Player.h b/src/servers/Server_Zone/Actor/Player.h index 488065df..95fd1b89 100644 --- a/src/servers/Server_Zone/Actor/Player.h +++ b/src/servers/Server_Zone/Actor/Player.h @@ -338,8 +338,14 @@ public: void setTitle( uint16_t titleId ); /*! change gear param state */ void setEquipDisplayFlags( uint8_t state ); - /*! get gear param state and send update to inRangeSet */ + /*! get gear param state */ uint8_t getEquipDisplayFlags() const; + /*! mount the specified mount and send the packets */ + void mount( uint32_t id ); + /*! dismount the current mount and send the packets */ + void dismount(); + /*! get the current mount */ + uint8_t getCurrentMount() const; void calculateStats() override; void sendStats(); diff --git a/src/servers/Server_Zone/Actor/PlayerSql.cpp b/src/servers/Server_Zone/Actor/PlayerSql.cpp index 4ff24919..23ed16f0 100644 --- a/src/servers/Server_Zone/Actor/PlayerSql.cpp +++ b/src/servers/Server_Zone/Actor/PlayerSql.cpp @@ -85,7 +85,8 @@ bool Core::Entity::Player::load( uint32_t charId, Core::SessionPtr pSession ) "cd.EquipDisplayFlags, " "cd.ActiveTitle, " "cd.TitleList, " // 40 - "cd.Orchestrion " + "cd.Orchestrion, " + "c.Mount " "FROM charabase AS c " " INNER JOIN charadetail AS cd " " ON c.CharacterId = cd.CharacterId " @@ -180,10 +181,13 @@ bool Core::Entity::Player::load( uint32_t charId, Core::SessionPtr pSession ) m_equipDisplayFlags = field[38].get< uint8_t >(); m_title = field[39].get< uint8_t >(); + field[40].getBinary( reinterpret_cast< char* >( m_titleList ), sizeof( m_titleList ) ); field[41].getBinary( reinterpret_cast< char* >( m_orchestrion ), sizeof( m_orchestrion ) ); + m_mount = field[42].get< uint8_t >(); + m_pCell = nullptr; if( !loadActiveQuests() || !loadClassData() || !loadSearchInfo() ) @@ -371,6 +375,7 @@ void Core::Entity::Player::createUpdateSql() charaDetailSet.insert( " Class = " + std::to_string( static_cast< uint32_t >( getClass() ) ) ); charaDetailSet.insert( " Status = " + std::to_string( static_cast< uint8_t >( getStatus() ) ) ); charaDetailSet.insert( " EquipDisplayFlags = " + std::to_string( static_cast< uint8_t >( getEquipDisplayFlags() ) ) ); + charaBaseSet.insert( " Mount = " + std::to_string( getCurrentMount() ) ); } if( m_updateFlags & PlayerSyncFlags::OpeningSeq ) diff --git a/src/servers/Server_Zone/DebugCommand/DebugCommandHandler.cpp b/src/servers/Server_Zone/DebugCommand/DebugCommandHandler.cpp index 3a8805ed..c1f8f2f7 100644 --- a/src/servers/Server_Zone/DebugCommand/DebugCommandHandler.cpp +++ b/src/servers/Server_Zone/DebugCommand/DebugCommandHandler.cpp @@ -258,6 +258,14 @@ void Core::DebugCommandHandler::set( char * data, Core::Entity::PlayerPtr pPlaye pPlayer->sendModel(); pPlayer->sendDebug( "Model updated" ); } + else if ( subCommand == "mount" ) + { + int32_t id; + sscanf( params.c_str(), "%d", &id ); + + pPlayer->dismount(); + pPlayer->mount( id ); + } else { pPlayer->sendUrgent( subCommand + " is not a valid SET command." ); diff --git a/src/servers/Server_Zone/Forwards.h b/src/servers/Server_Zone/Forwards.h index c0f48782..2eaaa75e 100644 --- a/src/servers/Server_Zone/Forwards.h +++ b/src/servers/Server_Zone/Forwards.h @@ -44,6 +44,7 @@ namespace Core TYPE_FORWARD( Action ); TYPE_FORWARD( ActionTeleport ); TYPE_FORWARD( ActionCast ); + TYPE_FORWARD( ActionMount ); TYPE_FORWARD( EventAction ); } diff --git a/src/servers/Server_Zone/Network/GameConnection.cpp b/src/servers/Server_Zone/Network/GameConnection.cpp index a89d1bfe..a9ce9353 100644 --- a/src/servers/Server_Zone/Network/GameConnection.cpp +++ b/src/servers/Server_Zone/Network/GameConnection.cpp @@ -206,7 +206,7 @@ void Core::Network::GameConnection::handleZonePacket( const Packets::GamePacket& g_log.debug( sessionStr + " Undefined Zone IPC : Unknown ( " + boost::str( boost::format( "%|04X|" ) % static_cast< uint32_t >( pPacket.getSubType() & 0xFFFF ) ) + " )" ); - g_log.debug( pPacket.toString() ); + g_log.debug( "\n" + pPacket.toString() ); } } diff --git a/src/servers/Server_Zone/Network/Handlers/ActionHandler.cpp b/src/servers/Server_Zone/Network/Handlers/ActionHandler.cpp index 0b30d71d..9a6dcd6a 100644 --- a/src/servers/Server_Zone/Network/Handlers/ActionHandler.cpp +++ b/src/servers/Server_Zone/Network/Handlers/ActionHandler.cpp @@ -106,6 +106,11 @@ void Core::Network::GameConnection::actionHandler( const Packets::GamePacket& in pPlayer->changeTarget( targetId ); break; } + case 0x65: + { + pPlayer->dismount(); + break; + } case 0x68: // Remove status (clicking it off) { // todo: check if status can be removed by client from exd diff --git a/src/servers/Server_Zone/Network/Handlers/SkillHandler.cpp b/src/servers/Server_Zone/Network/Handlers/SkillHandler.cpp index 457aa855..e83219f0 100644 --- a/src/servers/Server_Zone/Network/Handlers/SkillHandler.cpp +++ b/src/servers/Server_Zone/Network/Handlers/SkillHandler.cpp @@ -25,9 +25,10 @@ #include "src/servers/Server_Zone/Forwards.h" #include "src/servers/Server_Zone/Action/Action.h" -#include "src/servers/Server_Zone/Action/ActionTeleport.h" #include "src/servers/Server_Zone/Action/ActionCast.h" +#include "src/servers/Server_Zone/Action/ActionMount.h" #include "src/servers/Server_Zone/Script/ScriptManager.h" +#include "Server_Zone/Network/PacketWrappers/MoveActorPacket.h" extern Core::Scripting::ScriptManager g_scriptMgr; @@ -41,12 +42,19 @@ using namespace Core::Network::Packets::Server; void Core::Network::GameConnection::skillHandler( const Packets::GamePacket& inPacket, Entity::PlayerPtr pPlayer ) { + uint8_t type = inPacket.getValAt< uint32_t >( 0x21 ); uint32_t action = inPacket.getValAt< uint32_t >( 0x24 ); uint32_t useCount = inPacket.getValAt< uint32_t >( 0x28 ); uint64_t targetId = inPacket.getValAt< uint64_t >( 0x30 ); + pPlayer->sendDebug( "Skill type:" + std::to_string( type ) ); + + switch( type ) + { + case Common::SkillType::Normal: + if( action < 1000000 ) // normal action { std::string actionIdStr = boost::str( boost::format( "%|04X|" ) % action ); @@ -104,4 +112,19 @@ void Core::Network::GameConnection::skillHandler( const Packets::GamePacket& inP } + break; + + case Common::SkillType::MountSkill: + + pPlayer->sendDebug( "Request mount " + std::to_string( action ) ); + + Action::ActionMountPtr pActionMount( new Action::ActionMount(pPlayer, action) ); + pPlayer->setCurrentAction( pActionMount ); + pPlayer->sendDebug("setCurrentAction()"); + pPlayer->getCurrentAction()->onStart(); + + break; + + } + } \ No newline at end of file diff --git a/src/servers/Server_Zone/Network/PacketWrappers/InitUIPacket.h b/src/servers/Server_Zone/Network/PacketWrappers/InitUIPacket.h index 60b69f1d..e28f87e1 100644 --- a/src/servers/Server_Zone/Network/PacketWrappers/InitUIPacket.h +++ b/src/servers/Server_Zone/Network/PacketWrappers/InitUIPacket.h @@ -64,6 +64,9 @@ private: memcpy( m_data.orchestrionMask, player->getOrchestrionBitmask(), sizeof( m_data.orchestrionMask ) ); + memset( m_data.mountGuideMask, 0xFF, sizeof( m_data.mountGuideMask) ); + memset( m_data.fishingGuideMask, 0xFF, sizeof( m_data.fishingGuideMask ) ); + memcpy( m_data.unlockBitmask, player->getUnlockBitmask(), sizeof( m_data.unlockBitmask ) ); memcpy( m_data.discovery, player->getDiscoveryBitmask(), sizeof( m_data.discovery ) ); diff --git a/src/servers/Server_Zone/Network/PacketWrappers/PlayerSpawnPacket.h b/src/servers/Server_Zone/Network/PacketWrappers/PlayerSpawnPacket.h index f96160f6..1c3e8eed 100644 --- a/src/servers/Server_Zone/Network/PacketWrappers/PlayerSpawnPacket.h +++ b/src/servers/Server_Zone/Network/PacketWrappers/PlayerSpawnPacket.h @@ -111,6 +111,8 @@ namespace Server { m_data.displayFlags |= Entity::Actor::DisplayFlags::Visor; } + m_data.currentMount = pPlayer->getCurrentMount(); + m_data.targetId = pPlayer->getTargetId(); //m_data.type = 1; //m_data.unknown_33 = 4;