1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-24 13:47:46 +00:00
sapphire/src/world/Actor/Player.cpp

2244 lines
62 KiB
C++
Raw Normal View History

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/ExdData.h>
#include <datReader/DatCategories/bg/LgbTypes.h>
2020-01-06 20:19:42 +11:00
#include <datReader/DatCategories/bg/lgb.h>
2018-10-26 14:11:02 +02:00
#include <cmath>
#include <utility>
2020-02-29 22:30:10 +11:00
#include <Service.h>
#include "Session.h"
2017-08-08 13:53:47 +02:00
#include "Player.h"
2019-01-19 22:56:07 +01:00
#include "BNpc.h"
2017-08-08 13:53:47 +02:00
#include "Manager/HousingMgr.h"
#include "Manager/TerritoryMgr.h"
#include "Manager/RNGMgr.h"
#include "Manager/PlayerMgr.h"
#include "Manager/PartyMgr.h"
2019-07-21 22:33:33 +10:00
#include "Territory/Territory.h"
#include "Territory/ZonePosition.h"
#include "Territory/InstanceContent.h"
#include "Territory/InstanceObjectCache.h"
#include "Territory/Land.h"
2018-11-10 19:00:13 +01:00
#include "Network/GameConnection.h"
#include "Network/PacketContainer.h"
#include "Network/CommonActorControl.h"
#include "Network/PacketWrappers/ActorControlPacket.h"
#include "Network/PacketWrappers/ActorControlSelfPacket.h"
#include "Network/PacketWrappers/PlayerSetupPacket.h"
#include "Network/PacketWrappers/PlayerSpawnPacket.h"
#include "Network/PacketWrappers/EffectPacket.h"
#include "Network/PacketWrappers/InitZonePacket.h"
#include "Network/PacketWrappers/WarpPacket.h"
#include "Action/Action.h"
#include "Math/CalcStats.h"
2017-08-08 13:53:47 +02:00
#include "WorldServer.h"
using namespace Sapphire::Common;
using namespace Sapphire::Network::Packets;
using namespace Sapphire::Network::Packets::WorldPackets::Server;
using namespace Sapphire::Network::ActorControl;
using namespace Sapphire::World::Manager;
2017-08-08 13:53:47 +02:00
using InventoryMap = std::map< uint16_t, Sapphire::ItemContainerPtr >;
using InvSlotPair = std::pair< uint16_t, int8_t >;
using InvSlotPairVec = std::vector< InvSlotPair >;
2017-08-08 13:53:47 +02:00
// player constructor
2020-03-01 01:00:57 +11:00
Sapphire::Entity::Player::Player() :
Chara( ObjKind::Player ),
m_lastDBWrite( 0 ),
m_bIsLogin( false ),
m_characterId( 0 ),
m_modelMainWeapon( 0 ),
m_modelSubWeapon( 0 ),
m_homePoint( 0 ),
m_startTown( 0 ),
m_townWarpFstFlags( 0 ),
m_playTime( 0 ),
m_lastActionTick( 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 ),
2019-02-06 08:49:57 +01:00
m_onEnterEventDone( false ),
2020-01-05 17:09:27 +09:00
m_falling( false ),
m_pQueuedAction( nullptr ),
m_partyId( 0 ),
m_onlineStatusCustom( 0 ),
m_onlineStatus( 0 )
{
m_id = 0;
m_currentStance = Stance::Passive;
m_onlineStatus = 0;
m_queuedZoneing = nullptr;
m_status = ActorStatus::Idle;
m_invincibilityType = InvincibilityType::InvincibilityNone;
m_radius = 1.f;
std::memset( m_questTracking.data(), 0, sizeof( m_questTracking ) );
memset( m_name, 0, sizeof( m_name ) );
memset( m_stateFlags.data(), 0, m_stateFlags.size() );
memset( m_searchMessage, 0, sizeof( m_searchMessage ) );
memset( m_classArray.data(), 0, sizeof( m_classArray.data() ) );
memset( m_expArray.data(), 0, sizeof( m_expArray.data() ) );
2021-12-13 22:36:29 -03:00
for( uint8_t i = 0; i < 80; ++i )
{
m_recast[ i ] = 0.0f;
m_recastMax[ i ] = 0.0f;
}
2021-12-13 22:36:29 -03:00
for( auto& i : m_charaLandData )
2018-11-07 11:59:59 +01:00
{
memset( &i, 0xFF, 8 );
memset( &i.flags, 0, 8 );
2018-11-07 11:59:59 +01:00
}
m_objSpawnIndexAllocator.init( MAX_DISPLAYED_EOBJS );
m_actorSpawnIndexAllocator.init( MAX_DISPLAYED_ACTORS, true );
initHateSlotQueue();
initSpawnIdQueue();
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::~Player() = default;
2017-08-08 13:53:47 +02:00
void Sapphire::Entity::Player::unload()
2018-03-05 22:10:14 +11:00
{
// do one last update to db
updateSql();
// reset the zone, so the zone handler knows to remove the actor
setCurrentZone( nullptr );
// reset isLogin and loading sequences just in case
setIsLogin( false );
setLoadingComplete( false );
// unset player for removal
setMarkedForRemoval( false );
// send updates to mgrs
if( getPartyId() != 0 )
{
auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref();
partyMgr.onMemberDisconnect( *this );
}
syncLastDBWrite();
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 Sapphire::Entity::Player::getMaxHp()
2017-08-08 13:53:47 +02:00
{
return max_hp;
2017-08-08 13:53:47 +02:00
}
uint32_t Sapphire::Entity::Player::getMaxMp()
2017-08-08 13:53:47 +02:00
{
return max_mp;
2017-08-08 13:53:47 +02:00
}
uint16_t Sapphire::Entity::Player::getZoneId() const
2017-08-08 13:53:47 +02:00
{
return m_territoryTypeId;
2017-08-08 13:53:47 +02:00
}
uint32_t Sapphire::Entity::Player::getTerritoryId() const
{
return m_territoryId;
}
uint32_t Sapphire::Entity::Player::getPrevTerritoryId() const
{
return m_prevTerritoryId;
}
void Sapphire::Entity::Player::setTerritoryId( uint32_t territoryId )
{
m_territoryId = territoryId;
}
uint8_t Sapphire::Entity::Player::getGmRank() const
2017-09-11 18:59:50 +02:00
{
return m_gmRank;
2017-09-11 18:59:50 +02:00
}
void Sapphire::Entity::Player::setGmRank( uint8_t rank )
2017-09-11 18:59:50 +02:00
{
m_gmRank = rank;
2017-09-11 18:59:50 +02:00
}
bool Sapphire::Entity::Player::getGmInvis() const
{
return m_gmInvis;
}
void Sapphire::Entity::Player::setGmInvis( bool invis )
{
m_gmInvis = invis;
}
bool Sapphire::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
}
uint8_t Sapphire::Entity::Player::getMode() const
2017-08-08 13:53:47 +02:00
{
return m_mode;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setMode( uint8_t mode )
2017-08-08 13:53:47 +02:00
{
m_mode = mode;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getStartTown() const
2017-08-08 13:53:47 +02:00
{
return m_startTown;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setMarkedForRemoval( bool removal )
{
m_markedForRemoval = removal;
}
bool Sapphire::Entity::Player::isMarkedForRemoval() const
{
return m_markedForRemoval;
}
Sapphire::Common::OnlineStatus Sapphire::Entity::Player::getOnlineStatus() const
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint32_t statusDisplayOrder = 0xFF14;
auto 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 = ( getFullOnlineStatusMask() >> i ) & 1;
if( !bit )
continue;
auto pOnlineStatus = exdData.getRow< Component::Excel::OnlineStatus >( i );
if( !pOnlineStatus )
continue;
2017-08-08 13:53:47 +02:00
if( pOnlineStatus->data().ListOrder < 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->data().ListOrder;
applicableStatus = i;
}
}
return static_cast< OnlineStatus >( applicableStatus );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setOnlineStatusMask( uint64_t status )
2017-08-08 13:53:47 +02:00
{
m_onlineStatus = status;
2017-08-08 13:53:47 +02:00
}
uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const
2017-08-08 13:53:47 +02:00
{
return m_onlineStatus;
2017-08-08 13:53:47 +02:00
}
uint64_t Sapphire::Entity::Player::getFullOnlineStatusMask() const
{
return m_onlineStatus | m_onlineStatusCustom;
}
/*! sets the list of current online status */
void Sapphire::Entity::Player::setOnlineStatusCustomMask( uint64_t status )
{
m_onlineStatusCustom = status;
}
uint64_t Sapphire::Entity::Player::getOnlineStatusCustomMask() const
{
return m_onlineStatusCustom;
}
void Sapphire::Entity::Player::addOnlineStatus( OnlineStatus status )
{
uint64_t statusValue = 1ull << static_cast< uint8_t >( status );
uint64_t newFlags = ( getOnlineStatusMask() & getOnlineStatusCustomMask() ) | statusValue;
setOnlineStatusMask( newFlags );
Service< World::Manager::PlayerMgr >::ref().onOnlineStatusChanged( *this, false );
}
void Sapphire::Entity::Player::addOnlineStatus( const std::vector< Common::OnlineStatus >& status )
{
uint64_t newFlags = getOnlineStatusMask();
for( const auto& state : status )
{
uint64_t statusValue = 1ull << static_cast< uint8_t >( state );
newFlags |= statusValue;
}
setOnlineStatusMask( newFlags );
Service< World::Manager::PlayerMgr >::ref().onOnlineStatusChanged( *this, false );
}
void Sapphire::Entity::Player::removeOnlineStatus( OnlineStatus status )
{
uint64_t statusValue = 1ull << static_cast< uint8_t >( status );
uint64_t newFlags = getOnlineStatusMask();
uint64_t newFlagsCustom = getOnlineStatusCustomMask();
newFlags &= ~statusValue;
newFlagsCustom &= ~statusValue;
setOnlineStatusMask( newFlags );
setOnlineStatusCustomMask( newFlagsCustom );
Service< World::Manager::PlayerMgr >::ref().onOnlineStatusChanged( *this, false );
}
void Sapphire::Entity::Player::removeOnlineStatus( const std::vector< Common::OnlineStatus >& status )
{
uint64_t newFlags = getOnlineStatusMask();
uint64_t newFlagsCustom = getOnlineStatusCustomMask();
for( const auto& state : status )
{
uint64_t statusValue = 1ull << static_cast< uint8_t >( state );
newFlags &= ~statusValue;
newFlagsCustom &= ~statusValue;
}
setOnlineStatusMask( newFlags );
setOnlineStatusCustomMask( newFlagsCustom );
Service< World::Manager::PlayerMgr >::ref().onOnlineStatusChanged( *this, false );
}
void Sapphire::Entity::Player::prepareZoning( uint16_t targetZone, bool fadeOut, uint8_t fadeOutTime, uint16_t animation )
2017-08-08 13:53:47 +02:00
{
2019-07-29 22:22:45 +10: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 Sapphire::Entity::Player::calculateStats()
2017-08-08 13:53:47 +02:00
{
uint8_t tribe = getLookAt( Common::CharaLook::Tribe );
uint8_t level = getLevel();
auto job = static_cast< uint8_t >( getClass() );
2017-08-08 13:53:47 +02:00
auto& exdData = Common::Service< Data::ExdData >::ref();
2018-03-09 00:06:44 +01:00
auto classInfo = exdData.getRow< Component::Excel::ClassJob >( job );
auto tribeInfo = exdData.getRow< Component::Excel::Tribe >( tribe );
auto paramGrowthInfo = exdData.getRow< Component::Excel::ParamGrow >( level );
2017-08-08 13:53:47 +02:00
float base = Math::CalcStats::calculateBaseStat( *this );
2017-08-08 13:53:47 +02:00
auto str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().STR ) / 100 ) ) + tribeInfo->data().STR;
auto dex = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().DEX ) / 100 ) ) + tribeInfo->data().DEX;
auto vit = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().VIT ) / 100 ) ) + tribeInfo->data().VIT;
auto inte = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().INT_ ) / 100 ) ) + tribeInfo->data().INT_;
auto mnd = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().MND ) / 100 ) ) + tribeInfo->data().MND;
auto pie = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().PIE ) / 100 ) ) + tribeInfo->data().PIE;
setStatValue( BaseParam::Strength, str );
setStatValue( BaseParam::Dexterity, dex );
setStatValue( BaseParam::Vitality, vit );
setStatValue( BaseParam::Intelligence, inte );
setStatValue( BaseParam::Mind, mnd );
setStatValue( BaseParam::Piety, pie );
auto determination = static_cast< uint32_t >( base );
auto skillSpeed = paramGrowthInfo->data().ParamBase;
auto spellSpeed = paramGrowthInfo->data().ParamBase;
auto accuracy = paramGrowthInfo->data().ParamBase;
auto critHitRate = paramGrowthInfo->data().ParamBase;
auto parry = paramGrowthInfo->data().ParamBase;
setStatValue( BaseParam::Determination, determination );
setStatValue( BaseParam::SkillSpeed, skillSpeed );
setStatValue( BaseParam::SpellSpeed, spellSpeed );
setStatValue( BaseParam::CriticalHit, critHitRate );
setStatValue( BaseParam::Accuracy, accuracy );
setStatValue( BaseParam::Parry, parry );
2018-01-31 11:43:22 +01:00
setStatValue( BaseParam::Haste, 100 );
setStatValue( BaseParam::Defense, 0 );
setStatValue( BaseParam::MagicDefense, 0 );
2017-08-08 13:53:47 +02:00
setStatValue( BaseParam::FireResistance, classInfo->data().Element[0] );
setStatValue( BaseParam::IceResistance, classInfo->data().Element[1] );
setStatValue( BaseParam::WindResistance, classInfo->data().Element[2] );
setStatValue( BaseParam::EarthResistance, classInfo->data().Element[3] );
setStatValue( BaseParam::LightningResistance, classInfo->data().Element[4] );
setStatValue( BaseParam::WaterResistance, classInfo->data().Element[5] );
setStatValue( BaseParam::AttackPower, str );
setStatValue( BaseParam::AttackMagicPotency, inte );
setStatValue( BaseParam::HealingMagicPotency, mnd );
2021-12-14 23:05:58 +01:00
max_mp = Math::CalcStats::calculateMaxMp( *this );
2017-08-08 13:53:47 +02:00
max_hp = Math::CalcStats::calculateMaxHp( *this );
2017-08-08 13:53:47 +02:00
if( m_mp > max_mp )
m_mp = max_mp;
2017-08-08 13:53:47 +02:00
if( m_hp > max_hp )
m_hp = max_hp;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setAutoattack( bool mode )
2017-08-08 13:53:47 +02:00
{
m_bAutoattack = mode;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::isAutoattackOn() const
2017-08-08 13:53:47 +02:00
{
return m_bAutoattack;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::sendStats()
2017-08-08 13:53:47 +02:00
{
Service< World::Manager::PlayerMgr >::ref().onSendStats( *this );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::teleport( uint16_t aetheryteId, uint8_t type )
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
2020-03-01 01:00:57 +11:00
auto& terriMgr = Common::Service< TerritoryMgr >::ref();
2018-03-09 00:06:44 +01:00
auto aetherData = exdData.getRow< Component::Excel::Aetheryte >( aetheryteId );
2017-08-08 13:53:47 +02:00
if( !aetherData )
return;
const auto& data = aetherData->data();
setStateFlag( PlayerStateFlag::BetweenAreas );
2020-03-01 01:00:57 +11:00
auto& instanceObjectCache = Common::Service< InstanceObjectCache >::ref();
auto pop = instanceObjectCache.getPopRange( data.TerritoryType, data.PopRange[ 0 ] );
Common::FFXIVARR_POSITION3 pos{ 0.f, 0.f, 0.f };
float rot = 0.f;
if( pop )
{
PlayerMgr::sendDebug( *this, "Teleport: popRange {0} found!", data.PopRange[ 0 ] );
const auto& transform = pop->header.transform;
pos = Common::FFXIVARR_POSITION3{ transform.translation.x, transform.translation.y, transform.translation.z };
auto targetRot = Common::FFXIVARR_POSITION3{ transform.rotation.x, transform.rotation.y, transform.rotation.z };
rot = Common::Util::eulerToDirection( targetRot );
}
else
{
PlayerMgr::sendDebug( *this, "Teleport: popRange {0} not found in {1}!", data.PopRange[ 0 ], data.TerritoryType );
}
auto townPlace = exdData.getRow< Component::Excel::PlaceName >( data.TelepoName );
auto aetherytePlace = exdData.getRow< Component::Excel::PlaceName >( data.TransferName );
PlayerMgr::sendDebug( *this, "Teleport: {0} - {1} ({2})",
townPlace->getString( townPlace->data().Text.SGL ),
aetherytePlace->getString( aetherytePlace->data().Text.SGL ),
data.TerritoryType );
// TODO: this should be simplified and a type created in server_common/common.h.
if( type == 1 ) // teleport
{
prepareZoning( data.TerritoryType, true, 1, 0 ); // TODO: Really?
2019-10-09 18:42:25 +02:00
sendToInRangeSet( makeActorControl( getId(), ActorDespawnEffect, 0x04 ) );
setZoningType( Common::ZoneingType::Teleport );
}
else if( type == 2 ) // aethernet
{
prepareZoning( data.TerritoryType, true, 1, 112 );
2019-10-09 18:42:25 +02:00
sendToInRangeSet( makeActorControl( getId(), ActorDespawnEffect, 0x04 ) );
setZoningType( Common::ZoneingType::Teleport );
}
else if( type == 3 ) // return
{
prepareZoning( data.TerritoryType, true, 1, 111 );
2019-10-09 18:42:25 +02:00
sendToInRangeSet( makeActorControl( getId(), ActorDespawnEffect, 0x03 ) );
setZoningType( Common::ZoneingType::Return );
}
m_queuedZoneing = std::make_shared< QueuedZoning >( data.TerritoryType, pos, Util::getTimeMs(), rot );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::forceZoneing( uint32_t zoneId )
2017-08-08 13:53:47 +02:00
{
2018-10-25 12:44:51 +11:00
m_queuedZoneing = std::make_shared< QueuedZoning >( zoneId, getPos(), Util::getTimeMs(), 0.f );
//performZoning( zoneId, Common::ZoneingType::None, getPos() );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::returnToHomepoint()
2017-08-10 16:31:48 +02:00
{
setZoningType( Common::ZoneingType::Return );
teleport( getHomepoint(), 3 );
2017-08-10 16:31:48 +02:00
}
void Sapphire::Entity::Player::setZone( uint32_t zoneId )
2017-08-08 13:53:47 +02:00
{
2020-03-01 01:00:57 +11:00
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
m_onEnterEventDone = false;
if( !teriMgr.movePlayer( zoneId, *this ) )
{
// 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_territoryTypeId = m_prevTerritoryTypeId;
if( !teriMgr.movePlayer( m_territoryTypeId, *this ) )
return;
}
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::setInstance( uint32_t instanceContentId )
{
m_onEnterEventDone = false;
2020-03-01 01:00:57 +11:00
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
2020-03-01 01:00:57 +11:00
auto instance = teriMgr.getTerritoryByGuId( instanceContentId );
if( !instance )
return false;
return setInstance( instance );
}
bool Sapphire::Entity::Player::setInstance( const TerritoryPtr& instance )
{
m_onEnterEventDone = false;
if( !instance )
return false;
2020-03-01 01:00:57 +11:00
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
auto currentZone = getCurrentTerritory();
// zoning within the same zone won't cause the prev data to be overwritten
if( instance->getTerritoryTypeId() != m_territoryTypeId )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
}
return teriMgr.movePlayer( instance, *this );
}
bool Sapphire::Entity::Player::setInstance( const TerritoryPtr& instance, Common::FFXIVARR_POSITION3 pos )
{
m_onEnterEventDone = false;
if( !instance )
return false;
2020-03-01 01:00:57 +11:00
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
auto currentZone = getCurrentTerritory();
// zoning within the same zone won't cause the prev data to be overwritten
if( instance->getTerritoryTypeId() != m_territoryTypeId )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
}
if( teriMgr.movePlayer( instance, *this ) )
{
m_pos = pos;
return true;
}
return false;
}
bool Sapphire::Entity::Player::exitInstance()
{
2020-03-01 01:00:57 +11:00
auto& teriMgr = Common::Service< TerritoryMgr >::ref();
auto pZone = getCurrentTerritory();
auto pInstance = pZone->getAsInstanceContent();
resetHp();
resetMp();
// check if housing zone
2020-03-01 01:00:57 +11:00
if( teriMgr.isHousingTerritory( m_prevTerritoryTypeId ) )
{
if( !teriMgr.movePlayer( teriMgr.getZoneByLandSetId( m_prevTerritoryId ), *this ) )
return false;
}
else
{
if( !teriMgr.movePlayer( m_prevTerritoryTypeId, *this ) )
return false;
}
m_pos = m_prevPos;
m_rot = m_prevRot;
m_territoryTypeId = m_prevTerritoryTypeId;
m_territoryId = m_prevTerritoryId;
//m_queuedZoneing = std::make_shared< QueuedZoning >( m_territoryTypeId, m_pos, Util::getTimeMs(), m_rot );
return true;
}
uint32_t Sapphire::Entity::Player::getPlayTime() const
2017-08-08 13:53:47 +02:00
{
return m_playTime;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getRace() const
2017-08-08 13:53:47 +02:00
{
return getLookAt( CharaLook::Race );
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getGender() const
2017-08-08 13:53:47 +02:00
{
return getLookAt( CharaLook::Gender );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::initSpawnIdQueue()
2017-08-08 13:53:47 +02:00
{
m_actorSpawnIndexAllocator.freeAllSpawnIndexes();
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getSpawnIdForActorId( uint32_t actorId )
2017-08-08 13:53:47 +02:00
{
auto index = m_actorSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
if( index == m_actorSpawnIndexAllocator.getAllocFailId() )
{
Logger::warn( "Failed to spawn Chara#{0} for Player#{1} - no remaining spawn indexes available. "
"Consider lowering InRangeDistance in world config.",
actorId, getId() );
PlayerMgr::sendUrgent( *this, "Failed to spawn Chara#{0} for you - no remaining spawn slots. See world log.", actorId );
}
return index;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::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 Sapphire::Entity::Player::registerAetheryte( uint8_t aetheryteId )
2017-08-08 13:53:47 +02:00
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( aetheryteId, value, index );
2017-08-08 13:53:47 +02:00
m_aetheryte[ index ] |= value;
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), LearnTeleport, aetheryteId, 1 ) );
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::isAetheryteRegistered( uint8_t aetheryteId ) const
2017-08-08 13:53:47 +02:00
{
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
}
Sapphire::Entity::Player::Discovery& Sapphire::Entity::Player::getDiscoveryBitmask()
2017-08-08 13:53:47 +02:00
{
return m_discovery;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::discover( int16_t map_id, int16_t sub_id )
2017-08-08 13:53:47 +02:00
{
// 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 0 - 2 bytes each
// section to starts at 320 - 4 bytes long
2017-08-08 13:53:47 +02:00
auto& exdData = Common::Service< Data::ExdData >::ref();
2018-03-09 00:06:44 +01:00
int32_t offset;
2017-08-08 13:53:47 +02:00
auto info = exdData.getRow< Component::Excel::Map >( map_id );
if( !info )
{
PlayerMgr::sendDebug( *this, "discover(): Could not obtain map data for map_id == {0}", map_id );
return;
}
if( info->data().IsUint16Discovery )
offset = 2 * info->data().DiscoveryIndex;
else
offset = 320 + 4 * info->data().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 = ( exdData.getRow< Component::Excel::ParamGrow >( level )->data().NextExp * 5 / 100 );
2017-08-08 13:53:47 +02:00
gainExp( exp );
2017-08-08 13:53:47 +02:00
// gain 10x additional EXP if entire map is completed
uint32_t mask = info->data().DiscoveryFlag;
uint32_t discoveredAreas;
if( info->data().IsUint16Discovery )
{
discoveredAreas = ( m_discovery[ offset + 1 ] << 8 ) |
m_discovery[ offset ];
}
else
{
discoveredAreas = ( m_discovery[ offset + 3 ] << 24 ) |
( m_discovery[ offset + 2 ] << 16 ) |
( m_discovery[ offset + 1 ] << 8 ) |
m_discovery[ offset ];
}
bool allDiscovered = ( ( discoveredAreas & mask ) == mask );
2017-08-08 13:53:47 +02:00
if( allDiscovered )
{
gainExp( exp * 10 );
}
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::isNewAdventurer() const
2017-08-08 13:53:47 +02:00
{
return m_bNewAdventurer;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setNewAdventurer( bool state )
2017-08-08 13:53:47 +02:00
{
//if( !state )
//{
// unsetStateFlag( PlayerStateFlag::NewAdventurer );
//}
//else
//{
// setStateFlag( PlayerStateFlag::NewAdventurer );
//}
m_bNewAdventurer = state;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::resetDiscovery()
2017-08-08 13:53:47 +02:00
{
memset( m_discovery.data(), 0, m_discovery.size() );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::changePosition( float x, float y, float z, float o )
2017-08-08 13:53:47 +02:00
{
Common::FFXIVARR_POSITION3 pos{ x, y, z };
2018-10-25 12:44:51 +11:00
m_queuedZoneing = std::make_shared< QueuedZoning >( getZoneId(), pos, Util::getTimeMs(), o );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::learnAction( Common::UnlockEntry unlockId )
2017-08-08 13:53:47 +02:00
{
uint16_t index;
uint8_t value;
auto unlock = static_cast< uint16_t >( unlockId );
Util::valueToFlagByteIndexValue( unlock, value, index );
2017-08-08 13:53:47 +02:00
m_unlocks[ index ] |= value;
2017-08-08 13:53:47 +02:00
queuePacket( makeActorControlSelf( getId(), ToggleActionUnlock, unlock, 1 ) );
2017-08-08 13:53:47 +02:00
}
void Sapphire::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
Service< World::Manager::PlayerMgr >::ref().onUnlockOrchestrion( *this, songId, itemId );
2017-10-09 20:09:49 +02:00
}
bool Sapphire::Entity::Player::isActionLearned( Common::UnlockEntry unlockId ) const
2017-08-08 13:53:47 +02:00
{
uint16_t index;
uint8_t value;
Util::valueToFlagByteIndexValue( static_cast< uint16_t >( unlockId ), 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 Sapphire::Entity::Player::gainExp( uint32_t amount )
2017-08-08 13:53:47 +02: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
if( level >= Common::MAX_PLAYER_LEVEL )
{
setExp( 0 );
if( currentExp != 0 )
Service< World::Manager::PlayerMgr >::ref().onGainExp( *this, 0 );
return;
}
auto& exdData = Common::Service< Data::ExdData >::ref();
2017-08-08 13:53:47 +02:00
uint32_t neededExpToLevel = exdData.getRow< Component::Excel::ParamGrow >( level )->data().NextExp;
uint32_t neededExpToLevelPlus1 = exdData.getRow< Component::Excel::ParamGrow >( level + 1 )->data().NextExp;
2017-08-08 13:53:47 +02:00
if( ( currentExp + amount ) >= neededExpToLevel )
{
// levelup
amount = ( currentExp + amount - neededExpToLevel ) > neededExpToLevelPlus1 ?
neededExpToLevelPlus1 - 1 :
( currentExp + amount - neededExpToLevel );
if( level + 1 >= Common::MAX_PLAYER_LEVEL )
amount = 0;
2017-08-08 13:53:47 +02:00
setExp( amount );
Service< World::Manager::PlayerMgr >::ref().onGainExp( *this, amount );
levelUp();
}
else
{
setExp( currentExp + amount );
Service< World::Manager::PlayerMgr >::ref().onGainExp( *this, amount );
}
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::levelUp()
2017-08-08 13:53:47 +02:00
{
m_hp = getMaxHp();
m_mp = getMaxMp();
2017-08-08 13:53:47 +02:00
setLevel( getLevel() + 1 );
2017-08-08 13:53:47 +02:00
Service< World::Manager::PlayerMgr >::ref().onLevelUp( *this );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::sendStatusUpdate()
2017-08-08 13:53:47 +02:00
{
Service< World::Manager::PlayerMgr >::ref().onPlayerHpMpTpChanged( *this );
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getLevel() const
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( getClass() ) )->data().WorkIndex;
return static_cast< uint8_t >( m_classArray[ classJobIndex ] );
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getLevelSync() const
{
// TODO: implement levelSync
return getLevel();
}
uint8_t Sapphire::Entity::Player::getLevelForClass( Common::ClassJob pClass ) const
2017-08-09 14:38:46 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( pClass ) )->data().WorkIndex;
return static_cast< uint8_t >( m_classArray[ classJobIndex ] );
2017-08-09 14:38:46 +02:00
}
bool Sapphire::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;
}
uint32_t Sapphire::Entity::Player::getExp() const
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( getClass() ) )->data().WorkIndex;
return m_expArray[ classJobIndex ];
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setExp( uint32_t amount )
{
auto exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( getClass() ) )->data().WorkIndex;
m_expArray[ classJobIndex ] = amount;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::isInCombat() const
2017-08-08 13:53:47 +02:00
{
return m_bInCombat;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setInCombat( bool mode )
2017-08-08 13:53:47 +02:00
{
//m_lastAttack = GetTickCount();
m_bInCombat = mode;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setClassJob( Common::ClassJob classJob )
2017-08-08 13:53:47 +02:00
{
m_class = classJob;
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
Service< World::Manager::PlayerMgr >::ref().onPlayerStatusUpdate( *this );
Service< World::Manager::PlayerMgr >::ref().onChangeClass( *this );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setLevel( uint8_t level )
2017-08-08 13:53:47 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( getClass() ) )->data().WorkIndex;
m_classArray[ classJobIndex ] = level;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setLevelForClass( uint8_t level, Common::ClassJob classjob )
2017-08-09 14:38:46 +02:00
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Component::Excel::ClassJob >( static_cast< uint8_t >( getClass() ) )->data().WorkIndex;
if( m_classArray[ classJobIndex ] == 0 )
insertDbClass( classJobIndex, level );
m_classArray[ classJobIndex ] = level;
2017-08-09 14:38:46 +02:00
}
void Sapphire::Entity::Player::sendModel()
2017-08-08 13:53:47 +02:00
{
Service< World::Manager::PlayerMgr >::ref().onChangeGear( *this );
2017-08-08 13:53:47 +02:00
}
uint32_t Sapphire::Entity::Player::getModelForSlot( Common::GearModelSlot slot )
2017-08-08 13:53:47 +02:00
{
return m_modelEquip[ slot ];
2017-08-08 13:53:47 +02:00
}
uint64_t Sapphire::Entity::Player::getModelMainWeapon() const
2017-08-08 13:53:47 +02:00
{
return m_modelMainWeapon;
2017-08-08 13:53:47 +02:00
}
uint64_t Sapphire::Entity::Player::getModelSubWeapon() const
2017-08-08 13:53:47 +02:00
{
return m_modelSubWeapon;
2017-08-08 13:53:47 +02:00
}
uint64_t Sapphire::Entity::Player::getModelSystemWeapon() const
2017-08-08 13:53:47 +02:00
{
return m_modelSystemWeapon;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getAetheryteMaskAt( uint8_t index ) const
2017-08-08 13:53:47 +02:00
{
if( index > sizeof( m_aetheryte ) )
return 0;
return m_aetheryte[ index ];
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getBirthDay() const
2017-08-08 13:53:47 +02:00
{
return m_birthDay;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getBirthMonth() const
2017-08-08 13:53:47 +02:00
{
return m_birthMonth;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getGuardianDeity() const
2017-08-08 13:53:47 +02:00
{
return m_guardianDeity;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getLookAt( uint8_t index ) const
2017-08-08 13:53:47 +02:00
{
return m_customize[ index ];
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setLookAt( uint8_t index, uint8_t value )
2017-08-08 13:53:47 +02:00
{
m_customize[ index ] = value;
2017-08-08 13:53:47 +02:00
}
// spawn this player for pTarget
void Sapphire::Entity::Player::spawn( Entity::PlayerPtr pTarget )
2017-08-08 13:53:47 +02:00
{
2021-12-02 19:47:31 +01:00
Logger::debug( "Spawning {0} for {1}", getName(), pTarget->getName() );
2021-12-01 01:03:36 +01:00
auto spawnPacket = std::make_shared< PlayerSpawnPacket >( *this, *pTarget );
pTarget->queuePacket( spawnPacket );
2017-08-08 13:53:47 +02:00
}
// despawn
void Sapphire::Entity::Player::despawn( Entity::PlayerPtr pTarget )
2017-08-08 13:53:47 +02:00
{
const auto& pPlayer = pTarget;
2019-01-04 12:34:19 +01:00
Logger::debug( "Despawning {0} for {1}", getName(), pTarget->getName() );
2018-02-21 18:06:52 +01:00
pPlayer->freePlayerSpawnId( getId() );
2017-08-08 13:53:47 +02:00
2019-10-09 18:42:25 +02:00
pPlayer->queuePacket( makeActorControlSelf( getId(), DespawnZoneScreenMsg, 0x04, getId(), 0x01 ) );
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::GameObjectPtr Sapphire::Entity::Player::lookupTargetById( uint64_t targetId )
2017-08-10 22:06:05 +02:00
{
GameObjectPtr targetActor;
auto inRange = getInRangeActors( true );
for( const auto& actor : inRange )
{
if( actor->getId() == targetId )
targetActor = actor;
}
return targetActor;
2017-08-10 22:06:05 +02:00
}
uint64_t Sapphire::Entity::Player::getLastDBWrite() const
2017-08-08 13:53:47 +02:00
{
return m_lastDBWrite;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setVoiceId( uint8_t voiceId )
2017-08-08 13:53:47 +02:00
{
m_voice = voiceId;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setGc( uint8_t gc )
2017-08-08 13:53:47 +02:00
{
m_gc = gc;
2017-08-08 13:53:47 +02:00
Service< World::Manager::PlayerMgr >::ref().onGcUpdate( *this );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setGcRankAt( uint8_t index, uint8_t rank )
2017-08-08 13:53:47 +02:00
{
m_gcRank[ index ] = rank;
2017-08-08 13:53:47 +02:00
Service< World::Manager::PlayerMgr >::ref().onGcUpdate( *this );
2017-08-08 13:53:47 +02:00
}
const Sapphire::Entity::Player::StateFlags& Sapphire::Entity::Player::getStateFlags() const
2017-08-08 13:53:47 +02:00
{
return m_stateFlags;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) const
2017-08-08 13:53:47 +02:00
{
auto iFlag = static_cast< int32_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 Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
2017-08-08 13:53:47 +02:00
{
auto prevOnlineStatus = getOnlineStatus();
auto iFlag = static_cast< int32_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;
2018-01-11 14:59:39 +01:00
auto newOnlineStatus = getOnlineStatus();
sendStateFlags( prevOnlineStatus != newOnlineStatus );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
{
for( const auto& flag : flags )
{
setStateFlag( flag );
}
}
void Sapphire::Entity::Player::sendStateFlags( bool updateInRange )
2017-08-08 13:53:47 +02:00
{
Service< World::Manager::PlayerMgr >::ref().onSendStateFlags( *this, updateInRange );
2017-08-08 13:53:47 +02:00
}
void Sapphire::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
auto iFlag = static_cast< int32_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;
auto newOnlineStatus = getOnlineStatus();
sendStateFlags( prevOnlineStatus != newOnlineStatus );
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::update( uint64_t tickCount )
2017-08-08 13:53:47 +02:00
{
// a zoning is pending, lets do it
if( m_queuedZoneing && ( tickCount - m_queuedZoneing->m_queueTime ) > 800 )
{
Common::FFXIVARR_POSITION3 targetPos = m_queuedZoneing->m_targetPosition;
if( getCurrentTerritory()->getTerritoryTypeId() != m_queuedZoneing->m_targetZone )
{
performZoning( m_queuedZoneing->m_targetZone, targetPos, m_queuedZoneing->m_targetRotation );
}
else
{
setPos( targetPos );
sendToInRangeSet( makeWarp( *this, WARP_TYPE_TELEPO, targetPos, m_queuedZoneing->m_targetRotation ), true );
}
m_queuedZoneing.reset();
return;
}
if( m_hp <= 0 && m_status != ActorStatus::Dead )
die();
if( !isAlive() )
return;
m_lastUpdate = tickCount;
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( const auto& actor : m_inRangeActor )
2017-08-08 13:53:47 +02:00
{
if( actor->getId() == m_targetId && actor->getAsChara()->isAlive() && mainWeap )
{
2019-04-25 22:45:23 +10:00
auto chara = actor->getAsChara();
// default autoattack range
float range = 3.f + chara->getRadius() + getRadius() * 0.5f;
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.f + chara->getRadius() + getRadius() * 0.5f;
2017-08-08 13:53:47 +02:00
if( Util::distance( getPos(), actor->getPos() ) <= range )
{
if( ( tickCount - m_lastAttack ) > mainWeap->getDelay() )
2017-08-08 13:53:47 +02:00
{
m_lastAttack = tickCount;
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
Chara::update( tickCount );
2017-08-08 13:53:47 +02:00
}
2022-01-02 22:32:17 +01:00
void Sapphire::Entity::Player::setLastAttack( uint64_t tickCount )
{
m_lastAttack = tickCount;
}
void Sapphire::Entity::Player::freePlayerSpawnId( uint32_t actorId )
2017-08-08 13:53:47 +02:00
{
auto spawnId = m_actorSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
2017-08-08 13:53:47 +02:00
// actor was never spawned for this player
if( spawnId == m_actorSpawnIndexAllocator.getAllocFailId() )
return;
2019-07-29 22:22:45 +10:00
auto freeActorSpawnPacket = makeZonePacket< FFXIVIpcActorFreeSpawn >( getId() );
freeActorSpawnPacket->data().actorId = actorId;
freeActorSpawnPacket->data().spawnId = spawnId;
queuePacket( freeActorSpawnPacket );
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::AetheryteList& Sapphire::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 Sapphire::Entity::Player::setHomepoint( uint8_t aetheryteId )
2017-08-08 13:53:47 +02:00
{
m_homePoint = aetheryteId;
2017-08-08 13:53:47 +02:00
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), SetHomepoint, aetheryteId ) );
2017-08-08 13:53:47 +02:00
}
/*! get homepoint */
uint8_t Sapphire::Entity::Player::getHomepoint() const
2017-08-08 13:53:47 +02:00
{
return m_homePoint;
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::ClassList& Sapphire::Entity::Player::getClassArray()
2017-08-08 13:53:47 +02:00
{
return m_classArray;
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::ExpList& Sapphire::Entity::Player::getExpArray()
2017-08-08 13:53:47 +02:00
{
return m_expArray;
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::HowToList& Sapphire::Entity::Player::getHowToArray()
2017-08-08 13:53:47 +02:00
{
return m_howTo;
2017-08-08 13:53:47 +02:00
}
const Sapphire::Entity::Player::UnlockList& Sapphire::Entity::Player::getUnlockBitmask() const
2017-08-08 13:53:47 +02:00
{
return m_unlocks;
2017-08-08 13:53:47 +02:00
}
const Sapphire::Entity::Player::OrchestrionList& Sapphire::Entity::Player::getOrchestrionBitmask() const
2017-10-09 20:09:49 +02:00
{
return m_orchestrion;
2017-10-09 20:09:49 +02:00
}
Sapphire::Entity::Player::MountList& Sapphire::Entity::Player::getMountGuideBitmask()
{
return m_mountGuide;
}
uint64_t Sapphire::Entity::Player::getCharacterId() const
2021-08-30 10:16:05 +02:00
{
return m_characterId;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getVoiceId() const
2017-08-08 13:53:47 +02:00
{
return m_voice;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getGc() const
2017-08-08 13:53:47 +02:00
{
return m_gc;
2017-08-08 13:53:47 +02:00
}
const std::array< uint8_t, 3 >& Sapphire::Entity::Player::getGcRankArray() const
2017-08-08 13:53:47 +02:00
{
return m_gcRank;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::queuePacket( Network::Packets::FFXIVPacketBasePtr pPacket )
2017-08-08 13:53:47 +02:00
{
auto& server = Common::Service< World::WorldServer >::ref();
2017-11-28 00:09:36 +01:00
server.queueForPlayer( getCharacterId(), std::move( pPacket ) );
}
bool Sapphire::Entity::Player::isLoadingComplete() const
2017-08-08 13:53:47 +02:00
{
return m_bLoadingComplete;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setLoadingComplete( bool bComplete )
2017-08-08 13:53:47 +02:00
{
m_bLoadingComplete = bComplete;
2017-08-08 13:53:47 +02:00
}
void Sapphire::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_territoryTypeId = zoneId;
m_bMarkedForZoning = true;
setRot( rotation );
setZone( zoneId );
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Entity::Player::isMarkedForZoning() const
2017-08-08 13:53:47 +02:00
{
return m_bMarkedForZoning;
2017-08-08 13:53:47 +02:00
}
ZoneingType Sapphire::Entity::Player::getZoningType() const
2017-08-08 13:53:47 +02:00
{
return m_zoningType;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setZoningType( Common::ZoneingType zoneingType )
2017-08-08 13:53:47 +02:00
{
m_zoningType = zoneingType;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setSearchInfo( uint8_t selectRegion, uint8_t selectClass, const char* searchMessage )
2017-08-08 13:53:47 +02:00
{
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* Sapphire::Entity::Player::getSearchMessage() const
2017-08-08 13:53:47 +02:00
{
return &m_searchMessage[ 0 ];
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getSearchSelectRegion() const
2017-08-08 13:53:47 +02:00
{
return m_searchSelectRegion;
2017-08-08 13:53:47 +02:00
}
uint8_t Sapphire::Entity::Player::getSearchSelectClass() const
2017-08-08 13:53:47 +02:00
{
return m_searchSelectClass;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::updateHowtosSeen( uint32_t howToId )
2017-08-08 13:53:47 +02:00
{
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 Sapphire::Entity::Player::initHateSlotQueue()
2017-08-08 13:53:47 +02:00
{
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 Sapphire::Entity::Player::hateListAdd( const BNpc& bnpc )
2019-01-19 22:56:07 +01:00
{
if( !m_freeHateSlotQueue.empty() )
{
uint8_t hateId = m_freeHateSlotQueue.front();
m_freeHateSlotQueue.pop();
m_actorIdTohateSlotMap[ bnpc.getId() ] = hateId;
Service< World::Manager::PlayerMgr >::ref().onHateListChanged( *this );
2019-01-19 22:56:07 +01:00
}
}
void Sapphire::Entity::Player::hateListRemove( const BNpc& bnpc )
2019-01-19 22:56:07 +01:00
{
auto it = m_actorIdTohateSlotMap.begin();
for( ; it != m_actorIdTohateSlotMap.end(); ++it )
{
if( it->first == bnpc.getId() )
2019-01-19 22:56:07 +01:00
{
uint8_t hateSlot = it->second;
m_freeHateSlotQueue.push( hateSlot );
m_actorIdTohateSlotMap.erase( it );
Service< World::Manager::PlayerMgr >::ref().onHateListChanged( *this );
2019-01-19 22:56:07 +01:00
return;
}
}
}
bool Sapphire::Entity::Player::hateListHasEntry( const BNpc& bnpc )
2019-01-19 22:56:07 +01:00
{
return std::any_of( m_actorIdTohateSlotMap.begin(), m_actorIdTohateSlotMap.end(),
[ bnpc ]( const auto& entry ) { return entry.first == bnpc.getId(); } );
2019-01-19 22:56:07 +01:00
}
const std::map< uint32_t, uint8_t >& Sapphire::Entity::Player::getActorIdToHateSlotMap()
2017-08-08 13:53:47 +02:00
{
return m_actorIdTohateSlotMap;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::onMobAggro( const BNpc& bnpc )
2019-01-19 22:56:07 +01:00
{
hateListAdd( bnpc );
queuePacket( makeActorControl( getId(), SetBattle, 1 ) );
2019-01-19 22:56:07 +01:00
}
void Sapphire::Entity::Player::onMobDeaggro( const BNpc& bnpc )
2019-01-19 22:56:07 +01:00
{
hateListRemove( bnpc );
2019-01-19 22:56:07 +01:00
if( m_actorIdTohateSlotMap.empty() )
queuePacket( makeActorControl( getId(), SetBattle ) );
2019-01-19 22:56:07 +01:00
}
bool Sapphire::Entity::Player::isLogin() const
2017-08-08 13:53:47 +02:00
{
return m_bIsLogin;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Entity::Player::setIsLogin( bool bIsLogin )
2017-08-08 13:53:47 +02:00
{
m_bIsLogin = bIsLogin;
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::Player::TitleList& Sapphire::Entity::Player::getTitleList()
2018-02-17 01:20:40 +01:00
{
return m_titleList;
2018-02-17 01:20:40 +01:00
}
uint16_t Sapphire::Entity::Player::getTitle() const
2017-10-09 02:06:31 -03:00
{
return m_activeTitle;
2017-10-09 02:06:31 -03:00
}
void Sapphire::Entity::Player::addTitle( uint16_t titleId )
2017-10-09 00:31:31 -03:00
{
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
}
void Sapphire::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;
2019-10-09 18:42:25 +02:00
sendToInRangeSet( makeActorControl( getId(), SetTitle, titleId ), true );
2017-10-04 23:19:38 -03:00
}
void Sapphire::Entity::Player::setEquipDisplayFlags( uint16_t state )
{
m_equipDisplayFlags = static_cast< uint8_t >( state );
}
uint8_t Sapphire::Entity::Player::getEquipDisplayFlags() const
{
return m_equipDisplayFlags;
}
void Sapphire::Entity::Player::setMount( uint32_t mountId )
{
m_mount = mountId;
2017-10-18 17:54:17 +02:00
Service< World::Manager::PlayerMgr >::ref().onMountUpdate( *this, m_mount );
2017-10-18 17:54:17 +02:00
}
void Sapphire::Entity::Player::setCompanion( uint16_t id )
{
auto& exdData = Common::Service< Data::ExdData >::ref();
auto companion = exdData.getRow< Component::Excel::Companion >( id );
if( !id )
return;
m_companionId = id;
2019-10-09 18:42:25 +02:00
sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleCompanion, id ), true );
}
uint16_t Sapphire::Entity::Player::getCurrentCompanion() const
{
return m_companionId;
}
uint8_t Sapphire::Entity::Player::getCurrentMount() const
2017-10-18 17:54:17 +02:00
{
return m_mount;
}
void Sapphire::Entity::Player::setPersistentEmote( uint32_t emoteId )
{
m_emoteMode = emoteId;
}
uint32_t Sapphire::Entity::Player::getPersistentEmote() const
{
return m_emoteMode;
}
void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
2017-08-08 13:53:47 +02:00
{
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
pTarget->onActionHostile( getAsChara() );
//uint64_t tick = Util::getTimeMs();
//srand(static_cast< uint32_t >(tick));
2020-03-01 01:00:57 +11:00
auto& RNGMgr = Common::Service< World::Manager::RNGMgr >::ref();
auto variation = static_cast< uint32_t >( RNGMgr.getRandGenerator< float >( 0, 3 ).next() );
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
2017-08-08 13:53:47 +02:00
auto effectPacket = std::make_shared< EffectPacket >( getId(), pTarget->getId(), 8 );
Common::CalcResultParam entry{};
entry.Value = static_cast< int16_t >( damage.first );
entry.Type = Common::ActionEffectType::CALC_RESULT_TYPE_DAMAGE_HP;
entry.Arg0 = static_cast< uint8_t >( damage.second );
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{
effectPacket->setAnimationId( 8 );
2022-01-02 22:32:17 +01:00
//entry.Arg2 = 0x72;
}
else
{
effectPacket->setAnimationId( 7 );
2022-01-02 22:32:17 +01:00
//entry.Arg2 = 0x73;
}
2017-08-08 13:53:47 +02:00
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
effectPacket->addEffect( entry, static_cast< uint64_t >( pTarget->getId() ) );
sendToInRangeSet( effectPacket, true );
pTarget->takeDamage( static_cast< uint32_t >( damage.first ) );
2017-08-08 13:53:47 +02:00
}
2017-08-14 17:10:19 +02:00
/////////////////////////////
// Content Finder
/////////////////////////////
uint32_t Sapphire::Entity::Player::getCFPenaltyTimestamp() const
{
return m_cfPenaltyUntil;
}
void Sapphire::Entity::Player::setCFPenaltyTimestamp( uint32_t timestamp )
{
m_cfPenaltyUntil = timestamp;
}
uint32_t Sapphire::Entity::Player::getCFPenaltyMinutes() const
{
auto currentTimestamp = Common::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 > ( std::ceil( static_cast< float > ( deltaTime ) / 60 ) );
}
void Sapphire::Entity::Player::setCFPenaltyMinutes( uint32_t minutes )
{
auto currentTimestamp = Common::Util::getTimeSeconds();
setCFPenaltyTimestamp( currentTimestamp + minutes * 60 );
}
uint8_t Sapphire::Entity::Player::getOpeningSequence() const
{
return m_openingSequence;
}
void Sapphire::Entity::Player::setOpeningSequence( uint8_t seq )
{
m_openingSequence = seq;
}
uint16_t Sapphire::Entity::Player::getItemLevel() const
{
return m_itemLevel;
}
/// Tells client to offset their eorzean time by given timestamp.
void Sapphire::Entity::Player::setEorzeaTimeOffset( uint64_t timestamp )
{
// TODO: maybe change to persistent?
2019-07-29 22:22:45 +10:00
auto packet = makeZonePacket< FFXIVIpcEorzeaTimeOffset >( getId() );
packet->data().timestamp = timestamp;
// Send to single player
queuePacket( packet );
}
2018-01-28 22:36:43 +01:00
void Sapphire::Entity::Player::setTerritoryTypeId( uint32_t territoryTypeId )
2018-01-28 22:36:43 +01:00
{
m_territoryTypeId = territoryTypeId;
2018-01-28 22:36:43 +01:00
}
uint32_t Sapphire::Entity::Player::getTerritoryTypeId() const
2018-01-28 22:36:43 +01:00
{
return m_territoryTypeId;
2018-01-28 22:36:43 +01:00
}
uint32_t Sapphire::Entity::Player::getPrevTerritoryTypeId() const
2018-01-28 22:36:43 +01:00
{
return m_prevTerritoryTypeId;
}
void Sapphire::Entity::Player::sendZonePackets()
{
auto initPacket = makeZonePacket< FFXIVIpcLogin >( getId() );
initPacket->data().playerActorId = getId();
queuePacket( initPacket );
2018-01-28 22:36:43 +01:00
sendInventory();
2018-01-28 22:36:43 +01:00
if( isLogin() )
{
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( 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
if( isActionLearned( Common::UnlockEntry::HuntingLog ) )
sendHuntingLog();
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< FFXIVIpcContentAttainFlags >( getId() );
std::memset( &contentFinderList->data(), 0xFF, sizeof( contentFinderList->data() ) );
queuePacket( contentFinderList );
2018-01-28 22:36:43 +01:00
auto statusPacket = makePlayerSetup( *this );
queuePacket( statusPacket );
2018-01-28 22:36:43 +01:00
Service< World::Manager::PlayerMgr >::ref().onPlayerStatusUpdate( *this );
2018-01-28 22:36:43 +01:00
sendItemLevel();
clearSoldItems();
}
2018-01-28 22:36:43 +01:00
2020-03-01 01:00:57 +11:00
auto& housingMgr = Common::Service< HousingMgr >::ref();
if( Sapphire::LandPtr pLand = housingMgr.getLandByOwnerId( getCharacterId() ) )
2018-11-10 19:00:13 +01:00
{
uint32_t state = 0;
if( pLand->getHouse() )
{
state |= LandFlags::CHARA_HOUSING_LAND_DATA_FLAG_HOUSE;
// todo: remove this, debug for now
state |= LandFlags::CHARA_HOUSING_LAND_DATA_FLAG_AETHERYTE;
}
2018-12-21 22:23:49 +11:00
setLandFlags( LandFlagsSlot::Private, state, pLand->getLandIdent() );
2018-11-10 19:00:13 +01:00
}
2018-11-27 23:12:26 +11:00
sendLandFlags();
queuePacket( makeInitZone( *this, *getCurrentTerritory() ) );
2018-01-28 22:36:43 +01:00
getCurrentTerritory()->onPlayerZoneIn( *this );
2018-11-06 13:05:39 +01:00
if( isLogin() )
{
queuePacket( makeZonePacket< FFXIVIpcQuestRepeatFlags >( getId() ) );
queuePacket( makeZonePacket< FFXIVIpcDailyQuests >( getId() ) );
}
2018-01-28 22:36:43 +01:00
if( getPartyId() != 0 )
{
auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref();
partyMgr.onMoveZone( *this );
}
2018-01-28 22:36:43 +01:00
m_bMarkedForZoning = false;
2018-01-28 22:36:43 +01:00
}
void Sapphire::Entity::Player::setDirectorInitialized( bool isInitialized )
{
m_directorInitialized = isInitialized;
}
bool Sapphire::Entity::Player::isDirectorInitialized() const
{
return m_directorInitialized;
}
2018-02-17 01:20:40 +01:00
void Sapphire::Entity::Player::sendTitleList()
2018-02-17 01:20:40 +01:00
{
auto titleListPacket = makeZonePacket< FFXIVIpcTitleList >( getId() );
memcpy( titleListPacket->data().TitleFlagsArray, getTitleList().data(), sizeof( titleListPacket->data().TitleFlagsArray ) );
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
Sapphire::Entity::Player::sendZoneInPackets( uint32_t param1, uint32_t param2 = 0, uint32_t param3 = 0, uint32_t param4 = 0,
bool shouldSetStatus = false )
{
auto zoneInPacket = makeActorControlSelf( getId(), Appear, param1, param2, param3, param4 );
2019-10-09 18:42:25 +02:00
auto SetStatusPacket = makeActorControl( getId(), SetStatus, static_cast< uint8_t >( Common::ActorStatus::Idle ) );
if( !getGmInvis() )
sendToInRangeSet( zoneInPacket );
if( shouldSetStatus )
sendToInRangeSet( SetStatusPacket, true );
queuePacket( zoneInPacket );
setZoningType( Common::ZoneingType::None );
unsetStateFlag( PlayerStateFlag::BetweenAreas );
}
void Sapphire::Entity::Player::finishZoning()
2018-02-18 01:50:20 +01:00
{
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
}
2019-02-08 21:20:53 +11:00
void Sapphire::Entity::Player::teleportQuery( uint16_t aetheryteId )
{
auto& exdData = Common::Service< Data::ExdData >::ref();
// TODO: only register this action if enough gil is in possession
auto targetAetheryte = exdData.getRow< Component::Excel::Aetheryte >( aetheryteId );
if( targetAetheryte )
{
auto fromAetheryte = exdData.getRow< Component::Excel::Aetheryte >(
exdData.getRow< Component::Excel::TerritoryType >( getZoneId() )->data().Aetheryte );
// calculate cost - does not apply for favorite points or homepoints neither checks for aether tickets
auto cost = static_cast< uint16_t > (
( std::sqrt( std::pow( fromAetheryte->data().CostPosX - targetAetheryte->data().CostPosX, 2 ) +
std::pow( fromAetheryte->data().CostPosY - targetAetheryte->data().CostPosY, 2 ) ) / 2 ) + 100 );
// cap at 999 gil
cost = std::min< uint16_t >( 999, cost );
bool insufficientGil = getCurrency( Common::CurrencyType::Gil ) < cost;
// TODO: figure out what param1 really does
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), TeleportStart, insufficientGil ? 2 : 0, aetheryteId ) );
if( !insufficientGil )
{
m_teleportQuery.targetAetheryte = aetheryteId;
m_teleportQuery.cost = cost;
}
else
{
clearTeleportQuery();
}
}
}
Sapphire::Common::PlayerTeleportQuery Sapphire::Entity::Player::getTeleportQuery() const
{
return m_teleportQuery;
}
void Sapphire::Entity::Player::clearTeleportQuery()
{
memset( &m_teleportQuery, 0x0, sizeof( Common::PlayerTeleportQuery ) );
2018-02-18 01:50:20 +01:00
}
uint8_t Sapphire::Entity::Player::getNextObjSpawnIndexForActorId( uint32_t actorId )
{
auto index = m_objSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
if( index == m_objSpawnIndexAllocator.getAllocFailId() )
{
Logger::warn( "Failed to spawn EObj#{0} for Player#{1} - no remaining spawn indexes available. "
"Consider lowering InRangeDistance in world config.",
actorId, getId() );
PlayerMgr::sendUrgent( *this, "Failed to spawn EObj#{0} for you - no remaining spawn slots. See world log.", actorId );
return index;
}
return index;
}
2020-03-16 01:35:49 -07:00
void Sapphire::Entity::Player::setDyeingInfo( uint32_t itemToDyeContainer, uint32_t itemToDyeSlot, uint32_t dyeBagContainer, uint32_t dyeBagSlot )
{
m_dyeingInfo.itemToDyeContainer = itemToDyeContainer;
m_dyeingInfo.itemToDyeSlot = itemToDyeSlot;
m_dyeingInfo.dyeBagContainer = dyeBagContainer;
m_dyeingInfo.dyeBagSlot = dyeBagSlot;
}
void Sapphire::Entity::Player::dyeItemFromDyeingInfo()
{
uint32_t itemToDyeContainer = m_dyeingInfo.itemToDyeContainer;
uint32_t itemToDyeSlot = m_dyeingInfo.itemToDyeSlot;
uint32_t dyeBagContainer = m_dyeingInfo.dyeBagContainer;
uint32_t dyeBagSlot = m_dyeingInfo.dyeBagSlot;
sendStateFlags(); // Retail sends all 0s to unlock player after a dye? Possibly not setting a flag when the action is started in the backend..?
2020-03-16 01:35:49 -07:00
auto itemToDye = getItemAt( itemToDyeContainer, itemToDyeSlot );
auto dyeToUse = getItemAt( dyeBagContainer, dyeBagSlot );
if( !itemToDye || !dyeToUse )
return;
uint32_t stainColorID = dyeToUse->getAdditionalData();
2020-03-16 01:35:49 -07:00
itemToDye->setStain( stainColorID );
2020-03-16 01:35:49 -07:00
// TODO: subtract/remove dye used
2020-03-16 02:08:35 -07:00
insertInventoryItem( static_cast< Sapphire::Common::InventoryType >( itemToDyeContainer ), static_cast< uint16_t >( itemToDyeSlot ), itemToDye );
writeItem( itemToDye );
}
void Sapphire::Entity::Player::resetObjSpawnIndex()
{
m_objSpawnIndexAllocator.freeAllSpawnIndexes();
}
void Sapphire::Entity::Player::freeObjSpawnIndexForActorId( uint32_t actorId )
{
auto spawnId = m_objSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
2018-03-01 23:17:35 +01:00
// obj was never spawned for this player
if( spawnId == m_objSpawnIndexAllocator.getAllocFailId() )
return;
auto freeObjectSpawnPacket = makeZonePacket< FFXIVIpcDeleteObject >( getId() );
freeObjectSpawnPacket->data().Index = spawnId;
queuePacket( freeObjectSpawnPacket );
}
bool Sapphire::Entity::Player::isObjSpawnIndexValid( uint8_t index )
{
return m_objSpawnIndexAllocator.isSpawnIndexValid( index );
2018-02-24 23:53:32 +01:00
}
void Sapphire::Entity::Player::setOnEnterEventDone( bool isDone )
2018-02-24 23:53:32 +01:00
{
m_onEnterEventDone = isDone;
2018-02-24 23:53:32 +01:00
}
bool Sapphire::Entity::Player::isOnEnterEventDone() const
2018-02-24 23:53:32 +01:00
{
return m_onEnterEventDone;
2018-02-24 23:53:32 +01:00
}
2018-11-07 11:59:59 +01:00
2018-12-21 22:23:49 +11:00
void Sapphire::Entity::Player::setLandFlags( uint8_t flagSlot, uint32_t landFlags, Common::LandIdent ident )
2018-11-07 11:59:59 +01:00
{
auto& server = Common::Service< World::WorldServer >::ref();
m_charaLandData[ flagSlot ].landId = ident;
m_charaLandData[ flagSlot ].landId.worldId = server.getWorldId();
m_charaLandData[ flagSlot ].flags = landFlags;
2018-11-10 22:04:40 +01:00
}
2018-11-07 11:59:59 +01:00
void Sapphire::Entity::Player::sendLandFlags()
2018-11-07 11:59:59 +01:00
{
auto landFlags = makeZonePacket< FFXIVIpcCharaHousing >( getId() );
2018-11-07 11:59:59 +01:00
landFlags->data().FcLands[ 0 ] = m_charaLandData[ Common::LandFlagsSlot::FreeCompany ];
landFlags->data().CharaLands[ 0 ] = m_charaLandData[ Common::LandFlagsSlot::Private ];
2018-11-07 11:59:59 +01:00
queuePacket( landFlags );
2018-11-10 19:15:04 +01:00
}
void Sapphire::Entity::Player::sendLandFlagsSlot( Common::LandFlagsSlot slot )
{
auto landFlags = makeZonePacket< FFXIVIpcCharaHousingLandData >( getId() );
LandType type;
switch( slot )
{
case LandFlagsSlot::Private:
type = LandType::Private;
break;
case LandFlagsSlot::FreeCompany:
type = LandType::FreeCompany;
break;
default:
// todo: other/unsupported land types
return;
}
landFlags->data().Flags = static_cast< uint32_t >( type );
landFlags->data().LandId = m_charaLandData[ slot ].landId;
queuePacket( landFlags );
2019-01-04 12:34:19 +01:00
}
2019-03-26 00:04:27 +01:00
Sapphire::Common::HuntingLogEntry& Sapphire::Entity::Player::getHuntingLogEntry( uint8_t index )
{
assert( index < m_huntingLogEntries.size() );
return m_huntingLogEntries[ index ];
}
2019-03-26 23:08:34 +01:00
void Sapphire::Entity::Player::sendHuntingLog()
{
auto& exdData = Common::Service< Data::ExdData >::ref();
2019-03-26 23:08:34 +01:00
uint8_t count = 0;
for( const auto& entry : m_huntingLogEntries )
{
uint64_t completionFlag = 0;
auto huntPacket = makeZonePacket< FFXIVIpcMonsterNoteCategory >( getId() );
2019-03-26 23:08:34 +01:00
huntPacket->data().contextId = -1;
huntPacket->data().currentRank = entry.rank;
huntPacket->data().categoryIndex = count;
2019-03-26 23:08:34 +01:00
for( int i = 1; i <= 10; ++i )
{
auto index0 = i - 1;
bool allComplete = true;
auto monsterNoteId = ( count + 1 ) * 10000 + entry.rank * 10 + i;
auto monsterNote = exdData.getRow< Component::Excel::MonsterNote >( monsterNoteId );
2019-03-26 23:08:34 +01:00
if( !monsterNote )
continue;
const auto huntEntry = entry.entries[ index0 ];
for( int x = 0; x < 3; ++x )
{
if( ( huntEntry[ x ] == monsterNote->data().NeededKills[ x ] ) && monsterNote->data().NeededKills[ x ] != 0 )
2019-03-26 23:08:34 +01:00
completionFlag |= ( 1ull << ( index0 * 5 + x ) );
else if( monsterNote->data().NeededKills[ x ] != 0 )
2019-03-26 23:08:34 +01:00
allComplete = false;
}
if( allComplete )
completionFlag |= ( 1ull << ( index0 * 5 + 4 ) );
}
memcpy( huntPacket->data().killCount, entry.entries, sizeof( entry.entries ) );
2019-03-26 23:08:34 +01:00
huntPacket->data().completeFlags = completionFlag;
++count;
queuePacket( huntPacket );
}
}
void Sapphire::Entity::Player::updateHuntingLog( uint16_t id )
{
std::vector< uint32_t > rankRewards{ 2500, 10000, 20000, 30000, 40000 };
const auto maxRank = 4;
auto& pExdData = Common::Service< Data::ExdData >::ref();
auto currentClassId = static_cast< uint8_t >( getClass() );
auto& logEntry = m_huntingLogEntries[ currentClassId - 1 ];
bool logChanged = false;
// make sure we get the matching base-class if a job is being used
auto currentClass = currentClassId;
auto classJobInfo = pExdData.getRow< Component::Excel::ClassJob >( currentClass );
if( !classJobInfo )
return;
bool allSectionsComplete = true;
for( int i = 1; i <= 10; ++i )
{
bool sectionComplete = true;
bool sectionChanged = false;
auto monsterNoteId = static_cast< uint32_t >( classJobInfo->data().MainClass * 10000 + logEntry.rank * 10 + i );
auto note = pExdData.getRow< Component::Excel::MonsterNote >( monsterNoteId );
// for classes that don't have entries, if the first fails the rest will fail
if( !note )
break;
for( auto x = 0; x < 4; ++x )
{
auto note1 = pExdData.getRow< Component::Excel::MonsterNoteTarget >( note->data().Target[ x ] );
2021-12-07 00:51:09 +01:00
if( note1->data().Monster == id && logEntry.entries[ i - 1 ][ x ] < note->data().NeededKills[ x ] )
{
logEntry.entries[ i - 1 ][ x ]++;
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), HuntingLogEntryUpdate, monsterNoteId, x, logEntry.entries[ i - 1 ][ x ] ) );
logChanged = true;
sectionChanged = true;
}
2021-12-07 00:51:09 +01:00
if( logEntry.entries[ i - 1 ][ x ] != note->data().NeededKills[ x ] )
sectionComplete = false;
}
if( logChanged && sectionComplete && sectionChanged )
{
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), HuntingLogSectionFinish, monsterNoteId, i, 0 ) );
gainExp( note->data().RewardExp );
}
if( !sectionComplete )
{
allSectionsComplete = false;
}
}
if( logChanged && allSectionsComplete )
{
2019-10-09 18:42:25 +02:00
queuePacket( makeActorControlSelf( getId(), HuntingLogRankFinish, 4, 0, 0 ) );
gainExp( rankRewards[ logEntry.rank ] );
if( logEntry.rank < 4 )
{
logEntry.rank++;
memset( logEntry.entries, 0, 40 );
queuePacket( makeActorControlSelf( getId(), HuntingLogRankUnlock, currentClassId, logEntry.rank + 1, 0 ) );
}
}
if( logChanged )
sendHuntingLog();
}
void Sapphire::Entity::Player::setActiveLand( uint8_t land, uint8_t ward )
{
m_activeLand.plot = land;
m_activeLand.ward = ward;
}
Sapphire::Common::ActiveLand Sapphire::Entity::Player::getActiveLand() const
{
return m_activeLand;
}
2020-01-05 17:09:27 +09:00
bool Sapphire::Entity::Player::hasQueuedAction() const
{
return m_pQueuedAction != nullptr;
}
void Sapphire::Entity::Player::setQueuedAction( Sapphire::World::Action::ActionPtr pAction )
{
2020-01-05 20:49:50 +09:00
m_pQueuedAction = std::move( pAction ); // overwrite previous queued action if any
2020-01-05 17:09:27 +09:00
}
void Sapphire::Entity::Player::setLastActionTick( uint64_t tick )
{
m_lastActionTick = tick;
}
uint64_t Sapphire::Entity::Player::getLastActionTick() const
{
return m_lastActionTick;
}
void Sapphire::Entity::Player::setRecastGroup( uint8_t index, float time )
{
m_recast[ index ] = time;
if( time > m_recastMax[ index ] )
m_recastMax[ index ] = time;
}
float Sapphire::Entity::Player::getRecastGroup( uint8_t index ) const
{
return m_recast[ index ];
}
void Sapphire::Entity::Player::sendRecastGroups()
{
auto recastGroupPaket = makeZonePacket< FFXIVIpcRecastGroup >( getId() );
memcpy( &recastGroupPaket->data().Recast, &m_recast, sizeof( m_recast ) );
memcpy( &recastGroupPaket->data().RecastMax, &m_recastMax, sizeof( m_recastMax ) );
queuePacket( recastGroupPaket );
}
void Sapphire::Entity::Player::resetRecastGroups()
{
for( size_t i = 0; i < 80; ++i )
{
m_recast[ i ] = 0.0f;
m_recastMax[ i ] = 0.0f;
}
sendRecastGroups();
}
2020-01-05 17:09:27 +09:00
bool Sapphire::Entity::Player::checkAction()
{
if( m_pCurrentAction == nullptr )
return false;
if( m_pCurrentAction->update() )
{
if( m_pCurrentAction->isInterrupted() && m_pCurrentAction->getInterruptType() != Common::ActionInterruptType::DamageInterrupt )
{
// we moved (or whatever not damage interrupt) so we don't want to execute queued cast
m_pQueuedAction = nullptr;
}
m_pCurrentAction = nullptr;
if( hasQueuedAction() )
{
PlayerMgr::sendDebug( *this, "Queued skill start: {0}", m_pQueuedAction->getId() );
2020-01-05 17:09:27 +09:00
if( m_pQueuedAction->hasCastTime() )
{
setCurrentAction( m_pQueuedAction );
}
m_pQueuedAction->start();
m_pQueuedAction = nullptr;
}
}
return true;
}
2020-04-24 19:24:04 +09:00
uint64_t Sapphire::Entity::Player::getPartyId() const
{
return m_partyId;
}
void Sapphire::Entity::Player::setPartyId( uint64_t partyId )
{
m_partyId = partyId;
}
Sapphire::Entity::Player::FriendListIDVec& Sapphire::Entity::Player::getFriendListID()
{
return m_friendList;
}
2021-12-13 22:36:29 -03:00
Sapphire::Entity::Player::FriendListDataVec& Sapphire::Entity::Player::getFriendListData()
{
return m_friendInviteList;
}
2021-12-13 22:36:29 -03:00
Sapphire::Entity::Player::FriendListIDVec& Sapphire::Entity::Player::getBlacklistID()
{
return m_blacklist;
}
void Sapphire::Entity::Player::setFalling( bool state, const Common::FFXIVARR_POSITION3& pos, bool ignoreDamage )
{
bool isFalling = m_falling;
auto initialPos = m_initialFallPos;
// update internal values - only use scoped values for old state
m_falling = state;
m_initialFallPos = pos;
if( ignoreDamage )
return;
// if the player is currently falling and new state is grounded - calc and apply fall dmg
if( isFalling && !state )
{
// calc height difference
auto fallHeight = initialPos.y - pos.y;
// if we've hit the breakpoint in fall damage (min: 10y)
if( fallHeight >= 10.f )
{
// calculate how much damage to deal out (max. 20y : 100%)
float deltaMax = std::min( fallHeight, 20.f );
// get hp percentage starting from 0.1, increasing to 100% at max height
float hpPer = std::min( 0.1f + ( deltaMax - 10.f ) / 10.f, 1.f );
uint32_t damage = getMaxHp() * hpPer;
// check if player has aggro - if not, player should "live"
if( m_actorIdTohateSlotMap.empty() )
{
// "trick" client into thinking we took more damage than internally passed to takeDamage, if > playerHp
uint32_t surviveDamage = damage;
if( surviveDamage >= getHp() )
{
surviveDamage = ( getHp() - 1 );
}
takeDamage( surviveDamage );
}
else
{
// no mercy on hated players
takeDamage( damage );
}
sendToInRangeSet( makeActorControl( getId(), DmgTakenMsg, damage ), true );
}
}
}
bool Sapphire::Entity::Player::isFalling() const
{
return m_falling;
}
void Sapphire::Entity::Player::setLastPcSearchResult( std::vector< uint32_t > result )
{
m_lastPcSearch = std::move( result );
}
std::vector< uint32_t >& Sapphire::Entity::Player::getLastPcSearchResult()
2020-04-24 19:24:04 +09:00
{
return m_lastPcSearch;
2020-04-24 19:24:04 +09:00
}
const FFXIVARR_POSITION3& Sapphire::Entity::Player::getPrevPos() const
2020-04-24 19:24:04 +09:00
{
return m_prevPos;
}
2020-04-24 19:24:04 +09:00
float Sapphire::Entity::Player::getPrevRot() const
{
return m_prevRot;
2020-04-24 19:24:04 +09:00
}
std::optional< Sapphire::World::Quest > Sapphire::Entity::Player::getQuest( uint32_t questId )
2020-04-24 19:24:04 +09:00
{
if( !hasQuest( questId ) )
return std::nullopt;
auto idx = getQuestIndex( questId );
auto quest = getQuestByIndex( idx );
return { quest };
}