mirror of https://github.com/AxioDL/metaforce.git
Additional CPlayer implementations
This commit is contained in:
parent
4528a6b60a
commit
473103d41a
|
@ -17,6 +17,7 @@ struct ITweakPlayer : ITweak
|
|||
virtual float GetFluidGravAccel() const=0;
|
||||
virtual float GetHudLagAmount() const=0;
|
||||
virtual float GetOrbitNormalDistance() const=0;
|
||||
virtual float GetOrbitDistanceCompareSignificance() const=0;
|
||||
virtual uint32_t GetOrbitScreenBoxHalfExtentX(int zone) const=0;
|
||||
virtual uint32_t GetOrbitScreenBoxHalfExtentY(int zone) const=0;
|
||||
virtual uint32_t GetOrbitScreenBoxCenterX(int zone) const=0;
|
||||
|
@ -42,6 +43,7 @@ struct ITweakPlayer : ITweak
|
|||
virtual float GetX274() const=0; // x274
|
||||
virtual float GetX278() const=0; // x278
|
||||
virtual float GetPlayerBallHalfExtent() const=0; // x27c
|
||||
virtual float GetOrbitDistanceThreshold() const=0;
|
||||
virtual float GetGrappleSwingLength() const=0;
|
||||
virtual float GetGrappleSwingPeriod() const=0;
|
||||
virtual float GetGrapplePullSpeedMin() const=0;
|
||||
|
|
|
@ -290,7 +290,7 @@ void CTweakPlayer::read(athena::io::IStreamReader& __dna_reader)
|
|||
/* x1a0_ */
|
||||
x1a0_ = __dna_reader.readFloatBig();
|
||||
/* x1a4_ */
|
||||
x1a4_ = __dna_reader.readFloatBig();
|
||||
x1a4_orbitDistanceCompareSignificance = __dna_reader.readFloatBig();
|
||||
/* x1a8_orbitScreenBoxHalfExtentX[0] */
|
||||
x1a8_orbitScreenBoxHalfExtentX[0] = __dna_reader.readUint32Big();
|
||||
/* x1b0_orbitScreenBoxHalfExtentY[0] */
|
||||
|
@ -361,8 +361,8 @@ void CTweakPlayer::read(athena::io::IStreamReader& __dna_reader)
|
|||
x220_ = __dna_reader.readFloatBig();
|
||||
/* x224_scanningFrameSenseRange */
|
||||
x224_scanningFrameSenseRange = __dna_reader.readFloatBig();
|
||||
/* x2a0_ */
|
||||
x2a0_ = __dna_reader.readFloatBig();
|
||||
/* x2a0_orbitDistanceThreshold */
|
||||
x2a0_orbitDistanceThreshold = __dna_reader.readFloatBig();
|
||||
/* x2a4_grappleSwingLength */
|
||||
x2a4_grappleSwingLength = __dna_reader.readFloatBig();
|
||||
/* x2a8_grappleSwingPeriod */
|
||||
|
@ -730,7 +730,7 @@ void CTweakPlayer::write(athena::io::IStreamWriter& __dna_writer) const
|
|||
/* x1a0_ */
|
||||
__dna_writer.writeFloatBig(x1a0_);
|
||||
/* x1a4_ */
|
||||
__dna_writer.writeFloatBig(x1a4_);
|
||||
__dna_writer.writeFloatBig(x1a4_orbitDistanceCompareSignificance);
|
||||
/* x1a8_orbitScreenBoxHalfExtentX[0] */
|
||||
__dna_writer.writeUint32Big(x1a8_orbitScreenBoxHalfExtentX[0]);
|
||||
/* x1b0_orbitScreenBoxHalfExtentY[0] */
|
||||
|
@ -801,8 +801,8 @@ void CTweakPlayer::write(athena::io::IStreamWriter& __dna_writer) const
|
|||
__dna_writer.writeFloatBig(x220_);
|
||||
/* x224_scanningFrameSenseRange */
|
||||
__dna_writer.writeFloatBig(x224_scanningFrameSenseRange);
|
||||
/* x2a0_ */
|
||||
__dna_writer.writeFloatBig(x2a0_);
|
||||
/* x2a0_orbitDistanceThreshold */
|
||||
__dna_writer.writeFloatBig(x2a0_orbitDistanceThreshold);
|
||||
/* x2a4_grappleSwingLength */
|
||||
__dna_writer.writeFloatBig(x2a4_grappleSwingLength);
|
||||
/* x2a8_grappleSwingPeriod */
|
||||
|
@ -1215,7 +1215,7 @@ void CTweakPlayer::read(athena::io::YAMLDocReader& __dna_docin)
|
|||
/* x1a0_ */
|
||||
x1a0_ = __dna_docin.readFloat("x1a0_");
|
||||
/* x1a4_ */
|
||||
x1a4_ = __dna_docin.readFloat("x1a4_");
|
||||
x1a4_orbitDistanceCompareSignificance = __dna_docin.readFloat("x1a4_");
|
||||
/* x1a8_orbitScreenBoxHalfExtentX */
|
||||
size_t __x1a8_Count;
|
||||
if (auto v = __dna_docin.enterSubVector("x1a8_orbitScreenBoxHalfExtentX", __x1a8_Count))
|
||||
|
@ -1316,8 +1316,8 @@ void CTweakPlayer::read(athena::io::YAMLDocReader& __dna_docin)
|
|||
x220_ = __dna_docin.readFloat("x220_");
|
||||
/* x224_scanningFrameSenseRange */
|
||||
x224_scanningFrameSenseRange = __dna_docin.readFloat("x224_scanningFrameSenseRange");
|
||||
/* x2a0_ */
|
||||
x2a0_ = __dna_docin.readFloat("x2a0_");
|
||||
/* x2a0_orbitDistanceThreshold */
|
||||
x2a0_orbitDistanceThreshold = __dna_docin.readFloat("x2a0_orbitDistanceThreshold");
|
||||
/* x2a4_grappleSwingLength */
|
||||
x2a4_grappleSwingLength = __dna_docin.readFloat("x2a4_grappleSwingLength");
|
||||
/* x2a8_grappleSwingPeriod */
|
||||
|
@ -1721,7 +1721,7 @@ void CTweakPlayer::write(athena::io::YAMLDocWriter& __dna_docout) const
|
|||
/* x1a0_ */
|
||||
__dna_docout.writeFloat("x1a0_", x1a0_);
|
||||
/* x1a4_ */
|
||||
__dna_docout.writeFloat("x1a4_", x1a4_);
|
||||
__dna_docout.writeFloat("x1a4_", x1a4_orbitDistanceCompareSignificance);
|
||||
/* x1a8_orbitScreenBoxHalfExtentX */
|
||||
if (auto v = __dna_docout.enterSubVector("x1a8_orbitScreenBoxHalfExtentX"))
|
||||
{
|
||||
|
@ -1816,8 +1816,8 @@ void CTweakPlayer::write(athena::io::YAMLDocWriter& __dna_docout) const
|
|||
__dna_docout.writeFloat("x220_", x220_);
|
||||
/* x224_scanningFrameSenseRange */
|
||||
__dna_docout.writeFloat("x224_scanningFrameSenseRange", x224_scanningFrameSenseRange);
|
||||
/* x2a0_ */
|
||||
__dna_docout.writeFloat("x2a0_", x2a0_);
|
||||
/* x2a0_orbitDistanceThreshold */
|
||||
__dna_docout.writeFloat("x2a0_orbitDistanceThreshold", x2a0_orbitDistanceThreshold);
|
||||
/* x2a4_grappleSwingLength */
|
||||
__dna_docout.writeFloat("x2a4_grappleSwingLength", x2a4_grappleSwingLength);
|
||||
/* x2a8_grappleSwingPeriod */
|
||||
|
|
|
@ -67,7 +67,7 @@ struct CTweakPlayer : ITweakPlayer
|
|||
Value<float> x198_;
|
||||
Value<float> x19c_;
|
||||
Value<float> x1a0_;
|
||||
Value<float> x1a4_;
|
||||
Value<float> x1a4_orbitDistanceCompareSignificance;
|
||||
Value<atUint32> x1a8_orbitScreenBoxHalfExtentX[2];
|
||||
Value<atUint32> x1b0_orbitScreenBoxHalfExtentY[2];
|
||||
Value<atUint32> x1b8_orbitScreenBoxCenterX[2];
|
||||
|
@ -147,7 +147,7 @@ struct CTweakPlayer : ITweakPlayer
|
|||
Value<float> x294_;
|
||||
Value<float> x298_;
|
||||
Value<float> x29c_;
|
||||
Value<float> x2a0_;
|
||||
Value<float> x2a0_orbitDistanceThreshold;
|
||||
Value<float> x2a4_grappleSwingLength;
|
||||
Value<float> x2a8_grappleSwingPeriod;
|
||||
Value<float> x2ac_grapplePullSpeedMin;
|
||||
|
@ -183,6 +183,7 @@ struct CTweakPlayer : ITweakPlayer
|
|||
float GetFluidGravAccel() const { return xc8_fluidGravAccel; }
|
||||
float GetHudLagAmount() const { return x138_hudLagAmount; }
|
||||
float GetOrbitNormalDistance() const { return x180_orbitNormalDistance; }
|
||||
float GetOrbitDistanceCompareSignificance() const { return x1a4_orbitDistanceCompareSignificance; }
|
||||
uint32_t GetOrbitScreenBoxHalfExtentX(int zone) const { return x1a8_orbitScreenBoxHalfExtentX[zone]; }
|
||||
uint32_t GetOrbitScreenBoxHalfExtentY(int zone) const { return x1b0_orbitScreenBoxHalfExtentY[zone]; }
|
||||
uint32_t GetOrbitScreenBoxCenterX(int zone) const { return x1b8_orbitScreenBoxCenterX[zone]; }
|
||||
|
@ -207,6 +208,7 @@ struct CTweakPlayer : ITweakPlayer
|
|||
float GetX274() const { return x274_; }
|
||||
float GetX278() const { return x278_; }
|
||||
float GetPlayerBallHalfExtent() const { return x27c_playerBallHalfExtent; }
|
||||
float GetOrbitDistanceThreshold() const { return x2a0_orbitDistanceThreshold; }
|
||||
float GetGrappleSwingLength() const { return x2a4_grappleSwingLength; }
|
||||
float GetGrappleSwingPeriod() const { return x2a8_grappleSwingPeriod; }
|
||||
float GetGrapplePullSpeedMin() const { return x2ac_grapplePullSpeedMin; }
|
||||
|
|
|
@ -52,7 +52,7 @@ class CWorldState
|
|||
std::shared_ptr<CWorldLayerState> x14_layerState;
|
||||
|
||||
public:
|
||||
CWorldState(ResId id);
|
||||
explicit CWorldState(ResId id);
|
||||
CWorldState(CBitStreamReader& reader, ResId mlvlId, const CSaveWorld& saveWorld);
|
||||
ResId GetWorldAssetId() const { return x0_mlvlId; }
|
||||
void SetAreaId(TAreaId aid) { x4_areaId = aid; }
|
||||
|
|
|
@ -31,12 +31,16 @@ namespace urde
|
|||
static const CMaterialFilter SolidMaterialFilter =
|
||||
CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::Solid));
|
||||
|
||||
static const CMaterialFilter TargetingFilter =
|
||||
static const CMaterialFilter LineOfSightFilter =
|
||||
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid},
|
||||
{EMaterialTypes::ProjectilePassthrough,
|
||||
EMaterialTypes::ScanPassthrough,
|
||||
EMaterialTypes::Player});
|
||||
|
||||
static const CMaterialFilter OccluderFilter =
|
||||
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Occluder},
|
||||
{EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::Player});
|
||||
|
||||
static CModelData MakePlayerAnimRes(ResId resId, const zeus::CVector3f& scale)
|
||||
{
|
||||
return {CAnimRes(resId, 0, scale, 0, true), 1};
|
||||
|
@ -2037,7 +2041,7 @@ void CPlayer::UpdateGrappleState(const CFinalInput& input, CStateManager& mgr)
|
|||
{
|
||||
CRayCastResult result =
|
||||
mgr.RayStaticIntersection(eyePosition, playerToPoint.normalized(), playerToPoint.magnitude(),
|
||||
TargetingFilter);
|
||||
LineOfSightFilter);
|
||||
if (result.IsInvalid())
|
||||
{
|
||||
HolsterGun(mgr);
|
||||
|
@ -2170,7 +2174,7 @@ void CPlayer::UpdateGrappleState(const CFinalInput& input, CStateManager& mgr)
|
|||
{
|
||||
CRayCastResult result =
|
||||
mgr.RayStaticIntersection(eyePos, playerToPoint.normalized(), playerToPoint.magnitude(),
|
||||
TargetingFilter);
|
||||
LineOfSightFilter);
|
||||
if (result.IsValid())
|
||||
{
|
||||
BreakGrapple(EPlayerOrbitRequest::Twelve, mgr);
|
||||
|
@ -2442,12 +2446,189 @@ void CPlayer::UpdateOrbitableObjects(CStateManager& mgr)
|
|||
FindOrbitableObjects(nearList, x364_offScreenOrbitObjects, x330_orbitZone, x334_orbitType, mgr, false);
|
||||
}
|
||||
|
||||
TUniqueId CPlayer::FindBestOrbitableObject(const std::vector<TUniqueId>&, EPlayerZoneInfo, CStateManager& mgr) const
|
||||
TUniqueId CPlayer::FindBestOrbitableObject(const std::vector<TUniqueId>& ids,
|
||||
EPlayerZoneInfo info, CStateManager& mgr) const
|
||||
{
|
||||
zeus::CVector3f eyePos = GetEyePosition();
|
||||
zeus::CVector3f lookDir = x34_transform.basis[1].normalized();
|
||||
/* TODO: Finish */
|
||||
return {};
|
||||
float minEyeToOrbitMag = 10000.f;
|
||||
float minPosInBoxMagSq = 10000.f;
|
||||
TUniqueId bestId = kInvalidUniqueId;
|
||||
int vpWidthHalf = g_Viewport.x8_width / 2;
|
||||
int vpHeightHalf = g_Viewport.xc_height / 2;
|
||||
float boxLeft = (g_tweakPlayer->GetEnemyScreenBoxCenterX(int(info)) *
|
||||
g_Viewport.x8_width / 640 - vpWidthHalf) / vpWidthHalf;
|
||||
float boxTop = (g_tweakPlayer->GetEnemyScreenBoxCenterY(int(info)) *
|
||||
g_Viewport.xc_height / 448 - vpHeightHalf) / vpHeightHalf;
|
||||
|
||||
CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera();
|
||||
|
||||
for (TUniqueId id : ids)
|
||||
{
|
||||
if (TCastToPtr<CActor> act = mgr.ObjectById(id))
|
||||
{
|
||||
zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr);
|
||||
zeus::CVector3f eyeToOrbit = orbitPos - eyePos;
|
||||
float eyeToOrbitMag = eyeToOrbit.magnitude();
|
||||
zeus::CVector3f orbitPosScreen = fpCam->ConvertToScreenSpace(orbitPos);
|
||||
if (orbitPosScreen.z >= 0.f)
|
||||
{
|
||||
if (x310_orbitTargetId != id)
|
||||
{
|
||||
if (TCastToPtr<CScriptGrapplePoint> point = act.GetPtr())
|
||||
{
|
||||
if (x310_orbitTargetId != point->GetUniqueId())
|
||||
{
|
||||
if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) &&
|
||||
eyeToOrbitMag < minEyeToOrbitMag &&
|
||||
eyeToOrbitMag < g_tweakPlayer->GetOrbitDistanceThreshold())
|
||||
{
|
||||
rstl::reserved_vector<TUniqueId, 1024> nearList;
|
||||
TUniqueId intersectId = kInvalidUniqueId;
|
||||
eyeToOrbit.normalize();
|
||||
mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
OccluderFilter, act.GetPtr());
|
||||
eyeToOrbit.normalize();
|
||||
CRayCastResult result =
|
||||
mgr.RayWorldIntersection(intersectId, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
LineOfSightFilter, nearList);
|
||||
if (result.IsInvalid())
|
||||
{
|
||||
if (point->GetGrappleParameters().GetLockSwingTurn())
|
||||
{
|
||||
zeus::CVector3f pointToPlayer =
|
||||
GetTranslation() - point->GetTranslation();
|
||||
if (pointToPlayer.canBeNormalized())
|
||||
{
|
||||
pointToPlayer.z = 0.f;
|
||||
if (std::fabs(point->GetTransform().basis[1].normalized().
|
||||
dot(pointToPlayer.normalized())) <= M_SQRT1_2F)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bestId = act->GetUniqueId();
|
||||
float posInBoxLeft = orbitPosScreen.x - boxLeft;
|
||||
float posInBoxTop = orbitPosScreen.y - boxTop;
|
||||
minEyeToOrbitMag = eyeToOrbitMag;
|
||||
minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (minEyeToOrbitMag - eyeToOrbitMag > g_tweakPlayer->GetOrbitDistanceCompareSignificance() &&
|
||||
mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan)
|
||||
{
|
||||
rstl::reserved_vector<TUniqueId, 1024> nearList;
|
||||
TUniqueId bestId = kInvalidUniqueId;
|
||||
eyeToOrbit.normalize();
|
||||
mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
OccluderFilter, act.GetPtr());
|
||||
for (auto it = nearList.begin() ; it != nearList.end() ;)
|
||||
{
|
||||
if (CEntity* obj = mgr.ObjectById(*it))
|
||||
{
|
||||
if (obj->GetAreaIdAlways() != kInvalidAreaId)
|
||||
{
|
||||
if (mgr.GetNextAreaId() != obj->GetAreaIdAlways())
|
||||
{
|
||||
const CGameArea* area = mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways());
|
||||
CGameArea::EOcclusionState state =
|
||||
area->IsPostConstructed() ? area->GetOcclusionState() :
|
||||
CGameArea::EOcclusionState::Occluded;
|
||||
if (state == CGameArea::EOcclusionState::Occluded)
|
||||
{
|
||||
it = nearList.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it = nearList.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
eyeToOrbit.normalize();
|
||||
CRayCastResult result =
|
||||
mgr.RayWorldIntersection(bestId, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
LineOfSightFilter, nearList);
|
||||
if (result.IsInvalid())
|
||||
{
|
||||
bestId = act->GetUniqueId();
|
||||
float posInBoxLeft = orbitPosScreen.x - boxLeft;
|
||||
float posInBoxTop = orbitPosScreen.y - boxTop;
|
||||
minEyeToOrbitMag = eyeToOrbitMag;
|
||||
minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::fabs(eyeToOrbitMag - minEyeToOrbitMag) <
|
||||
g_tweakPlayer->GetOrbitDistanceCompareSignificance() ||
|
||||
mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan)
|
||||
{
|
||||
float posInBoxLeft = orbitPosScreen.x - boxLeft;
|
||||
float posInBoxTop = orbitPosScreen.y - boxTop;
|
||||
float posInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop;
|
||||
if (posInBoxMagSq < minPosInBoxMagSq)
|
||||
{
|
||||
rstl::reserved_vector<TUniqueId, 1024> nearList;
|
||||
TUniqueId bestId = kInvalidUniqueId;
|
||||
eyeToOrbit.normalize();
|
||||
mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
OccluderFilter, act.GetPtr());
|
||||
for (auto it = nearList.begin() ; it != nearList.end() ;)
|
||||
{
|
||||
if (CEntity* obj = mgr.ObjectById(*it))
|
||||
{
|
||||
if (obj->GetAreaIdAlways() != kInvalidAreaId)
|
||||
{
|
||||
if (mgr.GetNextAreaId() != obj->GetAreaIdAlways())
|
||||
{
|
||||
const CGameArea* area =
|
||||
mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways());
|
||||
CGameArea::EOcclusionState state =
|
||||
area->IsPostConstructed() ? area->GetOcclusionState() :
|
||||
CGameArea::EOcclusionState::Occluded;
|
||||
if (state == CGameArea::EOcclusionState::Occluded)
|
||||
{
|
||||
it = nearList.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it = nearList.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
eyeToOrbit.normalize();
|
||||
CRayCastResult result =
|
||||
mgr.RayWorldIntersection(bestId, eyePos, eyeToOrbit, eyeToOrbitMag,
|
||||
LineOfSightFilter, nearList);
|
||||
if (result.IsInvalid())
|
||||
{
|
||||
bestId = act->GetUniqueId();
|
||||
minPosInBoxMagSq = posInBoxMagSq;
|
||||
minEyeToOrbitMag = eyeToOrbitMag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestId;
|
||||
}
|
||||
|
||||
void CPlayer::FindOrbitableObjects(const rstl::reserved_vector<TUniqueId, 1024>& nearObjects,
|
||||
|
@ -2482,13 +2663,15 @@ void CPlayer::FindOrbitableObjects(const rstl::reserved_vector<TUniqueId, 1024>&
|
|||
pass = true;
|
||||
}
|
||||
|
||||
if (pass && (!act->GetDoTargetDistanceTest() || (orbitPos - eyePos).magnitude() <= GetOrbitMaxTargetDistance(mgr)))
|
||||
if (pass && (!act->GetDoTargetDistanceTest() ||
|
||||
(orbitPos - eyePos).magnitude() <= GetOrbitMaxTargetDistance(mgr)))
|
||||
listOut.push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CPlayer::WithinOrbitScreenBox(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone, EPlayerZoneType type) const
|
||||
bool CPlayer::WithinOrbitScreenBox(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone,
|
||||
EPlayerZoneType type) const
|
||||
{
|
||||
if (screenCoords.z >= 1.f)
|
||||
return false;
|
||||
|
|
2
nod
2
nod
|
@ -1 +1 @@
|
|||
Subproject commit d597400f4a83f9bb759b1c1ce44517c2fe7f886d
|
||||
Subproject commit 42ef3a7958b616eb606c43fbe0fc5fa443373358
|
2
specter
2
specter
|
@ -1 +1 @@
|
|||
Subproject commit ea1f1f7b93352baf8b0142bc9458b6a0f512775e
|
||||
Subproject commit cd448ae32819248448d04fa3ff050c2a82e1d190
|
Loading…
Reference in New Issue