2017-08-08 13:53:47 +02:00
|
|
|
#include <Server_Common/Common.h>
|
|
|
|
#include <Server_Common/Database.h>
|
|
|
|
#include <Server_Common/Util.h>
|
|
|
|
#include <Server_Common/UtilMath.h>
|
|
|
|
#include <Server_Common/XMLConfig.h>
|
|
|
|
#include <Server_Common/GamePacket.h>
|
|
|
|
#include <Server_Common/Logger.h>
|
|
|
|
#include <Server_Common/ExdData.h>
|
|
|
|
#include <Server_Common/PacketContainer.h>
|
|
|
|
|
|
|
|
#include "Session.h"
|
|
|
|
#include "Player.h"
|
|
|
|
#include "BattleNpc.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "ZoneMgr.h"
|
|
|
|
#include "Zone.h"
|
|
|
|
|
|
|
|
#include "ServerZone.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "GameConnection.h"
|
|
|
|
|
|
|
|
#include "ActorControlPacket142.h"
|
|
|
|
#include "ActorControlPacket143.h"
|
|
|
|
#include "InitUIPacket.h"
|
|
|
|
#include "ServerNoticePacket.h"
|
|
|
|
#include "ChatPacket.h"
|
|
|
|
#include "ModelEquipPacket.h"
|
|
|
|
#include "ActorSpawnPacket.h"
|
|
|
|
#include "UpdateHpMpTpPacket.h"
|
|
|
|
#include "PlayerStateFlagsPacket.h"
|
|
|
|
#include "PlayerSpawnPacket.h"
|
|
|
|
|
|
|
|
#include "ScriptManager.h"
|
|
|
|
|
|
|
|
#include "StatusEffectContainer.h"
|
|
|
|
|
|
|
|
#include "Item.h"
|
|
|
|
|
|
|
|
#include "Inventory.h"
|
|
|
|
#include "Event.h"
|
|
|
|
#include "Action.h"
|
|
|
|
#include "EventAction.h"
|
|
|
|
#include "EventItemAction.h"
|
|
|
|
#include "ZonePosition.h"
|
|
|
|
#include <boost/make_shared.hpp>
|
|
|
|
|
|
|
|
extern Core::Logger g_log;
|
|
|
|
extern Core::Db::Database g_database;
|
|
|
|
extern Core::ServerZone g_serverZone;
|
|
|
|
extern Core::ZoneMgr g_zoneMgr;
|
|
|
|
extern Core::Data::ExdData g_exdData;
|
|
|
|
extern Core::Scripting::ScriptManager g_scriptMgr;
|
|
|
|
|
|
|
|
using namespace Core::Common;
|
|
|
|
using namespace Core::Network::Packets;
|
|
|
|
using namespace Core::Network::Packets::Server;
|
|
|
|
|
|
|
|
// player constructor
|
|
|
|
Core::Entity::Player::Player() :
|
|
|
|
m_lastWrite( 0 ),
|
|
|
|
m_lastPing( 0 ),
|
|
|
|
m_bIsLogin( false ),
|
|
|
|
m_contentId( 0 ),
|
|
|
|
m_modelMainWeapon( 0 ),
|
|
|
|
m_modelSubWeapon( 0 ),
|
|
|
|
m_homePoint( 0 ),
|
|
|
|
m_startTown( 0 ),
|
|
|
|
m_townWarpFstFlags( 0 ),
|
|
|
|
m_playTime( 0 ),
|
|
|
|
m_bInCombat( false ),
|
|
|
|
m_bLoadingComplete( false ),
|
|
|
|
m_bMarkedForZoning( false ),
|
|
|
|
m_zoningType( Common::ZoneingType::None ),
|
|
|
|
m_bAutoattack( false )
|
|
|
|
{
|
|
|
|
m_id = 0;
|
|
|
|
m_type = ActorType::Player;
|
|
|
|
m_currentStance = Stance::Passive;
|
|
|
|
m_onlineStatus = 0;
|
|
|
|
m_queuedZoneing = nullptr;
|
|
|
|
m_status = ActorStatus::Idle;
|
|
|
|
|
|
|
|
memset( m_questTracking, 0, sizeof( m_questTracking ) );
|
|
|
|
memset( m_name, 0, sizeof( m_name ) );
|
|
|
|
memset( m_stateFlags, 0, sizeof( m_stateFlags ) );
|
|
|
|
memset( m_searchMessage, 0, sizeof( m_searchMessage ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
Core::Entity::Player::~Player()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add a proper calculation based on race / job / level / gear
|
|
|
|
uint32_t Core::Entity::Player::getMaxHp()
|
|
|
|
{
|
|
|
|
return m_baseStats.max_hp;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Core::Entity::Player::getMaxMp()
|
|
|
|
{
|
|
|
|
return m_baseStats.max_mp;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t Core::Entity::Player::getZoneId() const
|
|
|
|
{
|
|
|
|
return m_zoneId;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getMode() const
|
|
|
|
{
|
|
|
|
return m_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setMode( uint8_t mode )
|
|
|
|
{
|
|
|
|
m_mode = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getStartTown() const
|
|
|
|
{
|
|
|
|
return m_startTown;
|
|
|
|
}
|
|
|
|
|
|
|
|
Core::Common::OnlineStatus Core::Entity::Player::getOnlineStatus()
|
|
|
|
{
|
|
|
|
uint64_t newMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::NewAdventurer );
|
|
|
|
uint64_t afkMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::Afk );
|
|
|
|
uint64_t busyMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::Busy );
|
|
|
|
uint64_t dcMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::Disconnected );
|
|
|
|
uint64_t meldMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::LfMeld );
|
|
|
|
uint64_t ptMask = uint64_t( 1 ) << static_cast< uint32_t >( OnlineStatus::LfParty );
|
|
|
|
|
|
|
|
OnlineStatus status = OnlineStatus::Online;
|
|
|
|
|
|
|
|
//if( hasStateFlag( Common::PlayerStateFlag::NewAdventurer ) )
|
|
|
|
if( m_onlineStatus & newMask )
|
|
|
|
status = OnlineStatus::NewAdventurer;
|
|
|
|
|
|
|
|
if( m_onlineStatus & afkMask )
|
|
|
|
status = OnlineStatus::Afk;
|
|
|
|
|
|
|
|
if( m_onlineStatus & busyMask )
|
|
|
|
status = OnlineStatus::Busy;
|
|
|
|
|
|
|
|
if( m_onlineStatus & dcMask )
|
|
|
|
status = OnlineStatus::Disconnected;
|
|
|
|
|
|
|
|
if( m_onlineStatus & meldMask )
|
|
|
|
status = OnlineStatus::LfMeld;
|
|
|
|
|
|
|
|
if( m_onlineStatus & ptMask )
|
|
|
|
status = OnlineStatus::LfParty;
|
|
|
|
|
|
|
|
if( hasStateFlag( PlayerStateFlag::WatchingCutscene ) || hasStateFlag( PlayerStateFlag::WatchingCutscene1 ) )
|
|
|
|
status = OnlineStatus::Cutscene;
|
|
|
|
|
|
|
|
// TODO: add all the logic for returning the proper online status, there probably is a better way for this alltogether
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setOnlineStatusMask( uint64_t status )
|
|
|
|
{
|
|
|
|
m_onlineStatus = status;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Core::Entity::Player::getOnlineStatusMask() const
|
|
|
|
{
|
|
|
|
return m_onlineStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::prepareZoning( uint16_t targetZone, bool fadeOut )
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVIpcPrepareZoning > preparePacket( getId() );
|
|
|
|
preparePacket.data().targetZone = targetZone;
|
|
|
|
preparePacket.data().fadeOut = fadeOut == true ? 1 : 0;
|
|
|
|
queuePacket( preparePacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::calculateStats()
|
|
|
|
{
|
|
|
|
uint8_t tribe = getLookAt( Common::CharaLook::Tribe );
|
|
|
|
uint8_t level = getLevel();
|
|
|
|
uint8_t job = getClass();
|
|
|
|
|
|
|
|
auto classInfoIt = g_exdData.m_classJobInfoMap.find( job );
|
|
|
|
auto tribeInfoIt = g_exdData.m_tribeInfoMap.find( tribe );
|
|
|
|
auto paramGrowthInfoIt = g_exdData.m_paramGrowthInfoMap.find( level );
|
|
|
|
|
|
|
|
if( tribeInfoIt == g_exdData.m_tribeInfoMap.end() ||
|
|
|
|
classInfoIt == g_exdData.m_classJobInfoMap.end() ||
|
|
|
|
paramGrowthInfoIt == g_exdData.m_paramGrowthInfoMap.end() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto tribeInfo = tribeInfoIt->second;
|
|
|
|
auto classInfo = classInfoIt->second;
|
|
|
|
auto paramGrowthInfo = paramGrowthInfoIt->second;
|
|
|
|
|
|
|
|
// TODO: put formula somewhere else...
|
|
|
|
float base = 0.0f;
|
|
|
|
|
|
|
|
if( level < 51 )
|
|
|
|
base = 0.053f * ( level * level ) + ( 1.022f * level ) - 0.907f + 20;
|
|
|
|
else
|
|
|
|
base = 1.627f * level + 120.773f;
|
|
|
|
|
|
|
|
m_baseStats.str = base * ( static_cast< float >( classInfo.mod_str ) / 100 ) + tribeInfo.mod_str;
|
|
|
|
m_baseStats.dex = base * ( static_cast< float >( classInfo.mod_dex ) / 100 ) + tribeInfo.mod_dex;
|
|
|
|
m_baseStats.vit = base * ( static_cast< float >( classInfo.mod_vit ) / 100 ) + tribeInfo.mod_vit;
|
|
|
|
m_baseStats.inte = base * ( static_cast< float >( classInfo.mod_int ) / 100 ) + tribeInfo.mod_int;
|
|
|
|
m_baseStats.mnd = base * ( static_cast< float >( classInfo.mod_mnd ) / 100 ) + tribeInfo.mod_mnd;
|
|
|
|
m_baseStats.pie = base * ( static_cast< float >( classInfo.mod_pie ) / 100 ) + tribeInfo.mod_pie;
|
|
|
|
|
|
|
|
m_baseStats.skillSpeed = paramGrowthInfo.base_secondary;
|
|
|
|
m_baseStats.spellSpeed = paramGrowthInfo.base_secondary;
|
|
|
|
m_baseStats.accuracy = paramGrowthInfo.base_secondary;
|
|
|
|
m_baseStats.critHitRate = paramGrowthInfo.base_secondary;
|
|
|
|
m_baseStats.attackPotMagic = paramGrowthInfo.base_secondary;
|
|
|
|
m_baseStats.healingPotMagic = paramGrowthInfo.base_secondary;
|
|
|
|
|
|
|
|
m_baseStats.max_mp = floor(
|
|
|
|
floor(
|
|
|
|
( ( m_baseStats.pie - base ) * ( static_cast< float >( paramGrowthInfo.piety_scalar ) / 100 ) ) + paramGrowthInfo.mp_const ) * ( static_cast< float >( classInfo.mod_mpcpgp ) / 100 )
|
|
|
|
);
|
|
|
|
|
|
|
|
m_baseStats.max_hp = floor(
|
|
|
|
floor(
|
|
|
|
( ( m_baseStats.vit - base ) * ( ( static_cast< float >( paramGrowthInfo.piety_scalar ) ) / 100 ) ) + paramGrowthInfo.hp_mod ) * ( static_cast< float >( classInfo.mod_hp * 0.9f ) / 100 ) * 15
|
|
|
|
);
|
|
|
|
|
|
|
|
if( m_mp > m_baseStats.max_mp )
|
|
|
|
m_mp = m_baseStats.max_mp;
|
|
|
|
|
|
|
|
if( m_hp > m_baseStats.max_hp )
|
|
|
|
m_hp = m_baseStats.max_hp;
|
|
|
|
|
|
|
|
|
|
|
|
m_baseStats.determination = base;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Core::Entity::Player::setAutoattack(bool mode)
|
|
|
|
{
|
|
|
|
m_bAutoattack = mode;
|
|
|
|
m_lastAttack = Util::getTimeMs();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isAutoattackOn() const
|
|
|
|
{
|
|
|
|
return m_bAutoattack;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendStats()
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVIpcPlayerStats > statPacket( getId() );
|
|
|
|
statPacket.data().strength = m_baseStats.str;
|
|
|
|
statPacket.data().dexterity = m_baseStats.dex;
|
|
|
|
statPacket.data().vitality = m_baseStats.vit;
|
|
|
|
statPacket.data().intelligence = m_baseStats.inte;
|
|
|
|
statPacket.data().mind = m_baseStats.mnd;
|
|
|
|
statPacket.data().piety = m_baseStats.pie;
|
|
|
|
statPacket.data().determination = m_baseStats.determination;
|
|
|
|
statPacket.data().hp = m_baseStats.max_hp;
|
|
|
|
statPacket.data().mp = m_baseStats.max_mp;
|
|
|
|
statPacket.data().accuracy = m_baseStats.accuracy;
|
|
|
|
statPacket.data().attack = m_baseStats.attack;
|
|
|
|
statPacket.data().attackMagicPotency = m_baseStats.attackPotMagic;
|
|
|
|
statPacket.data().healingMagicPotency = m_baseStats.healingPotMagic;
|
|
|
|
statPacket.data().skillSpeed = m_baseStats.skillSpeed;
|
|
|
|
statPacket.data().spellSpeed = m_baseStats.spellSpeed;
|
|
|
|
statPacket.data().spellSpeed1 = m_baseStats.spellSpeed;
|
|
|
|
statPacket.data().spellSpeedMod = 100;
|
|
|
|
|
|
|
|
statPacket.data().criticalHitRate = m_baseStats.spellSpeed;
|
|
|
|
statPacket.data().defense = m_baseStats.spellSpeed;
|
|
|
|
statPacket.data().magicDefense = m_baseStats.spellSpeed;
|
|
|
|
statPacket.data().attack = m_baseStats.spellSpeed;
|
|
|
|
|
|
|
|
queuePacket( statPacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::teleport( uint16_t aetheryteId, uint8_t type )
|
|
|
|
{
|
|
|
|
auto data = g_exdData.getAetheryteInfo( aetheryteId );
|
|
|
|
|
|
|
|
if( data != nullptr )
|
|
|
|
{
|
|
|
|
|
|
|
|
setStateFlag( PlayerStateFlag::BetweenAreas );
|
|
|
|
sendStateFlags();
|
|
|
|
|
|
|
|
auto z_pos = g_zoneMgr.getZonePosition( data->levelId );
|
|
|
|
|
|
|
|
Common::FFXIVARR_POSITION3 pos;
|
|
|
|
pos.x = 0;
|
|
|
|
pos.y = 0;
|
|
|
|
pos.z = 0;
|
|
|
|
float rot = 0;
|
|
|
|
|
|
|
|
if( z_pos != nullptr )
|
|
|
|
{
|
|
|
|
pos = z_pos->getTargetPosition();
|
|
|
|
rot = z_pos->getTargetRotation();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sendDebug( "Teleport: " + data->placename + " " + data->placename_aethernet +
|
|
|
|
"(" + std::to_string( data->levelId ) + ")" );
|
|
|
|
|
|
|
|
// TODO: this should be simplified and a type created in server_common/common.h.
|
|
|
|
if( type == 1 ) // teleport
|
|
|
|
{
|
|
|
|
prepareZoning( data->target_zone, true );
|
|
|
|
sendToInRangeSet( ActorControlPacket142( getId(), ActorDespawnEffect, 0x04 ) );
|
|
|
|
setZoningType( Common::ZoneingType::Teleport );
|
|
|
|
}
|
|
|
|
else if( type == 2 ) // aethernet
|
|
|
|
{
|
|
|
|
prepareZoning( data->target_zone, true );
|
|
|
|
sendToInRangeSet( ActorControlPacket142( getId(), ActorDespawnEffect, 0x04 ) );
|
|
|
|
setZoningType( Common::ZoneingType::Teleport );
|
|
|
|
}
|
|
|
|
else if( type == 3 ) // return
|
|
|
|
{
|
|
|
|
prepareZoning( data->target_zone, true );
|
|
|
|
sendToInRangeSet( ActorControlPacket142( getId(), ActorDespawnEffect, 0x03 ) );
|
|
|
|
setZoningType( Common::ZoneingType::Return );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_queuedZoneing = boost::make_shared<QueuedZoning>( data->target_zone, pos, Util::getTimeMs(), rot );
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::forceZoneing( uint32_t zoneId )
|
|
|
|
{
|
|
|
|
m_queuedZoneing = boost::make_shared<QueuedZoning>( zoneId, getPos(), Util::getTimeMs(), 0 );
|
|
|
|
//performZoning( zoneId, Common::ZoneingType::None, getPos() );
|
|
|
|
}
|
|
|
|
|
2017-08-10 16:31:48 +02:00
|
|
|
void Core::Entity::Player::returnToHomepoint()
|
|
|
|
{
|
|
|
|
setZoningType( Common::ZoneingType::Return );
|
|
|
|
teleport( getHomepoint(), 3 );
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
void Core::Entity::Player::setZone( uint32_t zoneId )
|
|
|
|
{
|
|
|
|
auto pPlayer = getAsPlayer();
|
|
|
|
|
|
|
|
auto pZone = g_zoneMgr.getZone( zoneId );
|
|
|
|
|
|
|
|
|
|
|
|
if( !pZone /*|| ( ( pZone == m_pCurrentZone ) && m_lastPing )*/ )
|
|
|
|
{
|
|
|
|
g_log.error( "Zone " + std::to_string( zoneId ) + " not found on this server." );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_zoneId = zoneId;
|
|
|
|
|
|
|
|
// mark character as zoning in progress
|
|
|
|
setLoadingComplete( false );
|
|
|
|
|
|
|
|
if( m_lastPing != 0 )
|
|
|
|
m_pCurrentZone->removeActor( shared_from_this() );
|
|
|
|
|
|
|
|
m_pCurrentZone = pZone;
|
|
|
|
m_pCurrentZone->pushActor( shared_from_this() );
|
|
|
|
|
|
|
|
// mark the player for a position update in DB
|
|
|
|
setSyncFlag( PlayerSyncFlags::Position );
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcInit > initPacket( getId() );
|
|
|
|
initPacket.data().charId = getId();
|
|
|
|
queuePacket( initPacket );
|
|
|
|
|
|
|
|
sendInventory();
|
|
|
|
|
|
|
|
// set flags, will be reset automatically by zoning ( only on client side though )
|
|
|
|
pPlayer->setStateFlag( PlayerStateFlag::BetweenAreas );
|
|
|
|
pPlayer->setStateFlag( PlayerStateFlag::BetweenAreas1 );
|
|
|
|
pPlayer->sendStateFlags();
|
|
|
|
|
|
|
|
pPlayer->sendStats();
|
|
|
|
|
|
|
|
// only initialize the UI if the player in fact just logged in.
|
|
|
|
if( isLogin() )
|
|
|
|
{
|
2017-08-10 00:41:37 +09:00
|
|
|
GamePacketNew< FFXIVIpcCFAvailableContents > contentFinderList( getId() );
|
|
|
|
for( auto i = 0; i < 72; i++ )
|
|
|
|
{
|
|
|
|
// unlock all contents for now
|
|
|
|
contentFinderList.data().contents[i] = 0xFF;
|
|
|
|
}
|
|
|
|
queuePacket( contentFinderList );
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
Server::InitUIPacket initUIPacket( pPlayer );
|
|
|
|
queuePacket( initUIPacket );
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcPlayerClassInfo > classInfoPacket( getId() );
|
|
|
|
classInfoPacket.data().classId = getClass();
|
|
|
|
classInfoPacket.data().unknown = 1;
|
|
|
|
classInfoPacket.data().level = getLevel();
|
|
|
|
classInfoPacket.data().level1 = getLevel();
|
|
|
|
queuePacket( classInfoPacket );
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVGCAffiliation > gcAffPacket( getId() );
|
|
|
|
gcAffPacket.data().gcId = m_gc;
|
|
|
|
gcAffPacket.data().gcRank[0] = m_gcRank[0];
|
|
|
|
gcAffPacket.data().gcRank[1] = m_gcRank[1];
|
|
|
|
gcAffPacket.data().gcRank[2] = m_gcRank[2];
|
|
|
|
queuePacket( gcAffPacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcInitZone > initZonePacket( getId() );
|
|
|
|
initZonePacket.data().zoneId = getCurrentZone()->getLayoutId();
|
|
|
|
initZonePacket.data().weatherId = static_cast< uint8_t >( getCurrentZone()->getCurrentWeather() );
|
|
|
|
initZonePacket.data().bitmask = 0x2A;
|
|
|
|
initZonePacket.data().pos.x = getPos().x;
|
|
|
|
initZonePacket.data().pos.y = getPos().y;
|
|
|
|
initZonePacket.data().pos.z = getPos().z;
|
|
|
|
queuePacket( initZonePacket );
|
|
|
|
|
|
|
|
if( isLogin() )
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVARR_IPC_UNK322 > unk322( getId() );
|
|
|
|
queuePacket( unk322 );
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVARR_IPC_UNK320 > unk320( getId() );
|
|
|
|
queuePacket( unk320 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( getLastPing() == 0 )
|
|
|
|
sendQuestInfo();
|
|
|
|
|
|
|
|
m_bMarkedForZoning = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Core::Entity::Player::getPlayTime() const
|
|
|
|
{
|
|
|
|
return m_playTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getRace() const
|
|
|
|
{
|
|
|
|
return getLookAt( CharaLook::Race );
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getGender() const
|
|
|
|
{
|
|
|
|
return getLookAt( CharaLook::Gender );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::initSpawnIdQueue()
|
|
|
|
{
|
|
|
|
while( !m_freeSpawnIdQueue.empty() )
|
|
|
|
{
|
|
|
|
m_freeSpawnIdQueue.pop();
|
|
|
|
}
|
|
|
|
|
2017-08-11 22:56:30 +01:00
|
|
|
for( int32_t i = 1; i < MAX_DISPLAYED_ACTORS; i++ )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
m_freeSpawnIdQueue.push( i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getSpawnIdForActorId( uint32_t actorId )
|
|
|
|
{
|
|
|
|
if( m_freeSpawnIdQueue.empty() )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
uint8_t spawnId = m_freeSpawnIdQueue.front();
|
|
|
|
m_freeSpawnIdQueue.pop();
|
|
|
|
m_playerIdToSpawnIdMap[actorId] = spawnId;
|
|
|
|
return spawnId;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::assignSpawnIdToPlayerId( uint32_t actorId, uint8_t spawnId )
|
|
|
|
{
|
|
|
|
m_playerIdToSpawnIdMap[actorId] = spawnId;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::registerAetheryte( uint8_t aetheryteId )
|
|
|
|
{
|
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( aetheryteId, value, index );
|
|
|
|
|
|
|
|
m_aetheryte[index] |= value;
|
|
|
|
|
|
|
|
setSyncFlag( Aetherytes );
|
|
|
|
|
|
|
|
queuePacket( ActorControlPacket143( getId(), LearnTeleport, aetheryteId, 1 ) );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isAetheryteRegistered( uint8_t aetheryteId ) const
|
|
|
|
{
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( aetheryteId, value, index );
|
|
|
|
|
|
|
|
return m_aetheryte[index] & value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t * Core::Entity::Player::getDiscoveryBitmask()
|
|
|
|
{
|
|
|
|
return m_discovery;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::discover( int16_t map_id, int16_t sub_id )
|
|
|
|
{
|
|
|
|
// map.exd field 12 -> index in one of the two discovery sections, if field 15 is false, need to use 2nd section
|
|
|
|
// section 1 starts at 4 - 2 bytes each
|
|
|
|
|
|
|
|
// section to starts at 320 - 4 bytes long
|
|
|
|
|
|
|
|
int32_t offset = 4;
|
|
|
|
|
|
|
|
auto info = g_exdData.m_zoneInfoMap[getCurrentZone()->getId()];
|
|
|
|
if( info.is_two_byte )
|
|
|
|
offset = 4 + 2 * info.discovery_index;
|
|
|
|
else
|
|
|
|
offset = 324 + 4 * info.discovery_index;
|
|
|
|
|
|
|
|
int32_t index = offset + sub_id / 8;
|
|
|
|
uint8_t bitIndex = sub_id % 8;
|
|
|
|
|
|
|
|
uint8_t value = 1 << bitIndex;
|
|
|
|
|
|
|
|
m_discovery[index] |= value;
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::Discovery );
|
|
|
|
|
|
|
|
uint16_t level = getLevel();
|
|
|
|
|
|
|
|
uint32_t exp = ( g_exdData.m_paramGrowthInfoMap[level].needed_exp * 5 / 100 );
|
|
|
|
|
|
|
|
gainExp( exp );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isNewAdventurer() const
|
|
|
|
{
|
|
|
|
return m_bNewAdventurer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setNewAdventurer( bool state )
|
|
|
|
{
|
2017-08-13 18:28:05 +02:00
|
|
|
//if( !state )
|
|
|
|
//{
|
|
|
|
// unsetStateFlag( PlayerStateFlag::NewAdventurer );
|
|
|
|
//}
|
|
|
|
//else
|
|
|
|
//{
|
|
|
|
// setStateFlag( PlayerStateFlag::NewAdventurer );
|
|
|
|
//}
|
2017-08-08 13:53:47 +02:00
|
|
|
sendStateFlags();
|
|
|
|
m_bNewAdventurer = state;
|
|
|
|
setSyncFlag( PlayerSyncFlags::NewAdventurer );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::resetDiscovery()
|
|
|
|
{
|
|
|
|
memset( m_discovery, 0, sizeof( m_discovery ) );
|
|
|
|
setSyncFlag( PlayerSyncFlags::Discovery );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::changePosition( float x, float y, float z, float o )
|
|
|
|
{
|
|
|
|
Common::FFXIVARR_POSITION3 pos;
|
|
|
|
pos.x = x;
|
|
|
|
pos.y = y;
|
|
|
|
pos.z = z;
|
|
|
|
m_queuedZoneing = boost::make_shared<QueuedZoning>( getZoneId(), pos, Util::getTimeMs(), o );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::learnAction( uint8_t actionId )
|
|
|
|
{
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( actionId, value, index );
|
|
|
|
|
|
|
|
m_unlocks[index] |= value;
|
|
|
|
|
|
|
|
setSyncFlag( Unlocks );
|
|
|
|
queuePacket( ActorControlPacket143( getId(), ToggleActionUnlock, actionId, 1 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isActionLearned( uint8_t actionId ) const
|
|
|
|
{
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( actionId, value, index );
|
|
|
|
|
|
|
|
return m_unlocks[index] & value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::gainExp( uint32_t amount )
|
|
|
|
{
|
|
|
|
uint32_t currentExp = getExp();
|
|
|
|
|
|
|
|
uint16_t level = getLevel();
|
|
|
|
|
|
|
|
uint32_t neededExpToLevel = g_exdData.m_paramGrowthInfoMap[level].needed_exp;
|
|
|
|
|
|
|
|
uint32_t neededExpToLevelplus1 = g_exdData.m_paramGrowthInfoMap[level + 1].needed_exp;
|
|
|
|
|
|
|
|
queuePacket( ActorControlPacket143( getId(), GainExpMsg, static_cast< uint8_t >( getClass() ), amount ) );
|
|
|
|
|
2017-08-11 00:58:35 -03:00
|
|
|
if( level >= 70 ) // temporary fix for leveling over levelcap
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
queuePacket( ActorControlPacket143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), amount ) );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ( currentExp + amount ) >= neededExpToLevel )
|
|
|
|
{
|
|
|
|
// levelup
|
|
|
|
amount = ( currentExp + amount - neededExpToLevel ) > neededExpToLevelplus1 ? neededExpToLevelplus1 - 1 : ( currentExp + amount - neededExpToLevel );
|
|
|
|
setExp( amount );
|
|
|
|
gainLevel();
|
|
|
|
queuePacket( ActorControlPacket143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), amount ) );
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
queuePacket( ActorControlPacket143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), currentExp + amount ) );
|
|
|
|
setExp( currentExp + amount );
|
|
|
|
}
|
|
|
|
|
|
|
|
sendStatusUpdate();
|
|
|
|
setSyncFlag( PlayerSyncFlags::ExpLevel );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::gainLevel()
|
|
|
|
{
|
|
|
|
setLevel( getLevel() + 1 );
|
|
|
|
|
|
|
|
calculateStats();
|
|
|
|
sendStats();
|
|
|
|
sendStatusUpdate();
|
|
|
|
|
|
|
|
m_hp = getMaxHp();
|
|
|
|
m_mp = getMaxMp();
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcStatusEffectList > effectListPacket( getId() );
|
|
|
|
effectListPacket.data().classId = getClass();
|
|
|
|
effectListPacket.data().classId1 = getClass();
|
|
|
|
effectListPacket.data().level = getLevel();
|
|
|
|
effectListPacket.data().current_hp = getMaxHp();
|
|
|
|
effectListPacket.data().current_mp = getMaxMp();
|
|
|
|
effectListPacket.data().currentTp = 1000;
|
|
|
|
effectListPacket.data().max_hp = getMaxHp();
|
|
|
|
effectListPacket.data().max_mp = getMaxMp();
|
|
|
|
sendToInRangeSet( effectListPacket, true );
|
|
|
|
|
|
|
|
sendToInRangeSet( ActorControlPacket142( getId(), LevelUpEffect, static_cast< uint8_t >( getClass() ),
|
|
|
|
getLevel(), getLevel() - 1 ), true );
|
|
|
|
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcUpdateClassInfo > classInfoPacket( getId() );
|
|
|
|
classInfoPacket.data().classId = getClass();
|
|
|
|
classInfoPacket.data().classId1 = getClass();
|
|
|
|
classInfoPacket.data().level = getLevel();
|
|
|
|
classInfoPacket.data().nextLevelIndex = getLevel();
|
|
|
|
classInfoPacket.data().currentExp = getExp();
|
|
|
|
queuePacket( classInfoPacket );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::unlock()
|
|
|
|
{
|
|
|
|
queuePacket( PlayerStateFlagsPacket( getAsPlayer(), PlayerStateFlagList{} ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendStatusUpdate( bool toSelf )
|
|
|
|
{
|
|
|
|
// CGamePacket* pPE = new CGamePacket(0x140, 0x0128, getId(), getId());
|
|
|
|
|
|
|
|
//pPE->setInt8At(0x20, static_cast<uint8_t>(getClass()));
|
|
|
|
|
|
|
|
// pPE->setInt8At(0x21, getLevel());
|
|
|
|
// pPE->setInt8At(0x22, getLevel());
|
|
|
|
|
|
|
|
// // current exp
|
|
|
|
// pPE->setInt32At(0x28, getExp());
|
|
|
|
|
|
|
|
// // rested exp
|
|
|
|
// //pPE->setInt32At(0x2C, m_hp);
|
|
|
|
|
|
|
|
// pPE->setInt32At(0x24, m_hp);
|
|
|
|
// pPE->setInt32At(0x28, getMaxHp());
|
|
|
|
// pPE->setInt16At(0x2C, m_mp);
|
|
|
|
// pPE->setInt16At(0x2E, getMaxMp());
|
|
|
|
// pPE->setInt16At(0x30, m_tp);
|
|
|
|
|
|
|
|
// sendToInRangeSet(pPE, toSelf);
|
|
|
|
|
|
|
|
sendToInRangeSet( UpdateHpMpTpPacket( shared_from_this() ), true );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getLevel() const
|
|
|
|
{
|
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( getClass() )].exp_idx;
|
|
|
|
return static_cast< uint8_t >( m_classArray[classJobIndex] );
|
|
|
|
}
|
|
|
|
|
2017-08-09 14:38:46 +02:00
|
|
|
uint8_t Core::Entity::Player::getLevelForClass( Core::Common::ClassJob pClass ) const
|
|
|
|
{
|
2017-08-10 00:41:37 +09:00
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( pClass )].exp_idx;
|
|
|
|
return static_cast< uint8_t >( m_classArray[classJobIndex] );
|
2017-08-09 14:38:46 +02:00
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
uint32_t Core::Entity::Player::getExp() const
|
|
|
|
{
|
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( getClass() )].exp_idx;
|
|
|
|
return m_expArray[classJobIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setExp( uint32_t amount )
|
|
|
|
{
|
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( getClass() )].exp_idx;
|
|
|
|
m_expArray[classJobIndex] = amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isInCombat() const
|
|
|
|
{
|
|
|
|
return m_bInCombat;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setInCombat( bool mode )
|
|
|
|
{
|
|
|
|
//m_lastAttack = GetTickCount();
|
|
|
|
m_bInCombat = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setClassJob( Core::Common::ClassJob classJob )
|
|
|
|
{
|
|
|
|
m_class = classJob;
|
|
|
|
uint8_t level = getLevel();
|
|
|
|
|
|
|
|
if( getHp() > getMaxHp() )
|
|
|
|
m_hp = getMaxHp();
|
|
|
|
|
|
|
|
if( getMp() > getMaxMp() )
|
|
|
|
m_mp = getMaxMp();
|
|
|
|
|
|
|
|
m_tp = 0;
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcPlayerClassInfo > classInfoPacket( getId() );
|
|
|
|
classInfoPacket.data().classId = getClass();
|
|
|
|
classInfoPacket.data().level = getLevel();
|
|
|
|
queuePacket( classInfoPacket );
|
|
|
|
|
|
|
|
sendToInRangeSet( ActorControlPacket142( getId(), ClassJobChange, 0x04 ), true );
|
|
|
|
|
|
|
|
setSyncFlag( Status );
|
|
|
|
sendStatusUpdate( true );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setLevel( uint8_t level )
|
|
|
|
{
|
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( getClass() )].exp_idx;
|
|
|
|
m_classArray[classJobIndex] = level;
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::ExpLevel );
|
|
|
|
}
|
|
|
|
|
2017-08-09 14:38:46 +02:00
|
|
|
void Core::Entity::Player::setLevelForClass( uint8_t level, Core::Common::ClassJob classjob )
|
|
|
|
{
|
|
|
|
uint8_t classJobIndex = g_exdData.m_classJobInfoMap[static_cast< uint8_t >( classjob )].exp_idx;
|
|
|
|
m_classArray[classJobIndex] = level;
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::ExpLevel );
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
uint8_t Core::Entity::Player::getUserLevel() const
|
|
|
|
{
|
|
|
|
return m_userLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isFirstLogin() const
|
|
|
|
{
|
|
|
|
return m_bFirstLogin;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setFirstLogin( bool mode )
|
|
|
|
{
|
|
|
|
m_bFirstLogin = mode;
|
|
|
|
setSyncFlag( PlayerSyncFlags::FirstLogin );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::eventActionStart( uint32_t eventId,
|
|
|
|
uint32_t action,
|
|
|
|
ActionCallback finishCallback,
|
|
|
|
ActionCallback interruptCallback,
|
|
|
|
uint64_t additional )
|
|
|
|
{
|
|
|
|
Action::ActionPtr pEventAction( new Action::EventAction( shared_from_this(), eventId, action,
|
|
|
|
finishCallback, interruptCallback, additional ) );
|
|
|
|
|
|
|
|
setCurrentAction( pEventAction );
|
|
|
|
auto pEvent = getEvent( eventId );
|
|
|
|
|
|
|
|
if( !pEvent && getEventCount() )
|
|
|
|
{
|
|
|
|
// We're trying to play a nested event, need to start it first.
|
|
|
|
eventStart( getId(), eventId, Event::Event::Nest, 0, 0 );
|
|
|
|
pEvent = getEvent( eventId );
|
|
|
|
}
|
|
|
|
else if( !pEvent )
|
|
|
|
{
|
|
|
|
g_log.error( "Could not find event " + std::to_string( eventId ) + ", event has not been started!" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( pEvent )
|
|
|
|
pEvent->setPlayedScene( true );
|
|
|
|
pEventAction->onStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Core::Entity::Player::eventItemActionStart( uint32_t eventId,
|
|
|
|
uint32_t action,
|
|
|
|
ActionCallback finishCallback,
|
|
|
|
ActionCallback interruptCallback,
|
|
|
|
uint64_t additional )
|
|
|
|
{
|
|
|
|
Action::ActionPtr pEventItemAction( new Action::EventItemAction( shared_from_this(), eventId, action,
|
|
|
|
finishCallback, interruptCallback, additional ) );
|
|
|
|
|
|
|
|
setCurrentAction( pEventItemAction );
|
|
|
|
|
|
|
|
pEventItemAction->onStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendModel()
|
|
|
|
{
|
|
|
|
ModelEquipPacket modelEquip( getAsPlayer() );
|
|
|
|
sendToInRangeSet( modelEquip, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Core::Entity::Player::getModelForSlot( Inventory::EquipSlot slot )
|
|
|
|
{
|
|
|
|
return m_modelEquip[slot];
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Core::Entity::Player::getModelMainWeapon() const
|
|
|
|
{
|
|
|
|
return m_modelMainWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Core::Entity::Player::getModelSubWeapon() const
|
|
|
|
{
|
|
|
|
return m_modelSubWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Core::Entity::Player::getModelSystemWeapon() const
|
|
|
|
{
|
|
|
|
return m_modelSystemWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t Core::Entity::Player::getAetheryteMaskAt( uint8_t index ) const
|
|
|
|
{
|
|
|
|
if( index > 11 )
|
|
|
|
return 0;
|
|
|
|
return m_aetheryte[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getBirthDay() const
|
|
|
|
{
|
|
|
|
return m_birthDay;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getBirthMonth() const
|
|
|
|
{
|
|
|
|
return m_birthMonth;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getGuardianDeity() const
|
|
|
|
{
|
|
|
|
return m_guardianDeity;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getLookAt( uint8_t index ) const
|
|
|
|
{
|
|
|
|
return m_customize[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setLookAt( uint8_t index, uint8_t value )
|
|
|
|
{
|
|
|
|
m_customize[index] = value;
|
|
|
|
setSyncFlag( PlayerSyncFlags::Look );
|
|
|
|
}
|
|
|
|
|
|
|
|
// spawn this player for pTarget
|
|
|
|
void Core::Entity::Player::spawn( Core::Entity::PlayerPtr pTarget )
|
|
|
|
{
|
|
|
|
g_log.debug( "[" + std::to_string( pTarget->getId() ) + "] Spawning " +
|
|
|
|
getName() + " for " +
|
|
|
|
pTarget->getName() );
|
|
|
|
|
|
|
|
PlayerSpawnPacket spawnActor( getAsPlayer(), pTarget );
|
|
|
|
pTarget->queuePacket( spawnActor );
|
|
|
|
}
|
|
|
|
|
|
|
|
// despawn
|
|
|
|
void Core::Entity::Player::despawn( Core::Entity::ActorPtr pTarget )
|
|
|
|
{
|
|
|
|
auto pPlayer = pTarget->getAsPlayer();
|
|
|
|
|
|
|
|
pPlayer->freePlayerSpawnId( getId() );
|
|
|
|
|
|
|
|
pPlayer->queuePacket( ActorControlPacket143( getId(), DespawnZoneScreenMsg, 0x04, getId(), 0x01 ) );
|
|
|
|
}
|
|
|
|
|
2017-08-10 22:06:05 +02:00
|
|
|
Core::Entity::ActorPtr Core::Entity::Player::lookupTargetById( uint64_t targetId )
|
|
|
|
{
|
|
|
|
Core::Entity::ActorPtr targetActor;
|
|
|
|
auto inRange = getInRangeActors( true );
|
|
|
|
for( auto actor : inRange )
|
|
|
|
{
|
|
|
|
if( actor->getId() == targetId )
|
|
|
|
{
|
|
|
|
targetActor = actor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return targetActor;
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
void Core::Entity::Player::setLastPing( uint32_t ping )
|
|
|
|
{
|
|
|
|
m_lastPing = ping;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Core::Entity::Player::getLastPing() const
|
|
|
|
{
|
|
|
|
return m_lastPing;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Core::Entity::Player::setVoiceId( uint8_t voiceId )
|
|
|
|
{
|
|
|
|
m_voice = voiceId;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setGc( uint8_t gc )
|
|
|
|
{
|
|
|
|
m_gc = gc;
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVGCAffiliation > gcAffPacket( getId() );
|
|
|
|
gcAffPacket.data().gcId = m_gc;
|
|
|
|
gcAffPacket.data().gcRank[0] = m_gcRank[0];
|
|
|
|
gcAffPacket.data().gcRank[1] = m_gcRank[1];
|
|
|
|
gcAffPacket.data().gcRank[2] = m_gcRank[2];
|
|
|
|
queuePacket( gcAffPacket );
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::GC );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setGcRankAt( uint8_t index, uint8_t rank )
|
|
|
|
{
|
|
|
|
m_gcRank[index] = rank;
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVGCAffiliation > gcAffPacket( getId() );
|
|
|
|
gcAffPacket.data().gcId = m_gc;
|
|
|
|
gcAffPacket.data().gcRank[0] = m_gcRank[0];
|
|
|
|
gcAffPacket.data().gcRank[1] = m_gcRank[1];
|
|
|
|
gcAffPacket.data().gcRank[2] = m_gcRank[2];
|
|
|
|
queuePacket( gcAffPacket );
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::GC );
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t * Core::Entity::Player::getStateFlags() const
|
|
|
|
{
|
|
|
|
return m_stateFlags;
|
|
|
|
}
|
|
|
|
|
2017-08-10 22:06:05 +02:00
|
|
|
bool Core::Entity::Player::actionHasCastTime( uint32_t actionId ) //TODO: Add logic for special cases
|
|
|
|
{
|
|
|
|
return g_exdData.m_actionInfoMap[actionId].is_instant;
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
bool Core::Entity::Player::hasStateFlag( Core::Common::PlayerStateFlag flag ) const
|
|
|
|
{
|
2017-08-11 22:56:30 +01:00
|
|
|
int32_t iFlag = static_cast< uint32_t >( flag );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( iFlag, value, index );
|
|
|
|
|
|
|
|
return m_stateFlags[index] & value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setStateFlag( Core::Common::PlayerStateFlag flag )
|
|
|
|
{
|
2017-08-11 22:56:30 +01:00
|
|
|
int32_t iFlag = static_cast< uint32_t >( flag );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( iFlag, value, index );
|
|
|
|
|
|
|
|
m_stateFlags[index] |= value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-08-13 18:28:05 +02:00
|
|
|
void Core::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
|
|
|
|
{
|
|
|
|
for( const auto& flag : flags )
|
|
|
|
{
|
|
|
|
int iFlag = static_cast< uint32_t >( flag );
|
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( iFlag, value, index );
|
|
|
|
|
|
|
|
m_stateFlags[index] |= value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
void Core::Entity::Player::sendStateFlags()
|
|
|
|
{
|
|
|
|
queuePacket( PlayerStateFlagsPacket( getAsPlayer() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::unsetStateFlag( Core::Common::PlayerStateFlag flag )
|
|
|
|
{
|
|
|
|
if( !hasStateFlag( flag ) )
|
|
|
|
return;
|
|
|
|
|
2017-08-11 22:56:30 +01:00
|
|
|
int32_t iFlag = static_cast< uint32_t >( flag );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Util::valueToFlagByteIndexValue( iFlag, value, index );
|
|
|
|
|
|
|
|
m_stateFlags[index] ^= value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::update( int64_t currTime )
|
|
|
|
{
|
|
|
|
|
|
|
|
// a zoning is pending, lets do it
|
|
|
|
if( m_queuedZoneing && ( currTime - m_queuedZoneing->m_queueTime ) > 800 )
|
|
|
|
{
|
|
|
|
Common::FFXIVARR_POSITION3 targetPos = m_queuedZoneing->m_targetPosition;
|
|
|
|
if( getCurrentZone()->getId() != m_queuedZoneing->m_targetZone )
|
|
|
|
{
|
2017-08-13 23:39:04 +02:00
|
|
|
performZoning( m_queuedZoneing->m_targetZone, targetPos, m_queuedZoneing->m_targetRotation);
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVIpcActorSetPos > setActorPosPacket( getId() );
|
|
|
|
setActorPosPacket.data().r16 = Math::Util::floatToUInt16Rot( m_queuedZoneing->m_targetRotation );
|
|
|
|
setActorPosPacket.data().waitForLoad = 0x04;
|
|
|
|
setActorPosPacket.data().x = targetPos.x;
|
|
|
|
setActorPosPacket.data().y = targetPos.y;
|
|
|
|
setActorPosPacket.data().z = targetPos.z;
|
|
|
|
sendToInRangeSet( setActorPosPacket, true );
|
|
|
|
setPosition( targetPos );
|
|
|
|
}
|
|
|
|
m_queuedZoneing.reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( m_hp <= 0 && m_status != ActorStatus::Dead )
|
|
|
|
{
|
|
|
|
die();
|
|
|
|
setSyncFlag( PlayerSyncFlags::Status );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !isAlive() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pStatusEffectContainer->update();
|
|
|
|
|
|
|
|
m_lastUpdate = currTime;
|
|
|
|
|
|
|
|
// @TODO needs to happen in a if check. Don't want autoattacking while an action is being performed.
|
|
|
|
if( !checkAction() )
|
|
|
|
{
|
|
|
|
if( m_targetId )
|
|
|
|
{
|
|
|
|
auto mainWeap = m_pInventory->getItemAt( Inventory::GearSet0, Inventory::EquipSlot::MainHand );
|
|
|
|
|
|
|
|
for( auto actor : m_inRangeActors )
|
|
|
|
{
|
|
|
|
if( isAutoattackOn() &&
|
|
|
|
actor->getId() == m_targetId &&
|
|
|
|
actor->isAlive() &&
|
2017-08-11 00:58:35 -03:00
|
|
|
mainWeap &&
|
|
|
|
m_currentStance == Entity::Actor::Stance::Active
|
|
|
|
)
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
// default autoattack range
|
|
|
|
// TODO make this dependant on bnpc size
|
|
|
|
uint32_t range = 7;
|
|
|
|
|
|
|
|
// default autoattack range for ranged classes
|
|
|
|
if( getClass() == JOB_MACHINIST ||
|
|
|
|
getClass() == JOB_BARD ||
|
|
|
|
getClass() == CLASS_ARCHER )
|
|
|
|
range = 25;
|
|
|
|
|
|
|
|
|
|
|
|
if( Math::Util::distance(getPos().x, getPos().y, getPos().z,
|
|
|
|
actor->getPos().x, actor->getPos().y, actor->getPos().z) <= range )
|
|
|
|
{
|
|
|
|
|
|
|
|
if( ( currTime - m_lastAttack ) > mainWeap->getDelay() )
|
|
|
|
{
|
|
|
|
m_lastAttack = currTime;
|
|
|
|
autoAttack( actor );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ( currTime - m_lastTickTime ) > 3000 )
|
|
|
|
{
|
|
|
|
// add 3 seconds to total play time
|
|
|
|
m_playTime += 3;
|
|
|
|
setSyncFlag( PlayerSyncFlags::PlayTime );
|
|
|
|
|
|
|
|
m_lastTickTime = currTime;
|
|
|
|
onTick();
|
|
|
|
}
|
|
|
|
|
|
|
|
createUpdateSql();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::onMobKill( uint16_t nameId )
|
|
|
|
{
|
|
|
|
g_scriptMgr.onMobKill( getAsPlayer(), nameId );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::freePlayerSpawnId( uint32_t actorId )
|
|
|
|
{
|
|
|
|
uint8_t spawnId = m_playerIdToSpawnIdMap[actorId];
|
|
|
|
m_playerIdToSpawnIdMap.erase( actorId );
|
|
|
|
m_freeSpawnIdQueue.push( spawnId );
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcActorFreeSpawn > freeActorSpawnPacket( getId() );
|
|
|
|
freeActorSpawnPacket.data().actorId = actorId;
|
|
|
|
freeActorSpawnPacket.data().spawnId = spawnId;
|
|
|
|
queuePacket( freeActorSpawnPacket );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setSyncFlag( uint32_t updateFlag )
|
|
|
|
{
|
|
|
|
m_updateFlags |= updateFlag;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t * Core::Entity::Player::getAetheryteArray()
|
|
|
|
{
|
|
|
|
return m_aetheryte;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*! set homepoint */
|
|
|
|
void Core::Entity::Player::setHomepoint( uint8_t aetheryteId )
|
|
|
|
{
|
|
|
|
m_homePoint = aetheryteId;
|
|
|
|
|
|
|
|
queuePacket( ActorControlPacket143( getId(), SetHomepoint, aetheryteId ) );
|
|
|
|
|
|
|
|
setSyncFlag( HomePoint );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*! get homepoint */
|
|
|
|
uint8_t Core::Entity::Player::getHomepoint() const
|
|
|
|
{
|
|
|
|
return m_homePoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t * Core::Entity::Player::getClassArray()
|
|
|
|
{
|
|
|
|
return m_classArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint16_t * Core::Entity::Player::getClassArray() const
|
|
|
|
{
|
|
|
|
return m_classArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t * Core::Entity::Player::getLookArray() const
|
|
|
|
{
|
|
|
|
return m_customize;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t * Core::Entity::Player::getModelArray() const
|
|
|
|
{
|
|
|
|
return m_modelEquip;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t * Core::Entity::Player::getExpArray()
|
|
|
|
{
|
|
|
|
return m_expArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t * Core::Entity::Player::getExpArray() const
|
|
|
|
{
|
|
|
|
return m_expArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t * Core::Entity::Player::getHowToArray()
|
|
|
|
{
|
|
|
|
return m_howTo;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t * Core::Entity::Player::getHowToArray() const
|
|
|
|
{
|
|
|
|
return m_howTo;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t * Core::Entity::Player::getUnlockBitmask() const
|
|
|
|
{
|
|
|
|
return m_unlocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Core::Entity::Player::getContentId() const
|
|
|
|
{
|
|
|
|
return m_contentId;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getVoiceId() const
|
|
|
|
{
|
|
|
|
return m_voice;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getGc() const
|
|
|
|
{
|
|
|
|
return m_gc;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t * Core::Entity::Player::getGcRankArray() const
|
|
|
|
{
|
|
|
|
return m_gcRank;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::queuePacket( Core::Network::Packets::GamePacketPtr pPacket )
|
|
|
|
{
|
|
|
|
auto pSession = g_serverZone.getSession( m_id );
|
|
|
|
|
|
|
|
if( pSession )
|
|
|
|
{
|
|
|
|
auto pZoneCon = pSession->getZoneConnection();
|
|
|
|
|
|
|
|
if( pZoneCon )
|
|
|
|
pZoneCon->queueOutPacket( pPacket );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isLoadingComplete() const
|
|
|
|
{
|
|
|
|
return m_bLoadingComplete;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setLoadingComplete( bool bComplete )
|
|
|
|
{
|
|
|
|
m_bLoadingComplete = bComplete;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::performZoning(uint16_t zoneId, const Common::FFXIVARR_POSITION3 &pos, float rotation)
|
|
|
|
{
|
|
|
|
m_pos = pos;
|
|
|
|
m_zoneId = zoneId;
|
|
|
|
m_bMarkedForZoning = true;
|
|
|
|
setRotation( rotation );
|
|
|
|
|
|
|
|
// mark the player for a position update in DB
|
|
|
|
setSyncFlag( PlayerSyncFlags::Position );
|
|
|
|
setZone( zoneId );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isMarkedForZoning() const
|
|
|
|
{
|
|
|
|
return m_bMarkedForZoning;
|
|
|
|
}
|
|
|
|
|
|
|
|
ZoneingType Core::Entity::Player::getZoningType() const
|
|
|
|
{
|
|
|
|
return m_zoningType;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setZoningType( Common::ZoneingType zoneingType )
|
|
|
|
{
|
|
|
|
m_zoningType = zoneingType;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setSearchInfo( uint8_t selectRegion, uint8_t selectClass, const char* searchMessage )
|
|
|
|
{
|
|
|
|
m_searchSelectRegion = selectRegion;
|
|
|
|
m_searchSelectClass = selectClass;
|
|
|
|
memset( &m_searchMessage[0], 0, sizeof( searchMessage ) );
|
|
|
|
strcpy( &m_searchMessage[0], searchMessage );
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::SearchInfo );
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* Core::Entity::Player::getSearchMessage() const
|
|
|
|
{
|
|
|
|
return &m_searchMessage[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getSearchSelectRegion() const
|
|
|
|
{
|
|
|
|
return m_searchSelectRegion;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Core::Entity::Player::getSearchSelectClass() const
|
|
|
|
{
|
|
|
|
return m_searchSelectClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendNotice( const std::string& message ) //Purple Text
|
|
|
|
{
|
|
|
|
queuePacket( ServerNoticePacket( getId(), message ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendUrgent( const std::string& message ) //Red Text
|
|
|
|
{
|
|
|
|
queuePacket( ChatPacket( getAsPlayer(), ChatType::ServerUrgent, message ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendDebug( const std::string& message ) //Grey Text
|
|
|
|
{
|
|
|
|
queuePacket( ChatPacket( getAsPlayer(), ChatType::ServerDebug, message ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::updateHowtosSeen( uint32_t howToId )
|
|
|
|
{
|
|
|
|
uint8_t index = howToId / 8;
|
|
|
|
uint8_t bitIndex = howToId % 8;
|
|
|
|
|
|
|
|
uint8_t value = 1 << bitIndex;
|
|
|
|
|
|
|
|
m_howTo[index] |= value;
|
|
|
|
|
|
|
|
setSyncFlag( PlayerSyncFlags::HowTo );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Core::Entity::Player::onMobAggro( Core::Entity::BattleNpcPtr pBNpc )
|
|
|
|
{
|
|
|
|
hateListAdd( pBNpc );
|
|
|
|
|
|
|
|
queuePacket( ActorControlPacket142( getId(), ToggleAggro, 1 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::onMobDeaggro( Core::Entity::BattleNpcPtr pBNpc )
|
|
|
|
{
|
|
|
|
hateListRemove( pBNpc );
|
|
|
|
|
|
|
|
if( m_actorIdTohateSlotMap.empty() )
|
|
|
|
queuePacket( ActorControlPacket142( getId(), ToggleAggro ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::hateListAdd( Core::Entity::BattleNpcPtr pBNpc )
|
|
|
|
|
|
|
|
{
|
2017-08-13 23:39:04 +02:00
|
|
|
if( m_freeHateSlotQueue.empty() )
|
|
|
|
return;
|
|
|
|
uint8_t hateId = m_freeHateSlotQueue.front();
|
|
|
|
m_freeHateSlotQueue.pop();
|
|
|
|
m_actorIdTohateSlotMap[pBNpc->getId()] = hateId;
|
|
|
|
sendHateList();
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::hateListRemove( Core::Entity::BattleNpcPtr pBNpc )
|
|
|
|
{
|
|
|
|
|
|
|
|
auto it = m_actorIdTohateSlotMap.begin();
|
|
|
|
for( ; it != m_actorIdTohateSlotMap.end(); ++it )
|
|
|
|
{
|
|
|
|
if( it->first == pBNpc->getId() )
|
|
|
|
{
|
|
|
|
uint8_t hateSlot = it->second;
|
|
|
|
m_freeHateSlotQueue.push( hateSlot );
|
|
|
|
m_actorIdTohateSlotMap.erase( it );
|
|
|
|
sendHateList();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::hateListHasMob( Core::Entity::BattleNpcPtr pBNpc )
|
|
|
|
{
|
|
|
|
|
|
|
|
auto it = m_actorIdTohateSlotMap.begin();
|
|
|
|
for( ; it != m_actorIdTohateSlotMap.end(); ++it )
|
|
|
|
{
|
|
|
|
if( it->first == pBNpc->getId() )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::initHateSlotQueue()
|
|
|
|
{
|
|
|
|
m_freeHateSlotQueue = std::queue< uint8_t >();
|
2017-08-11 22:56:30 +01:00
|
|
|
for( int32_t i = 1; i < 26; i++ )
|
2017-08-08 13:53:47 +02:00
|
|
|
m_freeHateSlotQueue.push( i );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::sendHateList()
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVIpcHateList > hateListPacket( getId() );
|
|
|
|
hateListPacket.data().numEntries = m_actorIdTohateSlotMap.size();
|
|
|
|
auto it = m_actorIdTohateSlotMap.begin();
|
2017-08-11 22:56:30 +01:00
|
|
|
for( int32_t i = 0; it != m_actorIdTohateSlotMap.end(); ++it, i++ )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
hateListPacket.data().entry[i].actorId = it->first;
|
|
|
|
hateListPacket.data().entry[i].hatePercent = 100;
|
|
|
|
}
|
|
|
|
queuePacket( hateListPacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Player::isLogin() const
|
|
|
|
{
|
|
|
|
return m_bIsLogin;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::setIsLogin( bool bIsLogin )
|
|
|
|
{
|
|
|
|
m_bIsLogin = bIsLogin;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Player::autoAttack( ActorPtr pTarget )
|
|
|
|
{
|
|
|
|
|
2017-08-11 00:58:35 -03:00
|
|
|
auto mainWeap = m_pInventory->getItemAt(Inventory::GearSet0, Inventory::EquipSlot::MainHand);
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
pTarget->onActionHostile( shared_from_this() );
|
|
|
|
//uint64_t tick = Util::getTimeMs();
|
|
|
|
//srand(static_cast< uint32_t >(tick));
|
2017-08-11 00:58:35 -03:00
|
|
|
|
|
|
|
uint32_t damage = mainWeap->getAutoAttackDmg() + rand() % 12;
|
2017-08-08 13:53:47 +02:00
|
|
|
uint32_t variation = 0 + rand() % 3;
|
|
|
|
|
|
|
|
if( getClass() == 5 || getClass() == 23 || getClass() == 31 )
|
|
|
|
{
|
|
|
|
GamePacketNew< FFXIVIpcEffect > effectPacket(getId());
|
|
|
|
effectPacket.data().targetId = pTarget->getId();
|
|
|
|
effectPacket.data().actionAnimationId = 8;
|
|
|
|
// effectPacket.data().unknown_2 = variation;
|
|
|
|
effectPacket.data().numEffects = 1;
|
|
|
|
effectPacket.data().unknown_61 = 1;
|
|
|
|
effectPacket.data().unknown_62 = 1;
|
|
|
|
effectPacket.data().actionTextId = 8;
|
|
|
|
effectPacket.data().rotation = Math::Util::floatToUInt16Rot(getRotation());
|
|
|
|
effectPacket.data().effectTargetId = pTarget->getId();
|
|
|
|
effectPacket.data().effectTarget = pTarget->getId();
|
|
|
|
effectPacket.data().effects[0].param1 = damage;
|
|
|
|
effectPacket.data().effects[0].unknown_1 = 3;
|
|
|
|
effectPacket.data().effects[0].unknown_2 = 1;
|
|
|
|
effectPacket.data().effects[0].unknown_3 = 7;
|
|
|
|
|
|
|
|
sendToInRangeSet(effectPacket, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
GamePacketNew< FFXIVIpcEffect > effectPacket(getId());
|
|
|
|
effectPacket.data().targetId = pTarget->getId();
|
|
|
|
effectPacket.data().actionAnimationId = 7;
|
|
|
|
// effectPacket.data().unknown_2 = variation;
|
|
|
|
effectPacket.data().numEffects = 1;
|
|
|
|
effectPacket.data().unknown_61 = 1;
|
|
|
|
effectPacket.data().unknown_62 = 1;
|
|
|
|
effectPacket.data().actionTextId = 7;
|
|
|
|
effectPacket.data().rotation = Math::Util::floatToUInt16Rot(getRotation());
|
|
|
|
effectPacket.data().effectTarget = pTarget->getId();
|
|
|
|
effectPacket.data().effects[0].param1 = damage;
|
|
|
|
effectPacket.data().effects[0].unknown_1 = 3;
|
|
|
|
effectPacket.data().effects[0].unknown_2 = 2;
|
|
|
|
effectPacket.data().effects[0].unknown_3 = 71;
|
|
|
|
|
|
|
|
sendToInRangeSet(effectPacket, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
pTarget->takeDamage(damage);
|
|
|
|
|
|
|
|
}
|