-
Notifications
You must be signed in to change notification settings - Fork 0
feat(character): apply profile stats to bodies #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ public sealed class CharacterMemorySync : MonoBehaviour | |
| [SerializeField] private bool _syncOnStart = true; | ||
| [SerializeField] private bool _preferNakama = true; | ||
| [SerializeField] private bool _seedPrototypeMemory = true; | ||
| [SerializeField] private bool _applyProfileStatsToLocalPlayer = true; | ||
| [SerializeField] private bool _applyProfileEquipmentToLocalPlayer = true; | ||
| [SerializeField, TextArea] private string _prototypeMemory = | ||
| "JOY wants overnight prototype progress without client-side LLM secrets."; | ||
|
|
@@ -59,7 +60,7 @@ public IEnumerator Refresh() | |
| var soulName = ctx?.body?.soul?.name ?? "unknown"; | ||
| Debug.Log($"[CharacterMemorySync] Loaded Nakama soul '{soulName}'."); | ||
| }, Debug.LogWarning); | ||
| yield return ApplyProfileEquipmentWhenAvailable(); | ||
| yield return ApplyProfileToLocalPlayerWhenAvailable(); | ||
| yield break; | ||
| } | ||
| } | ||
|
|
@@ -70,7 +71,7 @@ public IEnumerator Refresh() | |
| var soulName = ctx?.body?.soul?.name ?? "unknown"; | ||
| Debug.Log($"[CharacterMemorySync] Loaded gateway prototype soul '{soulName}'."); | ||
| }, Debug.LogWarning); | ||
| yield return ApplyProfileEquipmentWhenAvailable(); | ||
| yield return ApplyProfileToLocalPlayerWhenAvailable(); | ||
| } | ||
|
|
||
| private IEnumerator WaitForAuthAttempt() | ||
|
|
@@ -95,36 +96,37 @@ private IEnumerator AddMemory(MemoryRecordDto memory) | |
| yield return _gateway.AddMemory(memory, ctx => _context = ctx, Debug.LogWarning); | ||
| } | ||
|
|
||
| private IEnumerator ApplyProfileEquipmentWhenAvailable() | ||
| private IEnumerator ApplyProfileToLocalPlayerWhenAvailable() | ||
| { | ||
| if (!_applyProfileEquipmentToLocalPlayer) | ||
| if (!_applyProfileEquipmentToLocalPlayer && !_applyProfileStatsToLocalPlayer) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| var equipmentVisualId = _context?.body?.equipment?.equipment_visual_id ?? EquipmentVisualCatalog.None; | ||
| if (equipmentVisualId == EquipmentVisualCatalog.None) | ||
| var body = _context?.body; | ||
| if (body == null) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| const float maxWaitSeconds = 10f; | ||
| const float retryIntervalSeconds = 0.25f; | ||
| var elapsed = 0f; | ||
| while (elapsed < maxWaitSeconds) | ||
| { | ||
| if (TryApplyProfileEquipment(equipmentVisualId)) | ||
| if (TryApplyProfileBody(body)) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| elapsed += Time.deltaTime; | ||
| yield return null; | ||
| elapsed += retryIntervalSeconds; | ||
| yield return new WaitForSeconds(retryIntervalSeconds); | ||
| } | ||
|
|
||
| Debug.LogWarning($"[CharacterMemorySync] No local state-authority player was ready for equipment visual {equipmentVisualId}."); | ||
| Debug.LogWarning("[CharacterMemorySync] No local state-authority player was ready for profile body sync."); | ||
| } | ||
|
|
||
| private static bool TryApplyProfileEquipment(int equipmentVisualId) | ||
| private bool TryApplyProfileBody(BodyProfileDto body) | ||
| { | ||
| var players = Object.FindObjectsByType<NetworkPlayer>(FindObjectsInactive.Exclude); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
| foreach (var player in players) | ||
|
|
@@ -134,20 +136,69 @@ private static bool TryApplyProfileEquipment(int equipmentVisualId) | |
| continue; | ||
| } | ||
|
|
||
| player.EquipmentVisualId = equipmentVisualId; | ||
| var loaders = player.GetComponentsInChildren<LocalVisualPrefabLoader>(includeInactive: true); | ||
| foreach (var loader in loaders) | ||
| if (_applyProfileStatsToLocalPlayer) | ||
| { | ||
| ApplyStats(player, body); | ||
| } | ||
|
|
||
| if (_applyProfileEquipmentToLocalPlayer) | ||
| { | ||
| loader.ApplyEquipmentVisual(equipmentVisualId); | ||
| ApplyEquipment(player, body); | ||
| } | ||
|
Comment on lines
+139
to
147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic attempts to mutate networked state from what appears to be a client-side sync script. In a server-authoritative model (Pillar 4), clients cannot set |
||
|
|
||
| Debug.Log($"[CharacterMemorySync] Applied profile equipment visual {equipmentVisualId} to local player."); | ||
| Debug.Log($"[CharacterMemorySync] Applied profile body stats and visuals to local player '{player.name}'."); | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static void ApplyStats(NetworkPlayer player, BodyProfileDto body) | ||
| { | ||
| var stats = body.stats ?? new CharacterStatsDto(); | ||
| var time = body.time ?? new BodyTimeDto(); | ||
| player.ApplyProfileStats( | ||
| stats.level, | ||
| stats.vitality, | ||
| stats.force, | ||
| stats.agility, | ||
| stats.focus, | ||
| stats.resilience, | ||
| stats.max_health, | ||
| stats.max_energy, | ||
| stats.attack_power, | ||
| stats.defense_power, | ||
| ToNetworkSeconds(time.remaining_seconds), | ||
| ToNetworkSeconds(time.max_seconds), | ||
| ToNetworkSeconds(time.danger_drain_rate)); | ||
| } | ||
|
|
||
| private static void ApplyEquipment(NetworkPlayer player, BodyProfileDto body) | ||
| { | ||
| var equipmentVisualId = body.equipment?.equipment_visual_id ?? EquipmentVisualCatalog.None; | ||
| if (equipmentVisualId == EquipmentVisualCatalog.None) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| player.ApplyProfileEquipment(equipmentVisualId); | ||
| var loaders = player.GetComponentsInChildren<LocalVisualPrefabLoader>(includeInactive: true); | ||
| foreach (var loader in loaders) | ||
| { | ||
| loader.ApplyEquipmentVisual(equipmentVisualId); | ||
| } | ||
| } | ||
|
|
||
| private static int ToNetworkSeconds(long seconds) | ||
| { | ||
| if (seconds <= 0) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| return seconds >= int.MaxValue ? int.MaxValue : (int)seconds; | ||
| } | ||
|
|
||
| private static bool IsLocalAuthoritativePlayer(NetworkPlayer player) | ||
| { | ||
| if (player == null || !player.HasStateAuthority) | ||
|
|
@@ -160,6 +211,16 @@ private static bool IsLocalAuthoritativePlayer(NetworkPlayer player) | |
| return true; | ||
| } | ||
|
|
||
| if (player.Runner.IsServer) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| if (!player.Runner.IsSharedModeMasterClient) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return player.Object.InputAuthority == PlayerRef.None || | ||
| player.Object.InputAuthority == player.Runner.LocalPlayer; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is called within a
whileloop inApplyProfileToLocalPlayerWhenAvailable, meaningObject.FindObjectsByTypeis executed every frame for up to 10 seconds until a player is found. This is inefficient. Consider caching the result or using a more direct way to reference the local authoritative player.