Skip to content

feat: 2D portrait fallback when the 3D model isn't loaded (12.0)#1844

Open
mjodheim wants to merge 2 commits into
tukui-org:mainfrom
mjodheim:feat/portrait-2d-fallback
Open

feat: 2D portrait fallback when the 3D model isn't loaded (12.0)#1844
mjodheim wants to merge 2 commits into
tukui-org:mainfrom
mjodheim:feat/portrait-2d-fallback

Conversation

@mjodheim

@mjodheim mjodheim commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a 2D portrait fallback for unit-frame portraits when the 3D model cannot be loaded on Midnight.

In instanced content, non-group unit identities are secret. Since 12.0.5, PlayerModel:SetUnit rejects those unit tokens and returns nil, so retrying the model load cannot succeed and can cause repeated work and stale portraits.

Approach

The portrait update is implemented through portrait.Override, leaving the bundled oUF element untouched.

  • Secret GUIDs and secret boolean returns are checked before comparison or branching.
  • Secret-identity units immediately receive a 2D cover frame above the PlayerModel; SetUnit is not called for them.
  • Non-secret units retain bounded asynchronous 3D model retries.
  • Retry state is cleared when the model is hidden or the unit changes.
  • UNIT_PORTRAIT_UPDATE forces a refresh while 3D portraits are enabled.

Validation

  • Tested in game with ungrouped NPC targets in instanced content: the 2D fallback displays correctly and the previous retry-related lag is gone.
  • git diff --check passes.

Limitations

A 3D model cannot be recovered for a secret-identity unit through the public API on 12.0.5+: PlayerModel:SetUnit intentionally rejects it. The 2D portrait is therefore the supported fallback until the identity is declassified.

Reimplements the oUF portrait Update via portrait.Override = UF.PortraitOverride
(oUF's Path calls `self.Portrait.Override or Update`), leaving the oUF library
file untouched. The override:

- detects secret-GUID units (UnitGUID secret inside instances) and secret
  booleans from UnitIsPlayer/UnitIsVisible/UnitIsConnected, treating them safely;
- shows a 2D cover frame (SetPortraitTexture, a child Frame above the PlayerModel)
  whenever the 3D model isn't loaded -- question mark, NPCs, not-visible units --
  instead of leaving a permanent question mark;
- hides the cover via an OnModelLoaded hook (only when a DIFFERENT model file id
  streams in, for secret units) and a bounded RetryPortraitModel ticker.

Fixes blank/stuck 3D portraits on targets/bosses/NPCs in 12.0 where models stream
asynchronously and UnitIsVisible returns false for NPCs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant