Player: Implement PlayerRecoverySafetyPoint and PlayerJudgeAbyssDeadStatus (#930)

This commit is contained in:
Spletz 2026-03-19 00:54:47 +11:00 committed by GitHub
parent e09c84f4fb
commit ffd385f51c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 487 additions and 56 deletions

View file

@ -123236,20 +123236,20 @@ Player/PlayerJudgeAbyssDeadStatus.o:
label:
- _ZN26PlayerJudgeAbyssDeadStatusC1EPK20PlayerJudgeSameNervePK25PlayerRecoverySafetyPoint
- _ZN26PlayerJudgeAbyssDeadStatusC2EPK20PlayerJudgeSameNervePK25PlayerRecoverySafetyPoint
status: NotDecompiled
status: Matching
- offset: 0x4589a0
size: 64
label: _ZNK26PlayerJudgeAbyssDeadStatus5judgeEv
status: NotDecompiled
status: Matching
- offset: 0x4589e0
size: 4
label: _ZN26PlayerJudgeAbyssDeadStatus5resetEv
status: NotDecompiled
status: Matching
lazy: true
- offset: 0x4589e4
size: 4
label: _ZN26PlayerJudgeAbyssDeadStatus6updateEv
status: NotDecompiled
status: Matching
lazy: true
Player/PlayerJudgeActiveCameraSubjective.o:
'.text':
@ -125472,79 +125472,79 @@ Player/PlayerRecoverySafetyPoint.o:
label:
- _ZN25PlayerRecoverySafetyPointC1EPKN2al9LiveActorEPK7HackCapRKNS0_13ActorInitInfoEPK13IUseDimensionPNS0_24CollisionPartsFilterBaseEPNS0_9HitSensorE
- _ZN25PlayerRecoverySafetyPointC2EPKN2al9LiveActorEPK7HackCapRKNS0_13ActorInitInfoEPK13IUseDimensionPNS0_24CollisionPartsFilterBaseEPNS0_9HitSensorE
status: NotDecompiled
status: Matching
- offset: 0x4604cc
size: 60
label: _ZN25PlayerRecoverySafetyPoint5resetEv
status: NotDecompiled
status: Matching
- offset: 0x460508
size: 264
label: _ZN25PlayerRecoverySafetyPoint14setSafetyPointERKN4sead7Vector3IfEES4_PKN2al7AreaObjE
status: NotDecompiled
status: Matching
- offset: 0x460610
size: 564
label: _ZN25PlayerRecoverySafetyPoint24noticeRequestSafetyPointERKN4sead7Vector3IfEES4_PKN2al7AreaObjE
status: NotDecompiled
status: Matching
- offset: 0x460844
size: 196
label: _ZN25PlayerRecoverySafetyPoint20noticeDangerousPointERKN4sead7Vector3IfEEb
status: NotDecompiled
status: Matching
- offset: 0x460908
size: 904
label: _ZN25PlayerRecoverySafetyPoint20slideLastSafetyPointEPN4sead7Vector3IfEES3_bRKS2_bPN2al24CollisionPartsFilterBaseE
status: NotDecompiled
status: Matching
- offset: 0x460c90
size: 108
label: _ZNK25PlayerRecoverySafetyPoint7isValidEv
status: NotDecompiled
status: Matching
- offset: 0x460cfc
size: 76
label: _ZNK25PlayerRecoverySafetyPoint16isEnableRecoveryEv
status: NotDecompiled
status: Matching
- offset: 0x460d48
size: 60
label: _ZNK25PlayerRecoverySafetyPoint14getSafetyPointEv
status: NotDecompiled
status: Matching
- offset: 0x460d84
size: 52
label: _ZNK25PlayerRecoverySafetyPoint21getSafetyPointGravityEv
status: NotDecompiled
status: Matching
- offset: 0x460db8
size: 56
label: _ZNK25PlayerRecoverySafetyPoint18getSafetyPointAreaEv
status: NotDecompiled
status: Matching
- offset: 0x460df0
size: 24
label: _ZN25PlayerRecoverySafetyPoint26updateRecoveryAreaValidityEv
status: NotDecompiled
status: Matching
- offset: 0x460e08
size: 8
label: _ZN25PlayerRecoverySafetyPoint15setRecoveryAreaEPKN2al7AreaObjE
status: NotDecompiled
status: Matching
- offset: 0x460e10
size: 32
label: _ZNK25PlayerRecoverySafetyPoint20isActiveRecoveryAreaEv
status: NotDecompiled
status: Matching
- offset: 0x460e30
size: 220
label: _ZN25PlayerRecoverySafetyPoint19checkInvalidateAreaEv
status: NotDecompiled
status: Matching
- offset: 0x460f0c
size: 364
label: _ZN25PlayerRecoverySafetyPoint13startRecoveryEf
status: NotDecompiled
status: Matching
- offset: 0x461078
size: 752
label: _ZN25PlayerRecoverySafetyPoint20updateRecoveryBubbleEv
status: NotDecompiled
status: Matching
- offset: 0x461368
size: 64
label: _ZN25PlayerRecoverySafetyPoint15startBubbleWaitEv
status: NotDecompiled
status: Matching
- offset: 0x4613a8
size: 88
label: _ZN25PlayerRecoverySafetyPoint11endRecoveryEv
status: NotDecompiled
status: Matching
Player/PlayerRippleGenerator.o:
'.text':
- offset: 0x461400

View file

@ -10,6 +10,16 @@ public:
virtual bool isInvalidTriangle(const Triangle& triangle) const = 0;
};
class TriangleFilterGroundOnly : public TriangleFilterBase {
public:
TriangleFilterGroundOnly(const sead::Vector3f& down) : mDown(down) {}
bool isInvalidTriangle(const Triangle& triangle) const override;
private:
const sead::Vector3f& mDown;
};
class TriangleFilterWallOnly : public TriangleFilterBase {
public:
TriangleFilterWallOnly(const sead::Vector3f& down) : mDown(down) {}

View file

@ -0,0 +1,18 @@
#include "Player/PlayerJudgeAbyssDeadStatus.h"
#include "Player/PlayerInfo.h"
#include "Player/PlayerJudgeSameNerve.h"
#include "Player/PlayerRecoverySafetyPoint.h"
#include "Util/JudgeUtil.h"
PlayerJudgeAbyssDeadStatus::PlayerJudgeAbyssDeadStatus(const PlayerJudgeSameNerve* nerve,
const PlayerRecoverySafetyPoint* safePoint)
: mNerve(nerve), mSafePoint(safePoint) {}
bool PlayerJudgeAbyssDeadStatus::judge() const {
return rs::isJudge(mNerve) && !mSafePoint->isValid();
}
void PlayerJudgeAbyssDeadStatus::reset() {}
void PlayerJudgeAbyssDeadStatus::update() {}

View file

@ -0,0 +1,22 @@
#pragma once
#include "Player/IJudge.h"
class PlayerJudgeSameNerve;
class PlayerRecoverySafetyPoint;
class PlayerJudgeAbyssDeadStatus : public IJudge {
public:
PlayerJudgeAbyssDeadStatus(const PlayerJudgeSameNerve* nerve,
const PlayerRecoverySafetyPoint* safePoint);
bool judge() const override;
void reset() override;
void update() override;
private:
const PlayerJudgeSameNerve* mNerve;
const PlayerRecoverySafetyPoint* mSafePoint;
};
static_assert(sizeof(PlayerJudgeAbyssDeadStatus) == 0x18);

View file

@ -0,0 +1,343 @@
#include "Player/PlayerRecoverySafetyPoint.h"
#include "Library/Area/AreaObj.h"
#include "Library/Area/AreaObjUtil.h"
#include "Library/Camera/CameraUtil.h"
#include "Library/Collision/CollisionPartsKeeperUtil.h"
#include "Library/Collision/CollisionPartsTriangle.h"
#include "Library/Collision/PartsInterpolator.h"
#include "Library/LiveActor/ActorActionFunction.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorFlagFunction.h"
#include "Library/LiveActor/ActorInitUtil.h"
#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/LiveActor/LiveActorFunction.h"
#include "Library/Math/MathUtil.h"
#include "Player/HackCap.h"
#include "System/GameDataUtil.h"
#include "Util/ActorDimensionUtil.h"
#include "Util/PlayerCollisionUtil.h"
#include "Util/PlayerUtil.h"
PlayerRecoverySafetyPoint::PlayerRecoverySafetyPoint(const al::LiveActor* actor,
const HackCap* hackCap,
const al::ActorInitInfo& initInfo,
const IUseDimension* dimension,
al::CollisionPartsFilterBase* colFilter,
al::HitSensor* hitSensor)
: mActor(actor), mHackCap(hackCap), mDimension(dimension), mColFilter(colFilter),
mHitSensor(hitSensor) {
mTractorBubble = new al::LiveActor("復帰泡");
al::initChildActorWithArchiveNameNoPlacementInfo(mTractorBubble, initInfo, "TractorBubble",
nullptr);
al::invalidateClipping(mTractorBubble);
mTractorBubble->makeActorDead();
mTractorBubble2D = new al::LiveActor("復帰泡2D");
al::initChildActorWithArchiveNameNoPlacementInfo(mTractorBubble2D, initInfo, "TractorBubble2D",
nullptr);
al::invalidateClipping(mTractorBubble2D);
mTractorBubble2D->makeActorDead();
}
void PlayerRecoverySafetyPoint::reset() {
mSafety3D.reset();
mSafety2D.reset();
mRecoveryArea = nullptr;
mDefaultSafetyPos = nullptr;
}
void PlayerRecoverySafetyPoint::setSafetyPoint(const sead::Vector3f& safetyPos,
const sead::Vector3f& safetyNormal,
const al::AreaObj* areaObj) {
if (!rs::isPlayer2D(mActor)) {
mSafety3D.set(safetyPos, safetyNormal, al::getGravity(mActor), areaObj);
} else if (rs::isIn2DArea(mDimension)) {
sead::Vector3f snapPos = safetyPos;
rs::calcSnap2DPosition(&snapPos, mDimension, safetyPos, 500.0f);
mSafety2D.set(snapPos, safetyNormal, al::getGravity(mActor), areaObj);
}
mDefaultSafetyPos = nullptr;
}
void PlayerRecoverySafetyPoint::noticeRequestSafetyPoint(const sead::Vector3f& safetyPos,
const sead::Vector3f& safetyNormal,
const al::AreaObj* areaObj) {
sead::Vector3f pos = safetyPos;
sead::Vector3f normal = safetyNormal;
sead::Vector3f* defaultPos = nullptr;
if (rs::isPlayer3D(mActor) && areaObj == nullptr) {
const sead::Vector3f& gravity = al::getGravity(mActor);
sead::Vector3f rayStart = safetyPos - gravity * 10.0f;
sead::Vector3f rayDir = gravity * 110.0f;
const al::ArrowHitInfo* hitInfo = nullptr;
al::TriangleFilterGroundOnly filter(gravity);
if (!alCollisionUtil::getFirstPolyOnArrow(mActor, &hitInfo, rayStart, rayDir, nullptr,
&filter))
return;
if (!rs::isEnableRecordSafetyPoint(&defaultPos, *hitInfo->hitInfo.data(), mHitSensor,
-gravity))
return;
pos = alCollisionUtil::getCollisionHitPos(hitInfo->hitInfo.data());
normal = alCollisionUtil::getCollisionHitNormal(hitInfo->hitInfo.data());
}
setSafetyPoint(pos, normal, areaObj);
mDefaultSafetyPos = defaultPos;
}
void PlayerRecoverySafetyPoint::noticeDangerousPoint(const sead::Vector3f& pos, bool skipIfValid) {
if (!rs::isPlayer2D(mActor)) {
slideLastSafetyPoint(&mSafety3D.safetyPos, &mSafety3D.safetyNormal, mSafety3D.hasSafety,
pos, skipIfValid, nullptr);
} else if (rs::isIn2DArea(mDimension)) {
sead::Vector3f snapPos = pos;
rs::calcSnap2DPosition(&snapPos, mDimension, pos, 500.0f);
slideLastSafetyPoint(&mSafety2D.safetyPos, &mSafety2D.safetyNormal, mSafety2D.hasSafety,
snapPos, skipIfValid, mColFilter);
}
}
// Function: PlayerRecoverySafetyPoint::slideLastSafetyPoint
// Description:
// If the player moves slightly from the last recorded safety point, this function
// adjusts ("slides") that point along the ground so it stays in a valid position.
//
// The slide distance is inversely proportional to the player's movement. Small
// movements cause a larger adjustment (~100 units), while larger movements
// cause little or no adjustment. This helps pull the safety point away from edges
// or dangerous geometry without letting it drift too far across the surface.
// I assume this is to allow some leeway for players when you're placed in a
// recovery position.
//
// When skipIfValid is true, a ray is first cast behind the current safety
// point to check whether it is still supported by valid ground. If it is, the
// function returns early and no sliding occurs.
void PlayerRecoverySafetyPoint::slideLastSafetyPoint(sead::Vector3f* safetyPos,
sead::Vector3f* safetyNormal, bool hasSafety,
const sead::Vector3f& lastSafetyPos,
bool skipIfValid,
al::CollisionPartsFilterBase* colFilter) {
if (!hasSafety)
return;
sead::Vector3f slideDir = {0.0f, 0.0f, 0.0f};
al::verticalizeVec(&slideDir, *safetyNormal, *safetyPos - lastSafetyPos);
f32 length = slideDir.length();
if (length > 100.0f || !al::tryNormalizeOrZero(&slideDir))
return;
const sead::Vector3f& gravity = al::getGravity(mActor);
sead::Vector3f rayDir = 2 * (gravity * 50.0f);
sead::Vector3f rayOffset = gravity * 50.0f;
const al::ArrowHitInfo* hitInfo = nullptr;
if (skipIfValid) {
sead::Vector3f rayStart = *safetyPos - (length * slideDir) - rayOffset;
al::TriangleFilterGroundOnly filter(gravity);
if (alCollisionUtil::getFirstPolyOnArrow(mActor, &hitInfo, rayStart, rayDir, colFilter,
&filter)) {
sead::Vector3f* outPos = nullptr;
if (rs::isEnableRecordSafetyPoint(&outPos, *hitInfo->hitInfo.data(), mHitSensor,
-gravity))
return;
}
}
// Inverse rubber band logic
// If the player hasn't moved much, assume the safety point might still be near a dangerous edge
// and correct it inward. This (presumably, from a design perspective) allows for some leeway
// when the player is placed in a recovery position in certain areas. However, if the player has
// moved a large distance, only adjust it a little bit. This prevents the safety point from
// sliding too far across the surface when the player has moved significantly.
length = sead::Mathf::clampMin(100.0f - length, 0.0f);
sead::Vector3f rayStart = *safetyPos + (length * slideDir) - rayOffset;
sead::Vector3f* outPos = nullptr;
if (alCollisionUtil::getFirstPolyOnArrow(mActor, &hitInfo, rayStart, rayDir, colFilter,
nullptr)) {
if (rs::isEnableRecordSafetyPoint(&outPos, *hitInfo->hitInfo.data(), mHitSensor,
-gravity) &&
rs::isCollisionCodeSafetyPoint(*hitInfo->hitInfo.data())) {
sead::Vector3f hitPos = alCollisionUtil::getCollisionHitPos(hitInfo->hitInfo.data());
sead::Vector3f hitNormal =
alCollisionUtil::getCollisionHitNormal(hitInfo->hitInfo.data());
al::TriangleFilterGroundOnly filter(al::getGravity(mActor));
if (!alCollisionUtil::getFirstPolyOnArrow(mActor, &hitInfo, *safetyPos - gravity * 5.0f,
hitPos - *safetyPos, colFilter, &filter)) {
*safetyPos = hitPos;
*safetyNormal = hitNormal;
}
}
}
}
bool PlayerRecoverySafetyPoint::isValid() const {
if (!isEnableRecovery())
return false;
if (rs::isPlayer2D(mActor))
return mSafety2D.hasSafety;
return mSafety3D.hasSafety;
}
bool PlayerRecoverySafetyPoint::isEnableRecovery() const {
return rs::isKidsMode(mActor) || isActiveRecoveryArea() || mHackCap->isEnableRescuePlayer();
}
const sead::Vector3f& PlayerRecoverySafetyPoint::getSafetyPoint() const {
if (mDefaultSafetyPos)
return *mDefaultSafetyPos;
if (rs::isPlayer2D(mActor))
return mSafety2D.safetyPos;
else
return mSafety3D.safetyPos;
}
const sead::Vector3f& PlayerRecoverySafetyPoint::getSafetyPointGravity() const {
if (rs::isPlayer2D(mActor))
return mSafety2D.gravity;
return mSafety3D.gravity;
}
const al::AreaObj* PlayerRecoverySafetyPoint::getSafetyPointArea() const {
if (rs::isPlayer2D(mActor))
return mSafety2D.area;
return mSafety3D.area;
}
void PlayerRecoverySafetyPoint::updateRecoveryAreaValidity() {
if (mRecoveryArea && !mRecoveryArea->isValid())
mRecoveryArea = nullptr;
}
void PlayerRecoverySafetyPoint::setRecoveryArea(const al::AreaObj* area) {
mRecoveryArea = area;
}
bool PlayerRecoverySafetyPoint::isActiveRecoveryArea() const {
return mRecoveryArea && mRecoveryArea->isValid();
}
void PlayerRecoverySafetyPoint::checkInvalidateArea() {
if (isValid() && al::isInAreaObj(mActor, "InvalidateRecoveryPosArea", getSafetyPoint()))
reset();
}
void PlayerRecoverySafetyPoint::startRecovery(f32 height) {
mBubbleHeight = height; // height will be 80.0 if the player is 3D, else 60.0.
if (mIsRecovering)
return;
mIsRecovering = true;
if (rs::isPlayer2D(mActor)) {
mTractorBubble2D->appear();
return;
}
mTractorBubble->appear();
al::offCollide(mTractorBubble);
al::startAction(mTractorBubble, "Appear");
updateRecoveryBubble();
if (mHackCap->isEnableRescuePlayer()) {
al::LiveActor* hat = al::getSubActor(mTractorBubble, "装着帽子");
hat->appear();
al::startAction(hat, "LockOn");
al::startHitReactionHitEffect(mHackCap, "プレイヤー救出出現",
al::getTrans(mTractorBubble) -
al::getGravity(mTractorBubble) * 160.0f);
al::LiveActor* eye = al::getSubActor(mTractorBubble, "");
eye->appear();
al::startAction(eye, "Appear");
}
}
void PlayerRecoverySafetyPoint::updateRecoveryBubble() {
if (rs::isPlayer2D(mActor)) {
if (al::isDead(mTractorBubble2D))
return;
sead::Vector3f up = -al::getGravity(mActor);
sead::Vector3f side = {0.0f, 0.0f, 0.0f};
al::calcSideDir(&side, mActor);
sead::Quatf quat = sead::Quatf::unit;
if (al::isParallelDirection(side, up, 0.01f))
al::calcQuat(&quat, mTractorBubble2D);
else
al::makeQuatSideUp(&quat, side, up);
sead::Vector3f upDir = {0.0f, 0.0f, 0.0f};
al::calcUpDir(&upDir, mActor);
al::resetPosition(mTractorBubble2D, al::getTrans(mActor) + upDir * mBubbleHeight);
al::updatePoseQuat(mTractorBubble2D, quat);
return;
}
if (al::isDead(mTractorBubble))
return;
if (al::isActionPlaying(mTractorBubble, "Disappear")) {
if (al::isActionEnd(mTractorBubble)) {
mTractorBubble->kill();
al::getSubActor(mTractorBubble, "装着帽子")->kill();
al::getSubActor(mTractorBubble, "")->kill();
}
return;
}
sead::Vector3f upDir = {0.0f, 0.0f, 0.0f};
al::calcUpDir(&upDir, mActor);
al::resetPosition(mTractorBubble, al::getTrans(mActor) + upDir * mBubbleHeight);
sead::Vector3f camFront = {0.0f, 0.0f, 0.0f};
al::calcCameraFront(&camFront, mActor, 0);
sead::Vector3f up = -al::getGravity(mActor);
if (al::isParallelDirection(camFront, up, 0.01f))
camFront = al::getCameraUp(mActor, 0);
camFront.negate();
sead::Quatf quat = sead::Quatf::unit;
al::makeQuatUpFront(&quat, up, camFront);
al::updatePoseQuat(mTractorBubble, quat);
}
void PlayerRecoverySafetyPoint::startBubbleWait() {
if (!rs::isPlayer2D(mActor))
al::startAction(mTractorBubble, "Wait");
}
void PlayerRecoverySafetyPoint::endRecovery() {
if (mIsRecovering) {
if (rs::isPlayer2D(mActor))
mTractorBubble2D->kill();
else
al::startAction(mTractorBubble, "Disappear");
mIsRecovering = false;
}
}

View file

@ -2,63 +2,101 @@
#include <math/seadVector.h>
class HackCap;
class IUseDimension;
namespace al {
class LiveActor;
class AreaObj;
struct ActorInitInfo;
class CollisionPartsFilterBase;
class AreaObj;
class HitSensor;
class ActorActionKeeper;
} // namespace al
class HackCap;
class IUseDimension;
struct SafetyPoint {
bool hasSafety = false;
sead::Vector3f safetyPos = {0.0f, 0.0f, 0.0f};
sead::Vector3f safetyNormal = {0.0f, 0.0f, 0.0f};
sead::Vector3f gravity = {0.0f, 0.0f, 0.0f};
const al::AreaObj* area = nullptr;
inline void reset() {
hasSafety = false;
safetyPos = {0.0f, 0.0f, 0.0f};
safetyNormal = {0.0f, 0.0f, 0.0f};
gravity = {0.0f, 0.0f, 0.0f};
area = nullptr;
}
inline void set(const sead::Vector3f& rSafetyPos, const sead::Vector3f& rSafetyNormal,
const sead::Vector3f& rGravity, const al::AreaObj* pAreaObj) {
hasSafety = true;
safetyPos.set(rSafetyPos);
safetyNormal.set(rSafetyNormal);
gravity.set(rGravity);
area = pAreaObj;
}
};
class PlayerRecoverySafetyPoint {
public:
PlayerRecoverySafetyPoint(const al::LiveActor*, const HackCap*, const al::ActorInitInfo&,
const IUseDimension*, al::CollisionPartsFilterBase*, al::HitSensor*);
PlayerRecoverySafetyPoint(const al::LiveActor* actor, const HackCap* hackCap,
const al::ActorInitInfo& initInfo, const IUseDimension* dimension,
al::CollisionPartsFilterBase* colFilter, al::HitSensor* hitSensor);
void reset();
void setSafetyPoint(const sead::Vector3f&, const sead::Vector3f&, const al::AreaObj*);
void noticeRequestSafetyPoint(const sead::Vector3f&, const sead::Vector3f&, const al::AreaObj*);
void noticeDangerousPoint(const sead::Vector3f&, bool);
void slideLastSafetyPoint(sead::Vector3f*, sead::Vector3f*, bool, const sead::Vector3f&, bool,
al::CollisionPartsFilterBase*);
void setSafetyPoint(const sead::Vector3f& safetyPos, const sead::Vector3f& safetyNormal,
const al::AreaObj* areaObj);
void noticeRequestSafetyPoint(const sead::Vector3f& safetyNormal, const sead::Vector3f&,
const al::AreaObj* areaObj);
void noticeDangerousPoint(const sead::Vector3f& pos, bool skipIfValid);
void slideLastSafetyPoint(sead::Vector3f* safetyPos, sead::Vector3f* safetyNormal,
bool hasSafety, const sead::Vector3f& lastSafetyPos, bool skipIfValid,
al::CollisionPartsFilterBase* colFilter);
bool isValid() const;
bool isEnableRecovery() const;
const sead::Vector3f& getSafetyPoint() const;
const sead::Vector3f& getSafetyPointGravity() const;
const sead::Vector3f& getSafetyPointArea() const;
const al::AreaObj* getSafetyPointArea() const;
void updateRecoveryAreaValidity();
void setRecoveryArea(const al::AreaObj*);
void setRecoveryArea(const al::AreaObj* area);
bool isActiveRecoveryArea() const;
void checkInvalidateArea();
void startRecovery(f32);
void startRecovery(f32 height);
void updateRecoveryBubble();
void startBubbleWait();
void endRecovery();
private:
al::LiveActor* mPlayer;
HackCap* mHackCap;
IUseDimension* mDimension;
al::CollisionPartsFilterBase* mCollisionPartsFilter;
const al::LiveActor* mActor;
const HackCap* mHackCap;
const IUseDimension* mDimension;
al::CollisionPartsFilterBase* mColFilter;
al::HitSensor* mHitSensor;
al::LiveActor* mBubble3D;
al::LiveActor* mBubble2D;
f32 mRecoveryTimer;
f32 _34;
bool mHasSafety3D;
sead::Vector3f mSafetyPos3D;
sead::Vector3f mSafetyNormal3D;
sead::Vector3f mGravity3D;
al::AreaObj* mArea3D;
bool mHasSafety2D;
sead::Vector3f mSafetyPos2D;
sead::Vector3f mSafetyNormal2D;
sead::Vector3f mGravity2D;
al::AreaObj* mArea2D;
bool mIsRecovering;
al::AreaObj* mRecoveryArea;
sead::Vector3f* mSafetyPoint;
al::LiveActor* mTractorBubble = nullptr;
al::LiveActor* mTractorBubble2D = nullptr;
f32 mBubbleHeight = 0.0f;
SafetyPoint mSafety3D;
SafetyPoint mSafety2D;
bool mIsRecovering = false;
const al::AreaObj* mRecoveryArea = nullptr;
sead::Vector3f* mDefaultSafetyPos = nullptr; // TODO: Determine actual use (currently unsure)
};
static_assert(sizeof(PlayerRecoverySafetyPoint) == 0xb8);