4980 lines
153 KiB
C++
4980 lines
153 KiB
C++
// File header
|
|
#include "Pickups/Data/PickupDataManager.h"
|
|
|
|
// rage headers
|
|
#include "phbound/boundcomposite.h"
|
|
|
|
// game headers
|
|
#include "audio/ambience/ambientaudioentity.h"
|
|
#include "camera/CamInterface.h"
|
|
#include "control/replay/Replay.h"
|
|
#include "control/restart.h"
|
|
#include "frontend/MobilePhone.h"
|
|
#include "Network/Events/NetworkEventTypes.h"
|
|
#include "peds/Action/ActionManager.h"
|
|
#include "peds/Ped.h"
|
|
#include "peds/PedIntelligence.h"
|
|
#include "peds/PlayerInfo.h"
|
|
#include "physics/gtaArchetype.h"
|
|
#include "physics/gtaInst.h"
|
|
#include "physics/physics.h"
|
|
#include "Pickups/Pickup.h"
|
|
#include "Pickups/PickupManager.h"
|
|
#include "Pickups/PickupRewards.h"
|
|
#include "renderer/OcclusionQueries.h"
|
|
#include "renderer/Water.h"
|
|
#include "script/script_channel.h"
|
|
#include "scene/world/gameworld.h"
|
|
#include "script/Handlers/GameScriptEntity.h"
|
|
#include "system/control.h"
|
|
#include "task/Combat/TaskCombatMelee.h"
|
|
#include "task/Default/TaskPlayer.h"
|
|
#include "text/messages.h"
|
|
#include "Vehicles/Submarine.h"
|
|
#include "Vehicles/AmphibiousAutomobile.h"
|
|
#include "vfx/misc/GameGlows.h"
|
|
#include "vfx/misc/Markers.h"
|
|
#include "weapons/WeaponFactory.h"
|
|
#include "script/script_hud.h"
|
|
|
|
// framework headers
|
|
#include "fwscript/scriptInterface.h"
|
|
#include "fwsys/timer.h"
|
|
#include "fwnet/netblender.h"
|
|
#include "fwnet/netblenderlininterp.h"
|
|
#include "fwscene/stores/staticboundsstore.h"
|
|
|
|
WEAPON_OPTIMISATIONS()
|
|
NETWORK_OPTIMISATIONS()
|
|
|
|
|
|
extern dev_float ARROW_MARKER_Z_OFFSET;
|
|
extern dev_float ARROW_MARKER_SCALE;
|
|
#if __DEV
|
|
extern Color32 ARROW_MARKER_COL;
|
|
#else
|
|
extern const Color32 ARROW_MARKER_COL;
|
|
#endif
|
|
extern dev_float ARROW_MARKER_FADE_START_RANGE;
|
|
extern dev_float ARROW_MARKER_FADE_OUT_RANGE;
|
|
extern dev_bool ARROW_MARKER_BOUNCE;
|
|
extern dev_bool ARROW_MARKER_FACE_CAM;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CPickup
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#if __BANK && ENABLE_GLOWS
|
|
float CPickup::ms_customGlowR = 0.0f;
|
|
float CPickup::ms_customGlowG = 0.0f;
|
|
float CPickup::ms_customGlowB = 0.0f;
|
|
float CPickup::ms_customGlowI = 0.0f;
|
|
float CPickup::ms_customGlowRange = 0.0f;
|
|
float CPickup::ms_customGlowFadeDist = 0.0f;
|
|
float CPickup::ms_customGlowOffset = 0.0f;
|
|
#endif // __BANK && ENABLE_GLOWS
|
|
|
|
#define PICKUP_GLOW_FADE_DIST 5.0f
|
|
dev_float g_pickupRotationDelta = -TWO_PI / 6000.0f;
|
|
|
|
#define MAX_BUILDING_CLIFF_HEIGHT (500.0f)
|
|
|
|
INSTANTIATE_RTTI_CLASS(CPickup,0xad2bcc1a);
|
|
FW_INSTANTIATE_BASECLASS_POOL_SPILLOVER(CPickup, CONFIGURED_FROM_FILE, 0.60f, atHashString("CPickup",0xad2bcc1a), sizeof(CPickup));
|
|
|
|
bool CPickup::ms_shareVehicleWeaponPickupsAmongstPassengers = false;
|
|
CPickup* CPickup::ms_pickupsPendingLocalCollection[MAX_PENDING_COLLECTIONS];
|
|
u32 CPickup::ms_numPickupsPendingLocalCollection = 0;
|
|
|
|
unsigned CPickup::m_ExtendedProbeCount = 0;
|
|
atRangeArray<CPickup::ExtendedProbeAreas, CPickup::MAX_NUM_EXTENDED_PROBE_AREAS> CPickup::m_AllExtendedProbeAreas;
|
|
|
|
u32 CPickup::PICKUP_ALPHA_WHEN_TRANSPARENT = 127;
|
|
|
|
const float CPickup::ACCESSIBLE_DISTANCE_FROM_YACHT = 80.0f;
|
|
const Vector3 CPickup::ALL_YACHT_LOCATIONS[SIZE_OF_ALL_YACHT_LOCATIONS] =
|
|
{
|
|
Vector3(-3555.1155f,1473.0128f,9.7027f),
|
|
Vector3(-3147.0488f,2827.0879f,9.7027f),
|
|
Vector3(-3277.4729f,2159.8499f,9.7027f),
|
|
Vector3(-2822.4194f,4054.8396f,9.7027f),
|
|
Vector3(-3249.8491f,3704.6814f,9.7027f),
|
|
Vector3(-2383.1934f,4685.0034f,9.7027f),
|
|
Vector3(-3224.6863f,-215.9825f,9.7027f),
|
|
Vector3(-3447.8765f,291.9275f,9.7027f),
|
|
Vector3(-2713.0979f,-528.3185f,9.7027f),
|
|
Vector3(-1981.6182f,-1537.2692f,9.7027f),
|
|
Vector3(-2100.8169f,-2533.2332f,9.7027f),
|
|
Vector3(-1599.6425f,-1891.2773f,9.7027f),
|
|
Vector3(-733.6151f,-3916.9846f,9.7027f),
|
|
Vector3(-363.3534f,-3568.5601f,9.7027f),
|
|
Vector3(-1478.4360f,-3753.5378f,9.7027f),
|
|
Vector3(1535.9740f,-3061.8774f,9.7027f),
|
|
Vector3(2471.4185f,-2430.9297f,9.7027f),
|
|
Vector3(2067.3708f,-2813.0103f,9.7027f),
|
|
Vector3(3021.0881f,-1513.6022f,9.7027f),
|
|
Vector3(3025.9556f,-704.3854f,9.7027f),
|
|
Vector3(2961.8629f,-2007.6315f,9.7027f),
|
|
Vector3(3398.1694f,1958.5214f,9.7027f),
|
|
Vector3(3428.6812f,1202.0597f,9.7027f),
|
|
Vector3(3787.8298f,2567.8838f,9.7027f),
|
|
Vector3(4235.9463f,4004.2522f,9.7027f),
|
|
Vector3(4245.1514f,4595.3750f,9.7027f),
|
|
Vector3(4209.0571f,3392.7053f,9.7027f),
|
|
Vector3(3738.8098f,5768.2524f,9.7027f),
|
|
Vector3(3472.9656f,6315.2451f,9.7027f),
|
|
Vector3(3693.4683f,5194.6587f,9.7027f),
|
|
Vector3(572.9806f,7142.1382f,9.7027f),
|
|
Vector3(2024.0360f,6907.5361f,9.7027f),
|
|
Vector3(1377.2958f,6863.2305f,9.7027f),
|
|
Vector3(-1169.3605f,6000.2139f,9.7027f),
|
|
Vector3(-759.2205f,6573.9551f,9.7027f),
|
|
Vector3(-373.8432f,6964.8599f,9.7027f),
|
|
};
|
|
|
|
CPickup::CPickup(const eEntityOwnedBy ownedBy, u32 hash, u32 customModelIndex, bool bCreateDefaultWeaponAttachments)
|
|
: CObject(ownedBy, customModelIndex != fwModelId::MI_INVALID ? customModelIndex : CPickupDataManager::GetPickupData(hash)->GetModelIndex())
|
|
, m_pickupHash(hash)
|
|
, m_pPickupData(CPickupDataManager::GetPickupData(hash))
|
|
, m_pPlacement(NULL)
|
|
, m_amount(0)
|
|
, m_amountCollected(0)
|
|
, m_StartingLinearVelocity(Vector3::ZeroType)
|
|
, m_StartingAngularVelocity(Vector3::ZeroType)
|
|
, m_pendingCollectionType(COLLECT_INVALID)
|
|
, m_pendingCollectionTimer(0)
|
|
, m_heightOffGround(0.0f)
|
|
, m_lastAccessibleLocation(VEC3_ZERO)
|
|
, m_lifeTime(0)
|
|
, m_glowOffset(0.5f)
|
|
, m_waterLevelNoWaves(-1.0f)
|
|
, m_teamPermits(MAX_UINT16)
|
|
, m_originalBoundRadius(0.0f)
|
|
, m_pWeaponItemForStreaming(NULL)
|
|
, m_includeProjectiles(false)
|
|
, m_customWeaponHash(0)
|
|
, m_allowNonScriptParticipantCollection(false)
|
|
#if ENABLE_NETWORK_LOGGING
|
|
, m_LastHasGlowFailReason(HasGlowFailReason::None)
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
#if __ASSERT
|
|
, m_ResetAccessiblePositionCallTimes()
|
|
#endif // __ASSERT
|
|
{
|
|
Assert(m_pPickupData);
|
|
|
|
const u32 modelIndex = customModelIndex!=fwModelId::MI_INVALID? customModelIndex : CPickupDataManager::GetPickupData(hash)->GetModelIndex();
|
|
Assert(modelIndex != fwModelId::MI_INVALID);
|
|
|
|
CBaseModelInfo* pModelInfo = CModelInfo::GetBaseModelInfo(rage::fwModelId(strLocalIndex(modelIndex)));
|
|
if(pModelInfo)
|
|
{
|
|
pModelInfo->SetUseAmbientScale(true);
|
|
}
|
|
|
|
m_nObjectFlags.bIsPickUp = true;
|
|
|
|
// Force setting as bIsPickup will change the behaviours.
|
|
AssignBaseFlag(fwEntity::USE_SCREENDOOR, GetShouldUseScreenDoor());
|
|
|
|
AssignBaseFlag(fwEntity::FORCE_ALPHA, true);
|
|
|
|
if(!m_pPickupData->GetCanBeDamaged())
|
|
{
|
|
m_nPhysicalFlags.bNotDamagedByAnything = true;
|
|
}
|
|
|
|
GetLodData().SetResetDisabled(true);
|
|
|
|
if (customModelIndex != fwModelId::MI_INVALID)
|
|
{
|
|
SetFlag(PF_HasCustomModel);
|
|
}
|
|
|
|
if (bCreateDefaultWeaponAttachments && (customModelIndex == fwModelId::MI_INVALID || customModelIndex == (u32)CPickupDataManager::GetPickupData(hash)->GetModelIndex()))
|
|
{
|
|
SetFlag(PF_CreateDefaultWeaponAttachments);
|
|
}
|
|
|
|
if (m_pPickupData->GetOrientateUpright())
|
|
{
|
|
SetFlag(PF_UprightOnGround);
|
|
}
|
|
|
|
REPLAY_ONLY(CReplayMgr::OnCreateEntity(this));
|
|
}
|
|
|
|
CPickup::~CPickup()
|
|
{
|
|
if (m_pPlacement)
|
|
{
|
|
m_pPlacement->SetPickup(NULL);
|
|
}
|
|
|
|
if (IsFlagSet(PF_HasCustomModel) && IsFlagSet(PF_GotCustomArchetype))
|
|
{
|
|
CPickupManager::RemoveRefForExtraCustomArchetype(GetModelIndex());
|
|
}
|
|
|
|
if(m_pWeaponItemForStreaming)
|
|
{
|
|
delete m_pWeaponItemForStreaming;
|
|
m_pWeaponItemForStreaming = NULL;
|
|
}
|
|
|
|
ClearPendingCollection(false);
|
|
}
|
|
|
|
void CPickup::SetSyncedFlags(u32 flags)
|
|
{
|
|
u64 currFlags = m_flags.GetAllFlags();
|
|
|
|
currFlags &= ~SYNCED_PICKUP_FLAGS_MASK;
|
|
currFlags |= (u64)flags;
|
|
|
|
m_flags.SetAllFlags(currFlags);
|
|
}
|
|
|
|
void CPickup::SetAmountAndAmountCollected(u32 amount, u32 amountCollected)
|
|
{
|
|
m_amount = amount;
|
|
m_amountCollected = amountCollected;
|
|
|
|
if(m_pPlacement)
|
|
{
|
|
m_pPlacement->SetAmount(static_cast<u16>(amount));
|
|
m_pPlacement->SetAmountCollected(static_cast<u16>(amountCollected));
|
|
}
|
|
}
|
|
|
|
void CPickup::SetHideWhenDetached(bool b)
|
|
{
|
|
if (b != IsFlagSet(PF_HideWhenDetached))
|
|
{
|
|
ChangeFlag(PF_HideWhenDetached, b);
|
|
|
|
if (!b && !GetIsAttached() && !GetIsVisible())
|
|
{
|
|
Expose();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CPickup::SetPlacement(CPickupPlacement* pPlacement)
|
|
{
|
|
Assert(!pPlacement || !m_pPlacement);
|
|
m_pPlacement = pPlacement;
|
|
|
|
if (m_pPlacement)
|
|
{
|
|
if (m_pPlacement->GetIsFixed())
|
|
{
|
|
m_nPhysicalFlags.bIgnoresExplosions = true;
|
|
}
|
|
else if (AssertVerify(m_pPickupData))
|
|
{
|
|
// only fixed pickups can be scaled
|
|
Assertf(IsClose(m_pPickupData->GetScale(), 1.0f), "Only fixed pickups can be scaled: %s is not a fixed pickup", GetPickupData()->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::SetLastOwner(CPed* pPed)
|
|
{
|
|
Assert(!m_pPlacement);
|
|
Assert(!m_lastOwner.Get());
|
|
|
|
m_lastOwner = pPed;
|
|
}
|
|
|
|
void CPickup::SetCollected()
|
|
{
|
|
SetFlag(PF_Collected);
|
|
m_pendingCollectionType = COLLECT_INVALID;
|
|
}
|
|
|
|
void CPickup::SetPortable()
|
|
{
|
|
if (Verifyf(GetPickupData()->GetAttachmentBone() != 0, "Trying to set a pickup as portable that has no attachment data specified in the meta file"))
|
|
{
|
|
// portable pickups are only created by scripts and are flagged as script objects so that they persist and extra state is synced
|
|
SetOwnedBy(ENTITY_OWNEDBY_SCRIPT);
|
|
|
|
if (GetNetworkObject() && !GetNetworkObject()->IsClone())
|
|
{
|
|
GetNetworkObject()->SetGlobalFlag(CNetObjGame::GLOBALFLAG_SCRIPTOBJECT, true);
|
|
|
|
// prevent the pickup from migrating to other machines not running the script that created it
|
|
GetNetworkObject()->SetGlobalFlag(CNetObjGame::GLOBALFLAG_SCRIPT_MIGRATION, true);
|
|
}
|
|
|
|
if (IsAlwaysFixed())
|
|
{
|
|
CObject::SetFixedPhysics(true, false);
|
|
}
|
|
else
|
|
{
|
|
// force the pickup to activate whenever it is unfrozen, to prevent it being left floating in the air
|
|
m_nObjectFlags.bActivatePhysicsAsSoonAsUnfrozen = true;
|
|
}
|
|
|
|
SetFlag(PF_Portable);
|
|
|
|
SetOwnedBy(ENTITY_OWNEDBY_SCRIPT);
|
|
|
|
if (IsUnderWater())
|
|
{
|
|
SetFlag(PF_UnderwaterPickup);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::ClearDroppedInWater()
|
|
{
|
|
ClearFlag(PF_DroppedInWater);
|
|
|
|
m_nObjectFlags.bFloater = false;
|
|
|
|
// make sure any portable pickups that have their physics enabled (due to being dropped in water) are fixed again
|
|
if (IsAlwaysFixed())
|
|
{
|
|
if (GetCurrentPhysicsInst())
|
|
{
|
|
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true);
|
|
}
|
|
|
|
CObject::SetFixedPhysics(true, false);
|
|
}
|
|
}
|
|
|
|
void CPickup::SetClonePortablePickupData(bool bInaccessible, const Vector3& lastAccessibleLoc, bool bLastAccessiblePosHasValidGround)
|
|
{
|
|
bInaccessible ? SetFlag(PF_Inaccessible) : ClearFlag(PF_Inaccessible);
|
|
m_lastAccessibleLocation = lastAccessibleLoc;
|
|
bLastAccessiblePosHasValidGround ? SetFlag(PF_LastAccessiblePosHasValidGround) : ClearFlag(PF_LastAccessiblePosHasValidGround);
|
|
}
|
|
|
|
void CPickup::Init()
|
|
{
|
|
if (!GetRequiresACustomArchetype())
|
|
{
|
|
SetPickupScale();
|
|
}
|
|
|
|
// add a looping sound for the pickup if it has one
|
|
if (GetPickupData()->GetLoopingSoundHash())
|
|
{
|
|
audSoundInitParams initParams;
|
|
initParams.Tracker = GetPlaceableTracker();
|
|
naEnvironmentGroup *occlusionGroup = naEnvironmentGroup::Allocate("Pickup");
|
|
initParams.EnvironmentGroup = occlusionGroup;
|
|
|
|
audSound *sound;
|
|
g_AmbientAudioEntity.CreateSound_LocalReference(GetPickupData()->GetLoopingSoundHash(), &sound, &initParams);
|
|
if(sound)
|
|
{
|
|
sound->InvalidateEntity();
|
|
sound->PrepareAndPlay();
|
|
}
|
|
|
|
if (occlusionGroup)
|
|
{
|
|
occlusionGroup->Init(NULL, 10.f);
|
|
occlusionGroup->ForceSourceEnvironmentUpdate(this);
|
|
occlusionGroup->SetSource(GetTransform().GetPosition());
|
|
}
|
|
}
|
|
|
|
m_lastAccessibleLocation = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
}
|
|
|
|
void CPickup::ForceSetLastAccessibleLocation()
|
|
{
|
|
m_lastAccessibleLocation = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
ClearFlag(PF_LastAccessiblePosHasValidGround);
|
|
|
|
#if __ASSERT
|
|
// overuse tracking
|
|
static const int MAX_CALL_INTERVAL_CHECK = 1000; // 1 sec
|
|
for(int i = 0; i < m_ResetAccessiblePositionCallTimes.GetCount(); i++)
|
|
{
|
|
// remove the ones that were set more then a second ago
|
|
if(m_ResetAccessiblePositionCallTimes[i] + MAX_CALL_INTERVAL_CHECK < fwTimer::GetTimeInMilliseconds())
|
|
{
|
|
m_ResetAccessiblePositionCallTimes.Delete(i);
|
|
i--;
|
|
}
|
|
}
|
|
if(m_ResetAccessiblePositionCallTimes.IsFull())
|
|
{
|
|
scriptAssertf(0, "%s 's last accessible position is being reset too frequently. It was called at least %d times in the last %d second", GetLogName(), MAX_CALL_TIMES, (MAX_CALL_INTERVAL_CHECK/1000));
|
|
}
|
|
else
|
|
{
|
|
m_ResetAccessiblePositionCallTimes.Push(fwTimer::GetTimeInMilliseconds());
|
|
}
|
|
#endif // __ASSERT
|
|
}
|
|
|
|
void CPickup::Update()
|
|
{
|
|
// pickup may be about to be removed
|
|
if (IsBaseFlagSet(fwEntity::REMOVE_FROM_WORLD) || IsFlagSet(PF_Destroyed))
|
|
{
|
|
CModelInfo::GetStreamingModule()->ClearRequiredFlag(GetModelIndex(), STRFLAG_DONTDELETE);
|
|
return;
|
|
}
|
|
// hide pickups when the player is taking a photo
|
|
if (IsFlagSet(PF_HideInPhotos) && CPhoneMgr::CamGetState())
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_CAMERA, false, true);
|
|
}
|
|
else
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_CAMERA, true, true);
|
|
}
|
|
|
|
if (!GetArchetype()->GetHasLoaded())
|
|
{
|
|
CModelInfo::GetStreamingModule()->StreamingRequest(strLocalIndex(GetModelIndex()), STRFLAG_PRIORITY_LOAD | STRFLAG_DONTDELETE);
|
|
}
|
|
else if (GetRequiresACustomArchetype() && !IsFlagSet(PF_GotCustomArchetype))
|
|
{
|
|
CreateDrawable();
|
|
AssignCustomArchetype();
|
|
|
|
if(GetNetworkObject())
|
|
{
|
|
CNetObjPickup* pickup = SafeCast(CNetObjPickup, GetNetworkObject());
|
|
if(pickup && pickup->GetDisableCollisionCompletely())
|
|
{
|
|
DisableCollision(nullptr, true);
|
|
}
|
|
}
|
|
}
|
|
else if (GetPlacement() && GetPlacement()->GetRoomHash() != 0 && !GetIsInInterior())
|
|
{
|
|
MoveIntoInterior();
|
|
}
|
|
else
|
|
{
|
|
if (!IsFlagSet(PF_Initialised))
|
|
{
|
|
if (!GetIsAttached() && IsNetworkClone() && GetNetworkObject()->GetNetBlender())
|
|
{
|
|
// the net blender may have predicted the pickup under the map while it had no physics, so move it back to the last received position
|
|
// or it may fall through the map
|
|
Vector3 lastPosReceived = static_cast<netBlenderLinInterp*>(GetNetworkObject()->GetNetBlender())->GetLastPositionReceived();
|
|
Vector3 lastVelReceived = static_cast<netBlenderLinInterp*>(GetNetworkObject()->GetNetBlender())->GetLastVelocityReceived();
|
|
|
|
// move the pickup up very slightly as the quantization of the position may have pushed the pickup down into the map slightly and it may fall through
|
|
lastPosReceived.z += 0.02f;
|
|
|
|
SetPosition(lastPosReceived);
|
|
SetVelocity(lastVelReceived);
|
|
}
|
|
|
|
if (IsFlagSet(PF_DroppedFromLocalPlayer))
|
|
{
|
|
SetFlag(PF_LocalPlayerCollision);
|
|
}
|
|
}
|
|
|
|
// pickup is now setup and added to world correctly
|
|
SetFlag(PF_Initialised);
|
|
|
|
// ambient pickups fade out and are removed after a while
|
|
HandleFade();
|
|
|
|
if(GetHealth() <= 0.0f && !IsFlagSet(PF_Destroyed) && GetPickupData()->GetCanBeDamaged())
|
|
{
|
|
AddExplosion();
|
|
}
|
|
|
|
if (!GetIsAttached() && IsFlagSet(PF_PlaceOnGround) && !IsFlagSet(PF_PlacedOnGround) && !IsFlagSet(PF_WarpToAccessibleLocation) && !GetPickupData()->GetIsAirbornePickup())
|
|
{
|
|
if (g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(GetTransform().GetPosition(), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER))
|
|
{
|
|
if(PlaceOnGroundProperly(3.0f, IsFlagSet(PF_OrientToGround), m_heightOffGround, GetPlacement()==NULL, IsFlagSet(PF_UprightOnGround)))
|
|
{
|
|
SetFlag(PF_PlacedOnGround);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sanity check collection states match between the pickup and its placement
|
|
if (GetPlacement() && IsFlagSet(PF_Collected) != GetPlacement()->GetIsCollected() && GetNetworkObject() && GetPlacement()->GetNetworkObject())
|
|
{
|
|
if (IsFlagSet(PF_Collected))
|
|
{
|
|
Assertf(0, "%s is flagged as not collected but its pickup %s is flagged as collected", GetPlacement()->GetNetworkObject()->GetLogName(), GetNetworkObject()->GetLogName());
|
|
}
|
|
else
|
|
{
|
|
Assertf(0, "%s is flagged as collected but its pickup %s is flagged as not collected", GetPlacement()->GetNetworkObject()->GetLogName(), GetNetworkObject()->GetLogName());
|
|
}
|
|
}
|
|
|
|
CPed* pPendingCarrier = m_pendingCarrier.Get();
|
|
|
|
if (pPendingCarrier)
|
|
{
|
|
AttachPortablePickupToPed(pPendingCarrier, "Pending carrier");
|
|
}
|
|
|
|
#if __ASSERT
|
|
if (IsAlwaysFixed() && !WasDroppedInWater() && GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel())
|
|
{
|
|
if (GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE))
|
|
{
|
|
Assertf(!CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()), "Fixed pickup %s has active physics with FLAG_NEVER_ACTIVATE flag set!", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "");
|
|
}
|
|
else
|
|
{
|
|
Assertf(0, "Fixed pickup %s has FLAG_NEVER_ACTIVATE flag cleared!", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "");
|
|
}
|
|
}
|
|
#endif // __ASSERT
|
|
|
|
if (!IsFlagSet(PF_LocalPlayerCollision))
|
|
{
|
|
if(IsFlagSet(PF_HelpTextDisplayed))
|
|
{
|
|
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
|
|
}
|
|
ClearFlag(PF_HelpTextDisplayed);
|
|
ClearFlag(PF_DroppedFromLocalPlayer);
|
|
}
|
|
|
|
ClearFlag(PF_LocalPlayerCollision);
|
|
}
|
|
|
|
// if we are waiting to collect this pickup, do it now if we own it
|
|
if (GetIsPendingCollection())
|
|
{
|
|
if (m_pendingCollectionType == COLLECT_REMOTE)
|
|
{
|
|
// remote pending collections time out after a while
|
|
m_pendingCollectionTimer -= (s16)fwTimer::GetTimeStepInMilliseconds();
|
|
|
|
if (m_pendingCollectionTimer <= 0)
|
|
{
|
|
ClearPendingCollection();
|
|
}
|
|
}
|
|
else if (!(GetPlacement() && GetPlacement()->GetIsMapPlacement()))
|
|
{
|
|
netObject* pNetObj = GetPlacement() ? GetPlacement()->GetNetworkObject() : GetNetworkObject();
|
|
|
|
if (!pNetObj || (!pNetObj->IsClone() && !pNetObj->IsPendingOwnerChange()))
|
|
{
|
|
ProcessCollectionResponse(NULL, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_Portable))
|
|
{
|
|
UpdatePortablePickup();
|
|
}
|
|
|
|
|
|
if (!IsFlagSet(PF_Collected) && (IsFlagSet(PF_TransparentWhenUncollectable) || IsFlagSet(PF_AddArrowMarker)))
|
|
{
|
|
CPed* pPed = CGameWorld::FindLocalPlayer();
|
|
|
|
static const unsigned FULL_ALPHA = 255;
|
|
|
|
if (pPed && (CanCollectScript(pPed) || IsFlagSet(PF_AllowArrowMarkerWhenUncollectable)))
|
|
{
|
|
if (IsFlagSet(PF_AddArrowMarker))
|
|
{
|
|
Vec3V vPickupPos = GetTransform().GetPosition();
|
|
Vec3V vCamPos = VECTOR3_TO_VEC3V(camInterface::GetPos());
|
|
float distToPickup = Dist(vCamPos, vPickupPos).Getf();
|
|
if (distToPickup<ARROW_MARKER_FADE_OUT_RANGE)
|
|
{
|
|
float alphaMult = 1.0f;
|
|
if (distToPickup>ARROW_MARKER_FADE_START_RANGE)
|
|
{
|
|
alphaMult = (ARROW_MARKER_FADE_OUT_RANGE-distToPickup) / (ARROW_MARKER_FADE_OUT_RANGE-ARROW_MARKER_FADE_START_RANGE);
|
|
}
|
|
|
|
u8 alpha = static_cast<u8>(alphaMult*ARROW_MARKER_COL.GetAlphaf()*GetAlpha());
|
|
|
|
MarkerInfo_t markerInfo;
|
|
markerInfo.type = MARKERTYPE_ARROW;
|
|
markerInfo.vPos = vPickupPos + Vec3V(0.0f, 0.0f, ARROW_MARKER_Z_OFFSET);
|
|
markerInfo.vRot = Vec4V(PI, 0.0f, 0.0f, 0.0f);
|
|
markerInfo.vScale = Vec3V(ARROW_MARKER_SCALE, ARROW_MARKER_SCALE, ARROW_MARKER_SCALE);
|
|
markerInfo.col = ARROW_MARKER_COL;
|
|
markerInfo.col.SetAlpha(alpha);
|
|
markerInfo.faceCam = ARROW_MARKER_FACE_CAM;
|
|
markerInfo.bounce = ARROW_MARKER_BOUNCE;
|
|
g_markers.Register(markerInfo);
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_TransparentWhenUncollectable))
|
|
{
|
|
if (GetAlpha() < FULL_ALPHA)
|
|
{
|
|
SetAlpha(FULL_ALPHA);
|
|
}
|
|
}
|
|
}
|
|
else if (IsFlagSet(PF_TransparentWhenUncollectable))
|
|
{
|
|
// fade out pickups that have their collection prohibited by script
|
|
SetAlpha(PICKUP_ALPHA_WHEN_TRANSPARENT);
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_CreateDefaultWeaponAttachments))
|
|
{
|
|
// Find out what weapon this is
|
|
u32 weaponHash = m_customWeaponHash;
|
|
|
|
if(weaponHash == 0)
|
|
{
|
|
u32 numRewards = m_pPickupData->GetNumRewards();
|
|
for (u32 i=0; i<numRewards; i++)
|
|
{
|
|
const CPickupRewardData* pReward = GetPickupData()->GetReward(i);
|
|
if (pReward && pReward->GetType() == PICKUP_REWARD_TYPE_WEAPON)
|
|
{
|
|
const CPickupRewardWeapon* pWeaponReward = static_cast<const CPickupRewardWeapon*>(pReward);
|
|
weaponHash = pWeaponReward->GetWeaponHash();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (weaponHash != 0)
|
|
{
|
|
// Ensure drawable is ready.
|
|
if(GetDrawable())
|
|
{
|
|
CWeapon* pWeapon = GetWeapon();
|
|
|
|
// Setup the weapon
|
|
if (!pWeapon)
|
|
{
|
|
pWeapon = WeaponFactory::Create(weaponHash, 1, this, "CPickup::Update");
|
|
SetWeapon(pWeapon);
|
|
}
|
|
|
|
// Ensure its streamed in (re-using weapon streaming code)
|
|
if (!m_pWeaponItemForStreaming && CInventoryItem::GetPool()->GetNoOfFreeSpaces() != 0)
|
|
{
|
|
m_pWeaponItemForStreaming = rage_new CWeaponItem(0, weaponHash, NULL);
|
|
}
|
|
|
|
#if __ASSERT
|
|
if(!m_pWeaponItemForStreaming)
|
|
{
|
|
CInventoryItem::SpewPoolUsage();
|
|
Assertf(0, "Inventory item pool is full, check tty for pool usage spew!");
|
|
}
|
|
#endif // __ASSERT
|
|
|
|
Assert(m_pWeaponItemForStreaming && m_pWeaponItemForStreaming->GetInfo()->GetHash() == weaponHash);
|
|
|
|
if (m_pWeaponItemForStreaming && m_pWeaponItemForStreaming->GetIsStreamedIn())
|
|
{
|
|
if (m_pWeaponItemForStreaming->GetInfo())
|
|
{
|
|
CPedEquippedWeapon::SetupAsWeapon(this, m_pWeaponItemForStreaming->GetInfo(), 1, true, NULL, pWeapon);
|
|
SetForceAlphaAndUseAmbientScale();
|
|
SetUseLightOverrideForGlow();
|
|
ClearFlag(PF_CreateDefaultWeaponAttachments);
|
|
}
|
|
}
|
|
}
|
|
#if !__NO_OUTPUT
|
|
else
|
|
{
|
|
Printf("GetDrawable() is NULL for pickup [%s] 0x%p, Placement 0x%p\n", m_pPickupData->GetName(), this, GetPlacement());
|
|
}
|
|
#endif // !__NO_OUTPUT
|
|
}
|
|
else
|
|
{
|
|
Printf("Not creating weapon for pickup [%s] 0x%p, Placement 0x%p\n", m_pPickupData->GetName(), this, GetPlacement());
|
|
|
|
// No weapon reward, flag to not do this again
|
|
ClearFlag(PF_CreateDefaultWeaponAttachments);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_pWeaponItemForStreaming)
|
|
{
|
|
delete m_pWeaponItemForStreaming;
|
|
m_pWeaponItemForStreaming = NULL;
|
|
}
|
|
}
|
|
|
|
if (!GetIsAttached())
|
|
{
|
|
if( GetPickupData()->GetRotates() || (GetPlacement() && GetPlacement()->GetShouldRotate()) )
|
|
{
|
|
const float time = (float)fwTimer::GetSystemTimeInMilliseconds();
|
|
const float angle = fwAngle::LimitRadianAngleSafe(time * g_pickupRotationDelta);
|
|
|
|
// Centre. We only centre if we're rotating on a placement
|
|
if( GetPlacement() )
|
|
{
|
|
Vector3 offset = VEC3V_TO_VECTOR3(GetBaseModelInfo()->GetBoundingBox().GetCenter());
|
|
|
|
Vector3 pickupOrigPosition = GetPlacement()->GetPickupPosition();
|
|
|
|
if (GetPickupData()->GetCollectableInBoat())
|
|
{
|
|
// pickups positioned above the water need to move up and down with the waves, so we need to calculate the wave offset and add it
|
|
// to the pickup's z position
|
|
float waveLevelWithWaves = 0.0f;
|
|
|
|
if (m_waterLevelNoWaves < 0.0f)
|
|
{
|
|
Water::GetWaterLevelNoWaves(pickupOrigPosition, &m_waterLevelNoWaves, 2.0f, 4.0f, NULL);
|
|
}
|
|
|
|
if (m_waterLevelNoWaves >= 0.0f && Water::GetWaterLevel(pickupOrigPosition, &waveLevelWithWaves, false, 2.0f, 4.0f, NULL))
|
|
{
|
|
float waveHeight = waveLevelWithWaves-m_waterLevelNoWaves;
|
|
pickupOrigPosition.z += waveHeight;
|
|
}
|
|
}
|
|
|
|
Matrix44 offsetMtx(Matrix44::IdentityType);
|
|
offsetMtx.d.Set(Vector4(-offset.x, -offset.y, 0.0f, 1.0f));
|
|
|
|
Matrix44 rotationMtx(Matrix44::IdentityType);
|
|
|
|
Matrix44 ToWorldPosMtx(Matrix44::IdentityType);
|
|
ToWorldPosMtx.d.Set(Vector4(pickupOrigPosition.GetX(), pickupOrigPosition.GetY(), pickupOrigPosition.GetZ(), 1.0f));
|
|
|
|
bool forceFaceUp = CPickupManager::GetForceRotatePickupFaceUp();
|
|
|
|
if(!forceFaceUp)
|
|
{
|
|
rotationMtx.MakeRotZ(angle);
|
|
|
|
|
|
Matrix44 finalMtx = offsetMtx;
|
|
finalMtx.Dot(rotationMtx);
|
|
finalMtx.Dot(ToWorldPosMtx);
|
|
|
|
Matrix34 final34Mtx;
|
|
finalMtx.ToMatrix34(final34Mtx);
|
|
SetMatrix(final34Mtx);
|
|
}
|
|
else
|
|
{
|
|
Matrix34 matrx;
|
|
Vec3V vecForward(GetTransform().GetB());
|
|
Vec3V_Out cross = Cross(Vec3V(0.0f, 0.0f, 1.0f), vecForward);
|
|
matrx.a.Set(Vector3(cross.GetXf(), cross.GetYf(), cross.GetZf()));
|
|
matrx.b.Set(Vector3(0.0f, 0.0f, 1.0f));
|
|
matrx.c.Set(Vector3(vecForward.GetXf(), vecForward.GetYf(), vecForward.GetZf()));
|
|
matrx.d.Set(GetTransform().GetPosition().GetXf(), GetTransform().GetPosition().GetYf(), GetTransform().GetPosition().GetZf());
|
|
matrx.Normalize();
|
|
SetMatrix(matrx);
|
|
|
|
rotationMtx.MakeRotX(angle);
|
|
|
|
Matrix44 finalMtx = offsetMtx;
|
|
finalMtx.Dot(rotationMtx);
|
|
finalMtx.Dot(ToWorldPosMtx);
|
|
|
|
Matrix34 final34Mtx;
|
|
finalMtx.ToMatrix34(final34Mtx);
|
|
SetMatrix(final34Mtx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetHeading(angle);
|
|
}
|
|
}
|
|
|
|
if( GetPickupData()->GetFacePlayer() || (GetPlacement() && GetPlacement()->GetShouldFacePlayer()) )
|
|
{
|
|
const Vec3V pos = GetTransform().GetPosition();
|
|
const Vec3V lookAtPos = VECTOR3_TO_VEC3V(camInterface::GetPos());
|
|
const Vec3V dir = lookAtPos - pos;
|
|
const Vec3V b = Normalize(dir);
|
|
const Vec3V up = Vec3V(V_Z_AXIS_WZERO);
|
|
const Vec3V a = NormalizeSafe( Cross(b,up), Vec3V(V_Y_AXIS_WZERO), Vec3V(V_FLT_EPSILON) ) ;
|
|
const Vec3V c = Cross(a,b);
|
|
|
|
Mat34V mtx;
|
|
mtx.SetCol0(a);
|
|
mtx.SetCol1(b);
|
|
mtx.SetCol2(c);
|
|
mtx.SetCol3(pos);
|
|
|
|
SetMatrix(MAT34V_TO_MATRIX34(mtx));
|
|
}
|
|
}
|
|
|
|
if (!IsFlagSet(PF_SleepThresholdsInitialized) && GetCollider())
|
|
{
|
|
static float sm_DefaultVelTolerance2 = 0.05f; //0.005f;
|
|
static float sm_DefaultAngVelTolerance2 = 1.0f; //0.01f;
|
|
GetCollider()->GetSleep()->SetVelTolerance2(sm_DefaultVelTolerance2);
|
|
GetCollider()->GetSleep()->SetAngVelTolerance2(sm_DefaultAngVelTolerance2);
|
|
SetFlag(PF_SleepThresholdsInitialized);
|
|
}
|
|
|
|
// If it's loaded, one of the code paths above should have created a dependency by now, so we can safely remove the DONTDELETE flag.
|
|
if (GetArchetype()->GetHasLoaded())
|
|
{
|
|
CModelInfo::GetStreamingModule()->ClearRequiredFlag(GetModelIndex(), STRFLAG_DONTDELETE);
|
|
}
|
|
}
|
|
|
|
void CPickup::AttachPortablePickupToPed(CPed* pPed, const char* reason)
|
|
{
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
// the pickup cannot be attached until it has it's custom archetype
|
|
if (GetRequiresACustomArchetype() && !IsFlagSet(PF_GotCustomArchetype))
|
|
{
|
|
m_pendingCarrier = pPed;
|
|
|
|
if (pPed->GetPlayerInfo())
|
|
{
|
|
if (pLog)
|
|
{
|
|
pLog->Log("PortablePickupPending set to true: AttachPortablePickupToPed\n");
|
|
}
|
|
Assert(!pPed->GetPlayerInfo()->PortablePickupPending);
|
|
pPed->GetPlayerInfo()->PortablePickupPending = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
eAnimBoneTag attachmentBone = GetPickupData()->GetAttachmentBone();
|
|
// const Vector3 attachOffset = GetPickupData()->GetAttachmentOffset();
|
|
// const Vector3 attachRotation = GetPickupData()->GetAttachmentRotation();
|
|
|
|
if (Verifyf(attachmentBone != BONETAG_INVALID, "Trying to attach a portable pickup that has no attachment data specified in the meta file"))
|
|
{
|
|
const Vector3 attachOffset = GetPickupData()->GetAttachmentOffset();
|
|
const Vector3 attachRotation = GetPickupData()->GetAttachmentRotation();
|
|
|
|
Quaternion quatRotate;
|
|
quatRotate.Identity();
|
|
if(attachRotation.IsNonZero())
|
|
{
|
|
CScriptEulers::QuaternionFromEulers(quatRotate, DtoR * attachRotation, static_cast<EulerAngleOrder>(EULER_XYZ));
|
|
}
|
|
|
|
if (GetIsAttached())
|
|
{
|
|
DetachFromParent(0);
|
|
}
|
|
|
|
u32 nBasicAttachFlags = ATTACH_STATE_BASIC|ATTACH_FLAG_INITIAL_WARP;
|
|
AttachToPhysicalBasic( pPed, (s16)pPed->GetBoneIndexFromBoneTag(attachmentBone), nBasicAttachFlags, &attachOffset, &quatRotate );
|
|
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Attach reason", reason);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::DetachPortablePickupFromPed(const char* LOGGING_ONLY(reason), bool bPlaceOnGround)
|
|
{
|
|
if (GetAttachParent() && Verifyf(static_cast<CEntity*>(GetAttachParent())->GetIsTypePed(), "DetachPortablePickupFromPed: the pickup is not attached to a ped"))
|
|
{
|
|
CPed* pPed = static_cast<CPed*>(GetAttachParent());
|
|
|
|
bool bInWater = IsInWater(*pPed);
|
|
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
if (pPed->GetIsInVehicle() && pPed->GetMyVehicle())
|
|
{
|
|
// prevent the pickup from colliding with the ped's vehicle
|
|
SetNoCollisionEntity(pPed->GetMyVehicle());
|
|
|
|
if (GetPickupData()->GetCollectableInVehicle())
|
|
{
|
|
SetFlag(PF_CollisionsWithPedVehicleDisabled);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// prevent the pickup from colliding with the ped
|
|
SetNoCollisionEntity(pPed);
|
|
SetFlag(PF_CollisionsWithPedDisabled);
|
|
}
|
|
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
|
|
if (pLog && GetNetworkObject())
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "DETACHING_PORTABLE_PICKUP", GetNetworkObject()->GetLogName());
|
|
pLog->WriteDataValue("Reason", "%s", reason);
|
|
pLog->WriteDataValue("Detached from", "%s", pPed->GetNetworkObject() ? pPed->GetNetworkObject()->GetLogName() : "??");
|
|
pLog->WriteDataValue("Detached at", "%f, %f, %f", pickupPos.x, pickupPos.y, pickupPos.z);
|
|
pLog->WriteDataValue("Last accessible pos", "%f, %f, %f", m_lastAccessibleLocation.x, m_lastAccessibleLocation.y, m_lastAccessibleLocation.z);
|
|
pLog->WriteDataValue("Inaccessible", "%s", IsFlagSet(PF_Inaccessible) ? "True" : "False");
|
|
|
|
if (!IsFlagSet(PF_Inaccessible))
|
|
{
|
|
pLog->WriteDataValue("Has valid ground", "%s", IsFlagSet(PF_LastAccessiblePosHasValidGround) ? "true" : "false");
|
|
|
|
if (bInWater)
|
|
{
|
|
pLog->WriteDataValue("In water", "true");
|
|
}
|
|
|
|
if (IsFlagSet(PF_LyingOnFixedObject))
|
|
{
|
|
pLog->WriteDataValue("Lying on fixed object", "true");
|
|
}
|
|
|
|
if (IsFlagSet(PF_LyingOnUnFixedObject))
|
|
{
|
|
pLog->WriteDataValue("Lying on unfixed object", "true");
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
if (pLog && (CGameWorld::IsEntityBeingRemoved(this) || pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_PedBeingDeleted )))
|
|
{
|
|
pLog->WriteDataValue("Reason", "Ped being deleted");
|
|
}
|
|
|
|
if(GetPickupHash()==PICKUP_JETPACK)
|
|
{
|
|
CObject::DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY|DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR);
|
|
}
|
|
else
|
|
{
|
|
CObject::DetachFromParent(0);
|
|
|
|
bool forceActivatePhysics = false;
|
|
if(IsFlagSet(PF_ForceActivatePhysicsOnDropNearSubmarine))
|
|
{
|
|
if(IsPickupDroppedNearSubmarine(pickupPos))
|
|
{
|
|
forceActivatePhysics = true;
|
|
}
|
|
}
|
|
|
|
if (forceActivatePhysics || IsFlagSet(PF_ForceActivatePhysicsOnPickup) || (!IsAlwaysFixed() && (IsFlagSet(PF_LyingOnFixedObject) || IsFlagSet(PF_LyingOnUnFixedObject))))
|
|
{
|
|
ActivatePhysicsOnPortablePickup();
|
|
bPlaceOnGround = false;
|
|
}
|
|
else
|
|
{
|
|
CObject::SetFixedPhysics(true, false);
|
|
|
|
m_nObjectFlags.bActivatePhysicsAsSoonAsUnfrozen = false;
|
|
|
|
SetFlag(PF_OrientToGround);
|
|
}
|
|
|
|
if (GetPickupData()->GetIsAirbornePickup())
|
|
{
|
|
static float MIN_DIST_FROM_GROUND = 15.0f;
|
|
|
|
// pickups collectable by plane are always left in the air
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Airborne pickup", "True");
|
|
}
|
|
|
|
bool bFoundGround = false;
|
|
|
|
// only leave in air if the plane is a certain distance above the ground
|
|
float groundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, pickupPos.x, pickupPos.y, pickupPos.z+MAX_BUILDING_CLIFF_HEIGHT, &bFoundGround);
|
|
|
|
float waterZ = 0.0f;
|
|
|
|
Water::GetWaterLevelNoWaves(pickupPos, &waterZ, POOL_DEPTH, REJECTIONABOVEWATER, NULL);
|
|
|
|
// use water level if the ground is under water
|
|
if (groundZ < waterZ || bInWater)
|
|
{
|
|
groundZ = waterZ;
|
|
bFoundGround = true;
|
|
}
|
|
|
|
if (pLog)
|
|
{
|
|
if (bFoundGround)
|
|
{
|
|
pLog->WriteDataValue("Found ground / water at", "%0.2f", groundZ);
|
|
pLog->WriteDataValue("Difference", "%0.2f", pickupPos.z - groundZ);
|
|
}
|
|
else
|
|
{
|
|
pLog->WriteDataValue("Found ground", "False");
|
|
}
|
|
}
|
|
|
|
if (bFoundGround && (groundZ+MIN_DIST_FROM_GROUND > pickupPos.z))
|
|
{
|
|
pickupPos.z = groundZ+MIN_DIST_FROM_GROUND;
|
|
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Too near ground at", "%f", groundZ);
|
|
pLog->WriteDataValue("Pickup z now", "%f", pickupPos.z);
|
|
}
|
|
|
|
Teleport(pickupPos);
|
|
}
|
|
}
|
|
else if (bInWater)
|
|
{
|
|
DropInWater();
|
|
}
|
|
else if (bPlaceOnGround)
|
|
{
|
|
Vector3 droppedPos = m_lastAccessibleLocation;
|
|
|
|
if (!IsFlagSet(PF_Inaccessible))
|
|
{
|
|
if (!IsFlagSet(PF_LastAccessiblePosHasValidGround))
|
|
{
|
|
float groundZ = 0.0f;
|
|
bool bFoundGround = false;
|
|
|
|
bool mapLoadedAboutPos = g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(GetTransform().GetPosition(), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER);
|
|
|
|
if (mapLoadedAboutPos)
|
|
{
|
|
groundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, droppedPos.x, droppedPos.y, droppedPos.z+0.5f, &bFoundGround);
|
|
|
|
if (bFoundGround)
|
|
{
|
|
if (pLog)
|
|
{
|
|
if (groundZ > droppedPos.z)
|
|
{
|
|
pLog->WriteDataValue("Below map", "True");
|
|
}
|
|
|
|
pLog->WriteDataValue("Ground Z found", "%f", groundZ);
|
|
}
|
|
|
|
droppedPos.z = groundZ + m_heightOffGround;
|
|
}
|
|
else
|
|
{
|
|
if(pLog)
|
|
{
|
|
pLog->WriteDataValue("Failed to find ground", "True");
|
|
}
|
|
|
|
// the ground was not found or is too far away
|
|
SetFlag(PF_Inaccessible);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("No map collision", "True");
|
|
}
|
|
|
|
// the ground was not found
|
|
SetFlag(PF_Inaccessible);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsNetworkClone() && IsFlagSet(PF_DroppedInWater))
|
|
{
|
|
DropInWater(false);
|
|
}
|
|
|
|
if (IsFlagSet(PF_Inaccessible))
|
|
{
|
|
if (pPed->GetIsInInterior())
|
|
{
|
|
SetFlag(PF_DroppedInInterior);
|
|
}
|
|
|
|
if (!IsNetworkClone() REPLAY_ONLY(&& GetOwnedBy() != ENTITY_OWNEDBY_REPLAY))
|
|
{
|
|
MoveToAccessibleLocation();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FindSuitablePortablePickupDropLocation(droppedPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
ForceUpdateOfDroppedPortablePickupData();
|
|
|
|
OnPedDetachment(pPed);
|
|
|
|
if (GetNetworkObject() && pPed->GetNetworkObject())
|
|
{
|
|
NetworkSetPositionOfPlaneDroppedPortablePickup(pPed, pPed->GetMyVehicle());
|
|
|
|
CNetObjPickup* pPickupObj = static_cast<CNetObjPickup*>(GetNetworkObject());
|
|
|
|
if (pPickupObj->GetPendingAttachmentObjectID() == pPed->GetNetworkObject()->GetObjectID())
|
|
{
|
|
pPickupObj->ClearPendingAttachmentData();
|
|
}
|
|
}
|
|
|
|
// this is a hacky GTAV-specific fix due to camManager::ModifyPedVisibility
|
|
// we expect this to be unnecessary once we have a more correct fix available
|
|
if(!GetRenderPhaseVisibilityMask().IsFlagSet(VIS_PHASE_MASK_GBUF))
|
|
{
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Reset GBuffer Visibility Mask", "True");
|
|
}
|
|
Displayf("Reset GBuffer Visibility Mask for pickup: %s", GetLogName());
|
|
|
|
GetRenderPhaseVisibilityMask().SetFlag(VIS_PHASE_MASK_GBUF);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::Destroy()
|
|
{
|
|
// Destroy() can be called more than once before the pickup is removed so check for this.
|
|
if (!IsFlagSet(PF_Destroyed) && AssertVerify(!IsBaseFlagSet(fwEntity::REMOVE_FROM_WORLD)))
|
|
{
|
|
SetFlag(PF_Destroyed);
|
|
|
|
FlagToDestroyWhenNextProcessed();
|
|
|
|
// hide pickup and remove collision to prevent further collection detection
|
|
Hide(true);
|
|
|
|
CTheScripts::UnregisterEntity(this, false);
|
|
|
|
if (GetNetworkObject() && !IsNetworkClone())
|
|
{
|
|
NetworkInterface::UnregisterObject(this);
|
|
}
|
|
|
|
if (m_pPlacement)
|
|
{
|
|
m_pPlacement->SetPickup(NULL);
|
|
m_pPlacement = NULL;
|
|
}
|
|
|
|
if (IsFlagSet(PF_HelpTextDisplayed))
|
|
{
|
|
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
|
|
ClearFlag(PF_HelpTextDisplayed);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPickup::IsPickupDroppedNearSubmarine(Vector3 pickupPos)
|
|
{
|
|
const float MIN_SUBMARINE_DISTANCE_FOR_PHYSICS_ACTIVATION = 100.0f;
|
|
|
|
CVehicle::Pool* pool = CVehicle::GetPool();
|
|
s32 i = (s32) pool->GetSize();
|
|
while (i--)
|
|
{
|
|
CVehicle* veh = pool->GetSlot(i);
|
|
if(veh && veh->GetModelNameHash() == ATSTRINGHASH("Kosatka", 0x4FAF0D70))
|
|
{
|
|
Vector3 vehPos(veh->GetVehiclePosition().GetXf(), veh->GetVehiclePosition().GetYf(), veh->GetVehiclePosition().GetZf());
|
|
if(pickupPos.Dist(vehPos) <MIN_SUBMARINE_DISTANCE_FOR_PHYSICS_ACTIVATION)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
netLoggingInterface* CPickup::GetNetworkLog() const
|
|
{
|
|
netLoggingInterface* pLog = NULL;
|
|
|
|
LOGGING_ONLY(pLog = NetworkInterface::IsGameInProgress() ? &NetworkInterface::GetObjectManager().GetLog() : NULL);
|
|
|
|
return pLog;
|
|
}
|
|
|
|
|
|
void CPickup::OnActivate(phInst* pInst, phInst* pOtherInst)
|
|
{
|
|
CObject::OnActivate(pInst, pOtherInst);
|
|
|
|
if (phCollider* collider = GetCollider())
|
|
{
|
|
collider->DisablePushCollisions();
|
|
}
|
|
}
|
|
|
|
//PURPOSE: special pickup constructor
|
|
bool CPickup::CreateDrawable()
|
|
{
|
|
bool rt = CObject::CreateDrawable();
|
|
// Somehow pickups are being created without a skeleton but being flagged as skinned. The shadow pass don't like that
|
|
// It pushes them thru as skinned but without a matrixset
|
|
if(GetSkeleton() == NULL)
|
|
ClearRenderFlags(RenderFlag_IS_SKINNED);
|
|
return rt;
|
|
}
|
|
|
|
// Name : RequiresProcessControl
|
|
// Purpose : Some pickups need to be on the process control list
|
|
bool CPickup::RequiresProcessControl() const
|
|
{
|
|
return (!IsAlwaysFixed() || GetPickupData()->GetLoopingSoundHash());
|
|
}
|
|
|
|
ePrerenderStatus CPickup::PreRender(const bool bIsVisibleInMainViewport)
|
|
{
|
|
return CObject::PreRender(bIsVisibleInMainViewport);
|
|
}
|
|
|
|
bool CPickup::HasGlow() const
|
|
{
|
|
LOGGING_ONLY(HasGlowFailReason failReason = HasGlowFailReason::None;)
|
|
|
|
bool bHasGlow = m_pPickupData->GetHasGlow();
|
|
|
|
LOGGING_ONLY(if(!bHasGlow) failReason = HasGlowFailReason::PickupData;)
|
|
|
|
if (!(CanCollectScript(CGameWorld::FindLocalPlayer()) || IsFlagSet(PF_GlowOnProhibitCollection)) || IsFlagSet(PF_DontGlow) )
|
|
{
|
|
bHasGlow = false;
|
|
LOGGING_ONLY(
|
|
if(IsFlagSet(PF_DontGlow)) failReason = HasGlowFailReason::DoNotGlowFlag;
|
|
else failReason = HasGlowFailReason::CanNotCollectScript;
|
|
)
|
|
}
|
|
|
|
// stop pickups glowing that our player is prohibited from collecting
|
|
if (bHasGlow && NetworkInterface::IsGameInProgress() && NetworkInterface::GetLocalPlayer())
|
|
{
|
|
// portable pickups can only be collected by the participants of the script that created them
|
|
if (IsFlagSet(PF_Portable) && !IsFlagSet(PF_DebugCreated))
|
|
{
|
|
const CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
|
|
if (pScriptExtension)
|
|
{
|
|
scriptHandler* pScriptHandler = pScriptExtension->GetScriptHandler();
|
|
|
|
if (!m_allowNonScriptParticipantCollection
|
|
&& (!pScriptHandler ||
|
|
!pScriptHandler->GetNetworkComponent() ||
|
|
pScriptHandler->GetNetworkComponent()->GetState() != scriptHandlerNetComponent::NETSCRIPT_PLAYING ||
|
|
!pScriptHandler->GetNetworkComponent()->IsPlayerAParticipant(*NetworkInterface::GetLocalPlayer())))
|
|
{
|
|
bHasGlow = false;
|
|
LOGGING_ONLY(failReason = HasGlowFailReason::NotParticipant;)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHasGlow = false;
|
|
LOGGING_ONLY(failReason = HasGlowFailReason::NoScriptExtension;)
|
|
}
|
|
}
|
|
|
|
if (bHasGlow)
|
|
{
|
|
// some pickups can only be collected by certain teams, so only glow for those teams. They always glow regardless of whether the
|
|
// local player is permitted to collect them.
|
|
if (IsFlagSet(PF_TeamPermitsSet))
|
|
{
|
|
if (NetworkInterface::GetLocalPlayer()->GetTeam() == INVALID_TEAM ||
|
|
(!(m_teamPermits & (1<<NetworkInterface::GetLocalPlayer()->GetTeam())) && !GetGlowWhenInSameTeam()))
|
|
{
|
|
bHasGlow = false;
|
|
LOGGING_ONLY(failReason = HasGlowFailReason::NotForTeam;)
|
|
}
|
|
}
|
|
else if (!IsFlagSet(PF_GlowOnProhibitCollection) && CPickupManager::IsCollectionProhibited(GetPickupHash(), NetworkInterface::GetLocalPlayer()->GetPhysicalPlayerIndex()))
|
|
{
|
|
bHasGlow = false;
|
|
LOGGING_ONLY(failReason = HasGlowFailReason::ProhibitCollection;)
|
|
}
|
|
}
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if(m_LastHasGlowFailReason != failReason)
|
|
{
|
|
m_LastHasGlowFailReason = failReason;
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
netLoggingInterface &log = NetworkInterface::GetObjectManager().GetLog();
|
|
NetworkLogUtils::WriteLogEvent(log, "PICKUP_CANNOT_GLOW", "%s", GetLogName());
|
|
log.WriteDataValue("Reason", "%s", GetCannotGlowReason(m_LastHasGlowFailReason));
|
|
}
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
return bHasGlow;
|
|
}
|
|
|
|
bool CPickup::HasLifetime() const
|
|
{
|
|
return (!GetPlacement() &&
|
|
!IsFlagSet(PF_Portable) &&
|
|
!IsFlagSet(PF_DontFadeOut) &&
|
|
GetOwnedBy() != ENTITY_OWNEDBY_SCRIPT &&
|
|
!GetExtension<CScriptEntityExtension>());
|
|
}
|
|
|
|
// Name : PreRender2
|
|
// Purpose : Handles pickup rendering effects (pickup glow)
|
|
// Parameters : None
|
|
// Returns : Nothing
|
|
void CPickup::PreRender2(const bool bIsVisibleInMainViewport)
|
|
{
|
|
CObject::PreRender2(bIsVisibleInMainViewport);
|
|
}
|
|
|
|
void CPickup::Teleport(const Vector3& vecSetCoors, float fSetHeading, bool bCalledByPedTask, bool bTriggerPortalRescan, bool bCalledByPedTask2, bool bWarp, bool UNUSED_PARAM(bKeepRagdoll), bool UNUSED_PARAM(bResetPlants))
|
|
{
|
|
CObject::Teleport(vecSetCoors, fSetHeading, bCalledByPedTask, bTriggerPortalRescan, bCalledByPedTask2, bWarp);
|
|
|
|
if (!IsNetworkClone())
|
|
{
|
|
ClearDroppedInWater();
|
|
}
|
|
|
|
SetPlaceOnGround(IsFlagSet(PF_OrientToGround), IsFlagSet(PF_UprightOnGround));
|
|
|
|
// stop any search for a new respawn location in case this is getting called by script
|
|
ClearFlag(PF_WarpToAccessibleLocation);
|
|
|
|
if (GetNetworkObject() && !GetNetworkObject()->IsClone() && !GetNetworkObject()->IsPendingOwnerChange())
|
|
{
|
|
static_cast<CNetObjPickup*>(GetNetworkObject())->SetOverridingRemoteVisibility(false, false, "Teleporting pickup");
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
void CPickup::DebugAttachToPhysicalBasic(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOrientation, s16 nMyBone)
|
|
{
|
|
fwEntity* pAttachParent = GetAttachParent();
|
|
|
|
CObject::DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOrientation, nMyBone);
|
|
|
|
if (GetAttachParent() == pPhysical && pAttachParent != GetAttachParent() && pPhysical->GetIsTypePed())
|
|
{
|
|
OnPedAttachment(static_cast<CPed*>(pPhysical));
|
|
}
|
|
}
|
|
#else
|
|
void CPickup::AttachToPhysicalBasic(CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOrientation, s16 nMyBone)
|
|
{
|
|
CObject::AttachToPhysicalBasic(pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOrientation, nMyBone);
|
|
|
|
if (pPhysical->GetIsTypePed() && AssertVerify(GetAttachParent() == pPhysical))
|
|
{
|
|
OnPedAttachment(static_cast<CPed*>(pPhysical));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CPickup::SetFixedPhysics(bool bFixed, bool bNetwork)
|
|
{
|
|
// don't allow other systems or script to tinker with the fixed state of permanently fixed pickups
|
|
if (!IsAlwaysFixed())
|
|
{
|
|
#if ENABLE_NETWORK_LOGGING
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (pLog && GetNetworkObject() && IsFlagSet(PF_Portable))
|
|
{
|
|
if (bFixed)
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "PORTABLE_PICKUP_BEING_FIXED", GetNetworkObject()->GetLogName());
|
|
}
|
|
else
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "PORTABLE_PICKUP_BEING_UNFIXED", GetNetworkObject()->GetLogName());
|
|
}
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
CObject::SetFixedPhysics(bFixed, bNetwork);
|
|
}
|
|
}
|
|
|
|
u32 CPickup::GetDefaultInstanceIncludeFlags(phInst* pInst) const
|
|
{
|
|
u32 includeFlags = CObject::GetDefaultInstanceIncludeFlags(pInst);
|
|
|
|
if (GetRequiresACustomArchetype() && IsFlagSet(CPickup::PF_Initialised))
|
|
{
|
|
phArchetypeDamp* pCustomArchetype = CPickupManager::GetCustomArchetype(*this);
|
|
|
|
if (AssertVerify(pCustomArchetype))
|
|
{
|
|
includeFlags = pCustomArchetype->GetIncludeFlags();
|
|
}
|
|
}
|
|
|
|
if( m_includeProjectiles )
|
|
{
|
|
includeFlags |= ArchetypeFlags::GTA_PROJECTILE_TYPE;
|
|
}
|
|
|
|
return includeFlags;
|
|
}
|
|
|
|
void CPickup::DetachFromParent(u16 nDetachFlags)
|
|
{
|
|
if(GetCurrentPhysicsInst() && !GetCurrentPhysicsInst()->IsInLevel())
|
|
{
|
|
AddPhysics();
|
|
}
|
|
|
|
CEntity* pAttachParent = SafeCast(CEntity, GetAttachParent());
|
|
|
|
if (pAttachParent && pAttachParent->GetIsTypePed())
|
|
{
|
|
if( !(nDetachFlags & DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS ))
|
|
{
|
|
if (CGameWorld::IsEntityBeingRemoved(this))
|
|
{
|
|
CObject::DetachFromParent(nDetachFlags);
|
|
OnPedDetachment((CPed*)pAttachParent);
|
|
}
|
|
else
|
|
{
|
|
DetachPortablePickupFromPed("Detach from parent");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsAlwaysFixed())
|
|
{
|
|
nDetachFlags &= ~DETACH_FLAG_ACTIVATE_PHYSICS;
|
|
}
|
|
|
|
CObject::DetachFromParent(nDetachFlags);
|
|
|
|
if(IsNetworkClone() && GetNetworkObject())
|
|
{
|
|
CNetObjPickup* netPickup = SafeCast(CNetObjPickup, GetNetworkObject());
|
|
if(netPickup->GetNetBlender())
|
|
{
|
|
netPickup->GetNetBlender()->GoStraightToTarget();
|
|
}
|
|
}
|
|
|
|
if (IsAlwaysFixed())
|
|
{
|
|
if(GetCurrentPhysicsInst())
|
|
{
|
|
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPickup::TestNoCollision(const phInst *pOtherInst)
|
|
{
|
|
if (IsFlagSet(PF_CollisionsWithPedDisabled) || IsFlagSet(PF_CollisionsWithPedVehicleDisabled))
|
|
{
|
|
const CEntity * pOtherEntity = CPhysics::GetEntityFromInst(pOtherInst);
|
|
const CEntity* pNoCollisionEntity = (const CEntity*)GetNoCollisionEntity();
|
|
|
|
if (pOtherEntity && pNoCollisionEntity && pOtherEntity == pNoCollisionEntity)
|
|
{
|
|
if (IsFlagSet(PF_CollisionsWithPedDisabled) && pNoCollisionEntity->GetIsTypePed())
|
|
{
|
|
const CPed* pPed = static_cast<const CPed*>(pOtherEntity);
|
|
|
|
if (!pPed->IsDead())
|
|
{
|
|
SetNoCollisionEntity(NULL);
|
|
ClearFlag(PF_CollisionsWithPedDisabled);
|
|
}
|
|
}
|
|
else if (IsFlagSet(PF_CollisionsWithPedVehicleDisabled) && pNoCollisionEntity->GetIsTypeVehicle())
|
|
{
|
|
// when a ped dies in a vehicle and drops the pickup, collisions are disabled between the vehicle and pickup to prevent physics glitches. We need to enable them again if the vehicle is being driven
|
|
// by a local alive ped, so that the pickup can be collected again
|
|
const CVehicle* pVehicle = static_cast<const CVehicle*>(pOtherEntity);
|
|
|
|
if (pVehicle->GetDriver() && !pVehicle->GetDriver()->IsNetworkClone() && !pVehicle->GetDriver()->IsDead())
|
|
{
|
|
SetNoCollisionEntity(NULL);
|
|
ClearFlag(PF_CollisionsWithPedVehicleDisabled);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return CObject::TestNoCollision(pOtherInst);
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
void CPickup::ShouldFindImpactsCalled()
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** ShouldFindImpactsCallback called on %s collision with local player **\n", GetNetworkObject()->GetLogName());
|
|
}
|
|
|
|
void CPickup::ShouldFindImpactsFailed(const char* reason)
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** FAILED: %s **\n", reason);
|
|
}
|
|
|
|
void CPickup::ShouldFindImpactsSuccess()
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** ShouldFindImpactsCallback successfully returned true for this pickup **\n", GetNetworkObject()->GetLogName());
|
|
}
|
|
|
|
const char* CPickup::GetCannotGlowReason(HasGlowFailReason reason) const
|
|
{
|
|
switch (reason)
|
|
{
|
|
case HasGlowFailReason::None:
|
|
return "None";
|
|
case HasGlowFailReason::PickupData:
|
|
return "PickupData";
|
|
case HasGlowFailReason::DoNotGlowFlag:
|
|
return "DoNotGlowFlag";
|
|
case HasGlowFailReason::CanNotCollectScript:
|
|
return "CanNotCollectScript";
|
|
case HasGlowFailReason::NotParticipant:
|
|
return "NotParticipant";
|
|
case HasGlowFailReason::NoScriptExtension:
|
|
return "NoScriptExtension";
|
|
case HasGlowFailReason::NotForTeam:
|
|
return "NotForTeam";
|
|
case HasGlowFailReason::ProhibitCollection:
|
|
return "ProhibitCollection";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
// Name : ProcessPreComputeImpacts
|
|
// Purpose : Called whenever there is a collision with the pickup. Detects "on foot" and "in car" collection.
|
|
// Parameters : impacts - the collision impact information
|
|
// Returns : Nothing
|
|
void CPickup::ProcessPreComputeImpacts(phContactIterator impacts)
|
|
{
|
|
////////////////////////////////////////
|
|
// ON FOOT & IN CAR COLLECTION DETECTION
|
|
////////////////////////////////////////
|
|
|
|
impacts.Reset();
|
|
while(!impacts.AtEnd())
|
|
{
|
|
// don't allow collection if the pickup is hidden
|
|
if (!GetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP))
|
|
{
|
|
impacts.DisableImpact();
|
|
impacts++;
|
|
continue;
|
|
}
|
|
|
|
if (GetRequiresACustomArchetype())
|
|
{
|
|
// Crash if archetype is set up wrong
|
|
physicsAssertf(impacts.GetMyComponent() == 0 || GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE, "Invalid bound type in pickup archetype");
|
|
|
|
const int iSphereComponent = (GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE) ? static_cast<phBoundComposite*>(GetCurrentPhysicsInst()->GetArchetype()->GetBound())->GetNumBounds()-1 : 0;
|
|
|
|
// always disable the impact for the additional sphere we added to the pickup's bounds for detecting collection
|
|
if (impacts.GetMyComponent() == iSphereComponent)
|
|
{
|
|
impacts.DisableImpact();
|
|
}
|
|
else
|
|
{
|
|
const float kfPickupFriction = 0.5f;
|
|
impacts.SetFriction(kfPickupFriction);
|
|
}
|
|
}
|
|
|
|
CEntity* pOtherEntity = impacts.GetOtherInstance() ? static_cast<CEntity*>(impacts.GetOtherInstance()->GetUserData()) : NULL;
|
|
CPed* pOtherPed = NULL;
|
|
eCollectionType collectionType = COLLECT_ONFOOT;
|
|
unsigned failureCode = PCC_NONE;
|
|
|
|
if (pOtherEntity)
|
|
{
|
|
if (pOtherEntity->GetIsTypePed())
|
|
{
|
|
pOtherPed = (CPed*)pOtherEntity;
|
|
}
|
|
else if (pOtherEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pOtherVehicle = static_cast<CVehicle*>(pOtherEntity);
|
|
pOtherPed = pOtherVehicle->GetDriver();
|
|
|
|
if( !pOtherPed || !pOtherPed->IsLocalPlayer())
|
|
{
|
|
// Search for the local player
|
|
for(int iSeatIndex = 0; iSeatIndex < pOtherVehicle->GetSeatManager()->GetMaxSeats(); iSeatIndex++)
|
|
{
|
|
pOtherPed = pOtherVehicle->GetSeatManager()->GetPedInSeat(iSeatIndex);
|
|
if(pOtherPed && pOtherPed->IsLocalPlayer())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
collectionType = COLLECT_INCAR;
|
|
}
|
|
}
|
|
|
|
if( pOtherPed && !pOtherPed->IsLocalPlayer() && NetworkInterface::IsGameInProgress() && !IsAlwaysFixed() && IsFlagSet(PF_Portable) )
|
|
{
|
|
impacts.DisableImpact();
|
|
impacts++;
|
|
continue;
|
|
}
|
|
|
|
LOGGING_ONLY(netLoggingInterface* pLog = NetworkInterface::IsGameInProgress() ? &NetworkInterface::GetObjectManagerLog() : NULL);
|
|
|
|
if (pOtherPed && pOtherPed->IsLocalPlayer())
|
|
{
|
|
LOGGING_ONLY(if (pLog) pLog->Log("** ProcessPreComputeImpacts called on %s collision with local player **\n", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "-map pickup-"));
|
|
|
|
#if FPS_MODE_SUPPORTED
|
|
bool bFirstPerson = pOtherPed->IsInFirstPersonVehicleCamera() || pOtherPed->IsFirstPersonShooterModeEnabledForPlayer(false);
|
|
|
|
if (GetPhysArch() && GetPhysArch()->GetBound())
|
|
{
|
|
const int iSphereComponent = (GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE) ? static_cast<phBoundComposite*>(GetCurrentPhysicsInst()->GetArchetype()->GetBound())->GetNumBounds()-1 : 0;
|
|
|
|
// always disable the impact for the additional sphere we added to the pickup's bounds for detecting collection
|
|
if (impacts.GetMyComponent() == iSphereComponent)
|
|
{
|
|
float fCollisionRadiusDiff = GetPickupData()->GetCollectionRadiusFirstPerson() - GetPickupData()->GetCollectionRadius();
|
|
|
|
if( (fCollisionRadiusDiff > 0.0f && !bFirstPerson) || (fCollisionRadiusDiff < 0.0f && bFirstPerson) )
|
|
{
|
|
fCollisionRadiusDiff = abs(fCollisionRadiusDiff);
|
|
float fDepth = impacts.GetDepth();
|
|
if(fDepth < fCollisionRadiusDiff)
|
|
{
|
|
impacts.DisableImpact();
|
|
impacts++;
|
|
LOGGING_ONLY(if (pLog) pLog->Log("** Impact rejected due to 1st/3rd person collection sphere check (1st person radius: %f, 3rd person: %f, depth = %f) **\n", GetPickupData()->GetCollectionRadiusFirstPerson(), GetPickupData()->GetCollectionRadius(), fDepth));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (CanCollect(pOtherPed, collectionType, &failureCode))
|
|
{
|
|
LOGGING_ONLY(if (pLog) pLog->Log("** CanCollect returned true **\n"));
|
|
|
|
if (collectionType == COLLECT_ONFOOT)
|
|
{
|
|
CollectOnFoot(pOtherPed);
|
|
}
|
|
else
|
|
{
|
|
CollectInCar(pOtherPed);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(failureCode == PCC_ON_THE_PHONE)
|
|
{
|
|
if(IsFlagSet(PF_HelpTextDisplayed))
|
|
{
|
|
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
|
|
ClearFlag(PF_HelpTextDisplayed);
|
|
}
|
|
}
|
|
|
|
// Display help text for manual pickups.
|
|
if(failureCode == PCC_BUTTON_NOT_PRESSED)
|
|
{
|
|
if (!IsFlagSet(PF_HelpTextDisplayed) && !IsFlagSet(PF_Destroyed))
|
|
{
|
|
bool bIsPetrolCan = false;
|
|
const u32 uPickupWeaponHash = GetPickupData()->GetFirstWeaponReward();
|
|
if (uPickupWeaponHash != 0)
|
|
{
|
|
const CWeaponInfo* pPickupWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uPickupWeaponHash);
|
|
if (pPickupWeaponInfo && pPickupWeaponInfo->GetGroup() == WEAPONGROUP_PETROLCAN)
|
|
{
|
|
bIsPetrolCan = true;
|
|
}
|
|
}
|
|
|
|
if(GetPickupData()->GetHash() == PICKUP_JETPACK)
|
|
{
|
|
CHelpMessage::SetMessageText(HELP_TEXT_SLOT_STANDARD, TheText.Get("PU_JET_HLP"));
|
|
SetFlag(PF_HelpTextDisplayed);
|
|
}
|
|
else if (bIsPetrolCan)
|
|
{
|
|
if (!pOtherPed->GetInventory()->GetWeapon(uPickupWeaponHash))
|
|
{
|
|
char* pJerryCanHelpText;
|
|
if(GetPickupData()->GetHash() == PICKUP_WEAPON_FERTILIZERCAN)
|
|
{
|
|
pJerryCanHelpText = TheText.Get("PU_FER_HLP");
|
|
}
|
|
else
|
|
{
|
|
pJerryCanHelpText = TheText.Get("PU_JER_HLP");
|
|
}
|
|
|
|
// B*2186514: Don't try to set the petrol can help text if we're already displaying it
|
|
bool bAlreadyDisplayingJerryCanText = false;
|
|
if (strcmp(CHelpMessage::GetMessageText(HELP_TEXT_SLOT_STANDARD), pJerryCanHelpText) == 0)
|
|
{
|
|
bAlreadyDisplayingJerryCanText = true;
|
|
SetFlag(PF_HelpTextDisplayed);
|
|
}
|
|
|
|
if (!bAlreadyDisplayingJerryCanText)
|
|
{
|
|
CHelpMessage::SetMessageText(HELP_TEXT_SLOT_STANDARD, pJerryCanHelpText);
|
|
SetFlag(PF_HelpTextDisplayed);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CHelpMessage::SetMessageText(HELP_TEXT_SLOT_STANDARD, TheText.Get("PU_HLP"));
|
|
SetFlag(PF_HelpTextDisplayed);
|
|
}
|
|
}
|
|
|
|
pOtherPed->SetPedResetFlag(CPED_RESET_FLAG_TemporarilyBlockWeaponSwitching, true);
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (pLog)
|
|
{
|
|
const char* failureReason = GetCanCollectFailureString(failureCode);
|
|
|
|
if (GetNetworkObject())
|
|
{
|
|
pLog->Log("** CAN'T COLLECT %s %s: %s\n", GetNetworkObject()->GetLogName(), collectionType == COLLECT_ONFOOT ? "ON FOOT" : "IN VEHICLE", failureReason);
|
|
}
|
|
else
|
|
{
|
|
Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
pLog->Log("** CAN'T COLLECT PICKUP AT %f, %f, %f %s: %s\n", pos.x, pos.y, pos.z, collectionType == COLLECT_ONFOOT ? "ON FOOT" : "IN VEHICLE", failureReason);
|
|
}
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
if (IsFlagSet(PF_DroppedFromLocalPlayer) || (NetworkInterface::IsGameInProgress() && !IsAlwaysFixed() && IsFlagSet(PF_Portable)) )
|
|
{
|
|
impacts.DisableImpact();
|
|
}
|
|
}
|
|
|
|
SetFlag(PF_LocalPlayerCollision);
|
|
}
|
|
|
|
bool bCollectableInVehicle = GetPickupData() ? GetPickupData()->GetCollectableInVehicle() : false;
|
|
bool bOtherEntityIsVehicle = false;
|
|
|
|
if(pOtherEntity && pOtherEntity->GetIsTypeVehicle())
|
|
{
|
|
bool disableKosatkaCollisionCheck = Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("DISABLE_KOSATKA_PICKUP_COLLISION_CHECK", 0xBD6EAC14), false);
|
|
if(disableKosatkaCollisionCheck || !MI_SUB_KOSATKA.IsValid() || pOtherEntity->GetModelIndex() != MI_SUB_KOSATKA)
|
|
{
|
|
bOtherEntityIsVehicle = true;
|
|
}
|
|
}
|
|
|
|
if ((!IsCollideable() && !IsFlagSet(PF_Portable)) || IsFlagSet(PF_Collected) || (bCollectableInVehicle && bOtherEntityIsVehicle))
|
|
{
|
|
impacts.DisableImpact();
|
|
}
|
|
|
|
impacts++;
|
|
}
|
|
}
|
|
|
|
// Name : ProcessWeaponImpact
|
|
// Purpose : Called whenever a weapon hits the pickup. Detects "on shot" collection.
|
|
// Parameters : pParentEntity - the entity using the weapon
|
|
// Returns : True if the impact is to be ignored by the rest of the weapon impact code
|
|
bool CPickup::ProcessWeaponImpact(CEntity* pParentEntity)
|
|
{
|
|
////////////////////////////////
|
|
// ON SHOT COLLECTION DETECTION
|
|
////////////////////////////////
|
|
|
|
// ignore clone shots
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
netObject* pNetObj = NetworkUtils::GetNetworkObjectFromEntity(pParentEntity);
|
|
|
|
if (pNetObj && pNetObj->IsClone())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool bIgnoreImpact = IsAlwaysFixed();
|
|
|
|
if (pParentEntity->GetIsTypePed() && GetPickupData()->GetCollectableOnShot())
|
|
{
|
|
// pickup is being collected by shooting
|
|
CPed* pPed = (CPed*)pParentEntity;
|
|
|
|
unsigned failureCode = PCC_NONE;
|
|
|
|
if (pPed->IsLocalPlayer())
|
|
{
|
|
if (CanCollect(pPed, COLLECT_ONSHOT, &failureCode))
|
|
{
|
|
CollectOnShot(pPed);
|
|
bIgnoreImpact = true;
|
|
}
|
|
else
|
|
{
|
|
#if ENABLE_NETWORK_LOGGING
|
|
const char* failureReason = GetCanCollectFailureString(failureCode);
|
|
|
|
if (GetNetworkObject())
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** CAN'T COLLECT %s BY SHOT: %s\n", GetNetworkObject()->GetLogName(), failureReason);
|
|
}
|
|
/* else
|
|
{
|
|
Vector3 position = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
} */
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetPickupData()->GetCanBeDamaged())
|
|
{
|
|
AddExplosion(pParentEntity);
|
|
}
|
|
|
|
return bIgnoreImpact;
|
|
}
|
|
|
|
// Create explosion if a valid explosion tag is set. Destroy this pickup after explosion.
|
|
void CPickup::AddExplosion(CEntity* explosionOwner)
|
|
{
|
|
eExplosionTag tag = GetPickupData()->GetExplosionTag();
|
|
if(tag != EXP_TAG_DONTCARE)
|
|
{
|
|
Vector3 vFireDirection(0.0f, 0.0f, 1.0f);
|
|
|
|
if (!explosionOwner)
|
|
{
|
|
if (GetWeaponDamageEntity())
|
|
explosionOwner = GetWeaponDamageEntity();
|
|
else
|
|
explosionOwner = this;
|
|
}
|
|
|
|
CExplosionManager::CExplosionArgs explosionArgs(tag, VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
|
|
|
|
explosionArgs.m_pEntExplosionOwner = explosionOwner;
|
|
explosionArgs.m_weaponHash = WEAPONTYPE_EXPLOSION;
|
|
explosionArgs.m_bInAir = false;
|
|
explosionArgs.m_vDirection = vFireDirection;
|
|
explosionArgs.m_originalExplosionTag = tag;
|
|
explosionArgs.m_bIsLocalOnly = true;
|
|
|
|
CExplosionManager::AddExplosion(explosionArgs);
|
|
|
|
if (GetPlacement())
|
|
{
|
|
GetPlacement()->SetPickupHasBeenDestroyed(true);
|
|
}
|
|
else if (GetNetworkObject())
|
|
{
|
|
CPickupDestroyedEvent::Trigger(*this);
|
|
}
|
|
|
|
if (!GetPlacement() && GetNetworkObject() && GetNetworkObject()->IsClone())
|
|
{
|
|
NetworkInterface::UnregisterObject(this, true);
|
|
}
|
|
else
|
|
{
|
|
Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if this pickup can be damaged by fire.
|
|
// Use the original bound radius to ignore the extra sphere bound manually added for pickup.
|
|
bool CPickup::CanBeDamagedByFire(CFire* pFire)
|
|
{
|
|
// portable pickups cannot be damaged while attached
|
|
if (IsFlagSet(PF_Portable) && GetIsAttached())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(pFire && GetPickupData()->GetCanBeDamaged())
|
|
{
|
|
Vector3 vFirePos = VEC3V_TO_VECTOR3(pFire->GetPositionWorld());
|
|
float fFireRadius = pFire->CalcCurrRadius();
|
|
Vector3 vPickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
if((vFirePos - vPickupPos).Mag() <= (m_originalBoundRadius + fFireRadius))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPickup::CanPhysicalBeDamaged(const CEntity* pInflictor, u32 nWeaponUsedHash, bool bDoNetworkCloneCheck, bool bDisableDamagingOwner NOTFINAL_ONLY(, u32* uRejectionReason)) const
|
|
{
|
|
// portable pickups cannot be damaged while attached
|
|
if (IsFlagSet(PF_Portable) && GetIsAttached())
|
|
{
|
|
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_AttachedPortablePickup; })
|
|
return false;
|
|
}
|
|
|
|
return CObject::CanPhysicalBeDamaged(pInflictor, nWeaponUsedHash, bDoNetworkCloneCheck, bDisableDamagingOwner NOTFINAL_ONLY(, uRejectionReason));
|
|
}
|
|
|
|
|
|
// Name : GetInScope
|
|
// Purpose : Pickups out of scope are too far away from the player and have to be removed
|
|
// Parameters : None
|
|
// Returns : True if in scope of the player
|
|
bool CPickup::GetInScope(const CPed& playerPed) const
|
|
{
|
|
float scopeRange = IsFlagSet(PF_UseExtendedScope) ? (float)PICKUP_EXTENDED_SCOPE_RANGE : (float)PICKUP_DEFAULT_SCOPE_RANGE;
|
|
|
|
// use the placement scope calculation function, so pickup removal is consistent with collection
|
|
return CPickupPlacement::GetInScope(playerPed, VEC3V_TO_VECTOR3(GetTransform().GetPosition()), scopeRange);
|
|
}
|
|
|
|
// Name : IsFixed
|
|
// Purpose : Returns true if the pickup is always fixed and has no physics
|
|
bool CPickup::IsAlwaysFixed() const
|
|
{
|
|
if (GetPlacement() && GetPlacement()->GetIsFixed())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if((GetPickupData() && GetPickupData()->GetRotates())
|
|
|| (GetPlacement() && GetPlacement()->GetShouldRotate()))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (IsFlagSet(PF_UnderwaterPickup) && IsUnderWater())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(WasDroppedInWater())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (GetPickupData()->GetAlwaysFixed());
|
|
}
|
|
|
|
// Name : IsFixed
|
|
// Purpose : Returns true if the pickup never collides with other entities
|
|
bool CPickup::IsCollideable() const
|
|
{
|
|
return !IsAlwaysFixed();
|
|
}
|
|
|
|
// Name : GetRequiresACustomArchetype
|
|
// Purpose : Pickups collectable on foot or in car and with a collection sphere need to have that sphere added to their collision archetype
|
|
// Parameters : None
|
|
// Returns : True if a custom archetype is required
|
|
bool CPickup::GetRequiresACustomArchetype() const
|
|
{
|
|
if (GetPickupData()->GetCollectionRadius() > 0.0f)
|
|
{
|
|
return (GetPickupData()->GetCollectableOnFoot() || GetAllowCollectionInVehicle());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPickup::GetAllowCollectionInVehicle() const
|
|
{
|
|
return GetPickupData()->GetCollectableInCar() || IsFlagSet(PF_AllowCollectionInVehicle);
|
|
}
|
|
|
|
bool CPickup::CanCollectCritical(const CPed* pPed, eCollectionType collectionType, unsigned *failureCode)
|
|
{
|
|
if(GetNetworkObject())
|
|
{
|
|
CNetObjPickup* netPickup = SafeCast(CNetObjPickup, GetNetworkObject());
|
|
if(netPickup && !netPickup->IsAllowedForLocalPlayer())
|
|
{
|
|
if (failureCode) *failureCode = PCC_NOT_ALLOWED_FOR_THIS_PLAYER;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!IsFlagSet(PF_Initialised))
|
|
{
|
|
if (failureCode) *failureCode = PCC_NOT_INITIALISED;
|
|
return false;
|
|
}
|
|
|
|
// if this flag is set we are waiting for a collection approval from another machine
|
|
if (GetIsPendingCollection())
|
|
{
|
|
if (failureCode) *failureCode = PCC_PENDING_COLLECTION;
|
|
return false;
|
|
}
|
|
|
|
if (GetHealth() < 0.001f)
|
|
{
|
|
if (failureCode) *failureCode = PCC_DESTROYED;
|
|
return false;
|
|
}
|
|
|
|
if (!CanCollectScript(pPed, failureCode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// don't allow collection when the ped is dead
|
|
if (pPed->GetIsDeadOrDying() || pPed->ShouldBeDead() || pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByStealth) || pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByTakedown))
|
|
{
|
|
if (failureCode) *failureCode = PCC_PED_IS_DEAD;
|
|
return false;
|
|
}
|
|
|
|
// Ignore certain checks when doing a team transfer - ie detaching the pickup from one dead player in a vehicle and attaching it to another passenger.
|
|
// The pickup is still attached to the other player at this point.
|
|
if (collectionType != COLLECT_TEAM_TRANSFER)
|
|
{
|
|
// collected pickups are not destroyed immediately, so can trigger collection a number of times before removal
|
|
if (IsFlagSet(PF_Collected))
|
|
{
|
|
if (failureCode) *failureCode = PCC_ALREADY_COLLECTED;
|
|
return false;
|
|
}
|
|
|
|
// pickups being carried cannot be collected
|
|
if (GetAttachParent() && SafeCast(CEntity, GetAttachParent())->GetIsTypePed())
|
|
{
|
|
if (failureCode) *failureCode = PCC_ATTACHED;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Don't allow collection if the pickup is part of a synced scene
|
|
CTask* pActiveTask = GetObjectIntelligence() ? GetObjectIntelligence()->GetTaskActive() : NULL;
|
|
|
|
if (pActiveTask && pActiveTask->GetTaskType() == CTaskTypes::TASK_SYNCHRONIZED_SCENE)
|
|
{
|
|
if (failureCode) *failureCode = PCC_IN_SYNCED_SCENE;
|
|
return false;
|
|
}
|
|
|
|
// Some player models are restricted from picking anything up.
|
|
if (pPed->IsAPlayerPed() && !CPlayerInfo::CanPlayerPedCollectAnyPickups(*pPed))
|
|
{
|
|
if (failureCode) *failureCode = PCC_INVALID_PED_MODEL;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPickup::CanCollectScript(const CPed* pPed, unsigned *failureCode) const
|
|
{
|
|
if (IsFlagSet(PF_CollectionProhibited) || (!pPed->IsNetworkClone() && IsFlagSet(PF_LocalCollectionProhibited)))
|
|
{
|
|
if (failureCode) *failureCode = PCC_COLLECTION_INSTANCE_PROHIBITED;
|
|
return false;
|
|
}
|
|
|
|
CNetGamePlayer* pPlayerOwner = pPed->GetNetworkObject() ? pPed->GetNetworkObject()->GetPlayerOwner() : NULL;
|
|
|
|
if (pPed->IsAPlayerPed())
|
|
{
|
|
if (pPlayerOwner)
|
|
{
|
|
// some pickups can only be collected by certain teams
|
|
if (IsFlagSet(PF_TeamPermitsSet) &&
|
|
(pPlayerOwner->GetTeam() == INVALID_TEAM ||
|
|
!(m_teamPermits & (1<<pPlayerOwner->GetTeam()))))
|
|
{
|
|
if (failureCode) *failureCode = PCC_WRONG_TEAM;
|
|
return false;
|
|
}
|
|
|
|
// certain players can be prohibited from collecting certain types of pickup in MP
|
|
PhysicalPlayerIndex playerIndex = pPlayerOwner->GetPhysicalPlayerIndex();
|
|
|
|
if (playerIndex != INVALID_PLAYER_INDEX && CPickupManager::IsCollectionProhibited(GetPickupHash(), playerIndex))
|
|
{
|
|
if (failureCode) *failureCode = PCC_COLLECTION_TYPE_PROHIBITED;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (pPed->IsLocalPlayer())
|
|
{
|
|
// scripts can prohibit collection of certain pickup models
|
|
CBaseModelInfo *modelInfo = GetBaseModelInfo();
|
|
if(AssertVerify(modelInfo))
|
|
{
|
|
if (CPickupManager::IsPickupModelCollectionProhibited(modelInfo->GetHashKey()))
|
|
{
|
|
if (failureCode) *failureCode = PCC_PICKUP_MODEL_TYPE_PROHIBITED;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_Portable))
|
|
{
|
|
CPlayerInfo* pPlayerInfo = pPed->GetPlayerInfo();
|
|
|
|
if (pPlayerInfo->m_maxPortablePickupsCarried >= 0 && pPlayerInfo->m_numPortablePickupsCarried >= pPlayerInfo->m_maxPortablePickupsCarried)
|
|
{
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (GetNetworkObject())
|
|
{
|
|
if (pPlayerInfo->m_maxPortablePickupsCarried == 0)
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** CAN'T COLLECT %s : the player has been blocked from collecting portable pickups by the scripts (m_maxPortablePickupsCarried is 0)\n", GetNetworkObject()->GetLogName());
|
|
}
|
|
else
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** CAN'T COLLECT %s : the player is carrying too many pickups (%d) - m_maxPortablePickupsCarried is %d\n", GetNetworkObject()->GetLogName(), pPlayerInfo->m_numPortablePickupsCarried, pPlayerInfo->m_maxPortablePickupsCarried);
|
|
}
|
|
}
|
|
#endif
|
|
if (failureCode) *failureCode = PCC_PORTABLE_PICKUPS_BLOCKED;
|
|
return false;
|
|
}
|
|
|
|
// the scripts can limit the amount of portable pickups that can be carried
|
|
if (pPlayerInfo && !pPlayerInfo->CanCarryPortablePickupOfType(GetModelIndex(), false))
|
|
{
|
|
if (failureCode) *failureCode = PCC_MAX_PORTABLE_PICKUPS;
|
|
return false;
|
|
}
|
|
|
|
if (!IsFlagSet(PF_DebugCreated) && pPlayerOwner)
|
|
{
|
|
// portable pickups can only be collected by the participants of the script that created them
|
|
const CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
|
|
if (pScriptExtension)
|
|
{
|
|
scriptHandler* pScriptHandler = pScriptExtension->GetScriptHandler();
|
|
|
|
if (!m_allowNonScriptParticipantCollection
|
|
&& (!pScriptHandler ||
|
|
!pScriptHandler->GetNetworkComponent() ||
|
|
pScriptHandler->GetNetworkComponent()->GetState() != scriptHandlerNetComponent::NETSCRIPT_PLAYING ||
|
|
!pScriptHandler->GetNetworkComponent()->IsPlayerAParticipant(*pPlayerOwner)))
|
|
{
|
|
if (failureCode) *failureCode = PCC_NON_PARTICIPANT;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (failureCode) *failureCode = PCC_DETACHED_FROM_SCRIPT;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Name : AssignCustomArchetype
|
|
// Purpose : Tries to assign the custom archetype
|
|
void CPickup::AssignCustomArchetype()
|
|
{
|
|
// make sure current current archetype is loaded (if one exists)
|
|
if ( (GetPhysArch() && GetCurrentPhysicsInst()) || !GetBaseModelInfo()->GetHasBoundInDrawable() )
|
|
{
|
|
// does a custom archetype already exist for this pickup type?
|
|
phArchetypeDamp* pCustomArchetype = CPickupManager::GetCustomArchetype(*this);
|
|
|
|
if (!pCustomArchetype)
|
|
{
|
|
// no, so create a new one
|
|
pCustomArchetype = CreateCustomArchetype();
|
|
|
|
// store the archetype for use by other pickups of this type
|
|
CPickupManager::StoreCustomArchetype(*this, pCustomArchetype);
|
|
}
|
|
|
|
if (IsFlagSet(PF_UprightOnGround))
|
|
{
|
|
m_heightOffGround = fabs(GetBoundingBoxMin().z);
|
|
}
|
|
else
|
|
{
|
|
m_heightOffGround = fabs(GetBoundingBoxMin().y);
|
|
}
|
|
|
|
if (AssertVerify(pCustomArchetype))
|
|
{
|
|
bool bAddPhysics = true;
|
|
|
|
// assign the custom archetype to the pickup
|
|
if (!GetPhysArch())
|
|
{
|
|
// give the pickup a phys inst first if it has none
|
|
phInstGta* pNewInst = rage_new phInstGta(PH_INST_OBJECT);
|
|
Assertf(pNewInst, "Failed to allocate a phInstGta for a new pickup.");
|
|
Matrix34 m = MAT34V_TO_MATRIX34(GetMatrix());
|
|
pNewInst->Init(*pCustomArchetype, m);
|
|
Assert(GetCurrentPhysicsInst() == NULL);
|
|
SetPhysicsInst(pNewInst, false);
|
|
m_originalBoundRadius = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
|
|
}
|
|
else
|
|
{
|
|
if (AssertVerify(GetCurrentPhysicsInst()->GetArchetype()) &&
|
|
AssertVerify(GetCurrentPhysicsInst()->GetArchetype()->GetBound()) &&
|
|
AssertVerify(pCustomArchetype->GetBound()))
|
|
{
|
|
// If the bound radius changes we need to inform the physics level
|
|
m_originalBoundRadius = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
|
|
GetCurrentPhysicsInst()->SetArchetype(pCustomArchetype);
|
|
float fNewRadius = GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
|
|
|
|
if(CPhysics::GetLevel()->IsInLevel(GetCurrentPhysicsInst()->GetLevelIndex()))
|
|
{
|
|
// Let the level know that we switched archetypes.
|
|
CPhysics::GetLevel()->UpdateObjectArchetype(GetCurrentPhysicsInst()->GetLevelIndex());
|
|
|
|
if(NetworkInterface::IsGameInProgress() && GetNetworkObject())
|
|
{
|
|
CNetObjEntity* netEnt = SafeCast(CNetObjEntity, GetNetworkObject());
|
|
if(netEnt && netEnt->GetDisableCollisionCompletely())
|
|
{
|
|
DisableCollision(NULL, true);
|
|
}
|
|
}
|
|
|
|
if(m_originalBoundRadius != fNewRadius)
|
|
{
|
|
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetCurrentPhysicsInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
|
|
}
|
|
|
|
// Don't need to add physics because its already been done, probably by CObject::InitPhys
|
|
// When an object requests its collision, it is added to the physics world as soon as collision is loaded
|
|
// as part of the physics request list.
|
|
// Therefore to simplify stuff here we would have to handle the streaming of pickups seperate to CObjects
|
|
bAddPhysics = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// fix the pickup and place on ground properly if a portable one
|
|
if (IsAlwaysFixed())
|
|
{
|
|
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE,true);
|
|
|
|
m_nObjectFlags.bActivatePhysicsAsSoonAsUnfrozen = false;
|
|
}
|
|
|
|
// Make sure RPG rocket falling on ground since it has zero gravity factor by default.
|
|
if(GetPickupHash() == PICKUP_AMMO_MISSILE_MP)
|
|
{
|
|
pCustomArchetype->SetGravityFactor(1.0f);
|
|
}
|
|
|
|
if (!GetIsRetainedByInteriorProxy())
|
|
{
|
|
if (Verifyf(GetOwnerEntityContainer() == NULL, "Pickup %s with a custom archetype is already in the world", GetNetworkObject() ? GetNetworkObject()->GetLogName() : ""))
|
|
{
|
|
// now we can add the pickup to the world
|
|
CGameWorld::Add(this, CGameWorld::OUTSIDE, !bAddPhysics );
|
|
bAddPhysics = false;
|
|
if (GetPlacement() && GetPlacement()->GetRoomHash())
|
|
{
|
|
MoveIntoInterior();
|
|
}
|
|
}
|
|
|
|
// pickups without placements have been dropped, so activate physics
|
|
if (!GetPlacement() && !IsAlwaysFixed())
|
|
{
|
|
if (bAddPhysics)
|
|
{
|
|
CPhysical::AddPhysics();
|
|
}
|
|
ActivatePhysics();
|
|
}
|
|
}
|
|
else if (bAddPhysics)
|
|
{
|
|
// we still need to add physics for the pickup, when it comes off the retain list the interior code will not add physics for it
|
|
CPhysical::AddPhysics();
|
|
}
|
|
|
|
// Apply the incoming velocity
|
|
if ((!m_StartingLinearVelocity.IsZero() || !m_StartingAngularVelocity.IsZero()) &&
|
|
m_StartingLinearVelocity.x == m_StartingLinearVelocity.x && m_StartingLinearVelocity.x*0.0f == 0.0f &&
|
|
m_StartingAngularVelocity.x == m_StartingAngularVelocity.x && m_StartingAngularVelocity.x*0.0f == 0.0f)
|
|
{
|
|
float linMag = m_StartingLinearVelocity.Mag();
|
|
float angMag = m_StartingAngularVelocity.Mag();
|
|
if (linMag == linMag && linMag*0.0f == 0.0f &&
|
|
angMag == angMag && angMag*0.0f == 0.0f)
|
|
{
|
|
linMag = Clamp(linMag, 0.0f, 20.0f);
|
|
angMag = Clamp(angMag, 0.0f, 20.0f);
|
|
m_StartingLinearVelocity.NormalizeSafe();
|
|
m_StartingAngularVelocity.NormalizeSafe();
|
|
GetCollider()->SetVelocity(m_StartingLinearVelocity * linMag);
|
|
GetCollider()->SetAngVelocity(m_StartingAngularVelocity * angMag);
|
|
|
|
// Apply the velocity now to avoid a one-frame freeze
|
|
ScalarV scal;
|
|
scal.Setf(fwTimer::GetTimeStep());
|
|
GetCollider()->UpdatePositionFromVelocity(scal.GetIntrin128Ref());
|
|
CDynamicEntity::SetMatrix(RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix()));
|
|
ScalarV orthoError(0.01f);
|
|
Assert(GetCurrentPhysicsInst()->GetMatrix().IsOrthonormal3x3(orthoError));
|
|
Assert(RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix()).d.Mag() < 70000.0f);
|
|
}
|
|
}
|
|
|
|
// tell the pickup to add itself to any interiors it may be in
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
GetPortalTracker()->SetProbeType(CPortalTracker::PROBE_TYPE_NEAR);
|
|
}
|
|
GetPortalTracker()->RequestRescanNextUpdate();
|
|
|
|
SetPickupScale();
|
|
|
|
// flag that this pickup has modified collision bounds
|
|
m_nPhysicalFlags.bModifiedBounds = true;
|
|
|
|
SetFlag(PF_GotCustomArchetype);
|
|
|
|
// inform the pickup manager that we are using this custom archetype
|
|
if (IsFlagSet(PF_HasCustomModel))
|
|
{
|
|
CPickupManager::AddRefForExtraCustomArchetype(pCustomArchetype);
|
|
}
|
|
|
|
// do an initial collision test to see if the local player is colliding with the pickup as it is spawned. This is to fix the bug where a player may
|
|
// be standing on the pickup but it will not be collected until the player moves and ProcessPreComputeImpacts called
|
|
CPed* pPlayerPed = CGameWorld::FindLocalPlayer();
|
|
|
|
if (pPlayerPed)
|
|
{
|
|
Vector3 diff = VEC3V_TO_VECTOR3(pPlayerPed->GetTransform().GetPosition()) - VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
float fCollectionRadius;
|
|
#if FPS_MODE_SUPPORTED
|
|
if(pPlayerPed->IsFirstPersonShooterModeEnabledForPlayer(false, true))
|
|
{
|
|
fCollectionRadius = GetPickupData()->GetCollectionRadiusFirstPerson();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
fCollectionRadius = GetPickupData()->GetCollectionRadius();
|
|
}
|
|
|
|
if (diff.Mag() <= fCollectionRadius*1.5f)
|
|
{
|
|
// activating the physics will result in ProcessPreComputeImpacts getting called when the physics system detects the collision
|
|
pPlayerPed->ActivatePhysics();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Name : MoveIntoInterior
|
|
// Purpose : Tries to move the pickup into its designated interior
|
|
void CPickup::MoveIntoInterior()
|
|
{
|
|
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
CInteriorInst* pIntInst = CInteriorInst::GetInstancedInteriorAtCoords(vThisPosition);
|
|
|
|
// if there is at least one instance interior in the area, then start probing for correct interior to insert into
|
|
if (pIntInst && pIntInst->CanReceiveObjects())
|
|
{
|
|
s32 probeRoomIdx;
|
|
CInteriorInst* pProbedIntInst = NULL;
|
|
CPortalTracker::ProbeForInterior(vThisPosition, pProbedIntInst, probeRoomIdx, NULL, CPortalTracker::SHORT_PROBE_DIST);
|
|
|
|
if (pProbedIntInst && pProbedIntInst->CanReceiveObjects())
|
|
{
|
|
#if __ASSERT
|
|
if (pProbedIntInst->FindRoomByHashcode(GetPlacement()->GetRoomHash()) == -1)
|
|
{
|
|
Assertf(false, "Pickup %s at (%.2f,%.2f,%.2f) failed to find room in interior %s for placement hash code %d",
|
|
GetModelName(),
|
|
vThisPosition.x, vThisPosition.y, vThisPosition.z,
|
|
pProbedIntInst->GetRoomName(probeRoomIdx),
|
|
GetPlacement()->GetRoomHash());
|
|
}
|
|
else
|
|
{
|
|
// Remove above assert
|
|
Assertf(pProbedIntInst->GetRoomHashcode(probeRoomIdx) == GetPlacement()->GetRoomHash(),
|
|
"[SCRIPT BUG] : Pickup @ (%.2f,%.2f,%.2f) using model %s is probably set to wrong room. Probe resolved room to %s : %d."
|
|
"Its placement hash was %d.",
|
|
vThisPosition.x, vThisPosition.y, vThisPosition.z,
|
|
GetModelName(),
|
|
pProbedIntInst->GetRoomName(probeRoomIdx),
|
|
probeRoomIdx,
|
|
GetPlacement()->GetRoomHash());
|
|
}
|
|
#endif
|
|
CGameWorld::Remove(this); // remove from external world
|
|
|
|
fwInteriorLocation interiorLocation = CInteriorInst::CreateLocation( pProbedIntInst, probeRoomIdx );
|
|
CGameWorld::Add(this, interiorLocation); // put object back into interior in world
|
|
|
|
// pickups without placements have been dropped, so activate physics
|
|
if (!GetPlacement() && !IsFlagSet(PF_Portable))
|
|
{
|
|
ActivatePhysics();
|
|
}
|
|
|
|
ClearFlag(PF_PlacedOnGround);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::HandleFade()
|
|
{
|
|
if (HasLifetime() && !fwTimer::IsGamePaused())
|
|
{
|
|
m_lifeTime += fwTimer::GetTimeStepInMilliseconds();
|
|
|
|
if (m_lifeTime >= (AMBIENT_PICKUP_LIFETIME - PICKUP_FADEOUT_TIME) && !GetIsPendingCollection())
|
|
{
|
|
if (m_lifeTime < AMBIENT_PICKUP_LIFETIME)
|
|
{
|
|
u8 alpha = static_cast<u8>((((float)(AMBIENT_PICKUP_LIFETIME - m_lifeTime) / (float) PICKUP_FADEOUT_TIME) * 255.0f));
|
|
|
|
SetAlpha(alpha);
|
|
}
|
|
else
|
|
{
|
|
SetAlpha(0);
|
|
|
|
if (!IsNetworkClone() && !(GetNetworkObject() && GetNetworkObject()->IsPendingOwnerChange()))
|
|
{
|
|
Destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Name : CreateCustomArchetype
|
|
// Purpose : Pickups that have a collection sphere require a custom archetype that is created here. The collision sphere is added to the
|
|
// existing collision archetype.
|
|
// Parameters : None
|
|
// Returns : The new archetype
|
|
phArchetypeDamp* CPickup::CreateCustomArchetype()
|
|
{
|
|
phArchetypeDamp* pNewArchetype = NULL;
|
|
phBound* pNewBound = NULL;
|
|
|
|
if (AssertVerify(GetRequiresACustomArchetype()) && AssertVerify(!IsFlagSet(PF_GotCustomArchetype)))
|
|
{
|
|
rage::phBoundSphere* pNewSphere = rage_new rage::phBoundSphere(GetPickupData()->GetCollectionRadius());
|
|
|
|
float fCollectionRadius;
|
|
#if FPS_MODE_SUPPORTED
|
|
fCollectionRadius = Max(GetPickupData()->GetCollectionRadiusFirstPerson(), GetPickupData()->GetCollectionRadius());
|
|
#else
|
|
fCollectionRadius = GetPickupData()->GetCollectionRadius();
|
|
#endif
|
|
|
|
pNewSphere->SetSphereRadius(fCollectionRadius);
|
|
|
|
phBound* pExistingBound = GetPhysArch() ? GetPhysArch()->GetBound() : NULL;
|
|
|
|
#if __ASSERT
|
|
// Verify that we aren't actually just waiting for a physics archetype to stream in
|
|
if(!GetPhysArch())
|
|
{
|
|
// If we DONT have an archetype but we DO have a valid physics dictionary index
|
|
// then we should wait on the archetype streaming in before calling this function
|
|
Assertf(!GetBaseModelInfo()->GetHasBoundInDrawable(),"Trying to create custom archetype before physics has streamed in for pickup");
|
|
}
|
|
#endif
|
|
|
|
if(!aiVerifyf((!pExistingBound || pExistingBound->GetType() != phBound::BVH),
|
|
"Pickup model has incomptabile bounds for pickup system: cannot be BVH!"))
|
|
{
|
|
// Pretend we have no bound if we have an incompatible type
|
|
pExistingBound = NULL;
|
|
}
|
|
|
|
// Set up the include flags here because we need them for the composite bound and the new archetype
|
|
// set flags in the sphere bound to say what type of physics object we wish it to collide with
|
|
u32 pickupSphereTypeFlags = ArchetypeFlags::GTA_PICKUP_TYPE;
|
|
u32 pickupSphereIncludeFlags = 0;
|
|
|
|
if (GetPickupData()->GetCollectableOnFoot())
|
|
pickupSphereIncludeFlags |= ArchetypeFlags::GTA_PED_TYPE;
|
|
|
|
if (GetPickupData()->GetCollectableInBoat() || GetPickupData()->GetCollectableInPlane() || GetAllowCollectionInVehicle())
|
|
pickupSphereIncludeFlags |= ArchetypeFlags::GTA_VEHICLE_TYPE;
|
|
|
|
if (GetPickupData()->GetCollectableOnShot())
|
|
{
|
|
pickupSphereIncludeFlags |= ArchetypeFlags::GTA_WEAPON_TEST;
|
|
pickupSphereIncludeFlags |= ArchetypeFlags::GTA_RAGDOLL_TYPE; // We want dropped weapons to collide with ragdoll bounds. In precomputeImpacts we'll filter out contacts between a pickup (intended usage is a dropped weapon) and the ped's arms, head, feet and chest since otherwise bad collision situations can occur
|
|
}
|
|
|
|
// Set up the collision flags for the original object that will still collide with the map and possibly other types
|
|
u32 collisionObjectTypeFlags = ArchetypeFlags::GTA_OBJECT_TYPE;
|
|
u32 collisionObjectIncludeFlags = ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES;
|
|
|
|
// Only weapons need to collide with ragdoll bounds. In precomputeImpacts we'll filter out contacts between a pickup (intended usage is a dropped weapon) and the ped's arms, head, feet and chest since otherwise bad collision situations can occur
|
|
if(GetPickupData()->GetDoesntCollideWithRagdolls())
|
|
{
|
|
collisionObjectIncludeFlags &= ~(ArchetypeFlags::GTA_RAGDOLL_TYPE);
|
|
}
|
|
if(GetPickupData()->GetDoesntCollideWithPeds())
|
|
{
|
|
collisionObjectIncludeFlags &= ~(ArchetypeFlags::GTA_PED_TYPE);
|
|
}
|
|
if(GetPickupData()->GetDoesntCollideWithVehicles())
|
|
{
|
|
// Don't exclude BVH type since that acts as a "ground". The inside of the cargo plane and boats for example.
|
|
collisionObjectIncludeFlags &= ~(ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE | ArchetypeFlags::GTA_WHEEL_TEST);
|
|
}
|
|
|
|
if( !m_includeProjectiles )
|
|
{
|
|
collisionObjectIncludeFlags &= ~(ArchetypeFlags::GTA_PROJECTILE_TYPE);
|
|
}
|
|
|
|
// Prevent camera shapetests from detecting pickups
|
|
// NOTE: We could use the instance include flags to do this only for 'always fixed' pickups, which disable all impacts in
|
|
// ProcessPreComputeImpacts. However, as the pickup objects are generally small, we really want to prevent the camera from popping
|
|
// around them as a result of occlusion anyway
|
|
// TODO: Modify the bound such that the camera system can avoid clipping whilst ignoring occlusion
|
|
collisionObjectIncludeFlags &= ~(ArchetypeFlags::GTA_CAMERA_TEST);
|
|
|
|
if (!pExistingBound)
|
|
{
|
|
// No existing bound: a new sphere bound is all our archetype needs
|
|
|
|
// pickups with no initial archetype must always be fixed, otherwise they will fall through the map when activated
|
|
Assert(IsAlwaysFixed());
|
|
|
|
pNewBound = pNewSphere;
|
|
|
|
// create a new archetype to hold this bound
|
|
pNewArchetype = rage_new phArchetypeDamp();
|
|
|
|
pNewArchetype->SetMass(1.0f);
|
|
pNewArchetype->SetAngInertia(Vector3(1.0f, 1.0f, 1.0f));
|
|
}
|
|
else
|
|
{
|
|
// We want to use our existing bound, so copy its contents into a new composite bound
|
|
// then add a sphere at the end
|
|
|
|
rage::phBoundComposite* pNewComposite = rage_new rage::phBoundComposite();
|
|
int nNumOriginalParts = 0;
|
|
|
|
// clone the existing archetype for this pickup
|
|
pNewArchetype = static_cast<phArchetypeDamp*>(GetPhysArch()->Clone());
|
|
|
|
Vec3V oldCGOffset(V_ZERO);
|
|
if (GetPhysArch()->GetBound()->GetType() != rage::phBound::COMPOSITE)
|
|
{
|
|
pNewComposite->Init(2);
|
|
|
|
phBound* oldBound = GetPhysArch()->GetBound();
|
|
pNewComposite->SetBound(0, oldBound);
|
|
oldCGOffset = oldBound->GetCGOffset();
|
|
|
|
pNewComposite->SetBound(1, pNewSphere);
|
|
pNewSphere->Release();
|
|
|
|
nNumOriginalParts = 1;
|
|
}
|
|
else
|
|
{
|
|
rage::phBoundComposite* pOldComposite = static_cast<rage::phBoundComposite*>(GetPhysArch()->GetBound());
|
|
nNumOriginalParts = pOldComposite->GetNumBounds();
|
|
|
|
pNewComposite->Init(nNumOriginalParts+1); // Need an extra slot for the sphere
|
|
|
|
// Copy the original bounds into the new composite
|
|
for(int i = 0; i < nNumOriginalParts; i++)
|
|
{
|
|
pNewComposite->SetBound(i, pOldComposite->GetBound(i));
|
|
pNewComposite->SetCurrentMatrix(i, pOldComposite->GetCurrentMatrix(i));
|
|
pNewComposite->SetLastMatrix(i, pOldComposite->GetLastMatrix(i));
|
|
}
|
|
oldCGOffset = pOldComposite->GetCGOffset();
|
|
|
|
// Now add the sphere at the end
|
|
pNewComposite->SetBound(nNumOriginalParts, pNewSphere);
|
|
pNewSphere->Release();
|
|
}
|
|
|
|
pNewComposite->AllocateTypeAndIncludeFlags();
|
|
|
|
for(int i = 0; i < nNumOriginalParts; i++)
|
|
{
|
|
pNewComposite->SetIncludeFlags(i,collisionObjectIncludeFlags);
|
|
pNewComposite->SetTypeFlags(i,collisionObjectTypeFlags);
|
|
}
|
|
|
|
// Set the sphere type up as a pickup
|
|
const int iSphereBoundIndex = nNumOriginalParts;
|
|
|
|
Assert(pickupSphereIncludeFlags);
|
|
|
|
pNewComposite->SetIncludeFlags(iSphereBoundIndex, pickupSphereIncludeFlags);
|
|
pNewComposite->SetTypeFlags(iSphereBoundIndex, pickupSphereTypeFlags);
|
|
pNewComposite->CalculateCompositeExtents();
|
|
pNewComposite->UpdateBvh(true);
|
|
|
|
pNewComposite->SetCGOffset(oldCGOffset);
|
|
|
|
pNewBound = pNewComposite;
|
|
}
|
|
|
|
if (pNewArchetype)
|
|
{
|
|
// setup the archetype
|
|
pNewArchetype->SetBound(pNewBound);
|
|
pNewBound->Release();
|
|
|
|
float fCollectionRadius;
|
|
#if FPS_MODE_SUPPORTED
|
|
fCollectionRadius = Max(GetPickupData()->GetCollectionRadiusFirstPerson(), GetPickupData()->GetCollectionRadius());
|
|
#else
|
|
fCollectionRadius = GetPickupData()->GetCollectionRadius();
|
|
#endif
|
|
|
|
// We know the proper radius of the pickup, use that instead of the one the physics computes for us.
|
|
// The physics doesn't know that the pickup is completely contained in the sphere, so it makes a
|
|
// sphere around the bounding box, which is no bueno.
|
|
pNewBound->SetRadiusAroundCentroid(ScalarV(fCollectionRadius));
|
|
|
|
int combinedTypeFlags = pickupSphereTypeFlags;
|
|
int combinedIncludeFlags = pickupSphereIncludeFlags;
|
|
|
|
if(pNewBound && pNewBound->GetType() == phBound::COMPOSITE)
|
|
{
|
|
// Make our archetype collide like a pickup AND an object, so we get all the collisions we need
|
|
combinedTypeFlags |= collisionObjectTypeFlags;
|
|
combinedIncludeFlags |= collisionObjectIncludeFlags;
|
|
}
|
|
// Else we have just made a sphere bound so keep the type and include flags PICKUP_TYPE only
|
|
|
|
pNewArchetype->SetTypeFlags(combinedTypeFlags);
|
|
pNewArchetype->SetIncludeFlags(combinedIncludeFlags);
|
|
|
|
// Add some default damping.
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::LINEAR_C,Vector3(0.05f,0.05f,0.05f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V,Vector3(0.05f,0.05f,0.05f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2,Vector3(0.05f,0.05f,0.05f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C,Vector3(40.0f,40.0f,40.0f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V,Vector3(10.0f,10.0f,10.0f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V2,Vector3(1.5f,1.5f,1.5f));
|
|
|
|
// Apply reasonable amount of damping for golf club. B*1905063
|
|
if(GetPickupData()->GetHash() == PICKUP_WEAPON_GOLFCLUB)
|
|
{
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C,Vector3(0.02f,0.02f,0.02f));
|
|
pNewArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V,Vector3(0.1f,0.1f,0.1f));
|
|
}
|
|
}
|
|
}
|
|
|
|
return pNewArchetype;
|
|
}
|
|
|
|
// Name : SetPickupScale
|
|
// Purpose : Scales the pickup model
|
|
bank_float pickupPlacedUpScale = 1.3f;
|
|
bank_float pickupNonPlacedUpScale = 1.0f;
|
|
|
|
void CPickup::SetPickupScale()
|
|
{
|
|
float scale = GetPickupData()->GetScale();
|
|
|
|
if( NetworkInterface::IsGameInProgress() && scale == 1.0f )
|
|
{
|
|
if( GetPlacement() )
|
|
scale *= pickupPlacedUpScale;
|
|
else
|
|
scale *= pickupNonPlacedUpScale;
|
|
}
|
|
|
|
ScaleObject(scale);
|
|
}
|
|
|
|
// Name : UpdatePortablePickup
|
|
// Purpose : Portable pickup specific stuff
|
|
void CPickup::UpdatePortablePickup()
|
|
{
|
|
// make sure it remains hidden
|
|
if (GetIsAttached())
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP, false, true);
|
|
}
|
|
else if (!IsFlagSet(PF_HideWhenDetached))
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP, true, true);
|
|
}
|
|
|
|
if (IsNetworkClone())
|
|
{
|
|
if (IsFlagSet(PF_WarpToAccessibleLocation))
|
|
{
|
|
ClearFlag(PF_WarpToAccessibleLocation);
|
|
|
|
if (!GetIsAttached())
|
|
{
|
|
Expose();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetIsAttached())
|
|
{
|
|
CEntity* pAttachParent = static_cast<CEntity*>(GetAttachParent());
|
|
|
|
if (pAttachParent->GetIsTypePed())
|
|
{
|
|
CPed* pAttachPed = static_cast<CPed*>(pAttachParent);
|
|
|
|
UpdateLastAccessibleLocation(*pAttachPed);
|
|
|
|
if (!GetNetworkObject() || !GetNetworkObject()->IsPendingOwnerChange())
|
|
{
|
|
// detach portable pickups from dead peds or players not running the script anymore
|
|
if (pAttachPed->GetIsDeadOrDying())
|
|
{
|
|
bool bDetach = true;
|
|
|
|
// if the dying ped is sharing a vehicle with another player, pass control of the pickup to him, or if this is our player and
|
|
// we are on the same team, attach the pickup to our player locally
|
|
if (pAttachPed->GetIsInVehicle())
|
|
{
|
|
CVehicle* pMyVehicle = pAttachPed->GetMyVehicle();
|
|
|
|
if (pMyVehicle && pMyVehicle->GetStatus() != STATUS_WRECKED)
|
|
{
|
|
CNetGamePlayer* pLocalNetPlayer = NetworkInterface::GetLocalPlayer();
|
|
CPed* pLocalPlayerPed = pLocalNetPlayer ? pLocalNetPlayer->GetPlayerPed() : NULL;
|
|
|
|
if (pLocalPlayerPed)
|
|
{
|
|
if (pAttachPed == pLocalPlayerPed)
|
|
{
|
|
// possibly pass control of the pickup to another team mate in the car
|
|
for (int i=0; i<pMyVehicle->GetSeatManager()->GetMaxSeats(); i++)
|
|
{
|
|
CPed* pOccupant = pMyVehicle->GetSeatManager()->GetPedInSeat(i);
|
|
|
|
if (pOccupant && pOccupant != pLocalPlayerPed && pOccupant->IsAPlayerPed() && !pOccupant->GetIsDeadOrDying())
|
|
{
|
|
CNetGamePlayer* pRemoteNetPlayer = pOccupant->GetNetworkObject() ? pOccupant->GetNetworkObject()->GetPlayerOwner() : NULL;
|
|
|
|
if (pRemoteNetPlayer && (pRemoteNetPlayer->GetTeam() == pLocalNetPlayer->GetTeam() || IsFlagSet(PF_DebugCreated)))
|
|
{
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (pLog)
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "MIGRATING_PORTABLE_PICKUP", GetNetworkObject()->GetLogName());
|
|
pLog->WriteDataValue("To player", "%s", pRemoteNetPlayer->GetLogName());
|
|
pLog->WriteDataValue("Reason", "Team mate in vehicle");
|
|
}
|
|
|
|
CGiveControlEvent::Trigger(*pRemoteNetPlayer, GetNetworkObject(), MIGRATE_FORCED);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (pLocalPlayerPed->GetIsInVehicle() && pLocalPlayerPed->GetMyVehicle() == pAttachPed->GetMyVehicle() && !pLocalPlayerPed->GetIsDeadOrDying())
|
|
{
|
|
CNetGamePlayer* pRemoteNetPlayer = pAttachPed->GetNetworkObject() ? pAttachPed->GetNetworkObject()->GetPlayerOwner() : NULL;
|
|
|
|
if (pRemoteNetPlayer && (pLocalNetPlayer->GetTeam() == pRemoteNetPlayer->GetTeam() || IsFlagSet(PF_DebugCreated)))
|
|
{
|
|
if (CanCollect(pLocalPlayerPed, COLLECT_TEAM_TRANSFER))
|
|
{
|
|
DetachPortablePickupFromPed("Ped is dead. Reattaching to local player in car.", false);
|
|
CollectInCar(pLocalPlayerPed);
|
|
}
|
|
|
|
bDetach = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDetach)
|
|
{
|
|
DetachPortablePickupFromPed("Ped is dead");
|
|
}
|
|
}
|
|
else if (IsFlagSet(PF_DetachWhenLocal))
|
|
{
|
|
DetachPortablePickupFromPed("PF_DetachWhenLocal");
|
|
}
|
|
else if (pAttachPed->IsAPlayerPed())
|
|
{
|
|
CPlayerInfo* pPlayerInfo = pAttachPed->GetPlayerInfo();
|
|
|
|
// the scripts can limit the amount of portable pickups that can be carried
|
|
if (AssertVerify(pPlayerInfo) && !pPlayerInfo->CanCarryPortablePickupOfType(GetModelIndex(), true) && pAttachPed->IsLocalPlayer())
|
|
{
|
|
DetachPortablePickupFromPed("CanCarryPortablePickupOfType returned false");
|
|
}
|
|
else if (pAttachPed->GetNetworkObject() && !IsFlagSet(PF_DebugCreated))
|
|
{
|
|
CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
|
|
if (pScriptExtension)
|
|
{
|
|
netPlayer* pPlayer = pAttachPed->GetNetworkObject()->GetPlayerOwner();
|
|
|
|
if (pPlayer)
|
|
{
|
|
scriptHandler* pScriptHandler = pScriptExtension->GetScriptHandler();
|
|
|
|
if (!m_allowNonScriptParticipantCollection
|
|
&& (!pScriptHandler ||
|
|
!pScriptHandler->GetNetworkComponent() ||
|
|
pScriptHandler->GetNetworkComponent()->GetState() != scriptHandlerNetComponent::NETSCRIPT_PLAYING ||
|
|
!pScriptHandler->GetNetworkComponent()->IsPlayerAParticipant(*pPlayer)))
|
|
{
|
|
DetachPortablePickupFromPed("Attach player not a script participant");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DetachPortablePickupFromPed("Parent script not running");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
#if __ASSERT
|
|
if (IsAlwaysFixed() && GetOwnedBy() == ENTITY_OWNEDBY_SCRIPT)
|
|
{
|
|
Assertf(IsBaseFlagSet(fwEntity::IS_FIXED), "Portable pickup %s has lost its fixed state", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "");
|
|
}
|
|
#endif // __ASSERT
|
|
|
|
// code to handle warping the pickup to the nearest accessible position on the network respawn nav mesh
|
|
if (IsFlagSet(PF_WarpToAccessibleLocation))
|
|
{
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (!IsFlagSet(PF_SearchingForAccessibleLocation))
|
|
{
|
|
if (!CNetRespawnMgr::IsSearchActive())
|
|
{
|
|
SetFlag(PF_SearchingForAccessibleLocation);
|
|
|
|
Vector3 diff = m_lastAccessibleLocation - pickupPos;
|
|
float distToLastAccessibleLocation = diff.Mag();
|
|
float radiusForSearch = distToLastAccessibleLocation * 0.7f;
|
|
|
|
if (IsFlagSet(PF_DroppedInInterior))
|
|
{
|
|
CNetRespawnMgr::StartSearch(pickupPos, CNetRespawnMgr::ms_vSearchTargetPosNone, CNetRespawnMgr::ms_fSearchMinDistDefault, radiusForSearch, CNetRespawnMgr::FLAG_MAY_SPAWN_IN_INTERIOR|CNetRespawnMgr::FLAG_MAY_SPAWN_IN_EXTERIOR|CNetRespawnMgr::FLAG_IGNORE_Z);
|
|
}
|
|
else
|
|
{
|
|
CNetRespawnMgr::StartSearch(pickupPos, CNetRespawnMgr::ms_vSearchTargetPosNone, CNetRespawnMgr::ms_fSearchMinDistDefault, radiusForSearch, CNetRespawnMgr::FLAG_MAY_SPAWN_IN_EXTERIOR|CNetRespawnMgr::FLAG_IGNORE_Z);
|
|
}
|
|
}
|
|
}
|
|
else if (CNetRespawnMgr::IsSearchComplete())
|
|
{
|
|
ClearFlag(PF_WarpToAccessibleLocation);
|
|
|
|
if (GetNetworkObject() && pLog)
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "WARPING_PORTABLE_PICKUP", GetNetworkObject()->GetLogName());
|
|
}
|
|
|
|
Expose();
|
|
|
|
if (CNetRespawnMgr::WasSearchSuccessful())
|
|
{
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Search", "successful");
|
|
}
|
|
|
|
int numResults = CNetRespawnMgr::GetNumSearchResults();
|
|
|
|
Vector3 diff = pickupPos - m_lastAccessibleLocation;
|
|
|
|
Vector3 closestPos = m_lastAccessibleLocation;
|
|
float closestDist = diff.Mag2();
|
|
bool bFoundCloserLocation = false;
|
|
|
|
for (int i=0; i<numResults; i++)
|
|
{
|
|
Vector3 searchPos;
|
|
float searchHeading;
|
|
|
|
CNetRespawnMgr::GetSearchResults(i, searchPos, searchHeading);
|
|
|
|
if(IsPositionInsidePrologueArea(searchPos) || IsPositionInsideBlockedBuildingArea(searchPos))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vector3 diff = searchPos - pickupPos;
|
|
float dist = diff.Mag2();
|
|
|
|
if (dist < closestDist)
|
|
{
|
|
closestPos = searchPos;
|
|
closestDist = dist;
|
|
bFoundCloserLocation = true;
|
|
}
|
|
}
|
|
|
|
if (pLog)
|
|
{
|
|
if (bFoundCloserLocation)
|
|
{
|
|
pLog->WriteDataValue("New pos", "%f, %f, %f", closestPos.x, closestPos.y, closestPos.z);
|
|
}
|
|
else
|
|
{
|
|
pLog->WriteDataValue("Still use last pos", "%f, %f, %f", closestPos.x, closestPos.y, closestPos.z);
|
|
}
|
|
}
|
|
|
|
closestPos.z += m_heightOffGround;
|
|
|
|
m_lastAccessibleLocation = closestPos;
|
|
|
|
FindSuitablePortablePickupDropLocation(m_lastAccessibleLocation);
|
|
}
|
|
else
|
|
{
|
|
if(pLog)
|
|
{
|
|
pLog->WriteDataValue("Search", "**unsuccessful!**");
|
|
}
|
|
|
|
MoveToAccessibleLocation(true);
|
|
}
|
|
|
|
ClearFlag(PF_Inaccessible);
|
|
|
|
ForceUpdateOfDroppedPortablePickupData();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Activate non fixed portable pickups under water when the player is nearby.
|
|
// This is a hack to fix a bug where they can sometimes be created by script and end up on the ocean floor asleep.
|
|
if (!IsAlwaysFixed() &&
|
|
!GetIsAttached() &&
|
|
GetCurrentPhysicsInst() &&
|
|
GetCurrentPhysicsInst()->IsInLevel() &&
|
|
CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()))
|
|
{
|
|
static const float ACTIVATE_RANGE = 100.0f;
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
const netPlayer* pUnused = NULL;
|
|
|
|
if (NetworkUtils::IsCloseToAnyPlayer(pickupPos, ACTIVATE_RANGE, pUnused))
|
|
{
|
|
if (IsUnderWater())
|
|
{
|
|
ActivatePhysics();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CPickup::UpdateLastAccessibleLocation(CPed& attachPed, bool bForceValid)
|
|
{
|
|
static float INACCESSIBLE_TOLERANCE_DISTANCE_SQR = 2.0f * 2.0f;
|
|
|
|
// this should never be called on clones, we must use the synced accessible location
|
|
if (IsNetworkClone())
|
|
return;
|
|
|
|
if (attachPed.GetIsDeadOrDying())
|
|
return;
|
|
|
|
Vector3 navMeshPosition;
|
|
|
|
// roads and interiors are always valid locations
|
|
bool bValidLocation = IsFlagSet(PF_LyingOnFixedObject);
|
|
|
|
if (attachPed.GetNavMeshTracker().GetIsValid())
|
|
{
|
|
navMeshPosition = attachPed.GetNavMeshTracker().GetLastNavMeshIntersection();
|
|
|
|
// reject nav mesh positions on the water (this is to fix B* 2830118, when the ped is on a yacht, which has no nav mesh, and the nav mesh on the sea is used instead)
|
|
if (navMeshPosition.z > 0.0f)
|
|
{
|
|
bValidLocation = true;
|
|
}
|
|
}
|
|
|
|
bool bInWater = IsInWater(attachPed);
|
|
|
|
CPhysical* pObject = attachPed.GetGroundPhysical() ? attachPed.GetGroundPhysical() : attachPed.GetClimbPhysical();
|
|
|
|
bool bOnFixedObject = pObject && pObject->GetIsFixedFlagSet();
|
|
bool bOnUnfixedObject = pObject && !pObject->GetIsFixedFlagSet();
|
|
bool bOnObjectWithFixedParent = false;
|
|
if(pObject && !bOnFixedObject)
|
|
{
|
|
if(Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("USE_FIXED_PARENT_CHECK_FOR_PORTABLE_PICKUP", 0x32D61595), true))
|
|
{
|
|
CPhysical* attachParent = (CPhysical*)pObject->GetAttachParent();
|
|
if(attachParent && attachParent->GetIsFixedFlagSet())
|
|
{
|
|
bOnObjectWithFixedParent = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// water, roads and interiors are always valid locations
|
|
bool bAlwaysValid = bForceValid || bInWater || (!IsFlagSet(PF_LyingOnUnFixedObject) && GetIsInInterior());
|
|
|
|
if (bValidLocation && !bAlwaysValid && attachPed.GetNavMeshTracker().GetIsValid())
|
|
{
|
|
bAlwaysValid = !IsFlagSet(PF_LyingOnUnFixedObject) && (attachPed.GetNavMeshTracker().GetNavPolyData().m_bIsRoad || attachPed.GetNavMeshTracker().GetNavPolyData().m_bInterior);
|
|
}
|
|
|
|
bool bUpdateLastAccessibleLocation = true;
|
|
|
|
aiTask* pActiveLeafTask = attachPed.GetPedIntelligence()->GetTaskManager()->GetActiveLeafTask(PED_TASK_TREE_PRIMARY);
|
|
|
|
if (pActiveLeafTask)
|
|
{
|
|
// if the attach ped is vaulting or climbing a ladder, this is always valid
|
|
switch (pActiveLeafTask->GetTaskType())
|
|
{
|
|
case CTaskTypes::TASK_VAULT:
|
|
case CTaskTypes::TASK_JUMPVAULT:
|
|
case CTaskTypes::TASK_JUMP:
|
|
if (bOnUnfixedObject)
|
|
{
|
|
// not valid if the ped is climbing a non-fixed physical
|
|
break;
|
|
}
|
|
bUpdateLastAccessibleLocation = false;
|
|
case CTaskTypes::TASK_CLIMB_LADDER:
|
|
bAlwaysValid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bAlwaysValid)
|
|
{
|
|
// if the attach ped is dropping down off a ledge, this is always valid
|
|
if (attachPed.GetPedIntelligence()->GetTaskManager()->FindTaskByTypeActive(PED_TASK_TREE_PRIMARY, CTaskTypes::TASK_DROP_DOWN))
|
|
{
|
|
bAlwaysValid = true;
|
|
}
|
|
}
|
|
|
|
bool bValidCarryingState = true;
|
|
|
|
if (!bAlwaysValid)
|
|
{
|
|
// check whether the vehicle the ped is in has left the ground
|
|
if (attachPed.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
|
|
{
|
|
CVehicle* pVehicle = attachPed.GetMyVehicle();
|
|
|
|
if (pVehicle && pVehicle->HasContactWheels() == false)
|
|
{
|
|
bValidCarryingState = false;
|
|
}
|
|
|
|
bInWater = pVehicle->GetIsInWater();
|
|
}
|
|
else if (attachPed.GetUsingRagdoll() )
|
|
{
|
|
// the ped's position isn't valid if he is ragdolling (he may be falling down a hill, etc)
|
|
bValidCarryingState = false;
|
|
}
|
|
else if (!attachPed.GetIsStanding() || !attachPed.IsOnGround() || attachPed.GetGroundPos().z < 0.0f)
|
|
{
|
|
// the ped's position isn't valid if he isn't standing/walking/running on the ground
|
|
bValidCarryingState = false;
|
|
}
|
|
else if (bOnUnfixedObject && !bOnObjectWithFixedParent)
|
|
{
|
|
// the ped's position isn't valid if he is standing / climbing on a non-fixed object
|
|
bValidCarryingState = false;
|
|
}
|
|
else if (attachPed.IsAPlayerPed())
|
|
{
|
|
// players scrambling up or down a slope are not allowed to drop a package as it may be inaccessible
|
|
CTaskPlayerOnFoot* pTaskPlayer = static_cast<CTaskPlayerOnFoot*>(attachPed.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_ON_FOOT));
|
|
if(pTaskPlayer && pTaskPlayer->IsSlopeScrambling())
|
|
{
|
|
bValidCarryingState = false;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < SIZE_OF_ALL_YACHT_LOCATIONS; i++)
|
|
{
|
|
if((ALL_YACHT_LOCATIONS[i] - VEC3V_TO_VECTOR3(attachPed.GetTransform().GetPosition())).XYMag2() < (ACCESSIBLE_DISTANCE_FROM_YACHT*ACCESSIBLE_DISTANCE_FROM_YACHT))
|
|
{
|
|
bAlwaysValid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_Inaccessible))
|
|
{
|
|
if (bAlwaysValid)
|
|
{
|
|
ClearFlag(PF_Inaccessible);
|
|
}
|
|
else if (bValidLocation && bValidCarryingState)
|
|
{
|
|
// players can get themselves into inaccessible locations, so we need additional checks here
|
|
if (attachPed.IsPlayer() && attachPed.GetNavMeshTracker().GetIsValid())
|
|
{
|
|
// if the player has not moved too far from his last accessible location, treat this as accessible, this is to stop the
|
|
// accessibility code from temporarily switching to false and not going back to true again (eg when dropping off a small ledge or crossing areas
|
|
// where the navmesh tracker temporarily returns an invalid location)
|
|
Vector3 diff = navMeshPosition - m_lastAccessibleLocation;
|
|
|
|
if (diff.Mag2() < INACCESSIBLE_TOLERANCE_DISTANCE_SQR)
|
|
{
|
|
ClearFlag(PF_Inaccessible);
|
|
}
|
|
|
|
// Use the network respawn nav mesh to determine whether we are back in a reachable location.
|
|
if (attachPed.GetNavMeshTracker().GetNavPolyData().m_bNetworkSpawn)
|
|
{
|
|
ClearFlag(PF_Inaccessible);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClearFlag(PF_Inaccessible);
|
|
}
|
|
}
|
|
}
|
|
else if (!bValidCarryingState)
|
|
{
|
|
SetFlag(PF_Inaccessible);
|
|
}
|
|
|
|
if (!IsFlagSet(PF_Inaccessible) && bUpdateLastAccessibleLocation)
|
|
{
|
|
Vector3 newPosition = VEC3V_TO_VECTOR3(attachPed.GetTransform().GetPosition());
|
|
|
|
ClearFlag(PF_LastAccessiblePosHasValidGround);
|
|
|
|
if (!bInWater)
|
|
{
|
|
const float groundToRootOffset = attachPed.GetCapsuleInfo() ? attachPed.GetCapsuleInfo()->GetGroundToRootOffset() : 1.0f;
|
|
|
|
if (bValidLocation)
|
|
{
|
|
if (IsFlagSet(PF_LyingOnFixedObject))
|
|
{
|
|
newPosition.z -= groundToRootOffset; // take off ped's pelvis height
|
|
}
|
|
else
|
|
{
|
|
newPosition = navMeshPosition;
|
|
}
|
|
|
|
newPosition.z += m_heightOffGround; // add on z adjustment
|
|
|
|
SetFlag(PF_LastAccessiblePosHasValidGround);
|
|
}
|
|
else if (!attachPed.GetUsingRagdoll())
|
|
{
|
|
if (attachPed.GetGroundPos().z > PED_GROUNDPOS_RESET_Z)
|
|
{
|
|
newPosition = attachPed.GetGroundPos();
|
|
}
|
|
else
|
|
{
|
|
newPosition.z -= groundToRootOffset; // take off ped's pelvis height
|
|
}
|
|
|
|
newPosition.z += m_heightOffGround; // add on z adjustment
|
|
}
|
|
}
|
|
|
|
if (!attachPed.GetIsDeadOrDying())
|
|
{
|
|
ClearFlag(PF_LyingOnFixedObject);
|
|
ClearFlag(PF_LyingOnUnFixedObject);
|
|
|
|
// the ground physical gets cleared on death, so we need to cache it here
|
|
if (bOnFixedObject || bOnObjectWithFixedParent)
|
|
SetFlag(PF_LyingOnFixedObject);
|
|
else if (bOnUnfixedObject)
|
|
SetFlag(PF_LyingOnUnFixedObject);
|
|
}
|
|
|
|
// keep a track of the last accessible position the ped was at
|
|
m_lastAccessibleLocation = newPosition;
|
|
}
|
|
}
|
|
|
|
void CPickup::FindSuitablePortablePickupDropLocation(Vector3& initialPos)
|
|
{
|
|
CPickupManager::CPickupPositionInfo dropInfo(initialPos);
|
|
dropInfo.m_MinDist = 1.5f;
|
|
dropInfo.m_bOnGround = true;
|
|
dropInfo.m_pickupHeightOffGround = m_heightOffGround;
|
|
|
|
bool bDroppedInWater = false;
|
|
|
|
if (IsFlagSet(PF_LyingOnFixedObject))
|
|
{
|
|
// we can't call CalculateDroppedPickupPosition as it only probes for the ground
|
|
dropInfo.m_Pos = initialPos;
|
|
}
|
|
else
|
|
{
|
|
float waterZ = 0.0f;
|
|
|
|
if (CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(initialPos, &waterZ, false) && waterZ > initialPos.z)
|
|
{
|
|
dropInfo.m_Pos = initialPos;
|
|
dropInfo.m_Pos.z = waterZ;
|
|
DropInWater(false);
|
|
bDroppedInWater = true;
|
|
}
|
|
else if (!CPickupManager::CalculateDroppedPickupPosition(dropInfo, true))
|
|
{
|
|
// expand the drop radius if we can't find a valid position
|
|
dropInfo.m_MinDist = 4.0f;
|
|
CPickupManager::CalculateDroppedPickupPosition(dropInfo, true);
|
|
}
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (GetNetworkLog())
|
|
{
|
|
GetNetworkLog()->WriteDataValue("Dropped at", "%f, %f, %f", dropInfo.m_Pos.x, dropInfo.m_Pos.y, dropInfo.m_Pos.z);
|
|
}
|
|
#endif
|
|
|
|
Teleport(dropInfo.m_Pos);
|
|
|
|
if (!bDroppedInWater)
|
|
{
|
|
if (g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(GetTransform().GetPosition(), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER))
|
|
{
|
|
PlaceOnGroundProperly(3.0f, true, m_heightOffGround, GetPlacement()==NULL, IsFlagSet(PF_UprightOnGround));
|
|
}
|
|
else
|
|
{
|
|
SetPlaceOnGround(true, IsFlagSet(PF_UprightOnGround));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::ActivatePhysicsOnPortablePickup()
|
|
{
|
|
if (GetCurrentPhysicsInst())
|
|
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false);
|
|
|
|
m_nObjectFlags.bActivatePhysicsAsSoonAsUnfrozen = true;
|
|
|
|
CObject::SetFixedPhysics(false, false);
|
|
|
|
AddPhysics();
|
|
ActivatePhysics();
|
|
}
|
|
|
|
bool CPickup::IsInWater(CPed& ped) const
|
|
{
|
|
bool bInWater = ped.GetIsInWater();
|
|
|
|
CVehicle* pPedVehicle = ped.GetIsInVehicle() ? ped.GetMyVehicle() : NULL;
|
|
|
|
if (pPedVehicle)
|
|
{
|
|
if (pPedVehicle->GetIsInWater() || pPedVehicle->GetWasInWater() || pPedVehicle->m_nVehicleFlags.bIsDrowning)
|
|
{
|
|
bInWater = true;
|
|
}
|
|
}
|
|
|
|
return bInWater;
|
|
}
|
|
|
|
void CPickup::DropInWater(bool bFindWaterLevel)
|
|
{
|
|
ActivatePhysicsOnPortablePickup();
|
|
|
|
SetDroppedInWater();
|
|
|
|
m_nObjectFlags.bFloater = true;
|
|
|
|
if (bFindWaterLevel)
|
|
{
|
|
if (!IsFlagSet(PF_UnderwaterPickup))
|
|
{
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
float waterLevel = 0.0f;
|
|
if (CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(pickupPos, &waterLevel, false))
|
|
{
|
|
// Probe upwards to see if there is anything in our way between the pickup's current
|
|
// position and the surface. If there is, we just detach.
|
|
WorldProbe::CShapeTestProbeDesc probeDesc;
|
|
probeDesc.SetStartAndEnd(pickupPos, Vector3(pickupPos.x, pickupPos.y, waterLevel));
|
|
probeDesc.SetIsDirected(true);
|
|
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
|
|
if(!WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
|
|
{
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (GetNetworkLog())
|
|
{
|
|
GetNetworkLog()->WriteDataValue("Dropped at water surface", "true");
|
|
}
|
|
#endif
|
|
pickupPos.z = waterLevel;
|
|
SetPosition(pickupPos);
|
|
}
|
|
else
|
|
{
|
|
ClearFlag(PF_PlaceOnGround);
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (GetNetworkLog())
|
|
{
|
|
GetNetworkLog()->WriteDataValue("Dropped under water", "true");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if ENABLE_NETWORK_LOGGING
|
|
if (GetNetworkLog())
|
|
{
|
|
GetNetworkLog()->WriteDataValue("Dropped under water (underwater pickup)", "true");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::SetLocalPendingCollection(CPed& pendingCollector, eCollectionType collectionType)
|
|
{
|
|
m_pendingCollector = &pendingCollector;
|
|
m_pendingCollectionType = collectionType;
|
|
|
|
// hide the pickup when pending collection (ie. pretend it is collected because it is about to be). This is done so that the pickup disappears
|
|
// immediately when someone is trying to collect it).
|
|
Hide();
|
|
|
|
for (u32 i=0; i<ms_numPickupsPendingLocalCollection; i++)
|
|
{
|
|
if (ms_pickupsPendingLocalCollection[i] == this)
|
|
{
|
|
Assertf(0, "Pickup %s already on pending collection list", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (AssertVerify(ms_numPickupsPendingLocalCollection<MAX_PENDING_COLLECTIONS))
|
|
{
|
|
ms_pickupsPendingLocalCollection[ms_numPickupsPendingLocalCollection++] = this;
|
|
}
|
|
}
|
|
|
|
void CPickup::ClearPendingCollection(bool bExpose)
|
|
{
|
|
m_pendingCollector = NULL;
|
|
m_pendingCollectionType = COLLECT_INVALID;
|
|
m_pendingCollectionTimer = 0;
|
|
|
|
if (bExpose && !GetIsAttached())
|
|
{
|
|
Expose();
|
|
}
|
|
|
|
for (u32 i=0; i<ms_numPickupsPendingLocalCollection; i++)
|
|
{
|
|
if (ms_pickupsPendingLocalCollection[i] == this)
|
|
{
|
|
for (u32 j=i; j<ms_numPickupsPendingLocalCollection; j++)
|
|
{
|
|
ms_pickupsPendingLocalCollection[j] = ms_pickupsPendingLocalCollection[j+1];
|
|
}
|
|
|
|
ms_numPickupsPendingLocalCollection--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Name : Hide
|
|
// Purpose : Hides the pickup (makes it uncollideable and invisible)
|
|
void CPickup::Hide(bool bMakeUncollideable)
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP, false, true);
|
|
|
|
if (bMakeUncollideable)
|
|
{
|
|
SetFlag(PF_DisabledCollision);
|
|
DisableCollision(NULL, true);
|
|
}
|
|
|
|
if (!GetIsAttached() && GetNetworkObject() && !GetNetworkObject()->IsClone() && !GetNetworkObject()->IsPendingOwnerChange() && !m_pendingCollector)
|
|
{
|
|
static_cast<CNetObjPickup*>(GetNetworkObject())->SetOverridingRemoteVisibility(true, false, "Hiding pickup");
|
|
}
|
|
}
|
|
|
|
// Name : Expose
|
|
// Purpose : Exposes the pickup again, after Hide() has been called
|
|
void CPickup::Expose()
|
|
{
|
|
if (!IsFlagSet(PF_HideWhenDetached))
|
|
{
|
|
if (Verifyf(!GetIsAttached(), "%s is being exposed while attached!", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Pickup"))
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP, true, true);
|
|
|
|
if (IsFlagSet(PF_DisabledCollision))
|
|
{
|
|
EnableCollision();
|
|
ClearFlag(PF_DisabledCollision);
|
|
}
|
|
|
|
if (GetNetworkObject() && !GetNetworkObject()->IsClone() && !GetNetworkObject()->IsPendingOwnerChange() && static_cast<CNetObjPickup*>(GetNetworkObject())->GetOverridingRemoteVisibility())
|
|
{
|
|
static_cast<CNetObjPickup*>(GetNetworkObject())->SetOverridingRemoteVisibility(false, true, "Hiding pickup");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::ForceUpdateOfDroppedPortablePickupData()
|
|
{
|
|
if (GetNetworkObject() && !IsNetworkClone())
|
|
{
|
|
CPickupSyncTree* pPickupSyncTree = SafeCast(CPickupSyncTree, GetNetworkObject()->GetSyncTree());
|
|
|
|
if (pPickupSyncTree)
|
|
{
|
|
pPickupSyncTree->DirtyNode(GetNetworkObject(), *pPickupSyncTree->GetSectorNode());
|
|
pPickupSyncTree->DirtyNode(GetNetworkObject(), *pPickupSyncTree->GetPickupSectorPosNode());
|
|
|
|
// forcibly send out an update with the attachment state and new position together, otherwise the position may be sent before the attachment
|
|
// state and not get applied
|
|
GetNetworkObject()->GetSyncTree()->Update(GetNetworkObject(), GetNetworkObject()->GetActivationFlags(), netInterface::GetSynchronisationTime());
|
|
GetNetworkObject()->GetSyncTree()->ForceSendOfSyncUpdateNodes(SERIALISEMODE_FORCE_SEND_OF_DIRTY, GetNetworkObject()->GetActivationFlags(), GetNetworkObject());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks if pickup is networked local has been dropped from plane and sets the matrix to identity to ensure it isnt left with plane orientation
|
|
// and checks for best position with respect to nearness to buildings and ground and reposition the pickup if necessary
|
|
void CPickup::NetworkSetPositionOfPlaneDroppedPortablePickup(const CPed* pPed, const CVehicle* pVehicle)
|
|
{
|
|
if (GetNetworkObject() && !IsNetworkClone() && GetPickupData()->GetIsAirbornePickup())
|
|
{
|
|
static float CAPSULE_PROBE_RADIUS = 15.0f; //same as distance MIN_DIST_FROM_GROUND for now
|
|
|
|
float fTestRadius = CAPSULE_PROBE_RADIUS;
|
|
|
|
//Test for space that should allow room for this plane to get into
|
|
if(pVehicle && pVehicle->GetBaseModelInfo())
|
|
{
|
|
fTestRadius = 2.0f * pVehicle->GetBaseModelInfo()->GetBoundingSphereRadius();
|
|
}
|
|
|
|
Vector3 vDropPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
Vector3 vStartProbePos = vDropPos;
|
|
vStartProbePos.z+=MAX_BUILDING_CLIFF_HEIGHT; //raise probe up about height of highest building in city
|
|
|
|
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
|
|
WorldProbe::CShapeTestHitPoint probeIsect;
|
|
WorldProbe::CShapeTestResults probeResult(probeIsect);
|
|
capsuleDesc.SetCapsule(vStartProbePos, vDropPos, fTestRadius);
|
|
capsuleDesc.SetResultsStructure(&probeResult);
|
|
capsuleDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
|
|
capsuleDesc.SetIsDirected(true);
|
|
capsuleDesc.SetDoInitialSphereCheck(true);
|
|
|
|
static const s32 MAX_NUM_EXCEPTIONS = 2;
|
|
s32 iNumExceptions =0;
|
|
const CEntity* ppExceptions[MAX_NUM_EXCEPTIONS] = { NULL };
|
|
|
|
if(pPed)
|
|
{
|
|
ppExceptions[0] = pPed;
|
|
iNumExceptions++;
|
|
}
|
|
if(pVehicle)
|
|
{
|
|
ppExceptions[1] = pVehicle;
|
|
iNumExceptions++;
|
|
}
|
|
|
|
if(iNumExceptions >0)
|
|
{
|
|
capsuleDesc.SetExcludeEntities(ppExceptions,iNumExceptions);
|
|
}
|
|
|
|
capsuleDesc.SetContext(WorldProbe::LOS_Unspecified);
|
|
|
|
if(WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc))
|
|
{
|
|
Vector3 vHitPos = VEC3V_TO_VECTOR3(probeResult[0].GetHitPositionV());
|
|
vDropPos.z = vHitPos.z + CAPSULE_PROBE_RADIUS; //lift the pick up above the collision pos
|
|
|
|
Assertf(vDropPos.z > WORLDLIMITS_ZMIN && vDropPos.z < WORLDLIMITS_ZMAX, "%s plane pickup being placed out of WORLDLIMITS_Z range %.2f", GetDebugName(), vDropPos.z );
|
|
}
|
|
|
|
//Make sure pick up orientates straight e.g. not adopting crashing planes last orientation
|
|
Matrix34 newMat;
|
|
newMat.Identity();
|
|
newMat.d.Set(vDropPos);
|
|
SetMatrix(newMat);
|
|
}
|
|
}
|
|
|
|
bool CPickup::IsUnderWater() const
|
|
{
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
bool bBelowWater = pickupPos.z < 0.0f;
|
|
|
|
if (!bBelowWater)
|
|
{
|
|
float waterZ = 0.0f;
|
|
|
|
if (CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(pickupPos, &waterZ, false))
|
|
{
|
|
bBelowWater = pickupPos.z < waterZ;
|
|
}
|
|
}
|
|
|
|
return bBelowWater;
|
|
}
|
|
|
|
bool CPickup::IsPositionInsidePrologueArea(Vector3& pos)
|
|
{
|
|
// TODO_UI: This may cause issues with Island X.
|
|
// Check this once we've had some time to test things on the island and update
|
|
// or remove based on what's required.
|
|
const float MAP_LIMIT_WEST_PROLOGUE = 1935.0f;
|
|
const float MAP_LIMIT_EAST_PROLOGUE = 7850.0f;
|
|
const float MAP_LIMIT_NORTH_PROLOGUE = -3561.0f;
|
|
const float MAP_LIMIT_SOUTH_PROLOGUE = -5295.0f;
|
|
|
|
if(pos.x > MAP_LIMIT_WEST_PROLOGUE
|
|
&& pos.x < MAP_LIMIT_EAST_PROLOGUE
|
|
&& pos.y > MAP_LIMIT_NORTH_PROLOGUE
|
|
&& pos.y < MAP_LIMIT_SOUTH_PROLOGUE
|
|
&& pos.z > 100.0f)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPickup::IsPositionInsideBlockedBuildingArea(Vector3 pos)
|
|
{
|
|
const float POS_Z_BOTTOM = 214.0f;
|
|
const float POS_Z_TOP = 252.0f;
|
|
const float CORNER_Y_ONE = -926.0f;
|
|
const float CORNER_Y_TWO = -1030.0f;
|
|
const float CORNER_X_ONE = -119.0;
|
|
const float CORNER_X_TWO = -200.0f;
|
|
|
|
|
|
if(pos.z < POS_Z_BOTTOM || pos.z > POS_Z_TOP)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(pos.y < CORNER_Y_ONE
|
|
&& pos.y > CORNER_Y_TWO
|
|
&& pos.x < CORNER_X_ONE
|
|
&& pos.x > CORNER_X_TWO)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CPickup::AddExtendedProbeArea(Vector3 pos, float radius)
|
|
{
|
|
if(gnetVerifyf(m_ExtendedProbeCount < MAX_NUM_EXTENDED_PROBE_AREAS, "Extended probe area array is full"))
|
|
{
|
|
gnetDebug1("ADDING_EXTENDED_PROBE_AREA - %f, %f, %f with Radius Sqr: %.f", pos.GetX(), pos.GetY(), pos.GetZ(), radius * radius);
|
|
ExtendedProbeAreas newArea(pos, radius);
|
|
m_AllExtendedProbeAreas[m_ExtendedProbeCount] = newArea;
|
|
m_ExtendedProbeCount++;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void CPickup::ClearExtendedProbeAreas()
|
|
{
|
|
m_ExtendedProbeCount = 0;
|
|
|
|
for(int i = 0; i < MAX_NUM_EXTENDED_PROBE_AREAS; i++)
|
|
{
|
|
m_AllExtendedProbeAreas[i].Reset();
|
|
}
|
|
}
|
|
|
|
// Name : CanCollect
|
|
// Purpose : Returns true if the given ped is allowed to collect the pickup
|
|
// Parameters : pPed - the ped trying to collect the pickup
|
|
// collectionType - how the pickup is being collected
|
|
// Returns : True if the given ped is allowed to collect the pickup
|
|
bool CPickup::CanCollect(const CPed* pPed, eCollectionType collectionType, unsigned *failureCode)
|
|
{
|
|
// do collection checks for both local and remote peds
|
|
if (!CanCollectCritical(pPed, collectionType, failureCode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pPed->IsNetworkClone())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool bCanCollect = false;
|
|
|
|
CVehicle *pPedVehicle = pPed->GetVehiclePedInside();
|
|
|
|
// pickup type: PICKUP_PORTABLE_CRATE_UNFIXED_INAIRVEHICLE_WITH_PASSENGERS can be collected from 4 meters away while in a plane
|
|
// on foor this pickup distance should still be 1.5m. Fail if too far
|
|
if(GetPickupData()->GetHash() == PICKUP_PORTABLE_CRATE_UNFIXED_INAIRVEHICLE_WITH_PASSENGERS)
|
|
{
|
|
if(!pPedVehicle || !(pPedVehicle->InheritsFromHeli() || pPedVehicle->InheritsFromPlane()) || !pPedVehicle->IsInDriveableState())
|
|
{
|
|
static const f32 ON_FOOT_COLLECTION_DIST_SQR = (1.5f * 1.5f);
|
|
f32 distSquare = VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()).Dist2(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()));
|
|
if(distSquare > ON_FOOT_COLLECTION_DIST_SQR)
|
|
{
|
|
if (failureCode) *failureCode = PCC_FAR_FOR_ON_FOOT_COLLECTION;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_DroppedFromLocalPlayer) && pPed->IsLocalPlayer())
|
|
{
|
|
if (failureCode) *failureCode = PCC_DROPPED_FROM_LOCAL_PLAYER;
|
|
return false;
|
|
}
|
|
|
|
if (collectionType == COLLECT_ONFOOT)
|
|
{
|
|
if (!GetPickupData()->GetCollectableOnFoot())
|
|
{
|
|
if (failureCode) *failureCode = PCC_COLLECTABLE_IN_VEHICLE;
|
|
return false;
|
|
}
|
|
|
|
if (pPedVehicle)
|
|
{
|
|
if (failureCode) *failureCode = PCC_COLLECTABLE_ON_FOOT;
|
|
return false;
|
|
}
|
|
}
|
|
else if (collectionType == COLLECT_INCAR)
|
|
{
|
|
if (!pPedVehicle)
|
|
{
|
|
if (failureCode) *failureCode = PCC_COLLECTABLE_IN_VEHICLE;
|
|
return false;
|
|
}
|
|
else if (!GetPickupData()->GetCollectableInCarByPassengers() && pPedVehicle->GetDriver() != pPed)
|
|
{
|
|
if (failureCode) *failureCode = PCC_NOT_DRIVER;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
bool bCanCollect = false;
|
|
|
|
switch (pPedVehicle->GetVehicleType())
|
|
{
|
|
case VEHICLE_TYPE_CAR:
|
|
case VEHICLE_TYPE_QUADBIKE:
|
|
case VEHICLE_TYPE_BIKE:
|
|
case VEHICLE_TYPE_BICYCLE:
|
|
case VEHICLE_TYPE_TRAIN:
|
|
case VEHICLE_TYPE_TRAILER:
|
|
case VEHICLE_TYPE_DRAFT:
|
|
bCanCollect = GetAllowCollectionInVehicle();
|
|
break;
|
|
case VEHICLE_TYPE_PLANE:
|
|
case VEHICLE_TYPE_HELI:
|
|
case VEHICLE_TYPE_AUTOGYRO:
|
|
case VEHICLE_TYPE_BLIMP:
|
|
bCanCollect = GetPickupData()->GetCollectableInPlane();
|
|
break;
|
|
case VEHICLE_TYPE_BOAT:
|
|
case VEHICLE_TYPE_SUBMARINE:
|
|
bCanCollect = GetPickupData()->GetCollectableInBoat();
|
|
break;
|
|
case VEHICLE_TYPE_SUBMARINECAR:
|
|
{
|
|
CSubmarineCar* submarineCar = static_cast< CSubmarineCar* >( pPedVehicle );
|
|
|
|
if( submarineCar->IsInSubmarineMode() )
|
|
{
|
|
bCanCollect = GetPickupData()->GetCollectableInBoat();
|
|
}
|
|
else
|
|
{
|
|
bCanCollect = GetPickupData()->GetCollectableInCar();
|
|
}
|
|
break;
|
|
}
|
|
case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE:
|
|
case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE:
|
|
{
|
|
bCanCollect = GetPickupData()->GetCollectableInCar();
|
|
}
|
|
break;
|
|
default:
|
|
Assertf(0, "CPickup::CanCollect: Unrecognised vehicle type");
|
|
}
|
|
|
|
if (!bCanCollect)
|
|
{
|
|
if (failureCode) *failureCode = PCC_WRONG_VEHICLE;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ignore certain checks when doing a team transfer - ie detaching the pickup from one dead player in a vehicle and attaching it to another passenger.
|
|
// The pickup is still attached to the other player at this point.
|
|
if (collectionType != COLLECT_TEAM_TRANSFER)
|
|
{
|
|
// pickups in a different interior cannot be collected
|
|
if (GetInteriorLocation().GetAsUint32() != pPed->GetInteriorLocation().GetAsUint32())
|
|
{
|
|
if (failureCode) *failureCode = PCC_DIFFERENT_INTERIORS;
|
|
return false;
|
|
}
|
|
|
|
// don't allow collection after ped gets on a vehicle
|
|
if (pPed->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_ENTER_VEHICLE) && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle))
|
|
{
|
|
if (failureCode) *failureCode = PCC_PED_ENTERING_VEHICLE;
|
|
return false;
|
|
}
|
|
|
|
// hidden pickups cannot be collected
|
|
if (!GetIsVisibleForModule(SETISVISIBLE_MODULE_PICKUP))
|
|
{
|
|
if (failureCode) *failureCode = PCC_HIDDEN;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// can't collect faded out ambient pickups
|
|
if (!GetPlacement() && m_lifeTime > AMBIENT_PICKUP_LIFETIME)
|
|
{
|
|
if (failureCode) *failureCode = PCC_FADED_OUT_AMBIENT;
|
|
return false;
|
|
}
|
|
|
|
// Don't allow collection during stealth kills / takedowns
|
|
CTaskMeleeActionResult* pMeleeActionTask = pPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
|
|
|
if (pMeleeActionTask)
|
|
{
|
|
const CActionResult* pActionResult = pMeleeActionTask ? pMeleeActionTask->GetActionResult() : NULL;
|
|
|
|
if(pActionResult && (pActionResult->GetIsAStealthKill() || pActionResult->GetIsATakedown()))
|
|
{
|
|
if (failureCode) *failureCode = PCC_DOING_STEALTH_KILL;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (IsFlagSet(PF_Portable) && pPed->IsLocalPlayer())
|
|
{
|
|
CPlayerInfo* pPlayerInfo = pPed->GetPlayerInfo();
|
|
|
|
// don't allow collection if we have another pending
|
|
if (pPlayerInfo->PortablePickupPending)
|
|
{
|
|
if (GetNetworkObject())
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("** CAN'T COLLECT %s : PortablePickupPending set to true!\n", GetNetworkObject()->GetLogName());
|
|
}
|
|
if (failureCode) *failureCode = PCC_PORTABLE_PICKUP_PENDING;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(IsFlagSet(PF_RequiresLineOfSight) && pPed->IsLocalPlayer())
|
|
{
|
|
if(!pPed->OurPedCanSeeThisEntity(this))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
u32 uNewWeaponHash = GetPickupData()->GetFirstWeaponReward();
|
|
|
|
// a weapon is being picked up
|
|
if (uNewWeaponHash != 0)
|
|
{
|
|
if (pPed->IsLocalPlayer() && (collectionType == COLLECT_ONFOOT|| (collectionType == COLLECT_INCAR && IsFlagSet(PF_AllowCollectionInVehicle))) && GetPickupData()->GetManualPickUp())
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uNewWeaponHash);
|
|
if (pWeaponInfo && pPed->GetWeaponManager() && pPed->GetInventory())
|
|
{
|
|
// If we're picking up the same weapon as we're using, don't set the last picked up weapon
|
|
if (pPed->GetWeaponManager()->GetEquippedWeaponHash() != uNewWeaponHash)
|
|
{
|
|
// If the weapon is different from one we already have a weapon of that type in the slot, only add it if the player actively presses for it
|
|
// or we are flagged to do so
|
|
const CWeaponItem* pWeaponItem = pPed->GetInventory()->GetWeaponBySlot(pWeaponInfo->GetSlot());
|
|
if ((pWeaponItem && pWeaponItem->GetInfo()->GetHash() != pWeaponInfo->GetHash()) || (m_pPickupData->GetRequiresButtonPressToPickup() && !m_pPickupData->GetAutoCollectIfInInventory()))
|
|
{
|
|
const CControl* pControl = pPed->GetControlFromPlayer();
|
|
|
|
// Can't pick it up whilst on the phone.
|
|
if(pPed->GetPedIntelligence()->FindTaskActiveByTreeAndType(PED_TASK_TREE_SECONDARY, CTaskTypes::TASK_MOBILE_PHONE) != NULL)
|
|
{
|
|
if (failureCode) *failureCode = PCC_ON_THE_PHONE;
|
|
return false;
|
|
}
|
|
else if(GetPickupHash()==PICKUP_JETPACK)
|
|
{
|
|
if(!pControl->GetPedEnter().IsPressed())
|
|
{
|
|
if (failureCode) *failureCode = PCC_BUTTON_NOT_PRESSED;
|
|
return false;
|
|
}
|
|
}
|
|
else if (!pControl->GetPedCollectPickup().IsPressed())
|
|
{
|
|
if (failureCode) *failureCode = PCC_BUTTON_NOT_PRESSED;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AssertVerify(pPed) &&
|
|
AssertVerify(m_pPickupData))
|
|
{
|
|
if (m_pPickupData->GetNumRewards() > 0 &&
|
|
!(ms_shareVehicleWeaponPickupsAmongstPassengers && m_pPickupData->GetShareWithPassengers())) // we may not be able to collect the rewards but our passengers might]
|
|
{
|
|
// allow collection if any of the rewards can be given
|
|
for (u32 i=0; i<m_pPickupData->GetNumRewards(); i++)
|
|
{
|
|
const CPickupRewardData* pReward = m_pPickupData->GetReward(i);
|
|
|
|
if (AssertVerify(pReward))
|
|
{
|
|
if (pReward->CanGive(this, pPed))
|
|
{
|
|
bCanCollect = true;
|
|
}
|
|
else if (pReward->PreventCollectionIfCantGive(this, pPed))
|
|
{
|
|
bCanCollect = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bCanCollect = true;
|
|
}
|
|
}
|
|
|
|
bool bEventSent = false;
|
|
|
|
if (!bCanCollect)
|
|
{
|
|
if (failureCode) *failureCode = PCC_REWARDS_CANT_BE_GIVEN;
|
|
return false;
|
|
}
|
|
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
// if the pickup is owned by another machine, we must request collection of it. It will be collected later if the request is granted
|
|
if (GetNetworkObject() && (GetNetworkObject()->IsClone() || GetNetworkObject()->IsPendingOwnerChange()))
|
|
{
|
|
if (ms_numPickupsPendingLocalCollection == MAX_PENDING_COLLECTIONS)
|
|
{
|
|
if (failureCode) *failureCode = PCC_PENDING_COLLECTIONS_EXCEEDED;
|
|
return false;
|
|
}
|
|
|
|
if (IsFlagSet(PF_Portable) && pPed->GetPlayerInfo())
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("PortablePickupPending set to true: CanCollect\n");
|
|
Assert(!pPed->GetPlayerInfo()->PortablePickupPending);
|
|
pPed->GetPlayerInfo()->PortablePickupPending = true;
|
|
}
|
|
|
|
SetLocalPendingCollection(*const_cast<CPed*>(pPed), collectionType);
|
|
CRequestPickupEvent::Trigger(pPed, this);
|
|
bEventSent = true;
|
|
}
|
|
else if (GetPlacement() && GetPlacement()->GetIsMapPlacement())
|
|
{
|
|
if (!GetPlacement()->GetLocalOnly())
|
|
{
|
|
SetLocalPendingCollection(*const_cast<CPed*>(pPed), collectionType);
|
|
CRequestMapPickupEvent::Trigger(GetPlacement());
|
|
bEventSent = true;
|
|
}
|
|
}
|
|
else if (GetPlacement() && GetPlacement()->GetNetworkObject() && (GetPlacement()->GetNetworkObject()->IsClone() || GetPlacement()->GetNetworkObject()->IsPendingOwnerChange()))
|
|
{
|
|
if (ms_numPickupsPendingLocalCollection == MAX_PENDING_COLLECTIONS)
|
|
{
|
|
if (failureCode) *failureCode = PCC_PENDING_COLLECTIONS_EXCEEDED;
|
|
return false;
|
|
}
|
|
|
|
SetLocalPendingCollection(*const_cast<CPed*>(pPed), collectionType);
|
|
CRequestPickupEvent::Trigger(pPed, GetPlacement());
|
|
bEventSent = true;
|
|
}
|
|
|
|
if (bEventSent)
|
|
{
|
|
if (failureCode) *failureCode = PCC_CLONE_REQUEST_SENT;
|
|
bCanCollect = false;
|
|
}
|
|
}
|
|
|
|
return bCanCollect;
|
|
}
|
|
|
|
// Name : CollectOnFoot
|
|
// Purpose : Called when the pickup is collected on foot. Triggers the on foot collection actions and gives the pickup rewards to the ped.
|
|
// Parameters : pPed - the ped collecting the pickup
|
|
// Returns : None
|
|
void CPickup::CollectOnFoot(CPed* pPed)
|
|
{
|
|
if (AssertVerify(pPed) &&
|
|
AssertVerify(m_pPickupData) &&
|
|
AssertVerify(m_pPickupData->GetCollectableOnFoot()))
|
|
{
|
|
if (m_pPickupData->GetManualPickUp() && pPed->IsLocalPlayer())
|
|
{
|
|
u32 uNewWeaponHash = m_pPickupData->GetFirstWeaponReward();
|
|
if (uNewWeaponHash !=0)
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uNewWeaponHash);
|
|
if (pWeaponInfo)
|
|
{
|
|
bool bSetLastPickedUpWeapon = true;
|
|
|
|
// If we're picking up the same weapon as we're using, don't set the last picked up weapon
|
|
if (pPed->GetWeaponManager()->GetEquippedWeaponHash() == uNewWeaponHash)
|
|
{
|
|
bSetLastPickedUpWeapon = false;
|
|
}
|
|
else
|
|
{
|
|
// If the weapon is different from one we already have a weapon of that type in the slot, only add it if the player actively presses for it
|
|
// or we are flagged to do so
|
|
const CWeaponItem* pWeaponItem = pPed->GetInventory()->GetWeaponBySlot(pWeaponInfo->GetSlot());
|
|
if ((pWeaponItem && pWeaponItem->GetInfo()->GetHash() != pWeaponInfo->GetHash()) || m_pPickupData->GetRequiresButtonPressToPickup())
|
|
{
|
|
// If we're switching for a weapon in the same slot we've got equipped we will auto equip, so
|
|
// don't set the quick switch help text
|
|
if (pPed->GetWeaponManager()->GetEquippedWeaponSlot() == pWeaponInfo->GetSlot())
|
|
{
|
|
bSetLastPickedUpWeapon = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the reward and record it, this allows us to switch to the
|
|
// last picked up weapon if we picked it up within the past X seconds
|
|
if (bSetLastPickedUpWeapon)
|
|
{
|
|
pPed->GetPlayerInfo()->SetLastWeaponHashPickedUp(uNewWeaponHash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 numOnFootActions = m_pPickupData->GetNumOnFootActions();
|
|
|
|
for (u32 i=0; i<numOnFootActions; i++)
|
|
{
|
|
const CPickupActionData* pAction = m_pPickupData->GetOnFootAction(i);
|
|
|
|
if (AssertVerify(pAction))
|
|
{
|
|
pAction->Apply(this, pPed);
|
|
}
|
|
}
|
|
|
|
scriptDebugf3(">>> Pickup of type %d collected on foot <<<\n", GetPickupHash());
|
|
|
|
GiveRewards(pPed);
|
|
Collect(pPed);
|
|
}
|
|
}
|
|
|
|
// Name : CollectInCar
|
|
// Purpose : Called when the pickup is collected in a vehicle. Triggers the in car collection actions and gives the pickup rewards to the ped.
|
|
// Parameters : pPed - the ped collecting the pickup
|
|
// Returns : None
|
|
void CPickup::CollectInCar(CPed* pPed)
|
|
{
|
|
if (AssertVerify(pPed) &&
|
|
AssertVerify(m_pPickupData) &&
|
|
AssertVerify(m_pPickupData->GetCollectableInBoat() || m_pPickupData->GetCollectableInPlane() || GetAllowCollectionInVehicle()))
|
|
{
|
|
for (u32 i=0; i<m_pPickupData->GetNumInCarActions(); i++)
|
|
{
|
|
const CPickupActionData* pAction = m_pPickupData->GetInCarAction(i);
|
|
|
|
if (AssertVerify(pAction))
|
|
{
|
|
pAction->Apply(this, pPed);
|
|
}
|
|
}
|
|
|
|
scriptDebugf3(">>> Pickup of type %d collected in car <<<\n", GetPickupHash());
|
|
|
|
GiveRewards(pPed);
|
|
|
|
if (ms_shareVehicleWeaponPickupsAmongstPassengers && m_pPickupData->GetShareWithPassengers())
|
|
{
|
|
GiveRewardsToPassengers(pPed);
|
|
}
|
|
|
|
Collect(pPed);
|
|
}
|
|
}
|
|
|
|
// Name : CollectOnShot
|
|
// Purpose : Called when the pickup is being shot. Triggers the on shot collection actions and gives the pickup rewards to the ped.
|
|
// Parameters : pPed - the ped collecting the pickup
|
|
// Returns : None
|
|
void CPickup::CollectOnShot(CPed* pPed)
|
|
{
|
|
if (AssertVerify(pPed) &&
|
|
AssertVerify(m_pPickupData) &&
|
|
AssertVerify(m_pPickupData->GetCollectableOnShot()))
|
|
{
|
|
for (u32 i=0; i<m_pPickupData->GetNumOnShotActions(); i++)
|
|
{
|
|
const CPickupActionData* pAction = m_pPickupData->GetOnShotAction(i);
|
|
|
|
if (AssertVerify(pAction))
|
|
{
|
|
pAction->Apply(this, pPed);
|
|
}
|
|
}
|
|
|
|
scriptDebugf3(">>> Pickup of type %d collected on shot <<<\n", GetPickupHash());
|
|
|
|
GiveRewards(pPed);
|
|
Collect(pPed);
|
|
}
|
|
}
|
|
|
|
// Name : GiveRewards
|
|
// Purpose : Called when the pickup is being collected. Gives the pickup goodies to the ped.
|
|
// Parameters : pPed - the ped collecting the pickup
|
|
// Returns : None
|
|
void CPickup::GiveRewards(CPed* pPed)
|
|
{
|
|
scriptDebugf3(">>> Rewards given for pickup of type %d <<<\n", GetPickupHash());
|
|
|
|
if (AssertVerify(pPed) &&
|
|
AssertVerify(m_pPickupData))
|
|
{
|
|
u32 numRewards = m_pPickupData->GetNumRewards();
|
|
|
|
for (u32 i=0; i<numRewards; i++)
|
|
{
|
|
const CPickupRewardData* pReward = m_pPickupData->GetReward(i);
|
|
|
|
if (AssertVerify(pReward))
|
|
{
|
|
if (CPickupManager::IsSuppressionFlagSet( pReward->GetType() ))
|
|
{
|
|
scriptDebugf3(">>> ** Couldn't give reward type %d, it is supressed! ** \n <<<", pReward->GetType() );
|
|
}
|
|
else if (pReward->CanGive(this, pPed))
|
|
{
|
|
pReward->Give(this, pPed);
|
|
}
|
|
else
|
|
{
|
|
scriptDebugf3(">>> ** Couldn't give reward type %d, CanGive() returned false ** \n <<<", pReward->GetType());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::GiveRewardsToPassengers(CPed* pPed)
|
|
{
|
|
PlayerFlags passengerFlags = 0;
|
|
|
|
if (AssertVerify(pPed))
|
|
{
|
|
if (pPed->GetIsInVehicle() && pPed->GetMyVehicle())
|
|
{
|
|
for (u32 i=0; i<pPed->GetMyVehicle()->GetSeatManager()->GetMaxSeats(); i++)
|
|
{
|
|
CPed* pPassenger = pPed->GetMyVehicle()->GetSeatManager()->GetPedInSeat(i);
|
|
|
|
if (pPassenger &&
|
|
pPassenger != pPed &&
|
|
pPassenger->GetNetworkObject() &&
|
|
pPassenger->IsAPlayerPed())
|
|
{
|
|
passengerFlags |= (1<<pPassenger->GetNetworkObject()->GetPlayerOwner()->GetPhysicalPlayerIndex());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (passengerFlags != 0 &&
|
|
AssertVerify(m_pPickupData) &&
|
|
AssertVerify(m_pPickupData->GetShareWithPassengers()))
|
|
{
|
|
u32 numRewards = m_pPickupData->GetNumRewards();
|
|
|
|
for (u32 i=0; i<numRewards; i++)
|
|
{
|
|
const CPickupRewardData* pReward = m_pPickupData->GetReward(i);
|
|
|
|
if (AssertVerify(pReward) &&
|
|
!CPickupManager::IsSuppressionFlagSet( pReward->GetType() ) &&
|
|
pReward->CanGiveToPassengers(this))
|
|
{
|
|
CGivePickupRewardsEvent::Trigger(passengerFlags, pReward->GetHash());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Name : Collect
|
|
// Purpose : Called when the pickup is being collected by any method.
|
|
// Parameters : pPed - the ped collecting the pickup
|
|
// Returns : None
|
|
void CPickup::Collect(CPed* pPed)
|
|
{
|
|
scriptDebugf3(">>> Pickup of type %d collected <<<\n", GetPickupHash());
|
|
|
|
if (AssertVerify(!IsFlagSet(PF_Collected)))
|
|
{
|
|
SetCollected();
|
|
|
|
bool bIsPetrolCan = false;
|
|
const u32 uPickupWeaponHash = GetPickupData()->GetFirstWeaponReward();
|
|
if (uPickupWeaponHash != 0)
|
|
{
|
|
const CWeaponInfo* pPickupWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uPickupWeaponHash);
|
|
if (pPickupWeaponInfo && pPickupWeaponInfo->GetGroup() == WEAPONGROUP_PETROLCAN)
|
|
{
|
|
bIsPetrolCan = true;
|
|
}
|
|
}
|
|
|
|
// Clear the help message
|
|
if(bIsPetrolCan && (strcmp(CHelpMessage::GetMessageText(HELP_TEXT_SLOT_STANDARD), TheText.Get("PU_JER_HLP")) == 0 || strcmp(CHelpMessage::GetMessageText(HELP_TEXT_SLOT_STANDARD), TheText.Get("PU_FER_HLP")) == 0))
|
|
{
|
|
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
|
|
}
|
|
|
|
// Clear the help message
|
|
if(GetPickupData()->GetHash() == PICKUP_JETPACK && strcmp(CHelpMessage::GetMessageText(HELP_TEXT_SLOT_STANDARD), TheText.Get("PU_JET_HLP")) == 0)
|
|
{
|
|
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
|
|
}
|
|
|
|
if (m_pPlacement)
|
|
{
|
|
// inform the manager
|
|
CPickupManager::RegisterPickupCollection(m_pPlacement, pPed);
|
|
}
|
|
else
|
|
{
|
|
// Add network script event for the pickup event.
|
|
if(pPed && pPed->IsPlayer() && !IsFlagSet(PF_Portable))
|
|
{
|
|
u32 modelHash = 0;
|
|
|
|
CBaseModelInfo *modelInfo = GetBaseModelInfo();
|
|
if(AssertVerify(modelInfo))
|
|
{
|
|
modelHash = modelInfo->GetHashKey();
|
|
}
|
|
|
|
int objectId = NETWORK_INVALID_OBJECT_ID;
|
|
if (GetNetworkObject())
|
|
{
|
|
objectId = GetNetworkObject()->GetObjectID();
|
|
}
|
|
else
|
|
{
|
|
CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
if (pScriptExtension && pScriptExtension->GetScriptInfo())
|
|
{
|
|
objectId = pScriptExtension->GetScriptInfo()->GetObjectId();
|
|
}
|
|
}
|
|
|
|
if (pPed->GetNetworkObject())
|
|
{
|
|
// *** THIS IS NOT A NETWORK EVENT!! Should be on AI queue ***
|
|
CEventNetworkPlayerCollectedAmbientPickup pickupEvent(*pPed->GetNetworkObject()->GetPlayerOwner(),
|
|
GetPickupHash(),
|
|
objectId,
|
|
GetAmount(),
|
|
modelHash,
|
|
IsFlagSet(PF_PlayerGift),
|
|
IsFlagSet(PF_DroppedByPed),
|
|
(GetAmountCollected() > 0) ? GetAmountCollected() : GetAmount(),
|
|
CTheScripts::GetGUIDFromEntity(*this));
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
else
|
|
{
|
|
// *** THIS IS NOT A NETWORK EVENT!! Should be on AI queue ***
|
|
CEventNetworkPlayerCollectedAmbientPickup pickupEvent(GetPickupHash(),
|
|
objectId,
|
|
GetAmount(),
|
|
modelHash,
|
|
IsFlagSet(PF_PlayerGift),
|
|
IsFlagSet(PF_DroppedByPed),
|
|
(GetAmountCollected() > 0) ? GetAmountCollected() : GetAmount(),
|
|
CTheScripts::GetGUIDFromEntity(*this));
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
}
|
|
|
|
// some pickups are attached to the ped after collection
|
|
if (IsFlagSet(PF_Portable))
|
|
{
|
|
AttachPortablePickupToPed(pPed, "Local collection");
|
|
}
|
|
else if (!NetworkUtils::IsNetworkCloneOrMigrating(this)) // clones will be cleared up by their owner
|
|
{
|
|
// there is no placement for this pickup, so it destroys itself
|
|
Destroy();
|
|
}
|
|
else if (GetNetworkObject())
|
|
{
|
|
SafeCast(CNetObjPickup, GetNetworkObject())->DestroyWhenLocal();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::SetRemotePendingCollection(CPed& ped)
|
|
{
|
|
static const unsigned REMOTE_PENDING_COLLECTION_TIMER = 2000;
|
|
|
|
if (m_pendingCollectionType == COLLECT_INVALID)
|
|
{
|
|
m_pendingCollectionType = COLLECT_REMOTE;
|
|
m_pendingCollector = &ped;
|
|
|
|
// hide the pickup when pending collection (ie. pretend it is collected because it is about to be). This is done so that the pickup disappears
|
|
// immediately when someone is trying to collect it).
|
|
Hide();
|
|
}
|
|
|
|
if (m_pendingCollectionType == COLLECT_REMOTE)
|
|
{
|
|
m_pendingCollectionTimer = REMOTE_PENDING_COLLECTION_TIMER;
|
|
}
|
|
}
|
|
|
|
// Name : ProcessCollectionResponse
|
|
// Purpose : Called from a network event when we get a reply for a collection request from the machine which controls the pickup
|
|
// Parameters : bCollected - if true, collection was successful
|
|
void CPickup::ProcessCollectionResponse(const netPlayer* fromPlayer, bool bCollected)
|
|
{
|
|
CPed* pCollector = m_pendingCollector.Get();
|
|
eCollectionType collectionType = m_pendingCollectionType;
|
|
bool bMapPickup = GetPlacement() ? GetPlacement()->GetIsMapPlacement() : false;
|
|
|
|
CNetGamePlayer* pCurrOwner = GetNetworkObject() ? GetNetworkObject()->GetPlayerOwner() : NULL;
|
|
|
|
// if collection is rejected but the pickup has migrated, try again with the new owner
|
|
if (!bMapPickup && !bCollected && fromPlayer && pCurrOwner && pCurrOwner != fromPlayer && !pCurrOwner->IsLocal() && m_pendingCollector)
|
|
{
|
|
CRequestPickupEvent::Trigger(m_pendingCollector, this);
|
|
return;
|
|
}
|
|
|
|
// have to clear these here, because the collection will destroy the pickup, which will call CNetObjPickup::CanDelete. This will fail because
|
|
// it will still think the pickup is pending collection.
|
|
ClearPendingCollection();
|
|
|
|
if (IsFlagSet(PF_Portable) && pCollector && pCollector->GetPlayerInfo())
|
|
{
|
|
if (pCollector->GetPlayerInfo()->PortablePickupPending)
|
|
{
|
|
NetworkInterface::GetObjectManagerLog().Log("PortablePickupPending set to false: process collection response\n");
|
|
}
|
|
|
|
pCollector->GetPlayerInfo()->PortablePickupPending = false;
|
|
}
|
|
|
|
// the pickup may already be collected by our player if we received an attachment update for it before the request collection event response
|
|
if (bCollected && !IsFlagSet(PF_Collected) && pCollector)
|
|
{
|
|
switch (collectionType)
|
|
{
|
|
case COLLECT_ONFOOT:
|
|
CollectOnFoot(pCollector);
|
|
break;
|
|
case COLLECT_INCAR:
|
|
CollectInCar(pCollector);
|
|
break;
|
|
case COLLECT_ONSHOT:
|
|
CollectOnShot(pCollector);
|
|
break;
|
|
default:
|
|
Assert(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPickup::OnPedAttachment(CPed* pPed)
|
|
{
|
|
#if GTA_REPLAY
|
|
if(CReplayMgr::IsReplayInControlOfWorld())
|
|
return;
|
|
#endif // GTA_REPLAY
|
|
|
|
SetFlag(PF_Collected);
|
|
ClearFlag(PF_Inaccessible);
|
|
ClearFlag(PF_LastAccessiblePosHasValidGround);
|
|
ClearFlag(PF_LyingOnFixedObject);
|
|
ClearFlag(PF_LyingOnUnFixedObject);
|
|
ClearFlag(PF_WarpToAccessibleLocation);
|
|
ClearFlag(PF_SearchingForAccessibleLocation);
|
|
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (pLog && GetNetworkObject() && IsFlagSet(PF_Portable))
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "ATTACHING_PORTABLE_PICKUP", GetNetworkObject()->GetLogName());
|
|
pLog->WriteDataValue("Attached to", "%s", pPed->GetNetworkObject() ? pPed->GetNetworkObject()->GetLogName() : "??");
|
|
}
|
|
|
|
// assume that the location the ped is in starts as a valid location
|
|
if (!IsNetworkClone())
|
|
{
|
|
UpdateLastAccessibleLocation(*pPed, true);
|
|
}
|
|
|
|
if (GetPickupData()->GetInvisibleWhenCarried())
|
|
{
|
|
Hide(true);
|
|
}
|
|
|
|
if (pPed->IsAPlayerPed())
|
|
{
|
|
if (!pPed->IsNetworkClone() && pPed->GetPlayerInfo())
|
|
{
|
|
pPed->GetPlayerInfo()->PortablePickupCollected(GetModelIndex());
|
|
}
|
|
|
|
CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
|
|
if (pScriptExtension)
|
|
{
|
|
u32 modelHash = 0;
|
|
|
|
CBaseModelInfo *modelInfo = GetBaseModelInfo();
|
|
if(AssertVerify(modelInfo))
|
|
{
|
|
modelHash = modelInfo->GetHashKey();
|
|
}
|
|
|
|
if (pPed->GetNetworkObject() && GetNetworkObject())
|
|
{
|
|
CEventNetworkPlayerCollectedPortablePickup pickupEvent(*pPed->GetNetworkObject()->GetPlayerOwner(), pScriptExtension->GetScriptInfo()->GetObjectId(), GetNetworkObject()->GetObjectID(), modelHash);
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
else
|
|
{
|
|
CEventNetworkPlayerCollectedPortablePickup pickupEvent(pScriptExtension->GetScriptInfo()->GetObjectId(), modelHash);
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If we are attaching the pickup from the local player, inform the network object, which keeps track of pending attachments.
|
|
// This is to avoid the network object immediately detaching the pickup if it has no pending pickup info
|
|
if (pPed->IsLocalPlayer())
|
|
{
|
|
CNetObjPickup* pPickupNetObj = static_cast<CNetObjPickup*>(GetNetworkObject());
|
|
|
|
if (pPickupNetObj && pPed->IsLocalPlayer())
|
|
{
|
|
pPickupNetObj->PortablePickupLocallyAttached();
|
|
}
|
|
}
|
|
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_HasPortablePickupAttached, true);
|
|
|
|
ClearDroppedInWater();
|
|
|
|
ClearFlag(PF_DroppedInInterior);
|
|
|
|
m_pendingCarrier = NULL;
|
|
|
|
m_nObjectFlags.bFloater = false;
|
|
|
|
if (GetNetworkObject() && !IsNetworkClone())
|
|
{
|
|
// forcibly send out an update with the attachment state a.s.a.p
|
|
GetNetworkObject()->GetSyncTree()->Update(GetNetworkObject(), GetNetworkObject()->GetActivationFlags(), netInterface::GetSynchronisationTime());
|
|
GetNetworkObject()->GetSyncTree()->ForceSendOfSyncUpdateNodes(SERIALISEMODE_FORCE_SEND_OF_DIRTY, GetNetworkObject()->GetActivationFlags(), GetNetworkObject());
|
|
}
|
|
}
|
|
|
|
void CPickup::OnPedDetachment(CPed* pPed)
|
|
{
|
|
ClearFlag(PF_Collected);
|
|
ClearFlag(PF_DetachWhenLocal);
|
|
ClearFlag(PF_PlacedOnGround);
|
|
ClearFlag(PF_LastAccessiblePosHasValidGround);
|
|
|
|
// make sure the pickup is not still set invisible by the gameplay module (this can happen when attached to the local player who is sniping)
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, true);
|
|
// make sure the pickup is not still set invisible by the respawn module (this can happen when being detached from a respawning player)
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_RESPAWN, true);
|
|
// make sure the pickup is not still set invisible by the script module (this can happen when being detached from an invisible player)
|
|
if (!IsFlagSet(PF_HiddenByScript))
|
|
{
|
|
SetIsVisibleForModule(SETISVISIBLE_MODULE_SCRIPT, true);
|
|
}
|
|
|
|
if (GetPickupData()->GetInvisibleWhenCarried() && !IsFlagSet(PF_WarpToAccessibleLocation))
|
|
{
|
|
Expose();
|
|
}
|
|
|
|
if (pPed->IsAPlayerPed())
|
|
{
|
|
if (!pPed->IsNetworkClone() && pPed->GetPlayerInfo())
|
|
{
|
|
pPed->GetPlayerInfo()->PortablePickupDropped(GetModelIndex());
|
|
}
|
|
|
|
CScriptEntityExtension* pScriptExtension = GetExtension<CScriptEntityExtension>();
|
|
|
|
if (pScriptExtension && pScriptExtension->GetScriptInfo())
|
|
{
|
|
if (pPed->GetNetworkObject() && GetNetworkObject())
|
|
{
|
|
CEventNetworkPlayerDroppedPortablePickup pickupEvent(*pPed->GetNetworkObject()->GetPlayerOwner(), pScriptExtension->GetScriptInfo()->GetObjectId(), GetNetworkObject()->GetObjectID());
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
else
|
|
{
|
|
CEventNetworkPlayerDroppedPortablePickup pickupEvent(pScriptExtension->GetScriptInfo()->GetObjectId());
|
|
GetEventScriptNetworkGroup()->Add(pickupEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsNetworkClone())
|
|
{
|
|
// hack to get clones to go to the right position after detachment. This is to fix 370348
|
|
GetNetworkObject()->GetNetBlender()->SetLastSyncMessageTime(NetworkInterface::GetNetworkTime());
|
|
}
|
|
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_HasPortablePickupAttached, false);
|
|
}
|
|
|
|
bool CPickup::PlaceOnGroundProperly(float fMaxRange, bool bAlign, float UNUSED_PARAM(heightOffGround), bool bIncludeWater, bool bUpright, bool *pInWater, bool bIncludeObjects, bool useExtendedProbe)
|
|
{
|
|
bool success = false;
|
|
bool inWater = false;
|
|
|
|
if (bIncludeWater && !pInWater)
|
|
{
|
|
pInWater = &inWater;
|
|
}
|
|
|
|
if (GetIsInInterior() || IsFlagSet(PF_LyingOnFixedObject))
|
|
{
|
|
bIncludeObjects = true;
|
|
}
|
|
|
|
if (bIncludeWater && IsFlagSet(PF_UnderwaterPickup))
|
|
{
|
|
bIncludeWater = false;
|
|
}
|
|
|
|
useExtendedProbe = false;
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
for(int i = 0; i < m_ExtendedProbeCount; i++)
|
|
{
|
|
if(gnetVerifyf(i < MAX_NUM_EXTENDED_PROBE_AREAS, "Extended probe count(%d) exceeded maximum allowed(%d)", m_ExtendedProbeCount, MAX_NUM_EXTENDED_PROBE_AREAS))
|
|
{
|
|
Vector3 diff = pickupPos - m_AllExtendedProbeAreas[i].m_Position;
|
|
if(diff.Mag2() < m_AllExtendedProbeAreas[i].m_RadiusSqrd)
|
|
{
|
|
useExtendedProbe = true;
|
|
gnetDebug1("PICKUP_USES_EXTENDED_PROBE - %s Because of Area pos: %f, %f, %f. Area Radius Sqr: %.f", GetLogName(), m_AllExtendedProbeAreas[i].m_Position.GetX(), m_AllExtendedProbeAreas[i].m_Position.GetY(), m_AllExtendedProbeAreas[i].m_Position.GetZ(), m_AllExtendedProbeAreas[i].m_RadiusSqrd);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
success = CObject::PlaceOnGroundProperly(fMaxRange, bAlign, m_heightOffGround, bIncludeWater, bUpright, pInWater, bIncludeObjects, useExtendedProbe);
|
|
|
|
if (success)
|
|
{
|
|
// B*3823574 - No idea if this is correct or not but sometimes pickups have been intentionally removed from the physics level and never added back in.
|
|
// seems like a network or general pickup issue, we'll just hack this in here and hope if fixes the bug and doesn't break anything else
|
|
if( GetCurrentPhysicsInst() &&
|
|
!GetCurrentPhysicsInst()->IsInLevel() )
|
|
{
|
|
AddPhysics();
|
|
}
|
|
|
|
if (IsAlwaysFixed())
|
|
{
|
|
// fixed pickups become unfixed in water so they can float to the surface
|
|
if (bIncludeWater && pInWater && *pInWater)
|
|
{
|
|
DropInWater();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Active physics after placing the pickup on the ground. This fixes the pickup floating in the air since the bounding box size is bigger than the object's actual size.
|
|
ActivatePhysics();
|
|
}
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (pLog && GetNetworkObject())
|
|
{
|
|
NetworkLogUtils::WriteLogEvent(*pLog, "PICKUP_PLACE_ON_GROUND", GetNetworkObject()->GetLogName());
|
|
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
if (success)
|
|
{
|
|
pLog->WriteDataValue("Placed at", "%f, %f, %f", pickupPos.x, pickupPos.y, pickupPos.z);
|
|
|
|
if (WasDroppedInWater())
|
|
{
|
|
pLog->WriteDataValue("In water", "true");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pLog->WriteDataValue("**FAILED**", "%f, %f, %f", pickupPos.x, pickupPos.y, pickupPos.z);
|
|
pLog->WriteDataValue("Include water", "%s", bIncludeWater?"TRUE":"FALSE");
|
|
}
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
return success;
|
|
}
|
|
|
|
void CPickup::MoveToAccessibleLocation(bool bUseLastLocation)
|
|
{
|
|
// the maximum distance a portable pickup is allowed to travel away from its last accessible position before a new one is found
|
|
static const float MAX_INACCESSIBLE_RANGE_SQR = 40.0f*40.0f;
|
|
|
|
Vector3 pickupPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
|
|
Vector3 diff = pickupPos - m_lastAccessibleLocation;
|
|
|
|
netLoggingInterface* pLog = GetNetworkLog();
|
|
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("MoveToAccessibleLocation", "true");
|
|
}
|
|
|
|
if (bUseLastLocation || diff.Mag2() < MAX_INACCESSIBLE_RANGE_SQR)
|
|
{
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Using last accessible pos", "True");
|
|
}
|
|
|
|
bool bFoundGround = IsFlagSet(PF_LastAccessiblePosHasValidGround);
|
|
|
|
if (!IsFlagSet(PF_LastAccessiblePosHasValidGround))
|
|
{
|
|
float groundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, m_lastAccessibleLocation.x, m_lastAccessibleLocation.y, m_lastAccessibleLocation.z+1.5f, &bFoundGround);
|
|
|
|
if (bFoundGround)
|
|
{
|
|
if(pLog)
|
|
{
|
|
pLog->WriteDataValue("Found ground", "%f", groundZ);
|
|
}
|
|
|
|
m_lastAccessibleLocation.z = groundZ + m_heightOffGround;
|
|
}
|
|
}
|
|
|
|
FindSuitablePortablePickupDropLocation(m_lastAccessibleLocation);
|
|
|
|
ClearFlag(PF_Inaccessible);
|
|
}
|
|
else
|
|
{
|
|
SetFlag(PF_WarpToAccessibleLocation);
|
|
ClearFlag(PF_SearchingForAccessibleLocation);
|
|
|
|
// hide the pickup until it is repositioned
|
|
Hide();
|
|
|
|
if (pLog)
|
|
{
|
|
pLog->WriteDataValue("Searching for new accessible pos", "True");
|
|
}
|
|
}
|
|
}
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
const char *CPickup::GetCanCollectFailureString(unsigned failureCode)
|
|
{
|
|
switch (failureCode)
|
|
{
|
|
case PCC_NONE:
|
|
Assertf(0, "CPickup::CanCollect failed to return a valid failure code");
|
|
return "Not specified";
|
|
case PCC_COLLECTABLE_ON_FOOT:
|
|
return "Pickup only collectable on foot";
|
|
case PCC_COLLECTABLE_IN_VEHICLE:
|
|
return "Pickup only collectable in a vehicle";
|
|
case PCC_NOT_DRIVER:
|
|
return "The ped is not the driver of the vehicle";
|
|
case PCC_WRONG_VEHICLE:
|
|
return "This type of vehicle is not permitted to collect the pickup";
|
|
case PCC_ALREADY_COLLECTED:
|
|
return "The pickup is already flagged as collected";
|
|
case PCC_PED_ENTERING_VEHICLE:
|
|
return "The ped is entering a vehicle";
|
|
case PCC_FADED_OUT_AMBIENT:
|
|
return "The ambient pickup has faded out";
|
|
case PCC_PED_IS_DEAD:
|
|
return "The ped is dead";
|
|
case PCC_PENDING_COLLECTION:
|
|
return "The pickup is pending collection";
|
|
case PCC_HIDDEN:
|
|
return "The pickup is hidden";
|
|
case PCC_WRONG_TEAM:
|
|
return "The player is on the wrong team";
|
|
case PCC_PORTABLE_PICKUP_PENDING:
|
|
return "The player is already pending a portable pickup collection";
|
|
case PCC_PORTABLE_PICKUPS_BLOCKED:
|
|
return "The player has been prevented from collecting portable pickups by the script (SET_MAX_NUM_PORTABLE_PICKUPS_CARRIED_BY_PLAYER)";
|
|
case PCC_MAX_PORTABLE_PICKUPS:
|
|
return "The player is carrying the maximum number of portable pickups allowed";
|
|
case PCC_NON_PARTICIPANT:
|
|
return "The player is not a script participant";
|
|
case PCC_DETACHED_FROM_SCRIPT:
|
|
return "The pickup has been unregistered from the script that created it";
|
|
case PCC_COLLECTION_INSTANCE_PROHIBITED:
|
|
return "Collection of this individual pickup has been blocked by the script (PREVENT_COLLECTION_OF_PORTABLE_PICKUP)";
|
|
case PCC_COLLECTION_TYPE_PROHIBITED:
|
|
return "Collection of this type of pickup has been blocked by the script (SET_PLAYER_PERMITTED_TO_COLLECT_PICKUPS_OF_TYPE)";
|
|
case PCC_PICKUP_MODEL_TYPE_PROHIBITED:
|
|
return "Collection of pickups with this model have been blocked by the script (SET_LOCAL_PLAYER_PERMITTED_TO_COLLECT_PICKUPS_WITH_MODEL)";
|
|
case PCC_ATTACHED:
|
|
return "The pickup is attached";
|
|
case PCC_WEAPON_SWITCHING_BLOCKED:
|
|
return "The ped is temporarily blocking weapon switching";
|
|
case PCC_BUTTON_NOT_PRESSED:
|
|
return "The pickup requires a button press to pickup and the button is not pressed";
|
|
case PCC_REWARDS_CANT_BE_GIVEN:
|
|
return "The pickup rewards can't be given";
|
|
case PCC_CLONE_REQUEST_SENT:
|
|
return "The pickup is a clone: a collection request has been sent";
|
|
case PCC_MAP_REQUEST_SENT:
|
|
return "The pickup is a map pickup: a collection request has been sent";
|
|
case PCC_DOING_STEALTH_KILL:
|
|
return "The player is doing stealth kill or takedown.";
|
|
case PCC_IN_SYNCED_SCENE:
|
|
return "The pickup is part of a synchronised scene";
|
|
case PCC_ON_THE_PHONE:
|
|
return "The player is on the phone";
|
|
case PCC_DROPPED_FROM_LOCAL_PLAYER:
|
|
return "The pickup has just been dropped by the local player";
|
|
case PCC_NOT_INITIALISED:
|
|
return "The pickup has not been initialised";
|
|
case PCC_DIFFERENT_INTERIORS:
|
|
return "The pickup is in a different interior";
|
|
case PCC_DESTROYED:
|
|
return "The pickup is destroyed";
|
|
case PCC_INVALID_PED_MODEL:
|
|
return "The ped model for the player is invalid";
|
|
case PCC_PENDING_COLLECTIONS_EXCEEDED:
|
|
return "The max number of pending collections has been exceeded";
|
|
case PCC_NOT_ALLOWED_FOR_THIS_PLAYER:
|
|
return "Script blocked local player from collecting this pickup";
|
|
case PCC_FAR_FOR_ON_FOOT_COLLECTION:
|
|
return "Too far for on foot collection for this type of pickup";
|
|
default:
|
|
Assertf(0, "Unrecognised CanCollect failure code %d", failureCode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif // ENABLE_NETWORK_LOGGING
|
|
|
|
#if __DEV
|
|
template<> void fwPool<CPickup>::PoolFullCallback()
|
|
{
|
|
s32 size = GetSize();
|
|
int iIndex = 0;
|
|
while(size--)
|
|
{
|
|
CPickup* pPickup = GetSlot(size);
|
|
if(pPickup)
|
|
{
|
|
if (pPickup->GetNetworkObject())
|
|
{
|
|
Displayf("%i, \"%s\", Pos: (%.2f,%.2f,%.2f), Net obj: %s, Clone: %s, Pending owner change: %s, Placement: %s, Lifetime: %u, Destroyed: %s",
|
|
iIndex,
|
|
pPickup->GetPickupData()->GetName(),
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).x,
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).y,
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).z,
|
|
pPickup->GetNetworkObject()->GetLogName(),
|
|
pPickup->GetNetworkObject()->IsClone() ? "true" : "false",
|
|
pPickup->GetNetworkObject()->IsPendingOwnerChange() ? "true" : "false",
|
|
pPickup->GetPlacement() ? "true" : "false",
|
|
pPickup->GetLifeTime(),
|
|
pPickup->IsFlagSet(CPickup::PF_Destroyed) ? "true" : "false");
|
|
|
|
}
|
|
else
|
|
{
|
|
Displayf("%i, \"%s\", Pos: (%.2f,%.2f,%.2f), Placement: %s, Lifetime: %u, Destroyed: %s",
|
|
iIndex,
|
|
pPickup->GetPickupData()->GetName(),
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).x,
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).y,
|
|
VEC3V_TO_VECTOR3(pPickup->GetTransform().GetPosition()).z,
|
|
pPickup->GetPlacement() ? "true" : "false",
|
|
pPickup->GetLifeTime(),
|
|
pPickup->IsFlagSet(CPickup::PF_Destroyed) ? "true" : "false");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Displayf("%i, NULL pickup", iIndex);
|
|
}
|
|
iIndex++;
|
|
}
|
|
}
|
|
#endif // __DEV
|
|
|
|
|
|
// Sets rendering flags for the pick up.
|
|
void CPickup::SetForceAlphaAndUseAmbientScale()
|
|
{
|
|
bool bRenderDeferred = false;
|
|
|
|
CPickupPlacement *pPickupPlacement = GetPlacement();
|
|
if(pPickupPlacement)
|
|
{
|
|
bRenderDeferred = pPickupPlacement->GetForceDeferredModel();
|
|
}
|
|
|
|
SetForceAlphaAndUseAmbientScaleTraverseHierarchy(static_cast<CEntity*>(this), bRenderDeferred);
|
|
}
|
|
|
|
|
|
void CPickup::SetUseLightOverrideForGlow()
|
|
{
|
|
SetUseLightOverride(true);
|
|
SetUseLightOverrideTraverseHierarchy(static_cast<CEntity*>(GetChildAttachment()), true);
|
|
SetUseLightOverrideTraverseHierarchy(static_cast<CEntity*>(GetSiblingAttachment()), true);
|
|
}
|
|
|
|
|
|
void CPickup::ClearUseLightOverrideForGlow()
|
|
{
|
|
SetUseLightOverride(false);
|
|
SetUseLightOverrideTraverseHierarchy(static_cast<CEntity*>(this), false);
|
|
}
|
|
|
|
|
|
void CPickup::SetForceAlphaAndUseAmbientScaleTraverseHierarchy(CEntity *pEntity, bool bRenderDeferred)
|
|
{
|
|
if(pEntity)
|
|
{
|
|
CBaseModelInfo *pMdlInfo = pEntity->GetBaseModelInfo();
|
|
|
|
if(pMdlInfo)
|
|
pMdlInfo->SetUseAmbientScale(true);
|
|
|
|
if(bRenderDeferred)
|
|
{ // deferred mode:
|
|
pEntity->AssignBaseFlag(fwEntity::USE_SCREENDOOR, true);
|
|
pEntity->AssignBaseFlag(fwEntity::FORCE_ALPHA, false);
|
|
}
|
|
else
|
|
{ // forward mode:
|
|
pEntity->AssignBaseFlag(fwEntity::USE_SCREENDOOR, false);
|
|
pEntity->AssignBaseFlag(fwEntity::FORCE_ALPHA, true);
|
|
}
|
|
|
|
SetForceAlphaAndUseAmbientScaleTraverseHierarchy(static_cast<CEntity*>(pEntity->GetChildAttachment()), bRenderDeferred);
|
|
SetForceAlphaAndUseAmbientScaleTraverseHierarchy(static_cast<CEntity*>(pEntity->GetSiblingAttachment()), bRenderDeferred);
|
|
}
|
|
}
|
|
|
|
|
|
void CPickup::SetUseLightOverrideTraverseHierarchy(CEntity *pEntity, bool bValue)
|
|
{
|
|
if(pEntity)
|
|
{
|
|
Assertf(pEntity->GetIsTypeObject(), "CPickup::SetUseLightOverrideTraverseHierarchy()...Expecting an object.");
|
|
static_cast<CObject *>(pEntity)->SetUseLightOverride(bValue);
|
|
SetUseLightOverrideTraverseHierarchy(static_cast<CEntity*>(pEntity->GetChildAttachment()), bValue);
|
|
SetUseLightOverrideTraverseHierarchy(static_cast<CEntity*>(pEntity->GetSiblingAttachment()), bValue);
|
|
}
|
|
}
|
|
|
|
|
|
CPickup *CPickup::GetParentPickUp(CObject *pObject)
|
|
{
|
|
do
|
|
{
|
|
if(pObject->IsPickup())
|
|
return static_cast<CPickup *>(pObject);
|
|
} while ((pObject = static_cast<CObject *>(pObject->GetAttachParent())) != NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CPickup::SetIncludeProjectileFlag( bool includeProjectiles )
|
|
{
|
|
if( GetCurrentPhysicsInst() )
|
|
{
|
|
u16 levelIndex = GetCurrentPhysicsInst()->GetLevelIndex();
|
|
|
|
if( CPhysics::GetLevel()->IsInLevel( levelIndex ) )
|
|
{
|
|
u32 nOrigIncludeFlags = CPhysics::GetLevel()->GetInstanceIncludeFlags( levelIndex );
|
|
|
|
if( includeProjectiles )
|
|
{
|
|
nOrigIncludeFlags |= ArchetypeFlags::GTA_PROJECTILE_TYPE;
|
|
}
|
|
else
|
|
{
|
|
nOrigIncludeFlags &= ~ArchetypeFlags::GTA_PROJECTILE_TYPE;
|
|
}
|
|
|
|
CPhysics::GetLevel()->SetInstanceIncludeFlags( levelIndex, nOrigIncludeFlags );
|
|
}
|
|
}
|
|
|
|
m_includeProjectiles = includeProjectiles;
|
|
}
|