Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots.

### Added

- Focused NPC dialog now reports response lifecycle in the UGUI chat panel:
waiting, retrying, answered, or failed after bounded DOS.AI retry.
- Player-to-NPC chat now uses bounded direct-reply retry in Unity when the
model path hits transient DOS.AI timeout or 5xx errors, while still
suppressing deterministic fallback speech so only model-authored NPC lines
Expand Down
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ Recommended views:
- [x] Player-to-NPC dialog now retries transient DOS.AI timeout or 5xx failures
a bounded number of times and keeps deterministic fallback speech out of the
conversation UI.
- [x] Focused NPC dialog now shows response lifecycle feedback in the chat panel
so a failed DOS.AI reply is visible instead of looking like a silent NPC.
- [ ] Real combat damage, enemy rewards, loot drops, quest progress, and player
time-loot from other users are not implemented yet.

Expand Down
2 changes: 2 additions & 0 deletions Unity/Assets/_SecondSpawn/Scripts/AI/PrototypeAgentBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ private IEnumerator PlayerChatResponseLoop()
string decisionError = null;
var request = BuildDecisionRequest();
SetBrainStatus("AI answering", new Color(0.72f, 0.82f, 0.95f), $"player chat attempt {_pendingPlayerChatAttemptCount}");
PrototypeNearbyNpcChatBox.TryNotifyFocusedNpcResponseStarted(AgentId, DisplayName, _pendingPlayerChatAttemptCount);
var stimulus = _pendingMessageFromNpc ? "npc_speech" : "player_chat";
Debug.Log($"[PrototypeAgentBrain] Player chat decision request agent={AgentId}, attempt={_pendingPlayerChatAttemptCount}, stimulus={stimulus}, allowed={string.Join(",", request.allowed ?? new string[0])}, message={request.world_snapshot?.last_player_message?.text}");
yield return NpcDecisionRequestScheduler.WaitForSlot(
Expand Down Expand Up @@ -1017,6 +1018,7 @@ private IEnumerator PlayerChatResponseLoop()
{
Debug.LogWarning($"[PrototypeAgentBrain] Player chat response exhausted model attempts agent={AgentId}, attempts={_pendingPlayerChatAttemptCount}");
SetBrainStatus("AI no answer", new Color(1f, 0.62f, 0.16f), "player chat model attempts exhausted");
PrototypeNearbyNpcChatBox.TryNotifyFocusedNpcResponseFailed(AgentId, DisplayName, "DOS.AI unavailable");
ClearPendingPlayerChat();
}

Expand Down
75 changes: 73 additions & 2 deletions Unity/Assets/_SecondSpawn/Scripts/AI/PrototypeNearbyNpcChatBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,24 @@ public static bool TryRouteFocusedNpcSpeech(string actorId, string displayName,
return chat != null && chat.TryAddFocusedNpcSpeech(actorId, displayName, text);
}

public static bool TryNotifyFocusedNpcResponseStarted(string actorId, string displayName, int attempt)
{
var chat = FindAnyObjectByType<PrototypeNearbyNpcChatBox>();
return chat != null && chat.TryMarkFocusedNpcResponseStarted(actorId, displayName, attempt);
}

public static bool TryNotifyFocusedNpcResponseFailed(string actorId, string displayName, string reason)
{
var chat = FindAnyObjectByType<PrototypeNearbyNpcChatBox>();
return chat != null && chat.TryMarkFocusedNpcResponseFailed(actorId, displayName, reason);
}

public sealed class DialogueLine
{
public string speaker;
public string text;
public bool is_player;
public bool is_system;
}

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
Expand Down Expand Up @@ -210,7 +223,7 @@ private IEnumerator SendNearbyMessage(string message, List<string> explicitActor
_status = nearbyActorIds.Count == 0
? "No nearby NPC heard that line."
: IsFocusedNpcRecipient(nearbyActorIds)
? $"Queued for {_focusedNpcDisplayName}."
? $"Waiting for {_focusedNpcDisplayName}."
: $"Queued for {nearbyActorIds.Count} nearby NPC{(nearbyActorIds.Count == 1 ? "" : "s")}.";
Debug.Log($"[PrototypeNearbyNpcChatBox] Player message route recipients={nearbyActorIds.Count}, focused={IsFocusedNpcRecipient(nearbyActorIds)}, message={Shorten(message, 80)}");

Expand Down Expand Up @@ -667,7 +680,8 @@ private void AddHistory(string speaker, string text, bool isPlayer)
{
speaker = safeSpeaker,
text = Shorten(text, 220),
is_player = isPlayer
is_player = isPlayer,
is_system = false
});

while (_history.Count > Mathf.Max(1, _historyLimit))
Expand Down Expand Up @@ -697,6 +711,63 @@ private bool TryAddFocusedNpcSpeech(string actorId, string displayName, string t
return true;
}

private bool TryMarkFocusedNpcResponseStarted(string actorId, string displayName, int attempt)
{
if (!IsMatchingFocusedNpc(actorId))
{
return false;
}

FocusNpc(ResolveBrain(actorId));
var safeName = string.IsNullOrWhiteSpace(displayName) ? FocusedNpcDisplayName : displayName.Trim();
var suffix = attempt > 1 ? $" retry {attempt}" : "";
_status = $"{safeName} is answering{suffix}.";
return true;
}

private bool TryMarkFocusedNpcResponseFailed(string actorId, string displayName, string reason)
{
if (!IsMatchingFocusedNpc(actorId))
{
return false;
}

FocusNpc(ResolveBrain(actorId));
var safeName = string.IsNullOrWhiteSpace(displayName) ? FocusedNpcDisplayName : displayName.Trim();
var safeReason = string.IsNullOrWhiteSpace(reason) ? "model unavailable" : reason.Trim();
_status = $"{safeName} could not answer: {safeReason}.";
AddSystemLine($"{safeName} could not reach DOS.AI after retrying. Try again in a moment.");
return true;
}

private bool IsMatchingFocusedNpc(string actorId)
{
return IsFocusedNpcActive() &&
!string.IsNullOrWhiteSpace(actorId) &&
string.Equals(actorId.Trim(), _focusedNpcActorId, System.StringComparison.OrdinalIgnoreCase);
}

private void AddSystemLine(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return;
}

_dialogueLines.Add(new DialogueLine
{
speaker = "System",
text = Shorten(text, 220),
is_player = false,
is_system = true
});

while (_dialogueLines.Count > Mathf.Max(1, _historyLimit))
{
_dialogueLines.RemoveAt(0);
}
}

private static bool ConsumeSubmitEventForChatField()
{
var current = Event.current;
Expand Down
8 changes: 8 additions & 0 deletions Unity/Assets/_SecondSpawn/Scripts/UI/NearbyNpcChatPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ private void Render()
builder.Append(line.text);
builder.Append(':');
builder.Append(line.is_player);
builder.Append(':');
builder.Append(line.is_system);
}

var state = builder.ToString();
Expand Down Expand Up @@ -327,6 +329,12 @@ private void RenderDialogueRows()
var firstIsPlayer = _chat.DialogueLines[0].is_player;
foreach (var line in _chat.DialogueLines)
{
if (line.is_system)
{
CreateSystemRow(line.text);
continue;
}

CreateMessageRow(line, line.is_player == firstIsPlayer);
}
}
Expand Down
Loading