diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 48c0326..ede9580 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -383,6 +383,17 @@ public sealed class GameWindow : IDisposable private uint? _playerCurrentAnimCommand; private float _playerCurrentAnimSpeed = 1f; private uint? _playerMotionTableId; // server-sent MotionTable override for the player's character + + // K-fix7 (2026-04-26): server-authoritative Run + Jump skill values + // received from PlayerDescription. -1 = "not yet received, fall back + // to the controller's default (env-var or hardcoded 200/300)". + // Captured by the GameEventWiring.WireAll callback the moment PD + // arrives; pushed into _playerController via SetCharacterSkills both + // immediately (if the controller already exists from auto-entry) and + // again at every EnterPlayerModeNow so a player who Tab-toggles in + // and out keeps the right skills. + private int _lastSeenRunSkill = -1; + private int _lastSeenJumpSkill = -1; // K.1b: this field is RESERVED — written when entering / leaving player // mode and previously fed mouse-X into MovementInput.MouseDeltaX. Now // never consumed by MovementInput (mouse never drives character yaw — @@ -1198,7 +1209,28 @@ public sealed class GameWindow : IDisposable // the corresponding client-side state without further glue. AcDream.Core.Net.GameEventWiring.WireAll( _liveSession.GameEvents, Items, Combat, SpellBook, Chat, LocalPlayer, - TurbineChat); + TurbineChat, + onSkillsUpdated: (runSkill, jumpSkill) => + { + // K-fix7 (2026-04-26): cache the latest server-sent + // Run / Jump skill values so the next + // EnterPlayerModeNow can hand them to the new + // PlayerMovementController. Push immediately too, + // so a PD that arrives WHILE player mode is active + // (re-equip / log-in mid-session) updates the live + // controller. -1 from the wiring means "skill not + // present in this PD" — keep the previous cached + // value rather than overwriting with -1. + if (runSkill >= 0) _lastSeenRunSkill = runSkill; + if (jumpSkill >= 0) _lastSeenJumpSkill = jumpSkill; + if (_playerController is not null + && _lastSeenRunSkill >= 0 && _lastSeenJumpSkill >= 0) + { + _playerController.SetCharacterSkills( + _lastSeenRunSkill, _lastSeenJumpSkill); + Console.WriteLine($"player: applied server skills run={_lastSeenRunSkill} jump={_lastSeenJumpSkill}"); + } + }); // Phase I.7: subscribe to CombatState events and emit // retail-faithful "You hit X for Y damage" chat lines into @@ -5504,6 +5536,17 @@ public sealed class GameWindow : IDisposable } _playerController = new AcDream.App.Input.PlayerMovementController(_physicsEngine); + // K-fix7 (2026-04-26): if PlayerDescription already arrived, the + // server's Run / Jump skill values are cached here — push them + // into the freshly-constructed controller so the runRate / + // jump-arc formulas use real character data instead of the + // hardcoded ACDREAM_*_SKILL defaults. PD always arrives at + // login before auto-entry fires, so this branch normally hits. + if (_lastSeenRunSkill >= 0 && _lastSeenJumpSkill >= 0) + { + _playerController.SetCharacterSkills(_lastSeenRunSkill, _lastSeenJumpSkill); + Console.WriteLine($"live: {loggingTag} — applied server skills run={_lastSeenRunSkill} jump={_lastSeenJumpSkill}"); + } // Read the real step height from the player's Setup dat. if (_dats is not null && (playerEntity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u) { diff --git a/src/AcDream.Core.Net/GameEventWiring.cs b/src/AcDream.Core.Net/GameEventWiring.cs index f6c5b2b..a33b216 100644 --- a/src/AcDream.Core.Net/GameEventWiring.cs +++ b/src/AcDream.Core.Net/GameEventWiring.cs @@ -37,7 +37,18 @@ public static class GameEventWiring Spellbook spellbook, ChatLog chat, LocalPlayerState? localPlayer = null, - TurbineChatState? turbineChat = null) + TurbineChatState? turbineChat = null, + // K-fix7 (2026-04-26): server-sent skill update callback. Fires + // whenever PlayerDescription's skill table arrives with Run (24) + // or Jump (22) entries — we only care about those two for + // movement physics. Caller (GameWindow) plumbs this into the + // active PlayerMovementController + caches it for the next + // EnterPlayerModeNow construction. Each value is `init + ranks` + // (the holtburger-named "init" field is already the + // attribute-derived initial component, ranks is XP-bought + // additions; matches retail's GetCreatureSkill.Current minus + // augs / multipliers / vitae). + Action? onSkillsUpdated = null) { ArgumentNullException.ThrowIfNull(dispatcher); ArgumentNullException.ThrowIfNull(items); @@ -295,6 +306,33 @@ public static class GameEventWiring foreach (uint sid in p.Value.Spells.Keys) spellbook.OnSpellLearned(sid); + // K-fix7 (2026-04-26): push Run + Jump skill values to the + // PlayerMovementController so the runRate / jump-arc formulas + // use the SERVER's authoritative skill instead of our + // hardcoded ACDREAM_*_SKILL defaults. ACE Skill enum + // ordinals (Skill.cs:11-37): Jump = 22, Run = 24. The + // SkillEntry.Init field is the attribute-derived initial + // component; .Ranks is XP-bought additions. Their sum is + // the closest we get to ACE's CreatureSkill.Current short + // of porting the full Aug/Multiplier/Vitae chain. + if (onSkillsUpdated is not null) + { + int runSkill = -1; + int jumpSkill = -1; + foreach (var s in p.Value.Skills) + { + int total = (int)(s.Init + s.Ranks); + if (s.SkillId == 24u) runSkill = total; + else if (s.SkillId == 22u) jumpSkill = total; + } + if (runSkill >= 0 || jumpSkill >= 0) + { + if (dumpPd) + Console.WriteLine($"vitals: PD-skills run={runSkill} jump={jumpSkill}"); + onSkillsUpdated(runSkill, jumpSkill); + } + } + // Issue #7 — enchantment block: feed each entry into the // Spellbook with full StatMod data so EnchantmentMath can // aggregate buffs in vital-max calc (issue #6 lights up).