fix(combat): hydrate skills on spawn/switch, fix grapple anti-cheat dc, monster spawn & cutscenes, and update README feature matrix

This commit is contained in:
CAtlas0784 committed 2026-09-15 19:47:53 +07:00
1 parent 5d141e617f
commit 5e25d474c3
8 files changed
+158 -77

No files matched your search

@@ -573,6 +573,9 @@ internal sealed partial class GameRouter
CombatWeaponDefinition weapon,
CombatStyleDefinition style)
{
var state = GetWorldState(ctx);
state.CombatProfilePublished = true;
// Switching/individually styling one weapon must never overwrite the character-wide
// FightStyleInfo map. OnSwitchFightStyle publishes that map explicitly.
await ctx.NotifyAsync(MethodId.SyncPlayerAllSkillChargeData,
@@ -581,6 +584,7 @@ internal sealed partial class GameRouter
await PublishSkillBindings(ctx, unitId, weapon, style);
await ctx.NotifyAsync(MethodId.SyncSpiritLastUsedWeapon,
CombatCodec.SpiritLastUsedWeapon(templateId, weapon.InstanceId));
ctx.Session.Log.Info($"[COMBAT] profile published unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} skills={style.CommonSkill},{style.HeavyCommonSkill},{style.DodgeSkill},{style.ControlSkill},{weapon.ActiveSkill(style)},{weapon.UniqueSkill(style)}");
}
private static CombatStyleDefinition ResolveWeaponStyle(WorldEntryState state, CombatWeaponDefinition weapon)
@@ -66,4 +66,11 @@ internal sealed partial class GameRouter
{
conn.Log.Info("[SECURITY] DavinciCode notify absorbed");
}
[Handler(MethodId.GmDaVinciCode, HandlerPacketKind.Invoke)]
private static Task GmDaVinciCode(Connection conn, UxRpcMessage msg)
{
conn.Log.Info("[SECURITY] GmDaVinciCode invoke absorbed");
return conn.ReturnEmptyOkAsync(msg);
}
}
@@ -66,6 +66,21 @@ internal sealed partial class GameRouter
var unitId = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId;
var templateId = state.ActiveSpiritTemplateId != 0 ? state.ActiveSpiritTemplateId : Profile.InitialSpiritTemplateId;
if (!state.CombatProfilePublished)
{
var weapon = CombatCodec.Weapon(templateId, state.ActiveWeaponInstanceId)
?? CombatCodec.DefaultWeapon(templateId);
var style = ResolveWeaponStyle(state, weapon);
state.ActiveWeaponInstanceId = weapon.InstanceId;
state.ActiveFightStyleId = style.Id;
state.LastWeaponBySpirit[templateId] = weapon.InstanceId;
await PublishWeaponSnapshot(ctx, unitId, templateId, weapon.InstanceId);
await ctx.NotifyAsync(MethodId.SyncSpiritSwitchWeaponAction,
CombatCodec.SpiritSwitchWeapon(unitId, weapon.InstanceId));
await PublishSelectedWeaponProfile(ctx, unitId, templateId, weapon, style);
}
if (!state.AllBuildBuffsPublished)
{
await PublishSafeRuntimeBuffSnapshot4229938(ctx, unitId, templateId, "first-gameplay-movement");
@@ -78,6 +93,6 @@ internal sealed partial class GameRouter
await ctx.NotifyAsync(MethodId.SyncGamePause, WorldCodec.GamePause(false));
state.MovementCapabilityPublished = true;
state.FreeRoamReleased = true;
ctx.Session.Log.Info($"[GAMEPLAY-V7] first-movement post-load-buffs unit={unitId} template={templateId} actorPresentation=client-owned safeBuffs=true pause=false catalogBulk=false");
ctx.Session.Log.Info($"[GAMEPLAY-V7] first-movement post-load-buffs unit={unitId} template={templateId} combatProfile=true safeBuffs=true pause=false catalogBulk=false");
}
}
@@ -85,8 +85,9 @@ internal sealed partial class GameRouter
CombatCodec.SpiritLastUsedWeapon(templateId, weapon.InstanceId));
await ctx.NotifyAsync(MethodId.SyncSpiritSwitchWeaponAction,
CombatCodec.SpiritSwitchWeapon(unitId, weapon.InstanceId));
await PublishSelectedWeaponProfile(ctx, unitId, templateId, weapon, style);
ctx.Session.Log.Info($"[ACTOR-MIN] presentation unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} reason={reason} combatModules=false skills=false");
ctx.Session.Log.Info($"[ACTOR-MIN] presentation unit={unitId} template={templateId} weapon={weapon.TemplateId}/{weapon.InstanceId} style={style.Id} reason={reason} combatModules=true skills=true");
}
async Task PublishWeaponSnapshot(
@@ -175,11 +176,12 @@ internal sealed partial class GameRouter
// Keep the opening/loading barrier on the proven small traversal capability set.
// Never activate the entire BuffConfig as one player snapshot: it contains NPC/timeline actions.
var targetUnitId = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId;
var firstInstanceId = state.NextBuffInstanceId;
await ctx.NotifyAsync(MethodId.SyncUnitBuffList,
WorldCodec.UnitBuffList(Profile.InitialUnitId, WebTraversal.InitialCapabilityBuffIds, firstInstanceId));
WorldCodec.UnitBuffList(targetUnitId, WebTraversal.InitialCapabilityBuffIds, firstInstanceId));
state.NextBuffInstanceId += (uint)WebTraversal.InitialCapabilityBuffIds.Count;
ctx.Session.Log.Info($"[CAPABILITY] initial unit={Profile.InitialUnitId} count={WebTraversal.InitialCapabilityBuffIds.Count} loadingSafe=true allBuffsDeferred=true");
ctx.Session.Log.Info($"[CAPABILITY] initial unit={targetUnitId} count={WebTraversal.InitialCapabilityBuffIds.Count} loadingSafe=true allBuffsDeferred=true");
}
}
@@ -98,6 +98,7 @@ internal sealed partial class GameRouter
MethodId.GmAddEnemyByPlayer,
MethodId.GmTeleportXYZ,
MethodId.DavinciCode,
MethodId.GmDaVinciCode,
// Time of day: client-driven UI (accept + remember) + debug-panel slider push.
MethodId.AskPassingTime,