JumpChargeIsAllowed 0x00527a50 (CanJump→0x49 before posture; Fallen + Crouch..Sleeping→0x48), ChargeJump 0x005281c0 — now the ONLY place standing_longjump arms (grounded Contact+OnWalkable + forward Ready + no sidestep/turn, raw @305453-305466); the S2a-flagged side effect inside contact_allows_move is DELETED (J6) with regression pins that contact checks never touch the flag (the funnel's StandingLongJump branch is local-player scope; the controller wires ChargeJump in W4/W6 — remotes never charge client-side, no interim regression). jump_is_allowed 0x005282b0 full chain replaces the 15-line approximation: IsFullyConstrained→0x47 (new PhysicsBody stub, TS-35) BEFORE the pending-head peek (A2 ordering, pinned); head peek fires whenever the queue is non-empty (no ACE Count>1 gate; nonzero jump_error_code short-circuits the whole chain — test proves JumpStaminaCost is never consulted); retail entry shape verbatim (non-creature-weenie and gravity-off skip the ground gate; null physics obj → 0x24 NOT 8 per A10); charge → MotionAllowsJump(fwd) double-check → JumpStaminaCost gate (new IWeenieObject member, always-affordable PlayerWeenie stub — TS-5 extended). GetJumpVZ/GetLeaveGroundVelocity: epsilon corrected to retail's 0.000199999995f (was 0.001 — regression-pinned at extent 0.0005); A5 shape (clamp 1.0, weenie-null 10.0f); A6 momentum fallback fires only when ALL THREE components are sub-epsilon and overwrites all three with global→local(m_velocityVector). Jump 0x00528780: InterruptCurrentMovement no-op seam (TS-36, →R4 cancel_moveto), fires unconditionally; standing_longjump cleared ONLY on failure (success keeps it — pinned). IWeenieObject.IsThePlayer() (PlayerWeenie true) lands for W4's A3 dispatch. 51 new tests + 1 re-pin; superseded pre-W3 jump tests removed. Full suite: 3,623 passed. Registers: TS-5 extended, TS-35/36/37 added. Implemented by a dedicated agent against the W0-pinned spec; arming gates + entry shape verified against the quoted raw text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
3.1 KiB
C#
99 lines
3.1 KiB
C#
namespace AcDream.Core.Physics;
|
|
|
|
/// <summary>
|
|
/// IWeenieObject implementation for the local player. Provides skill-based
|
|
/// run rate and jump velocity calculations.
|
|
///
|
|
/// Formulas from decompiled acclient.exe, cross-referenced against
|
|
/// ACE MovementSystem.GetRunRate and MovementSystem.GetJumpHeight.
|
|
/// </summary>
|
|
public sealed class PlayerWeenie : IWeenieObject
|
|
{
|
|
private int _runSkill;
|
|
private int _jumpSkill;
|
|
private float _burden;
|
|
|
|
public PlayerWeenie(int runSkill = 0, int jumpSkill = 0, float burden = 0f)
|
|
{
|
|
_runSkill = runSkill;
|
|
_jumpSkill = jumpSkill;
|
|
_burden = burden;
|
|
}
|
|
|
|
public void SetSkills(int runSkill, int jumpSkill)
|
|
{
|
|
_runSkill = runSkill;
|
|
_jumpSkill = jumpSkill;
|
|
}
|
|
|
|
public void SetBurden(float burden) => _burden = burden;
|
|
|
|
public bool InqRunRate(out float rate)
|
|
{
|
|
rate = GetRunRate(_burden, _runSkill);
|
|
return true;
|
|
}
|
|
|
|
public bool InqJumpVelocity(float extent, out float vz)
|
|
{
|
|
float height = GetJumpHeight(_burden, _jumpSkill, extent);
|
|
vz = MathF.Sqrt(height * 19.6f);
|
|
return true;
|
|
}
|
|
|
|
public bool CanJump(float extent) => true; // burden/stamina checks deferred
|
|
|
|
/// <summary>
|
|
/// R3-W3 (W0-pins.md A3): the local player's weenie is THE player.
|
|
/// Feeds W4's <c>apply_current_movement</c>/<c>ReportExhaustion</c>
|
|
/// dual-dispatch gate — no consumer yet in W3.
|
|
/// </summary>
|
|
public bool IsThePlayer() => true;
|
|
|
|
/// <summary>
|
|
/// TS-5 (extended): stamina cost gating deferred pending stat plumbing —
|
|
/// always affordable, cost 0. Matches <see cref="CanJump"/>'s existing
|
|
/// always-true stance.
|
|
/// </summary>
|
|
public bool JumpStaminaCost(float extent, out int cost)
|
|
{
|
|
cost = 0;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// RunRate = (burdenMod * (runSkill / (runSkill + 200)) * 11 + 4) / 4
|
|
/// Capped at 4.5 when runSkill >= 800.
|
|
/// Source: decompiled + ACE MovementSystem.GetRunRate
|
|
/// </summary>
|
|
public static float GetRunRate(float burden, int runSkill)
|
|
{
|
|
if (runSkill >= 800) return 18f / 4f;
|
|
float loadMod = GetBurdenMod(burden);
|
|
return (loadMod * ((float)runSkill / (runSkill + 200) * 11f) + 4f) / 4f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// JumpHeight = burdenMod * (jumpSkill / (jumpSkill + 1300) * 22.2 + 0.05) * extent
|
|
/// Clamped to minimum 0.35m.
|
|
/// Source: decompiled + ACE MovementSystem.GetJumpHeight
|
|
/// </summary>
|
|
public static float GetJumpHeight(float burden, int jumpSkill, float extent)
|
|
{
|
|
extent = Math.Clamp(extent, 0f, 1f);
|
|
float loadMod = GetBurdenMod(burden);
|
|
float height = loadMod * ((float)jumpSkill / (jumpSkill + 1300f) * 22.2f + 0.05f) * extent;
|
|
return MathF.Max(height, 0.35f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encumbrance modifier: 1.0 when unloaded, linearly decreasing to 0 at 200%.
|
|
/// Source: decompiled + ACE EncumbranceSystem.GetBurdenMod
|
|
/// </summary>
|
|
public static float GetBurdenMod(float burden)
|
|
{
|
|
if (burden < 1f) return 1f;
|
|
if (burden < 2f) return 2f - burden;
|
|
return 0f;
|
|
}
|
|
}
|