1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-23 13:17:45 +00:00
sapphire/src/servers/sapphire_zone/Actor/Player.cpp

1741 lines
47 KiB
C++
Raw Normal View History

#include <boost/make_shared.hpp>
2018-03-06 22:22:19 +01:00
#include <Common.h>
#include <Util/Util.h>
#include <Util/UtilMath.h>
#include <Logging/Logger.h>
#include <Exd/ExdDataGenerated.h>
#include <Network/PacketContainer.h>
#include <Network/CommonActorControl.h>
#include <Network/PacketWrappers/EffectPacket.h>
#include "Session.h"
2017-08-08 13:53:47 +02:00
#include "Player.h"
2018-01-27 23:52:49 +01:00
#include "Zone/TerritoryMgr.h"
#include "Zone/Zone.h"
#include "Zone/ZonePosition.h"
#include "Network/GameConnection.h"
#include "Network/PacketWrappers/ActorControlPacket142.h"
#include "Network/PacketWrappers/ActorControlPacket143.h"
2018-02-18 01:50:20 +01:00
#include "Network/PacketWrappers/ActorControlPacket144.h"
#include "Network/PacketWrappers/InitUIPacket.h"
#include "Network/PacketWrappers/ServerNoticePacket.h"
#include "Network/PacketWrappers/ChatPacket.h"
#include "Network/PacketWrappers/ModelEquipPacket.h"
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
#include "Network/PacketWrappers/PlayerStateFlagsPacket.h"
#include "Network/PacketWrappers/PlayerSpawnPacket.h"
#include "Script/ScriptMgr.h"
#include "Action/Action.h"
#include "Action/ActionTeleport.h"
#include "Action/EventAction.h"
#include "Action/EventItemAction.h"
#include "Math/CalcStats.h"
#include "Math/CalcBattle.h"
2017-08-08 13:53:47 +02:00
#include "ServerZone.h"
#include "Framework.h"
2018-03-09 00:06:44 +01:00
extern Core::Framework g_fw;
2017-08-08 13:53:47 +02:00
using namespace Core::Common;
using namespace Core::Network::Packets;
using namespace Core::Network::Packets::Server;
using namespace Core::Network::ActorControl;
2017-08-08 13:53:47 +02:00
using InventoryMap = std::map< uint16_t, Core::ItemContainerPtr >;
using InvSlotPair = std::pair< uint16_t, int8_t >;
using InvSlotPairVec = std::vector< InvSlotPair >;
2017-08-08 13:53:47 +02:00
// player constructor
Core::Entity::Player::Player() :
Chara( ObjKind::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_markedForRemoval( false ),
m_mount( 0 ),
m_emoteMode( 0 ),
m_directorInitialized( false ),
m_onEnterEventDone( false )
{
m_id = 0;
m_currentStance = Stance::Passive;
m_onlineStatus = 0;
m_queuedZoneing = nullptr;
m_status = ActorStatus::Idle;
m_invincibilityType = InvincibilityType::InvincibilityNone;
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 ) );
memset( m_classArray, 0, sizeof( m_classArray ) );
memset( m_expArray, 0, sizeof( m_expArray ) );
m_objSpawnIndexAllocator.init( MAX_DISPLAYED_EOBJS );
m_actorSpawnIndexAllocator.init( MAX_DISPLAYED_ACTORS, true );
2017-08-08 13:53:47 +02:00
}
Core::Entity::Player::~Player()
{
}
2018-03-05 22:10:14 +11:00
void Core::Entity::Player::injectPacket( std::string path )
{
auto pServerZone = g_fw.get< ServerZone >();
auto session = pServerZone->getSession( getId() );
if( session )
session->getZoneConnection()->injectPacket( path, *this );
2018-03-05 22:10:14 +11:00
}
2017-08-08 13:53:47 +02:00
// TODO: add a proper calculation based on race / job / level / gear
uint32_t Core::Entity::Player::getMaxHp()
{
return m_baseStats.max_hp;
2017-08-08 13:53:47 +02:00
}
uint32_t Core::Entity::Player::getMaxMp()
{
return m_baseStats.max_mp;
2017-08-08 13:53:47 +02:00
}
uint16_t Core::Entity::Player::getZoneId() const
{
return m_zoneId;
2017-08-08 13:53:47 +02:00
}
2017-09-11 18:59:50 +02:00
uint8_t Core::Entity::Player::getGmRank() const
{
return m_gmRank;
2017-09-11 18:59:50 +02:00
}
void Core::Entity::Player::setGmRank( uint8_t rank )
{
m_gmRank = rank;
2017-09-11 18:59:50 +02:00
}
bool Core::Entity::Player::getGmInvis() const
{
return m_gmInvis;
}
void Core::Entity::Player::setGmInvis( bool invis )
{
m_gmInvis = invis;
}
bool Core::Entity::Player::isActingAsGm() const
2018-07-01 21:31:49 +10:00
{
auto status = getOnlineStatus();
return status == OnlineStatus::GameMaster || status == OnlineStatus::GameMaster1 ||
status == OnlineStatus::GameMaster2;
2018-07-01 21:31:49 +10:00
}
2017-08-08 13:53:47 +02:00
uint8_t Core::Entity::Player::getMode() const
{
return m_mode;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setMode( uint8_t mode )
{
m_mode = mode;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getStartTown() const
{
return m_startTown;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setMarkedForRemoval()
{
m_markedForRemoval = true;
}
bool Core::Entity::Player::isMarkedForRemoval() const
{
return m_markedForRemoval;
}
Core::Common::OnlineStatus Core::Entity::Player::getOnlineStatus() const
2017-08-08 13:53:47 +02:00
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
if( !pExdData )
return OnlineStatus::Online;
uint32_t statusDisplayOrder = 0xFF14;
uint32_t applicableStatus = static_cast< uint32_t >( OnlineStatus::Online );
2018-04-19 23:04:31 +10:00
for( uint32_t i = 0; i < std::numeric_limits< decltype( m_onlineStatus ) >::digits; i++ )
{
bool bit = ( m_onlineStatus >> i ) & 1;
if( !bit )
continue;
auto pOnlineStatus = pExdData->get< Data::OnlineStatus >( i );
if( !pOnlineStatus )
continue;
2017-08-08 13:53:47 +02:00
if( pOnlineStatus->priority < statusDisplayOrder )
{
// todo: also check that the status can actually be set here, otherwise we need to ignore it (and ban the player obv)
statusDisplayOrder = pOnlineStatus->priority;
applicableStatus = i;
}
}
return static_cast< OnlineStatus >( applicableStatus );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setOnlineStatusMask( uint64_t status )
{
m_onlineStatus = status;
2017-08-08 13:53:47 +02:00
}
uint64_t Core::Entity::Player::getOnlineStatusMask() const
{
return m_onlineStatus;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::prepareZoning( uint16_t targetZone, bool fadeOut, uint8_t fadeOutTime, uint16_t animation )
2017-08-08 13:53:47 +02:00
{
auto preparePacket = makeZonePacket< FFXIVIpcPrepareZoning >( getId() );
preparePacket->data().targetZone = targetZone;
preparePacket->data().fadeOutTime = fadeOutTime;
preparePacket->data().animation = animation;
preparePacket->data().fadeOut = static_cast< uint8_t >( fadeOut ? 1 : 0 );
queuePacket( preparePacket );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::calculateStats()
{
uint8_t tribe = getLookAt( Common::CharaLook::Tribe );
uint8_t level = getLevel();
uint8_t job = static_cast< uint8_t >( getClass() );
2017-08-08 13:53:47 +02:00
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
2018-03-09 00:06:44 +01:00
auto classInfo = pExdData->get< Core::Data::ClassJob >( job );
auto tribeInfo = pExdData->get< Core::Data::Tribe >( tribe );
auto paramGrowthInfo = pExdData->get< Core::Data::ParamGrow >( level );
2017-08-08 13:53:47 +02:00
// TODO: put formula somewhere else...
float base = Math::CalcStats::calculateBaseStat( getAsPlayer() );
2017-08-08 13:53:47 +02:00
m_baseStats.str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierStrength ) / 100 ) +
tribeInfo->sTR );
m_baseStats.dex = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierDexterity ) / 100 ) +
tribeInfo->dEX );
m_baseStats.vit = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierVitality ) / 100 ) +
tribeInfo->vIT );
m_baseStats.inte = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierIntelligence ) / 100 ) +
tribeInfo->iNT );
m_baseStats.mnd = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierMind ) / 100 ) +
tribeInfo->mND );
m_baseStats.pie = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierPiety ) / 100 ) +
tribeInfo->pIE );
2018-01-31 11:43:22 +01:00
m_baseStats.skillSpeed = paramGrowthInfo->baseSpeed;
m_baseStats.spellSpeed = paramGrowthInfo->baseSpeed;
m_baseStats.accuracy = paramGrowthInfo->baseSpeed;
m_baseStats.critHitRate = paramGrowthInfo->baseSpeed;
m_baseStats.attackPotMagic = paramGrowthInfo->baseSpeed;
m_baseStats.healingPotMagic = paramGrowthInfo->baseSpeed;
m_baseStats.tenacity = paramGrowthInfo->baseSpeed;
2017-08-08 13:53:47 +02:00
m_baseStats.max_mp = Math::CalcStats::calculateMaxMp( getAsPlayer() );
m_baseStats.max_hp = Math::CalcStats::calculateMaxHp( getAsPlayer() );
2017-08-08 13:53:47 +02:00
if( m_mp > m_baseStats.max_mp )
m_mp = m_baseStats.max_mp;
2017-08-08 13:53:47 +02:00
if( m_hp > m_baseStats.max_hp )
m_hp = m_baseStats.max_hp;
2017-08-08 13:53:47 +02:00
m_baseStats.determination = static_cast< uint32_t >( base );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setAutoattack( bool mode )
2017-08-08 13:53:47 +02:00
{
m_bAutoattack = mode;
m_lastAttack = Util::getTimeMs();
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isAutoattackOn() const
{
return m_bAutoattack;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendStats()
{
2018-06-28 00:07:07 +02:00
auto statPacket = makeZonePacket< FFXIVIpcPlayerStats >( 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 );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::teleport( uint16_t aetheryteId, uint8_t type )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
auto pTeriMgr = g_fw.get< TerritoryMgr >();
2018-03-09 00:06:44 +01:00
auto data = pExdData->get< Core::Data::Aetheryte >( aetheryteId );
2017-08-08 13:53:47 +02:00
if( data == nullptr )
{
return;
}
setStateFlag( PlayerStateFlag::BetweenAreas );
auto targetPos = pTeriMgr->getTerritoryPosition( data->level.at( 0 ) );
Common::FFXIVARR_POSITION3 pos;
pos.x = 0;
pos.y = 0;
pos.z = 0;
float rot = 0;
if( targetPos != nullptr )
{
pos = targetPos->getTargetPosition();
rot = targetPos->getTargetRotation();
}
sendDebug( "Teleport: " + pExdData->get< Core::Data::PlaceName >( data->placeName )->name + " " +
pExdData->get< Core::Data::PlaceName >( data->aethernetName )->name +
"(" + std::to_string( data->territory ) + ")" );
// TODO: this should be simplified and a type created in server_common/common.h.
if( type == 1 ) // teleport
{
prepareZoning( data->territory, true, 1, 112 ); // TODO: Really?
sendToInRangeSet( makeActorControl142( getId(), ActorDespawnEffect, 0x04 ) );
setZoningType( Common::ZoneingType::Teleport );
}
else if( type == 2 ) // aethernet
{
prepareZoning( data->territory, true, 1, 112 );
sendToInRangeSet( makeActorControl142( getId(), ActorDespawnEffect, 0x04 ) );
setZoningType( Common::ZoneingType::Teleport );
}
else if( type == 3 ) // return
{
prepareZoning( data->territory, true, 1, 111 );
sendToInRangeSet( makeActorControl142( getId(), ActorDespawnEffect, 0x03 ) );
setZoningType( Common::ZoneingType::Return );
}
m_queuedZoneing = boost::make_shared< QueuedZoning >( data->territory, pos, Util::getTimeMs(), rot );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::forceZoneing( uint32_t zoneId )
{
m_queuedZoneing = boost::make_shared< QueuedZoning >( zoneId, getPos(), Util::getTimeMs(), 0.f );
//performZoning( zoneId, Common::ZoneingType::None, getPos() );
2017-08-08 13:53:47 +02:00
}
2017-08-10 16:31:48 +02:00
void Core::Entity::Player::returnToHomepoint()
{
setZoningType( Common::ZoneingType::Return );
teleport( getHomepoint(), 3 );
2017-08-10 16:31:48 +02:00
}
2017-08-08 13:53:47 +02:00
void Core::Entity::Player::setZone( uint32_t zoneId )
{
auto pTeriMgr = g_fw.get< TerritoryMgr >();
m_onEnterEventDone = false;
if( !pTeriMgr->movePlayer( zoneId, getAsPlayer() ) )
{
// todo: this will require proper handling, for now just return the player to their previous area
m_pos = m_prevPos;
m_rot = m_prevRot;
m_zoneId = m_prevZoneId;
if( !pTeriMgr->movePlayer( m_zoneId, getAsPlayer() ) )
return;
}
sendZonePackets();
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::setInstance( uint32_t instanceContentId )
{
auto pTeriMgr = g_fw.get< TerritoryMgr >();
m_onEnterEventDone = false;
auto instance = pTeriMgr->getInstanceZonePtr( instanceContentId );
if( !instance )
return false;
return setInstance( instance );
}
bool Core::Entity::Player::setInstance( ZonePtr instance )
{
m_onEnterEventDone = false;
if( !instance )
return false;
auto pTeriMgr = g_fw.get< TerritoryMgr >();
2018-03-09 00:06:44 +01:00
// zoning within the same zone won't cause the prev data to be overwritten
if( instance->getTerritoryId() != m_zoneId )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevZoneId = m_zoneId;
}
if( !pTeriMgr->movePlayer( instance, getAsPlayer() ) )
return false;
sendZonePackets();
return true;
}
bool Core::Entity::Player::exitInstance()
{
auto pTeriMgr = g_fw.get< TerritoryMgr >();
if( !pTeriMgr->movePlayer( m_prevZoneId, getAsPlayer() ) )
return false;
m_pos = m_prevPos;
m_rot = m_prevRot;
m_zoneId = m_prevZoneId;
sendZonePackets();
return true;
}
2017-08-08 13:53:47 +02:00
uint32_t Core::Entity::Player::getPlayTime() const
{
return m_playTime;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getRace() const
{
return getLookAt( CharaLook::Race );
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getGender() const
{
return getLookAt( CharaLook::Gender );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::initSpawnIdQueue()
{
m_actorSpawnIndexAllocator.freeAllSpawnIndexes();
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getSpawnIdForActorId( uint32_t actorId )
{
return m_actorSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isActorSpawnIdValid( uint8_t spawnIndex )
2017-08-08 13:53:47 +02:00
{
return m_actorSpawnIndexAllocator.isSpawnIndexValid( spawnIndex );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::registerAetheryte( uint8_t aetheryteId )
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( aetheryteId, value, index );
2017-08-08 13:53:47 +02:00
m_aetheryte[ index ] |= value;
queuePacket( makeActorControl143( getId(), LearnTeleport, aetheryteId, 1 ) );
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isAetheryteRegistered( uint8_t aetheryteId ) const
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( aetheryteId, value, index );
2017-08-08 13:53:47 +02:00
return ( m_aetheryte[ index ] & value ) != 0;
2017-08-08 13:53:47 +02:00
}
uint8_t* Core::Entity::Player::getDiscoveryBitmask()
2017-08-08 13:53:47 +02:00
{
return m_discovery;
2017-08-08 13:53:47 +02:00
}
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
2017-08-08 13:53:47 +02:00
// section to starts at 320 - 4 bytes long
2017-08-08 13:53:47 +02:00
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
2018-03-09 00:06:44 +01:00
int32_t offset = 4;
2017-08-08 13:53:47 +02:00
auto info = pExdData->get< Core::Data::Map >(
pExdData->get< Core::Data::TerritoryType >( getCurrentZone()->getTerritoryId() )->map );
if( info->discoveryArrayByte )
offset = 4 + 2 * info->discoveryIndex;
else
offset = 324 + 4 * info->discoveryIndex;
2017-08-08 13:53:47 +02:00
int32_t index = offset + sub_id / 8;
uint8_t bitIndex = sub_id % 8;
2017-08-08 13:53:47 +02:00
uint8_t value = 1 << bitIndex;
2017-08-08 13:53:47 +02:00
m_discovery[ index ] |= value;
2017-08-08 13:53:47 +02:00
uint16_t level = getLevel();
2017-08-08 13:53:47 +02:00
uint32_t exp = ( pExdData->get< Core::Data::ParamGrow >( level )->expToNext * 5 / 100 );
2017-08-08 13:53:47 +02:00
gainExp( exp );
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isNewAdventurer() const
{
return m_bNewAdventurer;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setNewAdventurer( bool state )
{
//if( !state )
//{
// unsetStateFlag( PlayerStateFlag::NewAdventurer );
//}
//else
//{
// setStateFlag( PlayerStateFlag::NewAdventurer );
//}
m_bNewAdventurer = state;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::resetDiscovery()
{
memset( m_discovery, 0, sizeof( m_discovery ) );
2017-08-08 13:53:47 +02:00
}
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 );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::learnAction( uint16_t actionId )
2017-08-08 13:53:47 +02:00
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( actionId, value, index );
2017-08-08 13:53:47 +02:00
m_unlocks[ index ] |= value;
2017-08-08 13:53:47 +02:00
queuePacket( makeActorControl143( getId(), ToggleActionUnlock, actionId, 1 ) );
2017-08-08 13:53:47 +02:00
}
2017-10-09 20:17:43 +02:00
void Core::Entity::Player::learnSong( uint8_t songId, uint32_t itemId )
2017-10-09 20:09:49 +02:00
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( songId, value, index );
2017-10-09 20:09:49 +02:00
m_orchestrion[ index ] |= value;
2017-10-09 20:09:49 +02:00
queuePacket( makeActorControl143( getId(), ToggleOrchestrionUnlock, songId, 1, itemId ) );
2017-10-09 20:09:49 +02:00
}
2017-08-08 13:53:47 +02:00
bool Core::Entity::Player::isActionLearned( uint8_t actionId ) const
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( actionId, value, index );
2017-08-08 13:53:47 +02:00
return ( m_unlocks[ index ] & value ) != 0;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::gainExp( uint32_t amount )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
2018-03-09 00:06:44 +01:00
uint32_t currentExp = getExp();
2017-08-08 13:53:47 +02:00
uint16_t level = getLevel();
2017-08-08 13:53:47 +02:00
uint32_t neededExpToLevel = pExdData->get< Core::Data::ParamGrow >( level )->expToNext;
2017-08-08 13:53:47 +02:00
uint32_t neededExpToLevelplus1 = pExdData->get< Core::Data::ParamGrow >( level + 1 )->expToNext;
2017-08-08 13:53:47 +02:00
queuePacket( makeActorControl143( getId(), GainExpMsg, static_cast< uint8_t >( getClass() ), amount ) );
2017-08-08 13:53:47 +02:00
if( level >= 70 ) // temporary fix for leveling over levelcap
{
queuePacket( makeActorControl143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), amount ) );
return;
}
2017-08-08 13:53:47 +02:00
if( ( currentExp + amount ) >= neededExpToLevel )
{
// levelup
amount = ( currentExp + amount - neededExpToLevel ) > neededExpToLevelplus1 ?
neededExpToLevelplus1 - 1 :
( currentExp + amount - neededExpToLevel );
setExp( amount );
gainLevel();
queuePacket( makeActorControl143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), amount ) );
2017-08-08 13:53:47 +02:00
}
else
{
queuePacket(
makeActorControl143( getId(), UpdateUiExp, static_cast< uint8_t >( getClass() ), currentExp + amount ) );
setExp( currentExp + amount );
}
2017-08-08 13:53:47 +02:00
sendStatusUpdate();
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::gainLevel()
{
setLevel( getLevel() + 1 );
calculateStats();
sendStats();
sendStatusUpdate();
2017-08-08 13:53:47 +02:00
m_hp = getMaxHp();
m_mp = getMaxMp();
2017-08-08 13:53:47 +02:00
auto effectListPacket = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
effectListPacket->data().classId = static_cast< uint8_t > ( getClass() );
effectListPacket->data().level1 = getLevel();
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 );
2017-08-08 13:53:47 +02:00
sendToInRangeSet( makeActorControl142( getId(), LevelUpEffect, static_cast< uint8_t >( getClass() ),
getLevel(), getLevel() - 1 ), true );
2017-08-08 13:53:47 +02:00
auto classInfoPacket = makeZonePacket< FFXIVIpcUpdateClassInfo >( getId() );
classInfoPacket->data().classId = static_cast< uint8_t > ( getClass() );
classInfoPacket->data().level1 = getLevel();
classInfoPacket->data().level = getLevel();
classInfoPacket->data().nextLevelIndex = getLevel();
classInfoPacket->data().currentExp = getExp();
queuePacket( classInfoPacket );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendStatusUpdate( bool toSelf )
{
sendToInRangeSet( boost::make_shared< UpdateHpMpTpPacket >( *this ), true );
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getLevel() const
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( getClass() ) )->expArrayIndex;
return static_cast< uint8_t >( m_classArray[ classJobIndex ] );
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getLevelForClass( Common::ClassJob pClass ) const
2017-08-09 14:38:46 +02:00
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( pClass ) )->expArrayIndex;
return static_cast< uint8_t >( m_classArray[ classJobIndex ] );
2017-08-09 14:38:46 +02:00
}
bool Core::Entity::Player::isClassJobUnlocked( Common::ClassJob classJob ) const
{
// todo: need to properly check if a job is unlocked, at the moment we just check the class array which will return true for every job if the base class is unlocked
return getLevelForClass( classJob ) != 0;
}
2017-08-08 13:53:47 +02:00
uint32_t Core::Entity::Player::getExp() const
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( getClass() ) )->expArrayIndex;
return m_expArray[ classJobIndex ];
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setExp( uint32_t amount )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( getClass() ) )->expArrayIndex;
m_expArray[ classJobIndex ] = amount;
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isInCombat() const
{
return m_bInCombat;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setInCombat( bool mode )
{
//m_lastAttack = GetTickCount();
m_bInCombat = mode;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setClassJob( Common::ClassJob classJob )
2017-08-08 13:53:47 +02:00
{
m_class = classJob;
uint8_t level = getLevel();
2017-08-08 13:53:47 +02:00
if( getHp() > getMaxHp() )
m_hp = getMaxHp();
2017-08-08 13:53:47 +02:00
if( getMp() > getMaxMp() )
m_mp = getMaxMp();
2017-08-08 13:53:47 +02:00
m_tp = 0;
2017-08-08 13:53:47 +02:00
auto classInfoPacket = makeZonePacket< FFXIVIpcPlayerClassInfo >( getId() );
classInfoPacket->data().classId = static_cast< uint8_t >( getClass() );
classInfoPacket->data().level = getLevel();
queuePacket( classInfoPacket );
2017-08-08 13:53:47 +02:00
sendToInRangeSet( makeActorControl142( getId(), ClassJobChange, 0x04 ), true );
2017-08-08 13:53:47 +02:00
sendStatusUpdate( true );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setLevel( uint8_t level )
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( getClass() ) )->expArrayIndex;
m_classArray[ classJobIndex ] = level;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setLevelForClass( uint8_t level, Common::ClassJob classjob )
2017-08-09 14:38:46 +02:00
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
uint8_t classJobIndex = pExdData->get< Core::Data::ClassJob >( static_cast< uint8_t >( classjob ) )->expArrayIndex;
if( m_classArray[ classJobIndex ] == 0 )
insertDbClass( classJobIndex );
m_classArray[ classJobIndex ] = level;
2017-08-09 14:38:46 +02:00
}
2017-08-08 13:53:47 +02:00
void Core::Entity::Player::sendModel()
{
sendToInRangeSet( boost::make_shared< ModelEquipPacket >( *getAsPlayer() ), true );
2017-08-08 13:53:47 +02:00
}
2018-08-25 22:56:46 +10:00
uint32_t Core::Entity::Player::getModelForSlot( Common::GearSetSlot slot )
2017-08-08 13:53:47 +02:00
{
return m_modelEquip[ slot ];
2017-08-08 13:53:47 +02:00
}
2018-08-25 22:56:46 +10:00
void Core::Entity::Player::setModelForSlot( Common::GearSetSlot slot, uint32_t val )
2017-10-02 16:00:26 +02:00
{
m_modelEquip[ slot ] = val;
2017-10-02 16:00:26 +02:00
}
2017-08-08 13:53:47 +02:00
uint64_t Core::Entity::Player::getModelMainWeapon() const
{
return m_modelMainWeapon;
2017-08-08 13:53:47 +02:00
}
uint64_t Core::Entity::Player::getModelSubWeapon() const
{
return m_modelSubWeapon;
2017-08-08 13:53:47 +02:00
}
uint64_t Core::Entity::Player::getModelSystemWeapon() const
{
return m_modelSystemWeapon;
2017-08-08 13:53:47 +02:00
}
int8_t Core::Entity::Player::getAetheryteMaskAt( uint8_t index ) const
{
if( index > sizeof( m_aetheryte ) )
return 0;
return m_aetheryte[ index ];
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getBirthDay() const
{
return m_birthDay;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getBirthMonth() const
{
return m_birthMonth;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getGuardianDeity() const
{
return m_guardianDeity;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getLookAt( uint8_t index ) const
{
return m_customize[ index ];
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setLookAt( uint8_t index, uint8_t value )
{
m_customize[ index ] = value;
2017-08-08 13:53:47 +02:00
}
// spawn this player for pTarget
void Core::Entity::Player::spawn( Entity::PlayerPtr pTarget )
2017-08-08 13:53:47 +02:00
{
auto pLog = g_fw.get< Logger >();
pLog->debug( "[" + std::to_string( pTarget->getId() ) + "] Spawning " +
getName() + " for " +
pTarget->getName() );
2017-08-08 13:53:47 +02:00
pTarget->queuePacket( boost::make_shared< PlayerSpawnPacket >( *getAsPlayer(), *pTarget ) );
2017-08-08 13:53:47 +02:00
}
// despawn
void Core::Entity::Player::despawn( Entity::PlayerPtr pTarget )
2017-08-08 13:53:47 +02:00
{
auto pPlayer = pTarget;
auto pLog = g_fw.get< Logger >();
pLog->debug( "despawning " + getName() + " for " + pTarget->getName() );
2018-02-21 18:06:52 +01:00
pPlayer->freePlayerSpawnId( getId() );
2017-08-08 13:53:47 +02:00
pPlayer->queuePacket( makeActorControl143( getId(), DespawnZoneScreenMsg, 0x04, getId(), 0x01 ) );
2017-08-08 13:53:47 +02:00
}
Core::Entity::ActorPtr Core::Entity::Player::lookupTargetById( uint64_t targetId )
2017-08-10 22:06:05 +02:00
{
ActorPtr targetActor;
auto inRange = getInRangeActors( true );
for( auto actor : inRange )
{
if( actor->getId() == targetId )
targetActor = actor;
}
return targetActor;
2017-08-10 22:06:05 +02:00
}
2017-08-08 13:53:47 +02:00
void Core::Entity::Player::setLastPing( uint32_t ping )
{
m_lastPing = ping;
2017-08-08 13:53:47 +02:00
}
uint32_t Core::Entity::Player::getLastPing() const
{
return m_lastPing;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setVoiceId( uint8_t voiceId )
{
m_voice = voiceId;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setGc( uint8_t gc )
{
m_gc = gc;
2017-08-08 13:53:47 +02:00
auto gcAffPacket = makeZonePacket< FFXIVGCAffiliation >( 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 );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setGcRankAt( uint8_t index, uint8_t rank )
{
m_gcRank[ index ] = rank;
2017-08-08 13:53:47 +02:00
auto gcAffPacket = makeZonePacket< FFXIVGCAffiliation >( 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 );
2017-08-08 13:53:47 +02:00
}
2017-10-01 18:38:58 +02:00
const uint8_t* Core::Entity::Player::getStateFlags() const
2017-08-08 13:53:47 +02:00
{
return m_stateFlags;
2017-08-08 13:53:47 +02:00
}
2017-08-10 22:06:05 +02:00
bool Core::Entity::Player::actionHasCastTime( uint32_t actionId ) //TODO: Add logic for special cases
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
auto actionInfoPtr = pExdData->get< Core::Data::Action >( actionId );
if( actionInfoPtr->preservesCombo )
return false;
2017-08-15 16:19:55 +02:00
return actionInfoPtr->cast100ms != 0;
2017-08-15 16:19:55 +02:00
2017-08-10 22:06:05 +02:00
}
bool Core::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) const
2017-08-08 13:53:47 +02: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 );
2017-08-08 13:53:47 +02:00
return ( m_stateFlags[ index ] & value ) != 0;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
2017-08-08 13:53:47 +02:00
{
auto prevOnlineStatus = getOnlineStatus();
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 );
2017-08-08 13:53:47 +02:00
m_stateFlags[ index ] |= value;
sendStateFlags();
2018-01-11 14:59:39 +01:00
auto newOnlineStatus = getOnlineStatus();
2018-01-11 14:59:39 +01:00
if( prevOnlineStatus != newOnlineStatus )
sendToInRangeSet( makeActorControl142( getId(), SetStatusIcon,
static_cast< uint8_t >( getOnlineStatus() ) ), true );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
{
for( const auto& flag : flags )
{
setStateFlag( flag );
}
}
2017-08-08 13:53:47 +02:00
void Core::Entity::Player::sendStateFlags()
{
queuePacket( boost::make_shared< PlayerStateFlagsPacket >( *getAsPlayer() ) );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
2017-08-08 13:53:47 +02:00
{
if( !hasStateFlag( flag ) )
return;
2017-08-08 13:53:47 +02:00
auto prevOnlineStatus = getOnlineStatus();
2018-01-11 14:59:39 +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 );
2017-08-08 13:53:47 +02:00
m_stateFlags[ index ] ^= value;
sendStateFlags();
2018-01-11 14:59:39 +01:00
auto newOnlineStatus = getOnlineStatus();
2017-08-08 13:53:47 +02:00
if( prevOnlineStatus != newOnlineStatus )
sendToInRangeSet( makeActorControl142( getId(), SetStatusIcon, static_cast< uint8_t >( getOnlineStatus() ) ),
true );
2017-08-08 13:53:47 +02:00
}
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()->getTerritoryId() != m_queuedZoneing->m_targetZone )
{
performZoning( m_queuedZoneing->m_targetZone, targetPos, m_queuedZoneing->m_targetRotation );
}
else
{
auto setActorPosPacket = makeZonePacket< FFXIVIpcActorSetPos >( 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 );
setPos( targetPos );
}
m_queuedZoneing.reset();
return;
}
if( m_hp <= 0 && m_status != ActorStatus::Dead )
die();
if( !isAlive() )
return;
updateStatusEffects();
m_lastUpdate = currTime;
if( !checkAction() )
{
if( m_targetId && m_currentStance == Common::Stance::Active && isAutoattackOn() )
{
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
// @TODO i dislike this, iterating over all in range actors when you already know the id of the actor you need...
for( auto actor : m_inRangeActor )
2017-08-08 13:53:47 +02:00
{
if( actor->getId() == m_targetId && actor->getAsChara()->isAlive() && mainWeap )
{
// default autoattack range
// TODO make this dependant on bnpc size
uint32_t range = 7;
2017-08-08 13:53:47 +02:00
// default autoattack range for ranged classes
if( getClass() == ClassJob::Machinist ||
getClass() == ClassJob::Bard ||
getClass() == ClassJob::Archer )
range = 25;
2017-08-08 13:53:47 +02:00
if( Math::Util::distance( getPos().x, getPos().y, getPos().z,
actor->getPos().x, actor->getPos().y, actor->getPos().z ) <= range )
{
2017-08-08 13:53:47 +02:00
if( ( currTime - m_lastAttack ) > mainWeap->getDelay() )
2017-08-08 13:53:47 +02:00
{
m_lastAttack = currTime;
autoAttack( actor->getAsChara() );
2017-08-08 13:53:47 +02:00
}
}
}
2017-08-08 13:53:47 +02:00
}
}
}
2017-08-08 13:53:47 +02:00
if( ( currTime - m_lastTickTime ) > 3000 )
{
// add 3 seconds to total play time
m_playTime += 3;
m_lastTickTime = currTime;
onTick();
}
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::onMobKill( uint16_t nameId )
{
auto pScriptMgr = g_fw.get< Scripting::ScriptMgr >();
pScriptMgr->onMobKill( *getAsPlayer(), nameId );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::freePlayerSpawnId( uint32_t actorId )
{
auto spawnId = m_actorSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
2017-08-08 13:53:47 +02:00
auto freeActorSpawnPacket = makeZonePacket< FFXIVIpcActorFreeSpawn >( getId() );
freeActorSpawnPacket->data().actorId = actorId;
freeActorSpawnPacket->data().spawnId = spawnId;
queuePacket( freeActorSpawnPacket );
2017-08-08 13:53:47 +02:00
}
uint8_t* Core::Entity::Player::getAetheryteArray()
2017-08-08 13:53:47 +02:00
{
return m_aetheryte;
2017-08-08 13:53:47 +02:00
}
/*! set homepoint */
void Core::Entity::Player::setHomepoint( uint8_t aetheryteId )
{
m_homePoint = aetheryteId;
2017-08-08 13:53:47 +02:00
queuePacket( makeActorControl143( getId(), SetHomepoint, aetheryteId ) );
2017-08-08 13:53:47 +02:00
}
/*! get homepoint */
uint8_t Core::Entity::Player::getHomepoint() const
{
return m_homePoint;
2017-08-08 13:53:47 +02:00
}
uint16_t* Core::Entity::Player::getClassArray()
2017-08-08 13:53:47 +02:00
{
return m_classArray;
2017-08-08 13:53:47 +02:00
}
const uint16_t* Core::Entity::Player::getClassArray() const
2017-08-08 13:53:47 +02:00
{
return m_classArray;
2017-08-08 13:53:47 +02:00
}
uint32_t* Core::Entity::Player::getExpArray()
2017-08-08 13:53:47 +02:00
{
return m_expArray;
2017-08-08 13:53:47 +02:00
}
const uint32_t* Core::Entity::Player::getExpArray() const
2017-08-08 13:53:47 +02:00
{
return m_expArray;
2017-08-08 13:53:47 +02:00
}
uint8_t* Core::Entity::Player::getHowToArray()
2017-08-08 13:53:47 +02:00
{
return m_howTo;
2017-08-08 13:53:47 +02:00
}
const uint8_t* Core::Entity::Player::getHowToArray() const
2017-08-08 13:53:47 +02:00
{
return m_howTo;
2017-08-08 13:53:47 +02:00
}
const uint8_t* Core::Entity::Player::getUnlockBitmask() const
2017-08-08 13:53:47 +02:00
{
return m_unlocks;
2017-08-08 13:53:47 +02:00
}
const uint8_t* Core::Entity::Player::getOrchestrionBitmask() const
2017-10-09 20:09:49 +02:00
{
return m_orchestrion;
2017-10-09 20:09:49 +02:00
}
const uint8_t* Core::Entity::Player::getMountGuideBitmask() const
{
return m_mountGuide;
}
2017-08-08 13:53:47 +02:00
uint64_t Core::Entity::Player::getContentId() const
{
return m_contentId;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getVoiceId() const
{
return m_voice;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getGc() const
{
return m_gc;
2017-08-08 13:53:47 +02:00
}
const uint8_t* Core::Entity::Player::getGcRankArray() const
2017-08-08 13:53:47 +02:00
{
return m_gcRank;
2017-08-08 13:53:47 +02:00
}
2018-06-28 00:07:07 +02:00
void Core::Entity::Player::queuePacket( Network::Packets::FFXIVPacketBasePtr pPacket )
2017-08-08 13:53:47 +02:00
{
auto pServerZone = g_fw.get< ServerZone >();
auto pSession = pServerZone->getSession( m_id );
2017-08-08 13:53:47 +02:00
if( !pSession )
return;
2017-11-28 00:09:36 +01:00
auto pZoneCon = pSession->getZoneConnection();
2017-11-28 00:09:36 +01:00
if( pZoneCon )
pZoneCon->queueOutPacket( pPacket );
2017-08-08 13:53:47 +02:00
}
2018-06-28 00:07:07 +02:00
void Core::Entity::Player::queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket )
{
auto pServerZone = g_fw.get< ServerZone >();
auto pSession = pServerZone->getSession( m_id );
if( !pSession )
return;
auto pChatCon = pSession->getChatConnection();
2017-11-28 00:09:36 +01:00
if( pChatCon )
pChatCon->queueOutPacket( pPacket );
}
2017-08-08 13:53:47 +02:00
bool Core::Entity::Player::isLoadingComplete() const
{
return m_bLoadingComplete;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setLoadingComplete( bool bComplete )
{
m_bLoadingComplete = bComplete;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::performZoning( uint16_t zoneId, const Common::FFXIVARR_POSITION3& pos, float rotation )
2017-08-08 13:53:47 +02:00
{
m_pos = pos;
m_zoneId = zoneId;
m_bMarkedForZoning = true;
setRot( rotation );
setZone( zoneId );
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isMarkedForZoning() const
{
return m_bMarkedForZoning;
2017-08-08 13:53:47 +02:00
}
ZoneingType Core::Entity::Player::getZoningType() const
{
return m_zoningType;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setZoningType( Common::ZoneingType zoneingType )
{
m_zoningType = zoneingType;
2017-08-08 13:53:47 +02:00
}
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 );
2017-08-08 13:53:47 +02:00
}
const char* Core::Entity::Player::getSearchMessage() const
{
return &m_searchMessage[ 0 ];
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getSearchSelectRegion() const
{
return m_searchSelectRegion;
2017-08-08 13:53:47 +02:00
}
uint8_t Core::Entity::Player::getSearchSelectClass() const
{
return m_searchSelectClass;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendNotice( const std::string& message ) //Purple Text
{
queuePacket( boost::make_shared< ServerNoticePacket >( getId(), message ) );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendUrgent( const std::string& message ) //Red Text
{
queuePacket( boost::make_shared< ChatPacket >( *getAsPlayer(), ChatType::ServerUrgent, message ) );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendDebug( const std::string& message ) //Grey Text
{
queuePacket( boost::make_shared< ChatPacket >( *getAsPlayer(), ChatType::ServerDebug, message ) );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::updateHowtosSeen( uint32_t howToId )
{
uint8_t index = howToId / 8;
uint8_t bitIndex = howToId % 8;
2017-08-08 13:53:47 +02:00
uint8_t value = 1 << bitIndex;
2017-08-08 13:53:47 +02:00
m_howTo[ index ] |= value;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::initHateSlotQueue()
{
m_freeHateSlotQueue = std::queue< uint8_t >();
for( int32_t i = 1; i < 26; i++ )
m_freeHateSlotQueue.push( i );
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::sendHateList()
{
auto hateListPacket = makeZonePacket< FFXIVIpcHateList >( getId() );
hateListPacket->data().numEntries = m_actorIdTohateSlotMap.size();
auto it = m_actorIdTohateSlotMap.begin();
for( int32_t i = 0; it != m_actorIdTohateSlotMap.end(); ++it, i++ )
{
hateListPacket->data().entry[ i ].actorId = it->first;
hateListPacket->data().entry[ i ].hatePercent = 100;
}
queuePacket( hateListPacket );
2017-08-08 13:53:47 +02:00
}
bool Core::Entity::Player::isLogin() const
{
return m_bIsLogin;
2017-08-08 13:53:47 +02:00
}
void Core::Entity::Player::setIsLogin( bool bIsLogin )
{
m_bIsLogin = bIsLogin;
2017-08-08 13:53:47 +02:00
}
uint8_t* Core::Entity::Player::getTitleList()
2017-10-09 00:31:31 -03:00
{
return m_titleList;
2017-10-09 00:31:31 -03:00
}
2018-02-17 01:20:40 +01:00
const uint8_t* Core::Entity::Player::getTitleList() const
{
return m_titleList;
2018-02-17 01:20:40 +01:00
}
2017-10-09 14:13:23 +02:00
uint16_t Core::Entity::Player::getTitle() const
2017-10-09 02:06:31 -03:00
{
return m_activeTitle;
2017-10-09 02:06:31 -03:00
}
2017-10-09 00:31:31 -03:00
void Core::Entity::Player::addTitle( uint16_t titleId )
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( titleId, value, index );
2017-10-09 00:31:31 -03:00
m_titleList[ index ] |= value;
2017-10-09 00:31:31 -03:00
}
2017-10-04 23:30:45 -03:00
void Core::Entity::Player::setTitle( uint16_t titleId )
2017-10-04 23:19:38 -03:00
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( titleId, value, index );
if( ( m_titleList[ index ] & value ) == 0 ) // Player doesn't have title - bail
return;
m_activeTitle = titleId;
sendToInRangeSet( makeActorControl142( getId(), SetTitle, titleId ), true );
2017-10-04 23:19:38 -03:00
}
2017-10-06 00:13:29 +02:00
void Core::Entity::Player::setEquipDisplayFlags( uint8_t state )
{
m_equipDisplayFlags = state;
auto paramPacket = makeZonePacket< FFXIVIpcEquipDisplayFlags >( getId() );
paramPacket->data().bitmask = m_equipDisplayFlags;
sendToInRangeSet( paramPacket, true );
}
2017-10-06 00:13:29 +02:00
uint8_t Core::Entity::Player::getEquipDisplayFlags() const
{
return m_equipDisplayFlags;
}
void Core::Entity::Player::mount( uint32_t id )
{
m_mount = id;
sendToInRangeSet( makeActorControl142( getId(), ActorControlType::SetStatus,
static_cast< uint8_t >( Common::ActorStatus::Mounted ) ), true );
sendToInRangeSet( makeActorControl143( getId(), 0x39e, 12 ), true ); //?
2017-10-18 17:54:17 +02:00
auto mountPacket = makeZonePacket< FFXIVIpcMount >( getId() );
mountPacket->data().id = id;
sendToInRangeSet( mountPacket, true );
2017-10-18 17:54:17 +02:00
}
void Core::Entity::Player::dismount()
{
sendToInRangeSet( makeActorControl142( getId(), ActorControlType::SetStatus,
static_cast< uint8_t >( Common::ActorStatus::Idle ) ), true );
sendToInRangeSet( makeActorControl143( getId(), ActorControlType::Dismount, 1 ), true );
m_mount = 0;
2017-10-18 17:54:17 +02:00
}
uint8_t Core::Entity::Player::getCurrentMount() const
{
return m_mount;
}
void Core::Entity::Player::setPersistentEmote( uint32_t emoteId )
{
m_emoteMode = emoteId;
}
uint32_t Core::Entity::Player::getPersistentEmote() const
{
return m_emoteMode;
}
void Core::Entity::Player::autoAttack( CharaPtr pTarget )
2017-08-08 13:53:47 +02:00
{
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
pTarget->onActionHostile( *this );
//uint64_t tick = Util::getTimeMs();
//srand(static_cast< uint32_t >(tick));
uint32_t damage = static_cast< uint32_t >( mainWeap->getAutoAttackDmg() );
uint32_t variation = 0 + rand() % 3;
2017-08-08 13:53:47 +02:00
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{
auto effectPacket = boost::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 8 );
effectPacket->setRotation( Math::Util::floatToUInt16Rot( getRot() ) );
Server::EffectEntry entry;
entry.value = damage;
entry.effectType = Common::ActionEffectType::Damage;
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
effectPacket->addEffect( entry );
sendToInRangeSet( effectPacket, true );
}
else
{
auto effectPacket = boost::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Math::Util::floatToUInt16Rot( getRot() ) );
Server::EffectEntry entry;
entry.value = damage;
entry.effectType = Common::ActionEffectType::Damage;
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
effectPacket->addEffect( entry );
sendToInRangeSet( effectPacket, true );
2017-08-08 13:53:47 +02:00
}
2017-08-08 13:53:47 +02:00
pTarget->takeDamage( damage );
2017-08-08 13:53:47 +02:00
}
2017-08-14 17:10:19 +02:00
/////////////////////////////
// Content Finder
/////////////////////////////
uint32_t Core::Entity::Player::getCFPenaltyTimestamp() const
{
return m_cfPenaltyUntil;
}
void Core::Entity::Player::setCFPenaltyTimestamp( uint32_t timestamp )
{
m_cfPenaltyUntil = timestamp;
}
uint32_t Core::Entity::Player::getCFPenaltyMinutes() const
{
auto currentTimestamp = Core::Util::getTimeSeconds();
auto endTimestamp = getCFPenaltyTimestamp();
// check if penalty timestamp already passed current time
if( currentTimestamp > endTimestamp )
return 0;
auto deltaTime = endTimestamp - currentTimestamp;
return static_cast< uint32_t > ( ceil( static_cast< float > (deltaTime) / 60 ) );
}
void Core::Entity::Player::setCFPenaltyMinutes( uint32_t minutes )
{
auto currentTimestamp = Core::Util::getTimeSeconds();
setCFPenaltyTimestamp( static_cast< uint32_t >( currentTimestamp + minutes * 60 ) );
}
uint8_t Core::Entity::Player::getOpeningSequence() const
{
return m_openingSequence;
}
void Core::Entity::Player::setOpeningSequence( uint8_t seq )
{
m_openingSequence = seq;
}
uint16_t Core::Entity::Player::getItemLevel() const
{
return m_itemLevel;
}
/// Tells client to offset their eorzean time by given timestamp.
void Core::Entity::Player::setEorzeaTimeOffset( uint64_t timestamp )
{
// TODO: maybe change to persistent?
auto packet = makeZonePacket< FFXIVIpcEorzeaTimeOffset >( getId() );
packet->data().timestamp = timestamp;
// Send to single player
queuePacket( packet );
}
2018-01-28 22:36:43 +01:00
2018-02-17 01:20:40 +01:00
void Core::Entity::Player::setTerritoryId( uint32_t territoryId )
2018-01-28 22:36:43 +01:00
{
m_zoneId = territoryId;
2018-01-28 22:36:43 +01:00
}
2018-02-17 01:20:40 +01:00
uint32_t Core::Entity::Player::getTerritoryId() const
2018-01-28 22:36:43 +01:00
{
return m_zoneId;
2018-01-28 22:36:43 +01:00
}
2018-02-17 01:20:40 +01:00
void Core::Entity::Player::sendZonePackets()
2018-01-28 22:36:43 +01:00
{
getCurrentZone()->onBeforePlayerZoneIn( *this );
auto initPacket = makeZonePacket< FFXIVIpcInit >( getId() );
initPacket->data().charId = getId();
queuePacket( initPacket );
2018-01-28 22:36:43 +01:00
sendInventory();
2018-01-28 22:36:43 +01:00
if( isLogin() )
{
queuePacket( makeActorControl143( getId(), SetCharaGearParamUI, m_equipDisplayFlags, 1 ) );
}
2018-01-28 22:36:43 +01:00
// set flags, will be reset automatically by zoning ( only on client side though )
//setStateFlag( PlayerStateFlag::BetweenAreas );
//setStateFlag( PlayerStateFlag::BetweenAreas1 );
2018-01-28 22:36:43 +01:00
sendStats();
2018-01-28 22:36:43 +01:00
// only initialize the UI if the player in fact just logged in.
if( isLogin() )
{
auto contentFinderList = makeZonePacket< FFXIVIpcCFAvailableContents >( getId() );
2018-06-28 00:07:07 +02:00
for( auto i = 0; i < sizeof( contentFinderList->data().contents ); i++ )
{
// unlock all contents for now
contentFinderList->data().contents[ i ] = 0xFF;
}
queuePacket( contentFinderList );
2018-01-28 22:36:43 +01:00
queuePacket( boost::make_shared< InitUIPacket >( *this ) );
2018-01-28 22:36:43 +01:00
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();
queuePacket( classInfoPacket );
2018-01-28 22:36:43 +01:00
m_itemLevel = calculateEquippedGearItemLevel();
sendItemLevel();
}
2018-01-28 22:36:43 +01:00
auto initZonePacket = makeZonePacket< FFXIVIpcInitZone >( getId() );
initZonePacket->data().zoneId = getCurrentZone()->getTerritoryId();
initZonePacket->data().weatherId = static_cast< uint8_t >( getCurrentZone()->getCurrentWeather() );
initZonePacket->data().bitmask = 0x1;
initZonePacket->data().unknown5 = 0x2A;
initZonePacket->data().festivalId = getCurrentZone()->getCurrentFestival();
initZonePacket->data().pos.x = getPos().x;
initZonePacket->data().pos.y = getPos().y;
initZonePacket->data().pos.z = getPos().z;
queuePacket( initZonePacket );
2018-01-28 22:36:43 +01:00
if( isLogin() )
{
auto unk322 = makeZonePacket< FFXIVARR_IPC_UNK322 >( getId() );
queuePacket( unk322 );
2018-01-28 22:36:43 +01:00
auto unk320 = makeZonePacket< FFXIVARR_IPC_UNK320 >( getId() );
queuePacket( unk320 );
}
2018-01-28 22:36:43 +01:00
if( getLastPing() == 0 )
sendQuestInfo();
2018-01-28 22:36:43 +01:00
getCurrentZone()->onPlayerZoneIn( *this );
m_bMarkedForZoning = false;
2018-01-28 22:36:43 +01:00
}
2018-02-17 01:20:40 +01:00
void Core::Entity::Player::setDirectorInitialized( bool isInitialized )
{
m_directorInitialized = isInitialized;
}
2018-02-17 01:20:40 +01:00
bool Core::Entity::Player::isDirectorInitialized() const
{
return m_directorInitialized;
}
2018-02-17 01:20:40 +01:00
2018-02-18 01:50:20 +01:00
void Core::Entity::Player::sendTitleList()
2018-02-17 01:20:40 +01:00
{
auto titleListPacket = makeZonePacket< FFXIVIpcPlayerTitleList >( getId() );
memcpy( titleListPacket->data().titleList, getTitleList(), sizeof( titleListPacket->data().titleList ) );
2018-02-17 01:20:40 +01:00
queuePacket( titleListPacket );
2018-02-17 01:20:40 +01:00
}
2018-02-18 01:50:20 +01:00
void
Core::Entity::Player::sendZoneInPackets( uint32_t param1, uint32_t param2 = 0, uint32_t param3 = 0, uint32_t param4 = 0,
bool shouldSetStatus = false )
{
auto zoneInPacket = makeActorControl143( getId(), ZoneIn, param1, param2, param3, param4 );
auto SetStatusPacket = makeActorControl142( getId(), SetStatus, static_cast< uint8_t >( Common::ActorStatus::Idle ) );
if( !getGmInvis() )
sendToInRangeSet( zoneInPacket, true );
if( shouldSetStatus )
sendToInRangeSet( SetStatusPacket );
else
queuePacket( zoneInPacket );
if( shouldSetStatus )
queuePacket( SetStatusPacket );
setZoningType( Common::ZoneingType::None );
unsetStateFlag( PlayerStateFlag::BetweenAreas );
}
2018-02-18 01:50:20 +01:00
void Core::Entity::Player::finishZoning()
{
switch( getZoningType() )
{
case ZoneingType::None:
sendZoneInPackets( 0x01 );
break;
2018-02-18 01:50:20 +01:00
case ZoneingType::Teleport:
sendZoneInPackets( 0x01, 0, 0, 110 );
break;
2018-02-18 01:50:20 +01:00
case ZoneingType::Return:
case ZoneingType::ReturnDead:
{
if( getStatus() == Common::ActorStatus::Dead )
2018-02-18 01:50:20 +01:00
{
resetHp();
resetMp();
setStatus( Common::ActorStatus::Idle );
sendZoneInPackets( 0x01, 0x01, 0, 111, true );
2018-02-18 01:50:20 +01:00
}
else
sendZoneInPackets( 0x01, 0x00, 0, 111 );
}
break;
2018-02-18 01:50:20 +01:00
case ZoneingType::FadeIn:
break;
}
2018-02-18 01:50:20 +01:00
}
2018-07-08 23:18:01 +02:00
void Core::Entity::Player::emote( uint32_t emoteId, uint64_t targetId, bool isSilent )
2018-02-18 01:50:20 +01:00
{
sendToInRangeSet( makeActorControl144( getId(), ActorControlType::Emote,
emoteId, 0, isSilent ? 1 : 0, 0, targetId ) );
2018-02-18 01:50:20 +01:00
}
void Core::Entity::Player::emoteInterrupt()
{
sendToInRangeSet( makeActorControl142( getId(), ActorControlType::EmoteInterrupt ) );
}
2018-02-24 23:53:32 +01:00
void Core::Entity::Player::teleportQuery( uint16_t aetheryteId )
2018-02-18 01:50:20 +01:00
{
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
// TODO: only register this action if enough gil is in possession
auto targetAetheryte = pExdData->get< Core::Data::Aetheryte >( aetheryteId );
2018-02-18 01:50:20 +01:00
if( targetAetheryte )
{
auto fromAetheryte = pExdData->get< Core::Data::Aetheryte >(
pExdData->get< Core::Data::TerritoryType >( getZoneId() )->aetheryte );
2018-02-18 01:50:20 +01:00
// calculate cost - does not apply for favorite points or homepoints neither checks for aether tickets
auto cost = static_cast< uint16_t > (
( sqrt( pow( fromAetheryte->aetherstreamX - targetAetheryte->aetherstreamX, 2 ) +
pow( fromAetheryte->aetherstreamY - targetAetheryte->aetherstreamY, 2 ) ) / 2 ) + 100 );
2018-02-18 01:50:20 +01:00
// cap at 999 gil
cost = cost > uint16_t{ 999 } ? uint16_t{ 999 } : cost;
2018-02-18 01:50:20 +01:00
bool insufficientGil = getCurrency( Common::CurrencyType::Gil ) < cost;
// TODO: figure out what param1 really does
queuePacket( makeActorControl143( getId(), TeleportStart, insufficientGil ? 2 : 0, aetheryteId ) );
2018-02-18 01:50:20 +01:00
if( !insufficientGil )
{
Action::ActionPtr pActionTeleport;
pActionTeleport = Action::make_ActionTeleport( getAsPlayer(), aetheryteId, cost );
setCurrentAction( pActionTeleport );
}
}
2018-02-18 01:50:20 +01:00
}
uint8_t Core::Entity::Player::getNextObjSpawnIndexForActorId( uint32_t actorId )
{
return m_objSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
}
void Core::Entity::Player::resetObjSpawnIndex()
{
m_objSpawnIndexAllocator.freeAllSpawnIndexes();
}
void Core::Entity::Player::freeObjSpawnIndexForActorId( uint32_t actorId )
{
auto spawnId = m_objSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
2018-03-01 23:17:35 +01:00
auto freeObjectSpawnPacket = makeZonePacket< FFXIVIpcObjectDespawn >( getId() );
freeObjectSpawnPacket->data().spawnIndex = spawnId;
queuePacket( freeObjectSpawnPacket );
}
bool Core::Entity::Player::isObjSpawnIndexValid( uint8_t index )
{
return m_objSpawnIndexAllocator.isSpawnIndexValid( index );
2018-02-24 23:53:32 +01:00
}
void Core::Entity::Player::setOnEnterEventDone( bool isDone )
{
m_onEnterEventDone = isDone;
2018-02-24 23:53:32 +01:00
}
bool Core::Entity::Player::isOnEnterEventDone() const
{
return m_onEnterEventDone;
2018-02-24 23:53:32 +01:00
}